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