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.net.InetAddress; 020import java.net.UnknownHostException; 021import java.util.List; 022import java.util.logging.Logger; 023 024/** 025 * ICE Transport candidate. 026 * <p/> 027 * A candidate represents the possible transport for data interchange between 028 * the two endpoints. 029 * 030 * @author Thiago Camargo 031 */ 032public class ICECandidate extends TransportCandidate implements Comparable<ICECandidate> { 033 034 private static final Logger LOGGER = Logger.getLogger(ICECandidate.class.getName()); 035 036 private String id; // An identification 037 038 private String username; 039 040 private int preference; 041 042 private Protocol proto; 043 044 private Channel channel; 045 046 private int network; 047 048 private Type type; 049 050 public enum Type { 051 relay, srflx, prflx, local, host 052 } 053 054 public ICECandidate() { 055 super(); 056 } 057 058 /** 059 * Constructor with the basic elements of a transport definition. 060 * 061 * @param ip the IP address to use as a local address 062 * @param generation used to keep track of the candidates 063 * @param network used for diagnostics (used when the machine has 064 * several NICs) 065 * @param password user name, as it is used in ICE 066 * @param port the port at the candidate IP address 067 * @param username user name, as it is used in ICE 068 * @param preference preference for this transportElement, as it is used 069 * in ICE 070 * @param type type as defined in ICE-12 071 */ 072 public ICECandidate(String ip, int generation, int network, 073 String password, int port, String username, 074 int preference, Type type) { 075 super(ip, port, generation); 076 077 proto = Protocol.UDP; 078 channel = Channel.MYRTPVOICE; 079 080 this.network = network; 081 this.password = password; 082 this.username = username; 083 this.preference = preference; 084 this.type = type; 085 } 086 087 /** 088 * Get the ID 089 * 090 * @return the id 091 */ 092 public String getId() { 093 return id; 094 } 095 096 /** 097 * Set the ID 098 * 099 * @param id the id to set 100 */ 101 public void setId(String id) { 102 this.id = id; 103 } 104 105 /** 106 * Get the protocol used for the transmission 107 * 108 * @return the protocol used for transmission 109 */ 110 public Protocol getProto() { 111 return proto; 112 } 113 114 /** 115 * Set the protocol for the transmission 116 * 117 * @param proto the protocol to use 118 */ 119 public void setProto(Protocol proto) { 120 this.proto = proto; 121 } 122 123 /** 124 * Get the network interface used for this connection 125 * 126 * @return the interface number 127 */ 128 public int getNetwork() { 129 return network; 130 } 131 132 /** 133 * Set the interface for this connection 134 * 135 * @param network the interface number 136 */ 137 public void setNetwork(int network) { 138 this.network = network; 139 } 140 141 /** 142 * Get the username for this transportElement in ICE 143 * 144 * @return a username string 145 */ 146 public String getUsername() { 147 return username; 148 } 149 150 /** 151 * Get the channel 152 * 153 * @return the channel associated 154 */ 155 public Channel getChannel() { 156 return channel; 157 } 158 159 /** 160 * Set the channel for this transportElement 161 * 162 * @param channel the new channel 163 */ 164 public void setChannel(Channel channel) { 165 this.channel = channel; 166 } 167 168 /** 169 * Set the username for this transportElement in ICE 170 * 171 * @param username the username used in ICE 172 */ 173 public void setUsername(String username) { 174 this.username = username; 175 } 176 177 /** 178 * Get the preference number for this transportElement 179 * 180 * @return the preference for this transportElement 181 */ 182 public int getPreference() { 183 return preference; 184 } 185 186 /** 187 * Set the preference order for this transportElement 188 * 189 * @param preference a number identifying the preference (as defined in 190 * ICE) 191 */ 192 public void setPreference(int preference) { 193 this.preference = preference; 194 } 195 196 /** 197 * Get the Candidate Type 198 * 199 * @return candidate Type 200 */ 201 public Type getType() { 202 return type; 203 } 204 205 /** 206 * Set the Candidate Type 207 * 208 * @param type candidate type. 209 */ 210 public void setType(Type type) { 211 this.type = type; 212 } 213 214 /** 215 * Check if a transport candidate is usable. The transport resolver should 216 * check if the transport candidate the other endpoint has provided is 217 * usable. 218 * <p/> 219 * ICE Candidate can check connectivity using UDP echo Test. 220 */ 221 public void check(final List<TransportCandidate> localCandidates) { 222 //TODO candidate is being checked trigger 223 //candidatesChecking.add(cand); 224 225 final ICECandidate checkingCandidate = this; 226 227 Thread checkThread = new Thread(new Runnable() { 228 public void run() { 229 230 final TestResult result = new TestResult(); 231 232 // Media Proxy don't have Echo features. 233 // If its a relayed candidate we assumpt that is NOT Valid while other candidates still being checked. 234 // The negotiator MUST add then in the correct situations 235 if (getType().equals("relay")) { 236 triggerCandidateChecked(false); 237 return; 238 } 239 240 ResultListener resultListener = new ResultListener() { 241 public void testFinished(TestResult testResult, TransportCandidate candidate) { 242 if (testResult.isReachable() && checkingCandidate.equals(candidate)) { 243 result.setResult(true); 244 LOGGER.fine("Candidate reachable: " + candidate.getIp() + ":" + candidate.getPort() + " from " + getIp() +":" + getPort()); 245 } 246 } 247 }; 248 249 for (TransportCandidate candidate : localCandidates) { 250 CandidateEcho echo = candidate.getCandidateEcho(); 251 if (echo != null) { 252 if (candidate instanceof ICECandidate) { 253 ICECandidate iceCandidate = (ICECandidate) candidate; 254 if (iceCandidate.getType().equals(getType())) { 255 try { 256 echo.addResultListener(resultListener); 257 InetAddress address = InetAddress.getByName(getIp()); 258 echo.testASync(checkingCandidate, getPassword()); 259 } 260 catch (UnknownHostException e) { 261 e.printStackTrace(); 262 } 263 } 264 } 265 } 266 } 267 268 for (int i = 0; i < 10 && !result.isReachable(); i++) 269 try { 270 LOGGER.severe("ICE Candidate retry #" + i); 271 Thread.sleep(400); 272 } 273 catch (InterruptedException e) { 274 e.printStackTrace(); 275 } 276 277 for (TransportCandidate candidate : localCandidates) { 278 CandidateEcho echo = candidate.getCandidateEcho(); 279 if (echo != null) { 280 echo.removeResultListener(resultListener); 281 } 282 } 283 284 triggerCandidateChecked(result.isReachable()); 285 286 //TODO candidate is being checked trigger 287 //candidatesChecking.remove(cand); 288 } 289 }, "Transport candidate check"); 290 291 checkThread.setName("Transport candidate test"); 292 checkThread.start(); 293 } 294 295 /* 296 * (non-Javadoc) 297 * 298 * @see java.lang.Object#equals(java.lang.Object) 299 */ 300 public boolean equals(Object obj) { 301 if (this == obj) { 302 return true; 303 } 304 if (!super.equals(obj)) { 305 return false; 306 } 307 if (getClass() != obj.getClass()) { 308 return false; 309 } 310 311 final ICECandidate other = (ICECandidate) obj; 312 if (getChannel() == null) { 313 if (other.getChannel() != null) { 314 return false; 315 } 316 } 317 else if (!getChannel().equals(other.getChannel())) { 318 return false; 319 } 320 if (getId() == null) { 321 if (other.getId() != null) { 322 return false; 323 } 324 } 325 else if (!getId().equals(other.getId())) { 326 return false; 327 } 328 if (getNetwork() != other.getNetwork()) { 329 return false; 330 } 331 if (getPassword() == null) { 332 if (other.getPassword() != null) { 333 return false; 334 } 335 } 336 else if (!getPassword().equals(other.password)) { 337 return false; 338 } 339 if (getPreference() != other.getPreference()) { 340 return false; 341 } 342 if (getProto() == null) { 343 if (other.getProto() != null) { 344 return false; 345 } 346 } 347 else if (!getProto().equals(other.getProto())) { 348 return false; 349 } 350 if (getUsername() == null) { 351 if (other.getUsername() != null) { 352 return false; 353 } 354 } 355 else if (!getUsername().equals(other.getUsername())) { 356 return false; 357 } 358 359 if (getIp() == null) { 360 if (other.getIp() != null) { 361 return false; 362 } 363 } 364 else if (!getIp().equals(other.getIp())) { 365 return false; 366 } 367 368 if (getPort() != other.getPort()) { 369 return false; 370 } 371 372 if (getType() == null) { 373 if (other.getType() != null) { 374 return false; 375 } 376 } 377 else if (!getType().equals(other.getType())) { 378 return false; 379 } 380 381 return true; 382 } 383 384 public boolean isNull() { 385 if (super.isNull()) { 386 return true; 387 } 388 else if (getProto().isNull()) { 389 return true; 390 } 391 else if (getChannel().isNull()) { 392 return true; 393 } 394 return false; 395 } 396 397 /** 398 * Compare the to other Transport candidate. 399 * 400 * @param arg another Transport candidate 401 * @return a negative integer, zero, or a positive integer as this 402 * object is less than, equal to, or greater than the specified 403 * object 404 */ 405 public int compareTo(ICECandidate arg) { 406 if (getPreference() < arg.getPreference()) { 407 return -1; 408 } else if (getPreference() > arg.getPreference()) { 409 return 1; 410 } 411 return 0; 412 } 413 414} 415