From 41fbc3906a62fe07fa4739a9f333aa3d343f1650 Mon Sep 17 00:00:00 2001 From: Thiago Camargo Date: Sun, 11 Mar 2007 22:11:42 +0000 Subject: [PATCH] Jingle Session Establishment Refactoring git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@7457 b35dd754-fafc-0310-a699-88a17e54d16e --- .../smackx/jingle/JingleManager.java | 2 +- .../smackx/jingle/nat/ICECandidate.java | 97 +++++++++++-------- .../smackx/jingle/nat/ResultListener.java | 32 ++++++ .../smackx/jingle/nat/TestResult.java | 72 ++++++++++++++ .../smackx/jingle/nat/TransportCandidate.java | 85 ++++++++++++---- .../jingle/nat/TransportNegotiator.java | 71 +++++++++++--- .../smackx/jingle/nat/TransportResolver.java | 2 +- 7 files changed, 289 insertions(+), 72 deletions(-) create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/nat/ResultListener.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TestResult.java diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/JingleManager.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/JingleManager.java index 53fd398ba..e8d35b65b 100644 --- a/jingle/extension/source/org/jivesoftware/smackx/jingle/JingleManager.java +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/JingleManager.java @@ -496,7 +496,7 @@ public class JingleManager implements JingleSessionListener { jingleSession.removeListener(this); jingleSessions.remove(jingleSession); jingleSession.close(); - System.err.println("Declined"); + System.err.println("Declined:"+reason); } public void sessionRedirected(String redirection, JingleSession jingleSession) { diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/ICECandidate.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/ICECandidate.java index 051f1f5e5..7b47e0a3a 100644 --- a/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/ICECandidate.java +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/ICECandidate.java @@ -98,8 +98,8 @@ public class ICECandidate extends TransportCandidate implements Comparable { * @param type type as defined in ICE-12 */ public ICECandidate(String ip, int generation, int network, - String password, int port, String username, - int preference, String type) { + String password, int port, String username, + int preference, String type) { super(ip, port, generation); proto = Protocol.UDP; @@ -252,50 +252,61 @@ public class ICECandidate extends TransportCandidate implements Comparable { Thread checkThread = new Thread(new Runnable() { public void run() { - boolean isUsable = false; + + final TestResult result = new TestResult(); // Media Proxy donīt have Echo features. - // If its a relayed candidate we assumpt that is Checked. + // If its a relayed candidate we assumpt that is NOT Valid while other candidates still being checked. + // The negotiator MUST add then in the correct situations if (getType().equals("relay")) { - try { - Thread.sleep(TransportNegotiator.CANDIDATES_ACCEPT_PERIOD*2); - } - catch (InterruptedException e) { - // Do Nothing - } - triggerCandidateChecked(true); + triggerCandidateChecked(false); return; } + ResultListener resultListener = new ResultListener() { + public void testFinished(TestResult testResult) { + if (testResult.isReachable()) { + result.setResult(true); + } + } + }; + for (TransportCandidate candidate : localCandidates) { CandidateEcho echo = candidate.getCandidateEcho(); if (echo != null) { - try { - InetAddress address = InetAddress.getByName(getIp()); - if (echo.test(address, getPort(),2000)) isUsable = true; - if (isUsable) break; - } - catch (UnknownHostException e) { - e.printStackTrace(); + if (candidate instanceof ICECandidate) { + ICECandidate iceCandidate = (ICECandidate) candidate; + if (!iceCandidate.getType().equals("relay")) { + try { + echo.addResultListener(resultListener); + InetAddress address = InetAddress.getByName(getIp()); + echo.testASync(address, getPort()); + } + catch (UnknownHostException e) { + e.printStackTrace(); + } + } } } } - if (isUsable) { - System.out.println("Checked using UDP Echo:" + getLocalIp()); - triggerCandidateChecked(isUsable); - return; + for (int i = 0; i < 10 && !result.isReachable(); i++) + try { + System.err.println(i); + Thread.sleep(300); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + + for (TransportCandidate candidate : localCandidates) { + CandidateEcho echo = candidate.getCandidateEcho(); + if (echo != null) { + echo.removeResultListener(resultListener); + } } - InetAddress candAddress; - try { - candAddress = InetAddress.getByName(getIp()); - isUsable = candAddress.isReachable(TransportResolver.CHECK_TIMEOUT); - } - catch (Exception e) { - isUsable = false; - } - triggerCandidateChecked(isUsable); + triggerCandidateChecked(result.isReachable()); //TODO candidate is being checked trigger //candidatesChecking.remove(cand); @@ -327,14 +338,16 @@ public class ICECandidate extends TransportCandidate implements Comparable { if (other.getChannel() != null) { return false; } - } else if (!getChannel().equals(other.getChannel())) { + } + else if (!getChannel().equals(other.getChannel())) { return false; } if (getId() == null) { if (other.getId() != null) { return false; } - } else if (!getId().equals(other.getId())) { + } + else if (!getId().equals(other.getId())) { return false; } if (getNetwork() != other.getNetwork()) { @@ -344,7 +357,8 @@ public class ICECandidate extends TransportCandidate implements Comparable { if (other.getPassword() != null) { return false; } - } else if (!getPassword().equals(other.password)) { + } + else if (!getPassword().equals(other.password)) { return false; } if (getPreference() != other.getPreference()) { @@ -354,14 +368,16 @@ public class ICECandidate extends TransportCandidate implements Comparable { if (other.getProto() != null) { return false; } - } else if (!getProto().equals(other.getProto())) { + } + else if (!getProto().equals(other.getProto())) { return false; } if (getUsername() == null) { if (other.getUsername() != null) { return false; } - } else if (!getUsername().equals(other.getUsername())) { + } + else if (!getUsername().equals(other.getUsername())) { return false; } return true; @@ -370,9 +386,11 @@ public class ICECandidate extends TransportCandidate implements Comparable { public boolean isNull() { if (super.isNull()) { return true; - } else if (getProto().isNull()) { + } + else if (getProto().isNull()) { return true; - } else if (getChannel().isNull()) { + } + else if (getChannel().isNull()) { return true; } return false; @@ -391,7 +409,8 @@ public class ICECandidate extends TransportCandidate implements Comparable { ICECandidate tc = (ICECandidate) arg; if (getPreference() < tc.getPreference()) { return -1; - } else if (getPreference() > tc.getPreference()) { + } + else if (getPreference() > tc.getPreference()) { return 1; } } diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/ResultListener.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/ResultListener.java new file mode 100644 index 000000000..b8f2cf752 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/ResultListener.java @@ -0,0 +1,32 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2005 Jive Software. + * + * All rights reserved. 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.jingle.nat; + +/** + * Listener for ECHO Test Results + * + * @author Thiago Camargo + */ +public interface ResultListener { + + public void testFinished(TestResult result); + +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TestResult.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TestResult.java new file mode 100644 index 000000000..e253b271b --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TestResult.java @@ -0,0 +1,72 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2006 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smackx.jingle.nat; + +/** + * Result of an ECHO Test + * + * @author Thiago Camargo + */ +public class TestResult { + + private boolean result = false; + + public boolean isReachable() { + return result; + } + + public void setResult(boolean result) { + this.result = result; + } +} + diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TransportCandidate.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TransportCandidate.java index b064c4dd2..6cc6bcc79 100644 --- a/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TransportCandidate.java +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TransportCandidate.java @@ -649,12 +649,13 @@ public abstract class TransportCandidate { DatagramSocket socket = null; byte password[] = null; List listeners = new ArrayList(); + List resultListeners = new ArrayList(); boolean enabled = true; boolean ended = false; long tries = 10; public CandidateEcho(TransportCandidate candidate) throws UnknownHostException, SocketException { - this.socket = new DatagramSocket(candidate.getPort(), InetAddress.getByName("0.0.0.0")); + this.socket = new DatagramSocket(candidate.getPort(), InetAddress.getByName(candidate.getLocalIp())); Random r = new Random(); password = longToByteArray((Math.abs(r.nextLong()))); } @@ -712,27 +713,30 @@ public abstract class TransportCandidate { socket.close(); } + private void fireTestResult(TestResult testResult) { + for (ResultListener resultListener : resultListeners) + resultListener.testFinished(testResult); + } + public boolean test(final InetAddress address, final int port) { return test(address, port, 2000); } public boolean test(final InetAddress address, final int port, int timeout) { - ended=false; + ended = false; - final TestResults testResults = new TestResults(); + final TestResult testResult = new TestResult(); DatagramListener listener = new DatagramListener() { public boolean datagramReceived(DatagramPacket datagramPacket) { if (datagramPacket.getAddress().equals(address) && datagramPacket.getPort() == port) { if (Arrays.equals(datagramPacket.getData(), password)) { - testResults.setResult(true); + testResult.setResult(true); ended = true; return true; } } - testResults.setResult(false); - ended = true; return false; } }; @@ -765,7 +769,59 @@ public abstract class TransportCandidate { this.removeListener(listener); - return testResults.isReachable(); + return testResult.isReachable(); + } + + public void testASync(final InetAddress address, final int port) { + + Thread thread = new Thread(new Runnable() { + + public void run() { + + DatagramListener listener = new DatagramListener() { + public boolean datagramReceived(DatagramPacket datagramPacket) { + if (datagramPacket.getAddress().equals(address) && datagramPacket.getPort() == port) { + if (Arrays.equals(datagramPacket.getData(), password)) { + TestResult testResult = new TestResult(); + testResult.setResult(true); + fireTestResult(testResult); + ended = true; + return true; + } + } + return false; + } + }; + + addListener(listener); + + DatagramPacket packet = new DatagramPacket(password, password.length); + + packet.setAddress(address); + packet.setPort(port); + + long delay = 200; + + try { + for (int i = 0; i < tries; i++) { + socket.send(packet); + if (ended) break; + try { + Thread.sleep(delay); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + catch (IOException e) { + // Do Nothing + } + + removeListener(listener); + } + }); + thread.start(); } public void addListener(DatagramListener listener) { @@ -776,17 +832,12 @@ public abstract class TransportCandidate { listeners.remove(listener); } - public class TestResults { + public void addResultListener(ResultListener resultListener) { + resultListeners.add(resultListener); + } - private boolean result=false; - - public boolean isReachable() { - return result; - } - - public void setResult(boolean result) { - this.result = result; - } + public void removeResultListener(ResultListener resultListener) { + resultListeners.remove(resultListener); } } diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TransportNegotiator.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TransportNegotiator.java index 50b980910..b13fe5017 100644 --- a/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TransportNegotiator.java +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TransportNegotiator.java @@ -52,7 +52,7 @@ public abstract class TransportNegotiator extends JingleNegotiator { // The time we give to the candidates check before we accept or decline the // transport (in milliseconds) - public final static int CANDIDATES_ACCEPT_PERIOD = 3000; + public final static int CANDIDATES_ACCEPT_PERIOD = 4000; // The session this nenotiator belongs to private final JingleSession session; @@ -69,6 +69,9 @@ public abstract class TransportNegotiator extends JingleNegotiator { // Valid remote candidates private final List validRemoteCandidates = new ArrayList(); + // Accepted Remote Candidates + private final List acceptedRemoteCandidates = new ArrayList(); + // The best local candidate we have offered (and accepted by the other part) private TransportCandidate acceptedLocalCandidate; @@ -165,10 +168,6 @@ public abstract class TransportNegotiator extends JingleNegotiator { public void close() { super.close(); - for (TransportCandidate candidate : offeredCandidates) - if (candidate.getCandidateEcho() != null) - candidate.removeCandidateEcho(); - } public List getOfferedCandidates() { @@ -283,10 +282,10 @@ public abstract class TransportNegotiator extends JingleNegotiator { // Sleep for some time, waiting for the candidates checks - int totalTime = (CANDIDATES_ACCEPT_PERIOD + (TransportResolver.CHECK_TIMEOUT * (resolver.getCandidatesList().size() + 1))); + int totalTime = (CANDIDATES_ACCEPT_PERIOD + TransportResolver.CHECK_TIMEOUT); int tries = (int) Math.ceil(totalTime / 1000); - for (int i = 0; i < tries; i++) { + for (int i = 0; i < tries - 2; i++) { try { Thread.sleep(1000); } @@ -301,18 +300,64 @@ public abstract class TransportNegotiator extends JingleNegotiator { if (bestRemote != null && (state == pending || state == active)) { // Accepting the remote candidate - Jingle jout = new Jingle(Jingle.Action.TRANSPORTACCEPT); - jout.addTransport(getJingleTransport(bestRemote)); - - // Send the packet - js.sendFormattedJingle(jin, jout); + if (!acceptedRemoteCandidates.contains(bestRemote)) { + Jingle jout = new Jingle(Jingle.Action.TRANSPORTACCEPT); + jout.addTransport(getJingleTransport(bestRemote)); + // Send the packet + js.sendFormattedJingle(jin, jout); + acceptedRemoteCandidates.add(bestRemote); + } if (isEstablished()) { setState(active); break; } } } + + // Once we are in pending state, look for any valid remote + // candidate, and send an "accept" if we have one... + TransportCandidate bestRemote = getBestRemoteCandidate(); + + if (bestRemote == null) { + for (TransportCandidate candidate : remoteCandidates) { + if (candidate instanceof ICECandidate) { + ICECandidate iceCandidate = (ICECandidate) candidate; + if (iceCandidate.getType().equals("relay")) { + //TODO Check if the relay is reacheable + addValidRemoteCandidate(iceCandidate); + } + } + } + + } + + for (int i = 0; i < 2; i++) { + try { + Thread.sleep(1000); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + + bestRemote = getBestRemoteCandidate(); + State state = getState(); + if (bestRemote != null && (state == pending || state == active)) { + if (!acceptedRemoteCandidates.contains(bestRemote)) { + Jingle jout = new Jingle(Jingle.Action.TRANSPORTACCEPT); + jout.addTransport(getJingleTransport(bestRemote)); + + // Send the packet + js.sendFormattedJingle(jin, jout); + acceptedRemoteCandidates.add(bestRemote); + } + if (isEstablished()) { + setState(active); + break; + } + } + } + if (getState() == null || !getState().equals(active)) { try { session.terminate(); @@ -698,9 +743,7 @@ public abstract class TransportNegotiator extends JingleNegotiator { setAcceptedLocalCandidate(cand); if (isEstablished()) { - System.out.println("SET ACTIVE"); - setState(active); } } diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TransportResolver.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TransportResolver.java index baba07a51..fb34a28d2 100644 --- a/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TransportResolver.java +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/nat/TransportResolver.java @@ -89,7 +89,7 @@ public abstract class TransportResolver { public Type type = Type.rawupd; // the time, in milliseconds, before a check aborts - public static final int CHECK_TIMEOUT = 4000; + public static final int CHECK_TIMEOUT = 5000; // Listeners for events private final ArrayList listeners = new ArrayList();