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}