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