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 * @throws InterruptedException 094 */ 095 public abstract void initialize() throws XMPPException, SmackException, InterruptedException; 096 097 /** 098 * Start a the resolution. 099 * @throws InterruptedException 100 */ 101 public abstract void resolve(JingleSession session) throws XMPPException, SmackException, InterruptedException; 102 103 /** 104 * Clear the list of candidates and start a new resolution process. 105 * 106 * @throws XMPPException 107 */ 108 public void clear() throws XMPPException { 109 cancel(); 110 candidates.clear(); 111 } 112 113 /** 114 * Cancel any asynchronous resolution operation. 115 */ 116 public abstract void cancel() throws XMPPException; 117 118 /** 119 * Return true if the resolver is working. 120 * 121 * @return true if the resolver is working. 122 */ 123 public boolean isResolving() { 124 return resolving; 125 } 126 127 /** 128 * Return true if the resolver has finished the search for transport 129 * candidates. 130 * 131 * @return true if the search has finished 132 */ 133 public boolean isResolved() { 134 return resolved; 135 } 136 137 /** 138 * Set the Transport Resolver as initialized. 139 */ 140 public synchronized void setInitialized() { 141 initialized = true; 142 } 143 144 /** 145 * Check if the Transport Resolver is initialized. 146 * 147 * @return true if initialized 148 */ 149 public synchronized boolean isInitialized() { 150 return initialized; 151 } 152 153 /** 154 * Indicate the beginning of the resolution process. This method must be 155 * used by subclasses at the beginning of their resolve() method. 156 */ 157 protected synchronized void setResolveInit() { 158 resolved = false; 159 resolving = true; 160 161 triggerResolveInit(); 162 } 163 164 /** 165 * Indicate the end of the resolution process. This method must be used by 166 * subclasses at the beginning of their resolve() method. 167 */ 168 protected synchronized void setResolveEnd() { 169 resolved = true; 170 resolving = false; 171 172 triggerResolveEnd(); 173 } 174 175 // Listeners management 176 177 /** 178 * Add a transport resolver listener. 179 * 180 * @param li The transport resolver listener to be added. 181 */ 182 public void addListener(TransportResolverListener li) { 183 synchronized (listeners) { 184 listeners.add(li); 185 } 186 } 187 188 /** 189 * Removes a transport resolver listener. 190 * 191 * @param li The transport resolver listener to be removed 192 */ 193 public void removeListener(TransportResolverListener li) { 194 synchronized (listeners) { 195 listeners.remove(li); 196 } 197 } 198 199 /** 200 * Get the list of listeners. 201 * 202 * @return the list of listeners 203 */ 204 public ArrayList<TransportResolverListener> getListenersList() { 205 synchronized (listeners) { 206 return new ArrayList<>(listeners); 207 } 208 } 209 210 /** 211 * Trigger a new candidate added event. 212 * 213 * @param cand The candidate added to the list of candidates. 214 * @throws NotConnectedException 215 * @throws InterruptedException 216 */ 217 protected void triggerCandidateAdded(TransportCandidate cand) throws NotConnectedException, InterruptedException { 218 Iterator<TransportResolverListener> iter = getListenersList().iterator(); 219 while (iter.hasNext()) { 220 TransportResolverListener trl = iter.next(); 221 if (trl instanceof TransportResolverListener.Resolver) { 222 TransportResolverListener.Resolver li = (TransportResolverListener.Resolver) trl; 223 LOGGER.fine("triggerCandidateAdded : " + cand.getLocalIp()); 224 li.candidateAdded(cand); 225 } 226 } 227 } 228 229 /** 230 * Trigger a event notifying the initialization of the resolution process. 231 */ 232 private void triggerResolveInit() { 233 Iterator<TransportResolverListener> iter = getListenersList().iterator(); 234 while (iter.hasNext()) { 235 TransportResolverListener trl = iter.next(); 236 if (trl instanceof TransportResolverListener.Resolver) { 237 TransportResolverListener.Resolver li = (TransportResolverListener.Resolver) trl; 238 li.init(); 239 } 240 } 241 } 242 243 /** 244 * Trigger a event notifying the obtainment of all the candidates. 245 */ 246 private void triggerResolveEnd() { 247 Iterator<TransportResolverListener> iter = getListenersList().iterator(); 248 while (iter.hasNext()) { 249 TransportResolverListener trl = iter.next(); 250 if (trl instanceof TransportResolverListener.Resolver) { 251 TransportResolverListener.Resolver li = (TransportResolverListener.Resolver) trl; 252 li.end(); 253 } 254 } 255 } 256 257 // Candidates management 258 259 /** 260 * Clear the list of candidate 261 */ 262 protected void clearCandidates() { 263 synchronized (candidates) { 264 candidates.clear(); 265 } 266 } 267 268 /** 269 * Add a new transport candidate 270 * 271 * @param cand The candidate to add 272 * @throws NotConnectedException 273 * @throws InterruptedException 274 */ 275 protected void addCandidate(TransportCandidate cand) throws NotConnectedException, InterruptedException { 276 synchronized (candidates) { 277 if (!candidates.contains(cand)) 278 candidates.add(cand); 279 } 280 281 // Notify the listeners 282 triggerCandidateAdded(cand); 283 } 284 285 /** 286 * Get an iterator for the list of candidates. 287 * 288 * @return an iterator 289 */ 290 public Iterator<TransportCandidate> getCandidates() { 291 synchronized (candidates) { 292 return Collections.unmodifiableList(new ArrayList<>(candidates)).iterator(); 293 } 294 } 295 296 /** 297 * Get the candidate with the highest preference. 298 * 299 * @return The best candidate, according to the preference order. 300 */ 301 public TransportCandidate getPreferredCandidate() { 302 TransportCandidate result = null; 303 304 ArrayList<ICECandidate> cands = new ArrayList<>(); 305 for (TransportCandidate tpcan : getCandidatesList()) { 306 if (tpcan instanceof ICECandidate) 307 cands.add((ICECandidate) tpcan); 308 } 309 310 // (ArrayList<ICECandidate>) getCandidatesList(); 311 if (cands.size() > 0) { 312 Collections.sort(cands); 313 // Return the last candidate 314 result = cands.get(cands.size() - 1); 315 LOGGER.fine("Result: " + result.getIp()); 316 } 317 318 return result; 319 } 320 321 /** 322 * Get the number of transport candidates. 323 * 324 * @return The length of the transport candidates list. 325 */ 326 public int getCandidateCount() { 327 synchronized (candidates) { 328 return candidates.size(); 329 } 330 } 331 332 /** 333 * Get the list of candidates. 334 * 335 * @return the list of transport candidates 336 */ 337 public List<TransportCandidate> getCandidatesList() { 338 List<TransportCandidate> result; 339 340 synchronized (candidates) { 341 result = new ArrayList<>(candidates); 342 } 343 344 return result; 345 } 346 347 /** 348 * Get the n-th candidate. 349 * 350 * @return a transport candidate 351 */ 352 public TransportCandidate getCandidate(int i) { 353 TransportCandidate cand; 354 355 synchronized (candidates) { 356 cand = candidates.get(i); 357 } 358 return cand; 359 } 360 361 /** 362 * Initialize Transport Resolver and wait until it is completely uninitialized. 363 * @throws SmackException 364 * @throws InterruptedException 365 */ 366 public void initializeAndWait() throws XMPPException, SmackException, InterruptedException { 367 this.initialize(); 368 try { 369 LOGGER.fine("Initializing transport resolver..."); 370 while (!this.isInitialized()) { 371 LOGGER.fine("Resolver init still pending"); 372 Thread.sleep(1000); 373 } 374 LOGGER.fine("Transport resolved"); 375 } 376 catch (Exception e) { 377 LOGGER.log(Level.WARNING, "exception", e); 378 } 379 } 380 381 /** 382 * Obtain a free port we can use. 383 * 384 * @return A free port number. 385 */ 386 protected int getFreePort() { 387 ServerSocket ss; 388 int freePort = 0; 389 390 for (int i = 0; i < 10; i++) { 391 freePort = (int) (10000 + Math.round(Math.random() * 10000)); 392 freePort = freePort % 2 == 0 ? freePort : freePort + 1; 393 try { 394 ss = new ServerSocket(freePort); 395 freePort = ss.getLocalPort(); 396 ss.close(); 397 return freePort; 398 } 399 catch (IOException e) { 400 LOGGER.log(Level.WARNING, "exception", e); 401 } 402 } 403 try { 404 ss = new ServerSocket(0); 405 freePort = ss.getLocalPort(); 406 ss.close(); 407 } 408 catch (IOException e) { 409 LOGGER.log(Level.WARNING, "exception", e); 410 } 411 return freePort; 412 } 413}