[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.
|