[Piper-net-devel] SF.net SVN: piper-net:[16] trunk/jnatlib/src/net/jnatlib/stun
Status: Pre-Alpha
Brought to you by:
rdodgen
From: <rd...@us...> - 2009-05-20 06:04:09
|
Revision: 16 http://piper-net.svn.sourceforge.net/piper-net/?rev=16&view=rev Author: rdodgen Date: 2009-05-20 06:03:44 +0000 (Wed, 20 May 2009) Log Message: ----------- StunClient is pretty much done and working. Has a main method which runs a quick test; should be moved to a unit test. sendMessage now has a variant which returns a Future. Instead of specifying a callback, one can now block on responses. Added StunBindingHelper, just a convenience class for getting an InetSocketAddress and not worrying about STUN messages. Modified Paths: -------------- trunk/jnatlib/src/net/jnatlib/stun/StunClient.java trunk/jnatlib/src/net/jnatlib/stun/StunMessageAttribute.java Added Paths: ----------- trunk/jnatlib/src/net/jnatlib/stun/StunBindingHelper.java Added: trunk/jnatlib/src/net/jnatlib/stun/StunBindingHelper.java =================================================================== --- trunk/jnatlib/src/net/jnatlib/stun/StunBindingHelper.java (rev 0) +++ trunk/jnatlib/src/net/jnatlib/stun/StunBindingHelper.java 2009-05-20 06:03:44 UTC (rev 16) @@ -0,0 +1,102 @@ +package net.jnatlib.stun; + +import java.net.InetSocketAddress; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import net.jnatlib.stun.StunMessage.MessageType; + +public class StunBindingHelper { + private final StunClient c; + private InetSocketAddress defaultServer = null; + + public StunBindingHelper(StunClient client) { + if (client == null) + throw new IllegalArgumentException("Client can't be null"); + c = client; + } + + public void setDefaultServer(InetSocketAddress defaultServer) { + this.defaultServer = defaultServer; + } + + /** + * @return Address contained in m's XOR_MAPPED_ADDRESS or MAPPED_ADDRESS, default null + */ + public static InetSocketAddress getBindingFromMessage(StunMessage m) { + StunMessageAttribute ma = + m.getAttributes().get(StunMessageAttribute.AttributeType.MAPPED_ADDRESS); + + if (ma != null) + return ma.getAsEndpoint(); + + ma = m.getAttributes().get(StunMessageAttribute.AttributeType.XOR_MAPPED_ADDRESS); + + if (ma != null) + return ma.getAsEndpoint(); + + return null; + } + + public Future<InetSocketAddress> getPublicAddress(InetSocketAddress server) { + StunMessage m = new StunMessage(MessageType.BIND_REQUEST); + final Future<StunMessage> futureResponse = c.sendRequest(m, server); + + return new Future<InetSocketAddress>() { + public boolean cancel(boolean mayInterruptIfRunning) { + return futureResponse.cancel(mayInterruptIfRunning); + } + + public InetSocketAddress get() throws InterruptedException, + ExecutionException { + + StunMessage response = futureResponse.get(); + return getResultForMessage(response); + + } + + public InetSocketAddress get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, + TimeoutException { + StunMessage response = futureResponse.get(timeout, unit); + return getResultForMessage(response); + } + + //Shared logic for get methods + private InetSocketAddress getResultForMessage(StunMessage response) + throws ExecutionException { + + InetSocketAddress responseAddr = + StunBindingHelper.getBindingFromMessage(response); + + if (responseAddr == null) + throw new ExecutionException( + new Exception("Message didn't contain a mapped address")); + + return responseAddr; + } + + public boolean isCancelled() { + return futureResponse.isCancelled(); + } + + public boolean isDone() { + return futureResponse.isCancelled(); + } + + }; + + } + + public Future<InetSocketAddress> getPublicAddress() { + //TODO: Better exception type + if (this.defaultServer == null) + throw new RuntimeException("No default server set"); + return getPublicAddress(defaultServer); + } + +} + + Modified: trunk/jnatlib/src/net/jnatlib/stun/StunClient.java =================================================================== --- trunk/jnatlib/src/net/jnatlib/stun/StunClient.java 2009-03-15 19:52:04 UTC (rev 15) +++ trunk/jnatlib/src/net/jnatlib/stun/StunClient.java 2009-05-20 06:03:44 UTC (rev 16) @@ -12,12 +12,16 @@ import java.io.*; import java.net.*; import java.util.Date; -import java.util.Enumeration; -import java.util.Hashtable; import java.util.Map; +import java.util.concurrent.CancellationException; import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -71,6 +75,19 @@ logger.info("Started client on local port " + sock.getLocalPort()); } + public void stop() { + if (stopping) return; + stopping = true; + retryThread.interrupt(); + readThread.interrupt(); + try { + retryThread.join(); + readThread.join(); + } catch (InterruptedException e) { + } + + } + private void createRetryThread() { retryThread = new Thread(new Runnable() { public void run() { @@ -160,9 +177,24 @@ public void sendRequest(StunMessage req, SocketAddress dst, StunResponseHandler callback) { StunTransmitAttempt t = new StunTransmitAttempt(req, dst); t.setResponseHandler(callback); + transmitQueue.put(t); + } + + public Future<StunMessage> sendRequest(StunMessage req, SocketAddress dst) { + StunTransmitAttempt t = new StunTransmitAttempt(req, dst); + StunRequestFuture f = new StunRequestFuture(t); transmitQueue.put(t); + return f; } + /** + * Cancels the given request if possible. Fails silently. + */ + public void cancelRequest(StunMessage req) { + transmitQueue.remove(req); + } + + //TODO: Move this to a unit test public static void main(String[] args) throws InterruptedException { // Get server name, port in args String host = args[0]; @@ -207,8 +239,121 @@ System.out.println("Waiting on STUN response..."); waiter.wait(); + + System.out.println("Trying with StunBindingHelper..."); + + StunBindingHelper helper = new StunBindingHelper(c); + helper.setDefaultServer(new InetSocketAddress(host, port)); + Future<InetSocketAddress> helperFuture = helper.getPublicAddress(); + try { + InetSocketAddress helperResult = helperFuture.get(); + System.out.println("Helper got response: " + helperResult.toString()); + } catch (ExecutionException e) { + System.err.println("ExecutionException in helper!"); + e.printStackTrace(); + } + + } } + + //TODO: Add an exception that can hold a StunMessage indicating error + private class StunRequestFuture implements Future<StunMessage> { + private StunTransmitAttempt _attempt = null; + private StunMessage _error = null; + private StunMessage _response = null; + private boolean _timeout = false; + private boolean _cancelled = false; + + //Callbacks occur in a worker thread. + //A thread may be waiting on the result. + //This is used to implement waiting. + private final Object _locker = new Object(); + + + public StunRequestFuture(StunTransmitAttempt t) { + _attempt = t; + _attempt.setResponseHandler(new StunResponseHandler() { + + public void handleError(StunMessage errorResponse) { + synchronized (_locker) { + _error = errorResponse; + _locker.notifyAll(); + } + } + + public void handleResponse(StunMessage response) { + synchronized (_locker) { + _response = response; + _locker.notifyAll(); + } + } + + public void handleTimeout() { + synchronized (_locker) { + _timeout = true; + _locker.notifyAll(); + } + } + }); + } + + public boolean cancel(boolean mayInterruptIfRunning) { + synchronized (_locker) { + cancelRequest(_attempt.getMessage()); + } + return true; + } + + public StunMessage get() throws InterruptedException, ExecutionException { + //Keep trying due to spurious wakeups + while (true) { + try { + return get(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + } + } + } + + public StunMessage get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + + synchronized (_locker) { + if (isDone()) + return tryGetResult(); + _locker.wait(unit.toMillis(timeout)); + if (isDone()) + return tryGetResult(); + throw new TimeoutException(); + } + + } + + //Helper for get. Throws exception or returns response. + //based on current state. + private StunMessage tryGetResult() throws ExecutionException { + if (_cancelled) + throw new CancellationException(); + if (_timeout) + throw new ExecutionException( + new Exception("STUN request timeout")); + if (_error != null) + throw new ExecutionException( + new Exception("STUN error response: " + _error.toString())); + if (_response != null) + return _response; + return null; + } + + public boolean isCancelled() { + return _cancelled; + } + + public boolean isDone() { + return _cancelled || _timeout || (_error != null) || (_response != null); + } + } + } class StunTransmitAttempt implements Delayed { @@ -348,5 +493,4 @@ if (diff > 0) return 1; return -1; } - -} +} \ No newline at end of file Modified: trunk/jnatlib/src/net/jnatlib/stun/StunMessageAttribute.java =================================================================== --- trunk/jnatlib/src/net/jnatlib/stun/StunMessageAttribute.java 2009-03-15 19:52:04 UTC (rev 15) +++ trunk/jnatlib/src/net/jnatlib/stun/StunMessageAttribute.java 2009-05-20 06:03:44 UTC (rev 16) @@ -185,22 +185,33 @@ return rawValue; } + /** + * See getAsEndpoint(boolean xor) + * This variant infers the xor parameter based on attribute type + */ + public InetSocketAddress getAsEndpoint() { + return getAsEndpoint( + this.attributeType == StunMessageAttribute.AttributeType.XOR_MAPPED_ADDRESS); + } + //TODO: An xor impl /** * Attempts to interpret the attribute value as an IP endpoint - * (address + port) as seen in MAPPED-ADDRESS - * getAsXORedEndpoint correctly handles XOR-MAPPED-ADDRESS + * (address + port) as seen in MAPPED-ADDRESS / XOR_MAPPED_ADDRESS * An exception will be thrown if the value length is incorrect * @throws IllegalArgumentException if the value is not suitable * @return IP + port encoded in the attribute */ - public InetSocketAddress getAsEndpoint() { + public InetSocketAddress getAsEndpoint(boolean xor) { + if (xor) + throw new RuntimeException("XOR not implemented"); + if (rawValue == null) throw new IllegalArgumentException("Null value"); //Two possible lengths - depends if IPv4 or IPv6 if (rawValue.length != (4 + 4) && rawValue.length != (4 + 16)) { //TODO: Better fitting exception type throw new IllegalArgumentException( - "Lenght does not correspond to an encoded IPv4 or IPv6 address"); + "Length does not correspond to an encoded IPv4 or IPv6 address"); } try { @@ -236,7 +247,7 @@ } InetSocketAddress ret = new InetSocketAddress(addr, port); - logger.debug("Parsed a socket address attribute: " + ret); + logger.trace("Parsed a socket address attribute: " + ret); return ret; } catch (IOException e) { //It's on a byte array, and we do our own checking on addr length @@ -252,6 +263,7 @@ public void setAsEndpoint(InetSocketAddress addr, boolean xor) { if (addr == null) throw new IllegalArgumentException("Addr can't be null"); //TODO: implement me! + throw new RuntimeException("XOR not implemented"); } /** This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |