diff options
Diffstat (limited to 'src/com/isode/stroke/network')
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); +} |