summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Smith <git@kismith.co.uk>2011-07-01 09:19:49 (GMT)
committerKevin Smith <git@kismith.co.uk>2011-07-01 09:19:49 (GMT)
commit2da71a8a85486a494343f1662d64fb5ae5a2a44e (patch)
tree23992f9f2a00bac23b345e5c2cc9c1194efc25be /src/com/isode/stroke/network
downloadstroke-2da71a8a85486a494343f1662d64fb5ae5a2a44e.zip
stroke-2da71a8a85486a494343f1662d64fb5ae5a2a44e.tar.bz2
Initial import
Diffstat (limited to 'src/com/isode/stroke/network')
-rw-r--r--src/com/isode/stroke/network/Connection.java40
-rw-r--r--src/com/isode/stroke/network/ConnectionFactory.java14
-rw-r--r--src/com/isode/stroke/network/Connector.java211
-rw-r--r--src/com/isode/stroke/network/DomainNameAddressQuery.java19
-rw-r--r--src/com/isode/stroke/network/DomainNameResolveError.java13
-rw-r--r--src/com/isode/stroke/network/DomainNameResolver.java30
-rw-r--r--src/com/isode/stroke/network/DomainNameServiceQuery.java46
-rw-r--r--src/com/isode/stroke/network/HostAddress.java56
-rw-r--r--src/com/isode/stroke/network/HostAddressPort.java57
-rw-r--r--src/com/isode/stroke/network/JavaConnection.java176
-rw-r--r--src/com/isode/stroke/network/JavaConnectionFactory.java26
-rw-r--r--src/com/isode/stroke/network/JavaNetworkFactories.java33
-rw-r--r--src/com/isode/stroke/network/JavaTimer.java72
-rw-r--r--src/com/isode/stroke/network/JavaTimerFactory.java27
-rw-r--r--src/com/isode/stroke/network/NetworkFactories.java17
-rw-r--r--src/com/isode/stroke/network/PlatformDomainNameResolver.java103
-rw-r--r--src/com/isode/stroke/network/PlatformDomainNameServiceQuery.java186
-rw-r--r--src/com/isode/stroke/network/Timer.java18
-rw-r--r--src/com/isode/stroke/network/TimerFactory.java15
19 files changed, 1159 insertions, 0 deletions
diff --git a/src/com/isode/stroke/network/Connection.java b/src/com/isode/stroke/network/Connection.java
new file mode 100644
index 0000000..ff4a056
--- /dev/null
+++ b/src/com/isode/stroke/network/Connection.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.signals.Signal;
+import com.isode.stroke.signals.Signal1;
+
+public abstract class Connection {
+
+ public enum Error {
+
+ ReadError,
+ WriteError
+ };
+
+ public Connection() {
+ }
+
+ public abstract void listen();
+
+ public abstract void connect(HostAddressPort address);
+
+ public abstract void disconnect();
+
+ public abstract void write(ByteArray data);
+
+ public abstract HostAddressPort getLocalAddress();
+ public final Signal1<Boolean /*error*/> onConnectFinished = new Signal1<Boolean>();
+ public final Signal1<Error> onDisconnected = new Signal1<Error>();
+ public final Signal1<ByteArray> onDataRead = new Signal1<ByteArray>();
+ public final Signal onDataWritten = new Signal();
+}
diff --git a/src/com/isode/stroke/network/ConnectionFactory.java b/src/com/isode/stroke/network/ConnectionFactory.java
new file mode 100644
index 0000000..6764d24
--- /dev/null
+++ b/src/com/isode/stroke/network/ConnectionFactory.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+public interface ConnectionFactory {
+ Connection createConnection();
+}
diff --git a/src/com/isode/stroke/network/Connector.java b/src/com/isode/stroke/network/Connector.java
new file mode 100644
index 0000000..560bf7b
--- /dev/null
+++ b/src/com/isode/stroke/network/Connector.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import com.isode.stroke.network.DomainNameServiceQuery.Result;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.signals.Slot2;
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class Connector {
+
+ public static Connector create(String hostname, DomainNameResolver resolver, ConnectionFactory connectionFactory, TimerFactory timerFactory) {
+ return new Connector(hostname, resolver, connectionFactory, timerFactory);
+ }
+
+ public void setTimeoutMilliseconds(int milliseconds) {
+ timeoutMilliseconds = milliseconds;
+ }
+
+ public void start() {
+ assert currentConnection == null;
+ assert serviceQuery == null;
+ assert timer == null;
+ queriedAllServices = false;
+ serviceQuery = resolver.createServiceQuery("_xmpp-client._tcp." + hostname);
+ serviceQuery.onResult.connect(new Slot1<Collection<DomainNameServiceQuery.Result>>() {
+ public void call(Collection<Result> p1) {
+ handleServiceQueryResult(p1);
+ }
+ });
+ if (timeoutMilliseconds > 0) {
+ timer = timerFactory.createTimer(timeoutMilliseconds);
+ timer.onTick.connect(new Slot() {
+ public void call() {
+ handleTimeout();
+ }
+ });
+ timer.start();
+ }
+ serviceQuery.run();
+ }
+
+ public void stop() {
+ finish(null);
+ }
+
+ public final Signal1<Connection> onConnectFinished = new Signal1<Connection>();
+
+ private Connector(String hostname, DomainNameResolver resolver, ConnectionFactory connectionFactory, TimerFactory timerFactory) {
+ this.hostname = hostname;
+ this.resolver = resolver;
+ this.connectionFactory = connectionFactory;
+ this.timerFactory = timerFactory;
+ }
+
+ private void handleServiceQueryResult(Collection<Result> result) {
+ serviceQueryResults = new ArrayList<Result>();
+ serviceQueryResults.addAll(result);
+ serviceQuery = null;
+ tryNextServiceOrFallback();
+ }
+
+ private void handleAddressQueryResult(Collection<HostAddress> addresses, DomainNameResolveError error) {
+ //std::cout << "Connector::handleAddressQueryResult(): Start" << std::endl;
+ addressQuery = null;
+ if (error != null || addresses.isEmpty()) {
+ if (!serviceQueryResults.isEmpty()) {
+ serviceQueryResults.remove(0);
+ }
+ tryNextServiceOrFallback();
+ }
+ else {
+ addressQueryResults = new ArrayList<HostAddress>();
+ addressQueryResults.addAll(addresses);
+ tryNextAddress();
+ }
+ }
+
+ private void queryAddress(String hostname) {
+ assert addressQuery == null;
+ addressQuery = resolver.createAddressQuery(hostname);
+ addressQuery.onResult.connect(new Slot2<Collection<HostAddress>, DomainNameResolveError>() {
+ public void call(Collection<HostAddress> p1, DomainNameResolveError p2) {
+ handleAddressQueryResult(p1, p2);
+ }
+ });
+ addressQuery.run();
+ }
+
+ private void tryNextServiceOrFallback() {
+ if (queriedAllServices) {
+ //std::cout << "Connector::tryNextServiceOrCallback(): Queried all hosts. Error." << std::endl;
+ finish(null);
+ }
+ else if (serviceQueryResults.isEmpty()) {
+ //std::cout << "Connector::tryNextHostName(): Falling back on A resolution" << std::endl;
+ // Fall back on simple address resolving
+ queriedAllServices = true;
+ queryAddress(hostname);
+ }
+ else {
+ //std::cout << "Connector::tryNextHostName(): Querying next address" << std::endl;
+ queryAddress(serviceQueryResults.get(0).hostname);
+ }
+ }
+
+ private void tryNextAddress() {
+ if (addressQueryResults.isEmpty()) {
+ //std::cout << "Connector::tryNextAddress(): Done trying addresses. Moving on" << std::endl;
+ // Done trying all addresses. Move on to the next host.
+ if (!serviceQueryResults.isEmpty()) {
+ serviceQueryResults.remove(0);
+ }
+ tryNextServiceOrFallback();
+ }
+ else {
+ //std::cout << "Connector::tryNextAddress(): trying next address." << std::endl;
+ HostAddress address = addressQueryResults.get(0);
+ addressQueryResults.remove(0);
+
+ int port = 5222;
+ if (!serviceQueryResults.isEmpty()) {
+ port = serviceQueryResults.get(0).port;
+ }
+
+ tryConnect(new HostAddressPort(address, port));
+ }
+ }
+
+ private void tryConnect(HostAddressPort target) {
+ assert currentConnection == null;
+ //std::cout << "Connector::tryConnect() " << target.getAddress().toString() << " " << target.getPort() << std::endl;
+ currentConnection = connectionFactory.createConnection();
+ currentConnectionConnectFinishedConnection = currentConnection.onConnectFinished.connect(new Slot1<Boolean>() {
+ public void call(Boolean p1) {
+ handleConnectionConnectFinished(p1);
+ }
+ });
+
+ currentConnection.connect(target);
+ }
+
+ private void handleConnectionConnectFinished(boolean error) {
+ //std::cout << "Connector::handleConnectionConnectFinished() " << error << std::endl;
+ currentConnectionConnectFinishedConnection.disconnect();
+ if (error) {
+ currentConnection = null;
+ if (!addressQueryResults.isEmpty()) {
+ tryNextAddress();
+ }
+ else {
+ if (!serviceQueryResults.isEmpty()) {
+ serviceQueryResults.remove(0);
+ }
+ tryNextServiceOrFallback();
+ }
+ }
+ else {
+ finish(currentConnection);
+ }
+ }
+
+ private void finish(Connection connection) {
+ if (timer != null) {
+ timer.stop();
+ timer.onTick.disconnectAll();
+ timer = null;
+ }
+ if (serviceQuery != null) {
+ serviceQuery.onResult.disconnectAll();
+ serviceQuery = null;
+ }
+ if (addressQuery != null) {
+ addressQuery.onResult.disconnectAll();
+ addressQuery = null;
+ }
+ if (currentConnection != null) {
+ currentConnectionConnectFinishedConnection.disconnect();
+ currentConnection = null;
+ }
+ onConnectFinished.emit(connection);
+ }
+
+ private void handleTimeout() {
+ finish(null);
+ }
+ private String hostname;
+ private DomainNameResolver resolver;
+ private ConnectionFactory connectionFactory;
+ private TimerFactory timerFactory;
+ private int timeoutMilliseconds = 0;
+ private Timer timer;
+ private DomainNameServiceQuery serviceQuery;
+ private ArrayList<DomainNameServiceQuery.Result> serviceQueryResults;
+ private DomainNameAddressQuery addressQuery;
+ private ArrayList<HostAddress> addressQueryResults;
+ private boolean queriedAllServices = true;
+ private Connection currentConnection;
+ private SignalConnection currentConnectionConnectFinishedConnection;
+}
diff --git a/src/com/isode/stroke/network/DomainNameAddressQuery.java b/src/com/isode/stroke/network/DomainNameAddressQuery.java
new file mode 100644
index 0000000..1bc9edf
--- /dev/null
+++ b/src/com/isode/stroke/network/DomainNameAddressQuery.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+import com.isode.stroke.signals.Signal2;
+import java.util.Collection;
+
+public abstract class DomainNameAddressQuery {
+ public abstract void run();
+
+ public final Signal2<Collection<HostAddress>, DomainNameResolveError> onResult = new Signal2<Collection<HostAddress>, DomainNameResolveError>();
+}
diff --git a/src/com/isode/stroke/network/DomainNameResolveError.java b/src/com/isode/stroke/network/DomainNameResolveError.java
new file mode 100644
index 0000000..b470c34
--- /dev/null
+++ b/src/com/isode/stroke/network/DomainNameResolveError.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+public class DomainNameResolveError implements com.isode.stroke.base.Error {
+
+}
diff --git a/src/com/isode/stroke/network/DomainNameResolver.java b/src/com/isode/stroke/network/DomainNameResolver.java
new file mode 100644
index 0000000..bbd4e79
--- /dev/null
+++ b/src/com/isode/stroke/network/DomainNameResolver.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+
+public abstract class DomainNameResolver {
+ public abstract DomainNameServiceQuery createServiceQuery(String name);
+ public abstract DomainNameAddressQuery createAddressQuery(String name);
+
+ protected String getNormalized(String domain) {
+ return domain;
+ //FIXME: port idna
+// char* output;
+// if (idna_to_ascii_8z(domain.getUTF8Data(), &output, 0) == IDNA_SUCCESS) {
+// String result(output);
+// free(output);
+// return result;
+// }
+// else {
+// return domain;
+// }
+ }
+}
diff --git a/src/com/isode/stroke/network/DomainNameServiceQuery.java b/src/com/isode/stroke/network/DomainNameServiceQuery.java
new file mode 100644
index 0000000..ddb94a8
--- /dev/null
+++ b/src/com/isode/stroke/network/DomainNameServiceQuery.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import com.isode.stroke.signals.Signal1;
+import java.util.Collection;
+
+public abstract class DomainNameServiceQuery {
+
+ public class Result {
+
+ public Result() {
+ hostname = "";
+ port = -1;
+ priority = -1;
+ weight = -1;
+ }
+
+ public Result(String hostname, int port, int priority, int weight) {
+ this.hostname = hostname;
+ this.port = port;
+ this.priority = priority;
+ this.weight = weight;
+ }
+ public final String hostname;
+ public final int port;
+ public final int priority;
+ public final int weight;
+ };
+
+ public class ResultPriorityComparator {
+
+ public boolean compare(DomainNameServiceQuery.Result a, DomainNameServiceQuery.Result b) {
+ return a.priority < b.priority;
+ }
+ };
+
+ public abstract void run();
+ public final Signal1<Collection<Result>> onResult = new Signal1<Collection<Result>>();
+}
diff --git a/src/com/isode/stroke/network/HostAddress.java b/src/com/isode/stroke/network/HostAddress.java
new file mode 100644
index 0000000..152dc2b
--- /dev/null
+++ b/src/com/isode/stroke/network/HostAddress.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import java.net.InetAddress;
+
+public class HostAddress {
+
+ public HostAddress() {
+ address_ = null;
+ }
+
+ public HostAddress(InetAddress address) {
+ address_ = address;
+ }
+ /* public HostAddress(const String&);
+ public HostAddress(const unsigned char* address, int length);
+ public HostAddress(const boost::asio::ip::address& address);*/
+
+ @Override
+ public String toString() {
+ return address_.getHostAddress();
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 29 * hash + (this.address_ != null ? this.address_.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof HostAddress) {
+ return address_.equals(((HostAddress)other).getInetAddress());
+ }
+ return false;
+ }
+
+ public boolean isValid() {
+ return address_ != null;
+ }
+
+ InetAddress getInetAddress() {
+ return address_;
+ }
+
+ private final InetAddress address_;
+}
diff --git a/src/com/isode/stroke/network/HostAddressPort.java b/src/com/isode/stroke/network/HostAddressPort.java
new file mode 100644
index 0000000..01048a3
--- /dev/null
+++ b/src/com/isode/stroke/network/HostAddressPort.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+public class HostAddressPort {
+
+ public HostAddressPort(HostAddress address) {
+ address_ = address;
+ port_ = -1;
+ }
+
+ public HostAddressPort(HostAddress address, int port) {
+ address_ = address;
+ port_ = port;
+ }
+
+ /*
+ public HostAddressPort(const boost::asio::ip::tcp::endpoint& endpoint) {
+ address_ = HostAddress(endpoint.address());
+ port_ = endpoint.port();
+ }*/ //FIXME
+ public HostAddress getAddress() {
+ return address_;
+ }
+
+ public int getPort() {
+ return port_;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof HostAddressPort)) return false;
+ HostAddressPort o = (HostAddressPort)other;
+ return getAddress().equals(o.getAddress()) && port_ == o.getPort();
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 17 * hash + (this.address_ != null ? this.address_.hashCode() : 0);
+ hash = 17 * hash + this.port_;
+ return hash;
+ }
+
+ public boolean isValid() {
+ return address_.isValid() && port_ > 0;
+ }
+ private HostAddress address_;
+ private int port_;
+}
diff --git a/src/com/isode/stroke/network/JavaConnection.java b/src/com/isode/stroke/network/JavaConnection.java
new file mode 100644
index 0000000..e43707c
--- /dev/null
+++ b/src/com/isode/stroke/network/JavaConnection.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.eventloop.Event.Callback;
+import com.isode.stroke.eventloop.EventLoop;
+import com.isode.stroke.eventloop.EventOwner;
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class JavaConnection extends Connection implements EventOwner {
+
+ private class Worker implements Runnable {
+
+ private final HostAddressPort address_;
+ private OutputStream write_;
+ private BufferedReader read_;
+ private final List<ByteArray> writeBuffer_ = Collections.synchronizedList(new ArrayList<ByteArray>());
+
+ public Worker(HostAddressPort address) {
+ address_ = address;
+ }
+
+ public void run() {
+ try {
+ socket_ = new Socket(address_.getAddress().getInetAddress(), address_.getPort());
+ write_ = socket_.getOutputStream();
+ read_ = new BufferedReader(new InputStreamReader(socket_.getInputStream(), "utf-8"));
+ } catch (IOException ex) {
+ handleConnected(true);
+ return;
+ }
+ handleConnected(false);
+ while (!disconnecting_) {
+ boolean didWrite = false;
+ while (!writeBuffer_.isEmpty()) {
+ didWrite = true;
+ ByteArray data = writeBuffer_.get(0);
+ for (byte datum : data.getData()) {
+ try {
+ write_.write(datum);
+ } catch (IOException ex) {
+ disconnecting_ = true;
+ handleDisconnected(Error.WriteError);
+ }
+ }
+ writeBuffer_.remove(0);
+ }
+ if (didWrite && !disconnecting_) {
+ try {
+ write_.flush();
+ } catch (IOException ex) {
+ disconnecting_ = true;
+ handleDisconnected(Error.WriteError);
+ }
+ }
+ ByteArray data = new ByteArray();
+ try {
+ while (read_.ready()) {
+ char[] c = new char[1024];
+ int i = read_.read(c, 0, c.length);
+ if (i > 0) {
+ data.append(new String(c, 0, i));
+ }
+ }
+ } catch (IOException ex) {
+ handleDisconnected(Error.ReadError);
+ return;
+ }
+ if (!data.isEmpty()) {
+ handleDataRead(data);
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ /* We've been woken up, probably to force us to do something.*/
+ }
+ }
+ try {
+ read_.close();
+ write_.close();
+ socket_.close();
+ } catch (IOException ex) {
+ /* Do we need to return an error if we're already trying to close? */
+ }
+ }
+
+ private void handleConnected(final boolean error) {
+ eventLoop_.postEvent(new Callback() {
+ public void run() {
+ onConnectFinished.emit(error);
+ }
+ });
+ }
+
+ private void handleDisconnected(final Error error) {
+ eventLoop_.postEvent(new Callback() {
+ public void run() {
+ onDisconnected.emit(error);
+ }
+ });
+ }
+
+ private void handleDataRead(final ByteArray data) {
+ eventLoop_.postEvent(new Callback() {
+ public void run() {
+ onDataRead.emit(data);
+ }
+ });
+ }
+
+ public void write(ByteArray data) {
+ writeBuffer_.add(data);
+ }
+ }
+
+ private JavaConnection(EventLoop eventLoop) {
+ eventLoop_ = eventLoop;
+ }
+
+ public static JavaConnection create(EventLoop eventLoop) {
+ return new JavaConnection(eventLoop);
+ }
+
+ @Override
+ public void listen() {
+ //TODO: needed for server, not for client.
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void connect(HostAddressPort address) {
+ worker_ = new Worker(address);
+ Thread workerThread = new Thread(worker_);
+ workerThread.setDaemon(true);
+ workerThread.start();
+ }
+
+ @Override
+ public void disconnect() {
+ disconnecting_ = true;
+ }
+
+ @Override
+ public void write(ByteArray data) {
+ worker_.writeBuffer_.add(data);
+ }
+
+ @Override
+ public HostAddressPort getLocalAddress() {
+ return new HostAddressPort(new HostAddress(socket_.getLocalAddress()), socket_.getLocalPort());
+ }
+ private final EventLoop eventLoop_;
+ private boolean disconnecting_ = false;
+ private Socket socket_;
+ private Worker worker_;
+}
diff --git a/src/com/isode/stroke/network/JavaConnectionFactory.java b/src/com/isode/stroke/network/JavaConnectionFactory.java
new file mode 100644
index 0000000..956e80b
--- /dev/null
+++ b/src/com/isode/stroke/network/JavaConnectionFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+import com.isode.stroke.eventloop.EventLoop;
+
+public class JavaConnectionFactory implements ConnectionFactory {
+
+ public JavaConnectionFactory(EventLoop eventLoop) {
+ this.eventLoop = eventLoop;
+ }
+
+ public Connection createConnection() {
+ return JavaConnection.create(eventLoop);
+ }
+
+ private final EventLoop eventLoop;
+}
diff --git a/src/com/isode/stroke/network/JavaNetworkFactories.java b/src/com/isode/stroke/network/JavaNetworkFactories.java
new file mode 100644
index 0000000..acd289b
--- /dev/null
+++ b/src/com/isode/stroke/network/JavaNetworkFactories.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import com.isode.stroke.eventloop.EventLoop;
+
+public class JavaNetworkFactories implements NetworkFactories {
+
+ public JavaNetworkFactories(EventLoop eventLoop) {
+ eventLoop_ = eventLoop;
+ timers_ = new JavaTimerFactory(eventLoop_);
+ connections_ = new JavaConnectionFactory(eventLoop_);
+ dns_ = new PlatformDomainNameResolver(eventLoop_);
+ }
+
+ public TimerFactory getTimerFactory() {
+ return timers_;
+ }
+
+ public ConnectionFactory getConnectionFactory() {
+ return connections_;
+ }
+
+ public DomainNameResolver getDomainNameResolver() {
+ return dns_;
+ }
+ private final EventLoop eventLoop_;
+ private final JavaTimerFactory timers_;
+ private final JavaConnectionFactory connections_;
+ private final PlatformDomainNameResolver dns_;
+}
diff --git a/src/com/isode/stroke/network/JavaTimer.java b/src/com/isode/stroke/network/JavaTimer.java
new file mode 100644
index 0000000..61357d4
--- /dev/null
+++ b/src/com/isode/stroke/network/JavaTimer.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import com.isode.stroke.eventloop.Event;
+import com.isode.stroke.eventloop.EventLoop;
+
+class JavaTimer extends Timer {
+
+ private class TimerRunnable implements Runnable {
+
+ boolean running_ = true;
+ private final EventLoop eventLoop_;
+ private final int milliseconds_;
+
+ public TimerRunnable(EventLoop eventLoop, int milliseconds) {
+ eventLoop_ = eventLoop;
+ milliseconds_ = milliseconds;
+ }
+
+ public void run() {
+ while (shouldEmit()) {
+ try {
+ Thread.sleep(milliseconds_);
+ } catch (InterruptedException ex) {
+ /* If we were interrupted, either emit or don't, based on whether stop was called.*/
+ }
+ if (shouldEmit()) {
+ eventLoop_.postEvent(new Event.Callback() {
+ public void run() {
+ onTick.emit();
+ }
+ });
+ }
+ }
+ }
+
+
+ synchronized boolean shouldEmit() {
+ return running_;
+ }
+
+ public synchronized void stop() {
+ running_ = false;
+ }
+ }
+
+ public JavaTimer(EventLoop eventLoop, int milliseconds) {
+ timer_ = new TimerRunnable(eventLoop, milliseconds);
+ }
+
+ @Override
+ public void start() {
+ Thread thread = (new Thread(timer_));
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ @Override
+ public void stop() {
+ timer_.stop();
+ //FIXME: This needs to clear any remaining events out of the EventLoop queue.
+ }
+ private final TimerRunnable timer_;
+}
diff --git a/src/com/isode/stroke/network/JavaTimerFactory.java b/src/com/isode/stroke/network/JavaTimerFactory.java
new file mode 100644
index 0000000..10017a8
--- /dev/null
+++ b/src/com/isode/stroke/network/JavaTimerFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+import com.isode.stroke.eventloop.EventLoop;
+
+public class JavaTimerFactory implements TimerFactory {
+
+ public JavaTimerFactory(EventLoop eventLoop) {
+ eventLoop_ = eventLoop;
+ }
+
+ public Timer createTimer(int milliseconds) {
+ return new JavaTimer(eventLoop_, milliseconds);
+ }
+
+ private final EventLoop eventLoop_;
+
+}
diff --git a/src/com/isode/stroke/network/NetworkFactories.java b/src/com/isode/stroke/network/NetworkFactories.java
new file mode 100644
index 0000000..b630b85
--- /dev/null
+++ b/src/com/isode/stroke/network/NetworkFactories.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+public interface NetworkFactories {
+
+ TimerFactory getTimerFactory();
+ ConnectionFactory getConnectionFactory();
+ DomainNameResolver getDomainNameResolver();
+
+}
diff --git a/src/com/isode/stroke/network/PlatformDomainNameResolver.java b/src/com/isode/stroke/network/PlatformDomainNameResolver.java
new file mode 100644
index 0000000..50d0e11
--- /dev/null
+++ b/src/com/isode/stroke/network/PlatformDomainNameResolver.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+import com.isode.stroke.eventloop.EventLoop;
+import com.isode.stroke.eventloop.EventOwner;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+
+public class PlatformDomainNameResolver extends DomainNameResolver {
+
+ private class AddressQuery extends DomainNameAddressQuery implements EventOwner {
+ AddressQuery(String host, EventLoop eventLoop) {
+ hostname = host;
+ this.eventLoop = eventLoop;
+ //FIXME: port asyncDNS
+// thread = null;
+// safeToJoin = false;
+ }
+
+ public void run() {
+ //FIXME: port asyncDNS
+ Collection<HostAddress> results = new ArrayList<HostAddress>();
+ try {
+ results.add(new HostAddress(InetAddress.getByName(hostname)));
+ } catch (UnknownHostException ex) {
+
+ }
+ onResult.emit(results, results.isEmpty() ? new DomainNameResolveError() : null);
+
+// safeToJoin = false;
+// thread = new boost::thread(boost::bind(&AddressQuery::doRun, shared_from_this()));
+ }
+// FIXME: Port async DNS.
+// void doRun() {
+// //std::cout << "PlatformDomainNameResolver::doRun()" << std::endl;
+// boost::asio::ip::tcp::resolver resolver(ioService);
+// boost::asio::ip::tcp::resolver::query query(hostname.getUTF8String(), "5222");
+// try {
+// //std::cout << "PlatformDomainNameResolver::doRun(): Resolving" << std::endl;
+// boost::asio::ip::tcp::resolver::iterator endpointIterator = resolver.resolve(query);
+// //std::cout << "PlatformDomainNameResolver::doRun(): Resolved" << std::endl;
+// if (endpointIterator == boost::asio::ip::tcp::resolver::iterator()) {
+// //std::cout << "PlatformDomainNameResolver::doRun(): Error 1" << std::endl;
+// emitError();
+// }
+// else {
+// std::vector<HostAddress> results;
+// for ( ; endpointIterator != boost::asio::ip::tcp::resolver::iterator(); ++endpointIterator) {
+// boost::asio::ip::address address = (*endpointIterator).endpoint().address();
+// results.push_back(address.is_v4() ? HostAddress(&address.to_v4().to_bytes()[0], 4) : HostAddress(&address.to_v6().to_bytes()[0], 16));
+// }
+//
+// //std::cout << "PlatformDomainNameResolver::doRun(): Success" << std::endl;
+// eventLoop->postEvent(
+// boost::bind(boost::ref(onResult), results, boost::optional<DomainNameResolveError>()),
+// shared_from_this());
+// }
+// }
+// catch (...) {
+// //std::cout << "PlatformDomainNameResolver::doRun(): Error 2" << std::endl;
+// emitError();
+// }
+// safeToJoin = true;
+// }
+//
+// void emitError() {
+// eventLoop->postEvent(boost::bind(boost::ref(onResult), std::vector<HostAddress>(), boost::optional<DomainNameResolveError>(DomainNameResolveError())), shared_from_this());
+// }
+//
+// boost::asio::io_service ioService;
+ String hostname;
+ EventLoop eventLoop;
+// boost::thread* thread;
+// bool safeToJoin;
+ }
+
+ public PlatformDomainNameResolver(EventLoop eventLoop) {
+ this.eventLoop = eventLoop;
+ }
+
+ @Override
+ public DomainNameServiceQuery createServiceQuery(String name) {
+ return new PlatformDomainNameServiceQuery(getNormalized(name), eventLoop);
+ }
+
+ @Override
+ public DomainNameAddressQuery createAddressQuery(String name) {
+ return new AddressQuery(getNormalized(name), eventLoop);
+ }
+
+ private final EventLoop eventLoop;
+}
diff --git a/src/com/isode/stroke/network/PlatformDomainNameServiceQuery.java b/src/com/isode/stroke/network/PlatformDomainNameServiceQuery.java
new file mode 100644
index 0000000..5c94e4d
--- /dev/null
+++ b/src/com/isode/stroke/network/PlatformDomainNameServiceQuery.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+import com.isode.stroke.eventloop.EventLoop;
+import com.isode.stroke.eventloop.EventOwner;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Hashtable;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+
+
+public class PlatformDomainNameServiceQuery extends DomainNameServiceQuery implements EventOwner {
+
+ public PlatformDomainNameServiceQuery(String service, EventLoop eventLoop) {
+ this.service = service;
+ this.eventLoop = eventLoop;
+ }
+
+ @Override
+ public void run() {
+ //TODO: Make async
+ Collection<Result> results = new ArrayList<Result>();
+ Hashtable env = new Hashtable();
+ env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
+ env.put("java.naming.provider.url", "dns:");
+ try {
+ DirContext ctx = new InitialDirContext(env);
+ Attributes attrs = ctx.getAttributes(this.service, new String[]{"SRV"});
+ Attribute attribute = attrs.get("SRV");
+ for (int i = 0; attribute != null && i < attribute.size(); i++) {
+ /* SRV results are going to be returned in the space-separated format
+ * Priority Weight Port Target
+ * (See RFC2782)
+ */
+ String[] srvParts = ((String) attribute.get(i)).split(" ");
+ String host = srvParts[3];
+ if (host.endsWith(".")) {
+ host = host.substring(0, host.length() - 1);
+ }
+ Result result = new Result(host, Integer.parseInt(srvParts[2]), Integer.parseInt(srvParts[0]), Integer.parseInt(srvParts[1]));
+ results.add(result);
+ }
+ } catch (NamingException ex) {
+ /* Turns out that you get the exception just for not finding a result, so we want to fall through to A lookups and ignore.*/
+ }
+
+ onResult.emit(results);
+ }
+
+
+// void PlatformDomainNameServiceQuery::doRun() {
+// std::vector<DomainNameServiceQuery::Result> records;
+//
+//#if defined(SWIFTEN_PLATFORM_WINDOWS)
+// DNS_RECORD* responses;
+// // FIXME: This conversion doesn't work if unicode is deffed above
+// if (DnsQuery(service.getUTF8Data(), DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &responses, NULL) != ERROR_SUCCESS) {
+// emitError();
+// return;
+// }
+//
+// DNS_RECORD* currentEntry = responses;
+// while (currentEntry) {
+// if (currentEntry->wType == DNS_TYPE_SRV) {
+// DomainNameServiceQuery::Result record;
+// record.priority = currentEntry->Data.SRV.wPriority;
+// record.weight = currentEntry->Data.SRV.wWeight;
+// record.port = currentEntry->Data.SRV.wPort;
+//
+// // The pNameTarget is actually a PCWSTR, so I would have expected this
+// // conversion to not work at all, but it does.
+// // Actually, it doesn't. Fix this and remove explicit cast
+// // Remove unicode undef above as well
+// record.hostname = String((const char*) currentEntry->Data.SRV.pNameTarget);
+// records.push_back(record);
+// }
+// currentEntry = currentEntry->pNext;
+// }
+// DnsRecordListFree(responses, DnsFreeRecordList);
+//
+//#else
+// // Make sure we reinitialize the domain list every time
+// res_init();
+//
+// //std::cout << "SRV: Querying " << service << std::endl;
+// ByteArray response;
+// response.resize(NS_PACKETSZ);
+// int responseLength = res_query(const_cast<char*>(service.getUTF8Data()), ns_c_in, ns_t_srv, reinterpret_cast<u_char*>(response.getData()), response.getSize());
+// if (responseLength == -1) {
+// emitError();
+// return;
+// }
+//
+// // Parse header
+// HEADER* header = reinterpret_cast<HEADER*>(response.getData());
+// unsigned char* messageStart = reinterpret_cast<unsigned char*>(response.getData());
+// unsigned char* messageEnd = messageStart + responseLength;
+// unsigned char* currentEntry = messageStart + NS_HFIXEDSZ;
+//
+// // Skip over the queries
+// int queriesCount = ntohs(header->qdcount);
+// while (queriesCount > 0) {
+// int entryLength = dn_skipname(currentEntry, messageEnd);
+// if (entryLength < 0) {
+// emitError();
+// return;
+// }
+// currentEntry += entryLength + NS_QFIXEDSZ;
+// queriesCount--;
+// }
+//
+// // Process the SRV answers
+// int answersCount = ntohs(header->ancount);
+// while (answersCount > 0) {
+// DomainNameServiceQuery::Result record;
+//
+// int entryLength = dn_skipname(currentEntry, messageEnd);
+// currentEntry += entryLength;
+// currentEntry += NS_RRFIXEDSZ;
+//
+// // Priority
+// if (currentEntry + 2 >= messageEnd) {
+// emitError();
+// return;
+// }
+// record.priority = ns_get16(currentEntry);
+// currentEntry += 2;
+//
+// // Weight
+// if (currentEntry + 2 >= messageEnd) {
+// emitError();
+// return;
+// }
+// record.weight = ns_get16(currentEntry);
+// currentEntry += 2;
+//
+// // Port
+// if (currentEntry + 2 >= messageEnd) {
+// emitError();
+// return;
+// }
+// record.port = ns_get16(currentEntry);
+// currentEntry += 2;
+//
+// // Hostname
+// if (currentEntry >= messageEnd) {
+// emitError();
+// return;
+// }
+// ByteArray entry;
+// entry.resize(NS_MAXDNAME);
+// entryLength = dn_expand(messageStart, messageEnd, currentEntry, entry.getData(), entry.getSize());
+// if (entryLength < 0) {
+// emitError();
+// return;
+// }
+// record.hostname = String(entry.getData());
+// records.push_back(record);
+// currentEntry += entryLength;
+// answersCount--;
+// }
+//#endif
+//
+// safeToJoin = true;
+// std::sort(records.begin(), records.end(), ResultPriorityComparator());
+// //std::cout << "Sending out " << records.size() << " SRV results " << std::endl;
+// eventLoop->postEvent(boost::bind(boost::ref(onResult), records));
+//}
+
+
+ private final String service;
+ private final EventLoop eventLoop;
+}
diff --git a/src/com/isode/stroke/network/Timer.java b/src/com/isode/stroke/network/Timer.java
new file mode 100644
index 0000000..5dc3854
--- /dev/null
+++ b/src/com/isode/stroke/network/Timer.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+import com.isode.stroke.signals.Signal;
+
+public abstract class Timer {
+ public abstract void start();
+ public abstract void stop();
+ public final Signal onTick = new Signal();
+}
diff --git a/src/com/isode/stroke/network/TimerFactory.java b/src/com/isode/stroke/network/TimerFactory.java
new file mode 100644
index 0000000..2732aaf
--- /dev/null
+++ b/src/com/isode/stroke/network/TimerFactory.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+
+public interface TimerFactory {
+ Timer createTimer(int milliseconds);
+}