/** * $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.listeners.JingleSessionStateListener; 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 javax.swing.*; 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 * @author Thiago Camargo */ 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 transportManager The transport manager * @param initialJingleSessionRequest the initial Jingle Session Request */ protected IncomingJingleSession(XMPPConnection conn, String responder, List payloadTypes, JingleTransportManager transportManager, JingleSessionRequest initialJingleSessionRequest) throws XMPPException { super(conn, responder, conn.getUser()); setSid(initialJingleSessionRequest.getSessionID()); // Create the states... accepting = new Accepting(this); pending = new Pending(this); active = new Active(this); TransportResolver resolver = null; try { resolver = transportManager.getResolver(this); } catch (XMPPException e) { e.printStackTrace(); } 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 transportManager The transport manager * @param jingleMediaManager The Media Manager for this Session * @param initialJingleSessionRequest the initial Jingle Session Request */ protected IncomingJingleSession(XMPPConnection conn, String responder, List payloadTypes, JingleTransportManager transportManager, JingleMediaManager jingleMediaManager, JingleSessionRequest initialJingleSessionRequest) throws XMPPException { this(conn, responder, payloadTypes, transportManager, initialJingleSessionRequest); 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 { // Establish the default state setState(accepting); updatePacketListener(); Jingle packet = initialJingleSessionRequest.getJingle(); if (packet != null) { // Initialize the session information setSid(packet.getSid()); respond(packet); } else { throw new XMPPException( "Session request with null Jingle packet."); } } /** * Start the session using initial Jingle Session Request used to create this session.. * * @throws XMPPException */ public void start() throws XMPPException { start(getInitialSessionRequest()); } /** * Get the initial Jingle packet request * * @return */ public JingleSessionRequest getInitialSessionRequest() { return initialSessionRequest; } /** * Get the initial Jingle packet request * * @param initialRequest the initial Jingle packet */ public 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); } /** * 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. */ 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: wait for the sub-negotiators. * * @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventEnter() */ public void eventEnter() { // Add the listeners to the sub-negotiators... 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); } /** * 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); } } /** * "Active" state: we have an agreement about the 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); } } public IQ sendFormattedError(JingleError error){ IQ perror = null; if (error != null) { perror = createIQ(getSid(), getInitiator(), getResponder(), IQ.Type.ERROR); // Fill in the fields with the info from the Jingle packet perror.addExtension(error); getConnection().sendPacket(perror); System.err.println(perror.toXML()); } return perror; } }