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