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