mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-09-27 18:19:33 +02:00
a078606ab4
git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@7460 b35dd754-fafc-0310-a699-88a17e54d16e
465 lines
16 KiB
Java
465 lines
16 KiB
Java
/**
|
|
* $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.jingle.nat.JingleTransportManager;
|
|
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.
|
|
* <p/>
|
|
* 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>
|
|
* @author Thiago Camargo
|
|
*/
|
|
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 transportManager The transport manager.
|
|
*/
|
|
protected OutgoingJingleSession(XMPPConnection conn, String responder,
|
|
List payloadTypes, JingleTransportManager transportManager) {
|
|
|
|
super(conn, conn.getUser(), responder);
|
|
|
|
setSid(generateSessionId());
|
|
|
|
// Initialize the states.
|
|
inviting = new Inviting(this);
|
|
pending = new Pending(this);
|
|
active = new Active(this);
|
|
|
|
TransportResolver resolver = null;
|
|
try {
|
|
resolver = transportManager.getResolver(this);
|
|
}
|
|
catch (XMPPException e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
// 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 transportManager The transport manager.
|
|
* @param jingleMediaManager The Media Manager for this Session
|
|
*/
|
|
protected OutgoingJingleSession(XMPPConnection conn, String responder,
|
|
List payloadTypes, JingleTransportManager transportManager, JingleMediaManager jingleMediaManager) {
|
|
this(conn, responder, payloadTypes, transportManager);
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* "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 {
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
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);
|
|
}
|
|
}
|
|
}
|