001/**
002 *
003 * Copyright the original author or authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.jingleold;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Random;
024import java.util.logging.Level;
025import java.util.logging.Logger;
026
027import org.jivesoftware.smack.AbstractConnectionClosedListener;
028import org.jivesoftware.smack.ConnectionListener;
029import org.jivesoftware.smack.SmackException;
030import org.jivesoftware.smack.SmackException.NotConnectedException;
031import org.jivesoftware.smack.StanzaListener;
032import org.jivesoftware.smack.XMPPConnection;
033import org.jivesoftware.smack.XMPPException;
034import org.jivesoftware.smack.filter.StanzaFilter;
035import org.jivesoftware.smack.packet.IQ;
036import org.jivesoftware.smack.packet.Stanza;
037import org.jivesoftware.smack.packet.StanzaError;
038
039import org.jivesoftware.smackx.jingleold.listeners.JingleListener;
040import org.jivesoftware.smackx.jingleold.listeners.JingleMediaListener;
041import org.jivesoftware.smackx.jingleold.listeners.JingleSessionListener;
042import org.jivesoftware.smackx.jingleold.listeners.JingleTransportListener;
043import org.jivesoftware.smackx.jingleold.media.JingleMediaManager;
044import org.jivesoftware.smackx.jingleold.media.JingleMediaSession;
045import org.jivesoftware.smackx.jingleold.media.MediaNegotiator;
046import org.jivesoftware.smackx.jingleold.media.MediaReceivedListener;
047import org.jivesoftware.smackx.jingleold.media.PayloadType;
048import org.jivesoftware.smackx.jingleold.nat.JingleTransportManager;
049import org.jivesoftware.smackx.jingleold.nat.TransportCandidate;
050import org.jivesoftware.smackx.jingleold.nat.TransportNegotiator;
051import org.jivesoftware.smackx.jingleold.nat.TransportResolver;
052import org.jivesoftware.smackx.jingleold.packet.Jingle;
053import org.jivesoftware.smackx.jingleold.packet.JingleError;
054
055import org.jxmpp.jid.Jid;
056
057/**
058 * An abstract Jingle session. This class contains some basic properties of
059 * every Jingle session. However, the concrete implementation can be found in
060 * subclasses.
061 *
062 * @author Alvaro Saurin
063 * @author Jeff Williams
064 */
065public final class JingleSession extends JingleNegotiator implements MediaReceivedListener {
066
067    private static final Logger LOGGER = Logger.getLogger(JingleSession.class.getName());
068
069    // static
070    private static final HashMap<XMPPConnection, JingleSession> sessions = new HashMap<>();
071
072    private static final Random randomGenerator = new Random();
073
074    // non-static
075
076    private Jid initiator; // Who started the communication
077
078    private Jid responder; // The other endpoint
079
080    private String sid; // A unique id that identifies this session
081
082    private ConnectionListener connectionListener;
083
084    private StanzaListener packetListener;
085
086    private StanzaFilter packetFilter;
087
088    protected List<JingleMediaManager> jingleMediaManagers = null;
089
090    private JingleSessionState sessionState;
091
092    private final List<ContentNegotiator> contentNegotiators;
093
094    private final XMPPConnection connection;
095
096    private String sessionInitPacketID;
097
098    private final Map<String, JingleMediaSession> mediaSessionMap;
099
100    /**
101     * Full featured JingleSession constructor.
102     *
103     * @param conn TODO javadoc me please
104     *            the XMPPConnection which is used
105     * @param initiator TODO javadoc me please
106     *            the initiator JID
107     * @param responder TODO javadoc me please
108     *            the responder JID
109     * @param sessionid TODO javadoc me please
110     *            the session ID
111     * @param jingleMediaManagers TODO javadoc me please
112     *            the jingleMediaManager
113     */
114    public JingleSession(XMPPConnection conn, Jid initiator, Jid responder, String sessionid,
115            List<JingleMediaManager> jingleMediaManagers) {
116        super();
117
118        this.initiator = initiator;
119        this.responder = responder;
120        this.sid = sessionid;
121        this.jingleMediaManagers = jingleMediaManagers;
122        this.setSession(this);
123        this.connection = conn;
124
125        // Initially, we don't known the session state.
126        setSessionState(JingleSessionStateUnknown.getInstance());
127
128        contentNegotiators = new ArrayList<>();
129        mediaSessionMap = new HashMap<>();
130
131        // Add the session to the list and register the listeners
132        registerInstance();
133        installConnectionListeners(conn);
134    }
135
136    /**
137     * JingleSession constructor (for an outgoing Jingle session).
138     *
139     * @param conn
140     *            Connection
141     * @param request the request.
142     * @param initiator
143     *            the initiator JID
144     * @param responder
145     *            the responder JID
146     * @param jingleMediaManagers
147     *            the jingleMediaManager
148     */
149    public JingleSession(XMPPConnection conn, JingleSessionRequest request, Jid initiator, Jid responder,
150            List<JingleMediaManager> jingleMediaManagers) {
151        this(conn, initiator, responder, generateSessionId(), jingleMediaManagers);
152        // sessionRequest = request; // unused
153    }
154
155    /**
156     * Get the session initiator.
157     *
158     * @return the initiator
159     */
160    public Jid getInitiator() {
161        return initiator;
162    }
163
164    @Override
165    public XMPPConnection getConnection() {
166        return connection;
167    }
168
169    /**
170     * Set the session initiator.
171     *
172     * @param initiator TODO javadoc me please
173     *            the initiator to set
174     */
175    public void setInitiator(Jid initiator) {
176        this.initiator = initiator;
177    }
178
179    /**
180     * Get the Media Manager of this Jingle Session.
181     *
182     * @return the JingleMediaManagers
183     */
184    public List<JingleMediaManager> getMediaManagers() {
185        return jingleMediaManagers;
186    }
187
188    /**
189     * Set the Media Manager of this Jingle Session.
190     *
191     * @param jingleMediaManagers TODO javadoc me please
192     */
193    public void setMediaManagers(List<JingleMediaManager> jingleMediaManagers) {
194        this.jingleMediaManagers = jingleMediaManagers;
195    }
196
197    /**
198     * Get the session responder.
199     *
200     * @return the responder
201     */
202    public Jid getResponder() {
203        return responder;
204    }
205
206    /**
207     * Set the session responder.
208     *
209     * @param responder TODO javadoc me please
210     *            the receptor to set
211     */
212    public void setResponder(Jid responder) {
213        this.responder = responder;
214    }
215
216    /**
217     * Get the session ID.
218     *
219     * @return the sid
220     */
221    public String getSid() {
222        return sid;
223    }
224
225    /**
226     * Set the session ID
227     *
228     * @param sessionId TODO javadoc me please
229     *            the sid to set
230     */
231    protected void setSid(String sessionId) {
232        sid = sessionId;
233    }
234
235    /**
236     * Generate a unique session ID.
237     *
238     * @return the generated session ID.
239     */
240    protected static String generateSessionId() {
241        return String.valueOf(randomGenerator.nextInt(Integer.MAX_VALUE) + randomGenerator.nextInt(Integer.MAX_VALUE));
242    }
243
244    /**
245     * Validate the state changes.
246     *
247     * @param stateIs the jingle session state.
248     */
249
250    public void setSessionState(JingleSessionState stateIs) {
251
252        LOGGER.fine("Session state change: " + sessionState + "->" + stateIs);
253        stateIs.enter();
254        sessionState = stateIs;
255    }
256
257    public JingleSessionState getSessionState() {
258        return sessionState;
259    }
260
261    /**
262     * Return true if all of the media managers have finished.
263     *
264     * @return <code>true</code> if fully established.
265     */
266    public boolean isFullyEstablished() {
267        boolean result = true;
268        for (ContentNegotiator contentNegotiator : contentNegotiators) {
269            if (!contentNegotiator.isFullyEstablished())
270                result = false;
271        }
272        return result;
273    }
274
275    // ----------------------------------------------------------------------------------------------------------
276    // Receive section
277    // ----------------------------------------------------------------------------------------------------------
278
279    /**
280     * Process and respond to an incoming packet. This method is called
281     * from the stanza listener dispatcher when a new stanza has arrived. The
282     * method is responsible for recognizing the stanza type and, depending on
283     * the current state, delivering it to the right event handler and wait for
284     * a response. The response will be another Jingle stanza that will be sent
285     * to the other end point.
286     *
287     * @param iq TODO javadoc me please
288     *            the stanza received
289     * @throws XMPPException if an XMPP protocol error was received.
290     * @throws SmackException if Smack detected an exceptional situation.
291     * @throws InterruptedException if the calling thread was interrupted.
292     */
293    public synchronized void receivePacketAndRespond(IQ iq) throws XMPPException, SmackException, InterruptedException {
294        List<IQ> responses = new ArrayList<>();
295
296        String responseId;
297
298        LOGGER.fine("Packet: " + iq.toXML());
299
300        try {
301
302            // Dispatch the packet to the JingleNegotiators and get back a list of the results.
303            responses.addAll(dispatchIncomingPacket(iq, null));
304
305            if (iq != null) {
306                responseId = iq.getStanzaId();
307
308                // Send the IQ to each of the content negotiators for further processing.
309                // Each content negotiator may pass back a list of JingleContent for addition to the response packet.
310                // CHECKSTYLE:OFF
311                for (ContentNegotiator contentNegotiator : contentNegotiators) {
312                    // If at this point the content negotiator isn't started, it's because we sent a session-init jingle
313                    // packet from startOutgoing() and we're waiting for the other side to let us know they're ready
314                    // to take jingle packets.  (This packet might be a session-terminate, but that will get handled
315                    // later.
316                    if (!contentNegotiator.isStarted()) {
317                        contentNegotiator.start();
318                    }
319                    responses.addAll(contentNegotiator.dispatchIncomingPacket(iq, responseId));
320                }
321                // CHECKSTYLE:ON
322
323            }
324            // Acknowledge the IQ reception
325            // Not anymore.  The state machine generates an appropriate response IQ that
326            // gets sent back at the end of this routine.
327            // sendAck(iq);
328
329        } catch (JingleException e) {
330            // Send an error message, if present
331            JingleError error = e.getError();
332            if (error != null) {
333                responses.add(createJingleError(iq, error));
334            }
335
336            // Notify the session end and close everything...
337            triggerSessionClosedOnError(e);
338        }
339
340        //        // If the response is anything other than a RESULT then send it now.
341        //        if ((response != null) && (!response.getType().equals(IQ.Type.result))) {
342        //            getConnection().sendStanza(response);
343        //        }
344
345        // Loop through all of the responses and send them.
346        for (IQ response : responses) {
347            sendStanza(response);
348        }
349    }
350
351    /**
352     * Dispatch an incoming packet. The method is responsible for recognizing
353     * the stanza type and, depending on the current state, delivering the
354     * stanza to the right event handler and wait for a response.
355     *
356     * @param iq TODO javadoc me please
357     *            the stanza received
358     * @return the new Jingle stanza to send.
359     * @throws XMPPException if an XMPP protocol error was received.
360     * @throws SmackException if Smack detected an exceptional situation.
361     * @throws InterruptedException if the calling thread was interrupted.
362     */
363    @Override
364    public List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException, InterruptedException {
365        List<IQ> responses = new ArrayList<>();
366        IQ response = null;
367
368        if (iq != null) {
369            if (iq.getType().equals(IQ.Type.error)) {
370                // Process errors
371                // TODO getState().eventError(iq);
372            } else if (iq.getType().equals(IQ.Type.result)) {
373                // Process ACKs
374                if (isExpectedId(iq.getStanzaId())) {
375
376                    // The other side provisionally accepted our session-initiate.
377                    // Kick off some negotiators.
378                    if (iq.getStanzaId().equals(sessionInitPacketID)) {
379                        startNegotiators();
380                    }
381                    removeExpectedId(iq.getStanzaId());
382                }
383            } else if (iq instanceof Jingle) {
384                // It is not an error: it is a Jingle packet...
385                Jingle jin = (Jingle) iq;
386                JingleActionEnum action = jin.getAction();
387
388                // Depending on the state we're in we'll get different processing actions.
389                // (See Design Patterns AKA GoF State behavioral pattern.)
390                response = getSessionState().processJingle(this, jin, action);
391            }
392        }
393
394        if (response != null) {
395            // Save the packet id, for recognizing ACKs...
396            addExpectedId(response.getStanzaId());
397            responses.add(response);
398        }
399
400        return responses;
401    }
402
403    /**
404     * Add a new content negotiator on behalf of a &lt;content/&gt; section received.
405     *
406     * @param inContentNegotiator the content negotiator.
407     */
408    public void addContentNegotiator(ContentNegotiator inContentNegotiator) {
409        contentNegotiators.add(inContentNegotiator);
410    }
411
412
413
414     // ----------------------------------------------------------------------------------------------------------
415    // Send section
416    // ----------------------------------------------------------------------------------------------------------
417
418    public void sendStanza(IQ iq) throws NotConnectedException, InterruptedException {
419
420        if (iq instanceof Jingle) {
421
422            sendFormattedJingle((Jingle) iq);
423
424        } else {
425
426            getConnection().sendStanza(iq);
427        }
428    }
429
430    /**
431     * Complete and send a packet. Complete all the null fields in a Jingle
432     * reponse, using the session information we have.
433     *
434     * @param jout
435     *            the Jingle stanza we want to complete and send
436     * @return the Jingle stanza.
437     * @throws NotConnectedException if the XMPP connection is not connected.
438     * @throws InterruptedException if the calling thread was interrupted.
439     */
440    public Jingle sendFormattedJingle(Jingle jout) throws NotConnectedException, InterruptedException {
441        return sendFormattedJingle(null, jout);
442    }
443
444    /**
445     * Complete and send a packet. Complete all the null fields in a Jingle
446     * reponse, using the session information we have or some info from the
447     * incoming packet.
448     *
449     * @param iq The Jingle stanza we are responding to
450     * @param jout the Jingle stanza we want to complete and send
451     * @return the Jingle stanza.
452     * @throws NotConnectedException if the XMPP connection is not connected.
453     * @throws InterruptedException if the calling thread was interrupted.
454     */
455    public Jingle sendFormattedJingle(IQ iq, Jingle jout) throws NotConnectedException, InterruptedException {
456        if (jout != null) {
457            if (jout.getInitiator() == null) {
458                jout.setInitiator(getInitiator());
459            }
460
461            if (jout.getResponder() == null) {
462                jout.setResponder(getResponder());
463            }
464
465            if (jout.getSid() == null) {
466                jout.setSid(getSid());
467            }
468
469            Jid me = getConnection().getUser();
470            Jid other = getResponder().equals(me) ? getInitiator() : getResponder();
471
472            if (jout.getTo() == null) {
473                if (iq != null) {
474                    jout.setTo(iq.getFrom());
475                } else {
476                    jout.setTo(other);
477                }
478            }
479
480            if (jout.getFrom() == null) {
481                if (iq != null) {
482                    jout.setFrom(iq.getTo());
483                } else {
484                    jout.setFrom(me);
485                }
486            }
487
488            // The the packet.
489            // CHECKSTYLE:OFF
490            if ((getConnection() != null) && getConnection().isConnected())
491                getConnection().sendStanza(jout);
492            // CHECKSTYLE:ON
493        }
494        return jout;
495    }
496
497    /**
498     *  @param inJingle TODO javadoc me please
499     *  @param inAction TODO javadoc me please
500     */
501    //    private void sendUnknownStateAction(Jingle inJingle, JingleActionEnum inAction) {
502    //
503    //        if (inAction == JingleActionEnum.SESSION_INITIATE) {
504    //            // Prepare to receive and act on response packets.
505    //            updatePacketListener();
506    //
507    //            // Send the actual packet.
508    //            sendStanza(inJingle);
509    //
510    //            // Change to the PENDING state.
511    //            setSessionState(JingleSessionStateEnum.PENDING);
512    //        } else {
513    //            throw new IllegalStateException("Only session-initiate allowed in the UNKNOWN state.");
514    //        }
515    //    }
516
517    /**
518     * Acknowledge a IQ packet.
519     *
520     * @param iq The IQ to acknowledge.
521     * @return the ack IQ.
522     */
523    public IQ createAck(IQ iq) {
524        IQ result = null;
525
526        if (iq != null) {
527            // Don't acknowledge ACKs, errors...
528            if (iq.getType().equals(IQ.Type.set)) {
529                IQ ack = IQ.createResultIQ(iq);
530
531                // No! Don't send it.  Let it flow to the normal way IQ results get processed and sent.
532                // getConnection().sendStanza(ack);
533                result = ack;
534            }
535        }
536        return result;
537    }
538
539    /**
540     * Send a content info message.
541     */
542    //    public synchronized void sendContentInfo(ContentInfo ci) {
543    //        sendStanza(new Jingle(new JingleContentInfo(ci)));
544    //    }
545    /*
546     * (non-Javadoc)
547     *
548     * @see java.lang.Object#hashCode()
549     */
550    @Override
551    public int hashCode() {
552        return Jingle.getSessionHash(getSid(), getInitiator());
553    }
554
555    /*
556     * (non-Javadoc)
557     *
558     * @see java.lang.Object#equals(java.lang.Object)
559     */
560    @Override
561    public boolean equals(Object obj) {
562        if (this == obj) {
563            return true;
564        }
565        if (obj == null) {
566            return false;
567        }
568        if (getClass() != obj.getClass()) {
569            return false;
570        }
571
572        final JingleSession other = (JingleSession) obj;
573
574        if (initiator == null) {
575            if (other.initiator != null) {
576                return false;
577            }
578        } else if (!initiator.equals(other.initiator)) {
579            // Todo check behavior
580            // return false;
581        }
582
583        if (responder == null) {
584            if (other.responder != null) {
585                return false;
586            }
587        } else if (!responder.equals(other.responder)) {
588            return false;
589        }
590
591        if (sid == null) {
592            if (other.sid != null) {
593                return false;
594            }
595        } else if (!sid.equals(other.sid)) {
596            return false;
597        }
598
599        return true;
600    }
601
602    // Instances management
603
604    /**
605     * Clean a session from the list.
606     *
607     * @param connection TODO javadoc me please
608     *            The connection to clean up
609     */
610    private static void unregisterInstanceFor(XMPPConnection connection) {
611        synchronized (sessions) {
612            sessions.remove(connection);
613        }
614    }
615
616    /**
617     * Register this instance.
618     */
619    private void registerInstance() {
620        synchronized (sessions) {
621            sessions.put(getConnection(), this);
622        }
623    }
624
625    /**
626     * Returns the JingleSession related to a particular connection.
627     *
628     * @param con TODO javadoc me please
629     *            A XMPP connection
630     * @return a Jingle session
631     */
632    public static synchronized JingleSession getInstanceFor(XMPPConnection con) {
633        if (con == null) {
634            throw new IllegalArgumentException("XMPPConnection cannot be null");
635        }
636
637        JingleSession result = null;
638        synchronized (sessions) {
639            if (sessions.containsKey(con)) {
640                result = sessions.get(con);
641            }
642        }
643
644        return result;
645    }
646
647    /**
648     * Configure a session, setting some action listeners...
649     *
650     * @param connection TODO javadoc me please
651     *            The connection to set up
652     */
653    private void installConnectionListeners(final XMPPConnection connection) {
654        if (connection != null) {
655            connectionListener = new AbstractConnectionClosedListener() {
656                @Override
657                public void connectionTerminated() {
658                    unregisterInstanceFor(connection);
659                }
660            };
661            connection.addConnectionListener(connectionListener);
662        }
663    }
664
665    private void removeConnectionListener() {
666        // CHECKSTYLE:OFF
667        if (connectionListener != null) {
668            getConnection().removeConnectionListener(connectionListener);
669
670            LOGGER.fine("JINGLE SESSION: REMOVE CONNECTION LISTENER");
671        }
672        // CHECKSTYLE:ON
673    }
674
675    /**
676     * Remove the stanza listener used for processing packet.
677     */
678    protected void removeAsyncPacketListener() {
679        if (packetListener != null) {
680            getConnection().removeAsyncStanzaListener(packetListener);
681
682            LOGGER.fine("JINGLE SESSION: REMOVE PACKET LISTENER");
683        }
684    }
685
686    /**
687     * Install the stanza listener. The listener is responsible for responding
688     * to any stanza that we receive...
689     */
690    protected void updatePacketListener() {
691        removeAsyncPacketListener();
692
693        LOGGER.fine("UpdatePacketListener");
694
695        packetListener = new StanzaListener() {
696            @Override
697            public void processStanza(Stanza packet) {
698                try {
699                    receivePacketAndRespond((IQ) packet);
700                } catch (Exception e) {
701                    LOGGER.log(Level.WARNING, "exception", e);
702                }
703            }
704        };
705
706        packetFilter = new StanzaFilter() {
707            @Override
708            public boolean accept(Stanza packet) {
709
710                if (packet instanceof IQ) {
711                    IQ iq = (IQ) packet;
712
713                    Jid me = getConnection().getUser();
714
715                    if (!iq.getTo().equals(me)) {
716                        return false;
717                    }
718
719                    Jid other = getResponder().equals(me) ? getInitiator() : getResponder();
720
721                    if (iq.getFrom() == null || !iq.getFrom().equals(other == null ? "" : other)) {
722                        return false;
723                    }
724
725                    if (iq instanceof Jingle) {
726                        Jingle jin = (Jingle) iq;
727
728                        String sid = jin.getSid();
729                        if (sid == null || !sid.equals(getSid())) {
730                            LOGGER.fine("Ignored Jingle(SID) " + sid + "|" + getSid() + " :" + iq.toXML());
731                            return false;
732                        }
733                        Jid ini = jin.getInitiator();
734                        if (!ini.equals(getInitiator())) {
735                            LOGGER.fine("Ignored Jingle(INI): " + iq.toXML());
736                            return false;
737                        }
738                    } else {
739                        // We accept some non-Jingle IQ packets: ERRORs and ACKs
740                        if (iq.getType().equals(IQ.Type.set)) {
741                            LOGGER.fine("Ignored Jingle(TYPE): " + iq.toXML());
742                            return false;
743                        } else if (iq.getType().equals(IQ.Type.get)) {
744                            LOGGER.fine("Ignored Jingle(TYPE): " + iq.toXML());
745                            return false;
746                        }
747                    }
748                    return true;
749                }
750                return false;
751            }
752        };
753
754        getConnection().addAsyncStanzaListener(packetListener, packetFilter);
755    }
756
757    // Listeners
758
759    /**
760     * Add a listener for jmf negotiation events.
761     *
762     * @param li TODO javadoc me please
763     *            The listener
764     */
765    public void addMediaListener(JingleMediaListener li) {
766        for (ContentNegotiator contentNegotiator : contentNegotiators) {
767            if (contentNegotiator.getMediaNegotiator() != null) {
768                contentNegotiator.getMediaNegotiator().addListener(li);
769            }
770        }
771
772    }
773
774    /**
775     * Remove a listener for jmf negotiation events.
776     *
777     * @param li TODO javadoc me please
778     *            The listener
779     */
780    public void removeMediaListener(JingleMediaListener li) {
781        for (ContentNegotiator contentNegotiator : contentNegotiators) {
782            if (contentNegotiator.getMediaNegotiator() != null) {
783                contentNegotiator.getMediaNegotiator().removeListener(li);
784            }
785        }
786    }
787
788    /**
789     * Add a listener for transport negotiation events.
790     *
791     * @param li TODO javadoc me please
792     *            The listener
793     */
794    public void addTransportListener(JingleTransportListener li) {
795        for (ContentNegotiator contentNegotiator : contentNegotiators) {
796            if (contentNegotiator.getTransportNegotiator() != null) {
797                contentNegotiator.getTransportNegotiator().addListener(li);
798            }
799        }
800    }
801
802    /**
803     * Remove a listener for transport negotiation events.
804     *
805     * @param li TODO javadoc me please
806     *            The listener
807     */
808    public void removeTransportListener(JingleTransportListener li) {
809        for (ContentNegotiator contentNegotiator : contentNegotiators) {
810            if (contentNegotiator.getTransportNegotiator() != null) {
811                contentNegotiator.getTransportNegotiator().removeListener(li);
812            }
813        }
814    }
815
816    /**
817     * Setup the listeners that act on events coming from the lower level negotiators.
818     */
819
820    public void setupListeners() {
821
822        JingleMediaListener jingleMediaListener = new JingleMediaListener() {
823            @Override
824            public void mediaClosed(PayloadType cand) {
825            }
826
827            @Override
828            public void mediaEstablished(PayloadType pt) throws NotConnectedException, InterruptedException {
829                if (isFullyEstablished()) {
830                    Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT);
831
832                    // Build up a response packet from each media manager.
833                    for (ContentNegotiator contentNegotiator : contentNegotiators) {
834                        if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
835                            jout.addContent(contentNegotiator.getJingleContent());
836                    }
837                    // Send the "accept" and wait for the ACK
838                    addExpectedId(jout.getStanzaId());
839                    sendStanza(jout);
840
841                    // triggerSessionEstablished();
842
843                }
844            }
845        };
846
847        JingleTransportListener jingleTransportListener = new JingleTransportListener() {
848
849            @Override
850            public void transportEstablished(TransportCandidate local, TransportCandidate remote) throws NotConnectedException, InterruptedException {
851                if (isFullyEstablished()) {
852                // CHECKSTYLE:OFF
853                    // Indicate that this session is active.
854                    setSessionState(JingleSessionStateActive.getInstance());
855
856                    for (ContentNegotiator contentNegotiator : contentNegotiators) {
857                // CHECKSTYLE:ON
858                        if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
859                            contentNegotiator.triggerContentEstablished();
860                    }
861
862                    if (getSessionState().equals(JingleSessionStatePending.getInstance())) {
863
864                        Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT);
865
866                        // Build up a response packet from each media manager.
867                        for (ContentNegotiator contentNegotiator : contentNegotiators) {
868                            if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
869                                jout.addContent(contentNegotiator.getJingleContent());
870                        }
871                        // Send the "accept" and wait for the ACK
872                        addExpectedId(jout.getStanzaId());
873                        sendStanza(jout);
874                    }
875                }
876            }
877
878            @Override
879            public void transportClosed(TransportCandidate cand) {
880            }
881
882            @Override
883            public void transportClosedOnError(XMPPException e) {
884            }
885        };
886
887        addMediaListener(jingleMediaListener);
888        addTransportListener(jingleTransportListener);
889    }
890
891    // Triggers
892
893    /**
894     * Trigger a session closed event.
895     *
896     * @param reason the reason.
897     */
898    protected void triggerSessionClosed(String reason) {
899        //        for (ContentNegotiator contentNegotiator : contentNegotiators) {
900        //
901        //            contentNegotiator.stopJingleMediaSession();
902        //
903        //            for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
904        //                candidate.removeCandidateEcho();
905        //        }
906
907        List<JingleListener> listeners = getListenersList();
908        for (JingleListener li : listeners) {
909            if (li instanceof JingleSessionListener) {
910                JingleSessionListener sli = (JingleSessionListener) li;
911                sli.sessionClosed(reason, this);
912            }
913        }
914        close();
915    }
916
917    /**
918     * Trigger a session closed event due to an error.
919     *
920     * @param exc the exception.
921     */
922    protected void triggerSessionClosedOnError(XMPPException exc) {
923        for (ContentNegotiator contentNegotiator : contentNegotiators) {
924
925            contentNegotiator.stopJingleMediaSession();
926
927            for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
928                candidate.removeCandidateEcho();
929        }
930        List<JingleListener> listeners = getListenersList();
931        for (JingleListener li : listeners) {
932            if (li instanceof JingleSessionListener) {
933                JingleSessionListener sli = (JingleSessionListener) li;
934                sli.sessionClosedOnError(exc, this);
935            }
936        }
937        close();
938    }
939
940    /**
941     * Trigger a session established event.
942     */
943    //    protected void triggerSessionEstablished() {
944    //        List<JingleListener> listeners = getListenersList();
945    //        for (JingleListener li : listeners) {
946    //            if (li instanceof JingleSessionListener) {
947    //                JingleSessionListener sli = (JingleSessionListener) li;
948    //                sli.sessionEstablished(this);
949    //            }
950    //        }
951    //    }
952    /**
953     * Trigger a media received event.
954     *
955     * @param participant the participant.
956     */
957    protected void triggerMediaReceived(String participant) {
958        List<JingleListener> listeners = getListenersList();
959        for (JingleListener li : listeners) {
960            if (li instanceof JingleSessionListener) {
961                JingleSessionListener sli = (JingleSessionListener) li;
962                sli.sessionMediaReceived(this, participant);
963            }
964        }
965    }
966
967    /**
968     * Trigger a session redirect event.
969     */
970    //    protected void triggerSessionRedirect(String arg) {
971    //        List<JingleListener> listeners = getListenersList();
972    //        for (JingleListener li : listeners) {
973    //            if (li instanceof JingleSessionListener) {
974    //                JingleSessionListener sli = (JingleSessionListener) li;
975    //                sli.sessionRedirected(arg, this);
976    //            }
977    //        }
978    //    }
979    /**
980     * Trigger a session decline event.
981     */
982    //    protected void triggerSessionDeclined(String reason) {
983    //        List<JingleListener> listeners = getListenersList();
984    //        for (JingleListener li : listeners) {
985    //            if (li instanceof JingleSessionListener) {
986    //                JingleSessionListener sli = (JingleSessionListener) li;
987    //                sli.sessionDeclined(reason, this);
988    //            }
989    //        }
990    //        for (ContentNegotiator contentNegotiator : contentNegotiators) {
991    //            for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
992    //                candidate.removeCandidateEcho();
993    //        }
994    //    }
995    /**
996     * Terminates the session with default reason.
997     *
998     * @throws XMPPException if an XMPP protocol error was received.
999     * @throws NotConnectedException if the XMPP connection is not connected.
1000     * @throws InterruptedException if the calling thread was interrupted.
1001     */
1002    public void terminate() throws XMPPException, NotConnectedException, InterruptedException {
1003        terminate("Closed Locally");
1004    }
1005
1006    /**
1007     * Terminates the session with a custom reason.
1008     *
1009     * @param reason the reason.
1010     * @throws XMPPException if an XMPP protocol error was received.
1011     * @throws NotConnectedException if the XMPP connection is not connected.
1012     * @throws InterruptedException if the calling thread was interrupted.
1013     */
1014    public void terminate(String reason) throws XMPPException, NotConnectedException, InterruptedException {
1015        if (isClosed())
1016            return;
1017        LOGGER.fine("Terminate " + reason);
1018        Jingle jout = new Jingle(JingleActionEnum.SESSION_TERMINATE);
1019        jout.setType(IQ.Type.set);
1020        sendStanza(jout);
1021        triggerSessionClosed(reason);
1022    }
1023
1024    /**
1025     * Terminate negotiations.
1026     */
1027    @Override
1028    public void close() {
1029        if (isClosed())
1030            return;
1031
1032        // Set the session state to ENDED.
1033        setSessionState(JingleSessionStateEnded.getInstance());
1034
1035        for (ContentNegotiator contentNegotiator : contentNegotiators) {
1036
1037            contentNegotiator.stopJingleMediaSession();
1038
1039            for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
1040                candidate.removeCandidateEcho();
1041
1042            contentNegotiator.close();
1043        }
1044        removeAsyncPacketListener();
1045        removeConnectionListener();
1046        getConnection().removeConnectionListener(connectionListener);
1047        LOGGER.fine("Negotiation Closed: " + getConnection().getUser() + " " + sid);
1048        super.close();
1049
1050    }
1051
1052    public boolean isClosed() {
1053        return getSessionState().equals(JingleSessionStateEnded.getInstance());
1054    }
1055
1056    // Packet and error creation
1057
1058    /**
1059     * Complete and send an error. Complete all the null fields in an IQ error
1060     * response, using the session information we have or some info from the
1061     * incoming packet.
1062     *
1063     * @param iq
1064     *            The Jingle stanza we are responding to
1065     * @param jingleError
1066     *            the IQ stanza we want to complete and send
1067     * @return the jingle error IQ.
1068     */
1069    public IQ createJingleError(IQ iq, JingleError jingleError) {
1070        IQ errorPacket = null;
1071        if (jingleError != null) {
1072            // TODO This is wrong according to XEP-166 ยง 10, but this jingle implementation is deprecated anyways
1073            StanzaError builder = StanzaError.getBuilder()
1074                            .setCondition(StanzaError.Condition.undefined_condition)
1075                            .addExtension(jingleError)
1076                            .build();
1077
1078            errorPacket = IQ.createErrorResponse(iq, builder);
1079
1080            //            errorPacket.addExtension(jingleError);
1081
1082            // NO! Let the normal state machinery do all of the sending.
1083            // getConnection().sendStanza(perror);
1084            LOGGER.severe("Error sent: " + errorPacket.toXML());
1085        }
1086        return errorPacket;
1087    }
1088
1089    /**
1090     * Called when new Media is received.
1091     */
1092    @Override
1093    public void mediaReceived(String participant) {
1094        triggerMediaReceived(participant);
1095    }
1096
1097    /**
1098     * This is the starting point for intitiating a new session.
1099     *
1100     * @throws IllegalStateException if an illegal state was encountered
1101     * @throws SmackException if Smack detected an exceptional situation.
1102     * @throws InterruptedException if the calling thread was interrupted.
1103     */
1104    public void startOutgoing() throws IllegalStateException, SmackException, InterruptedException {
1105
1106        updatePacketListener();
1107        setSessionState(JingleSessionStatePending.getInstance());
1108
1109        Jingle jingle = new Jingle(JingleActionEnum.SESSION_INITIATE);
1110
1111        // Create a content negotiator for each media manager on the session.
1112        for (JingleMediaManager mediaManager : getMediaManagers()) {
1113            ContentNegotiator contentNeg = new ContentNegotiator(this, ContentNegotiator.INITIATOR, mediaManager.getName());
1114
1115            // Create the media negotiator for this content description.
1116            contentNeg.setMediaNegotiator(new MediaNegotiator(this, mediaManager, mediaManager.getPayloads(), contentNeg));
1117
1118            JingleTransportManager transportManager = mediaManager.getTransportManager();
1119            TransportResolver resolver = null;
1120            try {
1121                resolver = transportManager.getResolver(this);
1122            } catch (XMPPException e) {
1123                LOGGER.log(Level.WARNING, "exception", e);
1124            }
1125
1126            if (resolver.getType().equals(TransportResolver.Type.rawupd)) {
1127                contentNeg.setTransportNegotiator(new TransportNegotiator.RawUdp(this, resolver, contentNeg));
1128            }
1129            if (resolver.getType().equals(TransportResolver.Type.ice)) {
1130                contentNeg.setTransportNegotiator(new TransportNegotiator.Ice(this, resolver, contentNeg));
1131            }
1132
1133            addContentNegotiator(contentNeg);
1134        }
1135
1136        // Give each of the content negotiators a chance to return a portion of the structure to make the Jingle packet.
1137        for (ContentNegotiator contentNegotiator : contentNegotiators) {
1138            jingle.addContent(contentNegotiator.getJingleContent());
1139        }
1140
1141        // Save the session-initiate packet ID, so that we can respond to it.
1142        sessionInitPacketID = jingle.getStanzaId();
1143
1144        sendStanza(jingle);
1145
1146        // Now setup to track the media negotiators, so that we know when (if) to send a session-accept.
1147        setupListeners();
1148
1149        // Give each of the content negotiators a chance to start
1150        // and return a portion of the structure to make the Jingle packet.
1151
1152// Don't do this anymore.  The problem is that the other side might not be ready.
1153// Later when we receive our first jingle packet from the other side we'll fire-up the negotiators
1154// before processing it.  (See receivePacketAndRespond() above.
1155//        for (ContentNegotiator contentNegotiator : contentNegotiators) {
1156//            contentNegotiator.start();
1157//        }
1158    }
1159
1160    /**
1161     *  This is the starting point for responding to a new session.
1162     */
1163    public void startIncoming() {
1164
1165        // updatePacketListener();
1166    }
1167
1168    @Override
1169    protected void doStart() {
1170
1171    }
1172
1173    /**
1174     * When we initiate a session we need to start a bunch of negotiators right after we receive the result
1175     * stanza for our session-initiate.  This is where we start them.
1176     *
1177     */
1178    private void startNegotiators() {
1179
1180        for (ContentNegotiator contentNegotiator : contentNegotiators) {
1181            TransportNegotiator transNeg = contentNegotiator.getTransportNegotiator();
1182            transNeg.start();
1183        }
1184    }
1185
1186    /**
1187     * The jingle session may have one or more media managers that are trying to establish media sessions.
1188     * When the media manager succeeds in creating a media session is registers it with the session by the
1189     * media manager's static name.  This routine is where the media manager does the registering.
1190     *
1191     * @param mediaManagerName the name of the media manager.
1192     * @param mediaSession the jingle media session.
1193     */
1194    public void addJingleMediaSession(String mediaManagerName, JingleMediaSession mediaSession) {
1195        mediaSessionMap.put(mediaManagerName, mediaSession);
1196    }
1197
1198    /**
1199     * The jingle session may have one or more media managers that are trying to establish media sessions.
1200     * When the media manager succeeds in creating a media session is registers it with the session by the
1201     * media manager's static name. This routine is where other objects can access the registered media sessions.
1202     * NB: If the media manager has not succeeded in establishing a media session then this could return null.
1203     *
1204     * @param mediaManagerName the name of the media manager.
1205     * @return the jingle media session.
1206     */
1207    public JingleMediaSession getMediaSession(String mediaManagerName) {
1208        return mediaSessionMap.get(mediaManagerName);
1209    }
1210}