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