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