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