001/**
002 *
003 * Copyright 2003-2006 Jive Software.
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 */
017
018package org.jivesoftware.smackx.jingleold.nat;
019
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.Iterator;
023import java.util.List;
024import java.util.logging.Level;
025import java.util.logging.Logger;
026
027import org.jivesoftware.smack.SmackException;
028import org.jivesoftware.smack.SmackException.NoResponseException;
029import org.jivesoftware.smack.SmackException.NotConnectedException;
030import org.jivesoftware.smack.XMPPException;
031import org.jivesoftware.smack.XMPPException.XMPPErrorException;
032import org.jivesoftware.smack.packet.IQ;
033
034import org.jivesoftware.smackx.jingleold.ContentNegotiator;
035import org.jivesoftware.smackx.jingleold.JingleActionEnum;
036import org.jivesoftware.smackx.jingleold.JingleException;
037import org.jivesoftware.smackx.jingleold.JingleNegotiator;
038import org.jivesoftware.smackx.jingleold.JingleNegotiatorState;
039import org.jivesoftware.smackx.jingleold.JingleSession;
040import org.jivesoftware.smackx.jingleold.listeners.JingleListener;
041import org.jivesoftware.smackx.jingleold.listeners.JingleTransportListener;
042import org.jivesoftware.smackx.jingleold.packet.Jingle;
043import org.jivesoftware.smackx.jingleold.packet.JingleContent;
044import org.jivesoftware.smackx.jingleold.packet.JingleTransport;
045import org.jivesoftware.smackx.jingleold.packet.JingleTransport.JingleTransportCandidate;
046
047/**
048 * Transport negotiator.
049 *
050 * This class is responsible for managing the transport negotiation process,
051 * handling all the stanza interchange and the stage control.
052 *
053 * @author Alvaro Saurin
054 */
055@SuppressWarnings("UnusedVariable")
056public abstract class TransportNegotiator extends JingleNegotiator {
057
058    private static final Logger LOGGER = Logger.getLogger(TransportNegotiator.class.getName());
059
060    // The time we give to the candidates check before we accept or decline the
061    // transport (in milliseconds)
062    public static final int CANDIDATES_ACCEPT_PERIOD = 4000;
063
064    // The session this negotiator belongs to
065    // private final JingleSession session;
066
067    // The transport manager
068    private final TransportResolver resolver;
069
070    // Transport candidates we have offered
071    private final List<TransportCandidate> offeredCandidates = new ArrayList<>();
072
073    // List of remote transport candidates
074    private final List<TransportCandidate> remoteCandidates = new ArrayList<>();
075
076    // Valid remote candidates
077    private final List<TransportCandidate> validRemoteCandidates = new ArrayList<>();
078
079    // Accepted Remote Candidates
080    private final List<TransportCandidate> acceptedRemoteCandidates = new ArrayList<>();
081
082    // The best local candidate we have offered (and accepted by the other part)
083    private TransportCandidate acceptedLocalCandidate;
084
085    // The thread that will report the result to the other end
086    private Thread resultThread;
087
088    // Listener for the resolver
089    private TransportResolverListener.Resolver resolverListener;
090
091    private final ContentNegotiator parentNegotiator;
092
093    /**
094    * Default constructor.
095    *
096    * @param session            The Jingle session
097    * @param transResolver The JingleTransportManager to use
098    * @param parentNegotiator the parent ngeotiator.
099    */
100    public TransportNegotiator(JingleSession session, TransportResolver transResolver, ContentNegotiator parentNegotiator) {
101        super(session);
102
103        resolver = transResolver;
104        this.parentNegotiator = parentNegotiator;
105
106        resultThread = null;
107    }
108
109    /**
110     * Get a new instance of the right TransportNegotiator class with this
111     * candidate.
112     *
113     * @param cand the transport candidate.
114     * @return A TransportNegotiator instance
115     */
116    public abstract JingleTransport getJingleTransport(TransportCandidate cand);
117
118    /**
119     * Return true if the transport candidate is acceptable for the current
120     * negotiator.
121     *
122     * @param tc the transport candidate.
123     * @param localCandidates a list of local transport candidates.
124     * @return true if the transport candidate is acceptable
125     */
126    public abstract boolean acceptableTransportCandidate(TransportCandidate tc, List<TransportCandidate> localCandidates);
127
128    /**
129     * Obtain the best local candidate we want to offer.
130     *
131     * @return the best local candidate
132     */
133    public final TransportCandidate getBestLocalCandidate() {
134        return resolver.getPreferredCandidate();
135    }
136
137    /**
138     * Set the best local transport candidate we have offered and accepted by
139     * the other endpoint.
140     *
141     * @param bestLocalCandidate the acceptedLocalCandidate to set
142     */
143    private void setAcceptedLocalCandidate(TransportCandidate bestLocalCandidate) {
144        for (int i = 0; i < resolver.getCandidateCount(); i++) {
145            // TODO FIX The EQUAL Sentence
146            if (resolver.getCandidate(i).getIp().equals(bestLocalCandidate.getIp())
147                    && resolver.getCandidate(i).getPort() == bestLocalCandidate.getPort()) {
148                acceptedLocalCandidate = resolver.getCandidate(i);
149                return;
150            }
151        }
152        LOGGER.fine("BEST: ip=" + bestLocalCandidate.getIp() + " port=" + bestLocalCandidate.getPort() + " has not been offered.");
153        // throw new XMPPException("Local transport candidate has not be offered.");
154    }
155
156    /**
157     * Get the best accepted local candidate we have offered.
158     *
159     * @return a transport candidate we have offered.
160     */
161    public TransportCandidate getAcceptedLocalCandidate() {
162        return acceptedLocalCandidate;
163    }
164
165    /**
166     *  Called from above to start the negotiator during a session-initiate.
167     */
168    @Override
169    protected void doStart() {
170
171        try {
172            sendTransportCandidatesOffer();
173            setNegotiatorState(JingleNegotiatorState.PENDING);
174        } catch (Exception e) {
175            // TODO Auto-generated catch block
176            LOGGER.log(Level.WARNING, "exception", e);
177        }
178
179    }
180
181    /**
182     * Called from above to session-terminate.
183     */
184    @Override
185    public void close() {
186        super.close();
187
188    }
189
190    /**
191     *  Return a JingleTransport that best reflects this transport negotiator.
192     *
193     *  @return the jingle transport.
194     */
195    public JingleTransport getJingleTransport() {
196        return getJingleTransport(getBestRemoteCandidate());
197    }
198
199    public List<TransportCandidate> getOfferedCandidates() {
200        return offeredCandidates;
201    }
202
203    /**
204     * Obtain the best common transport candidate obtained in the negotiation.
205     *
206     * @return the bestRemoteCandidate
207     */
208    public abstract TransportCandidate getBestRemoteCandidate();
209
210    /**
211     * Get the list of remote candidates.
212     *
213     * @return the remoteCandidates
214     */
215    private List<TransportCandidate> getRemoteCandidates() {
216        return remoteCandidates;
217    }
218
219    /**
220     * Add a remote candidate to the list. The candidate will be checked in
221     * order to verify if it is usable.
222     *
223     * @param rc a remote candidate to add and check.
224     */
225    private void addRemoteCandidate(TransportCandidate rc) {
226        // Add the candidate to the list
227        if (rc != null) {
228            if (acceptableTransportCandidate(rc, offeredCandidates)) {
229                synchronized (remoteCandidates) {
230                    remoteCandidates.add(rc);
231                }
232
233                // Check if the new candidate can be used.
234                checkRemoteCandidate(rc);
235            }
236        }
237    }
238
239    /**
240     * Add a offered candidate to the list.
241     *
242     * @param rc a remote candidate we have offered.
243     */
244    private void addOfferedCandidate(TransportCandidate rc) {
245        // Add the candidate to the list
246        if (rc != null) {
247            synchronized (offeredCandidates) {
248                offeredCandidates.add(rc);
249            }
250        }
251    }
252
253    /**
254     * Check asynchronously the new transport candidate.
255     *
256     * @param offeredCandidate a transport candidates to check
257     */
258    private void checkRemoteCandidate(final TransportCandidate offeredCandidate) {
259        offeredCandidate.addListener(new TransportResolverListener.Checker() {
260            @Override
261            public void candidateChecked(TransportCandidate cand, final boolean validCandidate) {
262                if (validCandidate) {
263                    if (getNegotiatorState() == JingleNegotiatorState.PENDING)
264                        addValidRemoteCandidate(offeredCandidate);
265                }
266            }
267
268            @Override
269            public void candidateChecking(TransportCandidate cand) {
270            }
271
272        });
273        offeredCandidate.check(resolver.getCandidatesList());
274    }
275
276    /**
277     * Return true if the transport is established.
278     *
279     * @return true if the transport is established.
280     */
281    private boolean isEstablished() {
282        return getBestRemoteCandidate() != null && getAcceptedLocalCandidate() != null;
283    }
284
285    /**
286     * Return true if the transport is fully established.
287     *
288     * @return true if the transport is fully established.
289     */
290    public final boolean isFullyEstablished() {
291        return isEstablished() && ((getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) || (getNegotiatorState() == JingleNegotiatorState.FAILED));
292    }
293
294    /**
295     * Launch a thread that checks, after some time, if any of the candidates
296     * offered by the other endpoint is usable. The thread does not check the
297     * candidates: it just checks if we have got a valid one and sends an Accept
298     * in that case.
299     */
300    private void delayedCheckBestCandidate(final JingleSession js, final Jingle jin) {
301        //
302        // If this is the first insertion in the list, start the thread that
303        // will send the result of our checks...
304        //
305        if (resultThread == null && !getRemoteCandidates().isEmpty()) {
306            resultThread = new Thread(new Runnable() {
307
308                @Override
309                public void run() {
310
311                    // Sleep for some time, waiting for the candidates checks
312
313                    int totalTime = CANDIDATES_ACCEPT_PERIOD + TransportResolver.CHECK_TIMEOUT;
314                    int tries = (int) Math.ceil(totalTime / 1000);
315
316                    for (int i = 0; i < tries - 1; i++) {
317                        try {
318                            Thread.sleep(1000);
319                        } catch (InterruptedException e) {
320                            LOGGER.log(Level.WARNING, "exception", e);
321                        }
322
323                        // Once we are in pending state, look for any valid remote
324                        // candidate, and send an "accept" if we have one...
325                        TransportCandidate bestRemote = getBestRemoteCandidate();
326                        // State state = getState();
327
328                        if (bestRemote != null
329                                && getNegotiatorState() == JingleNegotiatorState.PENDING) {
330                            // Accepting the remote candidate
331                            if (!acceptedRemoteCandidates.contains(bestRemote)) {
332                                Jingle jout = new Jingle(JingleActionEnum.CONTENT_ACCEPT);
333                                JingleContent content = parentNegotiator.getJingleContent();
334                                content.addJingleTransport(getJingleTransport(bestRemote));
335                                jout.addContent(content);
336
337                                // Send the packet
338                                try {
339                                    js.sendFormattedJingle(jin, jout);
340                                }
341                                catch (InterruptedException | NotConnectedException e) {
342                                    throw new IllegalStateException(e);
343                                }
344                                acceptedRemoteCandidates.add(bestRemote);
345                            }
346                            if (isEstablished() && getNegotiatorState() == JingleNegotiatorState.PENDING) {
347                                setNegotiatorState(JingleNegotiatorState.SUCCEEDED);
348                                try {
349                                    triggerTransportEstablished(getAcceptedLocalCandidate(), bestRemote);
350                                }
351                                catch (InterruptedException | NotConnectedException | NoResponseException | XMPPErrorException e) {
352                                    throw new IllegalStateException(e);
353                                }
354                                break;
355                            }
356                        }
357                    }
358
359                    // Once we are in pending state, look for any valid remote
360                    // candidate, and send an "accept" if we have one...
361                    TransportCandidate bestRemote = getBestRemoteCandidate();
362
363                    if (bestRemote == null) {
364                        boolean foundRemoteRelay = false;
365                        for (TransportCandidate candidate : remoteCandidates) {
366                            if (candidate instanceof ICECandidate) {
367                                ICECandidate iceCandidate = (ICECandidate) candidate;
368                                if (iceCandidate.getType().equals(ICECandidate.Type.relay)) {
369                                    // TODO Check if the relay is reachable.
370                                    addValidRemoteCandidate(iceCandidate);
371                                    foundRemoteRelay = true;
372                                }
373                            }
374                        }
375
376                        // If not found, check if we offered a relay. If yes, we should accept any remote candidate.
377                        // We should accept the Public One if we received it, otherwise, accepts any.
378                        if (!foundRemoteRelay) {
379                            boolean foundLocalRelay = false;
380                            for (TransportCandidate candidate : offeredCandidates) {
381                                if (candidate instanceof ICECandidate) {
382                                    ICECandidate iceCandidate = (ICECandidate) candidate;
383                                    if (iceCandidate.getType().equals(ICECandidate.Type.relay)) {
384                                        foundLocalRelay = true;
385                                    }
386                                }
387                            }
388                            if (foundLocalRelay) {
389                                boolean foundRemotePublic = false;
390                                for (TransportCandidate candidate : remoteCandidates) {
391                                    if (candidate instanceof ICECandidate) {
392                                        ICECandidate iceCandidate = (ICECandidate) candidate;
393                                        if (iceCandidate.getType().equals(ICECandidate.Type.srflx)) {
394                                            addValidRemoteCandidate(iceCandidate);
395                                            foundRemotePublic = true;
396                                        }
397                                    }
398                                }
399                                if (!foundRemotePublic) {
400                                    for (TransportCandidate candidate : remoteCandidates) {
401                                        if (candidate instanceof ICECandidate) {
402                                            ICECandidate iceCandidate = (ICECandidate) candidate;
403                                            addValidRemoteCandidate(iceCandidate);
404                                        }
405                                    }
406                                }
407                            }
408                        }
409                    }
410
411                    for (int i = 0; i < 6; i++) {
412                        try {
413                            Thread.sleep(500);
414                        } catch (InterruptedException e) {
415                            LOGGER.log(Level.WARNING, "exception", e);
416                        }
417
418                        bestRemote = getBestRemoteCandidate();
419                        // State state = getState();
420                        if (bestRemote != null
421                                && getNegotiatorState() == JingleNegotiatorState.PENDING) {
422                            if (!acceptedRemoteCandidates.contains(bestRemote)) {
423                                Jingle jout = new Jingle(JingleActionEnum.CONTENT_ACCEPT);
424                                JingleContent content = parentNegotiator.getJingleContent();
425                                content.addJingleTransport(getJingleTransport(bestRemote));
426                                jout.addContent(content);
427
428                                // Send the packet
429                                try {
430                                    js.sendFormattedJingle(jin, jout);
431                                }
432                                catch (InterruptedException | NotConnectedException e) {
433                                    throw new IllegalStateException(e);
434                                }
435                                acceptedRemoteCandidates.add(bestRemote);
436                            }
437                            if (isEstablished()) {
438                                setNegotiatorState(JingleNegotiatorState.SUCCEEDED);
439                                break;
440                            }
441                        }
442                    }
443
444                    if (getNegotiatorState() != JingleNegotiatorState.SUCCEEDED) {
445                        try {
446                            session
447                                    .terminate("Unable to negotiate session. This may be caused by firewall configuration problems.");
448                        } catch (Exception e) {
449                            LOGGER.log(Level.WARNING, "exception", e);
450                        }
451                    }
452                }
453            }, "Waiting for all the transport candidates checks...");
454
455            resultThread.setName("Transport Resolver Result");
456            resultThread.start();
457        }
458    }
459
460    /**
461     * Add a valid remote candidate to the list. The remote candidate has been
462     * checked, and the remote
463     *
464     * @param remoteCandidate a remote candidate to add
465     */
466    private void addValidRemoteCandidate(TransportCandidate remoteCandidate) {
467        // Add the candidate to the list
468        if (remoteCandidate != null) {
469            synchronized (validRemoteCandidates) {
470                LOGGER.fine("Added valid candidate: " + remoteCandidate.getIp() + ":" + remoteCandidate.getPort());
471                validRemoteCandidates.add(remoteCandidate);
472            }
473        }
474    }
475
476    /**
477     * Get the list of valid (ie, checked) remote candidates.
478     *
479     * @return The list of valid (ie, already checked) remote candidates.
480     */
481    final ArrayList<TransportCandidate> getValidRemoteCandidatesList() {
482        synchronized (validRemoteCandidates) {
483            return new ArrayList<>(validRemoteCandidates);
484        }
485    }
486
487    /**
488     * Get an iterator for the list of valid (ie, checked) remote candidates.
489     *
490     * @return The iterator for the list of valid (ie, already checked) remote
491     *         candidates.
492     */
493    public final Iterator<TransportCandidate> getValidRemoteCandidates() {
494        return Collections.unmodifiableList(getRemoteCandidates()).iterator();
495    }
496
497    /**
498     * Add an offered remote candidate. The transport candidate can be unusable:
499     * we must check if we can use it.
500     *
501     * @param rc the remote candidate to add.
502     */
503    private void addRemoteCandidates(List<TransportCandidate> rc) {
504        if (rc != null) {
505            if (rc.size() > 0) {
506                for (TransportCandidate aRc : rc) {
507                    addRemoteCandidate(aRc);
508                }
509            }
510        }
511    }
512
513    /**
514     * Parse the list of transport candidates from a Jingle packet.
515     *
516     * @param jingle The input jingle packet
517     */
518    private List<TransportCandidate> obtainCandidatesList(Jingle jingle) {
519        List<TransportCandidate> result = new ArrayList<>();
520
521        if (jingle != null) {
522            // Get the list of candidates from the packet
523            for (JingleContent jingleContent : jingle.getContentsList()) {
524                if (jingleContent.getName().equals(parentNegotiator.getName())) {
525                    for (JingleTransport jingleTransport : jingleContent.getJingleTransportsList()) {
526                        for (JingleTransportCandidate jingleTransportCandidate : jingleTransport.getCandidatesList()) {
527                            TransportCandidate transCand = jingleTransportCandidate.getMediaTransport();
528                            result.add(transCand);
529                        }
530                    }
531                }
532            }
533        }
534
535        return result;
536    }
537
538    /**
539     * Send an offer for a transport candidate
540     *
541     * @param cand TODO javadoc me please
542     * @throws NotConnectedException if the XMPP connection is not connected.
543     * @throws InterruptedException if the calling thread was interrupted.
544     */
545    private synchronized void sendTransportCandidateOffer(TransportCandidate cand) throws NotConnectedException, InterruptedException {
546        if (!cand.isNull()) {
547            // Offer our new candidate...
548            addOfferedCandidate(cand);
549            JingleContent content = parentNegotiator.getJingleContent();
550            content.addJingleTransport(getJingleTransport(cand));
551            Jingle jingle = new Jingle(JingleActionEnum.TRANSPORT_INFO);
552            jingle.addContent(content);
553
554            // We SHOULD NOT be sending packets directly.
555            // This circumvents the state machinery.
556            // TODO - work this into the state machinery.
557            session.sendFormattedJingle(jingle);
558        }
559    }
560
561    /**
562     * Create a Jingle stanza where we announce our transport candidates.
563     *
564     * @throws XMPPException if an XMPP protocol error was received.
565     * @throws SmackException if Smack detected an exceptional situation.
566     * @throws InterruptedException if the calling thread was interrupted.
567     */
568    private void sendTransportCandidatesOffer() throws XMPPException, SmackException, InterruptedException {
569        List<TransportCandidate> notOffered = resolver.getCandidatesList();
570
571        notOffered.removeAll(offeredCandidates);
572
573        // Send any unset candidate
574        for (Object aNotOffered : notOffered) {
575            sendTransportCandidateOffer((TransportCandidate) aNotOffered);
576        }
577
578        // .. and start a listener that will send any future candidate
579        if (resolverListener == null) {
580            // Add a listener that sends the offer when the resolver finishes...
581            resolverListener = new TransportResolverListener.Resolver() {
582                @Override
583                public void candidateAdded(TransportCandidate cand) throws NotConnectedException, InterruptedException {
584                    sendTransportCandidateOffer(cand);
585                }
586
587                @Override
588                public void end() {
589                }
590
591                @Override
592                public void init() {
593                }
594            };
595
596            resolver.addListener(resolverListener);
597        }
598
599        if (!(resolver.isResolving() || resolver.isResolved())) {
600            // Resolve our IP and port
601            LOGGER.fine("RESOLVER CALLED");
602            resolver.resolve(session);
603        }
604    }
605
606    /**
607     * Dispatch an incoming packet. The method is responsible for recognizing
608     * the stanza type and, depending on the current state, delivering the
609     * stanza to the right event handler and wait for a response.
610     *
611     * @param iq the stanza received
612     * @return the new Jingle stanza to send.
613     * @throws XMPPException if an XMPP protocol error was received.
614     * @throws SmackException if Smack detected an exceptional situation.
615     * @throws InterruptedException if the calling thread was interrupted.
616     */
617    @Override
618    public final List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException, InterruptedException {
619        List<IQ> responses = new ArrayList<>();
620        IQ response = null;
621
622        if (iq != null) {
623            if (iq.getType().equals(IQ.Type.error)) {
624                // Process errors
625                setNegotiatorState(JingleNegotiatorState.FAILED);
626                triggerTransportClosed(null);
627                // This next line seems wrong, and may subvert the normal closing process.
628                throw new JingleException(iq.getError().getDescriptiveText());
629            } else if (iq.getType().equals(IQ.Type.result)) {
630                // Process ACKs
631                if (isExpectedId(iq.getStanzaId())) {
632                    response = receiveResult(iq);
633                    removeExpectedId(iq.getStanzaId());
634                }
635            } else if (iq instanceof Jingle) {
636                // Get the action from the Jingle packet
637                Jingle jingle = (Jingle) iq;
638                JingleActionEnum action = jingle.getAction();
639
640                switch (action) {
641                    case CONTENT_ACCEPT:
642                        response = receiveContentAcceptAction(jingle);
643                        break;
644
645                    case CONTENT_MODIFY:
646                        break;
647
648                    case CONTENT_REMOVE:
649                        break;
650
651                    case SESSION_INFO:
652                        break;
653
654                    case SESSION_INITIATE:
655                        response = receiveSessionInitiateAction(jingle);
656                        break;
657
658                    case SESSION_ACCEPT:
659                        response = receiveSessionAcceptAction(jingle);
660                        break;
661
662                    case TRANSPORT_INFO:
663                        response = receiveTransportInfoAction(jingle);
664                        break;
665
666                    default:
667                        break;
668                }
669            }
670        }
671
672        if (response != null) {
673            addExpectedId(response.getStanzaId());
674            responses.add(response);
675        }
676
677        return responses;
678    }
679
680    /**
681     * The other endpoint has partially accepted our invitation: start
682     * offering a list of candidates.
683     *
684     * @return an IQ packet
685     * @throws XMPPException if an XMPP protocol error was received.
686     * @throws SmackException if Smack detected an exceptional situation.
687     * @throws InterruptedException if the calling thread was interrupted.
688     */
689    private Jingle receiveResult(IQ iq) throws XMPPException, SmackException, InterruptedException {
690        Jingle response = null;
691
692        sendTransportCandidatesOffer();
693        setNegotiatorState(JingleNegotiatorState.PENDING);
694
695        return response;
696    }
697
698    /**
699     *  @param jingle TODO javadoc me please
700     *  @return the iq
701     * @throws SmackException if Smack detected an exceptional situation.
702     * @throws InterruptedException if the calling thread was interrupted.
703     */
704    private IQ receiveSessionInitiateAction(Jingle jingle) throws XMPPException, SmackException, InterruptedException {
705        IQ response = null;
706
707        // Parse the Jingle and get any proposed transport candidates
708        // addRemoteCandidates(obtainCandidatesList(jin));
709
710        // Start offering candidates
711        sendTransportCandidatesOffer();
712
713        // All these candidates will be checked asynchronously. Wait for some
714        // time and check if we have a valid candidate to use...
715        delayedCheckBestCandidate(session, jingle);
716
717        // Set the next state
718        setNegotiatorState(JingleNegotiatorState.PENDING);
719
720        return response;
721    }
722
723    /**
724     *  @param jingle TODO javadoc me please
725     *  @return the iq
726     */
727    private IQ receiveTransportInfoAction(Jingle jingle) {
728        IQ response = null;
729
730        // Parse the Jingle and get any proposed transport candidates
731        // addRemoteCandidates(obtainCandidatesList(jin));
732
733        //        // Start offering candidates
734        //        sendTransportCandidatesOffer();
735        //
736        //        // All these candidates will be checked asynchronously. Wait for some
737        //        // time and check if we have a valid candidate to use...
738        //        delayedCheckBestCandidate(session, jingle);
739        //
740        //        // Set the next state
741        //        setNegotiatorState(JingleNegotiatorState.PENDING);
742
743        // Parse the Jingle and get any proposed transport candidates
744        addRemoteCandidates(obtainCandidatesList(jingle));
745
746        // Wait for some time and check if we have a valid candidate to
747        // use...
748        delayedCheckBestCandidate(session, jingle);
749
750        response = session.createAck(jingle);
751
752        return response;
753    }
754
755    /**
756     * One of our transport candidates has been accepted.
757     *
758     * @return a Jingle packet
759     * @throws XMPPException an exception
760     * @see org.jivesoftware.smackx.jingleold.JingleNegotiator.State#eventAccept(org.jivesoftware.smackx.jingleold.packet.Jingle)
761     */
762    private IQ receiveContentAcceptAction(Jingle jingle) throws XMPPException {
763        IQ response = null;
764
765        // Parse the Jingle and get the accepted candidate
766        List<TransportCandidate> accepted = obtainCandidatesList(jingle);
767        if (!accepted.isEmpty()) {
768
769            for (TransportCandidate cand : accepted) {
770                LOGGER.fine("Remote accepted candidate addr: " + cand.getIp());
771            }
772
773            TransportCandidate cand = accepted.get(0);
774            setAcceptedLocalCandidate(cand);
775
776            if (isEstablished()) {
777                LOGGER.fine(cand.getIp() + " is set active");
778                // setNegotiatorState(JingleNegotiatorState.SUCCEEDED);
779            }
780        }
781        return response;
782    }
783
784    /**
785     *  @param jingle TODO javadoc me please
786     *  @return the iq
787     */
788    private static IQ receiveSessionAcceptAction(Jingle jingle) {
789        IQ response = null;
790
791        LOGGER.fine("Transport established");
792        // triggerTransportEstablished(getAcceptedLocalCandidate(), getBestRemoteCandidate());
793
794        // setNegotiatorState(JingleNegotiatorState.SUCCEEDED);
795
796        return response;
797    }
798
799    /**
800     * Trigger a Transport session established event.
801     *
802     * @param local  TransportCandidate that has been agreed.
803     * @param remote TransportCandidate that has been agreed.
804     * @throws NotConnectedException if the XMPP connection is not connected.
805     * @throws InterruptedException if the calling thread was interrupted.
806     * @throws XMPPErrorException if there was an XMPP error returned.
807     * @throws NoResponseException if there was no response from the remote entity.
808     */
809    private void triggerTransportEstablished(TransportCandidate local, TransportCandidate remote) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException {
810        List<JingleListener> listeners = getListenersList();
811        for (JingleListener li : listeners) {
812            if (li instanceof JingleTransportListener) {
813                JingleTransportListener mli = (JingleTransportListener) li;
814                LOGGER.fine("triggerTransportEstablished " + local.getLocalIp() + ":" + local.getPort() + " <-> "
815                        + remote.getIp() + ":" + remote.getPort());
816                mli.transportEstablished(local, remote);
817            }
818        }
819    }
820
821    /**
822     * Trigger a Transport closed event.
823     *
824     * @param cand current TransportCandidate that is cancelled.
825     */
826    private void triggerTransportClosed(TransportCandidate cand) {
827        List<JingleListener> listeners = getListenersList();
828        for (JingleListener li : listeners) {
829            if (li instanceof JingleTransportListener) {
830                JingleTransportListener mli = (JingleTransportListener) li;
831                mli.transportClosed(cand);
832            }
833        }
834    }
835
836    // Subclasses
837
838    /**
839     * Raw-UDP transport negotiator.
840     *
841     * @author Alvaro Saurin
842     */
843    public static final class RawUdp extends TransportNegotiator {
844
845        /**
846         * Default constructor, with a JingleSession and transport manager.
847         *
848         * @param js  The Jingle session this negotiation belongs to.
849         * @param res The transport resolver to use.
850         * @param parentNegotiator the parent content negotiator.
851         */
852        public RawUdp(JingleSession js, final TransportResolver res, ContentNegotiator parentNegotiator) {
853            super(js, res, parentNegotiator);
854        }
855
856        /**
857         * Get a TransportNegotiator instance.
858         */
859        @Override
860        public org.jivesoftware.smackx.jingleold.packet.JingleTransport getJingleTransport(TransportCandidate bestRemote) {
861            org.jivesoftware.smackx.jingleold.packet.JingleTransport.RawUdp jt = new org.jivesoftware.smackx.jingleold.packet.JingleTransport.RawUdp();
862            jt.addCandidate(new org.jivesoftware.smackx.jingleold.packet.JingleTransport.RawUdp.Candidate(bestRemote));
863            return jt;
864        }
865
866        /**
867         * Obtain the best common transport candidate obtained in the
868         * negotiation.
869         *
870         * @return the bestRemoteCandidate
871         */
872        @Override
873        public TransportCandidate getBestRemoteCandidate() {
874            // Hopefully, we only have one validRemoteCandidate
875            ArrayList<TransportCandidate> cands = getValidRemoteCandidatesList();
876            if (!cands.isEmpty()) {
877                LOGGER.fine("RAW CAND");
878                return cands.get(0);
879            } else {
880                LOGGER.fine("No Remote Candidate");
881                return null;
882            }
883        }
884
885        /**
886         * Return true for fixed candidates.
887         */
888        @Override
889        public boolean acceptableTransportCandidate(TransportCandidate tc, List<TransportCandidate> localCandidates) {
890            return tc instanceof TransportCandidate.Fixed;
891        }
892    }
893
894    /**
895     * Ice transport negotiator.
896     *
897     * @author Alvaro Saurin
898     */
899    public static final class Ice extends TransportNegotiator {
900
901        /**
902         * Default constructor, with a JingleSession and transport manager.
903         *
904         * @param js  The Jingle session this negotiation belongs to.
905         * @param res The transport manager to use.
906         * @param parentNegotiator the parent content negotiator.
907         */
908        public Ice(JingleSession js, final TransportResolver res, ContentNegotiator parentNegotiator) {
909            super(js, res, parentNegotiator);
910        }
911
912        /**
913         * Get a TransportNegotiator instance.
914         *
915         * @param candidate TODO javadoc me please
916         */
917        @Override
918        public org.jivesoftware.smackx.jingleold.packet.JingleTransport getJingleTransport(TransportCandidate candidate) {
919            org.jivesoftware.smackx.jingleold.packet.JingleTransport.Ice jt = new org.jivesoftware.smackx.jingleold.packet.JingleTransport.Ice();
920            jt.addCandidate(new org.jivesoftware.smackx.jingleold.packet.JingleTransport.Ice.Candidate(candidate));
921            return jt;
922        }
923
924        /**
925         * Obtain the best remote candidate obtained in the negotiation so far.
926         *
927         * @return the bestRemoteCandidate
928         */
929        @Override
930        public TransportCandidate getBestRemoteCandidate() {
931            ICECandidate result = null;
932
933            ArrayList<TransportCandidate> cands = getValidRemoteCandidatesList();
934            if (!cands.isEmpty()) {
935                int highest = -1;
936                ICECandidate chose = null;
937                for (TransportCandidate transportCandidate : cands) {
938                    if (transportCandidate instanceof ICECandidate) {
939                        ICECandidate icecandidate = (ICECandidate) transportCandidate;
940                        if (icecandidate.getPreference() > highest) {
941                            chose = icecandidate;
942                            highest = icecandidate.getPreference();
943                        }
944                    }
945                }
946                result = chose;
947            }
948
949            if (result != null && result.getType().equals(ICECandidate.Type.relay))
950                LOGGER.fine("Relay Type");
951
952            return result;
953        }
954
955        /**
956         * Return true for ICE candidates.
957         */
958        @Override
959        public boolean acceptableTransportCandidate(TransportCandidate tc, List<TransportCandidate> localCandidates) {
960            return tc instanceof ICECandidate;
961        }
962    }
963}