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 final 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    @SuppressWarnings("UnusedVariable")
223    @Override
224    public void check(final List<TransportCandidate> localCandidates) {
225        // TODO candidate is being checked trigger
226        // candidatesChecking.add(cand);
227
228        final ICECandidate checkingCandidate = this;
229
230        Thread checkThread = new Thread(new Runnable() {
231            @Override
232            public void run() {
233
234                final TestResult result = new TestResult();
235
236                // Media Proxy don't have Echo features.
237                // If its a relayed candidate we assumed that is NOT Valid while other candidates still being checked.
238                // The negotiator MUST add then in the correct situations
239                if (getType().equals(Type.relay)) {
240                    triggerCandidateChecked(false);
241                    return;
242                }
243
244                ResultListener resultListener = new ResultListener() {
245                    @Override
246                    public void testFinished(TestResult testResult, TransportCandidate candidate) {
247                        if (testResult.isReachable() && checkingCandidate.equals(candidate)) {
248                            result.setResult(true);
249                            LOGGER.fine("Candidate reachable: " + candidate.getIp() + ":" + candidate.getPort() + " from " + getIp() + ":" + getPort());
250                        }
251                    }
252                };
253
254                for (TransportCandidate candidate : localCandidates) {
255                    CandidateEcho echo = candidate.getCandidateEcho();
256                    if (echo != null) {
257                        if (candidate instanceof ICECandidate) {
258                            ICECandidate iceCandidate = (ICECandidate) candidate;
259                            if (iceCandidate.getType().equals(getType())) {
260                                try {
261                                    echo.addResultListener(resultListener);
262                                    InetAddress address = InetAddress.getByName(getIp());
263                                    echo.testASync(checkingCandidate, getPassword());
264                                }
265                                catch (UnknownHostException e) {
266                                    LOGGER.log(Level.WARNING, "exception", e);
267                                }
268                            }
269                        }
270                    }
271                }
272
273                for (int i = 0; i < 10 && !result.isReachable(); i++)
274                    try {
275                        LOGGER.severe("ICE Candidate retry #" + i);
276                        Thread.sleep(400);
277                    }
278                    catch (InterruptedException e) {
279                        LOGGER.log(Level.WARNING, "exception", e);
280                    }
281
282                for (TransportCandidate candidate : localCandidates) {
283                    CandidateEcho echo = candidate.getCandidateEcho();
284                    if (echo != null) {
285                        echo.removeResultListener(resultListener);
286                    }
287                }
288
289                triggerCandidateChecked(result.isReachable());
290
291                // TODO candidate is being checked trigger
292                // candidatesChecking.remove(cand);
293            }
294        }, "Transport candidate check");
295
296        checkThread.setName("Transport candidate test");
297        checkThread.start();
298    }
299
300    @Override
301    public boolean equals(Object obj) {
302        if (this == obj) {
303            return true;
304        }
305        if (!super.equals(obj)) {
306            return false;
307        }
308        if (getClass() != obj.getClass()) {
309            return false;
310        }
311
312        final ICECandidate other = (ICECandidate) obj;
313        if (getChannel() == null) {
314            if (other.getChannel() != null) {
315                return false;
316            }
317        }
318        else if (!getChannel().equals(other.getChannel())) {
319            return false;
320        }
321        if (getId() == null) {
322            if (other.getId() != null) {
323                return false;
324            }
325        }
326        else if (!getId().equals(other.getId())) {
327            return false;
328        }
329        if (getNetwork() != other.getNetwork()) {
330            return false;
331        }
332        if (getPassword() == null) {
333            if (other.getPassword() != null) {
334                return false;
335            }
336        }
337        else if (!getPassword().equals(other.password)) {
338            return false;
339        }
340        if (getPreference() != other.getPreference()) {
341            return false;
342        }
343        if (getProto() == null) {
344            if (other.getProto() != null) {
345                return false;
346            }
347        }
348        else if (!getProto().equals(other.getProto())) {
349            return false;
350        }
351        if (getUsername() == null) {
352            if (other.getUsername() != null) {
353                return false;
354            }
355        }
356        else if (!getUsername().equals(other.getUsername())) {
357            return false;
358        }
359
360        if (getIp() == null) {
361            if (other.getIp() != null) {
362                return false;
363            }
364        }
365        else if (!getIp().equals(other.getIp())) {
366            return false;
367        }
368
369        if (getPort() != other.getPort()) {
370            return false;
371        }
372
373        if (getType() == null) {
374            if (other.getType() != null) {
375                return false;
376            }
377        }
378        else if (!getType().equals(other.getType())) {
379            return false;
380        }
381
382        return true;
383    }
384
385    @Override
386    public int hashCode() {
387        int res = 37;
388        res = 37 * res + (getChannel() == null ? 0 : getChannel().hashCode());
389        res = 37 * res + (getId() == null ? 0 : getId().hashCode());
390        res = 37 * res + getNetwork();
391        res = 37 * res + (getPassword() == null ? 0 : getPassword().hashCode());
392        res = 37 * res + getPreference();
393        res = 37 * res + (getProto() == null ? 0 : getProto().hashCode());
394        res = 37 * res + (getUsername() == null ? 0 : getUsername().hashCode());
395        res = 37 * res + (getIp() == null ? 0 : getIp().hashCode());
396        res = 37 * res + getPort();
397        res = 37 * res + (getType() == null ? 0 : getType().hashCode());
398        return res;
399    }
400
401    @Override
402    public boolean isNull() {
403        if (super.isNull()) {
404            return true;
405        }
406        else if (getProto().isNull()) {
407            return true;
408        }
409        else if (getChannel().isNull()) {
410            return true;
411        }
412        return false;
413    }
414
415    /**
416     * Compare the to other Transport candidate.
417     *
418     * @param arg another Transport candidate
419     * @return a negative integer, zero, or a positive integer as this
420     *         object is less than, equal to, or greater than the specified
421     *         object
422     */
423    @Override
424    public int compareTo(ICECandidate arg) {
425        if (getPreference() < arg.getPreference()) {
426            return -1;
427        } else if (getPreference() > arg.getPreference()) {
428            return 1;
429        }
430        return 0;
431    }
432
433}
434