mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-19 13:02:05 +01:00
127319a821
git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@11613 b35dd754-fafc-0310-a699-88a17e54d16e
1276 lines
42 KiB
Java
1276 lines
42 KiB
Java
/**
|
|
* $RCSfile: JingleSession.java,v $
|
|
* $Revision: 1.20 $
|
|
* $Date: 2007/07/18 18:29:21 $
|
|
*
|
|
* 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 java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Random;
|
|
|
|
import org.jivesoftware.smack.ConnectionListener;
|
|
import org.jivesoftware.smack.PacketListener;
|
|
import org.jivesoftware.smack.Connection;
|
|
import org.jivesoftware.smack.XMPPException;
|
|
import org.jivesoftware.smack.filter.PacketFilter;
|
|
import org.jivesoftware.smack.packet.IQ;
|
|
import org.jivesoftware.smack.packet.Packet;
|
|
import org.jivesoftware.smack.packet.PacketExtension;
|
|
import org.jivesoftware.smack.packet.XMPPError;
|
|
import org.jivesoftware.smackx.jingle.listeners.JingleListener;
|
|
import org.jivesoftware.smackx.jingle.listeners.JingleMediaListener;
|
|
import org.jivesoftware.smackx.jingle.listeners.JingleSessionListener;
|
|
import org.jivesoftware.smackx.jingle.listeners.JingleTransportListener;
|
|
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
|
|
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
|
|
import org.jivesoftware.smackx.jingle.media.MediaNegotiator;
|
|
import org.jivesoftware.smackx.jingle.media.MediaReceivedListener;
|
|
import org.jivesoftware.smackx.jingle.media.PayloadType;
|
|
import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
|
|
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.JingleError;
|
|
|
|
/**
|
|
* An abstract Jingle session. <p/> This class contains some basic properties of
|
|
* every Jingle session. However, the concrete implementation can be found in
|
|
* subclasses.
|
|
*
|
|
* @author Alvaro Saurin
|
|
* @author Jeff Williams
|
|
*/
|
|
public class JingleSession extends JingleNegotiator implements MediaReceivedListener {
|
|
|
|
private static final SmackLogger LOGGER = SmackLogger.getLogger(JingleSession.class);
|
|
|
|
// static
|
|
private static final HashMap sessions = new HashMap();
|
|
|
|
private static final Random randomGenerator = new Random();
|
|
|
|
// non-static
|
|
|
|
private String initiator; // Who started the communication
|
|
|
|
private String responder; // The other endpoint
|
|
|
|
private String sid; // A unique id that identifies this session
|
|
|
|
ConnectionListener connectionListener;
|
|
|
|
PacketListener packetListener;
|
|
|
|
PacketFilter packetFilter;
|
|
|
|
protected List<JingleMediaManager> jingleMediaManagers = null;
|
|
|
|
private boolean closed = false;
|
|
|
|
private JingleSessionState sessionState;
|
|
|
|
private List<ContentNegotiator> contentNegotiators;
|
|
|
|
private JingleSessionRequest sessionRequest;
|
|
|
|
private Connection connection;
|
|
|
|
private String sessionInitPacketID;
|
|
|
|
private Map<String, JingleMediaSession> mediaSessionMap;
|
|
|
|
/**
|
|
* Full featured JingleSession constructor
|
|
*
|
|
* @param conn
|
|
* the Connection which is used
|
|
* @param initiator
|
|
* the initiator JID
|
|
* @param responder
|
|
* the responder JID
|
|
* @param sessionid
|
|
* the session ID
|
|
* @param jingleMediaManager
|
|
* the jingleMediaManager
|
|
*/
|
|
public JingleSession(Connection conn, String initiator, String responder, String sessionid,
|
|
List<JingleMediaManager> jingleMediaManagers) {
|
|
super();
|
|
|
|
this.initiator = initiator;
|
|
this.responder = responder;
|
|
this.sid = sessionid;
|
|
this.jingleMediaManagers = jingleMediaManagers;
|
|
this.setSession(this);
|
|
this.connection = conn;
|
|
|
|
// Initially, we don't known the session state.
|
|
setSessionState(JingleSessionStateUnknown.getInstance());
|
|
|
|
contentNegotiators = new ArrayList<ContentNegotiator>();
|
|
mediaSessionMap = new HashMap<String, JingleMediaSession>();
|
|
|
|
// Add the session to the list and register the listeneres
|
|
registerInstance();
|
|
installConnectionListeners(conn);
|
|
}
|
|
|
|
/**
|
|
* JingleSession constructor (for an outgoing Jingle session)
|
|
*
|
|
* @param conn
|
|
* Connection
|
|
* @param initiator
|
|
* the initiator JID
|
|
* @param responder
|
|
* the responder JID
|
|
* @param jingleMediaManager
|
|
* the jingleMediaManager
|
|
*/
|
|
public JingleSession(Connection conn, JingleSessionRequest request, String initiator, String responder,
|
|
List<JingleMediaManager> jingleMediaManagers) {
|
|
this(conn, initiator, responder, generateSessionId(), jingleMediaManagers);
|
|
sessionRequest = request;
|
|
}
|
|
|
|
/**
|
|
* Get the session initiator
|
|
*
|
|
* @return the initiator
|
|
*/
|
|
public String getInitiator() {
|
|
return initiator;
|
|
}
|
|
|
|
public Connection getConnection() {
|
|
return connection;
|
|
}
|
|
|
|
/**
|
|
* Set the session initiator
|
|
*
|
|
* @param initiator
|
|
* the initiator to set
|
|
*/
|
|
public void setInitiator(String initiator) {
|
|
this.initiator = initiator;
|
|
}
|
|
|
|
/**
|
|
* Get the Media Manager of this Jingle Session
|
|
*
|
|
* @return
|
|
*/
|
|
public List<JingleMediaManager> getMediaManagers() {
|
|
return jingleMediaManagers;
|
|
}
|
|
|
|
/**
|
|
* Set the Media Manager of this Jingle Session
|
|
*
|
|
* @param jingleMediaManager
|
|
*/
|
|
public void setMediaManagers(List<JingleMediaManager> jingleMediaManagers) {
|
|
this.jingleMediaManagers = jingleMediaManagers;
|
|
}
|
|
|
|
/**
|
|
* Get the session responder
|
|
*
|
|
* @return the responder
|
|
*/
|
|
public String getResponder() {
|
|
return responder;
|
|
}
|
|
|
|
/**
|
|
* Set the session responder.
|
|
*
|
|
* @param responder
|
|
* the receptor to set
|
|
*/
|
|
public void setResponder(String responder) {
|
|
this.responder = responder;
|
|
}
|
|
|
|
/**
|
|
* Get the session ID
|
|
*
|
|
* @return the sid
|
|
*/
|
|
public String getSid() {
|
|
return sid;
|
|
}
|
|
|
|
/**
|
|
* Set the session ID
|
|
*
|
|
* @param sessionId
|
|
* the sid to set
|
|
*/
|
|
protected void setSid(String sessionId) {
|
|
sid = sessionId;
|
|
}
|
|
|
|
/**
|
|
* Generate a unique session ID.
|
|
*/
|
|
protected static String generateSessionId() {
|
|
return String.valueOf(Math.abs(randomGenerator.nextLong()));
|
|
}
|
|
|
|
/**
|
|
* Validate the state changes.
|
|
*/
|
|
|
|
public void setSessionState(JingleSessionState stateIs) {
|
|
|
|
LOGGER.debug("Session state change: " + sessionState + "->" + stateIs);
|
|
stateIs.enter();
|
|
sessionState = stateIs;
|
|
}
|
|
|
|
public JingleSessionState getSessionState() {
|
|
return sessionState;
|
|
}
|
|
|
|
/**
|
|
* Return true if all of the media managers have finished
|
|
*/
|
|
public boolean isFullyEstablished() {
|
|
boolean result = true;
|
|
for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
if (!contentNegotiator.isFullyEstablished())
|
|
result = false;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------------------------
|
|
// Receive section
|
|
// ----------------------------------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Process and respond to an incoming packet. <p/> This method is called
|
|
* from the packet listener dispatcher when a new packet has arrived. The
|
|
* method is responsible for recognizing the packet type and, depending on
|
|
* the current state, delivering it to the right event handler and wait for
|
|
* a response. The response will be another Jingle packet that will be sent
|
|
* to the other end point.
|
|
*
|
|
* @param iq
|
|
* the packet received
|
|
* @return the new Jingle packet to send.
|
|
* @throws XMPPException
|
|
*/
|
|
public synchronized void receivePacketAndRespond(IQ iq) throws XMPPException {
|
|
List<IQ> responses = new ArrayList<IQ>();
|
|
|
|
String responseId = null;
|
|
|
|
LOGGER.debug("Packet: " + iq.toXML());
|
|
|
|
try {
|
|
|
|
// Dispatch the packet to the JingleNegotiators and get back a list of the results.
|
|
responses.addAll(dispatchIncomingPacket(iq, null));
|
|
|
|
if (iq != null) {
|
|
responseId = iq.getPacketID();
|
|
|
|
// Send the IQ to each of the content negotiators for further processing.
|
|
// Each content negotiator may pass back a list of JingleContent for addition to the response packet.
|
|
|
|
for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
// If at this point the content negotiator isn't started, it's because we sent a session-init jingle
|
|
// packet from startOutgoing() and we're waiting for the other side to let us know they're ready
|
|
// to take jingle packets. (This packet might be a session-terminate, but that will get handled
|
|
// later.
|
|
if (!contentNegotiator.isStarted()) {
|
|
contentNegotiator.start();
|
|
}
|
|
responses.addAll(contentNegotiator.dispatchIncomingPacket(iq, responseId));
|
|
}
|
|
|
|
}
|
|
// Acknowledge the IQ reception
|
|
// Not anymore. The state machine generates an appropriate response IQ that
|
|
// gets sent back at the end of this routine.
|
|
//sendAck(iq);
|
|
|
|
} catch (JingleException e) {
|
|
// Send an error message, if present
|
|
JingleError error = e.getError();
|
|
if (error != null) {
|
|
responses.add(createJingleError(iq, error));
|
|
}
|
|
|
|
// Notify the session end and close everything...
|
|
triggerSessionClosedOnError(e);
|
|
}
|
|
|
|
// // If the response is anything other than a RESULT then send it now.
|
|
// if ((response != null) && (!response.getType().equals(IQ.Type.RESULT))) {
|
|
// getConnection().sendPacket(response);
|
|
// }
|
|
|
|
// Loop through all of the responses and send them.
|
|
for (IQ response : responses) {
|
|
sendPacket(response);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatch an incoming packet. The method is responsible for recognizing
|
|
* the packet type and, depending on the current state, delivering 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 List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException {
|
|
List<IQ> responses = new ArrayList<IQ>();
|
|
IQ response = null;
|
|
|
|
if (iq != null) {
|
|
if (iq.getType().equals(IQ.Type.ERROR)) {
|
|
// Process errors
|
|
// TODO getState().eventError(iq);
|
|
} else if (iq.getType().equals(IQ.Type.RESULT)) {
|
|
// Process ACKs
|
|
if (isExpectedId(iq.getPacketID())) {
|
|
|
|
// The other side provisionally accepted our session-initiate.
|
|
// Kick off some negotiators.
|
|
if (iq.getPacketID().equals(sessionInitPacketID)) {
|
|
startNegotiators();
|
|
}
|
|
removeExpectedId(iq.getPacketID());
|
|
}
|
|
} else if (iq instanceof Jingle) {
|
|
// It is not an error: it is a Jingle packet...
|
|
Jingle jin = (Jingle) iq;
|
|
JingleActionEnum action = jin.getAction();
|
|
|
|
// Depending on the state we're in we'll get different processing actions.
|
|
// (See Design Patterns AKA GoF State behavioral pattern.)
|
|
response = getSessionState().processJingle(this, jin, action);
|
|
}
|
|
}
|
|
|
|
if (response != null) {
|
|
// Save the packet id, for recognizing ACKs...
|
|
addExpectedId(response.getPacketID());
|
|
responses.add(response);
|
|
}
|
|
|
|
return responses;
|
|
}
|
|
|
|
/**
|
|
* Add a new content negotiator on behalf of a <content> section received.
|
|
*/
|
|
public void addContentNegotiator(ContentNegotiator inContentNegotiator) {
|
|
contentNegotiators.add(inContentNegotiator);
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------------------------------------
|
|
// Send section
|
|
// ----------------------------------------------------------------------------------------------------------
|
|
|
|
public void sendPacket(IQ iq) {
|
|
|
|
if (iq instanceof Jingle) {
|
|
|
|
sendFormattedJingle((Jingle) iq);
|
|
|
|
} else {
|
|
|
|
getConnection().sendPacket(iq);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Complete and send a packet. Complete all the null fields in a Jingle
|
|
* reponse, using the session information we have.
|
|
*
|
|
* @param jout
|
|
* the Jingle packet we want to complete and send
|
|
*/
|
|
public Jingle sendFormattedJingle(Jingle jout) {
|
|
return sendFormattedJingle(null, jout);
|
|
}
|
|
|
|
/**
|
|
* Complete and send a packet. Complete all the null fields in a Jingle
|
|
* reponse, using the session information we have or some info from the
|
|
* incoming packet.
|
|
*
|
|
* @param iq
|
|
* The Jingle packet we are responing to
|
|
* @param jout
|
|
* the Jingle packet we want to complete and send
|
|
*/
|
|
public Jingle sendFormattedJingle(IQ iq, Jingle jout) {
|
|
if (jout != null) {
|
|
if (jout.getInitiator() == null) {
|
|
jout.setInitiator(getInitiator());
|
|
}
|
|
|
|
if (jout.getResponder() == null) {
|
|
jout.setResponder(getResponder());
|
|
}
|
|
|
|
if (jout.getSid() == null) {
|
|
jout.setSid(getSid());
|
|
}
|
|
|
|
String me = getConnection().getUser();
|
|
String other = getResponder().equals(me) ? getInitiator() : getResponder();
|
|
|
|
if (jout.getTo() == null) {
|
|
if (iq != null) {
|
|
jout.setTo(iq.getFrom());
|
|
} else {
|
|
jout.setTo(other);
|
|
}
|
|
}
|
|
|
|
if (jout.getFrom() == null) {
|
|
if (iq != null) {
|
|
jout.setFrom(iq.getTo());
|
|
} else {
|
|
jout.setFrom(me);
|
|
}
|
|
}
|
|
|
|
// The the packet.
|
|
if ((getConnection() != null) && (getConnection().isConnected()))
|
|
getConnection().sendPacket(jout);
|
|
}
|
|
return jout;
|
|
}
|
|
|
|
/**
|
|
* @param inJingle
|
|
* @param inAction
|
|
*/
|
|
// private void sendUnknownStateAction(Jingle inJingle, JingleActionEnum inAction) {
|
|
//
|
|
// if (inAction == JingleActionEnum.SESSION_INITIATE) {
|
|
// // Prepare to receive and act on response packets.
|
|
// updatePacketListener();
|
|
//
|
|
// // Send the actual packet.
|
|
// sendPacket(inJingle);
|
|
//
|
|
// // Change to the PENDING state.
|
|
// setSessionState(JingleSessionStateEnum.PENDING);
|
|
// } else {
|
|
// throw new IllegalStateException("Only session-initiate allowed in the UNKNOWN state.");
|
|
// }
|
|
// }
|
|
/**
|
|
* @param inJingle
|
|
* @param inAction
|
|
*/
|
|
private void sendPendingStateAction(Jingle inJingle, JingleActionEnum inAction) {
|
|
|
|
}
|
|
|
|
/**
|
|
* @param inJingle
|
|
* @param inAction
|
|
*/
|
|
private void sendActiveStateAction(Jingle inJingle, JingleActionEnum inAction) {
|
|
|
|
}
|
|
|
|
/**
|
|
* @param inJingle
|
|
* @param inAction
|
|
*/
|
|
private void sendEndedStateAction(Jingle inJingle, JingleActionEnum inAction) {
|
|
|
|
}
|
|
|
|
/**
|
|
* Acknowledge a IQ packet.
|
|
*
|
|
* @param iq
|
|
* The IQ to acknowledge
|
|
*/
|
|
public IQ createAck(IQ iq) {
|
|
IQ result = null;
|
|
|
|
if (iq != null) {
|
|
// Don't acknowledge ACKs, errors...
|
|
if (iq.getType().equals(IQ.Type.SET)) {
|
|
IQ ack = createIQ(iq.getPacketID(), iq.getFrom(), iq.getTo(), IQ.Type.RESULT);
|
|
|
|
// No! Don't send it. Let it flow to the normal way IQ results get processed and sent.
|
|
// getConnection().sendPacket(ack);
|
|
result = ack;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Send a content info message.
|
|
*/
|
|
// public synchronized void sendContentInfo(ContentInfo ci) {
|
|
// sendPacket(new Jingle(new JingleContentInfo(ci)));
|
|
// }
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see java.lang.Object#hashCode()
|
|
*/
|
|
public int hashCode() {
|
|
return Jingle.getSessionHash(getSid(), getInitiator());
|
|
}
|
|
|
|
/*
|
|
* (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 JingleSession other = (JingleSession) obj;
|
|
|
|
if (initiator == null) {
|
|
if (other.initiator != null) {
|
|
return false;
|
|
}
|
|
} else if (!initiator.equals(other.initiator)) {
|
|
// Todo check behavior
|
|
// return false;
|
|
}
|
|
|
|
if (responder == null) {
|
|
if (other.responder != null) {
|
|
return false;
|
|
}
|
|
} else if (!responder.equals(other.responder)) {
|
|
return false;
|
|
}
|
|
|
|
if (sid == null) {
|
|
if (other.sid != null) {
|
|
return false;
|
|
}
|
|
} else if (!sid.equals(other.sid)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Instances management
|
|
|
|
/**
|
|
* Clean a session from the list.
|
|
*
|
|
* @param connection
|
|
* The connection to clean up
|
|
*/
|
|
private void unregisterInstanceFor(Connection connection) {
|
|
synchronized (sessions) {
|
|
sessions.remove(connection);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register this instance.
|
|
*/
|
|
private void registerInstance() {
|
|
synchronized (sessions) {
|
|
sessions.put(getConnection(), this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the JingleSession related to a particular connection.
|
|
*
|
|
* @param con
|
|
* A XMPP connection
|
|
* @return a Jingle session
|
|
*/
|
|
public static JingleSession getInstanceFor(Connection con) {
|
|
if (con == null) {
|
|
throw new IllegalArgumentException("Connection cannot be null");
|
|
}
|
|
|
|
JingleSession result = null;
|
|
synchronized (sessions) {
|
|
if (sessions.containsKey(con)) {
|
|
result = (JingleSession) sessions.get(con);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Configure a session, setting some action listeners...
|
|
*
|
|
* @param connection
|
|
* The connection to set up
|
|
*/
|
|
private void installConnectionListeners(final Connection connection) {
|
|
if (connection != null) {
|
|
connectionListener = new ConnectionListener() {
|
|
public void connectionClosed() {
|
|
unregisterInstanceFor(connection);
|
|
}
|
|
|
|
public void connectionClosedOnError(java.lang.Exception e) {
|
|
unregisterInstanceFor(connection);
|
|
}
|
|
|
|
public void reconnectingIn(int i) {
|
|
}
|
|
|
|
public void reconnectionSuccessful() {
|
|
}
|
|
|
|
public void reconnectionFailed(Exception exception) {
|
|
}
|
|
};
|
|
connection.addConnectionListener(connectionListener);
|
|
}
|
|
}
|
|
|
|
private void removeConnectionListener() {
|
|
if (connectionListener != null) {
|
|
getConnection().removeConnectionListener(connectionListener);
|
|
|
|
LOGGER.debug("JINGLE SESSION: REMOVE CONNECTION LISTENER");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove the packet listener used for processing packet.
|
|
*/
|
|
protected void removePacketListener() {
|
|
if (packetListener != null) {
|
|
getConnection().removePacketListener(packetListener);
|
|
|
|
LOGGER.debug("JINGLE SESSION: REMOVE PACKET LISTENER");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Install the packet listener. The listener is responsible for responding
|
|
* to any packet that we receive...
|
|
*/
|
|
protected void updatePacketListener() {
|
|
removePacketListener();
|
|
|
|
LOGGER.debug("UpdatePacketListener");
|
|
|
|
packetListener = new PacketListener() {
|
|
public void processPacket(Packet packet) {
|
|
try {
|
|
receivePacketAndRespond((IQ) packet);
|
|
} catch (XMPPException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
};
|
|
|
|
packetFilter = new PacketFilter() {
|
|
public boolean accept(Packet packet) {
|
|
|
|
if (packet instanceof IQ) {
|
|
IQ iq = (IQ) packet;
|
|
|
|
String me = getConnection().getUser();
|
|
|
|
if (!iq.getTo().equals(me)) {
|
|
return false;
|
|
}
|
|
|
|
String other = getResponder().equals(me) ? getInitiator() : getResponder();
|
|
|
|
if (iq.getFrom() == null || !iq.getFrom().equals(other == null ? "" : other)) {
|
|
return false;
|
|
}
|
|
|
|
if (iq instanceof Jingle) {
|
|
Jingle jin = (Jingle) iq;
|
|
|
|
String sid = jin.getSid();
|
|
if (sid == null || !sid.equals(getSid())) {
|
|
LOGGER.debug("Ignored Jingle(SID) " + sid + "|" + getSid() + " :" + iq.toXML());
|
|
return false;
|
|
}
|
|
String ini = jin.getInitiator();
|
|
if (!ini.equals(getInitiator())) {
|
|
LOGGER.debug("Ignored Jingle(INI): " + iq.toXML());
|
|
return false;
|
|
}
|
|
} else {
|
|
// We accept some non-Jingle IQ packets: ERRORs and ACKs
|
|
if (iq.getType().equals(IQ.Type.SET)) {
|
|
LOGGER.debug("Ignored Jingle(TYPE): " + iq.toXML());
|
|
return false;
|
|
} else if (iq.getType().equals(IQ.Type.GET)) {
|
|
LOGGER.debug("Ignored Jingle(TYPE): " + iq.toXML());
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
getConnection().addPacketListener(packetListener, packetFilter);
|
|
}
|
|
|
|
// Listeners
|
|
|
|
/**
|
|
* Add a listener for jmf negotiation events
|
|
*
|
|
* @param li
|
|
* The listener
|
|
*/
|
|
public void addMediaListener(JingleMediaListener li) {
|
|
for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
if (contentNegotiator.getMediaNegotiator() != null) {
|
|
contentNegotiator.getMediaNegotiator().addListener(li);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Remove a listener for jmf negotiation events
|
|
*
|
|
* @param li
|
|
* The listener
|
|
*/
|
|
public void removeMediaListener(JingleMediaListener li) {
|
|
for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
if (contentNegotiator.getMediaNegotiator() != null) {
|
|
contentNegotiator.getMediaNegotiator().removeListener(li);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a listener for transport negotiation events
|
|
*
|
|
* @param li
|
|
* The listener
|
|
*/
|
|
public void addTransportListener(JingleTransportListener li) {
|
|
for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
if (contentNegotiator.getTransportNegotiator() != null) {
|
|
contentNegotiator.getTransportNegotiator().addListener(li);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove a listener for transport negotiation events
|
|
*
|
|
* @param li
|
|
* The listener
|
|
*/
|
|
public void removeTransportListener(JingleTransportListener li) {
|
|
for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
if (contentNegotiator.getTransportNegotiator() != null) {
|
|
contentNegotiator.getTransportNegotiator().removeListener(li);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Setup the listeners that act on events coming from the lower level negotiators.
|
|
*/
|
|
|
|
public void setupListeners() {
|
|
|
|
JingleMediaListener jingleMediaListener = new JingleMediaListener() {
|
|
public void mediaClosed(PayloadType cand) {
|
|
}
|
|
|
|
public void mediaEstablished(PayloadType pt) {
|
|
if (isFullyEstablished()) {
|
|
Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT);
|
|
|
|
// Build up a response packet from each media manager.
|
|
for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
|
|
jout.addContent(contentNegotiator.getJingleContent());
|
|
}
|
|
// Send the "accept" and wait for the ACK
|
|
addExpectedId(jout.getPacketID());
|
|
sendPacket(jout);
|
|
|
|
//triggerSessionEstablished();
|
|
|
|
}
|
|
}
|
|
};
|
|
|
|
JingleTransportListener jingleTransportListener = new JingleTransportListener() {
|
|
|
|
public void transportEstablished(TransportCandidate local, TransportCandidate remote) {
|
|
if (isFullyEstablished()) {
|
|
|
|
// Indicate that this session is active.
|
|
setSessionState(JingleSessionStateActive.getInstance());
|
|
|
|
for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
|
|
contentNegotiator.triggerContentEstablished();
|
|
}
|
|
|
|
if (getSessionState().equals(JingleSessionStatePending.getInstance())) {
|
|
|
|
Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT);
|
|
|
|
// Build up a response packet from each media manager.
|
|
for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
|
|
jout.addContent(contentNegotiator.getJingleContent());
|
|
}
|
|
// Send the "accept" and wait for the ACK
|
|
addExpectedId(jout.getPacketID());
|
|
sendPacket(jout);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void transportClosed(TransportCandidate cand) {
|
|
}
|
|
|
|
public void transportClosedOnError(XMPPException e) {
|
|
}
|
|
};
|
|
|
|
addMediaListener(jingleMediaListener);
|
|
addTransportListener(jingleTransportListener);
|
|
}
|
|
|
|
// Triggers
|
|
|
|
/**
|
|
* Trigger a session closed event.
|
|
*/
|
|
protected void triggerSessionClosed(String reason) {
|
|
// for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
//
|
|
// contentNegotiator.stopJingleMediaSession();
|
|
//
|
|
// for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
|
|
// candidate.removeCandidateEcho();
|
|
// }
|
|
|
|
List<JingleListener> listeners = getListenersList();
|
|
for (JingleListener li : listeners) {
|
|
if (li instanceof JingleSessionListener) {
|
|
JingleSessionListener sli = (JingleSessionListener) li;
|
|
sli.sessionClosed(reason, this);
|
|
}
|
|
}
|
|
close();
|
|
}
|
|
|
|
/**
|
|
* Trigger a session closed event due to an error.
|
|
*/
|
|
protected void triggerSessionClosedOnError(XMPPException exc) {
|
|
for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
|
|
contentNegotiator.stopJingleMediaSession();
|
|
|
|
for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
|
|
candidate.removeCandidateEcho();
|
|
}
|
|
List<JingleListener> listeners = getListenersList();
|
|
for (JingleListener li : listeners) {
|
|
if (li instanceof JingleSessionListener) {
|
|
JingleSessionListener sli = (JingleSessionListener) li;
|
|
sli.sessionClosedOnError(exc, this);
|
|
}
|
|
}
|
|
close();
|
|
}
|
|
|
|
/**
|
|
* Trigger a session established event.
|
|
*/
|
|
// protected void triggerSessionEstablished() {
|
|
// List<JingleListener> listeners = getListenersList();
|
|
// for (JingleListener li : listeners) {
|
|
// if (li instanceof JingleSessionListener) {
|
|
// JingleSessionListener sli = (JingleSessionListener) li;
|
|
// sli.sessionEstablished(this);
|
|
// }
|
|
// }
|
|
// }
|
|
/**
|
|
* Trigger a media received event.
|
|
*/
|
|
protected void triggerMediaReceived(String participant) {
|
|
List<JingleListener> listeners = getListenersList();
|
|
for (JingleListener li : listeners) {
|
|
if (li instanceof JingleSessionListener) {
|
|
JingleSessionListener sli = (JingleSessionListener) li;
|
|
sli.sessionMediaReceived(this, participant);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Trigger a session redirect event.
|
|
*/
|
|
// protected void triggerSessionRedirect(String arg) {
|
|
// List<JingleListener> listeners = getListenersList();
|
|
// for (JingleListener li : listeners) {
|
|
// if (li instanceof JingleSessionListener) {
|
|
// JingleSessionListener sli = (JingleSessionListener) li;
|
|
// sli.sessionRedirected(arg, this);
|
|
// }
|
|
// }
|
|
// }
|
|
/**
|
|
* Trigger a session decline event.
|
|
*/
|
|
// protected void triggerSessionDeclined(String reason) {
|
|
// List<JingleListener> listeners = getListenersList();
|
|
// for (JingleListener li : listeners) {
|
|
// if (li instanceof JingleSessionListener) {
|
|
// JingleSessionListener sli = (JingleSessionListener) li;
|
|
// sli.sessionDeclined(reason, this);
|
|
// }
|
|
// }
|
|
// for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
// for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
|
|
// candidate.removeCandidateEcho();
|
|
// }
|
|
// }
|
|
/**
|
|
* Terminates the session with default reason.
|
|
*
|
|
* @throws XMPPException
|
|
*/
|
|
public void terminate() throws XMPPException {
|
|
terminate("Closed Locally");
|
|
}
|
|
|
|
/**
|
|
* Terminates the session with a custom reason.
|
|
*
|
|
* @throws XMPPException
|
|
*/
|
|
public void terminate(String reason) throws XMPPException {
|
|
if (isClosed())
|
|
return;
|
|
LOGGER.debug("Terminate " + reason);
|
|
Jingle jout = new Jingle(JingleActionEnum.SESSION_TERMINATE);
|
|
jout.setType(IQ.Type.SET);
|
|
sendPacket(jout);
|
|
triggerSessionClosed(reason);
|
|
}
|
|
|
|
/**
|
|
* Terminate negotiations.
|
|
*/
|
|
public void close() {
|
|
if (isClosed())
|
|
return;
|
|
|
|
// Set the session state to ENDED.
|
|
setSessionState(JingleSessionStateEnded.getInstance());
|
|
|
|
for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
|
|
contentNegotiator.stopJingleMediaSession();
|
|
|
|
for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
|
|
candidate.removeCandidateEcho();
|
|
|
|
contentNegotiator.close();
|
|
}
|
|
removePacketListener();
|
|
removeConnectionListener();
|
|
getConnection().removeConnectionListener(connectionListener);
|
|
LOGGER.debug("Negotiation Closed: " + getConnection().getUser() + " " + sid);
|
|
super.close();
|
|
|
|
}
|
|
|
|
public boolean isClosed() {
|
|
return getSessionState().equals(JingleSessionStateEnded.getInstance());
|
|
}
|
|
|
|
// Packet and error creation
|
|
|
|
/**
|
|
* A convience method to create an IQ packet.
|
|
*
|
|
* @param ID
|
|
* The packet ID of the
|
|
* @param to
|
|
* To whom the packet is addressed.
|
|
* @param from
|
|
* From whom the packet is sent.
|
|
* @param type
|
|
* The iq type of the packet.
|
|
* @return The created IQ packet.
|
|
*/
|
|
public static IQ createIQ(String ID, String to, String from, IQ.Type type) {
|
|
IQ iqPacket = new IQ() {
|
|
public String getChildElementXML() {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
iqPacket.setPacketID(ID);
|
|
iqPacket.setTo(to);
|
|
iqPacket.setFrom(from);
|
|
iqPacket.setType(type);
|
|
|
|
return iqPacket;
|
|
}
|
|
|
|
/**
|
|
* A convience method to create an error packet.
|
|
*
|
|
* @param ID
|
|
* The packet ID of the
|
|
* @param to
|
|
* To whom the packet is addressed.
|
|
* @param from
|
|
* From whom the packet is sent.
|
|
* @param errCode
|
|
* The error code.
|
|
* @param errStr
|
|
* The error string.
|
|
* @return The created IQ packet.
|
|
*/
|
|
public static IQ createError(String ID, String to, String from, int errCode, XMPPError error) {
|
|
|
|
IQ iqError = createIQ(ID, to, from, IQ.Type.ERROR);
|
|
iqError.setError(error);
|
|
|
|
LOGGER.debug("Created Error Packet:" + iqError.toXML());
|
|
|
|
return iqError;
|
|
}
|
|
|
|
/**
|
|
* Complete and send an error. Complete all the null fields in an IQ error
|
|
* reponse, using the sesssion information we have or some info from the
|
|
* incoming packet.
|
|
*
|
|
* @param iq
|
|
* The Jingle packet we are responing to
|
|
* @param error
|
|
* the IQ packet we want to complete and send
|
|
*/
|
|
public IQ createJingleError(IQ iq, JingleError jingleError) {
|
|
IQ errorPacket = null;
|
|
if (jingleError != null) {
|
|
errorPacket = createIQ(getSid(), iq.getFrom(), iq.getTo(), IQ.Type.ERROR);
|
|
|
|
List<PacketExtension> extList = new ArrayList<PacketExtension>();
|
|
extList.add(jingleError);
|
|
XMPPError error = new XMPPError(0, XMPPError.Type.CANCEL, jingleError.toString(), "", extList);
|
|
|
|
// Fill in the fields with the info from the Jingle packet
|
|
errorPacket.setPacketID(iq.getPacketID());
|
|
errorPacket.setError(error);
|
|
// errorPacket.addExtension(jingleError);
|
|
|
|
// NO! Let the normal state machinery do all of the sending.
|
|
// getConnection().sendPacket(perror);
|
|
LOGGER.error("Error sent: " + errorPacket.toXML());
|
|
}
|
|
return errorPacket;
|
|
}
|
|
|
|
/**
|
|
* Called when new Media is received.
|
|
*/
|
|
public void mediaReceived(String participant) {
|
|
triggerMediaReceived(participant);
|
|
}
|
|
|
|
/**
|
|
* This is the starting point for intitiating a new session.
|
|
*
|
|
* @throws IllegalStateException
|
|
*/
|
|
public void startOutgoing() throws IllegalStateException {
|
|
|
|
updatePacketListener();
|
|
setSessionState(JingleSessionStatePending.getInstance());
|
|
|
|
Jingle jingle = new Jingle(JingleActionEnum.SESSION_INITIATE);
|
|
|
|
// Create a content negotiator for each media manager on the session.
|
|
for (JingleMediaManager mediaManager : getMediaManagers()) {
|
|
ContentNegotiator contentNeg = new ContentNegotiator(this, ContentNegotiator.INITIATOR, mediaManager.getName());
|
|
|
|
// Create the media negotiator for this content description.
|
|
contentNeg.setMediaNegotiator(new MediaNegotiator(this, mediaManager, mediaManager.getPayloads(), contentNeg));
|
|
|
|
JingleTransportManager transportManager = mediaManager.getTransportManager();
|
|
TransportResolver resolver = null;
|
|
try {
|
|
resolver = transportManager.getResolver(this);
|
|
} catch (XMPPException e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
if (resolver.getType().equals(TransportResolver.Type.rawupd)) {
|
|
contentNeg.setTransportNegotiator(new TransportNegotiator.RawUdp(this, resolver, contentNeg));
|
|
}
|
|
if (resolver.getType().equals(TransportResolver.Type.ice)) {
|
|
contentNeg.setTransportNegotiator(new TransportNegotiator.Ice(this, resolver, contentNeg));
|
|
}
|
|
|
|
addContentNegotiator(contentNeg);
|
|
}
|
|
|
|
// Give each of the content negotiators a chance to return a portion of the structure to make the Jingle packet.
|
|
for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
jingle.addContent(contentNegotiator.getJingleContent());
|
|
}
|
|
|
|
// Save the session-initiate packet ID, so that we can respond to it.
|
|
sessionInitPacketID = jingle.getPacketID();
|
|
|
|
sendPacket(jingle);
|
|
|
|
// Now setup to track the media negotiators, so that we know when (if) to send a session-accept.
|
|
setupListeners();
|
|
|
|
// Give each of the content negotiators a chance to start
|
|
// and return a portion of the structure to make the Jingle packet.
|
|
|
|
// Don't do this anymore. The problem is that the other side might not be ready.
|
|
// Later when we receive our first jingle packet from the other side we'll fire-up the negotiators
|
|
// before processing it. (See receivePacketAndRespond() above.
|
|
// for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
// contentNegotiator.start();
|
|
// }
|
|
}
|
|
|
|
/**
|
|
* This is the starting point for responding to a new session.
|
|
*/
|
|
public void startIncoming() {
|
|
|
|
//updatePacketListener();
|
|
}
|
|
|
|
protected void doStart() {
|
|
|
|
}
|
|
|
|
/**
|
|
* When we initiate a session we need to start a bunch of negotiators right after we receive the result
|
|
* packet for our session-initiate. This is where we start them.
|
|
*
|
|
*/
|
|
private void startNegotiators() {
|
|
|
|
for (ContentNegotiator contentNegotiator : contentNegotiators) {
|
|
TransportNegotiator transNeg = contentNegotiator.getTransportNegotiator();
|
|
transNeg.start();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The jingle session may have one or more media managers that are trying to establish media sessions.
|
|
* When the media manager succeeds in creating a media session is registers it with the session by the
|
|
* media manager's static name. This routine is where the media manager does the registering.
|
|
*/
|
|
public void addJingleMediaSession(String mediaManagerName, JingleMediaSession mediaSession) {
|
|
mediaSessionMap.put(mediaManagerName, mediaSession);
|
|
}
|
|
|
|
/**
|
|
* The jingle session may have one or more media managers that are trying to establish media sessions.
|
|
* When the media manager succeeds in creating a media session is registers it with the session by the
|
|
* media manager's static name. This routine is where other objects can access the registered media sessions.
|
|
* NB: If the media manager has not succeeded in establishing a media session then this could return null.
|
|
*/
|
|
public JingleMediaSession getMediaSession(String mediaManagerName) {
|
|
return mediaSessionMap.get(mediaManagerName);
|
|
}
|
|
}
|