Smack/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/nat/ICECandidate.java

434 lines
12 KiB
Java

/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingleold.nat;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* ICE Transport candidate.
*
* A candidate represents the possible transport for data interchange between
* the two endpoints.
*
* @author Thiago Camargo
*/
public final class ICECandidate extends TransportCandidate implements Comparable<ICECandidate> {
private static final Logger LOGGER = Logger.getLogger(ICECandidate.class.getName());
private String id; // An identification
private String username;
private int preference;
private Protocol proto;
private Channel channel;
private int network;
private Type type;
public enum Type {
relay, srflx, prflx, local, host
}
public ICECandidate() {
super();
}
/**
* Constructor with the basic elements of a transport definition.
*
* @param ip the IP address to use as a local address
* @param generation used to keep track of the candidates
* @param network used for diagnostics (used when the machine has
* several NICs)
* @param password user name, as it is used in ICE
* @param port the port at the candidate IP address
* @param username user name, as it is used in ICE
* @param preference preference for this transportElement, as it is used
* in ICE
* @param type type as defined in ICE-12
*/
public ICECandidate(String ip, int generation, int network,
String password, int port, String username,
int preference, Type type) {
super(ip, port, generation);
proto = Protocol.UDP;
channel = Channel.MYRTPVOICE;
this.network = network;
this.password = password;
this.username = username;
this.preference = preference;
this.type = type;
}
/**
* Get the ID.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Set the ID.
*
* @param id the id to set
*/
public void setId(String id) {
this.id = id;
}
/**
* Get the protocol used for the transmission.
*
* @return the protocol used for transmission
*/
public Protocol getProto() {
return proto;
}
/**
* Set the protocol for the transmission.
*
* @param proto the protocol to use
*/
public void setProto(Protocol proto) {
this.proto = proto;
}
/**
* Get the network interface used for this connection.
*
* @return the interface number
*/
public int getNetwork() {
return network;
}
/**
* Set the interface for this connection.
*
* @param network the interface number
*/
public void setNetwork(int network) {
this.network = network;
}
/**
* Get the username for this transportElement in ICE.
*
* @return a username string
*/
public String getUsername() {
return username;
}
/**
* Get the channel.
*
* @return the channel associated
*/
public Channel getChannel() {
return channel;
}
/**
* Set the channel for this transportElement.
*
* @param channel the new channel
*/
public void setChannel(Channel channel) {
this.channel = channel;
}
/**
* Set the username for this transportElement in ICE.
*
* @param username the username used in ICE
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Get the preference number for this transportElement.
*
* @return the preference for this transportElement
*/
public int getPreference() {
return preference;
}
/**
* Set the preference order for this transportElement.
*
* @param preference a number identifying the preference (as defined in
* ICE)
*/
public void setPreference(int preference) {
this.preference = preference;
}
/**
* Get the Candidate Type.
*
* @return candidate Type
*/
public Type getType() {
return type;
}
/**
* Set the Candidate Type.
*
* @param type candidate type.
*/
public void setType(Type type) {
this.type = type;
}
/**
* Check if a transport candidate is usable. The transport resolver should
* check if the transport candidate the other endpoint has provided is
* usable.
*
* ICE Candidate can check connectivity using UDP echo Test.
*/
@Override
public void check(final List<TransportCandidate> localCandidates) {
// TODO candidate is being checked trigger
// candidatesChecking.add(cand);
final ICECandidate checkingCandidate = this;
Thread checkThread = new Thread(new Runnable() {
@Override
public void run() {
final TestResult result = new TestResult();
// Media Proxy don't have Echo features.
// If its a relayed candidate we assumed that is NOT Valid while other candidates still being checked.
// The negotiator MUST add then in the correct situations
if (getType().equals(Type.relay)) {
triggerCandidateChecked(false);
return;
}
ResultListener resultListener = new ResultListener() {
@Override
public void testFinished(TestResult testResult, TransportCandidate candidate) {
if (testResult.isReachable() && checkingCandidate.equals(candidate)) {
result.setResult(true);
LOGGER.fine("Candidate reachable: " + candidate.getIp() + ":" + candidate.getPort() + " from " + getIp() + ":" + getPort());
}
}
};
for (TransportCandidate candidate : localCandidates) {
CandidateEcho echo = candidate.getCandidateEcho();
if (echo != null) {
if (candidate instanceof ICECandidate) {
ICECandidate iceCandidate = (ICECandidate) candidate;
if (iceCandidate.getType().equals(getType())) {
try {
echo.addResultListener(resultListener);
InetAddress address = InetAddress.getByName(getIp());
echo.testASync(checkingCandidate, getPassword());
}
catch (UnknownHostException e) {
LOGGER.log(Level.WARNING, "exception", e);
}
}
}
}
}
for (int i = 0; i < 10 && !result.isReachable(); i++)
try {
LOGGER.severe("ICE Candidate retry #" + i);
Thread.sleep(400);
}
catch (InterruptedException e) {
LOGGER.log(Level.WARNING, "exception", e);
}
for (TransportCandidate candidate : localCandidates) {
CandidateEcho echo = candidate.getCandidateEcho();
if (echo != null) {
echo.removeResultListener(resultListener);
}
}
triggerCandidateChecked(result.isReachable());
// TODO candidate is being checked trigger
// candidatesChecking.remove(cand);
}
}, "Transport candidate check");
checkThread.setName("Transport candidate test");
checkThread.start();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ICECandidate other = (ICECandidate) obj;
if (getChannel() == null) {
if (other.getChannel() != null) {
return false;
}
}
else if (!getChannel().equals(other.getChannel())) {
return false;
}
if (getId() == null) {
if (other.getId() != null) {
return false;
}
}
else if (!getId().equals(other.getId())) {
return false;
}
if (getNetwork() != other.getNetwork()) {
return false;
}
if (getPassword() == null) {
if (other.getPassword() != null) {
return false;
}
}
else if (!getPassword().equals(other.password)) {
return false;
}
if (getPreference() != other.getPreference()) {
return false;
}
if (getProto() == null) {
if (other.getProto() != null) {
return false;
}
}
else if (!getProto().equals(other.getProto())) {
return false;
}
if (getUsername() == null) {
if (other.getUsername() != null) {
return false;
}
}
else if (!getUsername().equals(other.getUsername())) {
return false;
}
if (getIp() == null) {
if (other.getIp() != null) {
return false;
}
}
else if (!getIp().equals(other.getIp())) {
return false;
}
if (getPort() != other.getPort()) {
return false;
}
if (getType() == null) {
if (other.getType() != null) {
return false;
}
}
else if (!getType().equals(other.getType())) {
return false;
}
return true;
}
@Override
public int hashCode() {
int res = 37;
res = 37 * res + (getChannel() == null ? 0 : getChannel().hashCode());
res = 37 * res + (getId() == null ? 0 : getId().hashCode());
res = 37 * res + getNetwork();
res = 37 * res + (getPassword() == null ? 0 : getPassword().hashCode());
res = 37 * res + getPreference();
res = 37 * res + (getProto() == null ? 0 : getProto().hashCode());
res = 37 * res + (getUsername() == null ? 0 : getUsername().hashCode());
res = 37 * res + (getIp() == null ? 0 : getIp().hashCode());
res = 37 * res + getPort();
res = 37 * res + (getType() == null ? 0 : getType().hashCode());
return res;
}
@Override
public boolean isNull() {
if (super.isNull()) {
return true;
}
else if (getProto().isNull()) {
return true;
}
else if (getChannel().isNull()) {
return true;
}
return false;
}
/**
* Compare the to other Transport candidate.
*
* @param arg another Transport candidate
* @return a negative integer, zero, or a positive integer as this
* object is less than, equal to, or greater than the specified
* object
*/
@Override
public int compareTo(ICECandidate arg) {
if (getPreference() < arg.getPreference()) {
return -1;
} else if (getPreference() > arg.getPreference()) {
return 1;
}
return 0;
}
}