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