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