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