001/**
002 *
003 * Copyright the original author or authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.jingleold.nat;
018
019import java.io.IOException;
020import java.net.ServerSocket;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.Iterator;
024import java.util.List;
025import java.util.logging.Level;
026import java.util.logging.Logger;
027
028import org.jivesoftware.smack.SmackException;
029import org.jivesoftware.smack.SmackException.NotConnectedException;
030import org.jivesoftware.smack.XMPPException;
031import org.jivesoftware.smackx.jingleold.JingleSession;
032
033/**
034 * A TransportResolver is used for obtaining a list of valid transport
035 * candidates. A transport candidate is composed by an IP address and a port number.
036 * It is called candidate, because it can be elected or not.
037 *
038 * @author Thiago Camargo
039 * @author Alvaro Saurin <alvaro.saurin@gmail.com>
040 */
041public abstract class TransportResolver {
042
043    private static final Logger LOGGER = Logger.getLogger(TransportResolver.class.getName());
044
045    public enum Type {
046
047        rawupd, ice
048    }
049
050    public Type getType() {
051        return type;
052    }
053
054    public void setType(Type type) {
055        this.type = type;
056    }
057
058    public Type type = Type.rawupd;
059
060    // the time, in milliseconds, before a check aborts
061    public static final int CHECK_TIMEOUT = 3000;
062
063    // Listeners for events
064    private final ArrayList<TransportResolverListener> listeners = new ArrayList<TransportResolverListener>();
065
066    // TRue if the resolver is working
067    private boolean resolving;
068
069    // This will be true when all the transport candidates have been gathered...
070    private boolean resolved;
071
072    // This indicates that a transport resolver is initialized
073    private boolean initialized = false;
074
075    // We store a list of candidates internally, just in case there are several
076    // possibilities. When the user asks for a transport, we return the best
077    // one.
078    protected final List<TransportCandidate> candidates = new ArrayList<TransportCandidate>();
079
080    /**
081     * Default constructor.
082     */
083    protected TransportResolver() {
084        super();
085
086        resolving = false;
087        resolved = false;
088    }
089
090    /**
091     * Initialize the Resolver.
092     * @throws InterruptedException 
093     */
094    public abstract void initialize() throws XMPPException, SmackException, InterruptedException;
095
096    /**
097     * Start a the resolution.
098     * @throws InterruptedException 
099     */
100    public abstract void resolve(JingleSession session) throws XMPPException, SmackException, InterruptedException;
101
102    /**
103     * Clear the list of candidates and start a new resolution process.
104     *
105     * @throws XMPPException
106     */
107    public void clear() throws XMPPException {
108        cancel();
109        candidates.clear();
110    }
111
112    /**
113     * Cancel any asynchronous resolution operation.
114     */
115    public abstract void cancel() throws XMPPException;
116
117    /**
118     * Return true if the resolver is working.
119     *
120     * @return true if the resolver is working.
121     */
122    public boolean isResolving() {
123        return resolving;
124    }
125
126    /**
127     * Return true if the resolver has finished the search for transport
128     * candidates.
129     *
130     * @return true if the search has finished
131     */
132    public boolean isResolved() {
133        return resolved;
134    }
135
136    /**
137     * Set the Transport Resolver as initialized.
138     */
139    public synchronized void setInitialized() {
140        initialized = true;
141    }
142
143    /**
144     * Chack if the Transport Resolver is initialized.
145     *
146     * @return true if initialized
147     */
148    public synchronized boolean isInitialized() {
149        return initialized;
150    }
151
152    /**
153     * Indicate the beggining of the resolution process. This method must be
154     * used by subclasses at the begining of their resolve() method.
155     */
156    protected synchronized void setResolveInit() {
157        resolved = false;
158        resolving = true;
159
160        triggerResolveInit();
161    }
162
163    /**
164     * Indicate the end of the resolution process. This method must be used by
165     * subclasses at the begining of their resolve() method.
166     */
167    protected synchronized void setResolveEnd() {
168        resolved = true;
169        resolving = false;
170
171        triggerResolveEnd();
172    }
173
174    // Listeners management
175
176    /**
177     * Add a transport resolver listener.
178     *
179     * @param li The transport resolver listener to be added.
180     */
181    public void addListener(TransportResolverListener li) {
182        synchronized (listeners) {
183            listeners.add(li);
184        }
185    }
186
187    /**
188     * Removes a transport resolver listener.
189     *
190     * @param li The transport resolver listener to be removed
191     */
192    public void removeListener(TransportResolverListener li) {
193        synchronized (listeners) {
194            listeners.remove(li);
195        }
196    }
197
198    /**
199     * Get the list of listeners.
200     *
201     * @return the list of listeners
202     */
203    public ArrayList<TransportResolverListener> getListenersList() {
204        synchronized (listeners) {
205            return new ArrayList<TransportResolverListener>(listeners);
206        }
207    }
208
209    /**
210     * Trigger a new candidate added event.
211     *
212     * @param cand The candidate added to the list of candidates.
213     * @throws NotConnectedException 
214     * @throws InterruptedException 
215     */
216    protected void triggerCandidateAdded(TransportCandidate cand) throws NotConnectedException, InterruptedException {
217        Iterator<TransportResolverListener> iter = getListenersList().iterator();
218        while (iter.hasNext()) {
219            TransportResolverListener trl = iter.next();
220            if (trl instanceof TransportResolverListener.Resolver) {
221                TransportResolverListener.Resolver li = (TransportResolverListener.Resolver) trl;
222                LOGGER.fine("triggerCandidateAdded : " + cand.getLocalIp());
223                li.candidateAdded(cand);
224            }
225        }
226    }
227
228    /**
229     * Trigger a event notifying the initialization of the resolution process.
230     */
231    private void triggerResolveInit() {
232        Iterator<TransportResolverListener> iter = getListenersList().iterator();
233        while (iter.hasNext()) {
234            TransportResolverListener trl = iter.next();
235            if (trl instanceof TransportResolverListener.Resolver) {
236                TransportResolverListener.Resolver li = (TransportResolverListener.Resolver) trl;
237                li.init();
238            }
239        }
240    }
241
242    /**
243     * Trigger a event notifying the obtention of all the candidates.
244     */
245    private void triggerResolveEnd() {
246        Iterator<TransportResolverListener> iter = getListenersList().iterator();
247        while (iter.hasNext()) {
248            TransportResolverListener trl = iter.next();
249            if (trl instanceof TransportResolverListener.Resolver) {
250                TransportResolverListener.Resolver li = (TransportResolverListener.Resolver) trl;
251                li.end();
252            }
253        }
254    }
255
256    // Candidates management
257
258    /**
259     * Clear the list of candidate
260     */
261    protected void clearCandidates() {
262        synchronized (candidates) {
263            candidates.clear();
264        }
265    }
266
267    /**
268     * Add a new transport candidate
269     *
270     * @param cand The candidate to add
271     * @throws NotConnectedException 
272     * @throws InterruptedException 
273     */
274    protected void addCandidate(TransportCandidate cand) throws NotConnectedException, InterruptedException {
275        synchronized (candidates) {
276            if (!candidates.contains(cand))
277                candidates.add(cand);
278        }
279
280        // Notify the listeners
281        triggerCandidateAdded(cand);
282    }
283
284    /**
285     * Get an iterator for the list of candidates.
286     *
287     * @return an iterator
288     */
289    public Iterator<TransportCandidate> getCandidates() {
290        synchronized (candidates) {
291            return Collections.unmodifiableList(new ArrayList<TransportCandidate>(candidates)).iterator();
292        }
293    }
294
295    /**
296     * Get the candididate with the highest preference.
297     *
298     * @return The best candidate, according to the preference order.
299     */
300    public TransportCandidate getPreferredCandidate() {
301        TransportCandidate result = null;
302
303        ArrayList<ICECandidate> cands = new ArrayList<ICECandidate>();
304        for (TransportCandidate tpcan : getCandidatesList()) {
305            if (tpcan instanceof ICECandidate)
306                cands.add((ICECandidate) tpcan);
307        }
308
309        // (ArrayList<ICECandidate>) getCandidatesList();
310        if (cands.size() > 0) {
311            Collections.sort(cands);
312            // Return the last candidate
313            result = cands.get(cands.size() - 1);
314            LOGGER.fine("Result: " + result.getIp());
315        }
316
317        return result;
318    }
319
320    /**
321     * Get the numer of transport candidates.
322     *
323     * @return The length of the transport candidates list.
324     */
325    public int getCandidateCount() {
326        synchronized (candidates) {
327            return candidates.size();
328        }
329    }
330
331    /**
332     * Get the list of candidates.
333     *
334     * @return the list of transport candidates
335     */
336    public List<TransportCandidate> getCandidatesList() {
337        List<TransportCandidate> result = null;
338
339        synchronized (candidates) {
340            result = new ArrayList<TransportCandidate>(candidates);
341        }
342
343        return result;
344    }
345
346    /**
347     * Get the n-th candidate.
348     *
349     * @return a transport candidate
350     */
351    public TransportCandidate getCandidate(int i) {
352        TransportCandidate cand;
353
354        synchronized (candidates) {
355            cand = candidates.get(i);
356        }
357        return cand;
358    }
359
360    /**
361     * Initialize Transport Resolver and wait until it is complete unitialized.
362     * @throws SmackException 
363     * @throws InterruptedException 
364     */
365    public void initializeAndWait() throws XMPPException, SmackException, InterruptedException {
366        this.initialize();
367        try {
368            LOGGER.fine("Initializing transport resolver...");
369            while (!this.isInitialized()) {
370                LOGGER.fine("Resolver init still pending");
371                Thread.sleep(1000);
372            }
373            LOGGER.fine("Transport resolved");
374        }
375        catch (Exception e) {
376            LOGGER.log(Level.WARNING, "exception", e);
377        }
378    }
379
380    /**
381     * Obtain a free port we can use.
382     *
383     * @return A free port number.
384     */
385    protected int getFreePort() {
386        ServerSocket ss;
387        int freePort = 0;
388
389        for (int i = 0; i < 10; i++) {
390            freePort = (int) (10000 + Math.round(Math.random() * 10000));
391            freePort = freePort % 2 == 0 ? freePort : freePort + 1;
392            try {
393                ss = new ServerSocket(freePort);
394                freePort = ss.getLocalPort();
395                ss.close();
396                return freePort;
397            }
398            catch (IOException e) {
399                LOGGER.log(Level.WARNING, "exception", e);
400            }
401        }
402        try {
403            ss = new ServerSocket(0);
404            freePort = ss.getLocalPort();
405            ss.close();
406        }
407        catch (IOException e) {
408            LOGGER.log(Level.WARNING, "exception", e);
409        }
410        return freePort;
411    }
412}