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}