[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[or-cvs] [puppetor/gsoc2008] kill tabs in sourcecode as well as whitespace at the end of a line



a first step towards a consistent code style for puppetor. I will try
hard to follow it in the future :-)
---
 src/org/torproject/puppetor/ClientApplication.java |  152 +-
 src/org/torproject/puppetor/ClientEventType.java   |   86 +-
 src/org/torproject/puppetor/DirectoryNode.java     |   54 +-
 src/org/torproject/puppetor/Event.java             |   54 +-
 src/org/torproject/puppetor/EventListener.java     |   16 +-
 src/org/torproject/puppetor/EventManager.java      |  402 ++--
 src/org/torproject/puppetor/EventType.java         |   14 +-
 src/org/torproject/puppetor/HiddenService.java     |   84 +-
 .../puppetor/HiddenServiceEventType.java           |  402 ++--
 src/org/torproject/puppetor/Network.java           | 1266 ++++++------
 src/org/torproject/puppetor/NetworkFactory.java    |  102 +-
 src/org/torproject/puppetor/NodeEventType.java     |   94 +-
 src/org/torproject/puppetor/NodeState.java         |   40 +-
 src/org/torproject/puppetor/ProxyNode.java         |  412 ++--
 src/org/torproject/puppetor/PuppeTorException.java |   78 +-
 src/org/torproject/puppetor/RouterNode.java        |   68 +-
 src/org/torproject/puppetor/ServerApplication.java |   72 +-
 src/org/torproject/puppetor/ServerEventType.java   |   48 +-
 .../examples/AccessingPublicWebServerOverTor.java  |  186 +-
 ...ccessingHiddenServiceOverPrivateTorNetwork.java |  206 +-
 ...AccessingHiddenServiceOverPublicTorNetwork.java |  238 ++--
 ...AdvertisingHiddenServiceToPublicTorNetwork.java |  168 +-
 .../puppetor/impl/ClientApplicationImpl.java       |  854 ++++----
 .../puppetor/impl/DirectoryNodeImpl.java           |  794 ++++----
 src/org/torproject/puppetor/impl/EventImpl.java    |  202 +-
 .../torproject/puppetor/impl/EventManagerImpl.java | 1378 +++++++-------
 .../puppetor/impl/HiddenServiceImpl.java           |  282 ++--
 src/org/torproject/puppetor/impl/NetworkImpl.java  | 2084 ++++++++++----------
 .../torproject/puppetor/impl/ProxyNodeImpl.java    | 1396 +++++++-------
 .../torproject/puppetor/impl/RouterNodeImpl.java   |  746 ++++----
 .../puppetor/impl/ServerApplicationImpl.java       |  648 +++---
 .../puppetor/rmi/AbstractMasterFactory.java        |  101 +-
 .../puppetor/rmi/AbstractSlaveFactory.java         |  120 +-
 src/org/torproject/puppetor/rmi/LocalMaster.java   |   36 +-
 .../torproject/puppetor/rmi/MasterConnector.java   |   22 +-
 src/org/torproject/puppetor/rmi/Network.java       |  102 +-
 .../puppetor/rmi/NetworkDescription.java           |    6 +-
 src/org/torproject/puppetor/rmi/RemoteMaster.java  |   40 +-
 src/org/torproject/puppetor/rmi/Slave.java         |   58 +-
 src/org/torproject/puppetor/rmi/Task.java          |   44 +-
 .../rmi/TaskExecutionNotSuccessfulException.java   |   18 +-
 src/org/torproject/puppetor/rmi/TaskResult.java    |   46 +-
 src/org/torproject/puppetor/rmi/Test.java          |   34 +-
 src/org/torproject/puppetor/rmi/TestExecutor.java  |  162 +-
 src/org/torproject/puppetor/rmi/TestResult.java    |    2 +-
 src/org/torproject/puppetor/rmi/TorInstance.java   |   84 +-
 .../rmi/execute/PuppeTorMasterProgram.java         |  306 ++--
 .../puppetor/rmi/execute/PuppeTorSlaveProgram.java |  349 ++--
 .../puppetor/rmi/impl/AbstractTaskImpl.java        |   88 +-
 .../puppetor/rmi/impl/AbstractTestImpl.java        |   64 +-
 .../torproject/puppetor/rmi/impl/MasterImpl.java   |  150 +-
 .../puppetor/rmi/impl/MasterImplFactory.java       |   62 +-
 .../puppetor/rmi/impl/NetworkDescriptionImpl.java  |   56 +-
 .../torproject/puppetor/rmi/impl/NetworkImpl.java  |  204 +-
 .../torproject/puppetor/rmi/impl/SlaveImpl.java    |  210 +-
 .../puppetor/rmi/impl/SlaveImplFactory.java        |   18 +-
 .../puppetor/rmi/impl/TaskResultImpl.java          |   76 +-
 .../puppetor/rmi/impl/TestExecutorImpl.java        |  998 +++++-----
 .../puppetor/rmi/impl/TorInstanceImpl.java         |  298 ++--
 .../puppetor/rmi/tasks/AccessGoogleTask.java       |   98 +-
 .../puppetor/rmi/tasks/AddHiddenServiceTask.java   |  156 +-
 .../puppetor/rmi/tasks/BuildCircuitsTask.java      |   44 +-
 .../puppetor/rmi/tasks/ConfigureAsPrivateTask.java |   60 +-
 .../rmi/tasks/CreateDirectoryAuthorityTask.java    |   58 +-
 .../puppetor/rmi/tasks/CreateNetworkTask.java      |   28 +-
 .../puppetor/rmi/tasks/CreateProxyTask.java        |   60 +-
 .../puppetor/rmi/tasks/CreateRouterTask.java       |   60 +-
 .../puppetor/rmi/tasks/ShutdownNodesTask.java      |   38 +-
 .../puppetor/rmi/tasks/StartNodesTask.java         |   46 +-
 .../puppetor/rmi/tasks/TerminateTask.java          |   28 +-
 .../tests/AccessGoogleOverPublicTorNetwork.java    |   42 +-
 ...singHiddenServiceOverPrivateTorNetworkTest.java |   44 +-
 .../puppetor/rmi/tests/TestRegistration.java       |   40 +-
 73 files changed, 8590 insertions(+), 8592 deletions(-)

diff --git a/src/org/torproject/puppetor/ClientApplication.java b/src/org/torproject/puppetor/ClientApplication.java
index a1b27a0..cb5ca63 100644
--- a/src/org/torproject/puppetor/ClientApplication.java
+++ b/src/org/torproject/puppetor/ClientApplication.java
@@ -16,85 +16,85 @@ package org.torproject.puppetor;
  */
 public interface ClientApplication {
 
-	/**
-	 * <p>
-	 * Performs one or more HTTP requests to a previously provided address and
-	 * port. All requests are performed by a thread in the background, so that
-	 * this method returns immediately. That thread will try for
-	 * <code>retries</code> times to make the request with a timeout of
-	 * <code>timeoutForEachRetry</code> milliseconds each. If an attempt is
-	 * not successful, the thread nevertheless waits for the timeout to expire
-	 * before performing the next attempt. If <code>stopOnSuccess</code> is
-	 * set to <code>true</code>, the thread will quit performing requests
-	 * immediately after the first successful request.
-	 * </p>
-	 *
-	 * <p>
-	 * For each sent request the application fires a
-	 * <event>ClientEventType.CLIENT_SENDING_REQUEST</code> event. On receiving
-	 * a reply it fires an event of type <code>ClientEventType.CLIENT_REPLY_RECEIVED</code>,
-	 * if a request is not successful or times out, a <code>ClientEventType.CLIENT_GAVE_UP_REQUEST</code>
-	 * event is fired. After all requests have been performed (either
-	 * successfully, or not) an event of type <code>ClientEventType.CLIENT_REQUESTS_PERFORMED</code>
-	 * is fired.
-	 * </p>
-	 *
-	 * TODO may this method only be invoked once?!
-	 *
-	 * @param retries
-	 *            The number of retries that this client will perform. Must be
-	 *            <code>1</code> or greater.
-	 * @param timeoutForEachRetry
-	 *            The timeout for each request. If a request is not successful,
-	 *            the thread nevertheless waits for the timeout to expire. Must
-	 *            not be negative.
-	 * @param stopOnSuccess
-	 *            If set to <code>true</code>, the client quits performing
-	 *            requests after the first successful request, if <code>false</code>
-	 *            it continues until all <code>retries</code> have been
-	 *            processed.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract void startRequests(int retries, long timeoutForEachRetry,
-			boolean stopOnSuccess);
+    /**
+     * <p>
+     * Performs one or more HTTP requests to a previously provided address and
+     * port. All requests are performed by a thread in the background, so that
+     * this method returns immediately. That thread will try for
+     * <code>retries</code> times to make the request with a timeout of
+     * <code>timeoutForEachRetry</code> milliseconds each. If an attempt is
+     * not successful, the thread nevertheless waits for the timeout to expire
+     * before performing the next attempt. If <code>stopOnSuccess</code> is
+     * set to <code>true</code>, the thread will quit performing requests
+     * immediately after the first successful request.
+     * </p>
+     *
+     * <p>
+     * For each sent request the application fires a
+     * <event>ClientEventType.CLIENT_SENDING_REQUEST</code> event. On receiving
+     * a reply it fires an event of type <code>ClientEventType.CLIENT_REPLY_RECEIVED</code>,
+     * if a request is not successful or times out, a <code>ClientEventType.CLIENT_GAVE_UP_REQUEST</code>
+     * event is fired. After all requests have been performed (either
+     * successfully, or not) an event of type <code>ClientEventType.CLIENT_REQUESTS_PERFORMED</code>
+     * is fired.
+     * </p>
+     *
+     * TODO may this method only be invoked once?!
+     *
+     * @param retries
+     *            The number of retries that this client will perform. Must be
+     *            <code>1</code> or greater.
+     * @param timeoutForEachRetry
+     *            The timeout for each request. If a request is not successful,
+     *            the thread nevertheless waits for the timeout to expire. Must
+     *            not be negative.
+     * @param stopOnSuccess
+     *            If set to <code>true</code>, the client quits performing
+     *            requests after the first successful request, if <code>false</code>
+     *            it continues until all <code>retries</code> have been
+     *            processed.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for either of the
+     *             parameters.
+     */
+    public abstract void startRequests(int retries, long timeoutForEachRetry,
+            boolean stopOnSuccess);
 
-	/**
-	 * Stops all requests that are currently running.
-	 *
-	 * @throws IllegalStateException
-	 *             Thrown if no requests have been started before.
-	 */
-	public abstract void stopRequest();
+    /**
+     * Stops all requests that are currently running.
+     *
+     * @throws IllegalStateException
+     *             Thrown if no requests have been started before.
+     */
+    public abstract void stopRequest();
 
-	/**
-	 * Returns the name of this client.
-	 *
-	 * @return The name of this client.
-	 */
-	public abstract String getClientApplicationName();
+    /**
+     * Returns the name of this client.
+     *
+     * @return The name of this client.
+     */
+    public abstract String getClientApplicationName();
 
-	/**
-	 * Returns the SOCKS port of the local Tor node to which requests are sent.
-	 *
-	 * @return The SOCKS port of the local Tor node to which requests are sent.
-	 */
-	public abstract int getSocksPort();
+    /**
+     * Returns the SOCKS port of the local Tor node to which requests are sent.
+     *
+     * @return The SOCKS port of the local Tor node to which requests are sent.
+     */
+    public abstract int getSocksPort();
 
-	/**
-	 * Returns the target name for the requests sent by this client; can be
-	 * either a server name/address or an onion address.
-	 *
-	 * @return The target name for the requests sent by this client.
-	 */
-	public abstract String getTargetName();
+    /**
+     * Returns the target name for the requests sent by this client; can be
+     * either a server name/address or an onion address.
+     *
+     * @return The target name for the requests sent by this client.
+     */
+    public abstract String getTargetName();
 
-	/**
-	 * Returns the target port for the requests sent by this client; can be
-	 * either a server port or a virtual port of a hidden service.
-	 *
-	 * @return The target port for the requests sent by this client.
-	 */
-	public abstract int getTargetPort();
+    /**
+     * Returns the target port for the requests sent by this client; can be
+     * either a server port or a virtual port of a hidden service.
+     *
+     * @return The target port for the requests sent by this client.
+     */
+    public abstract int getTargetPort();
 }
diff --git a/src/org/torproject/puppetor/ClientEventType.java b/src/org/torproject/puppetor/ClientEventType.java
index 0b5c81e..a689713 100644
--- a/src/org/torproject/puppetor/ClientEventType.java
+++ b/src/org/torproject/puppetor/ClientEventType.java
@@ -12,53 +12,53 @@ package org.torproject.puppetor;
 @SuppressWarnings("serial")
 public class ClientEventType implements EventType {
 
-	/**
-	 * String identifying the type of the event type.
-	 */
-	String typeString;
+    /**
+     * String identifying the type of the event type.
+     */
+    String typeString;
 
-	/**
-	 * Creates a new event type with the given type string.
-	 *
-	 * @param typeString
-	 *            String identifying the type of the event type.
-	 */
-	public ClientEventType(final String typeString) {
-		this.typeString = typeString;
-	}
+    /**
+     * Creates a new event type with the given type string.
+     *
+     * @param typeString
+     *            String identifying the type of the event type.
+     */
+    public ClientEventType(final String typeString) {
+        this.typeString = typeString;
+    }
 
-	public String getTypeName() {
-		return typeString;
-	}
+    public String getTypeName() {
+        return typeString;
+    }
 
-	/**
-	 * The client application is sending a request; this event is fired
-	 * internally and not parsed from a log statement from Tor.
-	 */
-	public static final ClientEventType CLIENT_SENDING_REQUEST =
-			new ClientEventType("CLIENT_SENDING_REQUEST");
+    /**
+     * The client application is sending a request; this event is fired
+     * internally and not parsed from a log statement from Tor.
+     */
+    public static final ClientEventType CLIENT_SENDING_REQUEST =
+            new ClientEventType("CLIENT_SENDING_REQUEST");
 
-	/**
-	 * The client application has received a reply to a previously sent request;
-	 * this event is fired internally and not parsed from a log statement from
-	 * Tor.
-	 */
-	public static final ClientEventType CLIENT_REPLY_RECEIVED =
-			new ClientEventType("CLIENT_REPLY_RECEIVED");
+    /**
+     * The client application has received a reply to a previously sent request;
+     * this event is fired internally and not parsed from a log statement from
+     * Tor.
+     */
+    public static final ClientEventType CLIENT_REPLY_RECEIVED =
+            new ClientEventType("CLIENT_REPLY_RECEIVED");
 
-	/**
-	 * The client application has given up waiting for the reply to a previously
-	 * sent request; this event is fired internally and not parsed from a log
-	 * statement from Tor.
-	 */
-	public static final ClientEventType CLIENT_GAVE_UP_REQUEST =
-			new ClientEventType("CLIENT_GAVE_UP_REQUEST");
+    /**
+     * The client application has given up waiting for the reply to a previously
+     * sent request; this event is fired internally and not parsed from a log
+     * statement from Tor.
+     */
+    public static final ClientEventType CLIENT_GAVE_UP_REQUEST =
+            new ClientEventType("CLIENT_GAVE_UP_REQUEST");
 
-	/**
-	 * The client application has completed a series of requests, whether they
-	 * were successful or not; this event is fired internally and not parsed
-	 * from a log statement from Tor.
-	 */
-	public static final ClientEventType CLIENT_REQUESTS_PERFORMED =
-			new ClientEventType("CLIENT_REQUESTS_PERFORMED");
+    /**
+     * The client application has completed a series of requests, whether they
+     * were successful or not; this event is fired internally and not parsed
+     * from a log statement from Tor.
+     */
+    public static final ClientEventType CLIENT_REQUESTS_PERFORMED =
+            new ClientEventType("CLIENT_REQUESTS_PERFORMED");
 }
diff --git a/src/org/torproject/puppetor/DirectoryNode.java b/src/org/torproject/puppetor/DirectoryNode.java
index 41fa6a2..5d71f37 100644
--- a/src/org/torproject/puppetor/DirectoryNode.java
+++ b/src/org/torproject/puppetor/DirectoryNode.java
@@ -18,32 +18,32 @@ import java.util.Set;
  */
 public interface DirectoryNode extends RouterNode {
 
-	/**
-	 * Combines the fingerprint of this node to a <code>DirServer</code>
-	 * string that can be used to configure this or other nodes to use this node
-	 * as directory server.
-	 *
-	 * @return <code>DirServer</code> string to configure a node to use this
-	 *         node as directory server.
-	 * @throws PuppeTorException
-	 *             Thrown if a problem occurs when determining the fingerprint
-	 *             of this node.
-	 */
-	public abstract String getDirServerString() throws PuppeTorException;
+    /**
+     * Combines the fingerprint of this node to a <code>DirServer</code>
+     * string that can be used to configure this or other nodes to use this node
+     * as directory server.
+     *
+     * @return <code>DirServer</code> string to configure a node to use this
+     *         node as directory server.
+     * @throws PuppeTorException
+     *             Thrown if a problem occurs when determining the fingerprint
+     *             of this node.
+     */
+    public abstract String getDirServerString() throws PuppeTorException;
 
-	/**
-	 * Adds the given (possibly empty) set of onion router fingerprints to the
-	 * set of approved routers to confirm to directory clients, that the given
-	 * routers can be trusted. Changes are only stored locally and not written
-	 * to the <code>approved-routers</code> file to disk which will be done
-	 * when writing the configuration of this node.
-	 *
-	 * @param approvedRouters
-	 *            The set of approved routers to be added. Each provided string
-	 *            must be formatted as
-	 *            <code>nickname 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed as parameter.
-	 */
-	public void addApprovedRouters(Set<String> approvedRouters);
+    /**
+     * Adds the given (possibly empty) set of onion router fingerprints to the
+     * set of approved routers to confirm to directory clients, that the given
+     * routers can be trusted. Changes are only stored locally and not written
+     * to the <code>approved-routers</code> file to disk which will be done
+     * when writing the configuration of this node.
+     *
+     * @param approvedRouters
+     *            The set of approved routers to be added. Each provided string
+     *            must be formatted as
+     *            <code>nickname 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000</code>.
+     * @throws IllegalArgumentException
+     *             Thrown if <code>null</code> is passed as parameter.
+     */
+    public void addApprovedRouters(Set<String> approvedRouters);
 }
diff --git a/src/org/torproject/puppetor/Event.java b/src/org/torproject/puppetor/Event.java
index 03626b1..4b54af0 100644
--- a/src/org/torproject/puppetor/Event.java
+++ b/src/org/torproject/puppetor/Event.java
@@ -21,34 +21,34 @@ import java.io.Serializable;
  */
 public interface Event extends Serializable {
 
-	/**
-	 * Returns the name of the source of this event, which can be a Tor process
-	 * or a client/server application running as thread in the background.
-	 *
-	 * @return The event source.
-	 */
-	public abstract String getSource();
+    /**
+     * Returns the name of the source of this event, which can be a Tor process
+     * or a client/server application running as thread in the background.
+     *
+     * @return The event source.
+     */
+    public abstract String getSource();
 
-	/**
-	 * Returns the event type.
-	 *
-	 * @return The event type.
-	 */
-	public abstract EventType getType();
+    /**
+     * Returns the event type.
+     *
+     * @return The event type.
+     */
+    public abstract EventType getType();
 
-	/**
-	 * Returns the original message that lead to firing this event.
-	 *
-	 * @return The original message.
-	 */
-	public abstract String getMessage();
+    /**
+     * Returns the original message that lead to firing this event.
+     *
+     * @return The original message.
+     */
+    public abstract String getMessage();
 
-	/**
-	 * Returns the occurrence time of this event, which is either the parsed
-	 * time from a Tor log statement, or the current system time when a
-	 * client/server application fired this event.
-	 *
-	 * @return The occurrence time.
-	 */
-	public abstract long getOccurrenceTime();
+    /**
+     * Returns the occurrence time of this event, which is either the parsed
+     * time from a Tor log statement, or the current system time when a
+     * client/server application fired this event.
+     *
+     * @return The occurrence time.
+     */
+    public abstract long getOccurrenceTime();
 }
diff --git a/src/org/torproject/puppetor/EventListener.java b/src/org/torproject/puppetor/EventListener.java
index 099cfec..90d6a66 100644
--- a/src/org/torproject/puppetor/EventListener.java
+++ b/src/org/torproject/puppetor/EventListener.java
@@ -13,13 +13,13 @@ package org.torproject.puppetor;
  */
 public interface EventListener {
 
-	/**
-	 * Is invoked when an asynchronous event is fired by the source (or one of
-	 * the sources) for which this listener was registered.
-	 *
-	 * @param event
-	 *            The event that was fired.
-	 */
-	public void handleEvent(Event event);
+    /**
+     * Is invoked when an asynchronous event is fired by the source (or one of
+     * the sources) for which this listener was registered.
+     *
+     * @param event
+     *            The event that was fired.
+     */
+    public void handleEvent(Event event);
 
 }
diff --git a/src/org/torproject/puppetor/EventManager.java b/src/org/torproject/puppetor/EventManager.java
index eafb9c1..81a6e53 100644
--- a/src/org/torproject/puppetor/EventManager.java
+++ b/src/org/torproject/puppetor/EventManager.java
@@ -18,214 +18,214 @@ import java.util.List;
  */
 public interface EventManager {
 
-	/**
-	 * Registers the given <code>listener</code> as event listener for events
-	 * originating from the given <code>source</code>. This method returns a
-	 * list of all previously fired events by this source, so that each event
-	 * fired by this source is either included in the returned list or
-	 * signalized in a later invocation on the event listener, but not in both.
-	 * This prevents race conditions by eliminating the gap between registration
-	 * of an event handler and asking if an event has been fired before
-	 * registering.
-	 *
-	 * @param source
-	 *            The name of the source of events that the listener is
-	 *            interested in. May not be <code>null</code> and must be the
-	 *            name of a previously created node, client, or server.
-	 * @param listener
-	 *            The listener that wants to be notified about events from the
-	 *            given <code>source</code>. If the <code>listener</code>
-	 *            is already registered for the same <code>source</code>,
-	 *            nothing happens, i.e. the <code>listener</code> will not
-	 *            receive multiple invocations for the same event. May not be
-	 *            <code>null</code>.
-	 * @return A list of all previously fired events for the given
-	 *         <code>source</code>. If no event has been fired before, an
-	 *         empty list is returned instead of <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed for either of the
-	 *             parameters or if the <code>source</code> is unknown.
-	 */
-	public abstract List<Event> addEventListener(String source,
-			EventListener listener);
+    /**
+     * Registers the given <code>listener</code> as event listener for events
+     * originating from the given <code>source</code>. This method returns a
+     * list of all previously fired events by this source, so that each event
+     * fired by this source is either included in the returned list or
+     * signalized in a later invocation on the event listener, but not in both.
+     * This prevents race conditions by eliminating the gap between registration
+     * of an event handler and asking if an event has been fired before
+     * registering.
+     *
+     * @param source
+     *            The name of the source of events that the listener is
+     *            interested in. May not be <code>null</code> and must be the
+     *            name of a previously created node, client, or server.
+     * @param listener
+     *            The listener that wants to be notified about events from the
+     *            given <code>source</code>. If the <code>listener</code>
+     *            is already registered for the same <code>source</code>,
+     *            nothing happens, i.e. the <code>listener</code> will not
+     *            receive multiple invocations for the same event. May not be
+     *            <code>null</code>.
+     * @return A list of all previously fired events for the given
+     *         <code>source</code>. If no event has been fired before, an
+     *         empty list is returned instead of <code>null</code>.
+     * @throws IllegalArgumentException
+     *             Thrown if <code>null</code> is passed for either of the
+     *             parameters or if the <code>source</code> is unknown.
+     */
+    public abstract List<Event> addEventListener(String source,
+            EventListener listener);
 
-	/**
-	 * Registers the given <code>listener</code> as event listener for future
-	 * events originating from any source.
-	 *
-	 * @param listener
-	 *            The listener that wants to be notified about events from the
-	 *            given <code>source</code>. If the <code>listener</code>
-	 *            is already registered for all sources, nothing happens, i.e.
-	 *            the <code>listener</code> will not receive multiple
-	 *            invocations for the same event. May not be <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed for the parameter.
-	 */
-	public abstract void addEventListener(EventListener listener);
+    /**
+     * Registers the given <code>listener</code> as event listener for future
+     * events originating from any source.
+     *
+     * @param listener
+     *            The listener that wants to be notified about events from the
+     *            given <code>source</code>. If the <code>listener</code>
+     *            is already registered for all sources, nothing happens, i.e.
+     *            the <code>listener</code> will not receive multiple
+     *            invocations for the same event. May not be <code>null</code>.
+     * @throws IllegalArgumentException
+     *             Thrown if <code>null</code> is passed for the parameter.
+     */
+    public abstract void addEventListener(EventListener listener);
 
-	/**
-	 * Returns the list of all previously observed events from the given
-	 * <code>source</code>.
-	 *
-	 * @param source
-	 *            The source of the events that the invoking thread is
-	 *            interested in. May not be <code>null</code> and must be the
-	 *            name of a previously created node, client, or server.
-	 * @return List of all previously observed events from the given
-	 *         <code>source</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed as parameter or if
-	 *             the <code>source</code> is unknown.
-	 */
-	public abstract List<Event> getEventHistory(String source);
+    /**
+     * Returns the list of all previously observed events from the given
+     * <code>source</code>.
+     *
+     * @param source
+     *            The source of the events that the invoking thread is
+     *            interested in. May not be <code>null</code> and must be the
+     *            name of a previously created node, client, or server.
+     * @return List of all previously observed events from the given
+     *         <code>source</code>.
+     * @throws IllegalArgumentException
+     *             Thrown if <code>null</code> is passed as parameter or if
+     *             the <code>source</code> is unknown.
+     */
+    public abstract List<Event> getEventHistory(String source);
 
-	/**
-	 * Returns whether the given <code>eventType</code> has been observed from
-	 * the given <code>source</code> before, or not.
-	 *
-	 * @param source
-	 *            The source of the event that the invoking thread is interested
-	 *            in. May not be <code>null</code> and must be the name of a
-	 *            previously created node, client, or server.
-	 * @param eventType
-	 *            The event type that the invoking thread is interested in. May
-	 *            not be <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed for either of the
-	 *             parameters or if <code>source</code> is unknown.
-	 * @return <code>true</code> if the event has been observed from the
-	 *         source before, <code>false</code> otherwise.
-	 */
-	public abstract boolean hasEventOccured(String source, EventType eventType);
+    /**
+     * Returns whether the given <code>eventType</code> has been observed from
+     * the given <code>source</code> before, or not.
+     *
+     * @param source
+     *            The source of the event that the invoking thread is interested
+     *            in. May not be <code>null</code> and must be the name of a
+     *            previously created node, client, or server.
+     * @param eventType
+     *            The event type that the invoking thread is interested in. May
+     *            not be <code>null</code>.
+     * @throws IllegalArgumentException
+     *             Thrown if <code>null</code> is passed for either of the
+     *             parameters or if <code>source</code> is unknown.
+     * @return <code>true</code> if the event has been observed from the
+     *         source before, <code>false</code> otherwise.
+     */
+    public abstract boolean hasEventOccured(String source, EventType eventType);
 
-	/**
-	 * Removes the given <code>listener</code> as event listener from all
-	 * previously registered sources. If this listener is not registered for any
-	 * source, nothing happens.
-	 *
-	 * @param listener
-	 *            The listener that shall be removed from the list of registered
-	 *            listeners. May not be <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed as parameter.
-	 */
-	public abstract void removeEventListener(EventListener listener);
+    /**
+     * Removes the given <code>listener</code> as event listener from all
+     * previously registered sources. If this listener is not registered for any
+     * source, nothing happens.
+     *
+     * @param listener
+     *            The listener that shall be removed from the list of registered
+     *            listeners. May not be <code>null</code>.
+     * @throws IllegalArgumentException
+     *             Thrown if <code>null</code> is passed as parameter.
+     */
+    public abstract void removeEventListener(EventListener listener);
 
-	/**
-	 * Checks if the given <code>eventType</code> has been observed from the
-	 * given <code>source</code> before; if not, blocks the invoking thread
-	 * until the next event of this type is fired from that source. Note that
-	 * this method does not restrict waiting to a timeout, so that it could
-	 * potentially block forever!
-	 *
-	 * @param source
-	 *            The source of the event that the invoking thread is willing to
-	 *            wait for. May not be <code>null</code> and must be the name
-	 *            of a previously created node, client, or server.
-	 * @param eventType
-	 *            The event type that the invoking thread is willing to wait for
-	 *            from the given <code>source</code>. May not be
-	 *            <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed for either of the
-	 *             parameters or if the <code>source</code> is unknown.
-	 */
-	public abstract void waitForAnyOccurence(String source, EventType eventType);
+    /**
+     * Checks if the given <code>eventType</code> has been observed from the
+     * given <code>source</code> before; if not, blocks the invoking thread
+     * until the next event of this type is fired from that source. Note that
+     * this method does not restrict waiting to a timeout, so that it could
+     * potentially block forever!
+     *
+     * @param source
+     *            The source of the event that the invoking thread is willing to
+     *            wait for. May not be <code>null</code> and must be the name
+     *            of a previously created node, client, or server.
+     * @param eventType
+     *            The event type that the invoking thread is willing to wait for
+     *            from the given <code>source</code>. May not be
+     *            <code>null</code>.
+     * @throws IllegalArgumentException
+     *             Thrown if <code>null</code> is passed for either of the
+     *             parameters or if the <code>source</code> is unknown.
+     */
+    public abstract void waitForAnyOccurence(String source, EventType eventType);
 
-	/**
-	 * Checks if the given <code>eventType</code> has been observed from the
-	 * given <code>source</code> before; if not, blocks the invoking thread
-	 * until the next event of this type is fired from that source or the given
-	 * timeout of <code>maximumTimeToWaitInMillis</code> milliseconds has
-	 * expired.
-	 *
-	 * @param source
-	 *            The source of the event that the invoking thread is willing to
-	 *            wait for. May not be <code>null</code> and must be the name
-	 *            of a previously created node, client, or server.
-	 * @param eventType
-	 *            The event type that the invoking thread is willing to wait for
-	 *            from the given <code>source</code>. May not be
-	 *            <code>null</code>.
-	 * @param maximumTimeToWaitInMillis
-	 *            The maximum time to wait in milliseconds. A positive value or
-	 *            zero restricts waiting to this time. If this value is
-	 *            negative, we will wait potentially forever.
-	 * @return <code>true</code> if an event of the given type has been fired
-	 *         by the <code>source</code> within the given timeout,
-	 *         <code>false</code> otherwise.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is passed for either of the
-	 *             parameters or if the <code>source</code> is unknown.
-	 */
-	public abstract boolean waitForAnyOccurence(String source,
-			EventType eventType, long maximumTimeToWaitInMillis);
+    /**
+     * Checks if the given <code>eventType</code> has been observed from the
+     * given <code>source</code> before; if not, blocks the invoking thread
+     * until the next event of this type is fired from that source or the given
+     * timeout of <code>maximumTimeToWaitInMillis</code> milliseconds has
+     * expired.
+     *
+     * @param source
+     *            The source of the event that the invoking thread is willing to
+     *            wait for. May not be <code>null</code> and must be the name
+     *            of a previously created node, client, or server.
+     * @param eventType
+     *            The event type that the invoking thread is willing to wait for
+     *            from the given <code>source</code>. May not be
+     *            <code>null</code>.
+     * @param maximumTimeToWaitInMillis
+     *            The maximum time to wait in milliseconds. A positive value or
+     *            zero restricts waiting to this time. If this value is
+     *            negative, we will wait potentially forever.
+     * @return <code>true</code> if an event of the given type has been fired
+     *         by the <code>source</code> within the given timeout,
+     *         <code>false</code> otherwise.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is passed for either of the
+     *             parameters or if the <code>source</code> is unknown.
+     */
+    public abstract boolean waitForAnyOccurence(String source,
+            EventType eventType, long maximumTimeToWaitInMillis);
 
-	/**
-	 * Blocks the invoking thread until the next <code>event</code> is fired
-	 * from the given <code>source</code>. This method only waits for the
-	 * next occurence of an event, regardless of previous occurrences. Note that
-	 * this method does not restrict waiting to a timeout, so that it could
-	 * potentially block forever!
-	 *
-	 * @param source
-	 *            The source of the event that the invoking thread is willing to
-	 *            wait for. May not be <code>null</code> and must be the name
-	 *            of a previously created node, client, or server.
-	 * @param eventType
-	 *            The event type that the invoking thread is willing to wait for
-	 *            from the given <code>source</code>. May not be
-	 *            <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed for either of the
-	 *             parameters or if the <code>source</code> is unknown.
-	 */
-	public abstract void waitForNextOccurence(String source, EventType eventType);
+    /**
+     * Blocks the invoking thread until the next <code>event</code> is fired
+     * from the given <code>source</code>. This method only waits for the
+     * next occurence of an event, regardless of previous occurrences. Note that
+     * this method does not restrict waiting to a timeout, so that it could
+     * potentially block forever!
+     *
+     * @param source
+     *            The source of the event that the invoking thread is willing to
+     *            wait for. May not be <code>null</code> and must be the name
+     *            of a previously created node, client, or server.
+     * @param eventType
+     *            The event type that the invoking thread is willing to wait for
+     *            from the given <code>source</code>. May not be
+     *            <code>null</code>.
+     * @throws IllegalArgumentException
+     *             Thrown if <code>null</code> is passed for either of the
+     *             parameters or if the <code>source</code> is unknown.
+     */
+    public abstract void waitForNextOccurence(String source, EventType eventType);
 
-	/**
-	 * Blocks the invoking thread until the next <code>event</code> is fired
-	 * from the given <code>source</code> or the given timeout of
-	 * <code>maximumTimeToWaitInMillis</code> milliseconds has expired. This
-	 * method only waits for the next occurence of an event, regardless of
-	 * previous occurrences.
-	 *
-	 * @param source
-	 *            The source of the event that the invoking thread is willing to
-	 *            wait for. May not be <code>null</code> and must be the name
-	 *            of a previously created node, client, or server.
-	 * @param eventType
-	 *            The event type that the invoking thread is willing to wait for
-	 *            from the given <code>source</code>. May not be
-	 *            <code>null</code>.
-	 * @param maximumTimeToWaitInMillis
-	 *            The maximum time to wait in milliseconds. A positive value or
-	 *            zero restricts waiting to this time. If this value is
-	 *            negative, we will wait potentially forever.
-	 * @return <code>true</code> if an event of the given type has been fired
-	 *         by the <code>source</code> within the given timeout,
-	 *         <code>false</code> otherwise.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is passed for either of the
-	 *             parameters or if the <code>source</code> is unknown.
-	 */
-	public abstract boolean waitForNextOccurence(String source,
-			EventType eventType, long maximumTimeToWaitInMillis);
+    /**
+     * Blocks the invoking thread until the next <code>event</code> is fired
+     * from the given <code>source</code> or the given timeout of
+     * <code>maximumTimeToWaitInMillis</code> milliseconds has expired. This
+     * method only waits for the next occurence of an event, regardless of
+     * previous occurrences.
+     *
+     * @param source
+     *            The source of the event that the invoking thread is willing to
+     *            wait for. May not be <code>null</code> and must be the name
+     *            of a previously created node, client, or server.
+     * @param eventType
+     *            The event type that the invoking thread is willing to wait for
+     *            from the given <code>source</code>. May not be
+     *            <code>null</code>.
+     * @param maximumTimeToWaitInMillis
+     *            The maximum time to wait in milliseconds. A positive value or
+     *            zero restricts waiting to this time. If this value is
+     *            negative, we will wait potentially forever.
+     * @return <code>true</code> if an event of the given type has been fired
+     *         by the <code>source</code> within the given timeout,
+     *         <code>false</code> otherwise.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is passed for either of the
+     *             parameters or if the <code>source</code> is unknown.
+     */
+    public abstract boolean waitForNextOccurence(String source,
+            EventType eventType, long maximumTimeToWaitInMillis);
 
-	/**
-	 * Registers a new event type by passing a pattern string that can be
-	 * applied to a regular expression when parsing Tor log statements. This is
-	 * useful for log statements that are only included in modified Tor
-	 * versions. Therefore, the event type may be an instance of a self-defined
-	 * class that implements <code>EventType</code>.
-	 *
-	 * @param patternString
-	 *            The pattern string that will be used for parsing Tor log
-	 *            statements; the syntax corresponds to java.util.regex.Pattern.
-	 * @param eventType
-	 *            The event type of the event that will be fired when a log
-	 *            statement was parsed that includes the given pattern.
-	 */
-	public abstract void registerEventTypePattern(String patternString,
-			EventType eventType);
+    /**
+     * Registers a new event type by passing a pattern string that can be
+     * applied to a regular expression when parsing Tor log statements. This is
+     * useful for log statements that are only included in modified Tor
+     * versions. Therefore, the event type may be an instance of a self-defined
+     * class that implements <code>EventType</code>.
+     *
+     * @param patternString
+     *            The pattern string that will be used for parsing Tor log
+     *            statements; the syntax corresponds to java.util.regex.Pattern.
+     * @param eventType
+     *            The event type of the event that will be fired when a log
+     *            statement was parsed that includes the given pattern.
+     */
+    public abstract void registerEventTypePattern(String patternString,
+            EventType eventType);
 }
diff --git a/src/org/torproject/puppetor/EventType.java b/src/org/torproject/puppetor/EventType.java
index 4923984..ebfd6b0 100644
--- a/src/org/torproject/puppetor/EventType.java
+++ b/src/org/torproject/puppetor/EventType.java
@@ -16,11 +16,11 @@ import java.io.Serializable;
  */
 public interface EventType extends Serializable {
 
-	/**
-	 * Returns a string representation of the event type name for display
-	 * purposes.
-	 *
-	 * @return String representation of the event type name.
-	 */
-	public abstract String getTypeName();
+    /**
+     * Returns a string representation of the event type name for display
+     * purposes.
+     *
+     * @return String representation of the event type name.
+     */
+    public abstract String getTypeName();
 }
diff --git a/src/org/torproject/puppetor/HiddenService.java b/src/org/torproject/puppetor/HiddenService.java
index 26975fb..4a4381d 100644
--- a/src/org/torproject/puppetor/HiddenService.java
+++ b/src/org/torproject/puppetor/HiddenService.java
@@ -13,49 +13,49 @@ package org.torproject.puppetor;
  */
 public interface HiddenService {
 
-	/**
-	 * Determines the onion address for a previously added hidden service with
-	 * name <code>serviceName</code>. Requires that the node has been
-	 * started, i.e. is in state <code>NodeState.RUNNING</code>, and is
-	 * configured to provide this hidden service.
-	 *
-	 * @return The onion address string consisting of 16 base32 chars plus
-	 *         ".onion" for hidden service versions 0 and 1 or 16 base32 chars
-	 *         plus "." plus 24 base32 chars plus ".onion" for hidden service
-	 *         version 2.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> or a zero-length string is
-	 *             passed as parameter.
-	 * @throws IllegalStateException
-	 *             Thrown if the node at which this hidden service is configured
-	 *             is not in state <code>NodeState.RUNNING</code>.
-	 * @throws PuppeTorException
-	 *             Thrown if the onion address of this hidden service could not
-	 *             be read, which is also the case when the node's configuration
-	 *             has not been written and the node has not been HUP'ed after
-	 *             configuring the hidden service.
-	 */
-	public abstract String determineOnionAddress() throws PuppeTorException;
+    /**
+     * Determines the onion address for a previously added hidden service with
+     * name <code>serviceName</code>. Requires that the node has been
+     * started, i.e. is in state <code>NodeState.RUNNING</code>, and is
+     * configured to provide this hidden service.
+     *
+     * @return The onion address string consisting of 16 base32 chars plus
+     *         ".onion" for hidden service versions 0 and 1 or 16 base32 chars
+     *         plus "." plus 24 base32 chars plus ".onion" for hidden service
+     *         version 2.
+     * @throws IllegalArgumentException
+     *             Thrown if <code>null</code> or a zero-length string is
+     *             passed as parameter.
+     * @throws IllegalStateException
+     *             Thrown if the node at which this hidden service is configured
+     *             is not in state <code>NodeState.RUNNING</code>.
+     * @throws PuppeTorException
+     *             Thrown if the onion address of this hidden service could not
+     *             be read, which is also the case when the node's configuration
+     *             has not been written and the node has not been HUP'ed after
+     *             configuring the hidden service.
+     */
+    public abstract String determineOnionAddress() throws PuppeTorException;
 
-	/**
-	 * Returns the name of the hidden service.
-	 *
-	 * @return The name of the hidden service.
-	 */
-	public String getServiceName();
+    /**
+     * Returns the name of the hidden service.
+     *
+     * @return The name of the hidden service.
+     */
+    public String getServiceName();
 
-	/**
-	 * Returns the port on which the service listens for requests.
-	 *
-	 * @return The port on which the service listens for requests.
-	 */
-	public int getServicePort();
+    /**
+     * Returns the port on which the service listens for requests.
+     *
+     * @return The port on which the service listens for requests.
+     */
+    public int getServicePort();
 
-	/**
-	 * Returns the virtual port that this hidden service runs on as it is
-	 * announced to clients.
-	 *
-	 * @return The virtual port of this hidden service.
-	 */
-	public int getVirtualPort();
+    /**
+     * Returns the virtual port that this hidden service runs on as it is
+     * announced to clients.
+     *
+     * @return The virtual port of this hidden service.
+     */
+    public int getVirtualPort();
 }
diff --git a/src/org/torproject/puppetor/HiddenServiceEventType.java b/src/org/torproject/puppetor/HiddenServiceEventType.java
index 5d29f1f..50b7211 100644
--- a/src/org/torproject/puppetor/HiddenServiceEventType.java
+++ b/src/org/torproject/puppetor/HiddenServiceEventType.java
@@ -12,204 +12,204 @@ package org.torproject.puppetor;
 @SuppressWarnings("serial")
 public class HiddenServiceEventType implements EventType {
 
-	/**
-	 * String identifying the type of the event type.
-	 */
-	String typeString;
-
-	/**
-	 * Creates a new event type with the given type string.
-	 *
-	 * @param typeString
-	 *            String identifying the type of the event type.
-	 */
-	public HiddenServiceEventType(final String typeString) {
-		this.typeString = typeString;
-	}
-
-	public String getTypeName() {
-		return typeString;
-	}
-
-	/**
-	 * Alice has received an onion request; this event is parsed from a log
-	 * statement in connection_ap_handshake_rewrite_and_attach().
-	 */
-	public static final HiddenServiceEventType ALICE_ONION_REQUEST_RECEIVED =
-			new HiddenServiceEventType("ALICE_ONION_REQUEST_RECEIVED");
-
-	/**
-	 * Alice sends a fetch request for a hidden service descriptor to a
-	 * directory server; this event is parsed from a log statement in
-	 * rend_client_refetch_renddesc().
-	 */
-	public static final HiddenServiceEventType ALICE_SENDING_FETCH_DESC =
-			new HiddenServiceEventType("ALICE_SENDING_FETCH_DESC");
-
-	/**
-	 * Alice receives a reply to a previous fetch request for a hidden service
-	 * descriptors from a directory server; this event is parsed from a log
-	 * statement in connection_dir_client_reached_eof().
-	 */
-	public static final HiddenServiceEventType ALICE_DESC_FETCHED_RECEIVED =
-			new HiddenServiceEventType("ALICE_DESC_FETCHED_RECEIVED");
-
-	/**
-	 * Alice has built a circuit to a rendezvous point and sends an
-	 * ESTABLISH_RENDEZVOUS cell; this event is parsed from a log statement in
-	 * rend_client_send_establish_rendezvous().
-	 */
-	public static final HiddenServiceEventType ALICE_BUILT_REND_CIRC_SENDING_ESTABLISH_RENDEZVOUS =
-			new HiddenServiceEventType(
-					"ALICE_BUILT_REND_CIRC_SENDING_ESTABLISH_RENDEZVOUS");
-
-	/**
-	 * Alice receives a RENDEZVOUS_ESTABLISHED cell from a rendezvous point;
-	 * this event is parsed from a log statement in
-	 * rend_client_rendezvous_acked().
-	 */
-	public static final HiddenServiceEventType ALICE_RENDEZVOUS_ESTABLISHED_RECEIVED =
-			new HiddenServiceEventType("ALICE_RENDEZVOUS_ESTABLISHED_RECEIVED");
-
-	/**
-	 * Alice has built a circuit to an introduction point (which does not
-	 * automatically lead to sending an INTRODUCE1 cell, because the rendezvous
-	 * circuit might not be ready); this event is parsed from a log statement in
-	 * rend_client_introcirc_has_opened().
-	 */
-	public static final HiddenServiceEventType ALICE_BUILT_INTRO_CIRC =
-			new HiddenServiceEventType("ALICE_BUILT_INTRO_CIRC");
-
-	/**
-	 * Alice sends an INTRODUCE1 cell to an introduction point; this event is
-	 * parsed from a log statement in rend_client_send_introduction().
-	 */
-	public static final HiddenServiceEventType ALICE_SENDING_INTRODUCE1 =
-			new HiddenServiceEventType("ALICE_SENDING_INTRODUCE1");
-
-	/**
-	 * Alice has received an INTRODUCE_ACK cell as an acknowledgement to a
-	 * previously sent INTRODUCE1 cell; this event is parsed from a log
-	 * statement in rend_client_introduction_acked().
-	 */
-	public static final HiddenServiceEventType ALICE_INTRODUCE_ACK_RECEIVED =
-			new HiddenServiceEventType("ALICE_INTRODUCE_ACK_RECEIVED");
-
-	/**
-	 * Alice has received a RENDEZVOUS2 cell and can now open an application
-	 * connection to the client; this event is parsed from a log statement in
-	 * rend_client_receive_rendezvous().
-	 */
-	public static final HiddenServiceEventType ALICE_RENDEZVOUS2_RECEIVED_APP_CONN_OPENED =
-			new HiddenServiceEventType(
-					"ALICE_RENDEZVOUS2_RECEIVED_APP_CONN_OPENED");
-
-	/**
-	 * Bob has built a circuit to an introduction point and sends an
-	 * ESTABLISH_INTRO cell; this event is parsed from a log statement in
-	 * rend_service_intro_has_opened().
-	 */
-	public static final HiddenServiceEventType BOB_BUILT_INTRO_CIRC_SENDING_ESTABLISH_INTRO =
-			new HiddenServiceEventType(
-					"BOB_BUILT_INTRO_CIRC_SENDING_ESTABLISH_INTRO");
-
-	/**
-	 * Bob has received an INTRO_ESTABLISHED cell, i.e. a node has confirmed to
-	 * work as introduction point; this event is parsed from a log statement in
-	 * rend_service_intro_established().
-	 */
-	public static final HiddenServiceEventType BOB_INTRO_ESTABLISHED_RECEIVED =
-			new HiddenServiceEventType("BOB_INTRO_ESTABLISHED_RECEIVED");
-
-	/**
-	 * Bob posts a hidden service descriptor to the directory servers; this
-	 * event is parsed from a log statement in upload_service_descriptor().
-	 */
-	public static final HiddenServiceEventType BOB_SENDING_PUBLISH_DESC =
-			new HiddenServiceEventType("BOB_SENDING_PUBLISH_DESC");
-
-	/**
-	 * Bob received a response from a directory server to a previous publish
-	 * request; this event is parsed from a log statement in
-	 * connection_dir_client_reached_eof().
-	 */
-	public static final HiddenServiceEventType BOB_DESC_PUBLISHED_RECEIVED =
-			new HiddenServiceEventType("BOB_DESC_PUBLISHED_RECEIVED");
-
-	/**
-	 * Bob has received an INTRODUCE2 cell, i.e. a node wants to establish a
-	 * connection, and will now try to establish a circuit to the client's
-	 * rendezvous point; this event is parsed from a log statement in
-	 * rend_service_introduce().
-	 */
-	public static final HiddenServiceEventType BOB_INTRODUCE2_RECEIVED =
-			new HiddenServiceEventType("BOB_INTRODUCE2_RECEIVED");
-
-	/**
-	 * Bob has built a circuit to a rendezvous point and sends a RENDEZVOUS1
-	 * cell; this event is parsed from a log statement in
-	 * rend_service_rendezvous_has_opened().
-	 */
-	public static final HiddenServiceEventType BOB_BUILT_REND_CIRC_SENDING_RENDEZVOUS1 =
-			new HiddenServiceEventType(
-					"BOB_BUILT_REND_CIRC_SENDING_RENDEZVOUS1");
-
-	/**
-	 * Bob opens a connection to the actual hidden server; this event is parsed
-	 * from a log statement in connection_exit_begin_conn().
-	 */
-	public static final HiddenServiceEventType BOB_APP_CONN_OPENED =
-			new HiddenServiceEventType("BOB_APP_CONN_OPENED");
-
-	/**
-	 * The directory server has received a descriptor post request; this event
-	 * is parsed from a log statement in directory_handle_command_post().
-	 */
-	public static final HiddenServiceEventType DIR_PUBLISH_DESC_RECEIVED =
-			new HiddenServiceEventType("DIR_PUBLISH_DESC_RECEIVED");
-
-	/**
-	 * The directory server has received a descriptor fetch request; this event
-	 * is parsed from a log statement in directory_handle_command_get().
-	 */
-	public static final HiddenServiceEventType DIR_FETCH_DESC_RECEIVED =
-			new HiddenServiceEventType("DIR_FETCH_DESC_RECEIVED");
-
-	/**
-	 * The node received an ESTABLISH_INTRO cell, i.e. was requested to work as
-	 * introduction point, and replies with an INTRO_ESTABLISHED cell; this
-	 * event is parsed from a log statement in rend_mid_establish_intro().
-	 */
-	public static final HiddenServiceEventType IPO_RECEIVED_ESTABLISH_INTRO_SENDING_INTRO_ESTABLISHED =
-			new HiddenServiceEventType(
-					"IPO_RECEIVED_ESTABLISH_INTRO_SENDING_INTRO_ESTABLISHED");
-
-	/**
-	 * The introduction point received an INTRODUCE1 cell and reacts by sending
-	 * an INTRODUCE2 cell to Bob and an INTRODUCE_ACK cell to Alice; this event
-	 * is parsed from a log statement in rend_mid_introduce().
-	 */
-	public static final HiddenServiceEventType IPO_RECEIVED_INTRODUCE1_SENDING_INTRODUCE2_AND_INTRODUCE_ACK =
-			new HiddenServiceEventType(
-					"IPO_RECEIVED_INTRODUCE1_SENDING_INTRODUCE2_AND_INTRODUCE_ACK");
-
-	/**
-	 * The node received an ESTABLISH_RENDEZVOUS cell, i.e. was requested to
-	 * work as rendezvous point, and replies with a RENDEZVOUS_ESTABLISHED cell;
-	 * this event is parsed from a log statement in
-	 * rend_mid_establish_rendezvous().
-	 */
-	public static final HiddenServiceEventType RPO_RECEIVED_ESTABLISH_RENDEZVOUS_SENDING_RENDEZVOUS_ESTABLISHED =
-			new HiddenServiceEventType(
-					"RPO_RECEIVED_ESTABLISH_RENDEZVOUS_SENDING_RENDEZVOUS_ESTABLISHED");
-
-	/**
-	 * The rendezvous point received a RENDEZVOUS1 cell and reacts by sending a
-	 * RENDEZVOUS2 cell to Alice; this event is parsed from a log statement in
-	 * rend_mid_rendezvous().
-	 */
-	public static final HiddenServiceEventType RPO_RECEIVING_RENDEZVOUS1_SENDING_RENDEZVOUS2 =
-			new HiddenServiceEventType(
-					"RPO_RECEIVING_RENDEZVOUS1_SENDING_RENDEZVOUS2");
-
-}
\ No newline at end of file
+    /**
+     * String identifying the type of the event type.
+     */
+    String typeString;
+
+    /**
+     * Creates a new event type with the given type string.
+     *
+     * @param typeString
+     *            String identifying the type of the event type.
+     */
+    public HiddenServiceEventType(final String typeString) {
+        this.typeString = typeString;
+    }
+
+    public String getTypeName() {
+        return typeString;
+    }
+
+    /**
+     * Alice has received an onion request; this event is parsed from a log
+     * statement in connection_ap_handshake_rewrite_and_attach().
+     */
+    public static final HiddenServiceEventType ALICE_ONION_REQUEST_RECEIVED =
+            new HiddenServiceEventType("ALICE_ONION_REQUEST_RECEIVED");
+
+    /**
+     * Alice sends a fetch request for a hidden service descriptor to a
+     * directory server; this event is parsed from a log statement in
+     * rend_client_refetch_renddesc().
+     */
+    public static final HiddenServiceEventType ALICE_SENDING_FETCH_DESC =
+            new HiddenServiceEventType("ALICE_SENDING_FETCH_DESC");
+
+    /**
+     * Alice receives a reply to a previous fetch request for a hidden service
+     * descriptors from a directory server; this event is parsed from a log
+     * statement in connection_dir_client_reached_eof().
+     */
+    public static final HiddenServiceEventType ALICE_DESC_FETCHED_RECEIVED =
+            new HiddenServiceEventType("ALICE_DESC_FETCHED_RECEIVED");
+
+    /**
+     * Alice has built a circuit to a rendezvous point and sends an
+     * ESTABLISH_RENDEZVOUS cell; this event is parsed from a log statement in
+     * rend_client_send_establish_rendezvous().
+     */
+    public static final HiddenServiceEventType ALICE_BUILT_REND_CIRC_SENDING_ESTABLISH_RENDEZVOUS =
+            new HiddenServiceEventType(
+                    "ALICE_BUILT_REND_CIRC_SENDING_ESTABLISH_RENDEZVOUS");
+
+    /**
+     * Alice receives a RENDEZVOUS_ESTABLISHED cell from a rendezvous point;
+     * this event is parsed from a log statement in
+     * rend_client_rendezvous_acked().
+     */
+    public static final HiddenServiceEventType ALICE_RENDEZVOUS_ESTABLISHED_RECEIVED =
+            new HiddenServiceEventType("ALICE_RENDEZVOUS_ESTABLISHED_RECEIVED");
+
+    /**
+     * Alice has built a circuit to an introduction point (which does not
+     * automatically lead to sending an INTRODUCE1 cell, because the rendezvous
+     * circuit might not be ready); this event is parsed from a log statement in
+     * rend_client_introcirc_has_opened().
+     */
+    public static final HiddenServiceEventType ALICE_BUILT_INTRO_CIRC =
+            new HiddenServiceEventType("ALICE_BUILT_INTRO_CIRC");
+
+    /**
+     * Alice sends an INTRODUCE1 cell to an introduction point; this event is
+     * parsed from a log statement in rend_client_send_introduction().
+     */
+    public static final HiddenServiceEventType ALICE_SENDING_INTRODUCE1 =
+            new HiddenServiceEventType("ALICE_SENDING_INTRODUCE1");
+
+    /**
+     * Alice has received an INTRODUCE_ACK cell as an acknowledgement to a
+     * previously sent INTRODUCE1 cell; this event is parsed from a log
+     * statement in rend_client_introduction_acked().
+     */
+    public static final HiddenServiceEventType ALICE_INTRODUCE_ACK_RECEIVED =
+            new HiddenServiceEventType("ALICE_INTRODUCE_ACK_RECEIVED");
+
+    /**
+     * Alice has received a RENDEZVOUS2 cell and can now open an application
+     * connection to the client; this event is parsed from a log statement in
+     * rend_client_receive_rendezvous().
+     */
+    public static final HiddenServiceEventType ALICE_RENDEZVOUS2_RECEIVED_APP_CONN_OPENED =
+            new HiddenServiceEventType(
+                    "ALICE_RENDEZVOUS2_RECEIVED_APP_CONN_OPENED");
+
+    /**
+     * Bob has built a circuit to an introduction point and sends an
+     * ESTABLISH_INTRO cell; this event is parsed from a log statement in
+     * rend_service_intro_has_opened().
+     */
+    public static final HiddenServiceEventType BOB_BUILT_INTRO_CIRC_SENDING_ESTABLISH_INTRO =
+            new HiddenServiceEventType(
+                    "BOB_BUILT_INTRO_CIRC_SENDING_ESTABLISH_INTRO");
+
+    /**
+     * Bob has received an INTRO_ESTABLISHED cell, i.e. a node has confirmed to
+     * work as introduction point; this event is parsed from a log statement in
+     * rend_service_intro_established().
+     */
+    public static final HiddenServiceEventType BOB_INTRO_ESTABLISHED_RECEIVED =
+            new HiddenServiceEventType("BOB_INTRO_ESTABLISHED_RECEIVED");
+
+    /**
+     * Bob posts a hidden service descriptor to the directory servers; this
+     * event is parsed from a log statement in upload_service_descriptor().
+     */
+    public static final HiddenServiceEventType BOB_SENDING_PUBLISH_DESC =
+            new HiddenServiceEventType("BOB_SENDING_PUBLISH_DESC");
+
+    /**
+     * Bob received a response from a directory server to a previous publish
+     * request; this event is parsed from a log statement in
+     * connection_dir_client_reached_eof().
+     */
+    public static final HiddenServiceEventType BOB_DESC_PUBLISHED_RECEIVED =
+            new HiddenServiceEventType("BOB_DESC_PUBLISHED_RECEIVED");
+
+    /**
+     * Bob has received an INTRODUCE2 cell, i.e. a node wants to establish a
+     * connection, and will now try to establish a circuit to the client's
+     * rendezvous point; this event is parsed from a log statement in
+     * rend_service_introduce().
+     */
+    public static final HiddenServiceEventType BOB_INTRODUCE2_RECEIVED =
+            new HiddenServiceEventType("BOB_INTRODUCE2_RECEIVED");
+
+    /**
+     * Bob has built a circuit to a rendezvous point and sends a RENDEZVOUS1
+     * cell; this event is parsed from a log statement in
+     * rend_service_rendezvous_has_opened().
+     */
+    public static final HiddenServiceEventType BOB_BUILT_REND_CIRC_SENDING_RENDEZVOUS1 =
+            new HiddenServiceEventType(
+                    "BOB_BUILT_REND_CIRC_SENDING_RENDEZVOUS1");
+
+    /**
+     * Bob opens a connection to the actual hidden server; this event is parsed
+     * from a log statement in connection_exit_begin_conn().
+     */
+    public static final HiddenServiceEventType BOB_APP_CONN_OPENED =
+            new HiddenServiceEventType("BOB_APP_CONN_OPENED");
+
+    /**
+     * The directory server has received a descriptor post request; this event
+     * is parsed from a log statement in directory_handle_command_post().
+     */
+    public static final HiddenServiceEventType DIR_PUBLISH_DESC_RECEIVED =
+            new HiddenServiceEventType("DIR_PUBLISH_DESC_RECEIVED");
+
+    /**
+     * The directory server has received a descriptor fetch request; this event
+     * is parsed from a log statement in directory_handle_command_get().
+     */
+    public static final HiddenServiceEventType DIR_FETCH_DESC_RECEIVED =
+            new HiddenServiceEventType("DIR_FETCH_DESC_RECEIVED");
+
+    /**
+     * The node received an ESTABLISH_INTRO cell, i.e. was requested to work as
+     * introduction point, and replies with an INTRO_ESTABLISHED cell; this
+     * event is parsed from a log statement in rend_mid_establish_intro().
+     */
+    public static final HiddenServiceEventType IPO_RECEIVED_ESTABLISH_INTRO_SENDING_INTRO_ESTABLISHED =
+            new HiddenServiceEventType(
+                    "IPO_RECEIVED_ESTABLISH_INTRO_SENDING_INTRO_ESTABLISHED");
+
+    /**
+     * The introduction point received an INTRODUCE1 cell and reacts by sending
+     * an INTRODUCE2 cell to Bob and an INTRODUCE_ACK cell to Alice; this event
+     * is parsed from a log statement in rend_mid_introduce().
+     */
+    public static final HiddenServiceEventType IPO_RECEIVED_INTRODUCE1_SENDING_INTRODUCE2_AND_INTRODUCE_ACK =
+            new HiddenServiceEventType(
+                    "IPO_RECEIVED_INTRODUCE1_SENDING_INTRODUCE2_AND_INTRODUCE_ACK");
+
+    /**
+     * The node received an ESTABLISH_RENDEZVOUS cell, i.e. was requested to
+     * work as rendezvous point, and replies with a RENDEZVOUS_ESTABLISHED cell;
+     * this event is parsed from a log statement in
+     * rend_mid_establish_rendezvous().
+     */
+    public static final HiddenServiceEventType RPO_RECEIVED_ESTABLISH_RENDEZVOUS_SENDING_RENDEZVOUS_ESTABLISHED =
+            new HiddenServiceEventType(
+                    "RPO_RECEIVED_ESTABLISH_RENDEZVOUS_SENDING_RENDEZVOUS_ESTABLISHED");
+
+    /**
+     * The rendezvous point received a RENDEZVOUS1 cell and reacts by sending a
+     * RENDEZVOUS2 cell to Alice; this event is parsed from a log statement in
+     * rend_mid_rendezvous().
+     */
+    public static final HiddenServiceEventType RPO_RECEIVING_RENDEZVOUS1_SENDING_RENDEZVOUS2 =
+            new HiddenServiceEventType(
+                    "RPO_RECEIVING_RENDEZVOUS1_SENDING_RENDEZVOUS2");
+
+}
diff --git a/src/org/torproject/puppetor/Network.java b/src/org/torproject/puppetor/Network.java
index 743c67c..ea8456d 100644
--- a/src/org/torproject/puppetor/Network.java
+++ b/src/org/torproject/puppetor/Network.java
@@ -20,638 +20,638 @@ import java.util.Map;
  */
 public interface Network {
 
-	/**
-	 * <p>
-	 * Configures this network as private Tor network by exchanging directory
-	 * strings and router fingerprints between the nodes of this network.
-	 * Afterwards, the nodes will be able to run a private Tor network,
-	 * separated from public directory servers and onion routers.
-	 * </p>
-	 *
-	 * <p>
-	 * The configuration is done in two steps:
-	 * <ol>
-	 * <li>Directory strings of directory nodes are added to the configurations
-	 * of all nodes in the other network.</li>
-	 * <li>Router fingerprints of all router and directory nodes are added to
-	 * the <code>approved-routers</code> files of the directory nodes.</li>
-	 * </ol>
-	 * </p>
-	 *
-	 * <p>
-	 * This operation may be invoked in any state of the contained nodes.
-	 * However, a network that does not have directory nodes of its own but
-	 * relies on directory nodes of a merged network <b>should not be started
-	 * before being configured as private network!</b> Otherwise it would
-	 * connect to the public Tor network before being merged with the other
-	 * private Tor network. However, it may also be invoked at a later time,
-	 * e.g. to admit new nodes.
-	 * </p>
-	 *
-	 * <p>
-	 * This operation does not write any configurations to disk and neither
-	 * starts a nodes nor sends HUP signals to running nodes. These operations
-	 * are left to the application, so that they have more control over the
-	 * network behavior.
-	 * </p>
-	 *
-	 * <p>
-	 * Applications need to ensure that there are enough directory nodes (2) and
-	 * router nodes (3) in the network to allow normal operation.
-	 * </p>
-	 *
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while determining the nodes'
-	 *             fingerprints.
-	 */
-	public abstract void configureAsPrivateNetwork() throws PuppeTorException;
-
-	/**
-	 * XXX document properly -SH
-	 */
-	public void configureAsPartOfPrivateNetwork(
-			List<String> authorizedDirectoriesFingerprints);
-
-	
-	/**
-	 * Creates a new client application, but does not yet perform a request.
-	 *
-	 * @param clientApplicationName
-	 *            The name for this client application, which is used for
-	 *            logging purposes and as event source. May neither be
-	 *            <code>null</code> or a zero-length string. The name needs to
-	 *            be unique in this network.
-	 * @param targetAddress
-	 *            The target for requests sent by this client application. Can
-	 *            be a publicly available URL or an onion address. May neither
-	 *            be <code>null</code> or a zero-length string.
-	 * @param targetPort
-	 *            The TCP port for requests sent by this client application. If
-	 *            the target address is an onion address, this port is the
-	 *            virtual port that the hidden service has announced. May not be
-	 *            negative or greater than 65535.
-	 * @param socksPort
-	 *            The TCP port on which a local Tor process is waiting for
-	 *            incoming SOCKS requests. May not be negative or greater than
-	 *            65535.
-	 * @return Reference to the created client application.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract ClientApplication createClient(
-			String clientApplicationName, String targetAddress, int targetPort,
-			int socksPort);
-
-	/**
-	 * Creates a new directory node with automatically assigned ports and adds
-	 * it to the network, but does not yet write its configuration to disk or
-	 * start the corresponding Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @return Reference to the created directory node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given as node name.
-	 */
-	public abstract DirectoryNode createDirectory(String nodeName);
-
-	/**
-	 * Creates a new directory node with automatically assigned ports that will
-	 * listen on the given IP address and adds it to the network, but does not
-	 * yet write its configuration to disk or start the corresponding Tor
-	 * process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @param serverIpAddress
-	 *            The IP address on which the node will listen. Must be a valid
-	 *            IP v4 address in dotted decimal notation. May not be
-	 *            <code>null</code>.
-	 * @return Reference to the created directory node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given as node name.
-	 */
-	public abstract DirectoryNode createDirectory(String nodeName,
-			String serverIpAddress);
-
-	/**
-	 * Creates a new directory node and adds it to the network, but does not yet
-	 * write its configuration to disk or start the corresponding Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @param controlPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for a controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming SOCKS requests. May not be negative or greater
-	 *            than 65535.
-	 * @param orPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming requests from other onion routers. May not be
-	 *            negative or greater than 65535.
-	 * @param dirPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming directory requests. May not be negative or
-	 *            greater than 65535.
-	 * @return Reference to the created directory node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract DirectoryNode createDirectory(String nodeName,
-			int controlPort, int socksPort, int orPort, int dirPort);
-
-	/**
-	 * Creates a new directory node that will listen on the given IP address and
-	 * adds it to the network, but does not yet write its configuration to disk
-	 * or start the corresponding Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @param controlPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for a controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming SOCKS requests. May not be negative or greater
-	 *            than 65535.
-	 * @param orPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming requests from other onion routers. May not be
-	 *            negative or greater than 65535.
-	 * @param dirPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming directory requests. May not be negative or
-	 *            greater than 65535.
-	 * @param serverIpAddress
-	 *            The IP address on which the node will listen. Must be a valid
-	 *            IP v4 address in dotted decimal notation. May not be
-	 *            <code>null</code>.
-	 * @return Reference to the created directory node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract DirectoryNode createDirectory(String nodeName,
-			int controlPort, int socksPort, int orPort, int dirPort,
-			String serverIpAddress);
-
-	/**
-	 * Creates a new <code>ProxyNode</code> with automatically assigned ports
-	 * and adds it to the network, but does not yet write its configuration to
-	 * disk or start the corresponding Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, and as event source. May
-	 *            neither be <code>null</code> or have zero or more than 19
-	 *            alpha-numeric characters. The node name needs to be unique in
-	 *            this network.
-	 * @return Reference to the created proxy node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given as node name.
-	 */
-	public abstract ProxyNode createProxy(String nodeName);
-
-	/**
-	 * Creates a new <code>ProxyNode</code> and adds it to the network, but
-	 * does not yet write its configuration to disk or start the corresponding
-	 * Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, and as event source. May
-	 *            neither be <code>null</code> or have zero or more than 19
-	 *            alpha-numeric characters. The node name needs to be unique in
-	 *            this network.
-	 * @param controlPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for a controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming SOCKS requests. May not be negative or greater
-	 *            than 65535.
-	 * @return Reference to the created proxy node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract ProxyNode createProxy(String nodeName, int controlPort,
-			int socksPort);
-
-	/**
-	 * Creates a new <code>RouterNode</code> with automatically assigned ports
-	 * and adds it to the network, but does not yet write its configuration to
-	 * disk or start the corresponding Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @return Reference to the created router node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given as node name.
-	 */
-	public abstract RouterNode createRouter(String nodeName);
-
-	/**
-	 * Creates a new <code>RouterNode</code> and adds it to the network, but
-	 * does not yet write its configuration to disk or start the corresponding
-	 * Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @param controlPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for a controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming SOCKS requests. May not be negative or greater
-	 *            than 65535.
-	 * @param orPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming requests from other onion routers. May not be
-	 *            negative or greater than 65535.
-	 * @param dirPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming directory requests which in fact are requests for
-	 *            the mirrored directory. May not be negative or greater than
-	 *            65535.
-	 * @return Reference to the created router node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract RouterNode createRouter(String nodeName, int controlPort,
-			int socksPort, int orPort, int dirPort);
-
-	/**
-	 * Creates a new <code>RouterNode</code> with automatically assigned ports
-	 * that will listen on the given IP address and adds it to the network, but
-	 * does not yet write its configuration to disk or start the corresponding
-	 * Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @param serverIpAddress
-	 *            The IP address on which the node will listen. Must be a valid
-	 *            IP v4 address in dotted decimal notation. May not be
-	 *            <code>null</code>.
-	 * @return Reference to the created router node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given as node name.
-	 */
-	public abstract RouterNode createRouter(String nodeName,
-			String serverIpAddress);
-
-	/**
-	 * Creates a new <code>RouterNode</code> that will listen on the given IP
-	 * address and adds it to the network, but does not yet write its
-	 * configuration to disk or start the corresponding Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @param controlPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for a controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming SOCKS requests. May not be negative or greater
-	 *            than 65535.
-	 * @param orPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming requests from other onion routers. May not be
-	 *            negative or greater than 65535.
-	 * @param dirPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming directory requests which in fact are requests for
-	 *            the mirrored directory. May not be negative or greater than
-	 *            65535.
-	 * @param serverIpAddress
-	 *            The IP address on which the node will listen. Must be a valid
-	 *            IP v4 address in dotted decimal notation. May not be
-	 *            <code>null</code>.
-	 * @return Reference to the created router node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract RouterNode createRouter(String nodeName, int controlPort,
-			int socksPort, int orPort, int dirPort, String serverIpAddress);
-
-	/**
-	 * Creates a new <code>ServerApplication</code> with automatically
-	 * assigned ports, but does not start listening for incoming requests.
-	 *
-	 * @param serverApplicationName
-	 *            The name for this server application, which is used for
-	 *            logging purposes and as event source. May neither be
-	 *            <code>null</code> or a zero-length string. The name needs to
-	 *            be unique in this network.
-	 * @return Reference to the created server application.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given as server application
-	 *             name.
-	 */
-	public abstract ServerApplication createServer(String serverApplicationName);
-
-	/**
-	 * Creates a new <code>ServerApplication</code>, but does not start
-	 * listening for incoming requests.
-	 *
-	 * @param serverApplicationName
-	 *            The name for this server application, which is used for
-	 *            logging purposes and as event source. May neither be
-	 *            <code>null</code> or a zero-length string. The name needs to
-	 *            be unique in this network.
-	 * @param serverPort
-	 *            The TCP port on which the server will wait for incoming
-	 *            requests. May not be negative or greater than 65535.
-	 * @return Reference to the created server application.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract ServerApplication createServer(
-			String serverApplicationName, int serverPort);
-
-	/**
-	 * Returns a reference on the (single) event manager for this network.
-	 *
-	 * @return Reference on the (single) event manager for this network.
-	 */
-	public abstract EventManager getEventManager();
-
-	/**
-	 * Returns (a copy of) the map containing the names of all directory nodes
-	 * as keys and the corresponding directory nodes as values.
-	 *
-	 * @return Map containing all directory nodes.
-	 */
-	public abstract Map<String, DirectoryNode> getAllDirectoryNodes();
-
-	/**
-	 * Returns (a copy of) the map containing the names of all router nodes
-	 * (only those that are not acting as directory nodes at the same time) as
-	 * keys and the corresponding router nodes as values.
-	 *
-	 * @return Map containing all router nodes.
-	 */
-	public abstract Map<String, RouterNode> getAllRouterNodes();
-
-	/**
-	 * Returns (a copy of) the map containing the names of all proxy nodes (only
-	 * those that are not acting as router or directory nodes at the same time)
-	 * as keys and the corresponding proxy nodes as values.
-	 *
-	 * @return Map containing all proxy nodes.
-	 */
-	public abstract Map<String, ProxyNode> getAllProxyNodes();
-
-	/**
-	 * Returns (a copy of) the map containing the names of all nodes as keys and
-	 * the corresponding proxy nodes as values.
-	 *
-	 * @return Map containing all nodes.
-	 */
-	public abstract Map<String, ProxyNode> getAllNodes();
-
-	/**
-	 * Returns the node with name <code>nodeName</code> or <code>null</code>
-	 * if no such node exists.
-	 *
-	 * @param nodeName
-	 *            The node name to look up.
-	 * @return The node with name <code>nodeName</code>.
-	 */
-	public abstract ProxyNode getNode(String nodeName);
-
-	/**
-	 * <p>
-	 * Sends a HUP signal to all nodes in the network in regular intervals and
-	 * blocks the invoking thread until all nodes have reported to have
-	 * successfully opened a circuit.
-	 * </p>
-	 *
-	 * <p>
-	 * First, the method waits for <code>hupInterval</code> milliseconds for
-	 * the nodes to have successfully opened a circuit. If they do not succeed
-	 * within this time, a HUP signal is sent to all nodes and the method waits
-	 * for another <code>hupInterval</code> milliseconds. In total, the method
-	 * sends at most <code>tries</code> HUP signals before giving up and
-	 * returning with <code>false</code>. Thus, the maximum waiting time is
-	 * <code>(tries + 1)</code> times <code>hupInterval</code>. As soon as
-	 * all nodes have successfully opened circuits, the method returns with
-	 * <code>true</code>. This operation can only be invoked, if all nodes in
-	 * the network are in state <code>NodeState.RUNNING</code>.
-	 * </p>
-	 *
-	 * @param tries
-	 *            The maximum number of HUP signals that are sent to the Tor
-	 *            processes. Negative values are not allowed. A value of zero
-	 *            means to wait only for the given time of
-	 *            <code>hupInterval</code> milliseconds without sending a HUP
-	 *            signal. Typical values depend on the network being a public or
-	 *            private Tor network and range about 3 to 5 tries.
-	 * @param hupInterval
-	 *            The time in milliseconds that the method will wait between
-	 *            sending HUP signals. Negative values are not allowed.
-	 *            Typically, values should not be smaller than 5 seconds to
-	 *            permit Tor to stabilize.
-	 * @throws IllegalStateException
-	 *             Thrown if at least one node is not in state
-	 *             <code>NodeState.RUNNING</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if a negative value is given for either
-	 *             <code>tries</code> or <code>hupInterval</code>.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while sending HUP signals.
-	 * @return <code>true</code> if all nodes have reported to have
-	 *         successfully opened a circuit, <code>false</code> otherwise.
-	 */
-	public abstract boolean hupUntilUp(int tries, long hupInterval)
-			throws PuppeTorException;
-
-	/**
-	 * Sends a HUP signal to all nodes in the network once. This operation can
-	 * only be invoked, if all nodes in the network are in state
-	 * <code>NodeState.RUNNING</code>.
-	 *
-	 * @throws IllegalStateException
-	 *             Thrown if at least one node is not in state
-	 *             <code>NodeState.RUNNING</code>.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while sending HUP signals.
-	 */
-	public abstract void hupAllNodes() throws PuppeTorException;
-
-	/**
-	 * Sends a HUP signal to all directory nodes in the network once. This
-	 * operation can only be invoked, if all directory nodes in the network are
-	 * in state <code>NodeState.RUNNING</code>.
-	 *
-	 * @throws IllegalStateException
-	 *             Thrown if at least one directory node is not in state
-	 *             <code>NodeState.RUNNING</code>.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while sending HUP signals.
-	 */
-	public abstract void hupAllDirectories() throws PuppeTorException;
-
-	/**
-	 * Attempts to shut down all running nodes. The method blocks until all
-	 * shutdown requests have been sent and either returns, or throws the first
-	 * exception that has been observed when shutting down nodes. The method can
-	 * be assumed to return very quickly. If there are no running nodes in this
-	 * network, this operation has no effect.
-	 *
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while shutting down the
-	 *             nodes.
-	 */
-	public abstract void shutdownNodes() throws PuppeTorException;
-
-	/**
-	 * Attempts to start all nodes within a given timeout of
-	 * <code>maximumTimeToWaitInMillis</code> milliseconds. The method returns
-	 * as soon as all nodes have started and opened their control port so that
-	 * we can connect to them. It returns a boolean that states whether the
-	 * operation was either successful or has timed out. This operation can only
-	 * be invoked, if all nodes in the network have written their configuration,
-	 * i.e. are not in state <code>NodeState.CONFIGURING</code> anymore.
-	 *
-	 * @param maximumTimeToWaitInMillis
-	 *            The maximum time to wait in milliseconds. A positive value or
-	 *            zero restricts waiting to this time. Negative values are not
-	 *            allowed. Typical values are in the range of a few seconds.
-	 * @return <code>true</code> if all nodes could be started successfully,
-	 *         <code>false</code> if a timeout has occured.
-	 * @throws IllegalStateException
-	 *             Thrown if at least one node in the network is still in state
-	 *             <code>NodeState.CONFIGURING</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if a negative value is given for
-	 *             <code>maximumTimeToWaitInMillis</code>.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while starting the nodes.
-	 */
-	public abstract boolean startNodes(long maximumTimeToWaitInMillis)
-			throws PuppeTorException;
-
-	/**
-	 * Writes the configurations for all nodes in the network to disk, including
-	 * <code>torrc</code> and <code>approved-routers</code> files. This
-	 * method is assumed to return very quickly. In case of a private network,
-	 * <code>configureAsPrivateNetwork</code> should be invoked in advance to
-	 * this method!
-	 *
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while writing to the nodes'
-	 *             working directories.
-	 */
-	public abstract void writeConfigurations() throws PuppeTorException;
-
-	/**
-	 * Returns the working directory of this network configuration which is in
-	 * <code>test-env/networkName/</code>.
-	 *
-	 * @return Working directory of this network.
-	 */
-	public abstract File getWorkingDirectory();
-
-	/**
-	 * Returns all configuration strings of the template of a node class that
-	 * will be added to future instances of this node class and its subclasses.
-	 * Note that the result only contains those configuration strings that are
-	 * added by this node class to possible superclasses and that parameterized
-	 * configuration strings, e.g. port configurations, are not included.
-	 *
-	 * @param nodeClass
-	 *            The class which will be configured with the returned template
-	 *            configuration; may not be <code>null</code>.
-	 * @return The template configuration for the given node class.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for the parameter.
-	 */
-	public abstract List<String> getTemplateConfiguration(
-			Class<? extends ProxyNode> nodeClass);
-
-	/**
-	 * Adds a configuration string to the template of a node class, so that it
-	 * will be added to future instances of this node class and its subclasses.
-	 *
-	 * @param nodeClass
-	 *            The class of nodes of which future instances will have the
-	 *            given configuration string; may not be <code>null</code>.
-	 * @param templateConfigurationString
-	 *            The configuration string to add; may neither be
-	 *            <code>null</code> nor a zero-length string, and must consist
-	 *            of configuration key and value.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract void addTemplateConfiguration(
-			Class<? extends ProxyNode> nodeClass,
-			String templateConfigurationString);
-
-	/**
-	 * Removes a configuration string from the template of a node class, so that
-	 * it will not be added to future instances of this node class and its
-	 * subclasses.
-	 *
-	 * @param nodeClass
-	 *            The class of nodes of which future instances will have the
-	 *            given configuration string; may not be <code>null</code>.
-	 * @param templateConfigurationKey
-	 *            The configuration key to remove; may neither be
-	 *            <code>null</code> nor a zero-length key.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract void removeTemplateConfiguration(
-			Class<? extends ProxyNode> nodeClass,
-			String templateConfigurationKey);
-
-	/**
-	 * Returns the name of this network.
-	 *
-	 * @return The name of this network.
-	 */
-	public abstract String getNetworkName();
+    /**
+     * <p>
+     * Configures this network as private Tor network by exchanging directory
+     * strings and router fingerprints between the nodes of this network.
+     * Afterwards, the nodes will be able to run a private Tor network,
+     * separated from public directory servers and onion routers.
+     * </p>
+     *
+     * <p>
+     * The configuration is done in two steps:
+     * <ol>
+     * <li>Directory strings of directory nodes are added to the configurations
+     * of all nodes in the other network.</li>
+     * <li>Router fingerprints of all router and directory nodes are added to
+     * the <code>approved-routers</code> files of the directory nodes.</li>
+     * </ol>
+     * </p>
+     *
+     * <p>
+     * This operation may be invoked in any state of the contained nodes.
+     * However, a network that does not have directory nodes of its own but
+     * relies on directory nodes of a merged network <b>should not be started
+     * before being configured as private network!</b> Otherwise it would
+     * connect to the public Tor network before being merged with the other
+     * private Tor network. However, it may also be invoked at a later time,
+     * e.g. to admit new nodes.
+     * </p>
+     *
+     * <p>
+     * This operation does not write any configurations to disk and neither
+     * starts a nodes nor sends HUP signals to running nodes. These operations
+     * are left to the application, so that they have more control over the
+     * network behavior.
+     * </p>
+     *
+     * <p>
+     * Applications need to ensure that there are enough directory nodes (2) and
+     * router nodes (3) in the network to allow normal operation.
+     * </p>
+     *
+     * @throws PuppeTorException
+     *             Thrown if an I/O problem occurs while determining the nodes'
+     *             fingerprints.
+     */
+    public abstract void configureAsPrivateNetwork() throws PuppeTorException;
+
+    /**
+     * XXX document properly -SH
+     */
+    public void configureAsPartOfPrivateNetwork(
+            List<String> authorizedDirectoriesFingerprints);
+
+
+    /**
+     * Creates a new client application, but does not yet perform a request.
+     *
+     * @param clientApplicationName
+     *            The name for this client application, which is used for
+     *            logging purposes and as event source. May neither be
+     *            <code>null</code> or a zero-length string. The name needs to
+     *            be unique in this network.
+     * @param targetAddress
+     *            The target for requests sent by this client application. Can
+     *            be a publicly available URL or an onion address. May neither
+     *            be <code>null</code> or a zero-length string.
+     * @param targetPort
+     *            The TCP port for requests sent by this client application. If
+     *            the target address is an onion address, this port is the
+     *            virtual port that the hidden service has announced. May not be
+     *            negative or greater than 65535.
+     * @param socksPort
+     *            The TCP port on which a local Tor process is waiting for
+     *            incoming SOCKS requests. May not be negative or greater than
+     *            65535.
+     * @return Reference to the created client application.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for either of the
+     *             parameters.
+     */
+    public abstract ClientApplication createClient(
+            String clientApplicationName, String targetAddress, int targetPort,
+            int socksPort);
+
+    /**
+     * Creates a new directory node with automatically assigned ports and adds
+     * it to the network, but does not yet write its configuration to disk or
+     * start the corresponding Tor process.
+     *
+     * @param nodeName
+     *            The name for this node, which is used as name for the working
+     *            directory, for logging purposes, as node nickname, and as
+     *            event source. May neither be <code>null</code> or have zero
+     *            or more than 19 alpha-numeric characters. The node name needs
+     *            to be unique in this network.
+     * @return Reference to the created directory node.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given as node name.
+     */
+    public abstract DirectoryNode createDirectory(String nodeName);
+
+    /**
+     * Creates a new directory node with automatically assigned ports that will
+     * listen on the given IP address and adds it to the network, but does not
+     * yet write its configuration to disk or start the corresponding Tor
+     * process.
+     *
+     * @param nodeName
+     *            The name for this node, which is used as name for the working
+     *            directory, for logging purposes, as node nickname, and as
+     *            event source. May neither be <code>null</code> or have zero
+     *            or more than 19 alpha-numeric characters. The node name needs
+     *            to be unique in this network.
+     * @param serverIpAddress
+     *            The IP address on which the node will listen. Must be a valid
+     *            IP v4 address in dotted decimal notation. May not be
+     *            <code>null</code>.
+     * @return Reference to the created directory node.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given as node name.
+     */
+    public abstract DirectoryNode createDirectory(String nodeName,
+            String serverIpAddress);
+
+    /**
+     * Creates a new directory node and adds it to the network, but does not yet
+     * write its configuration to disk or start the corresponding Tor process.
+     *
+     * @param nodeName
+     *            The name for this node, which is used as name for the working
+     *            directory, for logging purposes, as node nickname, and as
+     *            event source. May neither be <code>null</code> or have zero
+     *            or more than 19 alpha-numeric characters. The node name needs
+     *            to be unique in this network.
+     * @param controlPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for a controller. May not be negative or greater than 65535.
+     * @param socksPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for incoming SOCKS requests. May not be negative or greater
+     *            than 65535.
+     * @param orPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for incoming requests from other onion routers. May not be
+     *            negative or greater than 65535.
+     * @param dirPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for incoming directory requests. May not be negative or
+     *            greater than 65535.
+     * @return Reference to the created directory node.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for either of the
+     *             parameters.
+     */
+    public abstract DirectoryNode createDirectory(String nodeName,
+            int controlPort, int socksPort, int orPort, int dirPort);
+
+    /**
+     * Creates a new directory node that will listen on the given IP address and
+     * adds it to the network, but does not yet write its configuration to disk
+     * or start the corresponding Tor process.
+     *
+     * @param nodeName
+     *            The name for this node, which is used as name for the working
+     *            directory, for logging purposes, as node nickname, and as
+     *            event source. May neither be <code>null</code> or have zero
+     *            or more than 19 alpha-numeric characters. The node name needs
+     *            to be unique in this network.
+     * @param controlPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for a controller. May not be negative or greater than 65535.
+     * @param socksPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for incoming SOCKS requests. May not be negative or greater
+     *            than 65535.
+     * @param orPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for incoming requests from other onion routers. May not be
+     *            negative or greater than 65535.
+     * @param dirPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for incoming directory requests. May not be negative or
+     *            greater than 65535.
+     * @param serverIpAddress
+     *            The IP address on which the node will listen. Must be a valid
+     *            IP v4 address in dotted decimal notation. May not be
+     *            <code>null</code>.
+     * @return Reference to the created directory node.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for either of the
+     *             parameters.
+     */
+    public abstract DirectoryNode createDirectory(String nodeName,
+            int controlPort, int socksPort, int orPort, int dirPort,
+            String serverIpAddress);
+
+    /**
+     * Creates a new <code>ProxyNode</code> with automatically assigned ports
+     * and adds it to the network, but does not yet write its configuration to
+     * disk or start the corresponding Tor process.
+     *
+     * @param nodeName
+     *            The name for this node, which is used as name for the working
+     *            directory, for logging purposes, and as event source. May
+     *            neither be <code>null</code> or have zero or more than 19
+     *            alpha-numeric characters. The node name needs to be unique in
+     *            this network.
+     * @return Reference to the created proxy node.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given as node name.
+     */
+    public abstract ProxyNode createProxy(String nodeName);
+
+    /**
+     * Creates a new <code>ProxyNode</code> and adds it to the network, but
+     * does not yet write its configuration to disk or start the corresponding
+     * Tor process.
+     *
+     * @param nodeName
+     *            The name for this node, which is used as name for the working
+     *            directory, for logging purposes, and as event source. May
+     *            neither be <code>null</code> or have zero or more than 19
+     *            alpha-numeric characters. The node name needs to be unique in
+     *            this network.
+     * @param controlPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for a controller. May not be negative or greater than 65535.
+     * @param socksPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for incoming SOCKS requests. May not be negative or greater
+     *            than 65535.
+     * @return Reference to the created proxy node.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for either of the
+     *             parameters.
+     */
+    public abstract ProxyNode createProxy(String nodeName, int controlPort,
+            int socksPort);
+
+    /**
+     * Creates a new <code>RouterNode</code> with automatically assigned ports
+     * and adds it to the network, but does not yet write its configuration to
+     * disk or start the corresponding Tor process.
+     *
+     * @param nodeName
+     *            The name for this node, which is used as name for the working
+     *            directory, for logging purposes, as node nickname, and as
+     *            event source. May neither be <code>null</code> or have zero
+     *            or more than 19 alpha-numeric characters. The node name needs
+     *            to be unique in this network.
+     * @return Reference to the created router node.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given as node name.
+     */
+    public abstract RouterNode createRouter(String nodeName);
+
+    /**
+     * Creates a new <code>RouterNode</code> and adds it to the network, but
+     * does not yet write its configuration to disk or start the corresponding
+     * Tor process.
+     *
+     * @param nodeName
+     *            The name for this node, which is used as name for the working
+     *            directory, for logging purposes, as node nickname, and as
+     *            event source. May neither be <code>null</code> or have zero
+     *            or more than 19 alpha-numeric characters. The node name needs
+     *            to be unique in this network.
+     * @param controlPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for a controller. May not be negative or greater than 65535.
+     * @param socksPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for incoming SOCKS requests. May not be negative or greater
+     *            than 65535.
+     * @param orPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for incoming requests from other onion routers. May not be
+     *            negative or greater than 65535.
+     * @param dirPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for incoming directory requests which in fact are requests for
+     *            the mirrored directory. May not be negative or greater than
+     *            65535.
+     * @return Reference to the created router node.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for either of the
+     *             parameters.
+     */
+    public abstract RouterNode createRouter(String nodeName, int controlPort,
+            int socksPort, int orPort, int dirPort);
+
+    /**
+     * Creates a new <code>RouterNode</code> with automatically assigned ports
+     * that will listen on the given IP address and adds it to the network, but
+     * does not yet write its configuration to disk or start the corresponding
+     * Tor process.
+     *
+     * @param nodeName
+     *            The name for this node, which is used as name for the working
+     *            directory, for logging purposes, as node nickname, and as
+     *            event source. May neither be <code>null</code> or have zero
+     *            or more than 19 alpha-numeric characters. The node name needs
+     *            to be unique in this network.
+     * @param serverIpAddress
+     *            The IP address on which the node will listen. Must be a valid
+     *            IP v4 address in dotted decimal notation. May not be
+     *            <code>null</code>.
+     * @return Reference to the created router node.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given as node name.
+     */
+    public abstract RouterNode createRouter(String nodeName,
+            String serverIpAddress);
+
+    /**
+     * Creates a new <code>RouterNode</code> that will listen on the given IP
+     * address and adds it to the network, but does not yet write its
+     * configuration to disk or start the corresponding Tor process.
+     *
+     * @param nodeName
+     *            The name for this node, which is used as name for the working
+     *            directory, for logging purposes, as node nickname, and as
+     *            event source. May neither be <code>null</code> or have zero
+     *            or more than 19 alpha-numeric characters. The node name needs
+     *            to be unique in this network.
+     * @param controlPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for a controller. May not be negative or greater than 65535.
+     * @param socksPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for incoming SOCKS requests. May not be negative or greater
+     *            than 65535.
+     * @param orPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for incoming requests from other onion routers. May not be
+     *            negative or greater than 65535.
+     * @param dirPort
+     *            The TCP port on which the corresponding Tor process will wait
+     *            for incoming directory requests which in fact are requests for
+     *            the mirrored directory. May not be negative or greater than
+     *            65535.
+     * @param serverIpAddress
+     *            The IP address on which the node will listen. Must be a valid
+     *            IP v4 address in dotted decimal notation. May not be
+     *            <code>null</code>.
+     * @return Reference to the created router node.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for either of the
+     *             parameters.
+     */
+    public abstract RouterNode createRouter(String nodeName, int controlPort,
+            int socksPort, int orPort, int dirPort, String serverIpAddress);
+
+    /**
+     * Creates a new <code>ServerApplication</code> with automatically
+     * assigned ports, but does not start listening for incoming requests.
+     *
+     * @param serverApplicationName
+     *            The name for this server application, which is used for
+     *            logging purposes and as event source. May neither be
+     *            <code>null</code> or a zero-length string. The name needs to
+     *            be unique in this network.
+     * @return Reference to the created server application.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given as server application
+     *             name.
+     */
+    public abstract ServerApplication createServer(String serverApplicationName);
+
+    /**
+     * Creates a new <code>ServerApplication</code>, but does not start
+     * listening for incoming requests.
+     *
+     * @param serverApplicationName
+     *            The name for this server application, which is used for
+     *            logging purposes and as event source. May neither be
+     *            <code>null</code> or a zero-length string. The name needs to
+     *            be unique in this network.
+     * @param serverPort
+     *            The TCP port on which the server will wait for incoming
+     *            requests. May not be negative or greater than 65535.
+     * @return Reference to the created server application.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for either of the
+     *             parameters.
+     */
+    public abstract ServerApplication createServer(
+            String serverApplicationName, int serverPort);
+
+    /**
+     * Returns a reference on the (single) event manager for this network.
+     *
+     * @return Reference on the (single) event manager for this network.
+     */
+    public abstract EventManager getEventManager();
+
+    /**
+     * Returns (a copy of) the map containing the names of all directory nodes
+     * as keys and the corresponding directory nodes as values.
+     *
+     * @return Map containing all directory nodes.
+     */
+    public abstract Map<String, DirectoryNode> getAllDirectoryNodes();
+
+    /**
+     * Returns (a copy of) the map containing the names of all router nodes
+     * (only those that are not acting as directory nodes at the same time) as
+     * keys and the corresponding router nodes as values.
+     *
+     * @return Map containing all router nodes.
+     */
+    public abstract Map<String, RouterNode> getAllRouterNodes();
+
+    /**
+     * Returns (a copy of) the map containing the names of all proxy nodes (only
+     * those that are not acting as router or directory nodes at the same time)
+     * as keys and the corresponding proxy nodes as values.
+     *
+     * @return Map containing all proxy nodes.
+     */
+    public abstract Map<String, ProxyNode> getAllProxyNodes();
+
+    /**
+     * Returns (a copy of) the map containing the names of all nodes as keys and
+     * the corresponding proxy nodes as values.
+     *
+     * @return Map containing all nodes.
+     */
+    public abstract Map<String, ProxyNode> getAllNodes();
+
+    /**
+     * Returns the node with name <code>nodeName</code> or <code>null</code>
+     * if no such node exists.
+     *
+     * @param nodeName
+     *            The node name to look up.
+     * @return The node with name <code>nodeName</code>.
+     */
+    public abstract ProxyNode getNode(String nodeName);
+
+    /**
+     * <p>
+     * Sends a HUP signal to all nodes in the network in regular intervals and
+     * blocks the invoking thread until all nodes have reported to have
+     * successfully opened a circuit.
+     * </p>
+     *
+     * <p>
+     * First, the method waits for <code>hupInterval</code> milliseconds for
+     * the nodes to have successfully opened a circuit. If they do not succeed
+     * within this time, a HUP signal is sent to all nodes and the method waits
+     * for another <code>hupInterval</code> milliseconds. In total, the method
+     * sends at most <code>tries</code> HUP signals before giving up and
+     * returning with <code>false</code>. Thus, the maximum waiting time is
+     * <code>(tries + 1)</code> times <code>hupInterval</code>. As soon as
+     * all nodes have successfully opened circuits, the method returns with
+     * <code>true</code>. This operation can only be invoked, if all nodes in
+     * the network are in state <code>NodeState.RUNNING</code>.
+     * </p>
+     *
+     * @param tries
+     *            The maximum number of HUP signals that are sent to the Tor
+     *            processes. Negative values are not allowed. A value of zero
+     *            means to wait only for the given time of
+     *            <code>hupInterval</code> milliseconds without sending a HUP
+     *            signal. Typical values depend on the network being a public or
+     *            private Tor network and range about 3 to 5 tries.
+     * @param hupInterval
+     *            The time in milliseconds that the method will wait between
+     *            sending HUP signals. Negative values are not allowed.
+     *            Typically, values should not be smaller than 5 seconds to
+     *            permit Tor to stabilize.
+     * @throws IllegalStateException
+     *             Thrown if at least one node is not in state
+     *             <code>NodeState.RUNNING</code>.
+     * @throws IllegalArgumentException
+     *             Thrown if a negative value is given for either
+     *             <code>tries</code> or <code>hupInterval</code>.
+     * @throws PuppeTorException
+     *             Thrown if an I/O problem occurs while sending HUP signals.
+     * @return <code>true</code> if all nodes have reported to have
+     *         successfully opened a circuit, <code>false</code> otherwise.
+     */
+    public abstract boolean hupUntilUp(int tries, long hupInterval)
+            throws PuppeTorException;
+
+    /**
+     * Sends a HUP signal to all nodes in the network once. This operation can
+     * only be invoked, if all nodes in the network are in state
+     * <code>NodeState.RUNNING</code>.
+     *
+     * @throws IllegalStateException
+     *             Thrown if at least one node is not in state
+     *             <code>NodeState.RUNNING</code>.
+     * @throws PuppeTorException
+     *             Thrown if an I/O problem occurs while sending HUP signals.
+     */
+    public abstract void hupAllNodes() throws PuppeTorException;
+
+    /**
+     * Sends a HUP signal to all directory nodes in the network once. This
+     * operation can only be invoked, if all directory nodes in the network are
+     * in state <code>NodeState.RUNNING</code>.
+     *
+     * @throws IllegalStateException
+     *             Thrown if at least one directory node is not in state
+     *             <code>NodeState.RUNNING</code>.
+     * @throws PuppeTorException
+     *             Thrown if an I/O problem occurs while sending HUP signals.
+     */
+    public abstract void hupAllDirectories() throws PuppeTorException;
+
+    /**
+     * Attempts to shut down all running nodes. The method blocks until all
+     * shutdown requests have been sent and either returns, or throws the first
+     * exception that has been observed when shutting down nodes. The method can
+     * be assumed to return very quickly. If there are no running nodes in this
+     * network, this operation has no effect.
+     *
+     * @throws PuppeTorException
+     *             Thrown if an I/O problem occurs while shutting down the
+     *             nodes.
+     */
+    public abstract void shutdownNodes() throws PuppeTorException;
+
+    /**
+     * Attempts to start all nodes within a given timeout of
+     * <code>maximumTimeToWaitInMillis</code> milliseconds. The method returns
+     * as soon as all nodes have started and opened their control port so that
+     * we can connect to them. It returns a boolean that states whether the
+     * operation was either successful or has timed out. This operation can only
+     * be invoked, if all nodes in the network have written their configuration,
+     * i.e. are not in state <code>NodeState.CONFIGURING</code> anymore.
+     *
+     * @param maximumTimeToWaitInMillis
+     *            The maximum time to wait in milliseconds. A positive value or
+     *            zero restricts waiting to this time. Negative values are not
+     *            allowed. Typical values are in the range of a few seconds.
+     * @return <code>true</code> if all nodes could be started successfully,
+     *         <code>false</code> if a timeout has occured.
+     * @throws IllegalStateException
+     *             Thrown if at least one node in the network is still in state
+     *             <code>NodeState.CONFIGURING</code>.
+     * @throws IllegalArgumentException
+     *             Thrown if a negative value is given for
+     *             <code>maximumTimeToWaitInMillis</code>.
+     * @throws PuppeTorException
+     *             Thrown if an I/O problem occurs while starting the nodes.
+     */
+    public abstract boolean startNodes(long maximumTimeToWaitInMillis)
+            throws PuppeTorException;
+
+    /**
+     * Writes the configurations for all nodes in the network to disk, including
+     * <code>torrc</code> and <code>approved-routers</code> files. This
+     * method is assumed to return very quickly. In case of a private network,
+     * <code>configureAsPrivateNetwork</code> should be invoked in advance to
+     * this method!
+     *
+     * @throws PuppeTorException
+     *             Thrown if an I/O problem occurs while writing to the nodes'
+     *             working directories.
+     */
+    public abstract void writeConfigurations() throws PuppeTorException;
+
+    /**
+     * Returns the working directory of this network configuration which is in
+     * <code>test-env/networkName/</code>.
+     *
+     * @return Working directory of this network.
+     */
+    public abstract File getWorkingDirectory();
+
+    /**
+     * Returns all configuration strings of the template of a node class that
+     * will be added to future instances of this node class and its subclasses.
+     * Note that the result only contains those configuration strings that are
+     * added by this node class to possible superclasses and that parameterized
+     * configuration strings, e.g. port configurations, are not included.
+     *
+     * @param nodeClass
+     *            The class which will be configured with the returned template
+     *            configuration; may not be <code>null</code>.
+     * @return The template configuration for the given node class.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for the parameter.
+     */
+    public abstract List<String> getTemplateConfiguration(
+            Class<? extends ProxyNode> nodeClass);
+
+    /**
+     * Adds a configuration string to the template of a node class, so that it
+     * will be added to future instances of this node class and its subclasses.
+     *
+     * @param nodeClass
+     *            The class of nodes of which future instances will have the
+     *            given configuration string; may not be <code>null</code>.
+     * @param templateConfigurationString
+     *            The configuration string to add; may neither be
+     *            <code>null</code> nor a zero-length string, and must consist
+     *            of configuration key and value.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for either of the
+     *             parameters.
+     */
+    public abstract void addTemplateConfiguration(
+            Class<? extends ProxyNode> nodeClass,
+            String templateConfigurationString);
+
+    /**
+     * Removes a configuration string from the template of a node class, so that
+     * it will not be added to future instances of this node class and its
+     * subclasses.
+     *
+     * @param nodeClass
+     *            The class of nodes of which future instances will have the
+     *            given configuration string; may not be <code>null</code>.
+     * @param templateConfigurationKey
+     *            The configuration key to remove; may neither be
+     *            <code>null</code> nor a zero-length key.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for either of the
+     *             parameters.
+     */
+    public abstract void removeTemplateConfiguration(
+            Class<? extends ProxyNode> nodeClass,
+            String templateConfigurationKey);
+
+    /**
+     * Returns the name of this network.
+     *
+     * @return The name of this network.
+     */
+    public abstract String getNetworkName();
 
 }
diff --git a/src/org/torproject/puppetor/NetworkFactory.java b/src/org/torproject/puppetor/NetworkFactory.java
index 2b93ed3..ac59fa0 100644
--- a/src/org/torproject/puppetor/NetworkFactory.java
+++ b/src/org/torproject/puppetor/NetworkFactory.java
@@ -26,58 +26,58 @@ import org.torproject.puppetor.impl.NetworkImpl;
  */
 public abstract class NetworkFactory {
 
-	final private static ConcurrentMap<String, Network> networks =
-			new ConcurrentHashMap<String, Network>();
+    final private static ConcurrentMap<String, Network> networks =
+            new ConcurrentHashMap<String, Network>();
 
-	/**
-	 * Creates a new network that is required for a test run. The new network is
-	 * initially unpopulated and creates its own working directory at
-	 * test-env/randomTestID/. The network automatically assigns port numbers to
-	 * newly created nodes starting at <code>7000</code>.
-	 *
-	 * @param networkName
-	 *            Name of this network configuration.
-	 * @return A new network instance.
-	 */
-	public static Network createNetwork(final String networkName) {
-		final Network network = new NetworkImpl(networkName);
-		networks.put(networkName, network);
-		return network;
-	}
+    /**
+     * Creates a new network that is required for a test run. The new network is
+     * initially unpopulated and creates its own working directory at
+     * test-env/randomTestID/. The network automatically assigns port numbers to
+     * newly created nodes starting at <code>7000</code>.
+     *
+     * @param networkName
+     *            Name of this network configuration.
+     * @return A new network instance.
+     */
+    public static Network createNetwork(final String networkName) {
+        final Network network = new NetworkImpl(networkName);
+        networks.put(networkName, network);
+        return network;
+    }
 
-	/**
-	 * Creates a new network that is required for a test run. The new network is
-	 * initially unpopulated and creates its own working directory at
-	 * test-env/randomTestID/. The network automatically assigns port numbers to
-	 * newly created nodes starting at <code>startPort</code>.
-	 *
-	 * @param networkName
-	 *            Name of this network configuration.
-	 * @param startPort
-	 *            The initial value for automatically assigned port numbers of
-	 *            nodes created by this <code>Network</code>; must be a value
-	 *            between <code>1024</code> and <code>65535</code>.
-	 *            Applications need to ensure that there are enough ports left
-	 *            to the maximum number port <code>65535</code> for all
-	 *            created nodes.
-	 * @return A new network instance.
-	 */
-	public static Network createNetwork(final String networkName,
-			final int startPort) {
-		final Network network = new NetworkImpl(networkName, startPort);
-		networks.put(networkName, network);
-		return network;
-	}
+    /**
+     * Creates a new network that is required for a test run. The new network is
+     * initially unpopulated and creates its own working directory at
+     * test-env/randomTestID/. The network automatically assigns port numbers to
+     * newly created nodes starting at <code>startPort</code>.
+     *
+     * @param networkName
+     *            Name of this network configuration.
+     * @param startPort
+     *            The initial value for automatically assigned port numbers of
+     *            nodes created by this <code>Network</code>; must be a value
+     *            between <code>1024</code> and <code>65535</code>.
+     *            Applications need to ensure that there are enough ports left
+     *            to the maximum number port <code>65535</code> for all
+     *            created nodes.
+     * @return A new network instance.
+     */
+    public static Network createNetwork(final String networkName,
+            final int startPort) {
+        final Network network = new NetworkImpl(networkName, startPort);
+        networks.put(networkName, network);
+        return network;
+    }
 
-	/**
-	 *
-	 */
-	public static Network getNetworkByName(final String networkName) {
-		final Network network = networks.get(networkName);
-		if (network == null) {
-			throw new IllegalStateException("BUG: The network with name "
-					+ networkName + " must exist and be non-null!");
-		}
-		return network;
-	}
+    /**
+     *
+     */
+    public static Network getNetworkByName(final String networkName) {
+        final Network network = networks.get(networkName);
+        if (network == null) {
+            throw new IllegalStateException("BUG: The network with name "
+                    + networkName + " must exist and be non-null!");
+        }
+        return network;
+    }
 }
diff --git a/src/org/torproject/puppetor/NodeEventType.java b/src/org/torproject/puppetor/NodeEventType.java
index 7b65bd4..600096e 100644
--- a/src/org/torproject/puppetor/NodeEventType.java
+++ b/src/org/torproject/puppetor/NodeEventType.java
@@ -11,50 +11,50 @@ package org.torproject.puppetor;
 @SuppressWarnings("serial")
 public class NodeEventType implements EventType {
 
-	/**
-	 * String identifying the type of the event type.
-	 */
-	String typeString;
-
-	/**
-	 * Creates a new event type with the given type string.
-	 *
-	 * @param typeString
-	 *            String identifying the type of the event type.
-	 */
-	public NodeEventType(final String typeString) {
-		this.typeString = typeString;
-	}
-
-	public String getTypeName() {
-		return typeString;
-	}
-
-	/**
-	 * The node was started and we managed to connect to its control port; this
-	 * event is fired internally and not parsed from a log statement from Tor.
-	 */
-	public static final NodeEventType NODE_STARTED =
-			new NodeEventType("NODE_STARTED");
-
-	/**
-	 * The node has opened its control port; this event is parsed from a log
-	 * statement in connection_create_listener().
-	 */
-	public static final NodeEventType NODE_CONTROL_PORT_OPENED =
-			new NodeEventType("NODE_CONTROL_PORT_OPENED");
-
-	/**
-	 * The node which has successfully opened a circuit; this event is parsed
-	 * from a log statement in circuit_send_next_onion_skin().
-	 */
-	public static final NodeEventType NODE_CIRCUIT_OPENED =
-			new NodeEventType("NODE_CIRCUIT_OPENED");
-
-	/**
-	 * The node was stopped; this event is fired internally and not parsed from
-	 * a log statement from Tor.
-	 */
-	public static final NodeEventType NODE_STOPPED =
-			new NodeEventType("NODE_STOPPED");
-}
\ No newline at end of file
+    /**
+     * String identifying the type of the event type.
+     */
+    String typeString;
+
+    /**
+     * Creates a new event type with the given type string.
+     *
+     * @param typeString
+     *            String identifying the type of the event type.
+     */
+    public NodeEventType(final String typeString) {
+        this.typeString = typeString;
+    }
+
+    public String getTypeName() {
+        return typeString;
+    }
+
+    /**
+     * The node was started and we managed to connect to its control port; this
+     * event is fired internally and not parsed from a log statement from Tor.
+     */
+    public static final NodeEventType NODE_STARTED =
+            new NodeEventType("NODE_STARTED");
+
+    /**
+     * The node has opened its control port; this event is parsed from a log
+     * statement in connection_create_listener().
+     */
+    public static final NodeEventType NODE_CONTROL_PORT_OPENED =
+            new NodeEventType("NODE_CONTROL_PORT_OPENED");
+
+    /**
+     * The node which has successfully opened a circuit; this event is parsed
+     * from a log statement in circuit_send_next_onion_skin().
+     */
+    public static final NodeEventType NODE_CIRCUIT_OPENED =
+            new NodeEventType("NODE_CIRCUIT_OPENED");
+
+    /**
+     * The node was stopped; this event is fired internally and not parsed from
+     * a log statement from Tor.
+     */
+    public static final NodeEventType NODE_STOPPED =
+            new NodeEventType("NODE_STOPPED");
+}
diff --git a/src/org/torproject/puppetor/NodeState.java b/src/org/torproject/puppetor/NodeState.java
index 720b44f..aa9b86a 100644
--- a/src/org/torproject/puppetor/NodeState.java
+++ b/src/org/torproject/puppetor/NodeState.java
@@ -18,27 +18,27 @@ package org.torproject.puppetor;
  */
 public enum NodeState {
 
-	/**
-	 * The configuration of this node has not been written to disk. This is the
-	 * initial state of a <code>ProxyNode</code> or one of its subclasses.
-	 */
-	CONFIGURING,
+    /**
+     * The configuration of this node has not been written to disk. This is the
+     * initial state of a <code>ProxyNode</code> or one of its subclasses.
+     */
+    CONFIGURING,
 
-	/**
-	 * The configuration of this node has been written to disk, but the Tor
-	 * process has not been started, yet. This state could be useful to review
-	 * the configuration that has been written to disk.
-	 */
-	CONFIGURATION_WRITTEN,
+    /**
+     * The configuration of this node has been written to disk, but the Tor
+     * process has not been started, yet. This state could be useful to review
+     * the configuration that has been written to disk.
+     */
+    CONFIGURATION_WRITTEN,
 
-	/**
-	 * The node has been started and is running.
-	 */
-	RUNNING,
+    /**
+     * The node has been started and is running.
+     */
+    RUNNING,
 
-	/**
-	 * The node had been started and shut down. It cannot be started at a later
-	 * time anymore.
-	 */
-	SHUT_DOWN
+    /**
+     * The node had been started and shut down. It cannot be started at a later
+     * time anymore.
+     */
+    SHUT_DOWN
 }
diff --git a/src/org/torproject/puppetor/ProxyNode.java b/src/org/torproject/puppetor/ProxyNode.java
index 78ac209..8aa9530 100644
--- a/src/org/torproject/puppetor/ProxyNode.java
+++ b/src/org/torproject/puppetor/ProxyNode.java
@@ -44,226 +44,226 @@ import java.util.List;
  */
 public interface ProxyNode {
 
-	/**
-	 * Adds the entries for a hidden service to the configuration of this node.
-	 *
-	 * @param serviceName
-	 *            Name of the hidden service that will be used as name for the
-	 *            hidden service directory. May neither be <code>null</code>
-	 *            or a zero-length string.
-	 * @param servicePort
-	 *            The TCP port on which the service will be available for
-	 *            requests. This can, but need not be different from the virtual
-	 *            port that is announced to clients. May not be negative or
-	 *            greater than 65535.
-	 * @param virtualPort
-	 *            The virtual TCP port that this hidden service runs on as it is
-	 *            announced to clients. May not be negative or greater than
-	 *            65535.
-	 * @return <code>HiddenService</code> object containing the configuration
-	 *         of the created hidden service.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract HiddenService addHiddenService(String serviceName,
-			int servicePort, int virtualPort);
+    /**
+     * Adds the entries for a hidden service to the configuration of this node.
+     *
+     * @param serviceName
+     *            Name of the hidden service that will be used as name for the
+     *            hidden service directory. May neither be <code>null</code>
+     *            or a zero-length string.
+     * @param servicePort
+     *            The TCP port on which the service will be available for
+     *            requests. This can, but need not be different from the virtual
+     *            port that is announced to clients. May not be negative or
+     *            greater than 65535.
+     * @param virtualPort
+     *            The virtual TCP port that this hidden service runs on as it is
+     *            announced to clients. May not be negative or greater than
+     *            65535.
+     * @return <code>HiddenService</code> object containing the configuration
+     *         of the created hidden service.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for either of the
+     *             parameters.
+     */
+    public abstract HiddenService addHiddenService(String serviceName,
+            int servicePort, int virtualPort);
 
-	/**
-	 * Adds the entries for a hidden service with virtual port 80 to the
-	 * configuration of this node.
-	 *
-	 * @param serviceName
-	 *            Name of the hidden service that will be used as name for the
-	 *            hidden service directory. May neither be <code>null</code>
-	 *            or a zero-length string.
-	 * @param servicePort
-	 *            The TCP port on which the service will be available for
-	 *            requests. This can, but need not be different from the virtual
-	 *            port that is announced to clients. May not be negative or
-	 *            greater than 65535.
-	 * @return <code>HiddenService</code> object containing the configuration
-	 *         of the created hidden service.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract HiddenService addHiddenService(String serviceName,
-			int servicePort);
+    /**
+     * Adds the entries for a hidden service with virtual port 80 to the
+     * configuration of this node.
+     *
+     * @param serviceName
+     *            Name of the hidden service that will be used as name for the
+     *            hidden service directory. May neither be <code>null</code>
+     *            or a zero-length string.
+     * @param servicePort
+     *            The TCP port on which the service will be available for
+     *            requests. This can, but need not be different from the virtual
+     *            port that is announced to clients. May not be negative or
+     *            greater than 65535.
+     * @return <code>HiddenService</code> object containing the configuration
+     *         of the created hidden service.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for either of the
+     *             parameters.
+     */
+    public abstract HiddenService addHiddenService(String serviceName,
+            int servicePort);
 
-	/**
-	 * Adds the entries for a hidden service with an automatically assigned
-	 * service port and virtual port 80 to the configuration of this node.
-	 *
-	 * service port automatically assigned virtual port 80
-	 *
-	 * @param serviceName
-	 *            Name of the hidden service that will be used as name for the
-	 *            hidden service directory. May neither be <code>null</code>
-	 *            or a zero-length string.
-	 * @return <code>HiddenService</code> object containing the configuration
-	 *         of the created hidden service.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for the parameter.
-	 */
-	public abstract HiddenService addHiddenService(String serviceName);
+    /**
+     * Adds the entries for a hidden service with an automatically assigned
+     * service port and virtual port 80 to the configuration of this node.
+     *
+     * service port automatically assigned virtual port 80
+     *
+     * @param serviceName
+     *            Name of the hidden service that will be used as name for the
+     *            hidden service directory. May neither be <code>null</code>
+     *            or a zero-length string.
+     * @return <code>HiddenService</code> object containing the configuration
+     *         of the created hidden service.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for the parameter.
+     */
+    public abstract HiddenService addHiddenService(String serviceName);
 
-	/**
-	 * Adds the given configuration string, consisting of "<configuration key>
-	 * <configuration value>", to the configuration of this node.
-	 *
-	 * @param configurationString
-	 *            The configuration string to be added.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given configuration string is either
-	 *             <code>null</code>, a zero-length string, or does not
-	 *             consist of configuration key and value.
-	 */
-	public abstract void addConfiguration(String configurationString);
+    /**
+     * Adds the given configuration string, consisting of "<configuration key>
+     * <configuration value>", to the configuration of this node.
+     *
+     * @param configurationString
+     *            The configuration string to be added.
+     * @throws IllegalArgumentException
+     *             Thrown if the given configuration string is either
+     *             <code>null</code>, a zero-length string, or does not
+     *             consist of configuration key and value.
+     */
+    public abstract void addConfiguration(String configurationString);
 
-	/**
-	 * Adds the given configuration strings, each consisting of "<configuration
-	 * key> <configuration value>", to the configuration of this node.
-	 *
-	 * @param configurationStrings
-	 *            A list of the configuration strings to be added.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given list is <code>null</code>, or any of
-	 *             the contained strings is either <code>null</code>, a
-	 *             zero-length string, or does not consist of configuration key
-	 *             and value.
-	 */
-	public abstract void addConfigurations(List<String> configurationStrings);
+    /**
+     * Adds the given configuration strings, each consisting of "<configuration
+     * key> <configuration value>", to the configuration of this node.
+     *
+     * @param configurationStrings
+     *            A list of the configuration strings to be added.
+     * @throws IllegalArgumentException
+     *             Thrown if the given list is <code>null</code>, or any of
+     *             the contained strings is either <code>null</code>, a
+     *             zero-length string, or does not consist of configuration key
+     *             and value.
+     */
+    public abstract void addConfigurations(List<String> configurationStrings);
 
-	/**
-	 * Replaces the first configuration string, consisting of "<configuration
-	 * key> <configuration value>", that contains the same configuration key as
-	 * <code>configurationString</code> by this new configuration string; if
-	 * multiple occurrences of the given configuration key are found, only the
-	 * first occurrence is replaced; if no configuration can be found, the
-	 * configuration string is appended.
-	 *
-	 * @param configurationString
-	 *            The replacing configuration string.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given configuration string is either
-	 *             <code>null</code>, a zero-length string, or does not
-	 *             consist of configuration key and value.
-	 */
-	public abstract void replaceConfiguration(String configurationString);
+    /**
+     * Replaces the first configuration string, consisting of "<configuration
+     * key> <configuration value>", that contains the same configuration key as
+     * <code>configurationString</code> by this new configuration string; if
+     * multiple occurrences of the given configuration key are found, only the
+     * first occurrence is replaced; if no configuration can be found, the
+     * configuration string is appended.
+     *
+     * @param configurationString
+     *            The replacing configuration string.
+     * @throws IllegalArgumentException
+     *             Thrown if the given configuration string is either
+     *             <code>null</code>, a zero-length string, or does not
+     *             consist of configuration key and value.
+     */
+    public abstract void replaceConfiguration(String configurationString);
 
-	/**
-	 * Removes all configuration strings containing the given configuration key
-	 * in "<configuration key> <configuration value>", regardless of their
-	 * configuration value.
-	 *
-	 * @param configurationKey
-	 *            The configuration key to remove.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given configuration key is either
-	 *             <code>null</code> or a zero-length key.
-	 */
-	public abstract void removeConfiguration(String configurationKey);
+    /**
+     * Removes all configuration strings containing the given configuration key
+     * in "<configuration key> <configuration value>", regardless of their
+     * configuration value.
+     *
+     * @param configurationKey
+     *            The configuration key to remove.
+     * @throws IllegalArgumentException
+     *             Thrown if the given configuration key is either
+     *             <code>null</code> or a zero-length key.
+     */
+    public abstract void removeConfiguration(String configurationKey);
 
-	/**
-	 * Returns the name of this node.
-	 *
-	 * @return The name of this node.
-	 */
-	public abstract String getNodeName();
+    /**
+     * Returns the name of this node.
+     *
+     * @return The name of this node.
+     */
+    public abstract String getNodeName();
 
-	/**
-	 * Returns the state of this node.
-	 *
-	 * @return The state of this node.
-	 */
-	public abstract NodeState getNodeState();
+    /**
+     * Returns the state of this node.
+     *
+     * @return The state of this node.
+     */
+    public abstract NodeState getNodeState();
 
-	/**
-	 * Sends a HUP command to the process via its control port to restart it;
-	 * can only be done if the node has already been started, i.e. is in state
-	 * <code>NodeState.RUNNING</code>!
-	 *
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while sending the HUP signal.
-	 * @throws IllegalStateException
-	 *             Thrown if node is not in state <code>NodeState.RUNNING</code>.
-	 */
-	public abstract void hup() throws PuppeTorException;
+    /**
+     * Sends a HUP command to the process via its control port to restart it;
+     * can only be done if the node has already been started, i.e. is in state
+     * <code>NodeState.RUNNING</code>!
+     *
+     * @throws PuppeTorException
+     *             Thrown if an I/O problem occurs while sending the HUP signal.
+     * @throws IllegalStateException
+     *             Thrown if node is not in state <code>NodeState.RUNNING</code>.
+     */
+    public abstract void hup() throws PuppeTorException;
 
-	/**
-	 * Shuts down the Tor process corresponding to this node immediately. This
-	 * is done by sending the <code>SHUTDOWN</code> signal twice, so that
-	 * those nodes extending <code>ProxyNode</code> which have opened their OR
-	 * port shutdown immediately, too.
-	 *
-	 * @throws IllegalStateException
-	 *             Thrown if this node is not in state
-	 *             <code>NodeState.RUNNING</code>.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while sending the
-	 *             <code>SHUTDOWN</code> signal.
-	 */
-	public abstract void shutdown() throws PuppeTorException;
+    /**
+     * Shuts down the Tor process corresponding to this node immediately. This
+     * is done by sending the <code>SHUTDOWN</code> signal twice, so that
+     * those nodes extending <code>ProxyNode</code> which have opened their OR
+     * port shutdown immediately, too.
+     *
+     * @throws IllegalStateException
+     *             Thrown if this node is not in state
+     *             <code>NodeState.RUNNING</code>.
+     * @throws PuppeTorException
+     *             Thrown if an I/O problem occurs while sending the
+     *             <code>SHUTDOWN</code> signal.
+     */
+    public abstract void shutdown() throws PuppeTorException;
 
-	/**
-	 * Starts the Tor process for this node and connects to the control port as
-	 * soon as it is opened. <b>In order for this method to succeed it is
-	 * absolutely necessary, that logging on the console is not changed in the
-	 * configuration of this node to a higher level than NOTICE, because the
-	 * output is parsed to see when the control port is opened.</b>
-	 *
-	 * @param maximumTimeToWaitInMillis
-	 *            Maximum time in milliseconds we will wait for the Tor process
-	 *            to be started and the control port being opened. If this value
-	 *            is negative or zero, we will wait potentially forever.
-	 * @return <code>true</code> if the node could be started successfully,
-	 *         <code>false</code> otherwise.
-	 * @throws IllegalStateException
-	 *             Thrown if node is not in state
-	 *             <code>NodeState.CONFIGURATION_WRITTEN</code>, i.e. if
-	 *             either configuration has not been written or the process has
-	 *             already been started.
-	 * @throws PuppeTorException
-	 *             Thrown if either the process could not be started, or the
-	 *             connection to the control port could not be established.
-	 */
-	public abstract boolean startNode(long maximumTimeToWaitInMillis)
-			throws PuppeTorException;
+    /**
+     * Starts the Tor process for this node and connects to the control port as
+     * soon as it is opened. <b>In order for this method to succeed it is
+     * absolutely necessary, that logging on the console is not changed in the
+     * configuration of this node to a higher level than NOTICE, because the
+     * output is parsed to see when the control port is opened.</b>
+     *
+     * @param maximumTimeToWaitInMillis
+     *            Maximum time in milliseconds we will wait for the Tor process
+     *            to be started and the control port being opened. If this value
+     *            is negative or zero, we will wait potentially forever.
+     * @return <code>true</code> if the node could be started successfully,
+     *         <code>false</code> otherwise.
+     * @throws IllegalStateException
+     *             Thrown if node is not in state
+     *             <code>NodeState.CONFIGURATION_WRITTEN</code>, i.e. if
+     *             either configuration has not been written or the process has
+     *             already been started.
+     * @throws PuppeTorException
+     *             Thrown if either the process could not be started, or the
+     *             connection to the control port could not be established.
+     */
+    public abstract boolean startNode(long maximumTimeToWaitInMillis)
+            throws PuppeTorException;
 
-	/**
-	 * Writes the configuration of this node to the <code>torrc</code> file in
-	 * its working directory and changes the state to
-	 * <code>NodeState.CONFIGURATION_WRITTEN</code>, if it was in state
-	 * <code>NodeState.CONFIGURING</code> before.
-	 *
-	 * @throws PuppeTorException
-	 *             Thrown if the configuration file <code>torrc</code> cannot
-	 *             be written to disk.
-	 */
-	public abstract void writeConfiguration() throws PuppeTorException;
+    /**
+     * Writes the configuration of this node to the <code>torrc</code> file in
+     * its working directory and changes the state to
+     * <code>NodeState.CONFIGURATION_WRITTEN</code>, if it was in state
+     * <code>NodeState.CONFIGURING</code> before.
+     *
+     * @throws PuppeTorException
+     *             Thrown if the configuration file <code>torrc</code> cannot
+     *             be written to disk.
+     */
+    public abstract void writeConfiguration() throws PuppeTorException;
 
-	/**
-	 * Returns the SOCKS port of this node.
-	 *
-	 * @return The SOCKS port of this node.
-	 */
-	public abstract int getSocksPort();
+    /**
+     * Returns the SOCKS port of this node.
+     *
+     * @return The SOCKS port of this node.
+     */
+    public abstract int getSocksPort();
 
-	/**
-	 * Returns the control port of this node.
-	 *
-	 * @return The control port of this node.
-	 */
-	public abstract int getControlPort();
+    /**
+     * Returns the control port of this node.
+     *
+     * @return The control port of this node.
+     */
+    public abstract int getControlPort();
 
-	/**
-	 * Returns (a copy of) the list of strings containing the configuration of
-	 * this node.
-	 *
-	 * @return (A copy of) the list of strings containing the configuration of
-	 *         this node.
-	 */
-	public abstract List<String> getConfiguration();
+    /**
+     * Returns (a copy of) the list of strings containing the configuration of
+     * this node.
+     *
+     * @return (A copy of) the list of strings containing the configuration of
+     *         this node.
+     */
+    public abstract List<String> getConfiguration();
 
 }
diff --git a/src/org/torproject/puppetor/PuppeTorException.java b/src/org/torproject/puppetor/PuppeTorException.java
index ff4ef72..741f177 100644
--- a/src/org/torproject/puppetor/PuppeTorException.java
+++ b/src/org/torproject/puppetor/PuppeTorException.java
@@ -20,46 +20,46 @@ package org.torproject.puppetor;
 @SuppressWarnings("serial")
 public class PuppeTorException extends Exception {
 
-	/**
-	 * Creates a <code>PuppeTorException</code> without detail message or
-	 * cause.
-	 */
-	public PuppeTorException() {
-		super();
-	}
+    /**
+     * Creates a <code>PuppeTorException</code> without detail message or
+     * cause.
+     */
+    public PuppeTorException() {
+        super();
+    }
 
-	/**
-	 * Creates a <code>PuppeTorException</code> with the given detail
-	 * <code>message</code> and <code>cause</code>.
-	 *
-	 * @param message
-	 *            The detail message of this exception.
-	 * @param cause
-	 *            The cause for this exception.
-	 */
-	public PuppeTorException(final String message, final Throwable cause) {
-		super(message, cause);
-	}
+    /**
+     * Creates a <code>PuppeTorException</code> with the given detail
+     * <code>message</code> and <code>cause</code>.
+     *
+     * @param message
+     *            The detail message of this exception.
+     * @param cause
+     *            The cause for this exception.
+     */
+    public PuppeTorException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
 
-	/**
-	 * Creates a <code>PuppeTorException</code> with the given detail
-	 * <code>message</code>, but without a <code>cause</code>.
-	 *
-	 * @param message
-	 *            The detail message of this exception.
-	 */
-	public PuppeTorException(final String message) {
-		super(message);
-	}
+    /**
+     * Creates a <code>PuppeTorException</code> with the given detail
+     * <code>message</code>, but without a <code>cause</code>.
+     *
+     * @param message
+     *            The detail message of this exception.
+     */
+    public PuppeTorException(final String message) {
+        super(message);
+    }
 
-	/**
-	 * Creates a <code>PuppeTorException</code> with the given
-	 * <code>cause</code>, but without a detail message.
-	 *
-	 * @param cause
-	 *            The cause for this exception.
-	 */
-	public PuppeTorException(final Throwable cause) {
-		super(cause);
-	}
+    /**
+     * Creates a <code>PuppeTorException</code> with the given
+     * <code>cause</code>, but without a detail message.
+     *
+     * @param cause
+     *            The cause for this exception.
+     */
+    public PuppeTorException(final Throwable cause) {
+        super(cause);
+    }
 }
diff --git a/src/org/torproject/puppetor/RouterNode.java b/src/org/torproject/puppetor/RouterNode.java
index ea42a87..170409b 100644
--- a/src/org/torproject/puppetor/RouterNode.java
+++ b/src/org/torproject/puppetor/RouterNode.java
@@ -16,40 +16,40 @@ package org.torproject.puppetor;
  */
 public interface RouterNode extends ProxyNode {
 
-	/**
-	 * Returns the dir port of this node.
-	 *
-	 * @return The dir port of this node.
-	 */
-	public abstract int getDirPort();
+    /**
+     * Returns the dir port of this node.
+     *
+     * @return The dir port of this node.
+     */
+    public abstract int getDirPort();
 
-	/**
-	 * Returns the onion port of this node.
-	 *
-	 * @return The onion port of this node.
-	 */
-	public abstract int getOrPort();
+    /**
+     * Returns the onion port of this node.
+     *
+     * @return The onion port of this node.
+     */
+    public abstract int getOrPort();
 
-	/**
-	 * <p>
-	 * Returns the fingerprint string of this node, formatted like
-	 * <code>nickname 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000</code>.
-	 * </p>
-	 *
-	 * <p>
-	 * The fingerprint is determined by a background thread that is started as
-	 * soon as the node is instantiated. If this background thread has not
-	 * finished when this method is invoked, the invoking thread will be blocked
-	 * until the fingerprint is available (or determining it has failed,
-	 * whereupon an exception will be thrown).
-	 * </p>
-	 *
-	 * @return The fingerprint of this node.
-	 * @throws PuppeTorException
-	 *             Thrown if either the temporary <code>torrc.temp</code>
-	 *             configuration file cannot be written, the Tor process cannot
-	 *             be started temporarily, or the fingerprint file cannot be
-	 *             read.
-	 */
-	public abstract String getFingerprint() throws PuppeTorException;
+    /**
+     * <p>
+     * Returns the fingerprint string of this node, formatted like
+     * <code>nickname 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000</code>.
+     * </p>
+     *
+     * <p>
+     * The fingerprint is determined by a background thread that is started as
+     * soon as the node is instantiated. If this background thread has not
+     * finished when this method is invoked, the invoking thread will be blocked
+     * until the fingerprint is available (or determining it has failed,
+     * whereupon an exception will be thrown).
+     * </p>
+     *
+     * @return The fingerprint of this node.
+     * @throws PuppeTorException
+     *             Thrown if either the temporary <code>torrc.temp</code>
+     *             configuration file cannot be written, the Tor process cannot
+     *             be started temporarily, or the fingerprint file cannot be
+     *             read.
+     */
+    public abstract String getFingerprint() throws PuppeTorException;
 }
diff --git a/src/org/torproject/puppetor/ServerApplication.java b/src/org/torproject/puppetor/ServerApplication.java
index 2859d97..16bbcab 100644
--- a/src/org/torproject/puppetor/ServerApplication.java
+++ b/src/org/torproject/puppetor/ServerApplication.java
@@ -15,44 +15,44 @@ package org.torproject.puppetor;
  */
 public interface ServerApplication {
 
-	/**
-	 * Starts listening for incoming <code>HTTP GET</code> requests from
-	 * clients. Any incoming request is answered by an empty
-	 * <code>HTTP OK</code> reply. This method may only be invoked when the
-	 * server is currently not in listening state!
-	 *
-	 * @throws IllegalStateException
-	 *             Thrown if the server is currently not in listening state.
-	 */
-	public abstract void startListening();
+    /**
+     * Starts listening for incoming <code>HTTP GET</code> requests from
+     * clients. Any incoming request is answered by an empty
+     * <code>HTTP OK</code> reply. This method may only be invoked when the
+     * server is currently not in listening state!
+     *
+     * @throws IllegalStateException
+     *             Thrown if the server is currently not in listening state.
+     */
+    public abstract void startListening();
 
-	/**
-	 * Stops listening for requests. This method may only be invoked when the
-	 * server is currently in listening state!
-	 *
-	 * @throws IllegalStateException
-	 *             Thrown if the server is currently in listening state.
-	 */
-	public abstract void stopListening();
+    /**
+     * Stops listening for requests. This method may only be invoked when the
+     * server is currently in listening state!
+     *
+     * @throws IllegalStateException
+     *             Thrown if the server is currently in listening state.
+     */
+    public abstract void stopListening();
 
-	/**
-	 * Returns whether this server is currently in listening state.
-	 *
-	 * @return The listening state of this server.
-	 */
-	public abstract boolean isListening();
+    /**
+     * Returns whether this server is currently in listening state.
+     *
+     * @return The listening state of this server.
+     */
+    public abstract boolean isListening();
 
-	/**
-	 * Returns the name of this server.
-	 *
-	 * @return The name of this server.
-	 */
-	public abstract String getServerApplicationName();
+    /**
+     * Returns the name of this server.
+     *
+     * @return The name of this server.
+     */
+    public abstract String getServerApplicationName();
 
-	/**
-	 * Returns the port on which this server listens.
-	 *
-	 * @return The port on which this server listens.
-	 */
-	public abstract int getServerPort();
+    /**
+     * Returns the port on which this server listens.
+     *
+     * @return The port on which this server listens.
+     */
+    public abstract int getServerPort();
 }
diff --git a/src/org/torproject/puppetor/ServerEventType.java b/src/org/torproject/puppetor/ServerEventType.java
index e98fd95..7f524d7 100644
--- a/src/org/torproject/puppetor/ServerEventType.java
+++ b/src/org/torproject/puppetor/ServerEventType.java
@@ -12,30 +12,30 @@ package org.torproject.puppetor;
 @SuppressWarnings("serial")
 public class ServerEventType implements EventType {
 
-	/**
-	 * String identifying the type of the event type.
-	 */
-	String typeString;
+    /**
+     * String identifying the type of the event type.
+     */
+    String typeString;
 
-	/**
-	 * Creates a new event type with the given type string.
-	 *
-	 * @param typeString
-	 *            String identifying the type of the event type.
-	 */
-	public ServerEventType(final String typeString) {
-		this.typeString = typeString;
-	}
+    /**
+     * Creates a new event type with the given type string.
+     *
+     * @param typeString
+     *            String identifying the type of the event type.
+     */
+    public ServerEventType(final String typeString) {
+        this.typeString = typeString;
+    }
 
-	public String getTypeName() {
-		return typeString;
-	}
+    public String getTypeName() {
+        return typeString;
+    }
 
-	/**
-	 * The server application has received a request and sent a reply to it;
-	 * this event is fired internally and not parsed from a log statement from
-	 * Tor.
-	 */
-	public static final ServerEventType SERVER_RECEIVING_REQUEST_SENDING_REPLY =
-			new ServerEventType("SERVER_RECEIVING_REQUEST_SENDING_REPLY");
-}
\ No newline at end of file
+    /**
+     * The server application has received a request and sent a reply to it;
+     * this event is fired internally and not parsed from a log statement from
+     * Tor.
+     */
+    public static final ServerEventType SERVER_RECEIVING_REQUEST_SENDING_REPLY =
+            new ServerEventType("SERVER_RECEIVING_REQUEST_SENDING_REPLY");
+}
diff --git a/src/org/torproject/puppetor/examples/AccessingPublicWebServerOverTor.java b/src/org/torproject/puppetor/examples/AccessingPublicWebServerOverTor.java
index 0d0d633..c9928e0 100644
--- a/src/org/torproject/puppetor/examples/AccessingPublicWebServerOverTor.java
+++ b/src/org/torproject/puppetor/examples/AccessingPublicWebServerOverTor.java
@@ -23,97 +23,97 @@ import org.torproject.puppetor.PuppeTorException;
  */
 public class AccessingPublicWebServerOverTor {
 
-	/**
-	 * Sets up and runs the test.
-	 *
-	 * @param args
-	 *            Command-line arguments (ignored).
-	 * @throws PuppeTorException
-	 *             Thrown if there is a problem with the JVM-external Tor
-	 *             processes that we cannot handle.
-	 */
-	public static void main(final String[] args) throws PuppeTorException {
-
-		// though we only need a single proxy, we always need to create a
-		// network to initialize a test case.
-		final Network network = NetworkFactory.createNetwork("example1");
-
-		// create a single proxy node with name "proxy"
-		final ProxyNode proxy = network.createProxy("proxy");
-
-		// write configuration of proxy node
-		network.writeConfigurations();
-
-		// start proxy node and wait until it has opened a circuit with a
-		// timeout of 5 seconds
-		if (!network.startNodes(5000)) {
-
-			// failed to start the proxy
-			System.out.println("Failed to start the node!");
-			return;
-		}
-		System.out.println("Successfully started the node!");
-
-		// hup until proxy has built circuits (5 retries, 10 seconds timeout
-		// each)
-		if (!network.hupUntilUp(5, 10000)) {
-
-			// failed to build circuits
-			System.out.println("Failed to build circuits!");
-			System.exit(0);
-		}
-		System.out.println("Successfully built circuits!");
-
-		// create client application
-		final ClientApplication client =
-				network.createClient("client", "www.google.com", 80, proxy
-						.getSocksPort());
-
-		// create event listener to listen for client application events
-		final EventListener clientEventListener = new EventListener() {
-
-			// remember time when request was sent
-			private long before;
-
-			public void handleEvent(Event event) {
-				if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
-					before = System.currentTimeMillis();
-				} else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
-					System.out.println("Request took "
-							+ (System.currentTimeMillis() - before)
-							+ " milliseconds");
-				}
-			}
-		};
-
-		// obtain reference to event manager to be able to respond to events
-		final EventManager manager = network.getEventManager();
-
-		// register event handler for client application events
-		manager.addEventListener(client.getClientApplicationName(),
-				clientEventListener);
-
-		// perform at most three request with a timeout of 20 seconds each
-		client.startRequests(3, 20000, true);
-
-		// block this thread as long as client requests are running
-		manager.waitForAnyOccurence(client.getClientApplicationName(),
-				ClientEventType.CLIENT_REQUESTS_PERFORMED);
-
-		// wait a second before shutting down the proxy
-		try {
-			Thread.sleep(1000);
-		} catch (final InterruptedException e) {}
-
-		// shut down proxy
-		network.shutdownNodes();
-
-		// wait another second before exiting the application
-		try {
-			Thread.sleep(1000);
-		} catch (final InterruptedException e) {}
-
-		// Shut down the JVM
-		System.out.println("Goodbye.");
-	}
+    /**
+     * Sets up and runs the test.
+     *
+     * @param args
+     *            Command-line arguments (ignored).
+     * @throws PuppeTorException
+     *             Thrown if there is a problem with the JVM-external Tor
+     *             processes that we cannot handle.
+     */
+    public static void main(final String[] args) throws PuppeTorException {
+
+        // though we only need a single proxy, we always need to create a
+        // network to initialize a test case.
+        final Network network = NetworkFactory.createNetwork("example1");
+
+        // create a single proxy node with name "proxy"
+        final ProxyNode proxy = network.createProxy("proxy");
+
+        // write configuration of proxy node
+        network.writeConfigurations();
+
+        // start proxy node and wait until it has opened a circuit with a
+        // timeout of 5 seconds
+        if (!network.startNodes(5000)) {
+
+            // failed to start the proxy
+            System.out.println("Failed to start the node!");
+            return;
+        }
+        System.out.println("Successfully started the node!");
+
+        // hup until proxy has built circuits (5 retries, 10 seconds timeout
+        // each)
+        if (!network.hupUntilUp(5, 10000)) {
+
+            // failed to build circuits
+            System.out.println("Failed to build circuits!");
+            System.exit(0);
+        }
+        System.out.println("Successfully built circuits!");
+
+        // create client application
+        final ClientApplication client =
+                network.createClient("client", "www.google.com", 80, proxy
+                        .getSocksPort());
+
+        // create event listener to listen for client application events
+        final EventListener clientEventListener = new EventListener() {
+
+            // remember time when request was sent
+            private long before;
+
+            public void handleEvent(Event event) {
+                if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
+                    before = System.currentTimeMillis();
+                } else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
+                    System.out.println("Request took "
+                            + (System.currentTimeMillis() - before)
+                            + " milliseconds");
+                }
+            }
+        };
+
+        // obtain reference to event manager to be able to respond to events
+        final EventManager manager = network.getEventManager();
+
+        // register event handler for client application events
+        manager.addEventListener(client.getClientApplicationName(),
+                clientEventListener);
+
+        // perform at most three request with a timeout of 20 seconds each
+        client.startRequests(3, 20000, true);
+
+        // block this thread as long as client requests are running
+        manager.waitForAnyOccurence(client.getClientApplicationName(),
+                ClientEventType.CLIENT_REQUESTS_PERFORMED);
+
+        // wait a second before shutting down the proxy
+        try {
+            Thread.sleep(1000);
+        } catch (final InterruptedException e) {}
+
+        // shut down proxy
+        network.shutdownNodes();
+
+        // wait another second before exiting the application
+        try {
+            Thread.sleep(1000);
+        } catch (final InterruptedException e) {}
+
+        // Shut down the JVM
+        System.out.println("Goodbye.");
+    }
 }
diff --git a/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java b/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
index d2d7016..e123ee7 100644
--- a/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
+++ b/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
@@ -26,107 +26,107 @@ import org.torproject.puppetor.ServerApplication;
  */
 public class AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork {
 
-	/**
-	 * Sets up and runs the test.
-	 *
-	 * @param args
-	 *            Command-line arguments (ignored).
-	 * @throws PuppeTorException
-	 *             Thrown if there is a problem with the JVM-external Tor
-	 *             processes that we cannot handle.
-	 */
-	public static void main(final String[] args) throws PuppeTorException {
-
-		// create a network to initialize the test case
-		final Network network = NetworkFactory.createNetwork("example4");
-
-		// create three router nodes
-		final RouterNode router1 = network.createRouter("router1");
-		network.createRouter("router2");
-		final RouterNode router3 = network.createRouter("router3");
-
-		// create only one directory node
-		network.createDirectory("dir1");
-
-		// add hidden service
-		final HiddenService hidServ1 = router1.addHiddenService("hidServ");
-
-		// configure nodes of this network to be part of a private network
-		network.configureAsPrivateNetwork();
-
-		// write node configurations
-		network.writeConfigurations();
-
-		// start nodes and wait until they have opened a circuit with a timeout
-		// of 5 seconds
-		if (!network.startNodes(5000)) {
-
-			// failed to start the nodes
-			System.out.println("Failed to start nodes!");
-			System.exit(0);
-		}
-		System.out.println("Successfully started nodes!");
-
-		// hup until nodes have built circuits (60 retries, 10 seconds timeout
-		// each)
-		if (!network.hupUntilUp(60, 10000)) {
-
-			// failed to build circuits
-			System.out.println("Failed to build circuits!");
-			System.exit(0);
-		}
-		System.out.println("Successfully built circuits!");
-
-		// obtain reference to event manager to be able to respond to events
-		final EventManager manager = network.getEventManager();
-
-		// wait for 1 hour that the proxy has published its first RSD
-		if (!manager.waitForAnyOccurence(router1.getNodeName(),
-				HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
-				1L * 60L * 60L * 1000L)) {
-			// failed to publish an RSD
-			System.out.println("Failed to publish an RSD!");
-			System.exit(0);
-		}
-		System.out.println("Successfully published an RSD!");
-
-		// create server application
-		final ServerApplication server =
-				network.createServer("server", hidServ1.getServicePort());
-
-		// create client application
-		final ClientApplication client =
-				network.createClient("client",
-						hidServ1.determineOnionAddress(), hidServ1
-								.getVirtualPort(), router3.getSocksPort());
-
-		// register event listener
-		final EventListener clientAndServerEventListener = new EventListener() {
-			public void handleEvent(Event event) {
-				System.out.println("Handling event: " + event.getMessage());
-			}
-		};
-		manager.addEventListener(client.getClientApplicationName(),
-				clientAndServerEventListener);
-		manager.addEventListener(server.getServerApplicationName(),
-				clientAndServerEventListener);
-
-		// start server
-		server.startListening();
-		System.out.println("Started server");
-
-		// perform at most five request with a timeout of 45 seconds each
-		client.startRequests(5, 45000, true);
-
-		// wait for request to be performed
-		manager.waitForAnyOccurence(client.getClientApplicationName(),
-				ClientEventType.CLIENT_REQUESTS_PERFORMED);
-
-		// shut down nodes
-		network.shutdownNodes();
-
-		// Shut down the JVM
-		System.out.println("Goodbye.");
-		System.exit(0);
-	}
+    /**
+     * Sets up and runs the test.
+     *
+     * @param args
+     *            Command-line arguments (ignored).
+     * @throws PuppeTorException
+     *             Thrown if there is a problem with the JVM-external Tor
+     *             processes that we cannot handle.
+     */
+    public static void main(final String[] args) throws PuppeTorException {
+
+        // create a network to initialize the test case
+        final Network network = NetworkFactory.createNetwork("example4");
+
+        // create three router nodes
+        final RouterNode router1 = network.createRouter("router1");
+        network.createRouter("router2");
+        final RouterNode router3 = network.createRouter("router3");
+
+        // create only one directory node
+        network.createDirectory("dir1");
+
+        // add hidden service
+        final HiddenService hidServ1 = router1.addHiddenService("hidServ");
+
+        // configure nodes of this network to be part of a private network
+        network.configureAsPrivateNetwork();
+
+        // write node configurations
+        network.writeConfigurations();
+
+        // start nodes and wait until they have opened a circuit with a timeout
+        // of 5 seconds
+        if (!network.startNodes(5000)) {
+
+            // failed to start the nodes
+            System.out.println("Failed to start nodes!");
+            System.exit(0);
+        }
+        System.out.println("Successfully started nodes!");
+
+        // hup until nodes have built circuits (60 retries, 10 seconds timeout
+        // each)
+        if (!network.hupUntilUp(60, 10000)) {
+
+            // failed to build circuits
+            System.out.println("Failed to build circuits!");
+            System.exit(0);
+        }
+        System.out.println("Successfully built circuits!");
+
+        // obtain reference to event manager to be able to respond to events
+        final EventManager manager = network.getEventManager();
+
+        // wait for 1 hour that the proxy has published its first RSD
+        if (!manager.waitForAnyOccurence(router1.getNodeName(),
+                HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
+                1L * 60L * 60L * 1000L)) {
+            // failed to publish an RSD
+            System.out.println("Failed to publish an RSD!");
+            System.exit(0);
+        }
+        System.out.println("Successfully published an RSD!");
+
+        // create server application
+        final ServerApplication server =
+                network.createServer("server", hidServ1.getServicePort());
+
+        // create client application
+        final ClientApplication client =
+                network.createClient("client",
+                        hidServ1.determineOnionAddress(), hidServ1
+                                .getVirtualPort(), router3.getSocksPort());
+
+        // register event listener
+        final EventListener clientAndServerEventListener = new EventListener() {
+            public void handleEvent(Event event) {
+                System.out.println("Handling event: " + event.getMessage());
+            }
+        };
+        manager.addEventListener(client.getClientApplicationName(),
+                clientAndServerEventListener);
+        manager.addEventListener(server.getServerApplicationName(),
+                clientAndServerEventListener);
+
+        // start server
+        server.startListening();
+        System.out.println("Started server");
+
+        // perform at most five request with a timeout of 45 seconds each
+        client.startRequests(5, 45000, true);
+
+        // wait for request to be performed
+        manager.waitForAnyOccurence(client.getClientApplicationName(),
+                ClientEventType.CLIENT_REQUESTS_PERFORMED);
+
+        // shut down nodes
+        network.shutdownNodes();
+
+        // Shut down the JVM
+        System.out.println("Goodbye.");
+        System.exit(0);
+    }
 }
diff --git a/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java b/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
index 87675c3..4e65f68 100644
--- a/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
+++ b/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
@@ -27,123 +27,123 @@ import org.torproject.puppetor.ServerEventType;
  */
 public class AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork {
 
-	/**
-	 * Sets up and runs the test.
-	 *
-	 * @param args
-	 *            Command-line arguments (ignored).
-	 * @throws PuppeTorException
-	 *             Thrown if there is a problem with the JVM-external Tor
-	 *             processes that we cannot handle.
-	 */
-	public static void main(final String[] args) throws PuppeTorException {
-
-		// create a network to initialize the test case
-		final Network network = NetworkFactory.createNetwork("example3");
-
-		// create two proxy nodes
-		final ProxyNode proxy1 = network.createProxy("proxy1");
-		final ProxyNode proxy2 = network.createProxy("proxy2");
-
-		// add hidden service to the configuration of proxy1
-		final HiddenService hidServ1 = proxy1.addHiddenService("hidServ");
-
-		// write configuration of proxy nodes
-		network.writeConfigurations();
-
-		// start nodes and wait until they have opened a circuit with a timeout
-		// of 5 seconds
-		if (!network.startNodes(5000)) {
-
-			// failed to start the proxy
-			System.out.println("Failed to start nodes!");
-			System.exit(0);
-		}
-		System.out.println("Successfully started nodes!");
-
-		// hup until nodes have built circuits (5 retries, 10 seconds timeout
-		// each)
-		if (!network.hupUntilUp(5, 10000)) {
-
-			// failed to build circuits
-			System.out.println("Failed to build circuits!");
-			System.exit(0);
-		}
-		System.out.println("Successfully built circuits!");
-
-		// obtain reference to event manager to be able to respond to events
-		final EventManager manager = network.getEventManager();
-
-		// wait for 3 minutes that the proxy has published its first RSD
-		if (!manager.waitForAnyOccurence(proxy1.getNodeName(),
-				HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
-				3L * 60L * 1000L)) {
-
-			// failed to publish an RSD
-			System.out.println("Failed to publish an RSD!");
-			System.exit(0);
-		}
-		System.out.println("Successfully published an RSD!");
-
-		// create server application
-		final ServerApplication server =
-				network.createServer("server", hidServ1.getServicePort());
-
-		// create client application
-		final ClientApplication client =
-				network.createClient("client",
-						hidServ1.determineOnionAddress(), hidServ1
-								.getVirtualPort(), proxy2.getSocksPort());
-
-		// create event listener to listen for client and server application
-		// events
-		final EventListener clientAndServerEventListener = new EventListener() {
-
-			private long requestReceivedAtServer;
-
-			// remember time when request was sent and when it was received
-			private long requestSentFromClient;
-
-			public void handleEvent(Event event) {
-				if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
-					requestSentFromClient = event.getOccurrenceTime();
-				} else if (event.getType() == ServerEventType.SERVER_RECEIVING_REQUEST_SENDING_REPLY) {
-					requestReceivedAtServer = event.getOccurrenceTime();
-					System.out.println("Request took "
-							+ (requestReceivedAtServer - requestSentFromClient)
-							+ " milliseconds from client to server!");
-				} else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
-					System.out
-							.println("Request took "
-									+ (event.getOccurrenceTime() - requestSentFromClient)
-									+ " milliseconds for the round-trip and "
-									+ (event.getOccurrenceTime() - requestReceivedAtServer)
-									+ " milliseconds from server to client!");
-				}
-			}
-		};
-
-		// register event handler for client and server application events
-		manager.addEventListener(client.getClientApplicationName(),
-				clientAndServerEventListener);
-		manager.addEventListener(server.getServerApplicationName(),
-				clientAndServerEventListener);
-
-		// start server
-		server.startListening();
-
-		// perform at most five request with a timeout of 45 seconds each
-		client.startRequests(5, 45000, true);
-
-		// block this thread as long as client requests are running
-		manager.waitForAnyOccurence(client.getClientApplicationName(),
-				ClientEventType.CLIENT_REQUESTS_PERFORMED);
-
-		// shut down proxy
-		network.shutdownNodes();
-
-		// Shut down the JVM
-		System.out.println("Goodbye.");
-		System.exit(0);
-	}
+    /**
+     * Sets up and runs the test.
+     *
+     * @param args
+     *            Command-line arguments (ignored).
+     * @throws PuppeTorException
+     *             Thrown if there is a problem with the JVM-external Tor
+     *             processes that we cannot handle.
+     */
+    public static void main(final String[] args) throws PuppeTorException {
+
+        // create a network to initialize the test case
+        final Network network = NetworkFactory.createNetwork("example3");
+
+        // create two proxy nodes
+        final ProxyNode proxy1 = network.createProxy("proxy1");
+        final ProxyNode proxy2 = network.createProxy("proxy2");
+
+        // add hidden service to the configuration of proxy1
+        final HiddenService hidServ1 = proxy1.addHiddenService("hidServ");
+
+        // write configuration of proxy nodes
+        network.writeConfigurations();
+
+        // start nodes and wait until they have opened a circuit with a timeout
+        // of 5 seconds
+        if (!network.startNodes(5000)) {
+
+            // failed to start the proxy
+            System.out.println("Failed to start nodes!");
+            System.exit(0);
+        }
+        System.out.println("Successfully started nodes!");
+
+        // hup until nodes have built circuits (5 retries, 10 seconds timeout
+        // each)
+        if (!network.hupUntilUp(5, 10000)) {
+
+            // failed to build circuits
+            System.out.println("Failed to build circuits!");
+            System.exit(0);
+        }
+        System.out.println("Successfully built circuits!");
+
+        // obtain reference to event manager to be able to respond to events
+        final EventManager manager = network.getEventManager();
+
+        // wait for 3 minutes that the proxy has published its first RSD
+        if (!manager.waitForAnyOccurence(proxy1.getNodeName(),
+                HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
+                3L * 60L * 1000L)) {
+
+            // failed to publish an RSD
+            System.out.println("Failed to publish an RSD!");
+            System.exit(0);
+        }
+        System.out.println("Successfully published an RSD!");
+
+        // create server application
+        final ServerApplication server =
+                network.createServer("server", hidServ1.getServicePort());
+
+        // create client application
+        final ClientApplication client =
+                network.createClient("client",
+                        hidServ1.determineOnionAddress(), hidServ1
+                                .getVirtualPort(), proxy2.getSocksPort());
+
+        // create event listener to listen for client and server application
+        // events
+        final EventListener clientAndServerEventListener = new EventListener() {
+
+            private long requestReceivedAtServer;
+
+            // remember time when request was sent and when it was received
+            private long requestSentFromClient;
+
+            public void handleEvent(Event event) {
+                if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
+                    requestSentFromClient = event.getOccurrenceTime();
+                } else if (event.getType() == ServerEventType.SERVER_RECEIVING_REQUEST_SENDING_REPLY) {
+                    requestReceivedAtServer = event.getOccurrenceTime();
+                    System.out.println("Request took "
+                            + (requestReceivedAtServer - requestSentFromClient)
+                            + " milliseconds from client to server!");
+                } else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
+                    System.out
+                            .println("Request took "
+                                    + (event.getOccurrenceTime() - requestSentFromClient)
+                                    + " milliseconds for the round-trip and "
+                                    + (event.getOccurrenceTime() - requestReceivedAtServer)
+                                    + " milliseconds from server to client!");
+                }
+            }
+        };
+
+        // register event handler for client and server application events
+        manager.addEventListener(client.getClientApplicationName(),
+                clientAndServerEventListener);
+        manager.addEventListener(server.getServerApplicationName(),
+                clientAndServerEventListener);
+
+        // start server
+        server.startListening();
+
+        // perform at most five request with a timeout of 45 seconds each
+        client.startRequests(5, 45000, true);
+
+        // block this thread as long as client requests are running
+        manager.waitForAnyOccurence(client.getClientApplicationName(),
+                ClientEventType.CLIENT_REQUESTS_PERFORMED);
+
+        // shut down proxy
+        network.shutdownNodes();
+
+        // Shut down the JVM
+        System.out.println("Goodbye.");
+        System.exit(0);
+    }
 }
diff --git a/src/org/torproject/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java b/src/org/torproject/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
index 2f9773a..8040f25 100644
--- a/src/org/torproject/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
+++ b/src/org/torproject/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
@@ -23,88 +23,88 @@ import org.torproject.puppetor.PuppeTorException;
  */
 public class AdvertisingHiddenServiceToPublicTorNetwork {
 
-	/**
-	 * Sets up and runs the test.
-	 *
-	 * @param args
-	 *            Command-line arguments (ignored).
-	 * @throws PuppeTorException
-	 *             Thrown if there is a problem with the JVM-external Tor
-	 *             processes that we cannot handle.
-	 */
-	public static void main(final String[] args) throws PuppeTorException {
-		// create a network to initialize the test case
-		final Network network = NetworkFactory.createNetwork("example2");
-
-		// create a single proxy node
-		final ProxyNode proxy = network.createProxy("proxy");
-
-		// add hidden service to the configuration
-		proxy.addHiddenService("hidServ");
-
-		// write configuration of proxy node
-		network.writeConfigurations();
-
-		// create event listener to listen for events from our proxy
-		final EventListener proxyEventListener = new EventListener() {
-
-			// remember time when request was sent
-			private long circuitOpened = -1;
-
-			public void handleEvent(Event event) {
-				if (event.getType() == NodeEventType.NODE_CIRCUIT_OPENED) {
-					if (circuitOpened == -1) {
-						circuitOpened = System.currentTimeMillis();
-					}
-				} else if (event.getType() == HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED) {
-					System.out.println("RSD published "
-							+ (System.currentTimeMillis() - circuitOpened)
-							+ " milliseconds after first circuit was opened");
-				}
-			}
-		};
-
-		// obtain reference to event manager to be able to respond to events
-		final EventManager manager = network.getEventManager();
-
-		// register event handler for proxy events
-		manager.addEventListener(proxy.getNodeName(), proxyEventListener);
-
-		// start proxy node and wait until it has opened a circuit with a
-		// timeout of 5 seconds
-		if (!network.startNodes(5000)) {
-
-			// failed to start the proxy
-			System.out.println("Failed to start the node!");
-			System.exit(0);
-		}
-		System.out.println("Successfully started the node!");
-
-		// hup until proxy has built circuits (5 retries, 10 seconds timeout
-		// each)
-		if (!network.hupUntilUp(5, 10000)) {
-
-			// failed to build circuits
-			System.out.println("Failed to build circuits!");
-			System.exit(0);
-		}
-		System.out.println("Successfully built circuits!");
-
-		// let it run for 2 minutes and observe when RSDs are published...
-		System.out
-				.println("Waiting for 2 minutes and observing RSD publications...");
-
-		try {
-			Thread.sleep(2L * 60L * 1000L);
-		} catch (final InterruptedException e) {
-			// do nothing
-		}
-
-		// shut down proxy
-		network.shutdownNodes();
-
-		// Shut down the JVM
-		System.out.println("Goodbye.");
-		System.exit(0);
-	}
+    /**
+     * Sets up and runs the test.
+     *
+     * @param args
+     *            Command-line arguments (ignored).
+     * @throws PuppeTorException
+     *             Thrown if there is a problem with the JVM-external Tor
+     *             processes that we cannot handle.
+     */
+    public static void main(final String[] args) throws PuppeTorException {
+        // create a network to initialize the test case
+        final Network network = NetworkFactory.createNetwork("example2");
+
+        // create a single proxy node
+        final ProxyNode proxy = network.createProxy("proxy");
+
+        // add hidden service to the configuration
+        proxy.addHiddenService("hidServ");
+
+        // write configuration of proxy node
+        network.writeConfigurations();
+
+        // create event listener to listen for events from our proxy
+        final EventListener proxyEventListener = new EventListener() {
+
+            // remember time when request was sent
+            private long circuitOpened = -1;
+
+            public void handleEvent(Event event) {
+                if (event.getType() == NodeEventType.NODE_CIRCUIT_OPENED) {
+                    if (circuitOpened == -1) {
+                        circuitOpened = System.currentTimeMillis();
+                    }
+                } else if (event.getType() == HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED) {
+                    System.out.println("RSD published "
+                            + (System.currentTimeMillis() - circuitOpened)
+                            + " milliseconds after first circuit was opened");
+                }
+            }
+        };
+
+        // obtain reference to event manager to be able to respond to events
+        final EventManager manager = network.getEventManager();
+
+        // register event handler for proxy events
+        manager.addEventListener(proxy.getNodeName(), proxyEventListener);
+
+        // start proxy node and wait until it has opened a circuit with a
+        // timeout of 5 seconds
+        if (!network.startNodes(5000)) {
+
+            // failed to start the proxy
+            System.out.println("Failed to start the node!");
+            System.exit(0);
+        }
+        System.out.println("Successfully started the node!");
+
+        // hup until proxy has built circuits (5 retries, 10 seconds timeout
+        // each)
+        if (!network.hupUntilUp(5, 10000)) {
+
+            // failed to build circuits
+            System.out.println("Failed to build circuits!");
+            System.exit(0);
+        }
+        System.out.println("Successfully built circuits!");
+
+        // let it run for 2 minutes and observe when RSDs are published...
+        System.out
+                .println("Waiting for 2 minutes and observing RSD publications...");
+
+        try {
+            Thread.sleep(2L * 60L * 1000L);
+        } catch (final InterruptedException e) {
+            // do nothing
+        }
+
+        // shut down proxy
+        network.shutdownNodes();
+
+        // Shut down the JVM
+        System.out.println("Goodbye.");
+        System.exit(0);
+    }
 }
diff --git a/src/org/torproject/puppetor/impl/ClientApplicationImpl.java b/src/org/torproject/puppetor/impl/ClientApplicationImpl.java
index ef4cbb4..bbbcf8a 100644
--- a/src/org/torproject/puppetor/impl/ClientApplicationImpl.java
+++ b/src/org/torproject/puppetor/impl/ClientApplicationImpl.java
@@ -28,431 +28,431 @@ import org.torproject.puppetor.ClientEventType;
  */
 public class ClientApplicationImpl implements ClientApplication {
 
-	/**
-	 * Internal thread class that is used to perform requests.
-	 */
-	private class RequestThread extends Thread {
-
-		/**
-		 * Flag to remember whether requests are performed at the moment (<code>true</code>),
-		 * or have been stopped (<code>false</code>).
-		 */
-		private boolean connected;
-
-		/**
-		 * Number of retries to be performed.
-		 */
-		private final int retries;
-
-		/**
-		 * Flag that determines whether requests shall be stopped after the
-		 * first successful reply (<code>true</code>), or not (<code>false</code>).
-		 */
-		private final boolean stopOnSuccess;
-
-		/**
-		 * Timeout in milliseconds for each retry.
-		 */
-		private final long timeoutForEachRetry;
-
-		/**
-		 * Creates a new thread, but does not start performing requests, yet.
-		 *
-		 * @param retries
-		 *            Number of retries to be performed.
-		 * @param timeoutForEachRetry
-		 *            Timeout in milliseconds for each retry.
-		 * @param stopOnSuccess
-		 *            Flag that determines whether requests shall be stopped
-		 *            after the first successful reply (<code>true</code>),
-		 *            or not (<code>false</code>).
-		 */
-		RequestThread(final int retries, final long timeoutForEachRetry,
-				final boolean stopOnSuccess) {
-
-			// log entering
-			logger
-					.entering(this.getClass().getName(), "RequestThread",
-							new Object[] { retries, timeoutForEachRetry,
-									stopOnSuccess });
-
-			// check parameters
-			if (retries < 0 || timeoutForEachRetry < 0) {
-				final IllegalArgumentException e =
-						new IllegalArgumentException();
-				logger.throwing(this.getClass().getName(), "RequestThread", e);
-				throw e;
-			}
-
-			// remember parameters
-			this.retries = retries;
-			this.timeoutForEachRetry = timeoutForEachRetry;
-			this.stopOnSuccess = stopOnSuccess;
-
-			// start connected
-			connected = true;
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "RequestThread");
-		}
-
-		/**
-		 * Perform one or more requests.
-		 */
-		@Override
-		public void run() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "run");
-
-			try {
-
-				// set Tor as proxy
-				final InetSocketAddress isa =
-						new InetSocketAddress("127.0.0.1", socksPort);
-				final Proxy p = new Proxy(Type.SOCKS, isa);
-
-				// create target address for socket -- don't resolve the target
-				// name to an IP address!
-				final InetSocketAddress hs =
-						InetSocketAddress.createUnresolved(targetName,
-								targetPort);
-
-				// start retry loop
-				for (int i = 0; connected && i < retries; i++) {
-
-					// log this try
-					logger.log(Level.FINE, "Trying to perform request");
-
-					// remember when we started
-					final long timeBeforeConnectionAttempt =
-							System.currentTimeMillis();
-
-					// send event to event manager
-					eventManager.observeInternalEvent(
-							timeBeforeConnectionAttempt,
-							getClientApplicationName(),
-							ClientEventType.CLIENT_SENDING_REQUEST,
-							"Sending request.");
-
-					Socket s = null;
-					try {
-
-						// create new socket using Tor as proxy
-						s = new Socket(p);
-
-						// try to connect to remote server
-						s.connect(hs, (int) timeoutForEachRetry);
-
-						// open output stream to write request
-						final PrintStream out =
-								new PrintStream(s.getOutputStream());
-						out.print("GET / HTTP/1.0\r\n\r\n");
-
-						// open input stream to read reply
-						final BufferedReader in =
-								new BufferedReader(new InputStreamReader(s
-										.getInputStream()));
-
-						// only read the first char in the response; this method
-						// blocks until there is a response
-						in.read();
-
-						// send event to event manager
-						eventManager.observeInternalEvent(System
-								.currentTimeMillis(),
-								getClientApplicationName(),
-								ClientEventType.CLIENT_REPLY_RECEIVED,
-								"Received response.");
-
-						// if we should stop on success, stop further connection
-						// attempts
-						if (stopOnSuccess) {
-							connected = false;
-						}
-
-						// clean up socket
-						in.close();
-						out.close();
-						s.close();
-
-					} catch (final SocketTimeoutException e) {
-
-						// log warning
-						logger.log(Level.WARNING,
-								"Connection to remote server timed out!", e);
-
-						// send event to event manager
-						eventManager.observeInternalEvent(System
-								.currentTimeMillis(),
-								getClientApplicationName(),
-								ClientEventType.CLIENT_GAVE_UP_REQUEST,
-								"Giving up request.");
-
-						// try again immediately, if there are retries left
-
-					} catch (final IOException e) {
-
-						// log warning
-						logger.log(Level.WARNING,
-								"Connection to remote server could not be "
-										+ "established!", e);
-
-						// send event to event manager
-						eventManager.observeInternalEvent(System
-								.currentTimeMillis(),
-								getClientApplicationName(),
-								ClientEventType.CLIENT_GAVE_UP_REQUEST,
-								"Giving up request.");
-
-						// wait for the rest of the timeout
-						final long timeOfTimeoutLeft =
-								timeBeforeConnectionAttempt
-										+ timeoutForEachRetry
-										- System.currentTimeMillis();
-						if (timeOfTimeoutLeft > 0) {
-							try {
-								Thread.sleep(timeOfTimeoutLeft);
-							} catch (final InterruptedException ex) {
-								// do nothing
-							}
-						}
-
-					} finally {
-						if (s != null) {
-							// close connection
-							try {
-
-								// try to close socket
-								logger.log(Level.FINER,
-										"Trying to close socket.");
-								s.close();
-								logger.log(Level.FINE, "Socket closed.");
-
-							} catch (final IOException e) {
-
-								// log warning
-								logger.log(Level.WARNING,
-									"Exception when trying to close socket!",
-									e);
-							}
-						}
-					}
-				}
-
-			} finally {
-
-				// we are done here
-				logger.log(Level.FINE, "Requests performed!");
-
-				// send event to event manager
-				eventManager.observeInternalEvent(System.currentTimeMillis(),
-						getClientApplicationName(),
-						ClientEventType.CLIENT_REQUESTS_PERFORMED,
-						"Requests performed.");
-
-				// log exiting
-				logger.exiting(this.getClass().getName(), "run");
-			}
-		}
-
-		/**
-		 * Immediately stops this and all possibly subsequent requests.
-		 */
-		public void stopRequest() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "stopRequest");
-
-			// change connected state to false and interrupt thread
-			connected = false;
-			interrupt();
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "stopRequest");
-		}
-	}
-
-	/**
-	 * Name of this client application that is used as logger name of this node.
-	 */
-	private final String clientApplicationName;
-
-	/**
-	 * Thread that performs the requests in the background.
-	 */
-	private RequestThread clientThread;
-
-	/**
-	 * Event manager that handles all events concerning this client application.
-	 */
-	private final EventManagerImpl eventManager;
-
-	/**
-	 * Logger for this client which is called "client." plus the name of this
-	 * client application.
-	 */
-	private final Logger logger;
-
-	/**
-	 * SOCKS port of the local Tor node to which requests are sent.
-	 */
-	private final int socksPort;
-
-	/**
-	 * Target name for the requests sent by this client; can be a publicly
-	 * available URL or an onion address.
-	 */
-	private final String targetName;
-
-	/**
-	 * Target port for the requests sent by this client; can be either a server
-	 * port or a virtual port of a hidden service.
-	 */
-	private final int targetPort;
-
-	/**
-	 * Creates a new HTTP client within this JVM, but does not start sending
-	 * requests.
-	 *
-	 * @param network
-	 *            Network to which this HTTP client belongs; at the moment this
-	 *            is only used to determine the event manager instance.
-	 * @param clientApplicationName
-	 *            Name of this client that is used as part of the logger name.
-	 * @param targetName
-	 *            Target name for requests; can be either a server name/address
-	 *            or an onion address.
-	 * @param targetPort
-	 *            Target port for requests; can be either a server port or a
-	 *            virtual port of a hidden service.
-	 * @param socksPort
-	 *            SOCKS port of the local Tor node.
-	 * @throws IllegalArgumentException
-	 *             If at least one of the parameters is <code>null</code> or
-	 *             has an invalid value.
-	 */
-	ClientApplicationImpl(final NetworkImpl network,
-			final String clientApplicationName, final String targetName,
-			final int targetPort, final int socksPort) {
-
-		// check if clientApplicationName can be used as logger name
-		if (clientApplicationName == null
-				|| clientApplicationName.length() == 0) {
-			throw new IllegalArgumentException(
-					"Invalid clientApplicationName: " + clientApplicationName);
-		}
-
-		// create logger
-		logger = Logger.getLogger("client." + clientApplicationName);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "ClientApplicationImpl",
-				new Object[] { network, clientApplicationName, targetName,
-						targetPort, socksPort });
-
-		// check parameters
-		if (network == null || targetName == null || targetName.length() == 0
-				|| targetPort < 0 || targetPort > 65535 || socksPort < 0
-				|| socksPort > 65535) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "ClientApplicationImpl",
-					e);
-			throw e;
-		}
-
-		// remember parameters
-		this.clientApplicationName = clientApplicationName;
-		this.targetName = targetName;
-		this.targetPort = targetPort;
-		this.socksPort = socksPort;
-
-		// obtain and store reference on event manager
-		eventManager = network.getEventManagerImpl();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "ClientApplicationImpl");
-	}
-
-	public synchronized void startRequests(final int retries,
-			final long timeoutForEachRetry, final boolean stopOnSuccess) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "performRequest",
-				new Object[] { retries, timeoutForEachRetry, stopOnSuccess });
-
-		// check parameters
-		if (retries <= 0 || timeoutForEachRetry < 0) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "performRequest", e);
-			throw e;
-		}
-
-		// check if we already have started a request (TODO change this to allow
-		// multiple requests in parallel? would be possible)
-		if (clientThread != null) {
-			final IllegalStateException e =
-					new IllegalStateException(
-							"Another request has already been started!");
-			logger.throwing(this.getClass().getName(), "performRequest", e);
-			throw e;
-		}
-
-		// create a thread that performs requests in the background
-		clientThread =
-				new RequestThread(retries, timeoutForEachRetry, stopOnSuccess);
-		clientThread.setName("Request Thread");
-		clientThread.setDaemon(true);
-		clientThread.start();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "performRequest");
-	}
-
-	public synchronized void stopRequest() {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "stopRequest");
-
-		// check if a request is running
-		if (clientThread == null) {
-			final IllegalStateException e =
-					new IllegalStateException("Cannot stop "
-							+ "request, because no request has been started!");
-			logger.throwing(this.getClass().getName(), "stopRequest", e);
-			throw e;
-		}
-
-		// log this event
-		logger.log(Level.FINE, "Shutting down client");
-
-		// interrupt thread
-		clientThread.stopRequest();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "stopRequest");
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": clientApplicationName=\""
-				+ clientApplicationName + "\", targetAddress=\"" + targetName
-				+ "\", targetPort=" + targetPort + ", socksPort=" + socksPort;
-	}
-
-	public String getClientApplicationName() {
-		return clientApplicationName;
-	}
-
-	public int getSocksPort() {
-		return socksPort;
-	}
-
-	public String getTargetName() {
-		return targetName;
-	}
-
-	public int getTargetPort() {
-		return targetPort;
-	}
+    /**
+     * Internal thread class that is used to perform requests.
+     */
+    private class RequestThread extends Thread {
+
+        /**
+         * Flag to remember whether requests are performed at the moment (<code>true</code>),
+         * or have been stopped (<code>false</code>).
+         */
+        private boolean connected;
+
+        /**
+         * Number of retries to be performed.
+         */
+        private final int retries;
+
+        /**
+         * Flag that determines whether requests shall be stopped after the
+         * first successful reply (<code>true</code>), or not (<code>false</code>).
+         */
+        private final boolean stopOnSuccess;
+
+        /**
+         * Timeout in milliseconds for each retry.
+         */
+        private final long timeoutForEachRetry;
+
+        /**
+         * Creates a new thread, but does not start performing requests, yet.
+         *
+         * @param retries
+         *            Number of retries to be performed.
+         * @param timeoutForEachRetry
+         *            Timeout in milliseconds for each retry.
+         * @param stopOnSuccess
+         *            Flag that determines whether requests shall be stopped
+         *            after the first successful reply (<code>true</code>),
+         *            or not (<code>false</code>).
+         */
+        RequestThread(final int retries, final long timeoutForEachRetry,
+                final boolean stopOnSuccess) {
+
+            // log entering
+            logger
+                    .entering(this.getClass().getName(), "RequestThread",
+                            new Object[] { retries, timeoutForEachRetry,
+                                    stopOnSuccess });
+
+            // check parameters
+            if (retries < 0 || timeoutForEachRetry < 0) {
+                final IllegalArgumentException e =
+                        new IllegalArgumentException();
+                logger.throwing(this.getClass().getName(), "RequestThread", e);
+                throw e;
+            }
+
+            // remember parameters
+            this.retries = retries;
+            this.timeoutForEachRetry = timeoutForEachRetry;
+            this.stopOnSuccess = stopOnSuccess;
+
+            // start connected
+            connected = true;
+
+            // log exiting
+            logger.exiting(this.getClass().getName(), "RequestThread");
+        }
+
+        /**
+         * Perform one or more requests.
+         */
+        @Override
+        public void run() {
+
+            // log entering
+            logger.entering(this.getClass().getName(), "run");
+
+            try {
+
+                // set Tor as proxy
+                final InetSocketAddress isa =
+                        new InetSocketAddress("127.0.0.1", socksPort);
+                final Proxy p = new Proxy(Type.SOCKS, isa);
+
+                // create target address for socket -- don't resolve the target
+                // name to an IP address!
+                final InetSocketAddress hs =
+                        InetSocketAddress.createUnresolved(targetName,
+                                targetPort);
+
+                // start retry loop
+                for (int i = 0; connected && i < retries; i++) {
+
+                    // log this try
+                    logger.log(Level.FINE, "Trying to perform request");
+
+                    // remember when we started
+                    final long timeBeforeConnectionAttempt =
+                            System.currentTimeMillis();
+
+                    // send event to event manager
+                    eventManager.observeInternalEvent(
+                            timeBeforeConnectionAttempt,
+                            getClientApplicationName(),
+                            ClientEventType.CLIENT_SENDING_REQUEST,
+                            "Sending request.");
+
+                    Socket s = null;
+                    try {
+
+                        // create new socket using Tor as proxy
+                        s = new Socket(p);
+
+                        // try to connect to remote server
+                        s.connect(hs, (int) timeoutForEachRetry);
+
+                        // open output stream to write request
+                        final PrintStream out =
+                                new PrintStream(s.getOutputStream());
+                        out.print("GET / HTTP/1.0\r\n\r\n");
+
+                        // open input stream to read reply
+                        final BufferedReader in =
+                                new BufferedReader(new InputStreamReader(s
+                                        .getInputStream()));
+
+                        // only read the first char in the response; this method
+                        // blocks until there is a response
+                        in.read();
+
+                        // send event to event manager
+                        eventManager.observeInternalEvent(System
+                                .currentTimeMillis(),
+                                getClientApplicationName(),
+                                ClientEventType.CLIENT_REPLY_RECEIVED,
+                                "Received response.");
+
+                        // if we should stop on success, stop further connection
+                        // attempts
+                        if (stopOnSuccess) {
+                            connected = false;
+                        }
+
+                        // clean up socket
+                        in.close();
+                        out.close();
+                        s.close();
+
+                    } catch (final SocketTimeoutException e) {
+
+                        // log warning
+                        logger.log(Level.WARNING,
+                                "Connection to remote server timed out!", e);
+
+                        // send event to event manager
+                        eventManager.observeInternalEvent(System
+                                .currentTimeMillis(),
+                                getClientApplicationName(),
+                                ClientEventType.CLIENT_GAVE_UP_REQUEST,
+                                "Giving up request.");
+
+                        // try again immediately, if there are retries left
+
+                    } catch (final IOException e) {
+
+                        // log warning
+                        logger.log(Level.WARNING,
+                                "Connection to remote server could not be "
+                                        + "established!", e);
+
+                        // send event to event manager
+                        eventManager.observeInternalEvent(System
+                                .currentTimeMillis(),
+                                getClientApplicationName(),
+                                ClientEventType.CLIENT_GAVE_UP_REQUEST,
+                                "Giving up request.");
+
+                        // wait for the rest of the timeout
+                        final long timeOfTimeoutLeft =
+                                timeBeforeConnectionAttempt
+                                        + timeoutForEachRetry
+                                        - System.currentTimeMillis();
+                        if (timeOfTimeoutLeft > 0) {
+                            try {
+                                Thread.sleep(timeOfTimeoutLeft);
+                            } catch (final InterruptedException ex) {
+                                // do nothing
+                            }
+                        }
+
+                    } finally {
+                        if (s != null) {
+                            // close connection
+                            try {
+
+                                // try to close socket
+                                logger.log(Level.FINER,
+                                        "Trying to close socket.");
+                                s.close();
+                                logger.log(Level.FINE, "Socket closed.");
+
+                            } catch (final IOException e) {
+
+                                // log warning
+                                logger.log(Level.WARNING,
+                                    "Exception when trying to close socket!",
+                                    e);
+                            }
+                        }
+                    }
+                }
+
+            } finally {
+
+                // we are done here
+                logger.log(Level.FINE, "Requests performed!");
+
+                // send event to event manager
+                eventManager.observeInternalEvent(System.currentTimeMillis(),
+                        getClientApplicationName(),
+                        ClientEventType.CLIENT_REQUESTS_PERFORMED,
+                        "Requests performed.");
+
+                // log exiting
+                logger.exiting(this.getClass().getName(), "run");
+            }
+        }
+
+        /**
+         * Immediately stops this and all possibly subsequent requests.
+         */
+        public void stopRequest() {
+
+            // log entering
+            logger.entering(this.getClass().getName(), "stopRequest");
+
+            // change connected state to false and interrupt thread
+            connected = false;
+            interrupt();
+
+            // log exiting
+            logger.exiting(this.getClass().getName(), "stopRequest");
+        }
+    }
+
+    /**
+     * Name of this client application that is used as logger name of this node.
+     */
+    private final String clientApplicationName;
+
+    /**
+     * Thread that performs the requests in the background.
+     */
+    private RequestThread clientThread;
+
+    /**
+     * Event manager that handles all events concerning this client application.
+     */
+    private final EventManagerImpl eventManager;
+
+    /**
+     * Logger for this client which is called "client." plus the name of this
+     * client application.
+     */
+    private final Logger logger;
+
+    /**
+     * SOCKS port of the local Tor node to which requests are sent.
+     */
+    private final int socksPort;
+
+    /**
+     * Target name for the requests sent by this client; can be a publicly
+     * available URL or an onion address.
+     */
+    private final String targetName;
+
+    /**
+     * Target port for the requests sent by this client; can be either a server
+     * port or a virtual port of a hidden service.
+     */
+    private final int targetPort;
+
+    /**
+     * Creates a new HTTP client within this JVM, but does not start sending
+     * requests.
+     *
+     * @param network
+     *            Network to which this HTTP client belongs; at the moment this
+     *            is only used to determine the event manager instance.
+     * @param clientApplicationName
+     *            Name of this client that is used as part of the logger name.
+     * @param targetName
+     *            Target name for requests; can be either a server name/address
+     *            or an onion address.
+     * @param targetPort
+     *            Target port for requests; can be either a server port or a
+     *            virtual port of a hidden service.
+     * @param socksPort
+     *            SOCKS port of the local Tor node.
+     * @throws IllegalArgumentException
+     *             If at least one of the parameters is <code>null</code> or
+     *             has an invalid value.
+     */
+    ClientApplicationImpl(final NetworkImpl network,
+            final String clientApplicationName, final String targetName,
+            final int targetPort, final int socksPort) {
+
+        // check if clientApplicationName can be used as logger name
+        if (clientApplicationName == null
+                || clientApplicationName.length() == 0) {
+            throw new IllegalArgumentException(
+                    "Invalid clientApplicationName: " + clientApplicationName);
+        }
+
+        // create logger
+        logger = Logger.getLogger("client." + clientApplicationName);
+
+        // log entering
+        logger.entering(this.getClass().getName(), "ClientApplicationImpl",
+                new Object[] { network, clientApplicationName, targetName,
+                        targetPort, socksPort });
+
+        // check parameters
+        if (network == null || targetName == null || targetName.length() == 0
+                || targetPort < 0 || targetPort > 65535 || socksPort < 0
+                || socksPort > 65535) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "ClientApplicationImpl",
+                    e);
+            throw e;
+        }
+
+        // remember parameters
+        this.clientApplicationName = clientApplicationName;
+        this.targetName = targetName;
+        this.targetPort = targetPort;
+        this.socksPort = socksPort;
+
+        // obtain and store reference on event manager
+        eventManager = network.getEventManagerImpl();
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "ClientApplicationImpl");
+    }
+
+    public synchronized void startRequests(final int retries,
+            final long timeoutForEachRetry, final boolean stopOnSuccess) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "performRequest",
+                new Object[] { retries, timeoutForEachRetry, stopOnSuccess });
+
+        // check parameters
+        if (retries <= 0 || timeoutForEachRetry < 0) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "performRequest", e);
+            throw e;
+        }
+
+        // check if we already have started a request (TODO change this to allow
+        // multiple requests in parallel? would be possible)
+        if (clientThread != null) {
+            final IllegalStateException e =
+                    new IllegalStateException(
+                            "Another request has already been started!");
+            logger.throwing(this.getClass().getName(), "performRequest", e);
+            throw e;
+        }
+
+        // create a thread that performs requests in the background
+        clientThread =
+                new RequestThread(retries, timeoutForEachRetry, stopOnSuccess);
+        clientThread.setName("Request Thread");
+        clientThread.setDaemon(true);
+        clientThread.start();
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "performRequest");
+    }
+
+    public synchronized void stopRequest() {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "stopRequest");
+
+        // check if a request is running
+        if (clientThread == null) {
+            final IllegalStateException e =
+                    new IllegalStateException("Cannot stop "
+                            + "request, because no request has been started!");
+            logger.throwing(this.getClass().getName(), "stopRequest", e);
+            throw e;
+        }
+
+        // log this event
+        logger.log(Level.FINE, "Shutting down client");
+
+        // interrupt thread
+        clientThread.stopRequest();
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "stopRequest");
+    }
+
+    @Override
+    public String toString() {
+        return this.getClass().getSimpleName() + ": clientApplicationName=\""
+                + clientApplicationName + "\", targetAddress=\"" + targetName
+                + "\", targetPort=" + targetPort + ", socksPort=" + socksPort;
+    }
+
+    public String getClientApplicationName() {
+        return clientApplicationName;
+    }
+
+    public int getSocksPort() {
+        return socksPort;
+    }
+
+    public String getTargetName() {
+        return targetName;
+    }
+
+    public int getTargetPort() {
+        return targetPort;
+    }
 }
diff --git a/src/org/torproject/puppetor/impl/DirectoryNodeImpl.java b/src/org/torproject/puppetor/impl/DirectoryNodeImpl.java
index b8cf15c..d9bcdf1 100644
--- a/src/org/torproject/puppetor/impl/DirectoryNodeImpl.java
+++ b/src/org/torproject/puppetor/impl/DirectoryNodeImpl.java
@@ -32,401 +32,401 @@ import org.torproject.puppetor.PuppeTorException;
  */
 public class DirectoryNodeImpl extends RouterNodeImpl implements DirectoryNode {
 
-	/**
-	 * Executable file for generating v3 directory authority certificates.
-	 *
-	 * TODO make this configurable!
-	 */
-	protected static final File torGencertExecutable = new File("tor-gencert");
-
-	/**
-	 * Internal thread class that is used to generate v3 directory authority
-	 * certificates in parallel, which can take a few seconds.
-	 */
-	private class GenerateCertificateThread extends Thread {
-
-		@Override
-		public void run() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "run");
-
-			// run tor-gencert
-			final ProcessBuilder processBuilder =
-					new ProcessBuilder(torGencertExecutable.getPath(),
-							"--create-identity-key"// );
-							, "--passphrase-fd", "0");
-			final File workingDirectory =
-					new File(workingDir.getAbsolutePath() + File.separator
-							+ "keys" + File.separator);
-
-			// create working directory
-			workingDirectory.mkdirs();
-
-			processBuilder.directory(workingDirectory);
-			processBuilder.redirectErrorStream(true);
-			Process tmpProcess = null;
-			try {
-				tmpProcess = processBuilder.start();
-			} catch (final IOException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not start tor-gencert process for generating a "
-										+ "v3 directory authority certificate!",
-								e);
-				logger.log(Level.WARNING, "Could not start tor-gencert "
-						+ "process for generating a v3 directory authority "
-						+ "certificate!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			final BufferedWriter writer =
-					new BufferedWriter(new OutputStreamWriter(tmpProcess
-							.getOutputStream()));
-			try {
-				writer.write("somepassword\n");
-				writer.close();
-			} catch (final IOException e1) {
-				System.out.println("Exception at write! " + e1.getMessage());
-				e1.printStackTrace();
-			}
-			// final InputStream read = tmpProcess.getErrorStream();
-
-			final InputStream stderr = tmpProcess.getInputStream();
-			final InputStreamReader isr = new InputStreamReader(stderr);
-			final BufferedReader br = new BufferedReader(isr);
-			String line = null;
-			try {
-				while ((line = br.readLine()) != null) {
-					;
-				}
-			} catch (final IOException e1) {
-				e1.printStackTrace();
-			}
-
-			// wait for process to terminate
-			int exitValue = 0;
-			try {
-				exitValue = tmpProcess.waitFor();
-			} catch (final InterruptedException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Interrupted while waiting for tor-gencert process to exit!",
-								e);
-				logger.log(Level.WARNING,
-						"tor-gencert process was interrupted!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			if (exitValue != 0) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not start tor-gencert process! tor-gencert exited with "
-										+ "exit value " + exitValue + "!");
-				logger.log(Level.WARNING,
-						"Could not start tor-gencert process!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			// read fingerprint from file
-			final File authorityCertificateFile =
-					new File(workingDirectory.getAbsolutePath()
-							+ File.separator + "authority_certificate");
-			String identity;
-			try {
-				final BufferedReader br2 =
-						new BufferedReader(new FileReader(
-								authorityCertificateFile));
-				while ((line = br2.readLine()) != null
-						&& !line.startsWith("fingerprint ")) {
-					;
-				}
-				if (line == null) {
-					final PuppeTorException ex =
-							new PuppeTorException(
-									"Could not find fingerprint line in file "
-											+ "authority_certificate!");
-					logger.log(Level.WARNING,
-							"Could not find fingerprint line in file "
-									+ "authority_certificate!", ex);
-					setCaughtException(ex);
-					return;
-				}
-				identity = line.substring(line.indexOf(" ") + 1);
-				br2.close();
-			} catch (final IOException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not read fingerprint from file!", e);
-				logger.log(Level.WARNING, "Could not read fingerprint file!",
-						ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			setV3Identity(identity);
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "run");
-		}
-	}
-
-	/**
-	 * Set of routers that are approved by this directory node.
-	 */
-	private final SortedSet<String> approvedRouters;
-
-	/**
-	 * Creates a <code>DirectoryNodeImpl</code> and adds it to the given
-	 * <code>network</code>, but does not yet write its configuration to disk
-	 * or start the corresponding Tor process.
-	 *
-	 * @param network
-	 *            Network configuration to which this node belongs.
-	 * @param nodeName
-	 *            The name of the new node which may only consist of between 1
-	 *            and 19 alpha-numeric characters.
-	 * @param controlPort
-	 *            Port on which the Tor node will be listening for us as its
-	 *            controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            Port on which the Tor node will be listening for SOCKS
-	 *            connection requests. May not be negative or greater than
-	 *            65535.
-	 * @param orPort
-	 *            Port on which the Tor node will be listening for onion
-	 *            requests by other Tor nodes. May not be negative or greater
-	 *            than 65535.
-	 * @param dirPort
-	 *            Port on which the Tor node will be listening for directory
-	 *            requests from other Tor nodes. May not be negative or greater
-	 *            than 65535.
-	 * @param serverIpAddress
-	 *            The IP address on which the node will listen. Must be a valid
-	 *            IP v4 address in dotted decimal notation. May not be
-	 *            <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             If at least one of the parameters is <code>null</code> or
-	 *             has an invalid value.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while writing the temporary
-	 *             <code>approved-routers</code> file.
-	 */
-	DirectoryNodeImpl(final NetworkImpl network, final String nodeName,
-			final int controlPort, final int socksPort, final int orPort,
-			final int dirPort, final String serverIpAddress) {
-		// create superclass instance; parameter checking is done in super
-		// constructor
-		super(network, nodeName, controlPort, socksPort, orPort, dirPort,
-				serverIpAddress);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "DirectoryNodeImpl",
-				new Object[] { network, nodeName, controlPort, socksPort,
-						orPort, dirPort });
-
-		// initialize attribute
-		approvedRouters = new TreeSet<String>();
-
-		// extend configuration by template configuration of directory nodes
-		configuration.addAll(templateConfiguration);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "DirectoryNodeImpl");
-	}
-
-	/**
-	 * Invoked by the certificate generating thread: sets the generated v3
-	 * identity string.
-	 *
-	 * @param v3Identity
-	 *            The generated v3 identity string.
-	 */
-	private synchronized void setV3Identity(final String v3Identity) {
-		// log entering
-		logger.entering(this.getClass().getName(), "setV3Identity", v3Identity);
-
-		// remember fingerprint and notify all waiting threads
-		this.v3Identity = v3Identity;
-		notifyAll();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "setV3Identity");
-	}
-
-	/**
-	 * The generated v3 identity string.
-	 */
-	private String v3Identity;
-
-	public synchronized String getV3Identity() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "getV3Identity");
-
-		// wait until either the v3 identity has been determined or an exception
-		// was caught
-		while (v3Identity == null && caughtException == null) {
-
-			try {
-				wait(500);
-			} catch (final InterruptedException e) {
-				// do nothing
-			}
-		}
-
-		if (caughtException != null) {
-			logger.throwing(this.getClass().getName(), "getV3Identity",
-					caughtException);
-			throw caughtException;
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "getV3Identity", v3Identity);
-		return v3Identity;
-	}
-
-	public synchronized String getDirServerString() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "getDirServerString");
-
-		// determine fingerprint
-		String fingerprint = getFingerprint();
-
-		// cut off router nickname
-		fingerprint = fingerprint.substring(fingerprint.indexOf(" ") + 1);
-
-		// determine v3 identity
-		final String determinedV3Identity = getV3Identity();
-
-		// put everything together
-		final String dirServerString =
-				"DirServer " + nodeName + " v3ident=" + determinedV3Identity
-						+ " orport=" + orPort + " " + serverIpAddress + ":"
-						+ dirPort + " " + fingerprint;
-
-		// log exiting and return dir server string
-		logger.exiting(this.getClass().getName(), "getDirServerString",
-				dirServerString);
-		return dirServerString;
-	}
-
-	public void addApprovedRouters(final Set<String> routers) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addApprovedRouters",
-				routers);
-
-		// check parameter
-		if (routers == null) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "addApprovedRouters", e);
-			throw e;
-		}
-
-		// add the given approved router strings to the sorted set of already
-		// known strings (if any)
-		approvedRouters.addAll(routers);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "addApprovedRouters");
-	}
-
-	@Override
-	protected synchronized void determineFingerprint() {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "determineFingerprint");
-
-		// start a thread to generate the directory's certificate
-		final GenerateCertificateThread certificateThread =
-				new GenerateCertificateThread();
-		certificateThread.setName(nodeName + " Certificate Generator");
-		certificateThread.start();
-
-		// wait (non-blocking) for the v3 identity string
-		try {
-			getV3Identity();
-		} catch (final PuppeTorException e1) {
-			final PuppeTorException ex =
-					new PuppeTorException("Could not read v3 identity string!",
-							e1);
-			caughtException = ex;
-			return;
-		}
-
-		// create an empty approved-routers file to make Tor happy
-		try {
-			new File(workingDir.getAbsolutePath() + File.separator
-					+ "approved-routers").createNewFile();
-		} catch (final IOException e) {
-			final PuppeTorException ex =
-					new PuppeTorException(
-							"Could not write empty approved-routers file!", e);
-			caughtException = ex;
-			return;
-		}
-
-		// invoke overwritten method
-		super.determineFingerprint();
-	}
-
-	@Override
-	public synchronized void writeConfiguration() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "writeConfiguration");
-
-		// write approved-routers file
-		try {
-			final File approvedRoutersFile =
-					new File(workingDir.getAbsolutePath() + File.separator
-							+ "approved-routers");
-			final BufferedWriter bw =
-					new BufferedWriter(new FileWriter(approvedRoutersFile));
-			for (final String approvedRouter : approvedRouters) {
-				bw.write(approvedRouter + "\n");
-			}
-			bw.close();
-		} catch (final IOException e) {
-			final PuppeTorException ex = new PuppeTorException(e);
-			logger
-					.throwing(this.getClass().getName(), "writeConfiguration",
-							ex);
-			throw ex;
-		}
-
-		// invoke overridden method
-		super.writeConfiguration();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "writeConfiguration");
-	}
-
-	/**
-	 * Template configuration of directory nodes.
-	 */
-	static List<String> templateConfiguration;
-
-	static {
-		templateConfiguration = new ArrayList<String>();
-
-		// configure this node as an authoritative directory
-		templateConfiguration.add("AuthoritativeDirectory 1");
-		templateConfiguration.add("V2AuthoritativeDirectory 1");
-		templateConfiguration.add("V3AuthoritativeDirectory 1");
-		templateConfiguration.add("DirAllowPrivateAddresses 1");
-		templateConfiguration.add("MinUptimeHidServDirectoryV2 0 minutes");
-
-		// TODO This is now contained in proposal 135.
-		// templateConfiguration.add("AuthDirMaxServersPerAddr 0");
-		// templateConfiguration.add("AuthDirMaxServersPerAuthAddr 0");
-		// templateConfiguration.add("V3AuthVotingInterval 5 minutes");
-		// templateConfiguration.add("V3AuthVoteDelay 20 seconds");
-		// templateConfiguration.add("V3AuthDistDelay 20 seconds");
-	}
+    /**
+     * Executable file for generating v3 directory authority certificates.
+     *
+     * TODO make this configurable!
+     */
+    protected static final File torGencertExecutable = new File("tor-gencert");
+
+    /**
+     * Internal thread class that is used to generate v3 directory authority
+     * certificates in parallel, which can take a few seconds.
+     */
+    private class GenerateCertificateThread extends Thread {
+
+        @Override
+        public void run() {
+
+            // log entering
+            logger.entering(this.getClass().getName(), "run");
+
+            // run tor-gencert
+            final ProcessBuilder processBuilder =
+                    new ProcessBuilder(torGencertExecutable.getPath(),
+                            "--create-identity-key"// );
+                            , "--passphrase-fd", "0");
+            final File workingDirectory =
+                    new File(workingDir.getAbsolutePath() + File.separator
+                            + "keys" + File.separator);
+
+            // create working directory
+            workingDirectory.mkdirs();
+
+            processBuilder.directory(workingDirectory);
+            processBuilder.redirectErrorStream(true);
+            Process tmpProcess = null;
+            try {
+                tmpProcess = processBuilder.start();
+            } catch (final IOException e) {
+                final PuppeTorException ex =
+                        new PuppeTorException(
+                                "Could not start tor-gencert process for generating a "
+                                        + "v3 directory authority certificate!",
+                                e);
+                logger.log(Level.WARNING, "Could not start tor-gencert "
+                        + "process for generating a v3 directory authority "
+                        + "certificate!", ex);
+                setCaughtException(ex);
+                return;
+            }
+
+            final BufferedWriter writer =
+                    new BufferedWriter(new OutputStreamWriter(tmpProcess
+                            .getOutputStream()));
+            try {
+                writer.write("somepassword\n");
+                writer.close();
+            } catch (final IOException e1) {
+                System.out.println("Exception at write! " + e1.getMessage());
+                e1.printStackTrace();
+            }
+            // final InputStream read = tmpProcess.getErrorStream();
+
+            final InputStream stderr = tmpProcess.getInputStream();
+            final InputStreamReader isr = new InputStreamReader(stderr);
+            final BufferedReader br = new BufferedReader(isr);
+            String line = null;
+            try {
+                while ((line = br.readLine()) != null) {
+                    ;
+                }
+            } catch (final IOException e1) {
+                e1.printStackTrace();
+            }
+
+            // wait for process to terminate
+            int exitValue = 0;
+            try {
+                exitValue = tmpProcess.waitFor();
+            } catch (final InterruptedException e) {
+                final PuppeTorException ex =
+                        new PuppeTorException(
+                                "Interrupted while waiting for tor-gencert process to exit!",
+                                e);
+                logger.log(Level.WARNING,
+                        "tor-gencert process was interrupted!", ex);
+                setCaughtException(ex);
+                return;
+            }
+
+            if (exitValue != 0) {
+                final PuppeTorException ex =
+                        new PuppeTorException(
+                                "Could not start tor-gencert process! tor-gencert exited with "
+                                        + "exit value " + exitValue + "!");
+                logger.log(Level.WARNING,
+                        "Could not start tor-gencert process!", ex);
+                setCaughtException(ex);
+                return;
+            }
+
+            // read fingerprint from file
+            final File authorityCertificateFile =
+                    new File(workingDirectory.getAbsolutePath()
+                            + File.separator + "authority_certificate");
+            String identity;
+            try {
+                final BufferedReader br2 =
+                        new BufferedReader(new FileReader(
+                                authorityCertificateFile));
+                while ((line = br2.readLine()) != null
+                        && !line.startsWith("fingerprint ")) {
+                    ;
+                }
+                if (line == null) {
+                    final PuppeTorException ex =
+                            new PuppeTorException(
+                                    "Could not find fingerprint line in file "
+                                            + "authority_certificate!");
+                    logger.log(Level.WARNING,
+                            "Could not find fingerprint line in file "
+                                    + "authority_certificate!", ex);
+                    setCaughtException(ex);
+                    return;
+                }
+                identity = line.substring(line.indexOf(" ") + 1);
+                br2.close();
+            } catch (final IOException e) {
+                final PuppeTorException ex =
+                        new PuppeTorException(
+                                "Could not read fingerprint from file!", e);
+                logger.log(Level.WARNING, "Could not read fingerprint file!",
+                        ex);
+                setCaughtException(ex);
+                return;
+            }
+
+            setV3Identity(identity);
+
+            // log exiting
+            logger.exiting(this.getClass().getName(), "run");
+        }
+    }
+
+    /**
+     * Set of routers that are approved by this directory node.
+     */
+    private final SortedSet<String> approvedRouters;
+
+    /**
+     * Creates a <code>DirectoryNodeImpl</code> and adds it to the given
+     * <code>network</code>, but does not yet write its configuration to disk
+     * or start the corresponding Tor process.
+     *
+     * @param network
+     *            Network configuration to which this node belongs.
+     * @param nodeName
+     *            The name of the new node which may only consist of between 1
+     *            and 19 alpha-numeric characters.
+     * @param controlPort
+     *            Port on which the Tor node will be listening for us as its
+     *            controller. May not be negative or greater than 65535.
+     * @param socksPort
+     *            Port on which the Tor node will be listening for SOCKS
+     *            connection requests. May not be negative or greater than
+     *            65535.
+     * @param orPort
+     *            Port on which the Tor node will be listening for onion
+     *            requests by other Tor nodes. May not be negative or greater
+     *            than 65535.
+     * @param dirPort
+     *            Port on which the Tor node will be listening for directory
+     *            requests from other Tor nodes. May not be negative or greater
+     *            than 65535.
+     * @param serverIpAddress
+     *            The IP address on which the node will listen. Must be a valid
+     *            IP v4 address in dotted decimal notation. May not be
+     *            <code>null</code>.
+     * @throws IllegalArgumentException
+     *             If at least one of the parameters is <code>null</code> or
+     *             has an invalid value.
+     * @throws PuppeTorException
+     *             Thrown if an I/O problem occurs while writing the temporary
+     *             <code>approved-routers</code> file.
+     */
+    DirectoryNodeImpl(final NetworkImpl network, final String nodeName,
+            final int controlPort, final int socksPort, final int orPort,
+            final int dirPort, final String serverIpAddress) {
+        // create superclass instance; parameter checking is done in super
+        // constructor
+        super(network, nodeName, controlPort, socksPort, orPort, dirPort,
+                serverIpAddress);
+
+        // log entering
+        logger.entering(this.getClass().getName(), "DirectoryNodeImpl",
+                new Object[] { network, nodeName, controlPort, socksPort,
+                        orPort, dirPort });
+
+        // initialize attribute
+        approvedRouters = new TreeSet<String>();
+
+        // extend configuration by template configuration of directory nodes
+        configuration.addAll(templateConfiguration);
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "DirectoryNodeImpl");
+    }
+
+    /**
+     * Invoked by the certificate generating thread: sets the generated v3
+     * identity string.
+     *
+     * @param v3Identity
+     *            The generated v3 identity string.
+     */
+    private synchronized void setV3Identity(final String v3Identity) {
+        // log entering
+        logger.entering(this.getClass().getName(), "setV3Identity", v3Identity);
+
+        // remember fingerprint and notify all waiting threads
+        this.v3Identity = v3Identity;
+        notifyAll();
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "setV3Identity");
+    }
+
+    /**
+     * The generated v3 identity string.
+     */
+    private String v3Identity;
+
+    public synchronized String getV3Identity() throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "getV3Identity");
+
+        // wait until either the v3 identity has been determined or an exception
+        // was caught
+        while (v3Identity == null && caughtException == null) {
+
+            try {
+                wait(500);
+            } catch (final InterruptedException e) {
+                // do nothing
+            }
+        }
+
+        if (caughtException != null) {
+            logger.throwing(this.getClass().getName(), "getV3Identity",
+                    caughtException);
+            throw caughtException;
+        }
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "getV3Identity", v3Identity);
+        return v3Identity;
+    }
+
+    public synchronized String getDirServerString() throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "getDirServerString");
+
+        // determine fingerprint
+        String fingerprint = getFingerprint();
+
+        // cut off router nickname
+        fingerprint = fingerprint.substring(fingerprint.indexOf(" ") + 1);
+
+        // determine v3 identity
+        final String determinedV3Identity = getV3Identity();
+
+        // put everything together
+        final String dirServerString =
+                "DirServer " + nodeName + " v3ident=" + determinedV3Identity
+                        + " orport=" + orPort + " " + serverIpAddress + ":"
+                        + dirPort + " " + fingerprint;
+
+        // log exiting and return dir server string
+        logger.exiting(this.getClass().getName(), "getDirServerString",
+                dirServerString);
+        return dirServerString;
+    }
+
+    public void addApprovedRouters(final Set<String> routers) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "addApprovedRouters",
+                routers);
+
+        // check parameter
+        if (routers == null) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "addApprovedRouters", e);
+            throw e;
+        }
+
+        // add the given approved router strings to the sorted set of already
+        // known strings (if any)
+        approvedRouters.addAll(routers);
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "addApprovedRouters");
+    }
+
+    @Override
+    protected synchronized void determineFingerprint() {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "determineFingerprint");
+
+        // start a thread to generate the directory's certificate
+        final GenerateCertificateThread certificateThread =
+                new GenerateCertificateThread();
+        certificateThread.setName(nodeName + " Certificate Generator");
+        certificateThread.start();
+
+        // wait (non-blocking) for the v3 identity string
+        try {
+            getV3Identity();
+        } catch (final PuppeTorException e1) {
+            final PuppeTorException ex =
+                    new PuppeTorException("Could not read v3 identity string!",
+                            e1);
+            caughtException = ex;
+            return;
+        }
+
+        // create an empty approved-routers file to make Tor happy
+        try {
+            new File(workingDir.getAbsolutePath() + File.separator
+                    + "approved-routers").createNewFile();
+        } catch (final IOException e) {
+            final PuppeTorException ex =
+                    new PuppeTorException(
+                            "Could not write empty approved-routers file!", e);
+            caughtException = ex;
+            return;
+        }
+
+        // invoke overwritten method
+        super.determineFingerprint();
+    }
+
+    @Override
+    public synchronized void writeConfiguration() throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "writeConfiguration");
+
+        // write approved-routers file
+        try {
+            final File approvedRoutersFile =
+                    new File(workingDir.getAbsolutePath() + File.separator
+                            + "approved-routers");
+            final BufferedWriter bw =
+                    new BufferedWriter(new FileWriter(approvedRoutersFile));
+            for (final String approvedRouter : approvedRouters) {
+                bw.write(approvedRouter + "\n");
+            }
+            bw.close();
+        } catch (final IOException e) {
+            final PuppeTorException ex = new PuppeTorException(e);
+            logger
+                    .throwing(this.getClass().getName(), "writeConfiguration",
+                            ex);
+            throw ex;
+        }
+
+        // invoke overridden method
+        super.writeConfiguration();
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "writeConfiguration");
+    }
+
+    /**
+     * Template configuration of directory nodes.
+     */
+    static List<String> templateConfiguration;
+
+    static {
+        templateConfiguration = new ArrayList<String>();
+
+        // configure this node as an authoritative directory
+        templateConfiguration.add("AuthoritativeDirectory 1");
+        templateConfiguration.add("V2AuthoritativeDirectory 1");
+        templateConfiguration.add("V3AuthoritativeDirectory 1");
+        templateConfiguration.add("DirAllowPrivateAddresses 1");
+        templateConfiguration.add("MinUptimeHidServDirectoryV2 0 minutes");
+
+        // TODO This is now contained in proposal 135.
+        // templateConfiguration.add("AuthDirMaxServersPerAddr 0");
+        // templateConfiguration.add("AuthDirMaxServersPerAuthAddr 0");
+        // templateConfiguration.add("V3AuthVotingInterval 5 minutes");
+        // templateConfiguration.add("V3AuthVoteDelay 20 seconds");
+        // templateConfiguration.add("V3AuthDistDelay 20 seconds");
+    }
 }
diff --git a/src/org/torproject/puppetor/impl/EventImpl.java b/src/org/torproject/puppetor/impl/EventImpl.java
index 4c277c5..c260e2c 100644
--- a/src/org/torproject/puppetor/impl/EventImpl.java
+++ b/src/org/torproject/puppetor/impl/EventImpl.java
@@ -19,105 +19,105 @@ import org.torproject.puppetor.EventType;
 @SuppressWarnings("serial")
 public class EventImpl implements Event {
 
-	/**
-	 * The source of this event.
-	 */
-	private final String source;
-
-	/**
-	 * The type of this event.
-	 */
-	private EventType type;
-
-	/**
-	 * Either the log message that led to firing this event, or an internal
-	 * message.
-	 */
-	private final String message;
-
-	/**
-	 * The occurrence time of the event or of the corresponding log statement.
-	 */
-	private long occurrenceTime;
-
-	/**
-	 * Creates a new <code>EventImpl</code>.
-	 *
-	 * @param occurrenceTime
-	 *            The occurrence time of the event or of the corresponding log
-	 *            statement.
-	 * @param source
-	 *            The source of this event.
-	 * @param type
-	 *            The type of this event.
-	 * @param message
-	 *            Either the log message that led to firing this event, or an
-	 *            internal message.
-	 */
-	EventImpl(final long occurrenceTime, final String source,
-			final EventType type, final String message) {
-		this.occurrenceTime = occurrenceTime;
-		this.source = source;
-		this.type = type;
-		this.message = message;
-	}
-
-	/**
-	 * Creates a new <code>EventImpl</code>.
-	 *
-	 * @param source
-	 *            The source of this event.
-	 * @param message
-	 *            Either the log message that led to firing this event, or an
-	 *            internal message.
-	 */
-	EventImpl(final String source, final String message) {
-		this.source = source;
-		this.message = message;
-	}
-
-	public String getSource() {
-		return source;
-	}
-
-	public EventType getType() {
-		return type;
-	}
-
-	/**
-	 * Sets the event type to the given type.
-	 *
-	 * @param type
-	 *            The type of this event.
-	 */
-	void setType(final EventType type) {
-		this.type = type;
-	}
-
-	public String getMessage() {
-		return message;
-	}
-
-	public long getOccurrenceTime() {
-		return occurrenceTime;
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": occurenceTime="
-				+ new Date(occurrenceTime) + ", source=\"" + source
-				+ "\", type=" + type.getTypeName() + ", message=\"" + message
-				+ "\"";
-	}
-
-	/**
-	 * Sets the occurrence time to the given time.
-	 *
-	 * @param occurrenceTime
-	 *            The occurrence time of the event or of the corresponding log
-	 *            statement.
-	 */
-	void setOccurenceTime(final long occurrenceTime) {
-		this.occurrenceTime = occurrenceTime;
-	}
+    /**
+     * The source of this event.
+     */
+    private final String source;
+
+    /**
+     * The type of this event.
+     */
+    private EventType type;
+
+    /**
+     * Either the log message that led to firing this event, or an internal
+     * message.
+     */
+    private final String message;
+
+    /**
+     * The occurrence time of the event or of the corresponding log statement.
+     */
+    private long occurrenceTime;
+
+    /**
+     * Creates a new <code>EventImpl</code>.
+     *
+     * @param occurrenceTime
+     *            The occurrence time of the event or of the corresponding log
+     *            statement.
+     * @param source
+     *            The source of this event.
+     * @param type
+     *            The type of this event.
+     * @param message
+     *            Either the log message that led to firing this event, or an
+     *            internal message.
+     */
+    EventImpl(final long occurrenceTime, final String source,
+            final EventType type, final String message) {
+        this.occurrenceTime = occurrenceTime;
+        this.source = source;
+        this.type = type;
+        this.message = message;
+    }
+
+    /**
+     * Creates a new <code>EventImpl</code>.
+     *
+     * @param source
+     *            The source of this event.
+     * @param message
+     *            Either the log message that led to firing this event, or an
+     *            internal message.
+     */
+    EventImpl(final String source, final String message) {
+        this.source = source;
+        this.message = message;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public EventType getType() {
+        return type;
+    }
+
+    /**
+     * Sets the event type to the given type.
+     *
+     * @param type
+     *            The type of this event.
+     */
+    void setType(final EventType type) {
+        this.type = type;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public long getOccurrenceTime() {
+        return occurrenceTime;
+    }
+
+    @Override
+    public String toString() {
+        return this.getClass().getSimpleName() + ": occurenceTime="
+                + new Date(occurrenceTime) + ", source=\"" + source
+                + "\", type=" + type.getTypeName() + ", message=\"" + message
+                + "\"";
+    }
+
+    /**
+     * Sets the occurrence time to the given time.
+     *
+     * @param occurrenceTime
+     *            The occurrence time of the event or of the corresponding log
+     *            statement.
+     */
+    void setOccurenceTime(final long occurrenceTime) {
+        this.occurrenceTime = occurrenceTime;
+    }
 }
diff --git a/src/org/torproject/puppetor/impl/EventManagerImpl.java b/src/org/torproject/puppetor/impl/EventManagerImpl.java
index 163e4d8..99f2ba2 100644
--- a/src/org/torproject/puppetor/impl/EventManagerImpl.java
+++ b/src/org/torproject/puppetor/impl/EventManagerImpl.java
@@ -38,693 +38,693 @@ import org.torproject.puppetor.NodeEventType;
  */
 public class EventManagerImpl implements EventManager {
 
-	/**
-	 * Registered event handlers for specific sources.
-	 */
-	private final Map<String, Set<EventListener>> eventHandlers;
-
-	/**
-	 * Registered event handlers for all sources.
-	 */
-	private final Set<EventListener> eventHandlersForAllSources;
-
-	/**
-	 * Logger for this event manager which is called "event." plus the name of
-	 * the network.
-	 */
-	private final Logger logger;
-
-	/**
-	 * Events observed so far.
-	 */
-	private final Map<String, List<Event>> observedEvents;
-
-	/**
-	 * Set of all registered event sources. This is required to ensure that
-	 * requests for events from a given source specify valid event sources.
-	 */
-	private final Set<String> eventSources;
-
-	/**
-	 * Creates a new <code>EventManagerImpl</code> for the network with name
-	 * <code>networkName</code> and initializes it.
-	 *
-	 * @param networkName
-	 *            Name of this event manager that is used as part of the logger
-	 *            name.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given <code>networkName</code> is either
-	 *             <code>null</code> or a zero-length string.
-	 */
-	EventManagerImpl(final String networkName) {
-
-		// check if networkName can be used as logger name
-		if (networkName == null || networkName.length() == 0) {
-			throw new IllegalArgumentException("Invalid networkName: "
-					+ networkName);
-		}
-
-		// create logger
-		logger = Logger.getLogger("event." + networkName);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "EventManagerImpl",
-				networkName);
-
-		// create data structures
-		observedEvents = new HashMap<String, List<Event>>();
-		eventHandlers = new HashMap<String, Set<EventListener>>();
-		eventHandlersForAllSources = new HashSet<EventListener>();
-		eventSources = new HashSet<String>();
-
-		// start thread to parse events
-		final Thread eventParseThread = new Thread() {
-			@Override
-			public void run() {
-				while (true) {
-					parseNextEvent();
-				}
-			}
-		};
-		eventParseThread.setDaemon(true);
-		eventParseThread.start();
-
-		// initialize event type patterns
-		initializeEventTypePatterns();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "EventManagerImpl");
-	}
-
-	public synchronized List<Event> addEventListener(final String source,
-			final EventListener listener) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addEventListener",
-				new Object[] { source, listener });
-
-		// check parameters
-		if (source == null || listener == null
-				|| !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "addEventListener", e);
-			throw e;
-		}
-
-		// if necessary, create new event listener set for source
-		if (!eventHandlers.containsKey(source)) {
-			eventHandlers.put(source, new HashSet<EventListener>());
-		}
-
-		// add listener
-		eventHandlers.get(source).add(listener);
-
-		// log change
-		logger.log(Level.FINE, "Added event listener for source " + source);
-
-		// log exiting and return
-		final List<Event> result = getEventHistory(source);
-		logger.exiting(this.getClass().getName(), "addEventListener", result);
-		return result;
-	}
-
-	public synchronized void addEventListener(final EventListener listener) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addEventListener",
-				new Object[] { listener });
-
-		// check parameters
-		if (listener == null) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "addEventListener", e);
-			throw e;
-		}
-
-		// add listener
-		eventHandlersForAllSources.add(listener);
-
-		// log change
-		logger.log(Level.FINE, "Added event listener for all sources.");
-
-		// log exiting and return
-		logger.exiting(this.getClass().getName(), "addEventListener");
-		return;
-	}
-
-	public synchronized List<Event> getEventHistory(final String source) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "getEventHistory", source);
-
-		// check parameter
-		if (source == null || !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "getEventHistory", e);
-			throw e;
-		}
-
-		// prepare result
-		final List<Event> result = new ArrayList<Event>();
-
-		// did we already observe events for this source?
-		if (observedEvents.containsKey(source)) {
-			// yes, add all events to result list
-			result.addAll(observedEvents.get(source));
-		}
-
-		// log exiting and return result
-		logger.exiting(this.getClass().getName(), "getEventHistory", result);
-		return result;
-	}
-
-	public synchronized boolean hasEventOccured(final String source,
-			final EventType type) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "hasEventOccured",
-				new Object[] { source, type });
-
-		// check parameters
-		if (source == null || type == null || !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "hasEventOccured", e);
-			throw e;
-		}
-
-		// determine result
-		boolean result = false;
-		if (observedEvents.containsKey(source)) {
-			for (final Event event : observedEvents.get(source)) {
-				if (event.getType() == type) {
-					result = true;
-					break;
-				}
-			}
-		}
-
-		// log exiting and return result
-		logger.exiting(this.getClass().getName(), "hasEventOccured", result);
-		return result;
-	}
-
-	/**
-	 * An ordered list of all log statements that are still unparsed.
-	 */
-	private final List<EventImpl> unparsedLogStatements =
-			new LinkedList<EventImpl>();
-
-	/**
-	 * Stores the occurrence of an unparsed Tor log events that might result in
-	 * an event. All such unparsed events are later parsed by a background
-	 * thread in invocation order. Then, the occurrence time and the event type
-	 * will be parsed from the log message; if the log message does not contain
-	 * anything of interest, the event will be discarded.
-	 *
-	 * @param source
-	 *            The event source.
-	 * @param logMessage
-	 *            The log message.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the source is unknown.
-	 */
-	synchronized void observeUnparsedEvent(final String source,
-			final String logMessage) {
-		unparsedLogStatements.add(new EventImpl(source, logMessage));
-		notifyAll();
-	}
-
-	/**
-	 * Add an internal event to the event queue.
-	 *
-	 * @param occurrenceTime
-	 *            The occurrence time of the event.
-	 * @param source
-	 *            The event source.
-	 * @param type
-	 *            The event type.
-	 * @param message
-	 *            The event message.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the source is unknown.
-	 */
-	public synchronized void observeInternalEvent(final long occurrenceTime,
-			final String source, final EventType type, final String message) {
-
-		if (!eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "observeInternalEvent",
-					e);
-			throw e;
-		}
-
-		unparsedLogStatements.add(new EventImpl(occurrenceTime, source, type,
-				message));
-		notifyAll();
-	}
-
-	/**
-	 * Parses a log statement coming from Tor and decides whether it is
-	 * interesting for us.
-	 */
-	void parseNextEvent() {
-
-		// wait for the next event in the queue
-		EventImpl event = null;
-		synchronized (this) {
-			while (unparsedLogStatements.isEmpty()) {
-				try {
-					wait();
-				} catch (final InterruptedException e) {}
-			}
-			event = unparsedLogStatements.remove(0);
-		}
-
-		// does the event contain a known source? if not, discard it
-		if (!eventSources.contains(event.getSource())) {
-			logger.log(Level.WARNING,
-					"Unknown event source while parsing an event: "
-							+ event.getSource());
-			return;
-		}
-
-		// does the event require parsing? if not, process immediately
-		if (event.getType() != null) {
-			observeEvent(event);
-		} else {
-			final String line = event.getMessage();
-
-			/*
-			 * the logging output of Tor does not contain a year component; put
-			 * in the year at the time of parsing (which happens approx. 10 ms
-			 * after the logging took place) in the good hope that this tool is
-			 * not run at midnight on New Year's Eve...
-			 */
-			final Calendar c = Calendar.getInstance();
-			final int currentYear = c.get(Calendar.YEAR);
-
-			// try to apply one of the event type patterns
-			for (final Entry<Pattern, EventType> entry : eventTypePatterns
-					.entrySet()) {
-				final Matcher matcher = entry.getKey().matcher(line);
-				if (matcher.find()) {
-					final SimpleDateFormat sdf =
-							new SimpleDateFormat("MMM dd HH:mm:ss.SSS",
-									Locale.US);
-					final Date logTime = sdf.parse(line, new ParsePosition(0));
-					c.setTimeInMillis(logTime.getTime());
-					c.set(Calendar.YEAR, currentYear);
-					final long t = c.getTimeInMillis();
-					event.setOccurenceTime(t);
-					event.setType(entry.getValue());
-					observeEvent(event);
-					break;
-				}
-			}
-		}
-	}
-
-	/**
-	 * Map of all patterns, that should be included when parsing log statements
-	 * coming from Tor, and the respective event types of the events that should
-	 * be fired.
-	 */
-	Map<Pattern, EventType> eventTypePatterns;
-
-	public synchronized void registerEventTypePattern(
-			final String patternString, final EventType eventType) {
-		eventTypePatterns.put(Pattern.compile(patternString), eventType);
-	}
-
-	/**
-	 * Initializes the parsing engine with the standard log message patterns
-	 * that should be included in current Tor. Any further patterns need to be
-	 * added by the test application manually.
-	 */
-	private void initializeEventTypePatterns() {
-		eventTypePatterns = new HashMap<Pattern, EventType>();
-		registerEventTypePattern("Opening Control listener on .*",
-				NodeEventType.NODE_CONTROL_PORT_OPENED);
-		registerEventTypePattern("Tor has successfully opened a circuit. "
-				+ "Looks like client functionality is working.",
-				NodeEventType.NODE_CIRCUIT_OPENED);
-		registerEventTypePattern(
-				"Established circuit .* as introduction "
-						+ "point for service .*",
-				HiddenServiceEventType.BOB_BUILT_INTRO_CIRC_SENDING_ESTABLISH_INTRO);
-		registerEventTypePattern("Received INTRO_ESTABLISHED cell on "
-				+ "circuit .* for service .*",
-				HiddenServiceEventType.BOB_INTRO_ESTABLISHED_RECEIVED);
-		registerEventTypePattern("Sending publish request for hidden "
-				+ "service .*", HiddenServiceEventType.BOB_SENDING_PUBLISH_DESC);
-		registerEventTypePattern("Uploaded rendezvous descriptor",
-				HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED);
-		registerEventTypePattern("Received INTRODUCE2 cell for service .* "
-				+ "on circ .*", HiddenServiceEventType.BOB_INTRODUCE2_RECEIVED);
-		registerEventTypePattern("Done building circuit .* to rendezvous "
-				+ "with cookie .* for service .*",
-				HiddenServiceEventType.BOB_BUILT_REND_CIRC_SENDING_RENDEZVOUS1);
-		registerEventTypePattern("begin is for rendezvous",
-				HiddenServiceEventType.BOB_APP_CONN_OPENED);
-		registerEventTypePattern("Got a hidden service request for ID '.*'",
-				HiddenServiceEventType.ALICE_ONION_REQUEST_RECEIVED);
-		registerEventTypePattern("Fetching rendezvous descriptor for "
-				+ "service .*", HiddenServiceEventType.ALICE_SENDING_FETCH_DESC);
-		registerEventTypePattern("Received rendezvous descriptor",
-				HiddenServiceEventType.ALICE_DESC_FETCHED_RECEIVED);
-		registerEventTypePattern(
-				"Sending an ESTABLISH_RENDEZVOUS cell",
-				HiddenServiceEventType.ALICE_BUILT_REND_CIRC_SENDING_ESTABLISH_RENDEZVOUS);
-		registerEventTypePattern("Got rendezvous ack. This circuit is now "
-				+ "ready for rendezvous",
-				HiddenServiceEventType.ALICE_RENDEZVOUS_ESTABLISHED_RECEIVED);
-		registerEventTypePattern("introcirc is open",
-				HiddenServiceEventType.ALICE_BUILT_INTRO_CIRC);
-		registerEventTypePattern("Sending an INTRODUCE1 cell",
-				HiddenServiceEventType.ALICE_SENDING_INTRODUCE1);
-		registerEventTypePattern("Received ack. Telling rend circ",
-				HiddenServiceEventType.ALICE_INTRODUCE_ACK_RECEIVED);
-		registerEventTypePattern(
-				"Got RENDEZVOUS2 cell from hidden service",
-				HiddenServiceEventType.ALICE_RENDEZVOUS2_RECEIVED_APP_CONN_OPENED);
-		registerEventTypePattern("Handling rendezvous descriptor post",
-				HiddenServiceEventType.DIR_PUBLISH_DESC_RECEIVED);
-		registerEventTypePattern("Handling rendezvous descriptor get",
-				HiddenServiceEventType.DIR_FETCH_DESC_RECEIVED);
-		registerEventTypePattern(
-				"Received an ESTABLISH_INTRO request on circuit .*",
-				HiddenServiceEventType.IPO_RECEIVED_ESTABLISH_INTRO_SENDING_INTRO_ESTABLISHED);
-		registerEventTypePattern(
-				"Received an INTRODUCE1 request on circuit .*",
-				HiddenServiceEventType.IPO_RECEIVED_INTRODUCE1_SENDING_INTRODUCE2_AND_INTRODUCE_ACK);
-		registerEventTypePattern(
-				"Received an ESTABLISH_RENDEZVOUS request on circuit .*",
-				HiddenServiceEventType.RPO_RECEIVED_ESTABLISH_RENDEZVOUS_SENDING_RENDEZVOUS_ESTABLISHED);
-		registerEventTypePattern(
-				"Got request for rendezvous from circuit .* to cookie .*",
-				HiddenServiceEventType.RPO_RECEIVING_RENDEZVOUS1_SENDING_RENDEZVOUS2);
-	}
-
-	/**
-	 * Stores the given <code>event</code> from <code>source</code> to the
-	 * event history and propagates its occurrence to all registered event
-	 * handlers.
-	 *
-	 * @param event
-	 *            The observed event.
-	 */
-	private synchronized void observeEvent(final Event event) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "observeEvent", event);
-
-		final String source = event.getSource();
-		logger.log(Level.FINE, "Observed event " + event + " from source "
-				+ source + "!");
-
-		// remember observed event
-		if (!observedEvents.containsKey(event.getSource())) {
-			observedEvents.put(source, new ArrayList<Event>());
-		}
-		observedEvents.get(source).add(event);
-
-		// notify waiting threads
-		notifyAll();
-
-		// inform event listeners
-		if (eventHandlers.containsKey(source)) {
-
-			// make a copy of the event handler set, because some event handlers
-			// might want to remove themselves from this set while handling the
-			// event
-			final Set<EventListener> copyOfEventHandlers =
-					new HashSet<EventListener>(eventHandlers.get(source));
-
-			for (final EventListener eventHandler : copyOfEventHandlers) {
-
-				logger.log(Level.FINE, "Informing event listener "
-						+ eventHandler + " about recently observed event "
-						+ event + " from source " + source + "!");
-				eventHandler.handleEvent(event);
-
-			}
-		}
-
-		// make a copy of the event handler set for all sources, because some
-		// event handlers might want to remove themselves from this set while
-		// handling the event
-		final Set<EventListener> copyOfEventHandlersForAllSources =
-				new HashSet<EventListener>(eventHandlersForAllSources);
-
-		for (final EventListener eventHandler : copyOfEventHandlersForAllSources) {
-
-			logger.log(Level.FINE, "Informing event listener " + eventHandler
-					+ " about recently observed event " + event
-					+ " from source " + source + "!");
-			eventHandler.handleEvent(event);
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "observeEvent");
-	}
-
-	public synchronized void removeEventListener(
-			final EventListener eventListener) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "removeEventListener",
-				eventListener);
-
-		// check parameters
-		if (eventListener == null) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger
-					.throwing(this.getClass().getName(), "removeEventListener",
-							e);
-			throw e;
-		}
-
-		// don't know to which source this listener has been added (may to more
-		// than one), so remove it from all possible sets
-		for (final Set<EventListener> set : eventHandlers.values()) {
-			if (set.remove(eventListener)) {
-				logger.log(Level.FINE, "Removed event listener!");
-			}
-		}
-
-		// remove from event listeners for all sources
-		if (eventHandlersForAllSources.remove(eventListener)) {
-			logger.log(Level.FINE, "Removed event listener!");
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "removeEventListener");
-	}
-
-	public synchronized void waitForAnyOccurence(final String source,
-			final EventType event) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "waitForAnyOccurence",
-				new Object[] { source, event });
-
-		// check parameters
-		if (source == null || event == null || !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger
-					.throwing(this.getClass().getName(), "waitForAnyOccurence",
-							e);
-			throw e;
-		}
-
-		// invoke overloaded method with maximumTimeToWaitInMillis of -1L which
-		// means to wait forever
-		waitForAnyOccurence(source, event, -1L);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "waitForAnyOccurence");
-
-	}
-
-	public synchronized boolean waitForAnyOccurence(final String source,
-			final EventType event, final long maximumTimeToWaitInMillis) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "waitForAnyOccurence",
-				new Object[] { source, event, maximumTimeToWaitInMillis });
-
-		// check parameters
-		if (source == null || event == null || !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger
-					.throwing(this.getClass().getName(), "waitForAnyOccurence",
-							e);
-			throw e;
-		}
-
-		// check if we have already observed the event
-		if (hasEventOccured(source, event)) {
-			logger.log(Level.FINE, "Waiting for any occurence of event "
-					+ event + " returned immediately!");
-			logger.exiting(this.getClass().getName(), "waitForAnyOccurence",
-					true);
-			return true;
-		}
-
-		// invoke method that waits for next occurence of the event
-		final boolean result =
-				waitForNextOccurence(source, event, maximumTimeToWaitInMillis);
-
-		// log exiting and return result
-		logger
-				.exiting(this.getClass().getName(), "waitForAnyOccurence",
-						result);
-		return result;
-	}
-
-	public synchronized void waitForNextOccurence(final String source,
-			final EventType event) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "waitForNextOccurence",
-				new Object[] { source, event });
-
-		// check parameters
-		if (source == null || event == null || !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "waitForNextOccurence",
-					e);
-			throw e;
-		}
-
-		// invoke overloaded method with maximumTimeToWaitInMillis of -1L which
-		// means to wait forever
-		waitForNextOccurence(source, event, -1L);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "waitForNextOccurence");
-	}
-
-	public synchronized boolean waitForNextOccurence(final String source,
-			final EventType type, final long maximumTimeToWaitInMillis) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "waitForNextOccurence",
-				new Object[] { source, type, maximumTimeToWaitInMillis });
-
-		// check parameters
-		if (source == null || type == null || !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "waitForNextOccurence",
-					e);
-			throw e;
-		}
-
-		// distinguish between negative waiting time (wait forever) and zero or
-		// positive waiting time
-		if (maximumTimeToWaitInMillis < 0) {
-
-			// wait forever
-			while (!hasEventOccured(source, type)) {
-
-				logger.log(Level.FINEST,
-						"We will wait infinetely for the next occurence of "
-								+ "event type " + type + " from source "
-								+ source + "...");
-				try {
-					wait();
-				} catch (final InterruptedException e) {
-					// don't handle
-				}
-
-				logger.log(Level.FINEST,
-						"We have been notified about an observed event while "
-								+ "waiting for events of type " + type
-								+ " from source " + source
-								+ "; need to check whether the observed event "
-								+ "is what we are looking for...");
-			}
-
-			logger.log(Level.FINE, "Waiting for occurence of event type "
-					+ type + " succeeded!");
-
-			// log exiting and return result
-			logger.exiting(this.getClass().getName(), "waitForNextOccurence",
-					true);
-			return true;
-
-		}
-
-		// wait for the given time at most
-		final long endOfTime =
-				System.currentTimeMillis() + maximumTimeToWaitInMillis;
-		long timeLeft = 0;
-		while (!hasEventOccured(source, type)
-				&& (timeLeft = endOfTime - System.currentTimeMillis()) > 0) {
-
-			logger.log(Level.FINEST, "We will wait for " + timeLeft
-					+ " milliseconds for the next occurence of event type "
-					+ type + " from source " + source + "...");
-
-			try {
-				wait(timeLeft);
-			} catch (final InterruptedException e) {
-				// don't handle
-			}
-
-			logger.log(Level.FINEST,
-					"We have been notified about an observed event while "
-							+ "waiting for events of type " + type
-							+ " from source " + source
-							+ "; need to check whether the observed event "
-							+ "is what we are looking for...");
-		}
-
-		// determine result
-		final boolean result = hasEventOccured(source, type);
-
-		logger.log(Level.FINE, "Waiting for next occurence of event type "
-				+ type + " from source " + source
-				+ (result ? " succeeded!" : " did not succeed!"));
-
-		// log exiting and return result
-		logger.exiting(this.getClass().getName(), "waitForNextOccurence",
-				result);
-		return result;
-	}
-
-	/**
-	 * Adds the given <code>source</code> as possible event source.
-	 *
-	 * @param source
-	 *            The name of the node, client, or server to add.
-	 * @throws IllegalArgumentException
-	 *             Thrown if there is already an event source with this name.
-	 */
-	void addEventSource(final String source) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addEventSource", source);
-
-		// check if source name is unique in this network
-		if (eventSources.contains(source)) {
-			final IllegalArgumentException e =
-					new IllegalArgumentException(
-							"There is already an event source with name "
-									+ source + " in this network!");
-			logger.throwing(this.getClass().getName(), "addEventSource", e);
-			throw e;
-		}
-
-		// add event source name
-		eventSources.add(source);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "addEventSource");
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName();
-	}
+    /**
+     * Registered event handlers for specific sources.
+     */
+    private final Map<String, Set<EventListener>> eventHandlers;
+
+    /**
+     * Registered event handlers for all sources.
+     */
+    private final Set<EventListener> eventHandlersForAllSources;
+
+    /**
+     * Logger for this event manager which is called "event." plus the name of
+     * the network.
+     */
+    private final Logger logger;
+
+    /**
+     * Events observed so far.
+     */
+    private final Map<String, List<Event>> observedEvents;
+
+    /**
+     * Set of all registered event sources. This is required to ensure that
+     * requests for events from a given source specify valid event sources.
+     */
+    private final Set<String> eventSources;
+
+    /**
+     * Creates a new <code>EventManagerImpl</code> for the network with name
+     * <code>networkName</code> and initializes it.
+     *
+     * @param networkName
+     *            Name of this event manager that is used as part of the logger
+     *            name.
+     * @throws IllegalArgumentException
+     *             Thrown if the given <code>networkName</code> is either
+     *             <code>null</code> or a zero-length string.
+     */
+    EventManagerImpl(final String networkName) {
+
+        // check if networkName can be used as logger name
+        if (networkName == null || networkName.length() == 0) {
+            throw new IllegalArgumentException("Invalid networkName: "
+                    + networkName);
+        }
+
+        // create logger
+        logger = Logger.getLogger("event." + networkName);
+
+        // log entering
+        logger.entering(this.getClass().getName(), "EventManagerImpl",
+                networkName);
+
+        // create data structures
+        observedEvents = new HashMap<String, List<Event>>();
+        eventHandlers = new HashMap<String, Set<EventListener>>();
+        eventHandlersForAllSources = new HashSet<EventListener>();
+        eventSources = new HashSet<String>();
+
+        // start thread to parse events
+        final Thread eventParseThread = new Thread() {
+            @Override
+            public void run() {
+                while (true) {
+                    parseNextEvent();
+                }
+            }
+        };
+        eventParseThread.setDaemon(true);
+        eventParseThread.start();
+
+        // initialize event type patterns
+        initializeEventTypePatterns();
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "EventManagerImpl");
+    }
+
+    public synchronized List<Event> addEventListener(final String source,
+            final EventListener listener) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "addEventListener",
+                new Object[] { source, listener });
+
+        // check parameters
+        if (source == null || listener == null
+                || !eventSources.contains(source)) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "addEventListener", e);
+            throw e;
+        }
+
+        // if necessary, create new event listener set for source
+        if (!eventHandlers.containsKey(source)) {
+            eventHandlers.put(source, new HashSet<EventListener>());
+        }
+
+        // add listener
+        eventHandlers.get(source).add(listener);
+
+        // log change
+        logger.log(Level.FINE, "Added event listener for source " + source);
+
+        // log exiting and return
+        final List<Event> result = getEventHistory(source);
+        logger.exiting(this.getClass().getName(), "addEventListener", result);
+        return result;
+    }
+
+    public synchronized void addEventListener(final EventListener listener) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "addEventListener",
+                new Object[] { listener });
+
+        // check parameters
+        if (listener == null) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "addEventListener", e);
+            throw e;
+        }
+
+        // add listener
+        eventHandlersForAllSources.add(listener);
+
+        // log change
+        logger.log(Level.FINE, "Added event listener for all sources.");
+
+        // log exiting and return
+        logger.exiting(this.getClass().getName(), "addEventListener");
+        return;
+    }
+
+    public synchronized List<Event> getEventHistory(final String source) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "getEventHistory", source);
+
+        // check parameter
+        if (source == null || !eventSources.contains(source)) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "getEventHistory", e);
+            throw e;
+        }
+
+        // prepare result
+        final List<Event> result = new ArrayList<Event>();
+
+        // did we already observe events for this source?
+        if (observedEvents.containsKey(source)) {
+            // yes, add all events to result list
+            result.addAll(observedEvents.get(source));
+        }
+
+        // log exiting and return result
+        logger.exiting(this.getClass().getName(), "getEventHistory", result);
+        return result;
+    }
+
+    public synchronized boolean hasEventOccured(final String source,
+            final EventType type) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "hasEventOccured",
+                new Object[] { source, type });
+
+        // check parameters
+        if (source == null || type == null || !eventSources.contains(source)) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "hasEventOccured", e);
+            throw e;
+        }
+
+        // determine result
+        boolean result = false;
+        if (observedEvents.containsKey(source)) {
+            for (final Event event : observedEvents.get(source)) {
+                if (event.getType() == type) {
+                    result = true;
+                    break;
+                }
+            }
+        }
+
+        // log exiting and return result
+        logger.exiting(this.getClass().getName(), "hasEventOccured", result);
+        return result;
+    }
+
+    /**
+     * An ordered list of all log statements that are still unparsed.
+     */
+    private final List<EventImpl> unparsedLogStatements =
+            new LinkedList<EventImpl>();
+
+    /**
+     * Stores the occurrence of an unparsed Tor log events that might result in
+     * an event. All such unparsed events are later parsed by a background
+     * thread in invocation order. Then, the occurrence time and the event type
+     * will be parsed from the log message; if the log message does not contain
+     * anything of interest, the event will be discarded.
+     *
+     * @param source
+     *            The event source.
+     * @param logMessage
+     *            The log message.
+     * @throws IllegalArgumentException
+     *             Thrown if the source is unknown.
+     */
+    synchronized void observeUnparsedEvent(final String source,
+            final String logMessage) {
+        unparsedLogStatements.add(new EventImpl(source, logMessage));
+        notifyAll();
+    }
+
+    /**
+     * Add an internal event to the event queue.
+     *
+     * @param occurrenceTime
+     *            The occurrence time of the event.
+     * @param source
+     *            The event source.
+     * @param type
+     *            The event type.
+     * @param message
+     *            The event message.
+     * @throws IllegalArgumentException
+     *             Thrown if the source is unknown.
+     */
+    public synchronized void observeInternalEvent(final long occurrenceTime,
+            final String source, final EventType type, final String message) {
+
+        if (!eventSources.contains(source)) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "observeInternalEvent",
+                    e);
+            throw e;
+        }
+
+        unparsedLogStatements.add(new EventImpl(occurrenceTime, source, type,
+                message));
+        notifyAll();
+    }
+
+    /**
+     * Parses a log statement coming from Tor and decides whether it is
+     * interesting for us.
+     */
+    void parseNextEvent() {
+
+        // wait for the next event in the queue
+        EventImpl event = null;
+        synchronized (this) {
+            while (unparsedLogStatements.isEmpty()) {
+                try {
+                    wait();
+                } catch (final InterruptedException e) {}
+            }
+            event = unparsedLogStatements.remove(0);
+        }
+
+        // does the event contain a known source? if not, discard it
+        if (!eventSources.contains(event.getSource())) {
+            logger.log(Level.WARNING,
+                    "Unknown event source while parsing an event: "
+                            + event.getSource());
+            return;
+        }
+
+        // does the event require parsing? if not, process immediately
+        if (event.getType() != null) {
+            observeEvent(event);
+        } else {
+            final String line = event.getMessage();
+
+            /*
+             * the logging output of Tor does not contain a year component; put
+             * in the year at the time of parsing (which happens approx. 10 ms
+             * after the logging took place) in the good hope that this tool is
+             * not run at midnight on New Year's Eve...
+             */
+            final Calendar c = Calendar.getInstance();
+            final int currentYear = c.get(Calendar.YEAR);
+
+            // try to apply one of the event type patterns
+            for (final Entry<Pattern, EventType> entry : eventTypePatterns
+                    .entrySet()) {
+                final Matcher matcher = entry.getKey().matcher(line);
+                if (matcher.find()) {
+                    final SimpleDateFormat sdf =
+                            new SimpleDateFormat("MMM dd HH:mm:ss.SSS",
+                                    Locale.US);
+                    final Date logTime = sdf.parse(line, new ParsePosition(0));
+                    c.setTimeInMillis(logTime.getTime());
+                    c.set(Calendar.YEAR, currentYear);
+                    final long t = c.getTimeInMillis();
+                    event.setOccurenceTime(t);
+                    event.setType(entry.getValue());
+                    observeEvent(event);
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Map of all patterns, that should be included when parsing log statements
+     * coming from Tor, and the respective event types of the events that should
+     * be fired.
+     */
+    Map<Pattern, EventType> eventTypePatterns;
+
+    public synchronized void registerEventTypePattern(
+            final String patternString, final EventType eventType) {
+        eventTypePatterns.put(Pattern.compile(patternString), eventType);
+    }
+
+    /**
+     * Initializes the parsing engine with the standard log message patterns
+     * that should be included in current Tor. Any further patterns need to be
+     * added by the test application manually.
+     */
+    private void initializeEventTypePatterns() {
+        eventTypePatterns = new HashMap<Pattern, EventType>();
+        registerEventTypePattern("Opening Control listener on .*",
+                NodeEventType.NODE_CONTROL_PORT_OPENED);
+        registerEventTypePattern("Tor has successfully opened a circuit. "
+                + "Looks like client functionality is working.",
+                NodeEventType.NODE_CIRCUIT_OPENED);
+        registerEventTypePattern(
+                "Established circuit .* as introduction "
+                        + "point for service .*",
+                HiddenServiceEventType.BOB_BUILT_INTRO_CIRC_SENDING_ESTABLISH_INTRO);
+        registerEventTypePattern("Received INTRO_ESTABLISHED cell on "
+                + "circuit .* for service .*",
+                HiddenServiceEventType.BOB_INTRO_ESTABLISHED_RECEIVED);
+        registerEventTypePattern("Sending publish request for hidden "
+                + "service .*", HiddenServiceEventType.BOB_SENDING_PUBLISH_DESC);
+        registerEventTypePattern("Uploaded rendezvous descriptor",
+                HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED);
+        registerEventTypePattern("Received INTRODUCE2 cell for service .* "
+                + "on circ .*", HiddenServiceEventType.BOB_INTRODUCE2_RECEIVED);
+        registerEventTypePattern("Done building circuit .* to rendezvous "
+                + "with cookie .* for service .*",
+                HiddenServiceEventType.BOB_BUILT_REND_CIRC_SENDING_RENDEZVOUS1);
+        registerEventTypePattern("begin is for rendezvous",
+                HiddenServiceEventType.BOB_APP_CONN_OPENED);
+        registerEventTypePattern("Got a hidden service request for ID '.*'",
+                HiddenServiceEventType.ALICE_ONION_REQUEST_RECEIVED);
+        registerEventTypePattern("Fetching rendezvous descriptor for "
+                + "service .*", HiddenServiceEventType.ALICE_SENDING_FETCH_DESC);
+        registerEventTypePattern("Received rendezvous descriptor",
+                HiddenServiceEventType.ALICE_DESC_FETCHED_RECEIVED);
+        registerEventTypePattern(
+                "Sending an ESTABLISH_RENDEZVOUS cell",
+                HiddenServiceEventType.ALICE_BUILT_REND_CIRC_SENDING_ESTABLISH_RENDEZVOUS);
+        registerEventTypePattern("Got rendezvous ack. This circuit is now "
+                + "ready for rendezvous",
+                HiddenServiceEventType.ALICE_RENDEZVOUS_ESTABLISHED_RECEIVED);
+        registerEventTypePattern("introcirc is open",
+                HiddenServiceEventType.ALICE_BUILT_INTRO_CIRC);
+        registerEventTypePattern("Sending an INTRODUCE1 cell",
+                HiddenServiceEventType.ALICE_SENDING_INTRODUCE1);
+        registerEventTypePattern("Received ack. Telling rend circ",
+                HiddenServiceEventType.ALICE_INTRODUCE_ACK_RECEIVED);
+        registerEventTypePattern(
+                "Got RENDEZVOUS2 cell from hidden service",
+                HiddenServiceEventType.ALICE_RENDEZVOUS2_RECEIVED_APP_CONN_OPENED);
+        registerEventTypePattern("Handling rendezvous descriptor post",
+                HiddenServiceEventType.DIR_PUBLISH_DESC_RECEIVED);
+        registerEventTypePattern("Handling rendezvous descriptor get",
+                HiddenServiceEventType.DIR_FETCH_DESC_RECEIVED);
+        registerEventTypePattern(
+                "Received an ESTABLISH_INTRO request on circuit .*",
+                HiddenServiceEventType.IPO_RECEIVED_ESTABLISH_INTRO_SENDING_INTRO_ESTABLISHED);
+        registerEventTypePattern(
+                "Received an INTRODUCE1 request on circuit .*",
+                HiddenServiceEventType.IPO_RECEIVED_INTRODUCE1_SENDING_INTRODUCE2_AND_INTRODUCE_ACK);
+        registerEventTypePattern(
+                "Received an ESTABLISH_RENDEZVOUS request on circuit .*",
+                HiddenServiceEventType.RPO_RECEIVED_ESTABLISH_RENDEZVOUS_SENDING_RENDEZVOUS_ESTABLISHED);
+        registerEventTypePattern(
+                "Got request for rendezvous from circuit .* to cookie .*",
+                HiddenServiceEventType.RPO_RECEIVING_RENDEZVOUS1_SENDING_RENDEZVOUS2);
+    }
+
+    /**
+     * Stores the given <code>event</code> from <code>source</code> to the
+     * event history and propagates its occurrence to all registered event
+     * handlers.
+     *
+     * @param event
+     *            The observed event.
+     */
+    private synchronized void observeEvent(final Event event) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "observeEvent", event);
+
+        final String source = event.getSource();
+        logger.log(Level.FINE, "Observed event " + event + " from source "
+                + source + "!");
+
+        // remember observed event
+        if (!observedEvents.containsKey(event.getSource())) {
+            observedEvents.put(source, new ArrayList<Event>());
+        }
+        observedEvents.get(source).add(event);
+
+        // notify waiting threads
+        notifyAll();
+
+        // inform event listeners
+        if (eventHandlers.containsKey(source)) {
+
+            // make a copy of the event handler set, because some event handlers
+            // might want to remove themselves from this set while handling the
+            // event
+            final Set<EventListener> copyOfEventHandlers =
+                    new HashSet<EventListener>(eventHandlers.get(source));
+
+            for (final EventListener eventHandler : copyOfEventHandlers) {
+
+                logger.log(Level.FINE, "Informing event listener "
+                        + eventHandler + " about recently observed event "
+                        + event + " from source " + source + "!");
+                eventHandler.handleEvent(event);
+
+            }
+        }
+
+        // make a copy of the event handler set for all sources, because some
+        // event handlers might want to remove themselves from this set while
+        // handling the event
+        final Set<EventListener> copyOfEventHandlersForAllSources =
+                new HashSet<EventListener>(eventHandlersForAllSources);
+
+        for (final EventListener eventHandler : copyOfEventHandlersForAllSources) {
+
+            logger.log(Level.FINE, "Informing event listener " + eventHandler
+                    + " about recently observed event " + event
+                    + " from source " + source + "!");
+            eventHandler.handleEvent(event);
+        }
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "observeEvent");
+    }
+
+    public synchronized void removeEventListener(
+            final EventListener eventListener) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "removeEventListener",
+                eventListener);
+
+        // check parameters
+        if (eventListener == null) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger
+                    .throwing(this.getClass().getName(), "removeEventListener",
+                            e);
+            throw e;
+        }
+
+        // don't know to which source this listener has been added (may to more
+        // than one), so remove it from all possible sets
+        for (final Set<EventListener> set : eventHandlers.values()) {
+            if (set.remove(eventListener)) {
+                logger.log(Level.FINE, "Removed event listener!");
+            }
+        }
+
+        // remove from event listeners for all sources
+        if (eventHandlersForAllSources.remove(eventListener)) {
+            logger.log(Level.FINE, "Removed event listener!");
+        }
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "removeEventListener");
+    }
+
+    public synchronized void waitForAnyOccurence(final String source,
+            final EventType event) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "waitForAnyOccurence",
+                new Object[] { source, event });
+
+        // check parameters
+        if (source == null || event == null || !eventSources.contains(source)) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger
+                    .throwing(this.getClass().getName(), "waitForAnyOccurence",
+                            e);
+            throw e;
+        }
+
+        // invoke overloaded method with maximumTimeToWaitInMillis of -1L which
+        // means to wait forever
+        waitForAnyOccurence(source, event, -1L);
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "waitForAnyOccurence");
+
+    }
+
+    public synchronized boolean waitForAnyOccurence(final String source,
+            final EventType event, final long maximumTimeToWaitInMillis) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "waitForAnyOccurence",
+                new Object[] { source, event, maximumTimeToWaitInMillis });
+
+        // check parameters
+        if (source == null || event == null || !eventSources.contains(source)) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger
+                    .throwing(this.getClass().getName(), "waitForAnyOccurence",
+                            e);
+            throw e;
+        }
+
+        // check if we have already observed the event
+        if (hasEventOccured(source, event)) {
+            logger.log(Level.FINE, "Waiting for any occurence of event "
+                    + event + " returned immediately!");
+            logger.exiting(this.getClass().getName(), "waitForAnyOccurence",
+                    true);
+            return true;
+        }
+
+        // invoke method that waits for next occurence of the event
+        final boolean result =
+                waitForNextOccurence(source, event, maximumTimeToWaitInMillis);
+
+        // log exiting and return result
+        logger
+                .exiting(this.getClass().getName(), "waitForAnyOccurence",
+                        result);
+        return result;
+    }
+
+    public synchronized void waitForNextOccurence(final String source,
+            final EventType event) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "waitForNextOccurence",
+                new Object[] { source, event });
+
+        // check parameters
+        if (source == null || event == null || !eventSources.contains(source)) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "waitForNextOccurence",
+                    e);
+            throw e;
+        }
+
+        // invoke overloaded method with maximumTimeToWaitInMillis of -1L which
+        // means to wait forever
+        waitForNextOccurence(source, event, -1L);
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "waitForNextOccurence");
+    }
+
+    public synchronized boolean waitForNextOccurence(final String source,
+            final EventType type, final long maximumTimeToWaitInMillis) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "waitForNextOccurence",
+                new Object[] { source, type, maximumTimeToWaitInMillis });
+
+        // check parameters
+        if (source == null || type == null || !eventSources.contains(source)) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "waitForNextOccurence",
+                    e);
+            throw e;
+        }
+
+        // distinguish between negative waiting time (wait forever) and zero or
+        // positive waiting time
+        if (maximumTimeToWaitInMillis < 0) {
+
+            // wait forever
+            while (!hasEventOccured(source, type)) {
+
+                logger.log(Level.FINEST,
+                        "We will wait infinetely for the next occurence of "
+                                + "event type " + type + " from source "
+                                + source + "...");
+                try {
+                    wait();
+                } catch (final InterruptedException e) {
+                    // don't handle
+                }
+
+                logger.log(Level.FINEST,
+                        "We have been notified about an observed event while "
+                                + "waiting for events of type " + type
+                                + " from source " + source
+                                + "; need to check whether the observed event "
+                                + "is what we are looking for...");
+            }
+
+            logger.log(Level.FINE, "Waiting for occurence of event type "
+                    + type + " succeeded!");
+
+            // log exiting and return result
+            logger.exiting(this.getClass().getName(), "waitForNextOccurence",
+                    true);
+            return true;
+
+        }
+
+        // wait for the given time at most
+        final long endOfTime =
+                System.currentTimeMillis() + maximumTimeToWaitInMillis;
+        long timeLeft = 0;
+        while (!hasEventOccured(source, type)
+                && (timeLeft = endOfTime - System.currentTimeMillis()) > 0) {
+
+            logger.log(Level.FINEST, "We will wait for " + timeLeft
+                    + " milliseconds for the next occurence of event type "
+                    + type + " from source " + source + "...");
+
+            try {
+                wait(timeLeft);
+            } catch (final InterruptedException e) {
+                // don't handle
+            }
+
+            logger.log(Level.FINEST,
+                    "We have been notified about an observed event while "
+                            + "waiting for events of type " + type
+                            + " from source " + source
+                            + "; need to check whether the observed event "
+                            + "is what we are looking for...");
+        }
+
+        // determine result
+        final boolean result = hasEventOccured(source, type);
+
+        logger.log(Level.FINE, "Waiting for next occurence of event type "
+                + type + " from source " + source
+                + (result ? " succeeded!" : " did not succeed!"));
+
+        // log exiting and return result
+        logger.exiting(this.getClass().getName(), "waitForNextOccurence",
+                result);
+        return result;
+    }
+
+    /**
+     * Adds the given <code>source</code> as possible event source.
+     *
+     * @param source
+     *            The name of the node, client, or server to add.
+     * @throws IllegalArgumentException
+     *             Thrown if there is already an event source with this name.
+     */
+    void addEventSource(final String source) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "addEventSource", source);
+
+        // check if source name is unique in this network
+        if (eventSources.contains(source)) {
+            final IllegalArgumentException e =
+                    new IllegalArgumentException(
+                            "There is already an event source with name "
+                                    + source + " in this network!");
+            logger.throwing(this.getClass().getName(), "addEventSource", e);
+            throw e;
+        }
+
+        // add event source name
+        eventSources.add(source);
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "addEventSource");
+    }
+
+    @Override
+    public String toString() {
+        return this.getClass().getSimpleName();
+    }
 }
diff --git a/src/org/torproject/puppetor/impl/HiddenServiceImpl.java b/src/org/torproject/puppetor/impl/HiddenServiceImpl.java
index 35fe787..1ece2e4 100644
--- a/src/org/torproject/puppetor/impl/HiddenServiceImpl.java
+++ b/src/org/torproject/puppetor/impl/HiddenServiceImpl.java
@@ -23,145 +23,145 @@ import org.torproject.puppetor.PuppeTorException;
  */
 public class HiddenServiceImpl implements HiddenService {
 
-	/**
-	 * Logger for this node which is called "hidserv." plus the name of this
-	 * hidden service.
-	 */
-	protected Logger logger;
-
-	/**
-	 * The node at which this hidden service is configured.
-	 */
-	private final ProxyNodeImpl node;
-
-	/**
-	 * Name of the hidden service that will be used as name for the hidden
-	 * service directory.
-	 */
-	private final String serviceName;
-
-	/**
-	 * The TCP port on which the service will be available for requests.
-	 */
-	private final int servicePort;
-
-	/**
-	 * The virtual TCP port that this hidden service runs on as it is announced
-	 * to clients.
-	 */
-	private final int virtualPort;
-
-	/**
-	 * Adds the entries for a hidden service to the configuration of this node.
-	 *
-	 * Creates a new <code>HiddenServiceImpl</code>.
-	 *
-	 * @param node
-	 *            The node at which this hidden service is configured.
-	 * @param serviceName
-	 *            Name of the hidden service that will be used as name for the
-	 *            hidden service directory. May neither be <code>null</code>
-	 *            or a zero-length string.
-	 * @param servicePort
-	 *            The TCP port on which the service will be available for
-	 *            requests. This can, but need not be different from the virtual
-	 *            port that is announced to clients. May not be negative or
-	 *            greater than 65535.
-	 * @param virtualPort
-	 *            The virtual TCP port that this hidden service runs on as it is
-	 *            announced to clients. May not be negative or greater than
-	 *            65535.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	HiddenServiceImpl(final ProxyNodeImpl node, final String serviceName,
-			final int servicePort, final int virtualPort) {
-
-		// check if networkName can be used as logger name
-		if (serviceName == null || serviceName.length() == 0) {
-			throw new IllegalArgumentException("Invalid serviceName: "
-					+ serviceName);
-		}
-
-		// create logger
-		logger = Logger.getLogger("hidserv." + serviceName);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "HiddenServiceImpl",
-				new Object[] { node, serviceName, servicePort, virtualPort });
-
-		// check parameters
-		if (servicePort < 0 || servicePort > 65535 || virtualPort < 0
-				|| virtualPort > 65535) {
-			logger.log(Level.SEVERE,
-					"Illegal argument when adding hidden service!");
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "addHiddenService", e);
-			throw e;
-		}
-
-		// store parameter values
-		this.node = node;
-		this.serviceName = serviceName;
-		this.servicePort = servicePort;
-		this.virtualPort = virtualPort;
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "HiddenServiceImpl");
-	}
-
-	public String determineOnionAddress() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "getOnionAddress");
-
-		// check if hidden service directory exists
-		final File hiddenServiceFile =
-				new File(node.getWorkingDir().getAbsolutePath()
-						+ File.separator + serviceName + File.separator
-						+ "hostname");
-		if (!hiddenServiceFile.exists()) {
-			logger.log(Level.SEVERE,
-					"Hidden service directory or hostname file does not exist: "
-							+ hiddenServiceFile.getAbsolutePath());
-
-			final PuppeTorException e =
-					new PuppeTorException(
-							"Hidden service directory or hostname file does not exist: "
-									+ hiddenServiceFile.getAbsolutePath());
-			logger.throwing(this.getClass().getName(), "getOnionAddress", e);
-			throw e;
-		}
-
-		// read hostname from file
-		String address = null;
-		try {
-			final BufferedReader br =
-					new BufferedReader(new FileReader(hiddenServiceFile));
-			address = br.readLine();
-			br.close();
-		} catch (final IOException e) {
-			final PuppeTorException ex =
-					new PuppeTorException("Could not read hostname file!", e);
-			logger.throwing(this.getClass().getName(), "getOnionAddress", ex);
-			throw ex;
-		}
-
-		// log exiting and return address
-		logger.exiting(this.getClass().getName(), "getOnionAddress", address);
-		return address;
-	}
-
-	public String getServiceName() {
-		return serviceName;
-	}
-
-	public int getServicePort() {
-		return servicePort;
-	}
-
-	public int getVirtualPort() {
-		return virtualPort;
-	}
+    /**
+     * Logger for this node which is called "hidserv." plus the name of this
+     * hidden service.
+     */
+    protected Logger logger;
+
+    /**
+     * The node at which this hidden service is configured.
+     */
+    private final ProxyNodeImpl node;
+
+    /**
+     * Name of the hidden service that will be used as name for the hidden
+     * service directory.
+     */
+    private final String serviceName;
+
+    /**
+     * The TCP port on which the service will be available for requests.
+     */
+    private final int servicePort;
+
+    /**
+     * The virtual TCP port that this hidden service runs on as it is announced
+     * to clients.
+     */
+    private final int virtualPort;
+
+    /**
+     * Adds the entries for a hidden service to the configuration of this node.
+     *
+     * Creates a new <code>HiddenServiceImpl</code>.
+     *
+     * @param node
+     *            The node at which this hidden service is configured.
+     * @param serviceName
+     *            Name of the hidden service that will be used as name for the
+     *            hidden service directory. May neither be <code>null</code>
+     *            or a zero-length string.
+     * @param servicePort
+     *            The TCP port on which the service will be available for
+     *            requests. This can, but need not be different from the virtual
+     *            port that is announced to clients. May not be negative or
+     *            greater than 65535.
+     * @param virtualPort
+     *            The virtual TCP port that this hidden service runs on as it is
+     *            announced to clients. May not be negative or greater than
+     *            65535.
+     * @throws IllegalArgumentException
+     *             Thrown if an invalid value is given for either of the
+     *             parameters.
+     */
+    HiddenServiceImpl(final ProxyNodeImpl node, final String serviceName,
+            final int servicePort, final int virtualPort) {
+
+        // check if networkName can be used as logger name
+        if (serviceName == null || serviceName.length() == 0) {
+            throw new IllegalArgumentException("Invalid serviceName: "
+                    + serviceName);
+        }
+
+        // create logger
+        logger = Logger.getLogger("hidserv." + serviceName);
+
+        // log entering
+        logger.entering(this.getClass().getName(), "HiddenServiceImpl",
+                new Object[] { node, serviceName, servicePort, virtualPort });
+
+        // check parameters
+        if (servicePort < 0 || servicePort > 65535 || virtualPort < 0
+                || virtualPort > 65535) {
+            logger.log(Level.SEVERE,
+                    "Illegal argument when adding hidden service!");
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "addHiddenService", e);
+            throw e;
+        }
+
+        // store parameter values
+        this.node = node;
+        this.serviceName = serviceName;
+        this.servicePort = servicePort;
+        this.virtualPort = virtualPort;
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "HiddenServiceImpl");
+    }
+
+    public String determineOnionAddress() throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "getOnionAddress");
+
+        // check if hidden service directory exists
+        final File hiddenServiceFile =
+                new File(node.getWorkingDir().getAbsolutePath()
+                        + File.separator + serviceName + File.separator
+                        + "hostname");
+        if (!hiddenServiceFile.exists()) {
+            logger.log(Level.SEVERE,
+                    "Hidden service directory or hostname file does not exist: "
+                            + hiddenServiceFile.getAbsolutePath());
+
+            final PuppeTorException e =
+                    new PuppeTorException(
+                            "Hidden service directory or hostname file does not exist: "
+                                    + hiddenServiceFile.getAbsolutePath());
+            logger.throwing(this.getClass().getName(), "getOnionAddress", e);
+            throw e;
+        }
+
+        // read hostname from file
+        String address = null;
+        try {
+            final BufferedReader br =
+                    new BufferedReader(new FileReader(hiddenServiceFile));
+            address = br.readLine();
+            br.close();
+        } catch (final IOException e) {
+            final PuppeTorException ex =
+                    new PuppeTorException("Could not read hostname file!", e);
+            logger.throwing(this.getClass().getName(), "getOnionAddress", ex);
+            throw ex;
+        }
+
+        // log exiting and return address
+        logger.exiting(this.getClass().getName(), "getOnionAddress", address);
+        return address;
+    }
+
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    public int getServicePort() {
+        return servicePort;
+    }
+
+    public int getVirtualPort() {
+        return virtualPort;
+    }
 }
diff --git a/src/org/torproject/puppetor/impl/NetworkImpl.java b/src/org/torproject/puppetor/impl/NetworkImpl.java
index 8bda03d..a0eb0c8 100644
--- a/src/org/torproject/puppetor/impl/NetworkImpl.java
+++ b/src/org/torproject/puppetor/impl/NetworkImpl.java
@@ -37,1047 +37,1047 @@ import org.torproject.puppetor.ServerApplication;
  */
 public class NetworkImpl implements Network {
 
-	/**
-	 * Internal thread class that is used to start Tor processes in parallel.
-	 */
-	private class NodeStarter extends Thread {
-
-		/**
-		 * The exception, if one is caught while trying to start the node.
-		 */
-		Exception caughtException;
-
-		/**
-		 * The maximum time to wait for the Tor process to start in
-		 * milliseconds.
-		 */
-		private final long maximumTimeToWaitInMillis;
-
-		/**
-		 * The node for which the Tor process shall be started.
-		 */
-		private final ProxyNode node;
-
-		/**
-		 * Flag that denotes whether starting the Tor process was successful.
-		 */
-		boolean success = false;
-
-		/**
-		 * Creates a new <code>NodeStarter</code> for node <code>node</code>
-		 * that will wait for <code>maximumTimeToWaitInMillis</code>
-		 * milliseconds to start a Tor process, but that is not started, yet.
-		 *
-		 * @param node
-		 *            The node for which the Tor process shall be started.
-		 * @param maximumTimeToWaitInMillis
-		 *            The maximum time to wait for the Tor process to start in
-		 *            milliseconds.
-		 */
-		NodeStarter(final ProxyNode node, final long maximumTimeToWaitInMillis) {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "NodeStarter",
-					new Object[] { node, maximumTimeToWaitInMillis });
-
-			// store parameters
-			this.node = node;
-			this.maximumTimeToWaitInMillis = maximumTimeToWaitInMillis;
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "NodeStarter");
-		}
-
-		@Override
-		public void run() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "run");
-
-			try {
-				// try to start node
-				success = node.startNode(maximumTimeToWaitInMillis);
-			} catch (final PuppeTorException e) {
-				logger.log(Level.SEVERE,
-						"Caught an exception while starting node "
-								+ node.toString() + "!");
-				caughtException = e;
-			}
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "run");
-		}
-	}
-
-	/**
-	 * Event manager to which all events concerning this network are notified.
-	 */
-	private final EventManagerImpl eventManager;
-
-	/**
-	 * Logger for this network which is called "network." plus the name of this
-	 * network.
-	 */
-	private final Logger logger;
-
-	/**
-	 * Contains the name of this network configuration which is the String
-	 * conversion of System.currentTimeMillis() of the network creation time.
-	 */
-	private final String networkName;
-
-	/**
-	 * All nodes contained in this network.
-	 */
-	private final Map<String, ProxyNode> nodes;
-
-	/**
-	 * Directory that contains status information of all nodes contained in this
-	 * network.
-	 */
-	private final File workingDir;
-
-	/**
-	 * The counter for automatically assigned port numbers created by this
-	 * <code>Network</code>.
-	 */
-	private int portCounter = 7000;
-
-	/**
-	 * Creates an initially unpopulated Tor network and creates a new working
-	 * directory for it at test-env/randomTestID/.
-	 *
-	 * @param networkName
-	 *            Name of this network configuration. May neither be
-	 *            <code>null</code> or a zero-length string.
-	 * @param startPort
-	 *            The initial value for automatically assigned port numbers of
-	 *            nodes created by this <code>Network</code>; must be a value
-	 *            between 1024 and 65535.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given <code>networkName</code> is either
-	 *             <code>null</code> or a zero-length string, or if an illegal
-	 *             number is given for <code>startPort</code>.
-	 */
-	public NetworkImpl(final String networkName, final int startPort) {
-		// initialize using overloaded constructor
-		this(networkName);
-
-		// check if start port is valid
-		if (startPort < 1024 || startPort > 65535) {
-			throw new IllegalArgumentException("Invalid startPort: "
-					+ startPort);
-		}
-
-		// remember parameter
-		portCounter = startPort;
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "NetworkImpl");
-	}
-
-	/**
-	 * Creates an initially unpopulated Tor network and creates a new working
-	 * directory for it at test-env/randomTestID/.
-	 *
-	 * @param networkName
-	 *            Name of this network configuration. May neither be
-	 *            <code>null</code> or a zero-length string.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given <code>networkName</code> is either
-	 *             <code>null</code> or a zero-length string.
-	 */
-	public NetworkImpl(final String networkName) {
-
-		// check if networkName can be used as logger name
-		if (networkName == null || networkName.length() == 0) {
-			throw new IllegalArgumentException("Invalid networkName: "
-					+ networkName);
-		}
-
-		// create logger
-		logger = Logger.getLogger("network." + networkName);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "NetworkImpl", networkName);
-
-		// TODO is this necessary?!
-		logger.setLevel(Level.ALL);
-
-		// remember parameter
-		this.networkName = networkName;
-
-		// create working directory
-		workingDir = new File("test-env/" + System.currentTimeMillis());
-		workingDir.mkdirs();
-		logger.log(Level.FINE, "Created working directory \""
-				+ workingDir.getAbsolutePath() + "\"");
-
-		// TODO if we want to log to file, set this... somehow...
-		// this.logFile = new File(this.workingDir.getAbsolutePath()
-		// + "\\events.log");
-
-		// initialize data structures
-		nodes = new ConcurrentHashMap<String, ProxyNode>();
-
-		// create event manager
-		eventManager = new EventManagerImpl(this.networkName);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "NetworkImpl");
-	}
-
-	/**
-	 * Returns whether all nodes in this network are up, or not.
-	 *
-	 * @return <code>true</code> if all nodes are up, <code>false</code> if
-	 *         at least one node is not up.
-	 */
-	private boolean allNodesUp() {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "allNodesUp");
-
-		// fail on first node that is not up
-		for (final ProxyNode node : nodes.values()) {
-			if (!eventManager.hasEventOccured(node.getNodeName(),
-					NodeEventType.NODE_CIRCUIT_OPENED)) {
-
-				// log exiting and return false
-				logger.exiting(this.getClass().getName(), "allNodesUp");
-				return false;
-			}
-		}
-
-		// log exiting and return true
-		logger.exiting(this.getClass().getName(), "allNodesUp");
-		return true;
-	}
-
-	public void configureAsPrivateNetwork() throws PuppeTorException {
-		// log entering
-		logger.entering(this.getClass().getName(), "configureAsPrivateNetwork");
-
-		// read DirServer strings for all directories
-		final List<String> authorizedDirectoriesFingerprints =
-				new ArrayList<String>();
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof DirectoryNode) {
-				final DirectoryNode dirNode = (DirectoryNode) node;
-				authorizedDirectoriesFingerprints.add(dirNode
-						.getDirServerString());
-			}
-		}
-
-		this.configureAsPartOfPrivateNetwork(authorizedDirectoriesFingerprints);
-
-		// read fingerprints for all directories and routers
-		final HashSet<String> approvedRoutersFingerprints =
-				new HashSet<String>();
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof RouterNode) {
-				final RouterNode routerOrDirNode = (RouterNode) node;
-				approvedRoutersFingerprints.add(routerOrDirNode
-						.getFingerprint());
-			}
-		}
-
-		// write fingerprints for all directories and routers to the
-		// approved-routers file
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof DirectoryNode) {
-				final DirectoryNode dirNode = (DirectoryNode) node;
-				dirNode.addApprovedRouters(approvedRoutersFingerprints);
-			}
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "configureAsPrivateNetwork");
-	}
-
-	public void configureAsPartOfPrivateNetwork(
-			List<String> authorizedDirectoriesFingerprints) {
-			//throws PuppeTorException {
-		// log entering
-		logger.entering(this.getClass().getName(), "configureAsPartOfPrivateNetwork");
-
-		for (final ProxyNode node : nodes.values()) {
-			if (node.getNodeState() == NodeState.CONFIGURING ||
-					node.getNodeState() == NodeState.CONFIGURATION_WRITTEN) {
-				// add to configuration
-				node.addConfiguration("TestingTorNetwork 1");
-			}
-		}
-
-		// configure nodes
-		for (final ProxyNode node : nodes.values()) {
-			if (node.getNodeState() == NodeState.CONFIGURING ||
-					node.getNodeState() == NodeState.CONFIGURATION_WRITTEN) {
-				// add to configuration
-				node.addConfigurations(authorizedDirectoriesFingerprints);
-			}
-		}
-
-/*		// read fingerprints for all directories and routers
-		final HashSet<String> approvedRoutersFingerprints =
-				new HashSet<String>();
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof RouterNode) {
-				final RouterNode routerOrDirNode = (RouterNode) node;
-				approvedRoutersFingerprints.add(routerOrDirNode
-						.getFingerprint());
-			}
-		}
-
-		// write fingerprints for all directories and routers to the
-		// approved-routers file
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof DirectoryNode) {
-				final DirectoryNode dirNode = (DirectoryNode) node;
-				dirNode.addApprovedRouters(approvedRoutersFingerprints);
-			}
-		}
+    /**
+     * Internal thread class that is used to start Tor processes in parallel.
+     */
+    private class NodeStarter extends Thread {
+
+        /**
+         * The exception, if one is caught while trying to start the node.
+         */
+        Exception caughtException;
+
+        /**
+         * The maximum time to wait for the Tor process to start in
+         * milliseconds.
+         */
+        private final long maximumTimeToWaitInMillis;
+
+        /**
+         * The node for which the Tor process shall be started.
+         */
+        private final ProxyNode node;
+
+        /**
+         * Flag that denotes whether starting the Tor process was successful.
+         */
+        boolean success = false;
+
+        /**
+         * Creates a new <code>NodeStarter</code> for node <code>node</code>
+         * that will wait for <code>maximumTimeToWaitInMillis</code>
+         * milliseconds to start a Tor process, but that is not started, yet.
+         *
+         * @param node
+         *            The node for which the Tor process shall be started.
+         * @param maximumTimeToWaitInMillis
+         *            The maximum time to wait for the Tor process to start in
+         *            milliseconds.
+         */
+        NodeStarter(final ProxyNode node, final long maximumTimeToWaitInMillis) {
+
+            // log entering
+            logger.entering(this.getClass().getName(), "NodeStarter",
+                    new Object[] { node, maximumTimeToWaitInMillis });
+
+            // store parameters
+            this.node = node;
+            this.maximumTimeToWaitInMillis = maximumTimeToWaitInMillis;
+
+            // log exiting
+            logger.exiting(this.getClass().getName(), "NodeStarter");
+        }
+
+        @Override
+        public void run() {
+
+            // log entering
+            logger.entering(this.getClass().getName(), "run");
+
+            try {
+                // try to start node
+                success = node.startNode(maximumTimeToWaitInMillis);
+            } catch (final PuppeTorException e) {
+                logger.log(Level.SEVERE,
+                        "Caught an exception while starting node "
+                                + node.toString() + "!");
+                caughtException = e;
+            }
+
+            // log exiting
+            logger.exiting(this.getClass().getName(), "run");
+        }
+    }
+
+    /**
+     * Event manager to which all events concerning this network are notified.
+     */
+    private final EventManagerImpl eventManager;
+
+    /**
+     * Logger for this network which is called "network." plus the name of this
+     * network.
+     */
+    private final Logger logger;
+
+    /**
+     * Contains the name of this network configuration which is the String
+     * conversion of System.currentTimeMillis() of the network creation time.
+     */
+    private final String networkName;
+
+    /**
+     * All nodes contained in this network.
+     */
+    private final Map<String, ProxyNode> nodes;
+
+    /**
+     * Directory that contains status information of all nodes contained in this
+     * network.
+     */
+    private final File workingDir;
+
+    /**
+     * The counter for automatically assigned port numbers created by this
+     * <code>Network</code>.
+     */
+    private int portCounter = 7000;
+
+    /**
+     * Creates an initially unpopulated Tor network and creates a new working
+     * directory for it at test-env/randomTestID/.
+     *
+     * @param networkName
+     *            Name of this network configuration. May neither be
+     *            <code>null</code> or a zero-length string.
+     * @param startPort
+     *            The initial value for automatically assigned port numbers of
+     *            nodes created by this <code>Network</code>; must be a value
+     *            between 1024 and 65535.
+     * @throws IllegalArgumentException
+     *             Thrown if the given <code>networkName</code> is either
+     *             <code>null</code> or a zero-length string, or if an illegal
+     *             number is given for <code>startPort</code>.
+     */
+    public NetworkImpl(final String networkName, final int startPort) {
+        // initialize using overloaded constructor
+        this(networkName);
+
+        // check if start port is valid
+        if (startPort < 1024 || startPort > 65535) {
+            throw new IllegalArgumentException("Invalid startPort: "
+                    + startPort);
+        }
+
+        // remember parameter
+        portCounter = startPort;
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "NetworkImpl");
+    }
+
+    /**
+     * Creates an initially unpopulated Tor network and creates a new working
+     * directory for it at test-env/randomTestID/.
+     *
+     * @param networkName
+     *            Name of this network configuration. May neither be
+     *            <code>null</code> or a zero-length string.
+     * @throws IllegalArgumentException
+     *             Thrown if the given <code>networkName</code> is either
+     *             <code>null</code> or a zero-length string.
+     */
+    public NetworkImpl(final String networkName) {
+
+        // check if networkName can be used as logger name
+        if (networkName == null || networkName.length() == 0) {
+            throw new IllegalArgumentException("Invalid networkName: "
+                    + networkName);
+        }
+
+        // create logger
+        logger = Logger.getLogger("network." + networkName);
+
+        // log entering
+        logger.entering(this.getClass().getName(), "NetworkImpl", networkName);
+
+        // TODO is this necessary?!
+        logger.setLevel(Level.ALL);
+
+        // remember parameter
+        this.networkName = networkName;
+
+        // create working directory
+        workingDir = new File("test-env/" + System.currentTimeMillis());
+        workingDir.mkdirs();
+        logger.log(Level.FINE, "Created working directory \""
+                + workingDir.getAbsolutePath() + "\"");
+
+        // TODO if we want to log to file, set this... somehow...
+        // this.logFile = new File(this.workingDir.getAbsolutePath()
+        // + "\\events.log");
+
+        // initialize data structures
+        nodes = new ConcurrentHashMap<String, ProxyNode>();
+
+        // create event manager
+        eventManager = new EventManagerImpl(this.networkName);
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "NetworkImpl");
+    }
+
+    /**
+     * Returns whether all nodes in this network are up, or not.
+     *
+     * @return <code>true</code> if all nodes are up, <code>false</code> if
+     *         at least one node is not up.
+     */
+    private boolean allNodesUp() {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "allNodesUp");
+
+        // fail on first node that is not up
+        for (final ProxyNode node : nodes.values()) {
+            if (!eventManager.hasEventOccured(node.getNodeName(),
+                    NodeEventType.NODE_CIRCUIT_OPENED)) {
+
+                // log exiting and return false
+                logger.exiting(this.getClass().getName(), "allNodesUp");
+                return false;
+            }
+        }
+
+        // log exiting and return true
+        logger.exiting(this.getClass().getName(), "allNodesUp");
+        return true;
+    }
+
+    public void configureAsPrivateNetwork() throws PuppeTorException {
+        // log entering
+        logger.entering(this.getClass().getName(), "configureAsPrivateNetwork");
+
+        // read DirServer strings for all directories
+        final List<String> authorizedDirectoriesFingerprints =
+                new ArrayList<String>();
+        for (final ProxyNode node : nodes.values()) {
+            if (node instanceof DirectoryNode) {
+                final DirectoryNode dirNode = (DirectoryNode) node;
+                authorizedDirectoriesFingerprints.add(dirNode
+                        .getDirServerString());
+            }
+        }
+
+        this.configureAsPartOfPrivateNetwork(authorizedDirectoriesFingerprints);
+
+        // read fingerprints for all directories and routers
+        final HashSet<String> approvedRoutersFingerprints =
+                new HashSet<String>();
+        for (final ProxyNode node : nodes.values()) {
+            if (node instanceof RouterNode) {
+                final RouterNode routerOrDirNode = (RouterNode) node;
+                approvedRoutersFingerprints.add(routerOrDirNode
+                        .getFingerprint());
+            }
+        }
+
+        // write fingerprints for all directories and routers to the
+        // approved-routers file
+        for (final ProxyNode node : nodes.values()) {
+            if (node instanceof DirectoryNode) {
+                final DirectoryNode dirNode = (DirectoryNode) node;
+                dirNode.addApprovedRouters(approvedRoutersFingerprints);
+            }
+        }
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "configureAsPrivateNetwork");
+    }
+
+    public void configureAsPartOfPrivateNetwork(
+            List<String> authorizedDirectoriesFingerprints) {
+            //throws PuppeTorException {
+        // log entering
+        logger.entering(this.getClass().getName(), "configureAsPartOfPrivateNetwork");
+
+        for (final ProxyNode node : nodes.values()) {
+            if (node.getNodeState() == NodeState.CONFIGURING ||
+                    node.getNodeState() == NodeState.CONFIGURATION_WRITTEN) {
+                // add to configuration
+                node.addConfiguration("TestingTorNetwork 1");
+            }
+        }
+
+        // configure nodes
+        for (final ProxyNode node : nodes.values()) {
+            if (node.getNodeState() == NodeState.CONFIGURING ||
+                    node.getNodeState() == NodeState.CONFIGURATION_WRITTEN) {
+                // add to configuration
+                node.addConfigurations(authorizedDirectoriesFingerprints);
+            }
+        }
+
+/*        // read fingerprints for all directories and routers
+        final HashSet<String> approvedRoutersFingerprints =
+                new HashSet<String>();
+        for (final ProxyNode node : nodes.values()) {
+            if (node instanceof RouterNode) {
+                final RouterNode routerOrDirNode = (RouterNode) node;
+                approvedRoutersFingerprints.add(routerOrDirNode
+                        .getFingerprint());
+            }
+        }
+
+        // write fingerprints for all directories and routers to the
+        // approved-routers file
+        for (final ProxyNode node : nodes.values()) {
+            if (node instanceof DirectoryNode) {
+                final DirectoryNode dirNode = (DirectoryNode) node;
+                dirNode.addApprovedRouters(approvedRoutersFingerprints);
+            }
+        }
 */
-		// log exiting
-		logger.exiting(this.getClass().getName(), "configureAsPartOfPrivateNetwork");
-	}
-
-	public ClientApplication createClient(final String clientApplicationName,
-			final String targetAddress, final int targetPort,
-			final int socksPort) {
-		// log entering
-		logger.entering(this.getClass().getName(), "createClient",
-				new Object[] { clientApplicationName, targetAddress,
-						targetPort, socksPort });
-
-		// create client; parameter checking is done in constructor
-		final ClientApplicationImpl client =
-				new ClientApplicationImpl(this, clientApplicationName,
-						targetAddress, targetPort, socksPort);
-
-		// add name to event manager as event source
-		eventManager.addEventSource(clientApplicationName);
-
-		// log exiting and return client
-		logger.exiting(this.getClass().getName(), "createClient", client);
-		return client;
-	}
-
-	public DirectoryNode createDirectory(final String nodeName,
-			final int controlPort, final int socksPort, final int orPort,
-			final int dirPort, final String serverIpAddress) {
-		// log entering
-		logger.entering(this.getClass().getName(), "createDirectory",
-				new Object[] { nodeName, controlPort, socksPort, orPort,
-						dirPort, serverIpAddress });
-
-		// create directory node; parameter checking is done in constructor
-		final DirectoryNode dir =
-				new DirectoryNodeImpl(this, nodeName, controlPort, socksPort,
-						orPort, dirPort, serverIpAddress);
-
-		// add new directory node to nodes collection
-		nodes.put(nodeName, dir);
-
-		// add name to event manager as event source
-		eventManager.addEventSource(nodeName);
-
-		// log exiting and return directory node
-		logger.exiting(this.getClass().getName(), "createDirectory", dir);
-		return dir;
-	}
-
-	public DirectoryNode createDirectory(final String nodeName,
-			final int controlPort, final int socksPort, final int orPort,
-			final int dirPort) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createDirectory",
-				new Object[] { nodeName, controlPort, socksPort, orPort,
-						dirPort });
-
-		// invoke overloaded method
-		final DirectoryNode dir =
-				this.createDirectory(nodeName, controlPort, socksPort, orPort,
-						dirPort, "127.0.0.1");
-
-		// log exiting and return directory node
-		logger.exiting(this.getClass().getName(), "createDirectory", dir);
-		return dir;
-	}
-
-	public DirectoryNode createDirectory(final String nodeName,
-			final String serverIpAddress) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createDirectory",
-				new Object[] { nodeName, serverIpAddress });
-
-		// invoke overloaded method
-		final DirectoryNode dir =
-				this.createDirectory(nodeName, portCounter++, portCounter++,
-						portCounter++, portCounter++, serverIpAddress);
-
-		// log exiting and return directory node
-		logger.exiting(this.getClass().getName(), "createDirectory", dir);
-		return dir;
-	}
-
-	public DirectoryNode createDirectory(final String nodeName) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createDirectory", nodeName);
-
-		// invoke overloaded method
-		final DirectoryNode dir =
-				this.createDirectory(nodeName, portCounter++, portCounter++,
-						portCounter++, portCounter++, "127.0.0.1");
-
-		// log exiting and return directory node
-		logger.exiting(this.getClass().getName(), "createDirectory", dir);
-		return dir;
-	}
-
-	public ProxyNode createProxy(final String nodeName, final int controlPort,
-			final int socksPort) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createProxy", new Object[] {
-				nodeName, controlPort, socksPort });
-
-		// create proxy node; parameter checking is done in constructor
-		final ProxyNode proxy =
-				new ProxyNodeImpl(this, nodeName, controlPort, socksPort);
-
-		// add new proxy node to nodes collection
-		nodes.put(nodeName, proxy);
-
-		// add name to event manager as event source
-		eventManager.addEventSource(nodeName);
-
-		// log exiting and return proxy node
-		logger.exiting(this.getClass().getName(), "createProxy", proxy);
-		return proxy;
-	}
-
-	public ProxyNode createProxy(final String nodeName) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createProxy", nodeName);
-
-		// invoke overloaded method
-		final ProxyNode proxy =
-				this.createProxy(nodeName, portCounter++, portCounter++);
-
-		// log exiting and return proxy node
-		logger.exiting(this.getClass().getName(), "createProxy", proxy);
-		return proxy;
-	}
-
-	public RouterNode createRouter(final String nodeName,
-			final int controlPort, final int socksPort, final int orPort,
-			final int dirPort, final String serverIpAddress) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createRouter",
-				new Object[] { nodeName, controlPort, socksPort, orPort,
-						dirPort, serverIpAddress });
-
-		// create router node; parameter checking is done in constructor
-		final RouterNode router =
-				new RouterNodeImpl(this, nodeName, controlPort, socksPort,
-						orPort, dirPort, serverIpAddress);
-
-		// add new router node to nodes collection
-		nodes.put(nodeName, router);
-
-		// add name to event manager as event source
-		eventManager.addEventSource(nodeName);
-
-		// log exiting and return router node
-		logger.exiting(this.getClass().getName(), "createRouter", router);
-		return router;
-	}
-
-	public RouterNode createRouter(final String nodeName,
-			final int controlPort, final int socksPort, final int orPort,
-			final int dirPort) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createRouter",
-				new Object[] { nodeName, controlPort, socksPort, orPort,
-						dirPort });
-
-		// invoke overloaded method
-		final DirectoryNode dir =
-				this.createDirectory(nodeName, controlPort, socksPort, orPort,
-						dirPort, "127.0.0.1");
-
-		// log exiting and return directory node
-		logger.exiting(this.getClass().getName(), "createRouter", dir);
-		return dir;
-	}
-
-	public RouterNode createRouter(final String nodeName,
-			final String serverIpAddress) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createRouter",
-				new Object[] { nodeName, serverIpAddress });
-
-		// invoke overloaded method
-		final RouterNode dir =
-				this.createRouter(nodeName, portCounter++, portCounter++,
-						portCounter++, portCounter++, serverIpAddress);
-
-		// log exiting and return directory node
-		logger.exiting(this.getClass().getName(), "createRouter", dir);
-		return dir;
-	}
-
-	public RouterNode createRouter(final String nodeName) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createRouter", nodeName);
-
-		// invoke overloaded method
-		final RouterNode router =
-				this.createRouter(nodeName, portCounter++, portCounter++,
-						portCounter++, portCounter++, "127.0.0.1");
-
-		// log exiting and return router node
-		logger.exiting(this.getClass().getName(), "createRouter", router);
-		return router;
-	}
-
-	public ServerApplication createServer(final String serverApplicationName,
-			final int serverPort) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createServer",
-				new Object[] { serverApplicationName, serverPort });
-
-		// create server; parameter checking is done in constructor
-		final ServerApplicationImpl server =
-				new ServerApplicationImpl(this, serverApplicationName,
-						serverPort);
-
-		// add name to event manager as event source
-		eventManager.addEventSource(serverApplicationName);
-
-		// log exiting and return server
-		logger.exiting(this.getClass().getName(), "createServer", server);
-		return server;
-	}
-
-	public ServerApplication createServer(final String serverApplicationName) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createServer",
-				serverApplicationName);
-
-		// invoke overloaded method
-		final ServerApplication server =
-				this.createServer(serverApplicationName, portCounter++);
-
-		// log exiting and return server
-		logger.exiting(this.getClass().getName(), "createServer", server);
-		return server;
-	}
-
-	public EventManager getEventManager() {
-		return eventManager;
-	}
-
-	/**
-	 * Returns the implementation instance of the event manager of this network.
-	 *
-	 * @return The implementation instance of the event manager of this network.
-	 */
-	public EventManagerImpl getEventManagerImpl() {
-		return eventManager;
-	}
-
-	public File getWorkingDirectory() {
-		return workingDir;
-	}
-
-	public ProxyNode getNode(final String nodeName) {
-		return nodes.get(nodeName);
-	}
-
-	public Map<String, ProxyNode> getAllProxyNodes() {
-		final Map<String, ProxyNode> result = new HashMap<String, ProxyNode>();
-		for (final String nodeName : nodes.keySet()) {
-			final ProxyNode node = nodes.get(nodeName);
-			if (!(node instanceof RouterNode)) {
-				result.put(nodeName, node);
-			}
-		}
-		return result;
-	}
-
-	public Map<String, RouterNode> getAllRouterNodes() {
-		final Map<String, RouterNode> result =
-				new HashMap<String, RouterNode>();
-		for (final String nodeName : nodes.keySet()) {
-			final ProxyNode node = nodes.get(nodeName);
-			if (node instanceof RouterNode && !(node instanceof DirectoryNode)) {
-				result.put(nodeName, (RouterNode) node);
-			}
-		}
-		return result;
-	}
-
-	public Map<String, DirectoryNode> getAllDirectoryNodes() {
-		final Map<String, DirectoryNode> result =
-				new HashMap<String, DirectoryNode>();
-		for (final String nodeName : nodes.keySet()) {
-			final ProxyNode node = nodes.get(nodeName);
-			if (node instanceof DirectoryNode) {
-				result.put(nodeName, (DirectoryNode) node);
-			}
-		}
-		return result;
-	}
-
-	public Map<String, ProxyNode> getAllNodes() {
-		return new HashMap<String, ProxyNode>(nodes);
-	}
-
-	public boolean hupUntilUp(final int tries, final long hupInterval)
-			throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "hupUntilUp", new Object[] {
-				tries, hupInterval });
-
-		// check if all nodes are running
-		for (final ProxyNode node : nodes.values()) {
-			if (node.getNodeState() != NodeState.RUNNING) {
-				final IllegalStateException e =
-						new IllegalStateException(
-								"All nodes must be running before sending them HUP "
-										+ "commands!");
-				logger.throwing(this.getClass().getName(), "hupUntilUp", e);
-				throw e;
-			}
-		}
-
-		// check if nodes are already up; if so, return immediately
-		if (allNodesUp()) {
-
-			// log exiting and return true
-			logger.exiting(this.getClass().getName(), "hupUntilUp", true);
-			return true;
-		}
-
-		final Object hupLock = new Object();
-		// create and register a new event handler for each node
-		for (final ProxyNode node : nodes.values()) {
-			eventManager.addEventListener(node.getNodeName(),
-					new EventListener() {
-						public void handleEvent(final Event event) {
-							if (event.getType() == NodeEventType.NODE_CIRCUIT_OPENED) {
-								synchronized(hupLock) {
-									hupLock.notify();	
-								}
-								eventManager.removeEventListener(this);
-							}
-						}
-					});
-		}
-
-		// walk through wait-check-hup loop until there are no tries left
-		for (int i = 0; i < tries; i++) {
-
-			// determine how long to try waiting for the hangup
-			final long endOfSleeping = System.currentTimeMillis() + hupInterval;
-
-			// unless all nodes have reported to be up, wait for the given
-			// maximum time
-			while (System.currentTimeMillis() < endOfSleeping) {
-
-				synchronized(hupLock) {
-					// check if nodes are up now
-					if (allNodesUp()) {
-
-						// log exiting and return true
-						logger.exiting(this.getClass().getName(), "hupUntilUp",
-								true);
-						return true;
-					}
-					// sleep
-					try {
-						hupLock.wait(hupInterval);
-					} catch (final InterruptedException e) {
-						// do nothing about it
-					}
-				}
-			}
-
-			logger.log(Level.FINE, "Sending HUP to nodes");
-			// send a HUP signal to all nodes
-			for (final ProxyNode node : nodes.values()) {
-				logger
-						.log(Level.FINE, "Sending HUP to node "
-								+ node.toString());
-				node.hup();
-			}
-
-			// continue in loop
-		}
-
-		// no retries left and not all nodes are up; log exiting and return
-		// failure
-		logger.exiting(this.getClass().getName(), "hupUntilUp", false);
-		return false;
-	}
-
-	public void hupAllNodes() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "hupAllNodes");
-
-		// check if all nodes are running
-		for (final ProxyNode node : nodes.values()) {
-			if (node.getNodeState() != NodeState.RUNNING) {
-				final IllegalStateException e =
-						new IllegalStateException(
-								"All nodes must be running before sending them HUP "
-										+ "commands!");
-				logger.throwing(this.getClass().getName(), "hupAllNodes", e);
-				throw e;
-			}
-		}
-
-		// send a HUP signal to all nodes
-		for (final ProxyNode node : nodes.values()) {
-			logger.log(Level.FINE, "Sending HUP to node " + node.toString());
-			node.hup();
-		}
-
-		// no retries left and not all nodes are up; log exiting and return
-		// failure
-		logger.exiting(this.getClass().getName(), "hupAllNodes");
-	}
-
-	public void hupAllDirectories() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "hupAllDirectories");
-
-		// check if all directory nodes are running
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof DirectoryNode
-					&& node.getNodeState() != NodeState.RUNNING) {
-				final IllegalStateException e =
-						new IllegalStateException(
-								"All directory nodes must be running before sending "
-										+ "them HUP commands!");
-				logger.throwing(this.getClass().getName(), "hupAllDirectories",
-						e);
-				throw e;
-			}
-		}
-
-		// send a HUP signal to all nodes
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof DirectoryNode) {
-				logger
-						.log(Level.FINE, "Sending HUP to node "
-								+ node.toString());
-				node.hup();
-			}
-		}
-
-		// no retries left and not all nodes are up; log exiting and return
-		// failure
-		logger.exiting(this.getClass().getName(), "hupAllDirectories");
-	}
-
-	public void shutdownNodes() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "shutdownNodes");
-
-		// iteratively shut down all running nodes; if an exception is caught,
-		// continue shutting down the other nodes and throw the first exception
-		// subsequently
-		PuppeTorException firstCaughtException = null;
-		for (final ProxyNode node : nodes.values()) {
-			if (node.getNodeState() == NodeState.RUNNING) {
-				try {
-					node.shutdown();
-				} catch (final PuppeTorException e) {
-					if (firstCaughtException == null) {
-						firstCaughtException = e;
-					}
-				}
-			}
-		}
-
-		// if an exception was caught during shutting down nodes, throw the
-		// first caught exception
-		if (firstCaughtException != null) {
-			logger.throwing(this.getClass().getName(), "shutdownNodes",
-					firstCaughtException);
-			throw firstCaughtException;
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "shutdownNodes");
-	}
-
-	public boolean startNodes(final long maximumTimeToWaitInMillis)
-			throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "startNodes",
-				maximumTimeToWaitInMillis);
-
-		// check node states
-		for (final ProxyNode node : nodes.values()) {
-			if (node.getNodeState() != NodeState.CONFIGURATION_WRITTEN) {
-				final IllegalStateException e =
-						new IllegalStateException(
-								"All configurations must be written before starting "
-										+ "nodes!");
-				logger.throwing(this.getClass().getName(), "startNodes", e);
-				throw e;
-			}
-		}
-
-		// check parameter
-		if (maximumTimeToWaitInMillis < 0) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "startNodes", e);
-			throw e;
-		}
-
-		// remember time when we begin starting the nodes
-		final long before = System.currentTimeMillis();
-
-		// start nodes in parallel
-		final Set<NodeStarter> allNodeStarters = new HashSet<NodeStarter>();
-		for (final ProxyNode node : nodes.values()) {
-			final NodeStarter nodeStarter =
-					new NodeStarter(node, maximumTimeToWaitInMillis);
-			allNodeStarters.add(nodeStarter);
-			nodeStarter.start();
-		}
-
-		// wait for all node starts to complete
-		for (final NodeStarter nodeStarter : allNodeStarters) {
-
-			// join node starts one after the other
-			try {
-				nodeStarter.join();
-			} catch (final InterruptedException e) {
-				// this happens?! we have some kind of problem here!
-				logger.log(Level.WARNING,
-						"Interrupt while joining node starter!");
-
-				// log exiting and return false
-				logger.exiting(this.getClass().getName(), "startNodes", false);
-				return false;
-			}
-
-			// if any thread has caught an exception, throw that exception now
-			final Exception caughtException = nodeStarter.caughtException;
-			if (caughtException != null) {
-				final PuppeTorException ex =
-						new PuppeTorException("Exception while starting node "
-								+ nodeStarter.node.getNodeName(),
-								caughtException);
-				logger.throwing(this.getClass().getName(), "startNodes", ex);
-				throw ex;
-			}
-
-			// if node start did not succeed in the given time, fail
-			if (!nodeStarter.success) {
-				logger.log(Level.WARNING,
-						"Starting nodes was not successful in "
-								+ maximumTimeToWaitInMillis / 1000
-								+ " seconds.", networkName);
-
-				// log exiting and return false
-				logger.exiting(this.getClass().getName(), "startNodes", false);
-				return false;
-			}
-		}
-
-		// determine how long we took to start all nodes
-		final long after = System.currentTimeMillis();
-		logger.log(Level.FINE, "Starting nodes was successful and took "
-				+ (after - before) / 1000 + " seconds.", networkName);
-
-		// log exiting and return true
-		logger.exiting(this.getClass().getName(), "startNodes", true);
-		return true;
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": networkName=\""
-				+ networkName;
-	}
-
-	public String getNetworkName() {
-		return networkName;
-	}
-
-	public void writeConfigurations() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "writeConfigurations");
-
-		// write configurations for all nodes
-		for (final ProxyNode node : nodes.values()) {
-			node.writeConfiguration();
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "writeConfigurations");
-	}
-
-	public void addTemplateConfiguration(
-			final Class<? extends ProxyNode> nodeClass,
-			final String templateConfigurationString) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addTemplateConfiguration",
-				new Object[] { nodeClass, templateConfigurationString });
-
-		// check parameters
-		if (nodeClass == null || templateConfigurationString == null
-				|| templateConfigurationString.length() < 1
-				|| !templateConfigurationString.contains(" ")) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(),
-					"addTemplateConfiguration", e);
-			throw e;
-		}
-
-		// add template string to appropriate template configuration
-		if (nodeClass == ProxyNode.class) {
-			ProxyNodeImpl.templateConfiguration
-					.add(templateConfigurationString);
-		} else if (nodeClass == RouterNode.class) {
-			RouterNodeImpl.templateConfiguration
-					.add(templateConfigurationString);
-		} else if (nodeClass == DirectoryNode.class) {
-			DirectoryNodeImpl.templateConfiguration
-					.add(templateConfigurationString);
-		} else {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(),
-					"addTemplateConfiguration", e);
-			throw e;
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "addTemplateConfiguration");
-	}
-
-	public List<String> getTemplateConfiguration(
-			final Class<? extends ProxyNode> nodeClass) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "getTemplateConfiguration",
-				nodeClass);
-
-		// check parameter
-		if (nodeClass == null) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(),
-					"getTemplateConfiguration", e);
-			throw e;
-		}
-
-		// obtain reference on appropriate template configuration
-		List<String> result = null;
-		if (nodeClass == ProxyNode.class) {
-			result = new ArrayList<String>(ProxyNodeImpl.templateConfiguration);
-		} else if (nodeClass == RouterNode.class) {
-			result =
-					new ArrayList<String>(RouterNodeImpl.templateConfiguration);
-		} else if (nodeClass == DirectoryNode.class) {
-			result =
-					new ArrayList<String>(
-							DirectoryNodeImpl.templateConfiguration);
-		} else {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(),
-					"getTemplateConfiguration", e);
-			throw e;
-		}
-
-		// log exiting and return result
-		logger.exiting(this.getClass().getName(), "getTemplateConfiguration",
-				result);
-		return result;
-	}
-
-	public void removeTemplateConfiguration(
-			final Class<? extends ProxyNode> nodeClass,
-			final String templateConfigurationKey) {
-
-		// log entering
-		logger.entering(this.getClass().getName(),
-				"removeTemplateConfiguration", new Object[] { nodeClass,
-						templateConfigurationKey });
-
-		// check parameters
-		if (nodeClass == null || templateConfigurationKey == null
-				|| templateConfigurationKey.length() < 1) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(),
-					"removeTemplateConfiguration", e);
-			throw e;
-		}
-
-		// obtain reference on appropriate template configuration
-		List<String> templateConfig = null;
-		if (nodeClass == ProxyNode.class) {
-			templateConfig = ProxyNodeImpl.templateConfiguration;
-		} else if (nodeClass == RouterNode.class) {
-			templateConfig = RouterNodeImpl.templateConfiguration;
-		} else if (nodeClass == DirectoryNode.class) {
-			templateConfig = DirectoryNodeImpl.templateConfiguration;
-		} else {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(),
-					"removeTemplateConfiguration", e);
-			throw e;
-		}
-
-		// iterate over existing template configuration strings and remove all
-		// configuration strings that have the given configuration key
-		final List<String> configurationStringsToRemove =
-				new ArrayList<String>();
-		for (final String currentConfigurationString : templateConfig) {
-			final String currentConfigurationKey =
-					currentConfigurationString.substring(0,
-							currentConfigurationString.indexOf(" "));
-			if (currentConfigurationKey.equals(templateConfigurationKey)) {
-				configurationStringsToRemove.add(currentConfigurationString);
-			}
-		}
-		templateConfig.removeAll(configurationStringsToRemove);
-
-		// log exiting
-		logger
-				.exiting(this.getClass().getName(),
-						"removeTemplateConfiguration");
-	}
-
-	/**
-	 * Returns the current port number and increments it afterwards.
-	 *
-	 * @return The current port number.
-	 */
-	int getNextPortNumber() {
-		return portCounter++;
-	}
+        // log exiting
+        logger.exiting(this.getClass().getName(), "configureAsPartOfPrivateNetwork");
+    }
+
+    public ClientApplication createClient(final String clientApplicationName,
+            final String targetAddress, final int targetPort,
+            final int socksPort) {
+        // log entering
+        logger.entering(this.getClass().getName(), "createClient",
+                new Object[] { clientApplicationName, targetAddress,
+                        targetPort, socksPort });
+
+        // create client; parameter checking is done in constructor
+        final ClientApplicationImpl client =
+                new ClientApplicationImpl(this, clientApplicationName,
+                        targetAddress, targetPort, socksPort);
+
+        // add name to event manager as event source
+        eventManager.addEventSource(clientApplicationName);
+
+        // log exiting and return client
+        logger.exiting(this.getClass().getName(), "createClient", client);
+        return client;
+    }
+
+    public DirectoryNode createDirectory(final String nodeName,
+            final int controlPort, final int socksPort, final int orPort,
+            final int dirPort, final String serverIpAddress) {
+        // log entering
+        logger.entering(this.getClass().getName(), "createDirectory",
+                new Object[] { nodeName, controlPort, socksPort, orPort,
+                        dirPort, serverIpAddress });
+
+        // create directory node; parameter checking is done in constructor
+        final DirectoryNode dir =
+                new DirectoryNodeImpl(this, nodeName, controlPort, socksPort,
+                        orPort, dirPort, serverIpAddress);
+
+        // add new directory node to nodes collection
+        nodes.put(nodeName, dir);
+
+        // add name to event manager as event source
+        eventManager.addEventSource(nodeName);
+
+        // log exiting and return directory node
+        logger.exiting(this.getClass().getName(), "createDirectory", dir);
+        return dir;
+    }
+
+    public DirectoryNode createDirectory(final String nodeName,
+            final int controlPort, final int socksPort, final int orPort,
+            final int dirPort) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "createDirectory",
+                new Object[] { nodeName, controlPort, socksPort, orPort,
+                        dirPort });
+
+        // invoke overloaded method
+        final DirectoryNode dir =
+                this.createDirectory(nodeName, controlPort, socksPort, orPort,
+                        dirPort, "127.0.0.1");
+
+        // log exiting and return directory node
+        logger.exiting(this.getClass().getName(), "createDirectory", dir);
+        return dir;
+    }
+
+    public DirectoryNode createDirectory(final String nodeName,
+            final String serverIpAddress) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "createDirectory",
+                new Object[] { nodeName, serverIpAddress });
+
+        // invoke overloaded method
+        final DirectoryNode dir =
+                this.createDirectory(nodeName, portCounter++, portCounter++,
+                        portCounter++, portCounter++, serverIpAddress);
+
+        // log exiting and return directory node
+        logger.exiting(this.getClass().getName(), "createDirectory", dir);
+        return dir;
+    }
+
+    public DirectoryNode createDirectory(final String nodeName) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "createDirectory", nodeName);
+
+        // invoke overloaded method
+        final DirectoryNode dir =
+                this.createDirectory(nodeName, portCounter++, portCounter++,
+                        portCounter++, portCounter++, "127.0.0.1");
+
+        // log exiting and return directory node
+        logger.exiting(this.getClass().getName(), "createDirectory", dir);
+        return dir;
+    }
+
+    public ProxyNode createProxy(final String nodeName, final int controlPort,
+            final int socksPort) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "createProxy", new Object[] {
+                nodeName, controlPort, socksPort });
+
+        // create proxy node; parameter checking is done in constructor
+        final ProxyNode proxy =
+                new ProxyNodeImpl(this, nodeName, controlPort, socksPort);
+
+        // add new proxy node to nodes collection
+        nodes.put(nodeName, proxy);
+
+        // add name to event manager as event source
+        eventManager.addEventSource(nodeName);
+
+        // log exiting and return proxy node
+        logger.exiting(this.getClass().getName(), "createProxy", proxy);
+        return proxy;
+    }
+
+    public ProxyNode createProxy(final String nodeName) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "createProxy", nodeName);
+
+        // invoke overloaded method
+        final ProxyNode proxy =
+                this.createProxy(nodeName, portCounter++, portCounter++);
+
+        // log exiting and return proxy node
+        logger.exiting(this.getClass().getName(), "createProxy", proxy);
+        return proxy;
+    }
+
+    public RouterNode createRouter(final String nodeName,
+            final int controlPort, final int socksPort, final int orPort,
+            final int dirPort, final String serverIpAddress) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "createRouter",
+                new Object[] { nodeName, controlPort, socksPort, orPort,
+                        dirPort, serverIpAddress });
+
+        // create router node; parameter checking is done in constructor
+        final RouterNode router =
+                new RouterNodeImpl(this, nodeName, controlPort, socksPort,
+                        orPort, dirPort, serverIpAddress);
+
+        // add new router node to nodes collection
+        nodes.put(nodeName, router);
+
+        // add name to event manager as event source
+        eventManager.addEventSource(nodeName);
+
+        // log exiting and return router node
+        logger.exiting(this.getClass().getName(), "createRouter", router);
+        return router;
+    }
+
+    public RouterNode createRouter(final String nodeName,
+            final int controlPort, final int socksPort, final int orPort,
+            final int dirPort) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "createRouter",
+                new Object[] { nodeName, controlPort, socksPort, orPort,
+                        dirPort });
+
+        // invoke overloaded method
+        final DirectoryNode dir =
+                this.createDirectory(nodeName, controlPort, socksPort, orPort,
+                        dirPort, "127.0.0.1");
+
+        // log exiting and return directory node
+        logger.exiting(this.getClass().getName(), "createRouter", dir);
+        return dir;
+    }
+
+    public RouterNode createRouter(final String nodeName,
+            final String serverIpAddress) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "createRouter",
+                new Object[] { nodeName, serverIpAddress });
+
+        // invoke overloaded method
+        final RouterNode dir =
+                this.createRouter(nodeName, portCounter++, portCounter++,
+                        portCounter++, portCounter++, serverIpAddress);
+
+        // log exiting and return directory node
+        logger.exiting(this.getClass().getName(), "createRouter", dir);
+        return dir;
+    }
+
+    public RouterNode createRouter(final String nodeName) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "createRouter", nodeName);
+
+        // invoke overloaded method
+        final RouterNode router =
+                this.createRouter(nodeName, portCounter++, portCounter++,
+                        portCounter++, portCounter++, "127.0.0.1");
+
+        // log exiting and return router node
+        logger.exiting(this.getClass().getName(), "createRouter", router);
+        return router;
+    }
+
+    public ServerApplication createServer(final String serverApplicationName,
+            final int serverPort) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "createServer",
+                new Object[] { serverApplicationName, serverPort });
+
+        // create server; parameter checking is done in constructor
+        final ServerApplicationImpl server =
+                new ServerApplicationImpl(this, serverApplicationName,
+                        serverPort);
+
+        // add name to event manager as event source
+        eventManager.addEventSource(serverApplicationName);
+
+        // log exiting and return server
+        logger.exiting(this.getClass().getName(), "createServer", server);
+        return server;
+    }
+
+    public ServerApplication createServer(final String serverApplicationName) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "createServer",
+                serverApplicationName);
+
+        // invoke overloaded method
+        final ServerApplication server =
+                this.createServer(serverApplicationName, portCounter++);
+
+        // log exiting and return server
+        logger.exiting(this.getClass().getName(), "createServer", server);
+        return server;
+    }
+
+    public EventManager getEventManager() {
+        return eventManager;
+    }
+
+    /**
+     * Returns the implementation instance of the event manager of this network.
+     *
+     * @return The implementation instance of the event manager of this network.
+     */
+    public EventManagerImpl getEventManagerImpl() {
+        return eventManager;
+    }
+
+    public File getWorkingDirectory() {
+        return workingDir;
+    }
+
+    public ProxyNode getNode(final String nodeName) {
+        return nodes.get(nodeName);
+    }
+
+    public Map<String, ProxyNode> getAllProxyNodes() {
+        final Map<String, ProxyNode> result = new HashMap<String, ProxyNode>();
+        for (final String nodeName : nodes.keySet()) {
+            final ProxyNode node = nodes.get(nodeName);
+            if (!(node instanceof RouterNode)) {
+                result.put(nodeName, node);
+            }
+        }
+        return result;
+    }
+
+    public Map<String, RouterNode> getAllRouterNodes() {
+        final Map<String, RouterNode> result =
+                new HashMap<String, RouterNode>();
+        for (final String nodeName : nodes.keySet()) {
+            final ProxyNode node = nodes.get(nodeName);
+            if (node instanceof RouterNode && !(node instanceof DirectoryNode)) {
+                result.put(nodeName, (RouterNode) node);
+            }
+        }
+        return result;
+    }
+
+    public Map<String, DirectoryNode> getAllDirectoryNodes() {
+        final Map<String, DirectoryNode> result =
+                new HashMap<String, DirectoryNode>();
+        for (final String nodeName : nodes.keySet()) {
+            final ProxyNode node = nodes.get(nodeName);
+            if (node instanceof DirectoryNode) {
+                result.put(nodeName, (DirectoryNode) node);
+            }
+        }
+        return result;
+    }
+
+    public Map<String, ProxyNode> getAllNodes() {
+        return new HashMap<String, ProxyNode>(nodes);
+    }
+
+    public boolean hupUntilUp(final int tries, final long hupInterval)
+            throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "hupUntilUp", new Object[] {
+                tries, hupInterval });
+
+        // check if all nodes are running
+        for (final ProxyNode node : nodes.values()) {
+            if (node.getNodeState() != NodeState.RUNNING) {
+                final IllegalStateException e =
+                        new IllegalStateException(
+                                "All nodes must be running before sending them HUP "
+                                        + "commands!");
+                logger.throwing(this.getClass().getName(), "hupUntilUp", e);
+                throw e;
+            }
+        }
+
+        // check if nodes are already up; if so, return immediately
+        if (allNodesUp()) {
+
+            // log exiting and return true
+            logger.exiting(this.getClass().getName(), "hupUntilUp", true);
+            return true;
+        }
+
+        final Object hupLock = new Object();
+        // create and register a new event handler for each node
+        for (final ProxyNode node : nodes.values()) {
+            eventManager.addEventListener(node.getNodeName(),
+                    new EventListener() {
+                        public void handleEvent(final Event event) {
+                            if (event.getType() == NodeEventType.NODE_CIRCUIT_OPENED) {
+                                synchronized(hupLock) {
+                                    hupLock.notify();
+                                }
+                                eventManager.removeEventListener(this);
+                            }
+                        }
+                    });
+        }
+
+        // walk through wait-check-hup loop until there are no tries left
+        for (int i = 0; i < tries; i++) {
+
+            // determine how long to try waiting for the hangup
+            final long endOfSleeping = System.currentTimeMillis() + hupInterval;
+
+            // unless all nodes have reported to be up, wait for the given
+            // maximum time
+            while (System.currentTimeMillis() < endOfSleeping) {
+
+                synchronized(hupLock) {
+                    // check if nodes are up now
+                    if (allNodesUp()) {
+
+                        // log exiting and return true
+                        logger.exiting(this.getClass().getName(), "hupUntilUp",
+                                true);
+                        return true;
+                    }
+                    // sleep
+                    try {
+                        hupLock.wait(hupInterval);
+                    } catch (final InterruptedException e) {
+                        // do nothing about it
+                    }
+                }
+            }
+
+            logger.log(Level.FINE, "Sending HUP to nodes");
+            // send a HUP signal to all nodes
+            for (final ProxyNode node : nodes.values()) {
+                logger
+                        .log(Level.FINE, "Sending HUP to node "
+                                + node.toString());
+                node.hup();
+            }
+
+            // continue in loop
+        }
+
+        // no retries left and not all nodes are up; log exiting and return
+        // failure
+        logger.exiting(this.getClass().getName(), "hupUntilUp", false);
+        return false;
+    }
+
+    public void hupAllNodes() throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "hupAllNodes");
+
+        // check if all nodes are running
+        for (final ProxyNode node : nodes.values()) {
+            if (node.getNodeState() != NodeState.RUNNING) {
+                final IllegalStateException e =
+                        new IllegalStateException(
+                                "All nodes must be running before sending them HUP "
+                                        + "commands!");
+                logger.throwing(this.getClass().getName(), "hupAllNodes", e);
+                throw e;
+            }
+        }
+
+        // send a HUP signal to all nodes
+        for (final ProxyNode node : nodes.values()) {
+            logger.log(Level.FINE, "Sending HUP to node " + node.toString());
+            node.hup();
+        }
+
+        // no retries left and not all nodes are up; log exiting and return
+        // failure
+        logger.exiting(this.getClass().getName(), "hupAllNodes");
+    }
+
+    public void hupAllDirectories() throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "hupAllDirectories");
+
+        // check if all directory nodes are running
+        for (final ProxyNode node : nodes.values()) {
+            if (node instanceof DirectoryNode
+                    && node.getNodeState() != NodeState.RUNNING) {
+                final IllegalStateException e =
+                        new IllegalStateException(
+                                "All directory nodes must be running before sending "
+                                        + "them HUP commands!");
+                logger.throwing(this.getClass().getName(), "hupAllDirectories",
+                        e);
+                throw e;
+            }
+        }
+
+        // send a HUP signal to all nodes
+        for (final ProxyNode node : nodes.values()) {
+            if (node instanceof DirectoryNode) {
+                logger
+                        .log(Level.FINE, "Sending HUP to node "
+                                + node.toString());
+                node.hup();
+            }
+        }
+
+        // no retries left and not all nodes are up; log exiting and return
+        // failure
+        logger.exiting(this.getClass().getName(), "hupAllDirectories");
+    }
+
+    public void shutdownNodes() throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "shutdownNodes");
+
+        // iteratively shut down all running nodes; if an exception is caught,
+        // continue shutting down the other nodes and throw the first exception
+        // subsequently
+        PuppeTorException firstCaughtException = null;
+        for (final ProxyNode node : nodes.values()) {
+            if (node.getNodeState() == NodeState.RUNNING) {
+                try {
+                    node.shutdown();
+                } catch (final PuppeTorException e) {
+                    if (firstCaughtException == null) {
+                        firstCaughtException = e;
+                    }
+                }
+            }
+        }
+
+        // if an exception was caught during shutting down nodes, throw the
+        // first caught exception
+        if (firstCaughtException != null) {
+            logger.throwing(this.getClass().getName(), "shutdownNodes",
+                    firstCaughtException);
+            throw firstCaughtException;
+        }
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "shutdownNodes");
+    }
+
+    public boolean startNodes(final long maximumTimeToWaitInMillis)
+            throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "startNodes",
+                maximumTimeToWaitInMillis);
+
+        // check node states
+        for (final ProxyNode node : nodes.values()) {
+            if (node.getNodeState() != NodeState.CONFIGURATION_WRITTEN) {
+                final IllegalStateException e =
+                        new IllegalStateException(
+                                "All configurations must be written before starting "
+                                        + "nodes!");
+                logger.throwing(this.getClass().getName(), "startNodes", e);
+                throw e;
+            }
+        }
+
+        // check parameter
+        if (maximumTimeToWaitInMillis < 0) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "startNodes", e);
+            throw e;
+        }
+
+        // remember time when we begin starting the nodes
+        final long before = System.currentTimeMillis();
+
+        // start nodes in parallel
+        final Set<NodeStarter> allNodeStarters = new HashSet<NodeStarter>();
+        for (final ProxyNode node : nodes.values()) {
+            final NodeStarter nodeStarter =
+                    new NodeStarter(node, maximumTimeToWaitInMillis);
+            allNodeStarters.add(nodeStarter);
+            nodeStarter.start();
+        }
+
+        // wait for all node starts to complete
+        for (final NodeStarter nodeStarter : allNodeStarters) {
+
+            // join node starts one after the other
+            try {
+                nodeStarter.join();
+            } catch (final InterruptedException e) {
+                // this happens?! we have some kind of problem here!
+                logger.log(Level.WARNING,
+                        "Interrupt while joining node starter!");
+
+                // log exiting and return false
+                logger.exiting(this.getClass().getName(), "startNodes", false);
+                return false;
+            }
+
+            // if any thread has caught an exception, throw that exception now
+            final Exception caughtException = nodeStarter.caughtException;
+            if (caughtException != null) {
+                final PuppeTorException ex =
+                        new PuppeTorException("Exception while starting node "
+                                + nodeStarter.node.getNodeName(),
+                                caughtException);
+                logger.throwing(this.getClass().getName(), "startNodes", ex);
+                throw ex;
+            }
+
+            // if node start did not succeed in the given time, fail
+            if (!nodeStarter.success) {
+                logger.log(Level.WARNING,
+                        "Starting nodes was not successful in "
+                                + maximumTimeToWaitInMillis / 1000
+                                + " seconds.", networkName);
+
+                // log exiting and return false
+                logger.exiting(this.getClass().getName(), "startNodes", false);
+                return false;
+            }
+        }
+
+        // determine how long we took to start all nodes
+        final long after = System.currentTimeMillis();
+        logger.log(Level.FINE, "Starting nodes was successful and took "
+                + (after - before) / 1000 + " seconds.", networkName);
+
+        // log exiting and return true
+        logger.exiting(this.getClass().getName(), "startNodes", true);
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return this.getClass().getSimpleName() + ": networkName=\""
+                + networkName;
+    }
+
+    public String getNetworkName() {
+        return networkName;
+    }
+
+    public void writeConfigurations() throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "writeConfigurations");
+
+        // write configurations for all nodes
+        for (final ProxyNode node : nodes.values()) {
+            node.writeConfiguration();
+        }
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "writeConfigurations");
+    }
+
+    public void addTemplateConfiguration(
+            final Class<? extends ProxyNode> nodeClass,
+            final String templateConfigurationString) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "addTemplateConfiguration",
+                new Object[] { nodeClass, templateConfigurationString });
+
+        // check parameters
+        if (nodeClass == null || templateConfigurationString == null
+                || templateConfigurationString.length() < 1
+                || !templateConfigurationString.contains(" ")) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(),
+                    "addTemplateConfiguration", e);
+            throw e;
+        }
+
+        // add template string to appropriate template configuration
+        if (nodeClass == ProxyNode.class) {
+            ProxyNodeImpl.templateConfiguration
+                    .add(templateConfigurationString);
+        } else if (nodeClass == RouterNode.class) {
+            RouterNodeImpl.templateConfiguration
+                    .add(templateConfigurationString);
+        } else if (nodeClass == DirectoryNode.class) {
+            DirectoryNodeImpl.templateConfiguration
+                    .add(templateConfigurationString);
+        } else {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(),
+                    "addTemplateConfiguration", e);
+            throw e;
+        }
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "addTemplateConfiguration");
+    }
+
+    public List<String> getTemplateConfiguration(
+            final Class<? extends ProxyNode> nodeClass) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "getTemplateConfiguration",
+                nodeClass);
+
+        // check parameter
+        if (nodeClass == null) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(),
+                    "getTemplateConfiguration", e);
+            throw e;
+        }
+
+        // obtain reference on appropriate template configuration
+        List<String> result = null;
+        if (nodeClass == ProxyNode.class) {
+            result = new ArrayList<String>(ProxyNodeImpl.templateConfiguration);
+        } else if (nodeClass == RouterNode.class) {
+            result =
+                    new ArrayList<String>(RouterNodeImpl.templateConfiguration);
+        } else if (nodeClass == DirectoryNode.class) {
+            result =
+                    new ArrayList<String>(
+                            DirectoryNodeImpl.templateConfiguration);
+        } else {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(),
+                    "getTemplateConfiguration", e);
+            throw e;
+        }
+
+        // log exiting and return result
+        logger.exiting(this.getClass().getName(), "getTemplateConfiguration",
+                result);
+        return result;
+    }
+
+    public void removeTemplateConfiguration(
+            final Class<? extends ProxyNode> nodeClass,
+            final String templateConfigurationKey) {
+
+        // log entering
+        logger.entering(this.getClass().getName(),
+                "removeTemplateConfiguration", new Object[] { nodeClass,
+                        templateConfigurationKey });
+
+        // check parameters
+        if (nodeClass == null || templateConfigurationKey == null
+                || templateConfigurationKey.length() < 1) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(),
+                    "removeTemplateConfiguration", e);
+            throw e;
+        }
+
+        // obtain reference on appropriate template configuration
+        List<String> templateConfig = null;
+        if (nodeClass == ProxyNode.class) {
+            templateConfig = ProxyNodeImpl.templateConfiguration;
+        } else if (nodeClass == RouterNode.class) {
+            templateConfig = RouterNodeImpl.templateConfiguration;
+        } else if (nodeClass == DirectoryNode.class) {
+            templateConfig = DirectoryNodeImpl.templateConfiguration;
+        } else {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(),
+                    "removeTemplateConfiguration", e);
+            throw e;
+        }
+
+        // iterate over existing template configuration strings and remove all
+        // configuration strings that have the given configuration key
+        final List<String> configurationStringsToRemove =
+                new ArrayList<String>();
+        for (final String currentConfigurationString : templateConfig) {
+            final String currentConfigurationKey =
+                    currentConfigurationString.substring(0,
+                            currentConfigurationString.indexOf(" "));
+            if (currentConfigurationKey.equals(templateConfigurationKey)) {
+                configurationStringsToRemove.add(currentConfigurationString);
+            }
+        }
+        templateConfig.removeAll(configurationStringsToRemove);
+
+        // log exiting
+        logger
+                .exiting(this.getClass().getName(),
+                        "removeTemplateConfiguration");
+    }
+
+    /**
+     * Returns the current port number and increments it afterwards.
+     *
+     * @return The current port number.
+     */
+    int getNextPortNumber() {
+        return portCounter++;
+    }
 }
diff --git a/src/org/torproject/puppetor/impl/ProxyNodeImpl.java b/src/org/torproject/puppetor/impl/ProxyNodeImpl.java
index 5b65479..70201b8 100644
--- a/src/org/torproject/puppetor/impl/ProxyNodeImpl.java
+++ b/src/org/torproject/puppetor/impl/ProxyNodeImpl.java
@@ -34,702 +34,702 @@ import net.freehaven.tor.control.TorControlConnection;
  */
 public class ProxyNodeImpl implements ProxyNode {
 
-	/**
-	 * Executable file containing Tor.
-	 *
-	 * TODO make this configurable!
-	 */
-	protected static final File torExecutable = new File("tor");
-
-	/**
-	 * The <code>torrc</code> configuration file of this Tor node.
-	 */
-	protected File configFile;
-
-	/**
-	 * Collects all configuration strings for this node during the configuration
-	 * phase in the order they are added.
-	 */
-	protected List<String> configuration;
-
-	/**
-	 * Connection via Tor controller.
-	 */
-	protected TorControlConnection conn;
-
-	/**
-	 * Port on which the Tor node will be listening for us as its controller.
-	 */
-	protected int controlPort;
-
-	/**
-	 * Event manager to which all events concerning this node are notified.
-	 */
-	private final EventManagerImpl eventManager;
-
-	/**
-	 * Logger for this node which is called "node." plus the name of this node.
-	 */
-	protected Logger logger;
-
-	/**
-	 * Network to which this node belongs.
-	 */
-	protected NetworkImpl network;
-
-	/**
-	 * Name of this node that is used as part of the working directory, as
-	 * logger name of this node, and as event source.
-	 */
-	protected String nodeName;
-
-	/**
-	 * The state of this node.
-	 */
-	protected NodeState nodeState = NodeState.CONFIGURING;
-
-	/**
-	 * Port on which the Tor node will be listening for SOCKS connection
-	 * requests.
-	 */
-	protected int socksPort;
-
-	/**
-	 * The running Tor process that belongs to this node.
-	 */
-	protected Process torProcess;
-
-	/**
-	 * Directory in which all information concerning this node is stored.
-	 */
-	protected File workingDir;
-
-	/**
-	 * Returns this node's working directory.
-	 *
-	 * @return This node's working directory.
-	 */
-	File getWorkingDir() {
-		return workingDir;
-	}
-
-	/**
-	 * Creates a new <code>ProxyNodeImpl</code> and adds it to the given
-	 * <code>network</code>, but does not yet write its configuration to disk
-	 * or start the corresponding Tor process.
-	 *
-	 * @param network
-	 *            Network configuration to which this node belongs.
-	 * @param nodeName
-	 *            The name of the new node which may only consist of between 1
-	 *            and 19 alpha-numeric characters.
-	 * @param controlPort
-	 *            Port on which the Tor node will be listening for us as its
-	 *            controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            Port on which the Tor node will be listening for SOCKS
-	 *            connection requests. May not be negative or greater than
-	 *            65535.
-	 * @throws IllegalArgumentException
-	 *             If at least one of the parameters is <code>null</code> or
-	 *             has an invalid value.
-	 */
-	ProxyNodeImpl(final NetworkImpl network, final String nodeName,
-			final int controlPort, final int socksPort) {
-
-		// make sure that nodeName is a valid logger name
-		if (nodeName == null || nodeName.length() < 1 || nodeName.length() > 19
-				|| !nodeName.matches("[a-zA-Z0-9]*")) {
-			final String reason =
-					"\"" + nodeName + "\" is not a valid node name!";
-			final IllegalArgumentException e =
-					new IllegalArgumentException(reason);
-			throw e;
-		}
-
-		// create logger
-		logger = Logger.getLogger(nodeName + "." + this.getClass().getName());
-
-		logger.setLevel(Level.ALL);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "ProxyNodeImpl",
-				new Object[] { network, nodeName, controlPort, socksPort });
-
-		// check remaining parameters
-		if (network == null || controlPort < 0 || controlPort > 65535
-				|| socksPort < 0 || socksPort > 65535) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "ProxyNodeImpl", e);
-			throw e;
-		}
-
-		// store parameter values
-		this.network = network;
-		this.nodeName = nodeName;
-		this.controlPort = controlPort;
-		this.socksPort = socksPort;
-
-		// obtain reference on event manager from network
-		eventManager = network.getEventManagerImpl();
-
-		// create working directory
-		workingDir =
-				new File(this.network.getWorkingDirectory().getAbsolutePath()
-						+ File.separator + nodeName + File.separator);
-		workingDir.mkdirs();
-		logger.log(Level.FINE, "Created working directory \""
-				+ workingDir.getAbsolutePath() + "\"");
-
-		// create reference on config file
-		configFile =
-				new File(workingDir.getAbsolutePath() + File.separator
-						+ "torrc");
-
-		// initialize configuration
-		configuration = new ArrayList<String>(templateConfiguration);
-		configuration.add("ControlPort " + controlPort);
-		configuration.add("SocksPort " + socksPort);
-
-		// initialize state
-		nodeState = NodeState.CONFIGURING;
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "ProxyNodeImpl");
-	}
-
-	public void addConfiguration(final String configurationString) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addConfiguration",
-				configurationString);
-
-		// check parameter
-		if (configurationString == null || configurationString.length() < 1
-				|| !configurationString.contains(" ")) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "addConfiguration", e);
-			throw e;
-		}
-
-		// add configuration string
-		configuration.add(configurationString);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "addConfiguration");
-	}
-
-	public void addConfigurations(final List<String> configurationStrings) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addConfigurations",
-				configurationStrings);
-
-		// check parameter
-		if (configurationStrings == null) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "addConfigurations", e);
-			throw e;
-		}
-
-		// add configuration strings one by one
-		for (final String conf : configurationStrings) {
-			addConfiguration(conf);
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "addConfigurations");
-	}
-
-	public void replaceConfiguration(final String configurationString) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "replaceConfiguration",
-				configurationString);
-
-		// check parameter
-		if (configurationString == null || configurationString.length() < 1
-				|| !configurationString.contains(" ")) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "replaceConfiguration",
-					e);
-			throw e;
-		}
-
-		// extract configuration key
-		final String configurationKey =
-				configurationString.substring(0, configurationString
-						.indexOf(" "));
-
-		// iterate over existing configuration strings and replace the first
-		// occurrence of configuration key with new configuration string
-		final Iterator<String> it = configuration.listIterator();
-		boolean replaced = false;
-		for (int counter = 0; !replaced && it.hasNext(); counter++) {
-			final String currentConfigurationString = it.next();
-			final String currentConfigurationKey =
-					currentConfigurationString.substring(0,
-							currentConfigurationString.indexOf(" "));
-			if (currentConfigurationKey.equals(configurationKey)) {
-				configuration.set(counter, configurationString);
-				replaced = true;
-			}
-		}
-
-		// if no such configuration key was found, append the configuration
-		// string
-		if (!replaced) {
-			configuration.add(configurationString);
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "replaceConfiguration");
-	}
-
-	public void removeConfiguration(final String configurationKey) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "deleteConfiguration",
-				configurationKey);
-
-		// check parameter
-		if (configurationKey == null || configurationKey.length() < 1) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger
-					.throwing(this.getClass().getName(), "deleteConfiguration",
-							e);
-			throw e;
-		}
-
-		// iterate over existing configuration strings and remove all
-		// configuration strings that have the given configuration key
-		final List<String> configurationStringsToRemove =
-				new ArrayList<String>();
-		for (final String currentConfigurationString : configuration) {
-			final String currentConfigurationKey =
-					currentConfigurationString.substring(0,
-							currentConfigurationString.indexOf(" "));
-			if (currentConfigurationKey.equals(configurationKey)) {
-				configurationStringsToRemove.add(currentConfigurationString);
-			}
-		}
-		configuration.removeAll(configurationStringsToRemove);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "deleteConfiguration");
-	}
-
-	public synchronized HiddenService addHiddenService(
-			final String serviceName, final int servicePort,
-			final int virtualPort) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addHiddenService",
-				new Object[] { serviceName, servicePort, virtualPort });
-
-		// create hidden service object; parameter checking is done in
-		// constructor
-		final HiddenService result =
-				new HiddenServiceImpl(this, serviceName, servicePort,
-						virtualPort);
-
-		// add hidden service using Tor controller
-		configuration.add("HiddenServiceDir " + workingDir.getAbsolutePath()
-				+ File.separator + serviceName + "\nHiddenServicePort "
-				+ virtualPort + " 127.0.0.1:" + servicePort);
-
-		// log exiting and return hidden service object
-		logger.exiting(this.getClass().getName(), "addHiddenService", result);
-		return result;
-	}
-
-	public synchronized HiddenService addHiddenService(
-			final String serviceName, final int servicePort) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addHiddenService",
-				new Object[] { serviceName, servicePort });
-
-		// invoke overloaded method
-		final HiddenService result =
-				this.addHiddenService(serviceName, servicePort, 80);
-
-		// log exiting and return hidden service
-		logger.exiting(this.getClass().getName(), "addHiddenService", result);
-		return result;
-	}
-
-	public synchronized HiddenService addHiddenService(final String serviceName) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addHiddenService",
-				serviceName);
-
-		// invoke overloaded method
-		final HiddenService result =
-				this.addHiddenService(serviceName, network.getNextPortNumber(),
-						80);
-
-		// log exiting and return hidden service
-		logger.exiting(this.getClass().getName(), "addHiddenService", result);
-		return result;
-	}
-
-	public String getNodeName() {
-		return nodeName;
-	}
-
-	public synchronized NodeState getNodeState() {
-		return nodeState;
-	}
-
-	public synchronized void hup() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "hup");
-
-		// check state
-		if (nodeState != NodeState.RUNNING || conn == null) {
-			final IllegalStateException e =
-					new IllegalStateException(
-							"Cannot hup a process when it's not running or there is "
-									+ "no connection to its control port!");
-			logger.throwing(this.getClass().getName(), "hup", e);
-			throw e;
-		}
-
-		// send HUP signal to Tor process
-		try {
-			conn.signal("HUP");
-		} catch (final IOException e) {
-			final PuppeTorException ex =
-					new PuppeTorException(
-							"Could not send the command HUP to the Tor process!",
-							e);
-			logger.throwing(this.getClass().getName(), "hup", ex);
-			throw ex;
-		}
-		// log exiting
-		logger.exiting(this.getClass().getName(), "hup");
-	}
-
-	public synchronized void shutdown() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "shutdown");
-
-		// check state
-		if (nodeState != NodeState.RUNNING) {
-			final IllegalStateException e = new IllegalStateException();
-			logger.throwing(this.getClass().getName(), "shutdown", e);
-			throw e;
-		}
-
-		// we cannot simply kill the Tor process, because we have established a
-		// controller connection to it which would interpret a closed socket as
-		// failure and throw a RuntimeException
-		try {
-			conn.shutdownTor("SHUTDOWN");
-		} catch (final IOException e) {
-			final PuppeTorException ex =
-					new PuppeTorException(
-							"Could not send shutdown command to Tor process!",
-							e);
-			logger.throwing(this.getClass().getName(), "shutdown", ex);
-			throw ex;
-		}
-
-		// change state
-		nodeState = NodeState.SHUT_DOWN;
-
-		// fire event
-		eventManager.observeInternalEvent(System.currentTimeMillis(),
-				getNodeName(), NodeEventType.NODE_STOPPED, "Node stopped.");
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "shutdown");
-	}
-
-	/**
-	 * Helper thread that waits for a given time for a given process to
-	 * potentially terminate in order to find out if there are problems. If
-	 * either the process terminates cleanly within this timeout, or does not
-	 * terminate, the exit value will be 0; otherwise it will contain the exit
-	 * code of the terminated process. This functionality is added, because it
-	 * is not provided by Process. XXX Some stuff in here looks still dodgy.
-	 * What happens if we get an InterruptedException in run and thus don't set
-	 * exitValue?-SH
-	 */
-	private static class ProcessWaiter extends Thread {
-
-		/** The process to wait for. */
-		private final Process process;
-
-		/** The exit value or 0 if the process is still running. */
-		private final AtomicInteger exitValue;
-
-		/**
-		 * Creates a new <code>ProcessWaiter</code> for process
-		 * <code>process</code>, but does not start it, yet.
-		 *
-		 * @param process
-		 *            The process to wait for.
-		 */
-		ProcessWaiter(final Process process) {
-			this.process = process;
-			exitValue = new AtomicInteger(0);
-		}
-
-		@Override
-		public void run() {
-			try {
-				exitValue.set(process.waitFor());
-			} catch (final InterruptedException e) {}
-		}
-
-		/**
-		 * Causes the current thread to wait until the process has terminated or
-		 * the <code>timeoutInMillis</code> has expired. This method returns
-		 * immediately if the subprocess has already terminated.
-		 *
-		 * @param timeoutInMillis
-		 *            The maximum time to wait for the process to terminate.
-		 * @return The exit value of the terminated process or 0 if the process
-		 *         is still running.
-		 */
-		public int waitFor(final long timeoutInMillis) {
-			try {
-				sleep(timeoutInMillis);
-			} catch (final InterruptedException e) {}
-			interrupt();
-			return exitValue.get();
-		}
-	}
-
-	public synchronized boolean startNode(final long maximumTimeToWaitInMillis)
-			throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "startNode",
-				maximumTimeToWaitInMillis);
-
-		// check state
-		if (nodeState != NodeState.CONFIGURATION_WRITTEN) {
-			final String reason =
-					"Node is not in state "
-							+ "NodeState.CONFIGURATION_WRITTEN!";
-			final IllegalStateException e = new IllegalStateException(reason);
-			logger.throwing(this.getClass().getName(), "startNode", e);
-			throw e;
-		}
-
-		// start process
-		final ProcessBuilder processBuilder =
-				new ProcessBuilder(torExecutable.getPath(), "-f", "torrc");
-		processBuilder.directory(workingDir);
-		processBuilder.redirectErrorStream(true);
-		try {
-			torProcess = processBuilder.start();
-			logger.log(Level.FINE, "Started Tor process successfully!");
-		} catch (final IOException e) {
-			final String reason = "Could not start Tor process!";
-			final PuppeTorException ex = new PuppeTorException(reason, e);
-			logger.throwing(this.getClass().getName(), "startNode", ex);
-			throw ex;
-		}
-
-		// start thread to parse output
-		final BufferedReader br =
-				new BufferedReader(new InputStreamReader(torProcess
-						.getInputStream()));
-		final Thread outputThread = new Thread() {
-			@Override
-			public void run() {
-
-				// log entering
-				logger.entering(this.getClass().getName(), "run");
-
-				// read output from Tor to parse it
-				String line = null;
-				try {
-					while ((line = br.readLine()) != null) {
-						eventManager.observeUnparsedEvent(ProxyNodeImpl.this
-								.getNodeName(), line);
-					}
-				} catch (IOException e) {
-
-					// only print out a warning for this exception if this node
-					// is running; otherwise, silently ignore it...
-					if (getNodeState() == NodeState.RUNNING) {
-						String reason =
-								"IOException when reading output from Tor "
-										+ "process "
-										+ ProxyNodeImpl.this.getNodeName()
-										+ "!";
-						logger.log(Level.WARNING, reason, e);
-					}
-				}
-
-				// log exiting
-				logger.exiting(this.getClass().getName(), "run");
-			}
-		};
-		outputThread.setDaemon(true);
-		outputThread.setName(nodeName + " Output Parser");
-		outputThread.start();
-		logger.log(Level.FINE, "Started thread to parse output!");
-
-		// add shutdown hook that kills the process on JVM exit
-		final Process p = torProcess;
-		Runtime.getRuntime().addShutdownHook(new Thread() {
-			@Override
-			public void run() {
-
-				// log entering
-				logger.entering(this.getClass().getName(), "run");
-
-				// destroy Tor process
-				p.destroy();
-
-				// log exiting
-				logger.exiting(this.getClass().getName(), "run");
-			}
-		});
-		logger.log(Level.FINER,
-				"Started shutdown hook that will destroy the Tor process on "
-						+ "JVM exit!");
-
-		// wait to see if the process is started or exited immediately; wait for
-		// one second to be sure that Tor terminates if there is an error,
-		// especially if the computer is very busy and many nodes are created
-		final ProcessWaiter waiter = new ProcessWaiter(torProcess);
-		waiter.start();
-		final int exitValue = waiter.waitFor(1000);
-		if (exitValue != 0) {
-			// Tor did not manage to start correctly
-			logger.log(Level.WARNING, "Could not start Tor process! Tor "
-					+ "exited with exit value " + exitValue
-					+ "! Please go check the config options in " + configFile
-					+ " manually!");
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "startNode", false);
-			return false;
-		}
-
-		// wait for Tor to open the control port
-		logger.log(Level.FINER, "Waiting for Tor to open its control port...");
-		if (!eventManager.waitForAnyOccurence(nodeName,
-				NodeEventType.NODE_CONTROL_PORT_OPENED,
-				maximumTimeToWaitInMillis)) {
-
-			// Tor did not open its control port
-			logger.log(Level.WARNING, "Tor node " + nodeName
-					+ " did not manage to open its control port within "
-					+ maximumTimeToWaitInMillis + " milliseconds!");
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "startNode", false);
-			return false;
-		}
-		logger.log(Level.FINE,
-				"Tor has successfully opened its control port and told us "
-						+ "about that!");
-
-		// connect to the controller
-		logger.log(Level.FINER, "Connecting to control port...");
-		try {
-			final Socket controlSocket =
-					new java.net.Socket("127.0.0.1", controlPort);
-			conn = TorControlConnection.getConnection(controlSocket);
-			conn.authenticate(new byte[0]);
-		} catch (final IOException e) {
-			final String reason =
-					"Could not connect to control port " + controlPort + "!";
-			final PuppeTorException ex = new PuppeTorException(reason, e);
-			logger.throwing(this.getClass().getName(), "startNode", ex);
-			throw ex;
-		}
-		logger.log(Level.FINE, "Connected to control port successfully!");
-
-		// set state to RUNNING
-		nodeState = NodeState.RUNNING;
-
-		// fire event
-		eventManager.observeInternalEvent(System.currentTimeMillis(),
-				getNodeName(), NodeEventType.NODE_STARTED, "Node started.");
-
-		// log exiting and return with success
-		logger.exiting(this.getClass().getName(), "startNode", true);
-		return true;
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": nodeName=\"" + nodeName
-				+ "\", controlPort=" + controlPort + ", socksPort=" + socksPort;
-	}
-
-	public synchronized void writeConfiguration() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "writeConfiguration");
-
-		// write config file
-		try {
-			final BufferedWriter bw =
-					new BufferedWriter(new FileWriter(configFile));
-			for (final String c : configuration) {
-				bw.write(c + "\n");
-			}
-			bw.close();
-		} catch (final IOException e) {
-			final PuppeTorException ex =
-					new PuppeTorException(
-							"Could not write configuration file!", e);
-			logger.throwing(this.getClass().getName(),
-					"writeConfigurationFile", ex);
-			throw ex;
-		}
-
-		// change state, if necessary
-		if (nodeState == NodeState.CONFIGURING) {
-			nodeState = NodeState.CONFIGURATION_WRITTEN;
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "writeConfiguration");
-	}
-
-	public int getSocksPort() {
-		return socksPort;
-	}
-
-	public int getControlPort() {
-		return controlPort;
-	}
-
-	public List<String> getConfiguration() {
-		return new ArrayList<String>(configuration);
-	}
-
-	/**
-	 * Template configuration of proxy nodes.
-	 */
-	static List<String> templateConfiguration;
-
-	static {
-		templateConfiguration = new ArrayList<String>();
-
-		templateConfiguration.add("DataDirectory .");
-		templateConfiguration.add("SafeLogging 0");
-		templateConfiguration.add("UseEntryGuards 0");
-
-		templateConfiguration.add("Log info stdout");
-		templateConfiguration.add("Log info file log");
-
-		// TODO This is now contained in proposal 135.
-		// templateConfiguration.add("EnforceDistinctSubnets 0");
-		// templateConfiguration.add("ClientDNSRejectInternalAddresses 0");
-	}
+    /**
+     * Executable file containing Tor.
+     *
+     * TODO make this configurable!
+     */
+    protected static final File torExecutable = new File("tor");
+
+    /**
+     * The <code>torrc</code> configuration file of this Tor node.
+     */
+    protected File configFile;
+
+    /**
+     * Collects all configuration strings for this node during the configuration
+     * phase in the order they are added.
+     */
+    protected List<String> configuration;
+
+    /**
+     * Connection via Tor controller.
+     */
+    protected TorControlConnection conn;
+
+    /**
+     * Port on which the Tor node will be listening for us as its controller.
+     */
+    protected int controlPort;
+
+    /**
+     * Event manager to which all events concerning this node are notified.
+     */
+    private final EventManagerImpl eventManager;
+
+    /**
+     * Logger for this node which is called "node." plus the name of this node.
+     */
+    protected Logger logger;
+
+    /**
+     * Network to which this node belongs.
+     */
+    protected NetworkImpl network;
+
+    /**
+     * Name of this node that is used as part of the working directory, as
+     * logger name of this node, and as event source.
+     */
+    protected String nodeName;
+
+    /**
+     * The state of this node.
+     */
+    protected NodeState nodeState = NodeState.CONFIGURING;
+
+    /**
+     * Port on which the Tor node will be listening for SOCKS connection
+     * requests.
+     */
+    protected int socksPort;
+
+    /**
+     * The running Tor process that belongs to this node.
+     */
+    protected Process torProcess;
+
+    /**
+     * Directory in which all information concerning this node is stored.
+     */
+    protected File workingDir;
+
+    /**
+     * Returns this node's working directory.
+     *
+     * @return This node's working directory.
+     */
+    File getWorkingDir() {
+        return workingDir;
+    }
+
+    /**
+     * Creates a new <code>ProxyNodeImpl</code> and adds it to the given
+     * <code>network</code>, but does not yet write its configuration to disk
+     * or start the corresponding Tor process.
+     *
+     * @param network
+     *            Network configuration to which this node belongs.
+     * @param nodeName
+     *            The name of the new node which may only consist of between 1
+     *            and 19 alpha-numeric characters.
+     * @param controlPort
+     *            Port on which the Tor node will be listening for us as its
+     *            controller. May not be negative or greater than 65535.
+     * @param socksPort
+     *            Port on which the Tor node will be listening for SOCKS
+     *            connection requests. May not be negative or greater than
+     *            65535.
+     * @throws IllegalArgumentException
+     *             If at least one of the parameters is <code>null</code> or
+     *             has an invalid value.
+     */
+    ProxyNodeImpl(final NetworkImpl network, final String nodeName,
+            final int controlPort, final int socksPort) {
+
+        // make sure that nodeName is a valid logger name
+        if (nodeName == null || nodeName.length() < 1 || nodeName.length() > 19
+                || !nodeName.matches("[a-zA-Z0-9]*")) {
+            final String reason =
+                    "\"" + nodeName + "\" is not a valid node name!";
+            final IllegalArgumentException e =
+                    new IllegalArgumentException(reason);
+            throw e;
+        }
+
+        // create logger
+        logger = Logger.getLogger(nodeName + "." + this.getClass().getName());
+
+        logger.setLevel(Level.ALL);
+
+        // log entering
+        logger.entering(this.getClass().getName(), "ProxyNodeImpl",
+                new Object[] { network, nodeName, controlPort, socksPort });
+
+        // check remaining parameters
+        if (network == null || controlPort < 0 || controlPort > 65535
+                || socksPort < 0 || socksPort > 65535) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "ProxyNodeImpl", e);
+            throw e;
+        }
+
+        // store parameter values
+        this.network = network;
+        this.nodeName = nodeName;
+        this.controlPort = controlPort;
+        this.socksPort = socksPort;
+
+        // obtain reference on event manager from network
+        eventManager = network.getEventManagerImpl();
+
+        // create working directory
+        workingDir =
+                new File(this.network.getWorkingDirectory().getAbsolutePath()
+                        + File.separator + nodeName + File.separator);
+        workingDir.mkdirs();
+        logger.log(Level.FINE, "Created working directory \""
+                + workingDir.getAbsolutePath() + "\"");
+
+        // create reference on config file
+        configFile =
+                new File(workingDir.getAbsolutePath() + File.separator
+                        + "torrc");
+
+        // initialize configuration
+        configuration = new ArrayList<String>(templateConfiguration);
+        configuration.add("ControlPort " + controlPort);
+        configuration.add("SocksPort " + socksPort);
+
+        // initialize state
+        nodeState = NodeState.CONFIGURING;
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "ProxyNodeImpl");
+    }
+
+    public void addConfiguration(final String configurationString) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "addConfiguration",
+                configurationString);
+
+        // check parameter
+        if (configurationString == null || configurationString.length() < 1
+                || !configurationString.contains(" ")) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "addConfiguration", e);
+            throw e;
+        }
+
+        // add configuration string
+        configuration.add(configurationString);
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "addConfiguration");
+    }
+
+    public void addConfigurations(final List<String> configurationStrings) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "addConfigurations",
+                configurationStrings);
+
+        // check parameter
+        if (configurationStrings == null) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "addConfigurations", e);
+            throw e;
+        }
+
+        // add configuration strings one by one
+        for (final String conf : configurationStrings) {
+            addConfiguration(conf);
+        }
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "addConfigurations");
+    }
+
+    public void replaceConfiguration(final String configurationString) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "replaceConfiguration",
+                configurationString);
+
+        // check parameter
+        if (configurationString == null || configurationString.length() < 1
+                || !configurationString.contains(" ")) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "replaceConfiguration",
+                    e);
+            throw e;
+        }
+
+        // extract configuration key
+        final String configurationKey =
+                configurationString.substring(0, configurationString
+                        .indexOf(" "));
+
+        // iterate over existing configuration strings and replace the first
+        // occurrence of configuration key with new configuration string
+        final Iterator<String> it = configuration.listIterator();
+        boolean replaced = false;
+        for (int counter = 0; !replaced && it.hasNext(); counter++) {
+            final String currentConfigurationString = it.next();
+            final String currentConfigurationKey =
+                    currentConfigurationString.substring(0,
+                            currentConfigurationString.indexOf(" "));
+            if (currentConfigurationKey.equals(configurationKey)) {
+                configuration.set(counter, configurationString);
+                replaced = true;
+            }
+        }
+
+        // if no such configuration key was found, append the configuration
+        // string
+        if (!replaced) {
+            configuration.add(configurationString);
+        }
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "replaceConfiguration");
+    }
+
+    public void removeConfiguration(final String configurationKey) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "deleteConfiguration",
+                configurationKey);
+
+        // check parameter
+        if (configurationKey == null || configurationKey.length() < 1) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger
+                    .throwing(this.getClass().getName(), "deleteConfiguration",
+                            e);
+            throw e;
+        }
+
+        // iterate over existing configuration strings and remove all
+        // configuration strings that have the given configuration key
+        final List<String> configurationStringsToRemove =
+                new ArrayList<String>();
+        for (final String currentConfigurationString : configuration) {
+            final String currentConfigurationKey =
+                    currentConfigurationString.substring(0,
+                            currentConfigurationString.indexOf(" "));
+            if (currentConfigurationKey.equals(configurationKey)) {
+                configurationStringsToRemove.add(currentConfigurationString);
+            }
+        }
+        configuration.removeAll(configurationStringsToRemove);
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "deleteConfiguration");
+    }
+
+    public synchronized HiddenService addHiddenService(
+            final String serviceName, final int servicePort,
+            final int virtualPort) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "addHiddenService",
+                new Object[] { serviceName, servicePort, virtualPort });
+
+        // create hidden service object; parameter checking is done in
+        // constructor
+        final HiddenService result =
+                new HiddenServiceImpl(this, serviceName, servicePort,
+                        virtualPort);
+
+        // add hidden service using Tor controller
+        configuration.add("HiddenServiceDir " + workingDir.getAbsolutePath()
+                + File.separator + serviceName + "\nHiddenServicePort "
+                + virtualPort + " 127.0.0.1:" + servicePort);
+
+        // log exiting and return hidden service object
+        logger.exiting(this.getClass().getName(), "addHiddenService", result);
+        return result;
+    }
+
+    public synchronized HiddenService addHiddenService(
+            final String serviceName, final int servicePort) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "addHiddenService",
+                new Object[] { serviceName, servicePort });
+
+        // invoke overloaded method
+        final HiddenService result =
+                this.addHiddenService(serviceName, servicePort, 80);
+
+        // log exiting and return hidden service
+        logger.exiting(this.getClass().getName(), "addHiddenService", result);
+        return result;
+    }
+
+    public synchronized HiddenService addHiddenService(final String serviceName) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "addHiddenService",
+                serviceName);
+
+        // invoke overloaded method
+        final HiddenService result =
+                this.addHiddenService(serviceName, network.getNextPortNumber(),
+                        80);
+
+        // log exiting and return hidden service
+        logger.exiting(this.getClass().getName(), "addHiddenService", result);
+        return result;
+    }
+
+    public String getNodeName() {
+        return nodeName;
+    }
+
+    public synchronized NodeState getNodeState() {
+        return nodeState;
+    }
+
+    public synchronized void hup() throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "hup");
+
+        // check state
+        if (nodeState != NodeState.RUNNING || conn == null) {
+            final IllegalStateException e =
+                    new IllegalStateException(
+                            "Cannot hup a process when it's not running or there is "
+                                    + "no connection to its control port!");
+            logger.throwing(this.getClass().getName(), "hup", e);
+            throw e;
+        }
+
+        // send HUP signal to Tor process
+        try {
+            conn.signal("HUP");
+        } catch (final IOException e) {
+            final PuppeTorException ex =
+                    new PuppeTorException(
+                            "Could not send the command HUP to the Tor process!",
+                            e);
+            logger.throwing(this.getClass().getName(), "hup", ex);
+            throw ex;
+        }
+        // log exiting
+        logger.exiting(this.getClass().getName(), "hup");
+    }
+
+    public synchronized void shutdown() throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "shutdown");
+
+        // check state
+        if (nodeState != NodeState.RUNNING) {
+            final IllegalStateException e = new IllegalStateException();
+            logger.throwing(this.getClass().getName(), "shutdown", e);
+            throw e;
+        }
+
+        // we cannot simply kill the Tor process, because we have established a
+        // controller connection to it which would interpret a closed socket as
+        // failure and throw a RuntimeException
+        try {
+            conn.shutdownTor("SHUTDOWN");
+        } catch (final IOException e) {
+            final PuppeTorException ex =
+                    new PuppeTorException(
+                            "Could not send shutdown command to Tor process!",
+                            e);
+            logger.throwing(this.getClass().getName(), "shutdown", ex);
+            throw ex;
+        }
+
+        // change state
+        nodeState = NodeState.SHUT_DOWN;
+
+        // fire event
+        eventManager.observeInternalEvent(System.currentTimeMillis(),
+                getNodeName(), NodeEventType.NODE_STOPPED, "Node stopped.");
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "shutdown");
+    }
+
+    /**
+     * Helper thread that waits for a given time for a given process to
+     * potentially terminate in order to find out if there are problems. If
+     * either the process terminates cleanly within this timeout, or does not
+     * terminate, the exit value will be 0; otherwise it will contain the exit
+     * code of the terminated process. This functionality is added, because it
+     * is not provided by Process. XXX Some stuff in here looks still dodgy.
+     * What happens if we get an InterruptedException in run and thus don't set
+     * exitValue?-SH
+     */
+    private static class ProcessWaiter extends Thread {
+
+        /** The process to wait for. */
+        private final Process process;
+
+        /** The exit value or 0 if the process is still running. */
+        private final AtomicInteger exitValue;
+
+        /**
+         * Creates a new <code>ProcessWaiter</code> for process
+         * <code>process</code>, but does not start it, yet.
+         *
+         * @param process
+         *            The process to wait for.
+         */
+        ProcessWaiter(final Process process) {
+            this.process = process;
+            exitValue = new AtomicInteger(0);
+        }
+
+        @Override
+        public void run() {
+            try {
+                exitValue.set(process.waitFor());
+            } catch (final InterruptedException e) {}
+        }
+
+        /**
+         * Causes the current thread to wait until the process has terminated or
+         * the <code>timeoutInMillis</code> has expired. This method returns
+         * immediately if the subprocess has already terminated.
+         *
+         * @param timeoutInMillis
+         *            The maximum time to wait for the process to terminate.
+         * @return The exit value of the terminated process or 0 if the process
+         *         is still running.
+         */
+        public int waitFor(final long timeoutInMillis) {
+            try {
+                sleep(timeoutInMillis);
+            } catch (final InterruptedException e) {}
+            interrupt();
+            return exitValue.get();
+        }
+    }
+
+    public synchronized boolean startNode(final long maximumTimeToWaitInMillis)
+            throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "startNode",
+                maximumTimeToWaitInMillis);
+
+        // check state
+        if (nodeState != NodeState.CONFIGURATION_WRITTEN) {
+            final String reason =
+                    "Node is not in state "
+                            + "NodeState.CONFIGURATION_WRITTEN!";
+            final IllegalStateException e = new IllegalStateException(reason);
+            logger.throwing(this.getClass().getName(), "startNode", e);
+            throw e;
+        }
+
+        // start process
+        final ProcessBuilder processBuilder =
+                new ProcessBuilder(torExecutable.getPath(), "-f", "torrc");
+        processBuilder.directory(workingDir);
+        processBuilder.redirectErrorStream(true);
+        try {
+            torProcess = processBuilder.start();
+            logger.log(Level.FINE, "Started Tor process successfully!");
+        } catch (final IOException e) {
+            final String reason = "Could not start Tor process!";
+            final PuppeTorException ex = new PuppeTorException(reason, e);
+            logger.throwing(this.getClass().getName(), "startNode", ex);
+            throw ex;
+        }
+
+        // start thread to parse output
+        final BufferedReader br =
+                new BufferedReader(new InputStreamReader(torProcess
+                        .getInputStream()));
+        final Thread outputThread = new Thread() {
+            @Override
+            public void run() {
+
+                // log entering
+                logger.entering(this.getClass().getName(), "run");
+
+                // read output from Tor to parse it
+                String line = null;
+                try {
+                    while ((line = br.readLine()) != null) {
+                        eventManager.observeUnparsedEvent(ProxyNodeImpl.this
+                                .getNodeName(), line);
+                    }
+                } catch (IOException e) {
+
+                    // only print out a warning for this exception if this node
+                    // is running; otherwise, silently ignore it...
+                    if (getNodeState() == NodeState.RUNNING) {
+                        String reason =
+                                "IOException when reading output from Tor "
+                                        + "process "
+                                        + ProxyNodeImpl.this.getNodeName()
+                                        + "!";
+                        logger.log(Level.WARNING, reason, e);
+                    }
+                }
+
+                // log exiting
+                logger.exiting(this.getClass().getName(), "run");
+            }
+        };
+        outputThread.setDaemon(true);
+        outputThread.setName(nodeName + " Output Parser");
+        outputThread.start();
+        logger.log(Level.FINE, "Started thread to parse output!");
+
+        // add shutdown hook that kills the process on JVM exit
+        final Process p = torProcess;
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+
+                // log entering
+                logger.entering(this.getClass().getName(), "run");
+
+                // destroy Tor process
+                p.destroy();
+
+                // log exiting
+                logger.exiting(this.getClass().getName(), "run");
+            }
+        });
+        logger.log(Level.FINER,
+                "Started shutdown hook that will destroy the Tor process on "
+                        + "JVM exit!");
+
+        // wait to see if the process is started or exited immediately; wait for
+        // one second to be sure that Tor terminates if there is an error,
+        // especially if the computer is very busy and many nodes are created
+        final ProcessWaiter waiter = new ProcessWaiter(torProcess);
+        waiter.start();
+        final int exitValue = waiter.waitFor(1000);
+        if (exitValue != 0) {
+            // Tor did not manage to start correctly
+            logger.log(Level.WARNING, "Could not start Tor process! Tor "
+                    + "exited with exit value " + exitValue
+                    + "! Please go check the config options in " + configFile
+                    + " manually!");
+
+            // log exiting
+            logger.exiting(this.getClass().getName(), "startNode", false);
+            return false;
+        }
+
+        // wait for Tor to open the control port
+        logger.log(Level.FINER, "Waiting for Tor to open its control port...");
+        if (!eventManager.waitForAnyOccurence(nodeName,
+                NodeEventType.NODE_CONTROL_PORT_OPENED,
+                maximumTimeToWaitInMillis)) {
+
+            // Tor did not open its control port
+            logger.log(Level.WARNING, "Tor node " + nodeName
+                    + " did not manage to open its control port within "
+                    + maximumTimeToWaitInMillis + " milliseconds!");
+
+            // log exiting
+            logger.exiting(this.getClass().getName(), "startNode", false);
+            return false;
+        }
+        logger.log(Level.FINE,
+                "Tor has successfully opened its control port and told us "
+                        + "about that!");
+
+        // connect to the controller
+        logger.log(Level.FINER, "Connecting to control port...");
+        try {
+            final Socket controlSocket =
+                    new java.net.Socket("127.0.0.1", controlPort);
+            conn = TorControlConnection.getConnection(controlSocket);
+            conn.authenticate(new byte[0]);
+        } catch (final IOException e) {
+            final String reason =
+                    "Could not connect to control port " + controlPort + "!";
+            final PuppeTorException ex = new PuppeTorException(reason, e);
+            logger.throwing(this.getClass().getName(), "startNode", ex);
+            throw ex;
+        }
+        logger.log(Level.FINE, "Connected to control port successfully!");
+
+        // set state to RUNNING
+        nodeState = NodeState.RUNNING;
+
+        // fire event
+        eventManager.observeInternalEvent(System.currentTimeMillis(),
+                getNodeName(), NodeEventType.NODE_STARTED, "Node started.");
+
+        // log exiting and return with success
+        logger.exiting(this.getClass().getName(), "startNode", true);
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return this.getClass().getSimpleName() + ": nodeName=\"" + nodeName
+                + "\", controlPort=" + controlPort + ", socksPort=" + socksPort;
+    }
+
+    public synchronized void writeConfiguration() throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "writeConfiguration");
+
+        // write config file
+        try {
+            final BufferedWriter bw =
+                    new BufferedWriter(new FileWriter(configFile));
+            for (final String c : configuration) {
+                bw.write(c + "\n");
+            }
+            bw.close();
+        } catch (final IOException e) {
+            final PuppeTorException ex =
+                    new PuppeTorException(
+                            "Could not write configuration file!", e);
+            logger.throwing(this.getClass().getName(),
+                    "writeConfigurationFile", ex);
+            throw ex;
+        }
+
+        // change state, if necessary
+        if (nodeState == NodeState.CONFIGURING) {
+            nodeState = NodeState.CONFIGURATION_WRITTEN;
+        }
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "writeConfiguration");
+    }
+
+    public int getSocksPort() {
+        return socksPort;
+    }
+
+    public int getControlPort() {
+        return controlPort;
+    }
+
+    public List<String> getConfiguration() {
+        return new ArrayList<String>(configuration);
+    }
+
+    /**
+     * Template configuration of proxy nodes.
+     */
+    static List<String> templateConfiguration;
+
+    static {
+        templateConfiguration = new ArrayList<String>();
+
+        templateConfiguration.add("DataDirectory .");
+        templateConfiguration.add("SafeLogging 0");
+        templateConfiguration.add("UseEntryGuards 0");
+
+        templateConfiguration.add("Log info stdout");
+        templateConfiguration.add("Log info file log");
+
+        // TODO This is now contained in proposal 135.
+        // templateConfiguration.add("EnforceDistinctSubnets 0");
+        // templateConfiguration.add("ClientDNSRejectInternalAddresses 0");
+    }
 }
diff --git a/src/org/torproject/puppetor/impl/RouterNodeImpl.java b/src/org/torproject/puppetor/impl/RouterNodeImpl.java
index d291452..7320e6a 100644
--- a/src/org/torproject/puppetor/impl/RouterNodeImpl.java
+++ b/src/org/torproject/puppetor/impl/RouterNodeImpl.java
@@ -27,377 +27,377 @@ import org.torproject.puppetor.RouterNode;
  */
 public class RouterNodeImpl extends ProxyNodeImpl implements RouterNode {
 
-	/**
-	 * Internal thread class that is used to determine fingerprints in parallel,
-	 * which can take a few seconds.
-	 */
-	private class FingerprintThread extends Thread {
-
-		@Override
-		public void run() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "run");
-
-			// create file reference for temporary config file
-			final File tempConfigFile =
-					new File(workingDir.getAbsolutePath() + File.separator
-							+ "torrc.tmp");
-
-			// compose a modified config file, including a DirServer option with
-			// false fingerprint; this is necessary, because otherwise Tor
-			// would not accept that this router node might have a private IP
-			// address, but connects to the public directory servers
-			final List<String> copyOfConfig =
-					new ArrayList<String>(configuration);
-			final String fakeDirServerString =
-					"DirServer "
-							+ nodeName
-							+ " 127.0.0.1:"
-							+ dirPort
-							+ " 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000";
-			copyOfConfig.add(fakeDirServerString);
-
-			// write config file
-			try {
-				final BufferedWriter bw =
-						new BufferedWriter(new FileWriter(tempConfigFile));
-				for (final String c : copyOfConfig) {
-					bw.write(c + "\n");
-				}
-				bw.close();
-			} catch (final IOException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not write configuration file!", e);
-				logger.log(Level.WARNING, "Could not start Tor process!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			// start process with option --list-fingerprint
-			final ProcessBuilder processBuilder =
-					new ProcessBuilder(torExecutable.getPath(),
-							"--list-fingerprint", "-f", "torrc.tmp");
-			processBuilder.directory(workingDir);
-			processBuilder.redirectErrorStream(true);
-			Process tmpProcess = null;
-			try {
-				tmpProcess = processBuilder.start();
-			} catch (final IOException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not start Tor process temporarily with "
-										+ "--list-fingerprint option!", e);
-				logger.log(Level.WARNING, "Could not start Tor process!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			// wait for process to terminate
-			int exitValue = 0;
-			try {
-				exitValue = tmpProcess.waitFor();
-			} catch (final InterruptedException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Interrupted while waiting for Tor process to exit!",
-								e);
-				logger.log(Level.WARNING,
-						"Temporary Tor process was interrupted!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			if (exitValue != 0) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not start Tor process temporarily with "
-										+ "--list-fingerprint option! Tor exited with "
-										+ "exit value "
-										+ exitValue
-										+ "! Please go check the config options in "
-										+ tempConfigFile + " manually!");
-				logger.log(Level.WARNING, "Could not start Tor process!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			// read fingerprint from file
-			final File fingerprintFile =
-					new File(workingDir.getAbsolutePath() + File.separator
-							+ "fingerprint");
-			try {
-				final BufferedReader br2 =
-						new BufferedReader(new FileReader(fingerprintFile));
-				setFingerprint(br2.readLine());
-				br2.close();
-			} catch (final IOException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not read fingerprint from file!", e);
-				logger.log(Level.WARNING, "Could not read fingerprint file!",
-						ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			// delete temporary config file
-			tempConfigFile.delete();
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "run");
-		}
-	}
-
-	/**
-	 * Invoked by the fingerprint thread: sets the determined fingerprint string
-	 *
-	 * @param fingerprint
-	 *            The determined fingerprint string.
-	 */
-	private synchronized void setFingerprint(final String fingerprint) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "setFingerprint",
-				fingerprint);
-
-		// remember fingerprint and notify all waiting threads
-		this.fingerprint = fingerprint;
-		notifyAll();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "setFingerprint");
-	}
-
-	/**
-	 * Invoked by the fingerprint thread: sets the exception that occurred when
-	 * trying to determine the fingerprint.
-	 *
-	 * @param caughtException
-	 *            The exception that occurred when trying to determine the
-	 *            fingerprint.
-	 */
-	protected synchronized void setCaughtException(
-			final PuppeTorException caughtException) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "setCaughtException",
-				caughtException);
-
-		// remember caught exception and notify all waiting threads
-		this.caughtException = caughtException;
-		notifyAll();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "setCaughtException");
-	}
-
-	/**
-	 * Port on which the Tor node will be listening for directory requests from
-	 * other Tor nodes.
-	 */
-	protected int dirPort;
-
-	/**
-	 * The IP v4 address on which the node will listen in dotted decimal
-	 * notation.
-	 */
-	protected String serverIpAddress;
-
-	/**
-	 * The fingerprint of this node that is determined as hash value of its
-	 * onion key. It is initialized with <code>null</code> and set by the
-	 * fingerprint thread as soon as it is determined.
-	 */
-	private String fingerprint;
-
-	/**
-	 * The exception that was caught when determining the fingerprint of this
-	 * node, if any.
-	 */
-	protected PuppeTorException caughtException;
-
-	/**
-	 * The pattern for valid IP v4 addresses in dotted decimal notation.
-	 */
-	private static final Pattern validIpAddressPattern =
-			Pattern
-					.compile("([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"
-							+ "(\\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){1,3}");
-
-	/**
-	 * Port on which the Tor node will be listening for onion requests by other
-	 * Tor nodes.
-	 */
-	protected int orPort;
-
-	/**
-	 * Creates a new <code>RouterNodeImpl</code> and adds it to the given
-	 * <code>network</code>, but does not yet write its configuration to disk
-	 * or start the corresponding Tor process.
-	 *
-	 * @param network
-	 *            Network configuration to which this node belongs.
-	 * @param nodeName
-	 *            The name of the new node which may only consist of between 1
-	 *            and 19 alpha-numeric characters.
-	 * @param controlPort
-	 *            Port on which the Tor node will be listening for us as its
-	 *            controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            Port on which the Tor node will be listening for SOCKS
-	 *            connection requests. May not be negative or greater than
-	 *            65535.
-	 * @param orPort
-	 *            Port on which the Tor node will be listening for onion
-	 *            requests by other Tor nodes. May not be negative or greater
-	 *            than 65535.
-	 * @param dirPort
-	 *            Port on which the Tor node will be listening for directory
-	 *            requests from other Tor nodes. May not be negative or greater
-	 *            than 65535.
-	 * @param serverIpAddress
-	 *            The IP address on which the node will listen. Must be a valid
-	 *            IP v4 address in dotted decimal notation. May not be
-	 *            <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             If at least one of the parameters is <code>null</code> or
-	 *             has an invalid value.
-	 */
-	RouterNodeImpl(final NetworkImpl network, final String nodeName,
-			final int controlPort, final int socksPort, final int orPort,
-			final int dirPort, final String serverIpAddress) {
-
-		// create superclass instance; parameter checking is done in super
-		// constructor
-		super(network, nodeName, controlPort, socksPort);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "RouterNodeImpl",
-				new Object[] { network, nodeName, controlPort, socksPort,
-						orPort, dirPort, serverIpAddress });
-
-		// check parameters
-		if (orPort < 0 || orPort > 65535 || dirPort < 0 || dirPort > 65535
-				|| serverIpAddress == null
-				|| !validIpAddressPattern.matcher(serverIpAddress).matches()) {
-			final IllegalArgumentException e =
-					new IllegalArgumentException("nodeName=" + nodeName
-							+ ", controlPort=" + controlPort + ", socksPort="
-							+ socksPort + ", orPort=" + orPort + ", dirPort="
-							+ dirPort + ", serverIpAddress='" + serverIpAddress
-							+ "'");
-			logger.throwing(this.getClass().getName(), "RouterNodeImpl", e);
-			throw e;
-		}
-
-		// remember parameters
-		this.orPort = orPort;
-		this.dirPort = dirPort;
-		this.serverIpAddress = serverIpAddress;
-
-		// extend configuration by template configuration of router nodes
-		configuration.addAll(templateConfiguration);
-
-		// add further configuration to make this node a router node
-		configuration.add("ORPort " + orPort);
-		configuration.add("Nickname " + nodeName);
-
-		// all routers mirror the directory
-		configuration.add("DirPort " + dirPort);
-
-		// the address of this node should be manually specified and not guessed
-		// by Tor
-		configuration.add("Address " + serverIpAddress);
-
-		// the OR port may only be contacted locally
-		configuration.add("ORListenAddress " + serverIpAddress);
-
-		// offer directory only locally (either by being an authority, or by
-		// mirroring it)
-		configuration.add("DirListenAddress " + serverIpAddress);
-
-		// start a thread to determine the node's fingerprint in the background
-		determineFingerprint();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "RouterNodeImpl");
-	}
-
-	public synchronized String getFingerprint() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "getFingerprint");
-
-		// wait until either the fingerprint has been determined or an exception
-		// was caught
-		while (fingerprint == null && caughtException == null) {
-			try {
-				wait();
-			} catch (final InterruptedException e) {
-				// do nothing
-			}
-		}
-
-		if (caughtException != null) {
-			logger.throwing(this.getClass().getName(), "getFingerprint",
-					caughtException);
-			throw caughtException;
-		}
-
-		// log exiting
-		logger
-				.exiting(this.getClass().getName(), "getFingerprint",
-						fingerprint);
-		return fingerprint;
-	}
-
-	@Override
-	public String toString() {
-		return super.toString() + ", orPort=" + orPort + ", dirPort=" + dirPort;
-	}
-
-	public int getDirPort() {
-		return dirPort;
-	}
-
-	public int getOrPort() {
-		return orPort;
-	}
-
-	/**
-	 * Determines the fingerprint of this node by starting a background thread
-	 * that performs this operation.
-	 */
-	protected synchronized void determineFingerprint() {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "determineFingerprint");
-
-		// start a thread to determine this node's fingerprint
-		final FingerprintThread fingerprintThread = new FingerprintThread();
-		fingerprintThread.setName(nodeName + " Fingerprint Resolver");
-		fingerprintThread.start();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "determineFingerprint");
-	}
-
-	/**
-	 * Template configuration of router nodes.
-	 */
-	static List<String> templateConfiguration;
-
-	static {
-		templateConfiguration = new ArrayList<String>();
-
-		templateConfiguration.add("ContactInfo wont@xxxxxxxxx");
-		templateConfiguration.add("HidServDirectoryV2 1");
-
-		// TODO This is now contained in proposal 135.
-		// templateConfiguration.add("ExitPolicyRejectPrivate 0");
-		// templateConfiguration.add("AssumeReachable 1");
-		// templateConfiguration.add("ServerDNSAllowBrokenResolvConf 1");
-	}
+    /**
+     * Internal thread class that is used to determine fingerprints in parallel,
+     * which can take a few seconds.
+     */
+    private class FingerprintThread extends Thread {
+
+        @Override
+        public void run() {
+
+            // log entering
+            logger.entering(this.getClass().getName(), "run");
+
+            // create file reference for temporary config file
+            final File tempConfigFile =
+                    new File(workingDir.getAbsolutePath() + File.separator
+                            + "torrc.tmp");
+
+            // compose a modified config file, including a DirServer option with
+            // false fingerprint; this is necessary, because otherwise Tor
+            // would not accept that this router node might have a private IP
+            // address, but connects to the public directory servers
+            final List<String> copyOfConfig =
+                    new ArrayList<String>(configuration);
+            final String fakeDirServerString =
+                    "DirServer "
+                            + nodeName
+                            + " 127.0.0.1:"
+                            + dirPort
+                            + " 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000";
+            copyOfConfig.add(fakeDirServerString);
+
+            // write config file
+            try {
+                final BufferedWriter bw =
+                        new BufferedWriter(new FileWriter(tempConfigFile));
+                for (final String c : copyOfConfig) {
+                    bw.write(c + "\n");
+                }
+                bw.close();
+            } catch (final IOException e) {
+                final PuppeTorException ex =
+                        new PuppeTorException(
+                                "Could not write configuration file!", e);
+                logger.log(Level.WARNING, "Could not start Tor process!", ex);
+                setCaughtException(ex);
+                return;
+            }
+
+            // start process with option --list-fingerprint
+            final ProcessBuilder processBuilder =
+                    new ProcessBuilder(torExecutable.getPath(),
+                            "--list-fingerprint", "-f", "torrc.tmp");
+            processBuilder.directory(workingDir);
+            processBuilder.redirectErrorStream(true);
+            Process tmpProcess = null;
+            try {
+                tmpProcess = processBuilder.start();
+            } catch (final IOException e) {
+                final PuppeTorException ex =
+                        new PuppeTorException(
+                                "Could not start Tor process temporarily with "
+                                        + "--list-fingerprint option!", e);
+                logger.log(Level.WARNING, "Could not start Tor process!", ex);
+                setCaughtException(ex);
+                return;
+            }
+
+            // wait for process to terminate
+            int exitValue = 0;
+            try {
+                exitValue = tmpProcess.waitFor();
+            } catch (final InterruptedException e) {
+                final PuppeTorException ex =
+                        new PuppeTorException(
+                                "Interrupted while waiting for Tor process to exit!",
+                                e);
+                logger.log(Level.WARNING,
+                        "Temporary Tor process was interrupted!", ex);
+                setCaughtException(ex);
+                return;
+            }
+
+            if (exitValue != 0) {
+                final PuppeTorException ex =
+                        new PuppeTorException(
+                                "Could not start Tor process temporarily with "
+                                        + "--list-fingerprint option! Tor exited with "
+                                        + "exit value "
+                                        + exitValue
+                                        + "! Please go check the config options in "
+                                        + tempConfigFile + " manually!");
+                logger.log(Level.WARNING, "Could not start Tor process!", ex);
+                setCaughtException(ex);
+                return;
+            }
+
+            // read fingerprint from file
+            final File fingerprintFile =
+                    new File(workingDir.getAbsolutePath() + File.separator
+                            + "fingerprint");
+            try {
+                final BufferedReader br2 =
+                        new BufferedReader(new FileReader(fingerprintFile));
+                setFingerprint(br2.readLine());
+                br2.close();
+            } catch (final IOException e) {
+                final PuppeTorException ex =
+                        new PuppeTorException(
+                                "Could not read fingerprint from file!", e);
+                logger.log(Level.WARNING, "Could not read fingerprint file!",
+                        ex);
+                setCaughtException(ex);
+                return;
+            }
+
+            // delete temporary config file
+            tempConfigFile.delete();
+
+            // log exiting
+            logger.exiting(this.getClass().getName(), "run");
+        }
+    }
+
+    /**
+     * Invoked by the fingerprint thread: sets the determined fingerprint string
+     *
+     * @param fingerprint
+     *            The determined fingerprint string.
+     */
+    private synchronized void setFingerprint(final String fingerprint) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "setFingerprint",
+                fingerprint);
+
+        // remember fingerprint and notify all waiting threads
+        this.fingerprint = fingerprint;
+        notifyAll();
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "setFingerprint");
+    }
+
+    /**
+     * Invoked by the fingerprint thread: sets the exception that occurred when
+     * trying to determine the fingerprint.
+     *
+     * @param caughtException
+     *            The exception that occurred when trying to determine the
+     *            fingerprint.
+     */
+    protected synchronized void setCaughtException(
+            final PuppeTorException caughtException) {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "setCaughtException",
+                caughtException);
+
+        // remember caught exception and notify all waiting threads
+        this.caughtException = caughtException;
+        notifyAll();
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "setCaughtException");
+    }
+
+    /**
+     * Port on which the Tor node will be listening for directory requests from
+     * other Tor nodes.
+     */
+    protected int dirPort;
+
+    /**
+     * The IP v4 address on which the node will listen in dotted decimal
+     * notation.
+     */
+    protected String serverIpAddress;
+
+    /**
+     * The fingerprint of this node that is determined as hash value of its
+     * onion key. It is initialized with <code>null</code> and set by the
+     * fingerprint thread as soon as it is determined.
+     */
+    private String fingerprint;
+
+    /**
+     * The exception that was caught when determining the fingerprint of this
+     * node, if any.
+     */
+    protected PuppeTorException caughtException;
+
+    /**
+     * The pattern for valid IP v4 addresses in dotted decimal notation.
+     */
+    private static final Pattern validIpAddressPattern =
+            Pattern
+                    .compile("([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"
+                            + "(\\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){1,3}");
+
+    /**
+     * Port on which the Tor node will be listening for onion requests by other
+     * Tor nodes.
+     */
+    protected int orPort;
+
+    /**
+     * Creates a new <code>RouterNodeImpl</code> and adds it to the given
+     * <code>network</code>, but does not yet write its configuration to disk
+     * or start the corresponding Tor process.
+     *
+     * @param network
+     *            Network configuration to which this node belongs.
+     * @param nodeName
+     *            The name of the new node which may only consist of between 1
+     *            and 19 alpha-numeric characters.
+     * @param controlPort
+     *            Port on which the Tor node will be listening for us as its
+     *            controller. May not be negative or greater than 65535.
+     * @param socksPort
+     *            Port on which the Tor node will be listening for SOCKS
+     *            connection requests. May not be negative or greater than
+     *            65535.
+     * @param orPort
+     *            Port on which the Tor node will be listening for onion
+     *            requests by other Tor nodes. May not be negative or greater
+     *            than 65535.
+     * @param dirPort
+     *            Port on which the Tor node will be listening for directory
+     *            requests from other Tor nodes. May not be negative or greater
+     *            than 65535.
+     * @param serverIpAddress
+     *            The IP address on which the node will listen. Must be a valid
+     *            IP v4 address in dotted decimal notation. May not be
+     *            <code>null</code>.
+     * @throws IllegalArgumentException
+     *             If at least one of the parameters is <code>null</code> or
+     *             has an invalid value.
+     */
+    RouterNodeImpl(final NetworkImpl network, final String nodeName,
+            final int controlPort, final int socksPort, final int orPort,
+            final int dirPort, final String serverIpAddress) {
+
+        // create superclass instance; parameter checking is done in super
+        // constructor
+        super(network, nodeName, controlPort, socksPort);
+
+        // log entering
+        logger.entering(this.getClass().getName(), "RouterNodeImpl",
+                new Object[] { network, nodeName, controlPort, socksPort,
+                        orPort, dirPort, serverIpAddress });
+
+        // check parameters
+        if (orPort < 0 || orPort > 65535 || dirPort < 0 || dirPort > 65535
+                || serverIpAddress == null
+                || !validIpAddressPattern.matcher(serverIpAddress).matches()) {
+            final IllegalArgumentException e =
+                    new IllegalArgumentException("nodeName=" + nodeName
+                            + ", controlPort=" + controlPort + ", socksPort="
+                            + socksPort + ", orPort=" + orPort + ", dirPort="
+                            + dirPort + ", serverIpAddress='" + serverIpAddress
+                            + "'");
+            logger.throwing(this.getClass().getName(), "RouterNodeImpl", e);
+            throw e;
+        }
+
+        // remember parameters
+        this.orPort = orPort;
+        this.dirPort = dirPort;
+        this.serverIpAddress = serverIpAddress;
+
+        // extend configuration by template configuration of router nodes
+        configuration.addAll(templateConfiguration);
+
+        // add further configuration to make this node a router node
+        configuration.add("ORPort " + orPort);
+        configuration.add("Nickname " + nodeName);
+
+        // all routers mirror the directory
+        configuration.add("DirPort " + dirPort);
+
+        // the address of this node should be manually specified and not guessed
+        // by Tor
+        configuration.add("Address " + serverIpAddress);
+
+        // the OR port may only be contacted locally
+        configuration.add("ORListenAddress " + serverIpAddress);
+
+        // offer directory only locally (either by being an authority, or by
+        // mirroring it)
+        configuration.add("DirListenAddress " + serverIpAddress);
+
+        // start a thread to determine the node's fingerprint in the background
+        determineFingerprint();
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "RouterNodeImpl");
+    }
+
+    public synchronized String getFingerprint() throws PuppeTorException {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "getFingerprint");
+
+        // wait until either the fingerprint has been determined or an exception
+        // was caught
+        while (fingerprint == null && caughtException == null) {
+            try {
+                wait();
+            } catch (final InterruptedException e) {
+                // do nothing
+            }
+        }
+
+        if (caughtException != null) {
+            logger.throwing(this.getClass().getName(), "getFingerprint",
+                    caughtException);
+            throw caughtException;
+        }
+
+        // log exiting
+        logger
+                .exiting(this.getClass().getName(), "getFingerprint",
+                        fingerprint);
+        return fingerprint;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + ", orPort=" + orPort + ", dirPort=" + dirPort;
+    }
+
+    public int getDirPort() {
+        return dirPort;
+    }
+
+    public int getOrPort() {
+        return orPort;
+    }
+
+    /**
+     * Determines the fingerprint of this node by starting a background thread
+     * that performs this operation.
+     */
+    protected synchronized void determineFingerprint() {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "determineFingerprint");
+
+        // start a thread to determine this node's fingerprint
+        final FingerprintThread fingerprintThread = new FingerprintThread();
+        fingerprintThread.setName(nodeName + " Fingerprint Resolver");
+        fingerprintThread.start();
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "determineFingerprint");
+    }
+
+    /**
+     * Template configuration of router nodes.
+     */
+    static List<String> templateConfiguration;
+
+    static {
+        templateConfiguration = new ArrayList<String>();
+
+        templateConfiguration.add("ContactInfo wont@xxxxxxxxx");
+        templateConfiguration.add("HidServDirectoryV2 1");
+
+        // TODO This is now contained in proposal 135.
+        // templateConfiguration.add("ExitPolicyRejectPrivate 0");
+        // templateConfiguration.add("AssumeReachable 1");
+        // templateConfiguration.add("ServerDNSAllowBrokenResolvConf 1");
+    }
 }
diff --git a/src/org/torproject/puppetor/impl/ServerApplicationImpl.java b/src/org/torproject/puppetor/impl/ServerApplicationImpl.java
index 95e1536..c855ea5 100644
--- a/src/org/torproject/puppetor/impl/ServerApplicationImpl.java
+++ b/src/org/torproject/puppetor/impl/ServerApplicationImpl.java
@@ -25,328 +25,328 @@ import org.torproject.puppetor.ServerEventType;
  */
 public class ServerApplicationImpl implements ServerApplication {
 
-	/**
-	 * Internal thread class that is used to process an incoming request.
-	 */
-	private class HandlerThread extends Thread {
-
-		/**
-		 * Accepted socket on which the request came in.
-		 */
-		private Socket handleSocket = null;
-
-		/**
-		 * Creates a new thread to handle the request coming in on
-		 * <code>handleSocket</code>, but does not start handling it.
-		 *
-		 * @param handleSocket
-		 *            Accepted socket on which the request came in.
-		 */
-		public HandlerThread(final Socket handleSocket) {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "HandlerThread",
-					handleSocket);
-
-			// remember parameter
-			this.handleSocket = handleSocket;
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "HandlerThread");
-		}
-
-		@Override
-		public void run() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "run");
-
-			try {
-
-				// wait for request (don't mind the content)
-				final BufferedReader in =
-						new BufferedReader(new InputStreamReader(handleSocket
-								.getInputStream()));
-				in.read();
-
-				// send event to event manager
-				eventManager.observeInternalEvent(System.currentTimeMillis(),
-						getServerApplicationName(),
-						ServerEventType.SERVER_RECEIVING_REQUEST_SENDING_REPLY,
-						"Receiving request.");
-
-				// write response
-				final PrintStream out =
-						new PrintStream(handleSocket.getOutputStream());
-				out.print("HTTP/1.0 200 OK\r\n");
-
-			} catch (final IOException e) {
-				logger.log(Level.SEVERE,
-						"I/O exception while handling incoming request!");
-				// we can't do more, because nobody takes notice of this thread.
-
-				// log exiting
-				logger.exiting(this.getClass().getName(), "run");
-				return;
-				// TODO do we need more?
-			}
-
-			// close socket
-			try {
-				handleSocket.close();
-			} catch (final IOException e) {
-				logger
-						.log(Level.WARNING,
-								"I/O exception while closing socket!");
-
-				// log exiting
-				logger.exiting(this.getClass().getName(), "run");
-				return;
-			}
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "run");
-		}
-	}
-
-	/**
-	 * Internal thread class that is used to listen for requests.
-	 */
-	private class ListenThread extends Thread {
-
-		/**
-		 * Flag to remember whether this thread listens for requests at the
-		 * moment (<code>true</code>), or has been stopped (<code>false</code>).
-		 */
-		private boolean connected;
-
-		/**
-		 * Creates a new thread to listen for requests, but does not start
-		 * listening, yet.
-		 */
-		ListenThread() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "ListenThread");
-
-			// start connected
-			connected = true;
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "ListenThread");
-		}
-
-		@Override
-		public void run() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "run");
-
-			try {
-
-				// create server socket
-				ServerSocket serverSocket = null;
-				try {
-					serverSocket = new ServerSocket(serverPort);
-				} catch (final IOException ioe) {
-					logger.log(Level.SEVERE,
-							"Can't open server socket on port " + serverPort
-									+ "!");
-
-					// log exiting
-					logger.exiting(this.getClass().getName(), "run");
-					return;
-				}
-
-				// as long as we are connected, accept incoming requests
-				logger.log(Level.FINE, "Listening on port " + serverPort
-						+ "...");
-				while (connected) {
-					Socket incomingConnection = null;
-					try {
-						incomingConnection = serverSocket.accept();
-					} catch (final Exception e) {
-						logger
-								.log(
-										Level.SEVERE,
-										"Exception while accepting socket requests! Stopping listening!",
-										e);
-						break;
-					}
-					new HandlerThread(incomingConnection).start();
-				}
-
-			} catch (final Exception e) {
-
-				// log that we have been interrupted
-				logger.log(Level.WARNING, "Server has been interrupted!", e);
-			}
-
-			// mark as disconnected
-			connected = false;
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "run");
-		}
-
-		/**
-		 * Stops listening on server socket.
-		 */
-		public void stopListening() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "stopListening");
-
-			// change connected state to false and interrupt thread
-			connected = false;
-			interrupt();
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "stopListening");
-		}
-	}
-
-	/**
-	 * Event manager that handles all events concerning this server application.
-	 */
-	private final EventManagerImpl eventManager;
-
-	/**
-	 * Logger for this server which is called "server." plus the name of this
-	 * server application.
-	 */
-	private final Logger logger;
-
-	/**
-	 * Name of this server application that is used as logger name of this node.
-	 */
-	private final String serverApplicationName;
-
-	/**
-	 * Port on which this server will listen for incoming requests.
-	 */
-	private final int serverPort;
-
-	/**
-	 * Thread that listens for requests in the background.
-	 */
-	private Thread serverThread;
-
-	/**
-	 * Creates a new HTTP server application within this JVM, but does not yet
-	 * listen for incoming requests.
-	 *
-	 * @param network
-	 *            Network to which this HTTP server belongs; at the moment this
-	 *            is only used to determine the event manager instance.
-	 * @param serverApplicationName
-	 *            Name of this server that is used as part of the logger name.
-	 * @param serverPort
-	 *            Port on which this server will listen for incoming requests.
-	 * @throws IllegalArgumentException
-	 *             If at least one of the parameters is <code>null</code> or
-	 *             has an invalid value.
-	 */
-	ServerApplicationImpl(final NetworkImpl network,
-			final String serverApplicationName, final int serverPort) {
-
-		// check if serverApplicationName can be used as logger name
-		if (serverApplicationName == null
-				|| serverApplicationName.length() == 0) {
-			throw new IllegalArgumentException(
-					"Invalid serverApplicationName: " + serverApplicationName);
-		}
-
-		// create logger
-		logger = Logger.getLogger("server." + serverApplicationName);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "ServerApplicationImpl",
-				new Object[] { network, serverApplicationName, serverPort });
-
-		// check parameters
-		if (network == null || serverPort < 0 || serverPort > 65535) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "ServerApplicationImpl",
-					e);
-			throw e;
-		}
-
-		// remember parameters
-		this.serverApplicationName = serverApplicationName;
-		this.serverPort = serverPort;
-
-		// obtain reference on event manager
-		eventManager = network.getEventManagerImpl();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "ServerApplicationImpl");
-	}
-
-	public synchronized void startListening() {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "listen");
-
-		// check if we are already listening
-		if (serverThread != null) {
-			final IllegalStateException e =
-					new IllegalStateException("We are already listening!");
-			logger.throwing(this.getClass().getName(), "listen", e);
-			throw e;
-		}
-
-		// create a thread that listens in the background
-		serverThread = new ListenThread();
-		serverThread.setName("Reply Thread");
-		serverThread.setDaemon(true);
-		serverThread.start();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "listen");
-	}
-
-	public synchronized void stopListening() {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "stopListening");
-
-		// check if we are listening
-		if (serverThread == null) {
-			final IllegalStateException e =
-					new IllegalStateException("We are not listening!");
-			logger.throwing(this.getClass().getName(), "stopListening", e);
-			throw e;
-		}
-
-		// log this event
-		logger.log(Level.FINE, "Shutting down server");
-
-		// interrupt thread
-		serverThread.interrupt();
-
-		// unset listening thread
-		serverThread = null;
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "stopListening");
-	}
-
-	public synchronized boolean isListening() {
-		return serverThread != null;
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": serverApplicationName=\""
-				+ serverApplicationName + "\", serverPort=" + serverPort;
-	}
-
-	public String getServerApplicationName() {
-		return serverApplicationName;
-	}
-
-	public int getServerPort() {
-		return serverPort;
-	}
+    /**
+     * Internal thread class that is used to process an incoming request.
+     */
+    private class HandlerThread extends Thread {
+
+        /**
+         * Accepted socket on which the request came in.
+         */
+        private Socket handleSocket = null;
+
+        /**
+         * Creates a new thread to handle the request coming in on
+         * <code>handleSocket</code>, but does not start handling it.
+         *
+         * @param handleSocket
+         *            Accepted socket on which the request came in.
+         */
+        public HandlerThread(final Socket handleSocket) {
+
+            // log entering
+            logger.entering(this.getClass().getName(), "HandlerThread",
+                    handleSocket);
+
+            // remember parameter
+            this.handleSocket = handleSocket;
+
+            // log exiting
+            logger.exiting(this.getClass().getName(), "HandlerThread");
+        }
+
+        @Override
+        public void run() {
+
+            // log entering
+            logger.entering(this.getClass().getName(), "run");
+
+            try {
+
+                // wait for request (don't mind the content)
+                final BufferedReader in =
+                        new BufferedReader(new InputStreamReader(handleSocket
+                                .getInputStream()));
+                in.read();
+
+                // send event to event manager
+                eventManager.observeInternalEvent(System.currentTimeMillis(),
+                        getServerApplicationName(),
+                        ServerEventType.SERVER_RECEIVING_REQUEST_SENDING_REPLY,
+                        "Receiving request.");
+
+                // write response
+                final PrintStream out =
+                        new PrintStream(handleSocket.getOutputStream());
+                out.print("HTTP/1.0 200 OK\r\n");
+
+            } catch (final IOException e) {
+                logger.log(Level.SEVERE,
+                        "I/O exception while handling incoming request!");
+                // we can't do more, because nobody takes notice of this thread.
+
+                // log exiting
+                logger.exiting(this.getClass().getName(), "run");
+                return;
+                // TODO do we need more?
+            }
+
+            // close socket
+            try {
+                handleSocket.close();
+            } catch (final IOException e) {
+                logger
+                        .log(Level.WARNING,
+                                "I/O exception while closing socket!");
+
+                // log exiting
+                logger.exiting(this.getClass().getName(), "run");
+                return;
+            }
+
+            // log exiting
+            logger.exiting(this.getClass().getName(), "run");
+        }
+    }
+
+    /**
+     * Internal thread class that is used to listen for requests.
+     */
+    private class ListenThread extends Thread {
+
+        /**
+         * Flag to remember whether this thread listens for requests at the
+         * moment (<code>true</code>), or has been stopped (<code>false</code>).
+         */
+        private boolean connected;
+
+        /**
+         * Creates a new thread to listen for requests, but does not start
+         * listening, yet.
+         */
+        ListenThread() {
+
+            // log entering
+            logger.entering(this.getClass().getName(), "ListenThread");
+
+            // start connected
+            connected = true;
+
+            // log exiting
+            logger.exiting(this.getClass().getName(), "ListenThread");
+        }
+
+        @Override
+        public void run() {
+
+            // log entering
+            logger.entering(this.getClass().getName(), "run");
+
+            try {
+
+                // create server socket
+                ServerSocket serverSocket = null;
+                try {
+                    serverSocket = new ServerSocket(serverPort);
+                } catch (final IOException ioe) {
+                    logger.log(Level.SEVERE,
+                            "Can't open server socket on port " + serverPort
+                                    + "!");
+
+                    // log exiting
+                    logger.exiting(this.getClass().getName(), "run");
+                    return;
+                }
+
+                // as long as we are connected, accept incoming requests
+                logger.log(Level.FINE, "Listening on port " + serverPort
+                        + "...");
+                while (connected) {
+                    Socket incomingConnection = null;
+                    try {
+                        incomingConnection = serverSocket.accept();
+                    } catch (final Exception e) {
+                        logger
+                                .log(
+                                        Level.SEVERE,
+                                        "Exception while accepting socket requests! Stopping listening!",
+                                        e);
+                        break;
+                    }
+                    new HandlerThread(incomingConnection).start();
+                }
+
+            } catch (final Exception e) {
+
+                // log that we have been interrupted
+                logger.log(Level.WARNING, "Server has been interrupted!", e);
+            }
+
+            // mark as disconnected
+            connected = false;
+
+            // log exiting
+            logger.exiting(this.getClass().getName(), "run");
+        }
+
+        /**
+         * Stops listening on server socket.
+         */
+        public void stopListening() {
+
+            // log entering
+            logger.entering(this.getClass().getName(), "stopListening");
+
+            // change connected state to false and interrupt thread
+            connected = false;
+            interrupt();
+
+            // log exiting
+            logger.exiting(this.getClass().getName(), "stopListening");
+        }
+    }
+
+    /**
+     * Event manager that handles all events concerning this server application.
+     */
+    private final EventManagerImpl eventManager;
+
+    /**
+     * Logger for this server which is called "server." plus the name of this
+     * server application.
+     */
+    private final Logger logger;
+
+    /**
+     * Name of this server application that is used as logger name of this node.
+     */
+    private final String serverApplicationName;
+
+    /**
+     * Port on which this server will listen for incoming requests.
+     */
+    private final int serverPort;
+
+    /**
+     * Thread that listens for requests in the background.
+     */
+    private Thread serverThread;
+
+    /**
+     * Creates a new HTTP server application within this JVM, but does not yet
+     * listen for incoming requests.
+     *
+     * @param network
+     *            Network to which this HTTP server belongs; at the moment this
+     *            is only used to determine the event manager instance.
+     * @param serverApplicationName
+     *            Name of this server that is used as part of the logger name.
+     * @param serverPort
+     *            Port on which this server will listen for incoming requests.
+     * @throws IllegalArgumentException
+     *             If at least one of the parameters is <code>null</code> or
+     *             has an invalid value.
+     */
+    ServerApplicationImpl(final NetworkImpl network,
+            final String serverApplicationName, final int serverPort) {
+
+        // check if serverApplicationName can be used as logger name
+        if (serverApplicationName == null
+                || serverApplicationName.length() == 0) {
+            throw new IllegalArgumentException(
+                    "Invalid serverApplicationName: " + serverApplicationName);
+        }
+
+        // create logger
+        logger = Logger.getLogger("server." + serverApplicationName);
+
+        // log entering
+        logger.entering(this.getClass().getName(), "ServerApplicationImpl",
+                new Object[] { network, serverApplicationName, serverPort });
+
+        // check parameters
+        if (network == null || serverPort < 0 || serverPort > 65535) {
+            final IllegalArgumentException e = new IllegalArgumentException();
+            logger.throwing(this.getClass().getName(), "ServerApplicationImpl",
+                    e);
+            throw e;
+        }
+
+        // remember parameters
+        this.serverApplicationName = serverApplicationName;
+        this.serverPort = serverPort;
+
+        // obtain reference on event manager
+        eventManager = network.getEventManagerImpl();
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "ServerApplicationImpl");
+    }
+
+    public synchronized void startListening() {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "listen");
+
+        // check if we are already listening
+        if (serverThread != null) {
+            final IllegalStateException e =
+                    new IllegalStateException("We are already listening!");
+            logger.throwing(this.getClass().getName(), "listen", e);
+            throw e;
+        }
+
+        // create a thread that listens in the background
+        serverThread = new ListenThread();
+        serverThread.setName("Reply Thread");
+        serverThread.setDaemon(true);
+        serverThread.start();
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "listen");
+    }
+
+    public synchronized void stopListening() {
+
+        // log entering
+        logger.entering(this.getClass().getName(), "stopListening");
+
+        // check if we are listening
+        if (serverThread == null) {
+            final IllegalStateException e =
+                    new IllegalStateException("We are not listening!");
+            logger.throwing(this.getClass().getName(), "stopListening", e);
+            throw e;
+        }
+
+        // log this event
+        logger.log(Level.FINE, "Shutting down server");
+
+        // interrupt thread
+        serverThread.interrupt();
+
+        // unset listening thread
+        serverThread = null;
+
+        // log exiting
+        logger.exiting(this.getClass().getName(), "stopListening");
+    }
+
+    public synchronized boolean isListening() {
+        return serverThread != null;
+    }
+
+    @Override
+    public String toString() {
+        return this.getClass().getSimpleName() + ": serverApplicationName=\""
+                + serverApplicationName + "\", serverPort=" + serverPort;
+    }
+
+    public String getServerApplicationName() {
+        return serverApplicationName;
+    }
+
+    public int getServerPort() {
+        return serverPort;
+    }
 }
diff --git a/src/org/torproject/puppetor/rmi/AbstractMasterFactory.java b/src/org/torproject/puppetor/rmi/AbstractMasterFactory.java
index 85508e3..fdd412b 100644
--- a/src/org/torproject/puppetor/rmi/AbstractMasterFactory.java
+++ b/src/org/torproject/puppetor/rmi/AbstractMasterFactory.java
@@ -18,62 +18,61 @@ import org.torproject.puppetor.rmi.tests.TestRegistration;
  */
 public abstract class AbstractMasterFactory {
 
-	/**
-	 * Hold the concrete RemotePuppeTorFactory that will be used to create the
-	 * implementation classes.
-	 */
-	private static AbstractMasterFactory factory;
+    /**
+     * Hold the concrete RemotePuppeTorFactory that will be used to create the
+     * implementation classes.
+     */
+    private static AbstractMasterFactory factory;
 
-	/**
-	 * @return a new concrete
-	 *         <code>AbstractRemotePuppeTorFactory<code>subclass as
-	 *     specified by the initialization
-	 */
-	final public synchronized static AbstractMasterFactory getInstance() {
-		return factory;
-	}
+    /**
+     * @return a new concrete
+     *         <code>AbstractRemotePuppeTorFactory<code>subclass as
+     *     specified by the initialization
+     */
+    final public synchronized static AbstractMasterFactory getInstance() {
+        return factory;
+    }
 
-	/**
-	 * @param factory
-	 *            save this as the factory if this hasn't been called before.
-	 */
-	final public synchronized static void initialize(
-			final AbstractMasterFactory factory) {
-		if (AbstractMasterFactory.factory != null) {
-			throw new RuntimeException("Don't call initialize twice!");
-		}
-		AbstractMasterFactory.factory = factory;
-	}
+    /**
+     * @param factory
+     *            save this as the factory if this hasn't been called before.
+     */
+    final public synchronized static void initialize(
+            final AbstractMasterFactory factory) {
+        if (AbstractMasterFactory.factory != null) {
+            throw new RuntimeException("Don't call initialize twice!");
+        }
+        AbstractMasterFactory.factory = factory;
+    }
 
-	/**
-	 * Override this to create a subclass of <code>RemotePuppeTor</code>
-	 *
-	 * @param slave
-	 *            Create the server represantation for this connected slave
-	 * @return The new <code>RemotePuppeTor</code> instance
-	 */
-	public abstract Master createMaster(Slave slave) throws RemoteException;
+    /**
+     * Override this to create a subclass of <code>RemotePuppeTor</code>
+     *
+     * @param slave
+     *            Create the server represantation for this connected slave
+     * @return The new <code>RemotePuppeTor</code> instance
+     */
+    public abstract Master createMaster(Slave slave) throws RemoteException;
 
-	/**
-	 * Override this to create a subclass of <code>TestExecutor</code>.
-	 *
-	 * @return The <code>TestExecutor</code> instance.
-	 */
-	public abstract TestExecutor getTestExecutorInstance();
+    /**
+     * Override this to create a subclass of <code>TestExecutor</code>.
+     *
+     * @return The <code>TestExecutor</code> instance.
+     */
+    public abstract TestExecutor getTestExecutorInstance();
 
-	/**
-	 * Override this to create a subclass of <code>TestRegistration</code>.
-	 *
-	 * @return The <code>TestRegistration</code> instance.
-	 */
-	public abstract TestRegistration getTestRegistrationInstance();
-	
-	/**
-	 * XXX just for now so I don't forget.-SH
-	 */
-	public abstract NetworkDescription createNetworkDescription();
+    /**
+     * Override this to create a subclass of <code>TestRegistration</code>.
+     *
+     * @return The <code>TestRegistration</code> instance.
+     */
+    public abstract TestRegistration getTestRegistrationInstance();
 
-	
+    /**
+     * XXX just for now so I don't forget.-SH
+     */
+    public abstract NetworkDescription createNetworkDescription();
 
-//	public abstract Network createNetwork(String name);
+
+//    public abstract Network createNetwork(String name);
 }
diff --git a/src/org/torproject/puppetor/rmi/AbstractSlaveFactory.java b/src/org/torproject/puppetor/rmi/AbstractSlaveFactory.java
index ac5b2d7..dd4c141 100644
--- a/src/org/torproject/puppetor/rmi/AbstractSlaveFactory.java
+++ b/src/org/torproject/puppetor/rmi/AbstractSlaveFactory.java
@@ -19,71 +19,71 @@ import org.torproject.puppetor.rmi.Slave.OS;
  */
 public abstract class AbstractSlaveFactory {
 
-	/**
-	 * Hold the concrete RemotePuppeTorFactory that will be used to create
-	 * <code>Slave</code> instances. Uses
-	 * <code>AbstractSlaveFactory.class</code> for locking.
-	 */
-	private static AbstractSlaveFactory factory;
+    /**
+     * Hold the concrete RemotePuppeTorFactory that will be used to create
+     * <code>Slave</code> instances. Uses
+     * <code>AbstractSlaveFactory.class</code> for locking.
+     */
+    private static AbstractSlaveFactory factory;
 
-	/**
-	 * Hold the remote master object. We only have one per slave. XXX Maybe this
-	 * entire thing is more like a SlaveManager, not a factory. Oh well. Rename
-	 * later, I guess -SH
-	 */
-	private static RemoteMaster master;
+    /**
+     * Hold the remote master object. We only have one per slave. XXX Maybe this
+     * entire thing is more like a SlaveManager, not a factory. Oh well. Rename
+     * later, I guess -SH
+     */
+    private static RemoteMaster master;
 
-	/**
-	 * @return a new concrete <code>AbstractSlaveFactory<code>subclass as
-	 *     specified by the initialization
-	 */
-	final public synchronized static AbstractSlaveFactory getInstance() {
-		return factory;
-	}
+    /**
+     * @return a new concrete <code>AbstractSlaveFactory<code>subclass as
+     *     specified by the initialization
+     */
+    final public synchronized static AbstractSlaveFactory getInstance() {
+        return factory;
+    }
 
-	/**
-	 * @param factory
-	 *            save this as the factory if this hasn't been called before.
-	 *            XXX - We could throw an exception here, too. I will think
-	 *            about that.-SH
-	 */
-	final public synchronized static void initialize(
-			final AbstractSlaveFactory factory) {
-		if (AbstractSlaveFactory.factory == null) {
-			AbstractSlaveFactory.factory = factory;
-		}
-	}
+    /**
+     * @param factory
+     *            save this as the factory if this hasn't been called before.
+     *            XXX - We could throw an exception here, too. I will think
+     *            about that.-SH
+     */
+    final public synchronized static void initialize(
+            final AbstractSlaveFactory factory) {
+        if (AbstractSlaveFactory.factory == null) {
+            AbstractSlaveFactory.factory = factory;
+        }
+    }
 
-	/**
-	 * Register a new master. Note that calling this twice for different master
-	 * doesn't indicate an error, but trying to to set the same master again is
-	 * a bug. Setting this to null means the connection to the master was
-	 * closed.
-	 *
-	 * @param master
-	 *            The master that should be used from now on.
-	 */
-	final public synchronized static void setMaster(final RemoteMaster master) {
-		AbstractSlaveFactory.master = master;
-	}
+    /**
+     * Register a new master. Note that calling this twice for different master
+     * doesn't indicate an error, but trying to to set the same master again is
+     * a bug. Setting this to null means the connection to the master was
+     * closed.
+     *
+     * @param master
+     *            The master that should be used from now on.
+     */
+    final public synchronized static void setMaster(final RemoteMaster master) {
+        AbstractSlaveFactory.master = master;
+    }
 
-	/**
-	 * @return the RemoteMaster currently registered.
-	 */
-	final public synchronized static RemoteMaster getMaster() {
-		if (AbstractSlaveFactory.master == null) {
-			throw new IllegalStateException("No master set yet!");
-		}
-		return AbstractSlaveFactory.master;
-	}
+    /**
+     * @return the RemoteMaster currently registered.
+     */
+    final public synchronized static RemoteMaster getMaster() {
+        if (AbstractSlaveFactory.master == null) {
+            throw new IllegalStateException("No master set yet!");
+        }
+        return AbstractSlaveFactory.master;
+    }
 
-	/**
-	 * Override this to create a subclass of <code>Slave</code>
-	 *
-	 * @return The new <code>Slave</code> instance
-	 */
-	public abstract Slave createSlave(final String slaveName, final OS os,
-			final String ip, final Set<Integer> ports,
-			final Set<Integer> availableRevisions) throws NullPointerException;
+    /**
+     * Override this to create a subclass of <code>Slave</code>
+     *
+     * @return The new <code>Slave</code> instance
+     */
+    public abstract Slave createSlave(final String slaveName, final OS os,
+            final String ip, final Set<Integer> ports,
+            final Set<Integer> availableRevisions) throws NullPointerException;
 
 }
diff --git a/src/org/torproject/puppetor/rmi/LocalMaster.java b/src/org/torproject/puppetor/rmi/LocalMaster.java
index 50dbacb..e926bec 100644
--- a/src/org/torproject/puppetor/rmi/LocalMaster.java
+++ b/src/org/torproject/puppetor/rmi/LocalMaster.java
@@ -12,25 +12,25 @@ package org.torproject.puppetor.rmi;
  * @author Sebastian Hahn
  */
 public interface LocalMaster {
-	/**
-	 * @return the slave instance this connection refers to.
-	 */
-	public Slave getSlave();
+    /**
+     * @return the slave instance this connection refers to.
+     */
+    public Slave getSlave();
 
-	/**
-	 * Add a new AbstractTaskImpl for the connected slave.
-	 *
-	 * @param abstractTaskImpl
-	 */
-	public void addTask(Task task);
+    /**
+     * Add a new AbstractTaskImpl for the connected slave.
+     *
+     * @param abstractTaskImpl
+     */
+    public void addTask(Task task);
 
-	/**
-	 * @param taskId
-	 *            The task id that we want to get
-	 * @return Get the TaskResult for the Task with the specified taskId.
-	 * @throws InterruptedException
-	 *             if interrupted while waiting for the next TestResult
-	 */
-	public TaskResult getTaskResult(int taskId) throws InterruptedException;
+    /**
+     * @param taskId
+     *            The task id that we want to get
+     * @return Get the TaskResult for the Task with the specified taskId.
+     * @throws InterruptedException
+     *             if interrupted while waiting for the next TestResult
+     */
+    public TaskResult getTaskResult(int taskId) throws InterruptedException;
 
 }
diff --git a/src/org/torproject/puppetor/rmi/MasterConnector.java b/src/org/torproject/puppetor/rmi/MasterConnector.java
index 44ea315..0660770 100644
--- a/src/org/torproject/puppetor/rmi/MasterConnector.java
+++ b/src/org/torproject/puppetor/rmi/MasterConnector.java
@@ -17,15 +17,15 @@ import java.rmi.RemoteException;
  */
 public interface MasterConnector extends Remote {
 
-	/**
-	 * Called once by every connecting client so that the master knows about it
-	 * and has a chance to pass back a representation of itself.
-	 *
-	 * @param slave
-	 *            A slave representation as passed from the connecting slave
-	 * @return The representation of the master
-	 * @throws RemoteException
-	 */
-	public RemoteMaster registerClient(final Slave slave)
-			throws RemoteException;
+    /**
+     * Called once by every connecting client so that the master knows about it
+     * and has a chance to pass back a representation of itself.
+     *
+     * @param slave
+     *            A slave representation as passed from the connecting slave
+     * @return The representation of the master
+     * @throws RemoteException
+     */
+    public RemoteMaster registerClient(final Slave slave)
+            throws RemoteException;
 }
diff --git a/src/org/torproject/puppetor/rmi/Network.java b/src/org/torproject/puppetor/rmi/Network.java
index 4f4f6c2..cd1b954 100644
--- a/src/org/torproject/puppetor/rmi/Network.java
+++ b/src/org/torproject/puppetor/rmi/Network.java
@@ -13,67 +13,67 @@ import java.util.Set;
  * This class describes all Tor instances on the slaves that are used to form a
  * private Tor network, or the Tor instances that are added to the public
  * network and are used in a set of tests.
- * 
+ *
  * @author Sebastian Hahn
  */
 public interface Network extends Serializable {
 
-	/**
-	 * @param torInstance
-	 *  	add this <code>TorInstance</code> to the network.
-	 */
-	public void addTorInstance(TorInstance torInstance);
-	
-	/**
-	 * @return this network's name, mainly to identify it in logs.
-	 */
-	public String getName();
+    /**
+     * @param torInstance
+     *      add this <code>TorInstance</code> to the network.
+     */
+    public void addTorInstance(TorInstance torInstance);
 
-	/**
-	 * @param test
-	 *            add this <code>test</code> to the network.
-	 */
-	public void addTest(Test test);
+    /**
+     * @return this network's name, mainly to identify it in logs.
+     */
+    public String getName();
 
-	/**
-	 * @return a link for each slave that is part of this network.
-	 */
-	public Set<LocalMaster> getMasters();
+    /**
+     * @param test
+     *            add this <code>test</code> to the network.
+     */
+    public void addTest(Test test);
 
-	/**
-	 * @return all Tor instances that are authorities
-	 */
-	public Set<TorInstance> getDirectoryAuthorities();
+    /**
+     * @return a link for each slave that is part of this network.
+     */
+    public Set<LocalMaster> getMasters();
 
-	/**
-	 * @return all Tor instances that are routers, but not authorities
-	 */
-	public Set<TorInstance> getRoutersOnly();
+    /**
+     * @return all Tor instances that are authorities
+     */
+    public Set<TorInstance> getDirectoryAuthorities();
 
-	/**
-	 * @return all Tor instances that are routers (including authorities)
-	 */
-	public Set<TorInstance> getRouters();
+    /**
+     * @return all Tor instances that are routers, but not authorities
+     */
+    public Set<TorInstance> getRoutersOnly();
 
-	/**
-	 * @return all Tor instances that are proxis (but not authorities or
-	 *         routers)
-	 */
-	public Set<TorInstance> getProxiesOnly();
+    /**
+     * @return all Tor instances that are routers (including authorities)
+     */
+    public Set<TorInstance> getRouters();
 
-	/**
-	 * @return all Tor instances that are proxies (including authorities and
-	 *         routers)
-	 */
-	public Set<TorInstance> getProxies();
-	
-	/**
-	 * @return all Tests that can run on this network -SH
-	 */
-	public List<Test> getTests();
+    /**
+     * @return all Tor instances that are proxis (but not authorities or
+     *         routers)
+     */
+    public Set<TorInstance> getProxiesOnly();
 
-	/**
-	 * Is this a private network or part of the public one?
-	 */
-	public boolean isPrivateNetwork();
+    /**
+     * @return all Tor instances that are proxies (including authorities and
+     *         routers)
+     */
+    public Set<TorInstance> getProxies();
+
+    /**
+     * @return all Tests that can run on this network -SH
+     */
+    public List<Test> getTests();
+
+    /**
+     * Is this a private network or part of the public one?
+     */
+    public boolean isPrivateNetwork();
 }
diff --git a/src/org/torproject/puppetor/rmi/NetworkDescription.java b/src/org/torproject/puppetor/rmi/NetworkDescription.java
index 1636566..7e23208 100644
--- a/src/org/torproject/puppetor/rmi/NetworkDescription.java
+++ b/src/org/torproject/puppetor/rmi/NetworkDescription.java
@@ -8,9 +8,9 @@ package org.torproject.puppetor.rmi;
 import java.io.Serializable;
 
 public interface NetworkDescription extends Serializable {
-	public int necessaryProxies();
+    public int necessaryProxies();
 
-	public int necessaryRouters();
+    public int necessaryRouters();
 
-	public int necessaryDirectoryAuthorities();
+    public int necessaryDirectoryAuthorities();
 }
diff --git a/src/org/torproject/puppetor/rmi/RemoteMaster.java b/src/org/torproject/puppetor/rmi/RemoteMaster.java
index 07a1b87..65a1412 100644
--- a/src/org/torproject/puppetor/rmi/RemoteMaster.java
+++ b/src/org/torproject/puppetor/rmi/RemoteMaster.java
@@ -23,25 +23,25 @@ import java.rmi.RemoteException;
  */
 public interface RemoteMaster extends Remote {
 
-	/**
-	 * Slaves that must poll because they are not reachable can use this method
-	 * to ask the master for new work.
-	 *
-	 * @throws RemoteException
-	 *             RMI...
-	 * @throws InterruptedException
-	 *             when interrupted while waiting for the next job to arrive.
-	 */
-	public Task getNewTask() throws RemoteException, InterruptedException;
+    /**
+     * Slaves that must poll because they are not reachable can use this method
+     * to ask the master for new work.
+     *
+     * @throws RemoteException
+     *             RMI...
+     * @throws InterruptedException
+     *             when interrupted while waiting for the next job to arrive.
+     */
+    public Task getNewTask() throws RemoteException, InterruptedException;
 
-	/**
-	 * Report back the result of the task to the master. If possible, add an
-	 * error message. XXX Maybe down the road an error object is
-	 * needed/wanted-SH
-	 *
-	 * @throws RemoteException
-	 *             RMI
-	 */
-	public void reportTaskResult(TaskResult task) throws RemoteException,
-			InterruptedException;
+    /**
+     * Report back the result of the task to the master. If possible, add an
+     * error message. XXX Maybe down the road an error object is
+     * needed/wanted-SH
+     *
+     * @throws RemoteException
+     *             RMI
+     */
+    public void reportTaskResult(TaskResult task) throws RemoteException,
+            InterruptedException;
 }
diff --git a/src/org/torproject/puppetor/rmi/Slave.java b/src/org/torproject/puppetor/rmi/Slave.java
index 2f037a1..405a071 100644
--- a/src/org/torproject/puppetor/rmi/Slave.java
+++ b/src/org/torproject/puppetor/rmi/Slave.java
@@ -19,33 +19,33 @@ import java.util.Set;
  */
 public interface Slave extends Serializable {
 
-	public enum OS {
-		UNDEFINED, LINUX_I86_32, LINUX_I86_64, MAC_OSX_PPC, MAC_OSX_I86,
-		WINDOWS_XP_32, WINDOWS_XP_64, WINDOWS_VISTA_32, WINDOWS_VISTA_64
-	}
-
-	/**
-	 * @return the slave's name.
-	 */
-	public String getName();
-
-	/**
-	 * @return the slave's operating system as defined in <code>Slave.OS</code>.
-	 */
-	public OS getOS();
-
-	/**
-	 * @return the slave's IP address. XXX Test this with IPv6-SH
-	 */
-	public InetAddress getIP();
-
-	/**
-	 * @return the ports this slave can open
-	 */
-	public Set<Integer> openablePorts();
-
-	/**
-	 * @return Tor revisions available on the slave
-	 */
-	public Set<Integer> availableRevisions();
+    public enum OS {
+        UNDEFINED, LINUX_I86_32, LINUX_I86_64, MAC_OSX_PPC, MAC_OSX_I86,
+        WINDOWS_XP_32, WINDOWS_XP_64, WINDOWS_VISTA_32, WINDOWS_VISTA_64
+    }
+
+    /**
+     * @return the slave's name.
+     */
+    public String getName();
+
+    /**
+     * @return the slave's operating system as defined in <code>Slave.OS</code>.
+     */
+    public OS getOS();
+
+    /**
+     * @return the slave's IP address. XXX Test this with IPv6-SH
+     */
+    public InetAddress getIP();
+
+    /**
+     * @return the ports this slave can open
+     */
+    public Set<Integer> openablePorts();
+
+    /**
+     * @return Tor revisions available on the slave
+     */
+    public Set<Integer> availableRevisions();
 }
diff --git a/src/org/torproject/puppetor/rmi/Task.java b/src/org/torproject/puppetor/rmi/Task.java
index bb466e8..3273e65 100644
--- a/src/org/torproject/puppetor/rmi/Task.java
+++ b/src/org/torproject/puppetor/rmi/Task.java
@@ -11,28 +11,28 @@ import java.util.Collection;
 
 public interface Task extends Serializable {
 
-	/**
-	 * Execute the task. Must be called by the slave.
-	 *
-	 * @throws InterruptedException
-	 *             if interrupted while the task is running.
-	 * @throws RemoteException
-	 *             RMI
-	 */
-	public void execute() throws InterruptedException, RemoteException;
+    /**
+     * Execute the task. Must be called by the slave.
+     *
+     * @throws InterruptedException
+     *             if interrupted while the task is running.
+     * @throws RemoteException
+     *             RMI
+     */
+    public void execute() throws InterruptedException, RemoteException;
 
-	/**
-	 * Returns this tasks id. Task ids must be globally unique and larger than 0
-	 * or 0 to indicate a task that was sent out by the slave, not the master.
-	 */
-	public int getId();
+    /**
+     * Returns this tasks id. Task ids must be globally unique and larger than 0
+     * or 0 to indicate a task that was sent out by the slave, not the master.
+     */
+    public int getId();
 
-	/**
-	 * This method will be called by the <code>TestExecutor</code> to allow
-	 * tasks to collect additional information if they need it.
-	 * @param additionalInformation
-	 * 		offer this information.
-	 */
-	public void addAdditionalInformation(
-			Collection<String> additionalInformation);
+    /**
+     * This method will be called by the <code>TestExecutor</code> to allow
+     * tasks to collect additional information if they need it.
+     * @param additionalInformation
+     *         offer this information.
+     */
+    public void addAdditionalInformation(
+            Collection<String> additionalInformation);
 }
diff --git a/src/org/torproject/puppetor/rmi/TaskExecutionNotSuccessfulException.java b/src/org/torproject/puppetor/rmi/TaskExecutionNotSuccessfulException.java
index a37bb9a..8c47102 100644
--- a/src/org/torproject/puppetor/rmi/TaskExecutionNotSuccessfulException.java
+++ b/src/org/torproject/puppetor/rmi/TaskExecutionNotSuccessfulException.java
@@ -8,16 +8,16 @@ package org.torproject.puppetor.rmi;
 import java.util.Set;
 
 public class TaskExecutionNotSuccessfulException extends Exception {
-	private static final long serialVersionUID = 1L;
-	final private Set<TaskResult> taskResults;
+    private static final long serialVersionUID = 1L;
+    final private Set<TaskResult> taskResults;
 
-	public TaskExecutionNotSuccessfulException(final Set<TaskResult> taskResults) {
-		this.taskResults = taskResults;
-	}
+    public TaskExecutionNotSuccessfulException(final Set<TaskResult> taskResults) {
+        this.taskResults = taskResults;
+    }
 
-	public Set<TaskResult> getTaskResults() {
-		return taskResults;
-	}
+    public Set<TaskResult> getTaskResults() {
+        return taskResults;
+    }
 
-	// XXX Add some logging capabilities -SH
+    // XXX Add some logging capabilities -SH
 }
diff --git a/src/org/torproject/puppetor/rmi/TaskResult.java b/src/org/torproject/puppetor/rmi/TaskResult.java
index 7cd9f68..e697c3c 100644
--- a/src/org/torproject/puppetor/rmi/TaskResult.java
+++ b/src/org/torproject/puppetor/rmi/TaskResult.java
@@ -10,30 +10,30 @@ import java.io.Serializable;
 /**
  * After the completion of a <code>Task</code>, a <code>TaskResult</code> is
  * created to report the result of the task as well as logging information to
- * the Master.  
- * 
+ * the Master.
+ *
  * @author Sebastian Hahn
  */
 public interface TaskResult extends Serializable {
 
-	/**
-	 * @return
-	 * 		the task that we have a result for
-	 */
-	public Task getTask();
-	
-	/**
-	 * @return
-	 * 		<code>true</code> if the task execution was successful,
-	 * 		false otherwise.
-	 */
-	public boolean wasSuccessful();
-	
-	/**
-	 * A <code>Task</code> may optionally pass back a result that can be used
-	 * by the Master to manage further Task execution. 
-	 * @return
-	 * 		the result of this task or null if no result is set.
-	 */
-	public String getTaskResult();
-}
\ No newline at end of file
+    /**
+     * @return
+     *         the task that we have a result for
+     */
+    public Task getTask();
+
+    /**
+     * @return
+     *         <code>true</code> if the task execution was successful,
+     *         false otherwise.
+     */
+    public boolean wasSuccessful();
+
+    /**
+     * A <code>Task</code> may optionally pass back a result that can be used
+     * by the Master to manage further Task execution.
+     * @return
+     *         the result of this task or null if no result is set.
+     */
+    public String getTaskResult();
+}
diff --git a/src/org/torproject/puppetor/rmi/Test.java b/src/org/torproject/puppetor/rmi/Test.java
index c6a400f..07f66e2 100644
--- a/src/org/torproject/puppetor/rmi/Test.java
+++ b/src/org/torproject/puppetor/rmi/Test.java
@@ -16,22 +16,22 @@ import java.io.Serializable;
  * @author Sebastian Hahn
  */
 public interface Test extends Serializable {
-	/**
-	 * @return
-	 * 		a description of what this test is useful for.
-	 */
-	public String getDescription();
+    /**
+     * @return
+     *         a description of what this test is useful for.
+     */
+    public String getDescription();
 
-	/**
-	 * @return
-	 * 		a description of the network that this test requires to run, so the
-	 * 		</code>TestExecutor</code> can pick or set up one. 
-	 */
-	public NetworkDescription getNetworkDescription();
-	
-	/**
-	 * Call this when the network has been set up and you want to run the test
-	 * with the current network.
-	 */
-	public void doTest();
+    /**
+     * @return
+     *         a description of the network that this test requires to run, so the
+     *         </code>TestExecutor</code> can pick or set up one.
+     */
+    public NetworkDescription getNetworkDescription();
+
+    /**
+     * Call this when the network has been set up and you want to run the test
+     * with the current network.
+     */
+    public void doTest();
 }
diff --git a/src/org/torproject/puppetor/rmi/TestExecutor.java b/src/org/torproject/puppetor/rmi/TestExecutor.java
index f5ed5c6..f736685 100644
--- a/src/org/torproject/puppetor/rmi/TestExecutor.java
+++ b/src/org/torproject/puppetor/rmi/TestExecutor.java
@@ -21,94 +21,94 @@ import java.util.Set;
  */
 public interface TestExecutor {
 
-	/**
-	 * Register a test with the executor so it will consider it for execution.
-	 * Accept all submitted tests even though some may not be executable at the
-	 * moment of submission.
-	 */
-	public void registerTest(Test test);
+    /**
+     * Register a test with the executor so it will consider it for execution.
+     * Accept all submitted tests even though some may not be executable at the
+     * moment of submission.
+     */
+    public void registerTest(Test test);
 
-	/**
-	 * Start executing tests. Callers should make sure that the PuppeTor network
-	 * that is supposed to be used is ready.
-	 */
-	public void startTesting();
+    /**
+     * Start executing tests. Callers should make sure that the PuppeTor network
+     * that is supposed to be used is ready.
+     */
+    public void startTesting();
 
-	/**
-	 * Return an immutable Map of the nodes that have been run successfully
-	 * along with the results of the tests.
-	 */
-	public Map<Test, TestResult> getSuccessfulTests();
+    /**
+     * Return an immutable Map of the nodes that have been run successfully
+     * along with the results of the tests.
+     */
+    public Map<Test, TestResult> getSuccessfulTests();
 
-	/**
-	 * Return an immutable Map of the nodes that have been run but didn't
-	 * complete successfully along with the results of the tests.
-	 */
-	public Map<Test, TestResult> getFailedTests();
+    /**
+     * Return an immutable Map of the nodes that have been run but didn't
+     * complete successfully along with the results of the tests.
+     */
+    public Map<Test, TestResult> getFailedTests();
 
-	/**
-	 * Return all tests that could not be executed because the PuppeTorNetwork
-	 * could not match a test's preconditions.
-	 */
-	public Set<Test> getCannotRunTests();
+    /**
+     * Return all tests that could not be executed because the PuppeTorNetwork
+     * could not match a test's preconditions.
+     */
+    public Set<Test> getCannotRunTests();
 
-	/**
-	 * Register a newly connected PuppeTor slave via its
-	 * <code>LocalMaster</code> connection object.
-	 */
-	public void registerLocalMaster(LocalMaster master);
+    /**
+     * Register a newly connected PuppeTor slave via its
+     * <code>LocalMaster</code> connection object.
+     */
+    public void registerLocalMaster(LocalMaster master);
 
-	/**
-	 * Unregister a PuppeTor slave. Called when a slave disconnects.
-	 */
-	public void unregisterLocalMaster(LocalMaster master);
+    /**
+     * Unregister a PuppeTor slave. Called when a slave disconnects.
+     */
+    public void unregisterLocalMaster(LocalMaster master);
 
-	/**
-	 * @return
-	 * 		the currently running network.
-	 */
-	public Network getCurrentNetwork();
+    /**
+     * @return
+     *         the currently running network.
+     */
+    public Network getCurrentNetwork();
 
-	/**
-	 * Execute the passed <code>Task</code> once per passed
-	 * <code>LocalMaster</code>. If a <code>LocalMaster</code> is passed
-	 * more than once, the task will be executed multiple times.
-	 *
-	 * @param masters
-	 *            execute the task for those masters.
-	 * @param taskClass
-	 *            create a task of this class and execute it.
-	 * @param taskResults
-	 * 			place the results of the task in this <code>Set</code> unless
-	 * 			<code>null</code> is passed.
-	 * @param additionalInformation TODO
-	 * @throws TaskExecutionNotSuccessfulException
-	 *             Indicates a failure during task execution
-	 */
-	public void executeTaskForMasters(final Collection<LocalMaster> masters,
-			final Class<? extends Task> taskClass, final Set<TaskResult> taskResults,
-			Collection<String> additionalInformation)
-			throws TaskExecutionNotSuccessfulException;
+    /**
+     * Execute the passed <code>Task</code> once per passed
+     * <code>LocalMaster</code>. If a <code>LocalMaster</code> is passed
+     * more than once, the task will be executed multiple times.
+     *
+     * @param masters
+     *            execute the task for those masters.
+     * @param taskClass
+     *            create a task of this class and execute it.
+     * @param taskResults
+     *             place the results of the task in this <code>Set</code> unless
+     *             <code>null</code> is passed.
+     * @param additionalInformation TODO
+     * @throws TaskExecutionNotSuccessfulException
+     *             Indicates a failure during task execution
+     */
+    public void executeTaskForMasters(final Collection<LocalMaster> masters,
+            final Class<? extends Task> taskClass, final Set<TaskResult> taskResults,
+            Collection<String> additionalInformation)
+            throws TaskExecutionNotSuccessfulException;
 
-	/**
-	 * Execute the passed <code>Task</code> once per passed
-	 * <code>TorInstance</code>. If a <code>TorInstance</code> is passed
-	 * more than once, the task will be executed multiple times.
-	 * @param taskClass
-	 *          create a task of this class and execute it.
-	 * @param taskResults
-	 * 			place the results of the task in this <code>Set</code> unless
-	 * 			<code>null</code> is passed.
-	 * @param additionalInformation
-	 * 			call the task's addAdditionalInformation method with this
-	 * 			parameter.
-	 * @param torInstances
-	 *          execute the task for those masters.
-	 * @throws TaskExecutionNotSuccessfulException
-	 *          Indicates a failure during task execution
-	 */
-	public void executeTaskForTorInstances(Collection<TorInstance> instances,
-			Class<? extends Task> taskClass, Set<TaskResult> taskResults,
-			Collection<String> additionalInformation)
-			throws TaskExecutionNotSuccessfulException;
+    /**
+     * Execute the passed <code>Task</code> once per passed
+     * <code>TorInstance</code>. If a <code>TorInstance</code> is passed
+     * more than once, the task will be executed multiple times.
+     * @param taskClass
+     *          create a task of this class and execute it.
+     * @param taskResults
+     *             place the results of the task in this <code>Set</code> unless
+     *             <code>null</code> is passed.
+     * @param additionalInformation
+     *             call the task's addAdditionalInformation method with this
+     *             parameter.
+     * @param torInstances
+     *          execute the task for those masters.
+     * @throws TaskExecutionNotSuccessfulException
+     *          Indicates a failure during task execution
+     */
+    public void executeTaskForTorInstances(Collection<TorInstance> instances,
+            Class<? extends Task> taskClass, Set<TaskResult> taskResults,
+            Collection<String> additionalInformation)
+            throws TaskExecutionNotSuccessfulException;
 }
diff --git a/src/org/torproject/puppetor/rmi/TestResult.java b/src/org/torproject/puppetor/rmi/TestResult.java
index 32788c1..d67b57b 100644
--- a/src/org/torproject/puppetor/rmi/TestResult.java
+++ b/src/org/torproject/puppetor/rmi/TestResult.java
@@ -11,5 +11,5 @@ import java.io.Serializable;
  * XXX unused for now. this will hold the result of a /<code>Test</code> -SH
  */
 public interface TestResult extends Serializable {
-	public Test getJob();
+    public Test getJob();
 }
diff --git a/src/org/torproject/puppetor/rmi/TorInstance.java b/src/org/torproject/puppetor/rmi/TorInstance.java
index fd9295e..fca8d61 100644
--- a/src/org/torproject/puppetor/rmi/TorInstance.java
+++ b/src/org/torproject/puppetor/rmi/TorInstance.java
@@ -13,45 +13,45 @@ import java.util.List;
  * @author Sebastian Hahn
  */
 public interface TorInstance {
-	/**
-	 * Additional configuration options that a test wants to be set for a tor
-	 * instance. <code>TorInstance.shouldReplaceConfiguration()</code>
-	 * determines whether this configuration should be added to the default tor
-	 * configuration or replace it.
-	 *
-	 * @return An immutable list of extra configuration settings.
-	 */
-	public List<String> getConfiguration();
-
-	/**
-	 * @return the name by which this Tor instance will be identified at the
-	 *         slaves. See org.torproject.puppetor.Network.getNode().
-	 */
-	public String getName();
-
-	/**
-	 * Whether the caller should use the result of
-	 * <code>TorInstance.getExtraConfiguration()</code> to append tor's
-	 * default configuration or replace it.
-	 *
-	 * @return true if configuration should be replaced, false if it should be
-	 *         appended.
-	 */
-	public boolean shouldReplaceConfiguration();
-
-	public boolean isDirectoryAuthority();
-
-	public boolean isBridgeAuthority();
-
-	public boolean isRouter();
-
-	public boolean isExit();
-	
-	public boolean isOnPrivateNetwork();
-
-	public int getRevision();
-
-	public boolean isBridge();
-
-	public LocalMaster getMaster();
-}
\ No newline at end of file
+    /**
+     * Additional configuration options that a test wants to be set for a tor
+     * instance. <code>TorInstance.shouldReplaceConfiguration()</code>
+     * determines whether this configuration should be added to the default tor
+     * configuration or replace it.
+     *
+     * @return An immutable list of extra configuration settings.
+     */
+    public List<String> getConfiguration();
+
+    /**
+     * @return the name by which this Tor instance will be identified at the
+     *         slaves. See org.torproject.puppetor.Network.getNode().
+     */
+    public String getName();
+
+    /**
+     * Whether the caller should use the result of
+     * <code>TorInstance.getExtraConfiguration()</code> to append tor's
+     * default configuration or replace it.
+     *
+     * @return true if configuration should be replaced, false if it should be
+     *         appended.
+     */
+    public boolean shouldReplaceConfiguration();
+
+    public boolean isDirectoryAuthority();
+
+    public boolean isBridgeAuthority();
+
+    public boolean isRouter();
+
+    public boolean isExit();
+
+    public boolean isOnPrivateNetwork();
+
+    public int getRevision();
+
+    public boolean isBridge();
+
+    public LocalMaster getMaster();
+}
diff --git a/src/org/torproject/puppetor/rmi/execute/PuppeTorMasterProgram.java b/src/org/torproject/puppetor/rmi/execute/PuppeTorMasterProgram.java
index e05593a..28c24ee 100644
--- a/src/org/torproject/puppetor/rmi/execute/PuppeTorMasterProgram.java
+++ b/src/org/torproject/puppetor/rmi/execute/PuppeTorMasterProgram.java
@@ -38,157 +38,157 @@ import org.torproject.puppetor.rmi.tests.TestRegistration;
  */
 public class PuppeTorMasterProgram {
 
-	/**
-	 * The RMI registry.
-	 */
-	private Registry registry;
-
-	/**
-	 * The port the server is supposed to listen on. This port must not be
-	 * firewalled and must be forwarded to the machine if necessary.
-	 */
-	final private static int serverport = 2050;
-
-	/**
-	 * The address this server should bind on. Use 127.0.0.1 for local testing.
-	 * XXX Someone should check if this works with IPv6 -SH
-	 */
-	final private static String serveraddress = "127.0.0.1";
-
-	/**
-	 * Master application entry point
-	 *
-	 * @param args
-	 *            Command-line arguments - ignored for now.
-	 * @throws NotBoundException
-	 * @throws RemoteException
-	 * @throws AccessException
-	 */
-	public static void main(final String[] args) throws AccessException,
-			RemoteException, NotBoundException {
-		final PuppeTorMasterProgram server = new PuppeTorMasterProgram();
-
-		final TestExecutor exec =
-				AbstractMasterFactory.getInstance().getTestExecutorInstance();
-		exec.startTesting();
-		server.unregisterRMIRegistry();
-		// Suggest a garbage collection, may increase shutdown.
-		System.gc();
-	}
-
-	/**
-	 * Private constructor to set up RMI-related Java-internal variables and
-	 * create a <code>RemotePuppeTorImpl</code>.
-	 *
-	 * @throws NotBoundException
-	 * @throws RemoteException
-	 * @throws AccessException
-	 */
-	private PuppeTorMasterProgram() throws AccessException, RemoteException,
-			NotBoundException {
-		// Set the location of the keystore where your private certificate is.
-		System.setProperty("javax.net.ssl.keyStore", "res/keystore");
-		// Set the password for your keystore.
-		System.setProperty("javax.net.ssl.keyStorePassword", "pleasechange");
-
-		/*
-		 * Set the location of the truststore which contains the exported
-		 * certificates of your slaves.
-		 */
-		System.setProperty("javax.net.ssl.trustStore", "res/truststore");
-		// Use a timeout of 45 seconds to detect disconnected clients.
-		System.setProperty("java.rmi.dgc.leaseValue", "45000");
-
-		// Tell the RMI system the location of the master
-		System.setProperty("java.rmi.server.hostname", serveraddress);
-
-		// Set up the factories we want to use
-		AbstractMasterFactory.initialize(new MasterImplFactory());
-
-		registerTests();
-		final InnerMasterConnector master = InnerMasterConnector.getInstance();
-
-		try {
-			registry =
-					LocateRegistry.createRegistry(serverport,
-							new SslRMIClientSocketFactory(),
-							new SslRMIServerSocketFactory(null, null, true));
-			registry.bind("Master", master);
-		} catch (final Throwable th) {
-			th.printStackTrace();
-			throw new RuntimeException(
-					"Could not create the registry or bind"
-							+ "an object in it. Please check your system's configuration. "
-							+ th);
-		}
-
-		System.out.println("waiting for slaves.");
-		try {
-			Thread.sleep(1000 * 60 * 2);
-		} catch (final InterruptedException e) {
-			e.printStackTrace();
-			throw new RuntimeException("This is the main thread. We don't "
-					+ "want to be interrupted.");
-		}
-		registry.unbind("Master");
-		// Only unbinding is not enough, the object will still be exported.
-		UnicastRemoteObject.unexportObject(master, true);
-	}
-
-	/**
-	 * Register all the tests you want to execute here.
-	 */
-	private void registerTests() {
-		final AbstractMasterFactory fact = AbstractMasterFactory.getInstance();
-		final TestExecutor exec = fact.getTestExecutorInstance();
-		final TestRegistration registration = fact.getTestRegistrationInstance();
-		registration.registerTests(exec);
-	}
-
-	private void unregisterRMIRegistry() throws NoSuchObjectException {
-		UnicastRemoteObject.unexportObject(registry, true);
-		registry = null;
-	}
-
-	static class InnerMasterConnector extends UnicastRemoteObject implements
-			MasterConnector {
-		/**
-		 * Required for serialization. Needs to change for new released
-		 * versions.
-		 */
-		private static final long serialVersionUID = 1L;
-
-		private static InnerMasterConnector instance;
-
-		static {
-			try {
-				instance = new InnerMasterConnector();
-			} catch (final RemoteException e) {
-				e.printStackTrace();
-				throw new ExceptionInInitializerError(e);
-			}
-		}
-
-		/**
-		 * private constructor, Singleton
-		 *
-		 * @throws RemoteException
-		 *             RMI
-		 */
-		private InnerMasterConnector() throws RemoteException {}
-
-		public static InnerMasterConnector getInstance() {
-			return instance;
-		}
-
-		public RemoteMaster registerClient(final Slave slave)
-				throws RemoteException {
-			final AbstractMasterFactory fact =
-					AbstractMasterFactory.getInstance();
-			final Master master = fact.createMaster(slave);
-			AbstractMasterFactory.getInstance().getTestExecutorInstance()
-					.registerLocalMaster((LocalMaster) master);
-			return (RemoteMaster) master;
-		}
-	}
+    /**
+     * The RMI registry.
+     */
+    private Registry registry;
+
+    /**
+     * The port the server is supposed to listen on. This port must not be
+     * firewalled and must be forwarded to the machine if necessary.
+     */
+    final private static int serverport = 2050;
+
+    /**
+     * The address this server should bind on. Use 127.0.0.1 for local testing.
+     * XXX Someone should check if this works with IPv6 -SH
+     */
+    final private static String serveraddress = "127.0.0.1";
+
+    /**
+     * Master application entry point
+     *
+     * @param args
+     *            Command-line arguments - ignored for now.
+     * @throws NotBoundException
+     * @throws RemoteException
+     * @throws AccessException
+     */
+    public static void main(final String[] args) throws AccessException,
+            RemoteException, NotBoundException {
+        final PuppeTorMasterProgram server = new PuppeTorMasterProgram();
+
+        final TestExecutor exec =
+                AbstractMasterFactory.getInstance().getTestExecutorInstance();
+        exec.startTesting();
+        server.unregisterRMIRegistry();
+        // Suggest a garbage collection, may increase shutdown.
+        System.gc();
+    }
+
+    /**
+     * Private constructor to set up RMI-related Java-internal variables and
+     * create a <code>RemotePuppeTorImpl</code>.
+     *
+     * @throws NotBoundException
+     * @throws RemoteException
+     * @throws AccessException
+     */
+    private PuppeTorMasterProgram() throws AccessException, RemoteException,
+            NotBoundException {
+        // Set the location of the keystore where your private certificate is.
+        System.setProperty("javax.net.ssl.keyStore", "res/keystore");
+        // Set the password for your keystore.
+        System.setProperty("javax.net.ssl.keyStorePassword", "pleasechange");
+
+        /*
+         * Set the location of the truststore which contains the exported
+         * certificates of your slaves.
+         */
+        System.setProperty("javax.net.ssl.trustStore", "res/truststore");
+        // Use a timeout of 45 seconds to detect disconnected clients.
+        System.setProperty("java.rmi.dgc.leaseValue", "45000");
+
+        // Tell the RMI system the location of the master
+        System.setProperty("java.rmi.server.hostname", serveraddress);
+
+        // Set up the factories we want to use
+        AbstractMasterFactory.initialize(new MasterImplFactory());
+
+        registerTests();
+        final InnerMasterConnector master = InnerMasterConnector.getInstance();
+
+        try {
+            registry =
+                    LocateRegistry.createRegistry(serverport,
+                            new SslRMIClientSocketFactory(),
+                            new SslRMIServerSocketFactory(null, null, true));
+            registry.bind("Master", master);
+        } catch (final Throwable th) {
+            th.printStackTrace();
+            throw new RuntimeException(
+                    "Could not create the registry or bind"
+                            + "an object in it. Please check your system's configuration. "
+                            + th);
+        }
+
+        System.out.println("waiting for slaves.");
+        try {
+            Thread.sleep(1000 * 60 * 2);
+        } catch (final InterruptedException e) {
+            e.printStackTrace();
+            throw new RuntimeException("This is the main thread. We don't "
+                    + "want to be interrupted.");
+        }
+        registry.unbind("Master");
+        // Only unbinding is not enough, the object will still be exported.
+        UnicastRemoteObject.unexportObject(master, true);
+    }
+
+    /**
+     * Register all the tests you want to execute here.
+     */
+    private void registerTests() {
+        final AbstractMasterFactory fact = AbstractMasterFactory.getInstance();
+        final TestExecutor exec = fact.getTestExecutorInstance();
+        final TestRegistration registration = fact.getTestRegistrationInstance();
+        registration.registerTests(exec);
+    }
+
+    private void unregisterRMIRegistry() throws NoSuchObjectException {
+        UnicastRemoteObject.unexportObject(registry, true);
+        registry = null;
+    }
+
+    static class InnerMasterConnector extends UnicastRemoteObject implements
+            MasterConnector {
+        /**
+         * Required for serialization. Needs to change for new released
+         * versions.
+         */
+        private static final long serialVersionUID = 1L;
+
+        private static InnerMasterConnector instance;
+
+        static {
+            try {
+                instance = new InnerMasterConnector();
+            } catch (final RemoteException e) {
+                e.printStackTrace();
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+
+        /**
+         * private constructor, Singleton
+         *
+         * @throws RemoteException
+         *             RMI
+         */
+        private InnerMasterConnector() throws RemoteException {}
+
+        public static InnerMasterConnector getInstance() {
+            return instance;
+        }
+
+        public RemoteMaster registerClient(final Slave slave)
+                throws RemoteException {
+            final AbstractMasterFactory fact =
+                    AbstractMasterFactory.getInstance();
+            final Master master = fact.createMaster(slave);
+            AbstractMasterFactory.getInstance().getTestExecutorInstance()
+                    .registerLocalMaster((LocalMaster) master);
+            return (RemoteMaster) master;
+        }
+    }
 }
diff --git a/src/org/torproject/puppetor/rmi/execute/PuppeTorSlaveProgram.java b/src/org/torproject/puppetor/rmi/execute/PuppeTorSlaveProgram.java
index 5605ed3..2e6aa02 100644
--- a/src/org/torproject/puppetor/rmi/execute/PuppeTorSlaveProgram.java
+++ b/src/org/torproject/puppetor/rmi/execute/PuppeTorSlaveProgram.java
@@ -38,179 +38,178 @@ import org.torproject.puppetor.rmi.tasks.TerminateTask;
  */
 public class PuppeTorSlaveProgram {
 
-	/**
-	 * The port the master server is supposed to listen on. This port must not
-	 * be firewalled and must be forwarded to the machine if necessary. Use the
-	 * same value as you used for the master's configuration.
-	 */
-	final private static int serverport = 2050;
-
-	/**
-	 * The address master server listens on. Use the same value as you used for
-	 * the master's configuration. XXX Someone should check if this works with
-	 * IPv6 -SH
-	 */
-	final private static String serveraddress = "127.0.0.1";//78.47.18.109";
-
-	/**
-	 * The address this slave listens on. Use 127.0.0.1 for slaves that do not
-	 * open any ports for the outside world and thus can act as Tor clients
-	 * only.
-	 */
-	final private static String myaddress = "127.0.0.1";
-	
-	/**
-	 * Each slave is identified by its unique <code>slaveName</code>. Be
-	 * careful to prevent naming collisions between slaves.
-	 */
-	final private static String slaveName = "slave1";
-
-	/**
-	 * A representation of this slave node to be sent to the master.
-	 */
-	final private Slave slave;
-
-	final protected static BlockingQueue<Task> tasks =
-			new LinkedBlockingQueue<Task>();
-
-	/**
-	 * Slave application entry point
-	 *
-	 * @param args
-	 *            Command-line arguments
-	 * @throws NoSuchObjectException
-	 */
-	public static void main(final String[] args) {
-		final PuppeTorSlaveProgram slave = new PuppeTorSlaveProgram();
-		while(true) {
-			while (slave.connectToMaster() == false) {
-				try {
-					Thread.sleep(60000);
-				} catch (final InterruptedException e) {
-					throw new RuntimeException("Bug: Nothing should " +
-							"interrupt us here!");
-				}
-			}
-	
-			final Thread taskGetter = new Thread(new Runnable() {
-				public void run() {
-					while (true) {
-						try {
-							try {
-								// XXX I dislike the following lines, but
-								// interruption doesn't work while we wait for the
-								// master. Maybe there is a better solution that
-								// doesn't eat the poison pill twice. -SH
-								Task task = AbstractSlaveFactory.getMaster()
-												.getNewTask();
-								tasks.put(task);
-								if (task instanceof TerminateTask) {
-									break;
-								}
-							} catch (final RemoteException e) {
-								// Empty the task queue - we cannot send any results
-								// back, anyways.
-								tasks.clear();
-								// A taskid of 0 indicates a local Task
-								tasks.put(new TerminateTask(0,
-										"local termination"));
-								Thread.currentThread().interrupt();
-							}
-						} catch (final InterruptedException e) {
-							Thread.currentThread().interrupt();
-						}
-						if (Thread.interrupted()) {
-							break;
-						}
-	
-					}
-				}
-			});
-	
-			taskGetter.start();
-			// XXX We use just a single Thread here. That isn't the smartest move.
-			// -SH
-			try {
-				while (true) {
-					final Task newTask = tasks.take();
-					newTask.execute();
-					if (Thread.interrupted()) {
-						break;
-					}
-				}
-			} catch (final InterruptedException ignored) {} catch (final RemoteException ignored) {}
-			taskGetter.interrupt();
-			AbstractSlaveFactory.setMaster(null);
-			// We don't shut down, but want the Master to see we disconnected,
-			// so hopefully a gc will happen soon.
-			System.gc();
-		}
-
-	}
-
-	/**
-	 * Private constructor to set up RMI-related Java-internal variables.
-	 */
-	private PuppeTorSlaveProgram() {
-		/*
-		 * Set the location of the keystore where your private certificate is.
-		 */
-		System.setProperty("javax.net.ssl.keyStore", "res/keystore");
-		/*
-		 * Set the password for your keystore.
-		 */
-		System.setProperty("javax.net.ssl.keyStorePassword", "pleasechange");
-		/*
-		 * Set the location of the truststore which contains the exported
-		 * certificates of your slaves
-		 */
-		System.setProperty("javax.net.ssl.trustStore", "res/truststore");
-		// Set up the factories we want to use
-		AbstractSlaveFactory.initialize(new SlaveImplFactory());
-		final AbstractSlaveFactory fac = AbstractSlaveFactory.getInstance();
-
-		/*
-		 * Create a new slave with the properties that are relevant for the
-		 * computer and networking environment it runs on.
-		 * XXX unused for now -SH
-		 */
-		final Set<Integer> ports = new HashSet<Integer>();
-		/*for (int i = 10025; i < 20000; i++) {
-			ports.add(i);
-		}*/
-		final Set<Integer> availableRevisions =
-				new CopyOnWriteArraySet<Integer>();
-		slave = fac.createSlave(slaveName, Slave.OS.UNDEFINED, myaddress,
-						ports, availableRevisions);
-
-	}
-
-	/**
-	 * Attempt to make a connection to the master through RMI. If the master is
-	 * down, that is not a fatal condition, we want to keep trying until we can
-	 * reach it. Don't call this except from main().
-	 *
-	 * @return true if the connection was successful, false otherwise.
-	 */
-	private boolean connectToMaster() {
-		try {
-			final Registry registry =
-					LocateRegistry.getRegistry(serveraddress, serverport,
-							new SslRMIClientSocketFactory());
-			AbstractSlaveFactory.setMaster(((MasterConnector) registry
-					.lookup("Master")).registerClient(slave));
-		} catch (final NotBoundException e) {
-			e.printStackTrace();
-			System.out.println("We could connect, but the master has not "
-					+ "exported the Master Object yet." + e);
-			return false;
-		} catch (final RemoteException e) {
-			return false;
-		} catch (final IllegalArgumentException e) {
-			throw new RuntimeException(
-					"A slave with this name is already connected "
-							+ "to the server! " + e);
-		}
-		return true;
-	}
+    /**
+     * The port the master server is supposed to listen on. This port must not
+     * be firewalled and must be forwarded to the machine if necessary. Use the
+     * same value as you used for the master's configuration.
+     */
+    final private static int serverport = 2050;
+
+    /**
+     * The address master server listens on. Use the same value as you used for
+     * the master's configuration. XXX Someone should check if this works with
+     * IPv6 -SH
+     */
+    final private static String serveraddress = "127.0.0.1";//78.47.18.109";
+
+    /**
+     * The address this slave listens on. Use 127.0.0.1 for slaves that do not
+     * open any ports for the outside world and thus can act as Tor clients
+     * only.
+     */
+    final private static String myaddress = "127.0.0.1";
+
+    /**
+     * Each slave is identified by its unique <code>slaveName</code>. Be
+     * careful to prevent naming collisions between slaves.
+     */
+    final private static String slaveName = "slave1";
+
+    /**
+     * A representation of this slave node to be sent to the master.
+     */
+    final private Slave slave;
+
+    final protected static BlockingQueue<Task> tasks =
+            new LinkedBlockingQueue<Task>();
+
+    /**
+     * Slave application entry point
+     *
+     * @param args
+     *            Command-line arguments
+     * @throws NoSuchObjectException
+     */
+    public static void main(final String[] args) {
+        final PuppeTorSlaveProgram slave = new PuppeTorSlaveProgram();
+        while(true) {
+            while (slave.connectToMaster() == false) {
+                try {
+                    Thread.sleep(60000);
+                } catch (final InterruptedException e) {
+                    throw new RuntimeException("Bug: Nothing should " +
+                            "interrupt us here!");
+                }
+            }
+
+            final Thread taskGetter = new Thread(new Runnable() {
+                public void run() {
+                    while (true) {
+                        try {
+                            try {
+                                // XXX I dislike the following lines, but
+                                // interruption doesn't work while we wait for the
+                                // master. Maybe there is a better solution that
+                                // doesn't eat the poison pill twice. -SH
+                                Task task = AbstractSlaveFactory.getMaster()
+                                                .getNewTask();
+                                tasks.put(task);
+                                if (task instanceof TerminateTask) {
+                                    break;
+                                }
+                            } catch (final RemoteException e) {
+                                // Empty the task queue - we cannot send any results
+                                // back, anyways.
+                                tasks.clear();
+                                // A taskid of 0 indicates a local Task
+                                tasks.put(new TerminateTask(0,
+                                        "local termination"));
+                                Thread.currentThread().interrupt();
+                            }
+                        } catch (final InterruptedException e) {
+                            Thread.currentThread().interrupt();
+                        }
+                        if (Thread.interrupted()) {
+                            break;
+                        }
+                    }
+                }
+            });
+
+            taskGetter.start();
+            // XXX We use just a single Thread here. That isn't the smartest move.
+            // -SH
+            try {
+                while (true) {
+                    final Task newTask = tasks.take();
+                    newTask.execute();
+                    if (Thread.interrupted()) {
+                        break;
+                    }
+                }
+            } catch (final InterruptedException ignored) {} catch (final RemoteException ignored) {}
+            taskGetter.interrupt();
+            AbstractSlaveFactory.setMaster(null);
+            // We don't shut down, but want the Master to see we disconnected,
+            // so hopefully a gc will happen soon.
+            System.gc();
+        }
+
+    }
+
+    /**
+     * Private constructor to set up RMI-related Java-internal variables.
+     */
+    private PuppeTorSlaveProgram() {
+        /*
+         * Set the location of the keystore where your private certificate is.
+         */
+        System.setProperty("javax.net.ssl.keyStore", "res/keystore");
+        /*
+         * Set the password for your keystore.
+         */
+        System.setProperty("javax.net.ssl.keyStorePassword", "pleasechange");
+        /*
+         * Set the location of the truststore which contains the exported
+         * certificates of your slaves
+         */
+        System.setProperty("javax.net.ssl.trustStore", "res/truststore");
+        // Set up the factories we want to use
+        AbstractSlaveFactory.initialize(new SlaveImplFactory());
+        final AbstractSlaveFactory fac = AbstractSlaveFactory.getInstance();
+
+        /*
+         * Create a new slave with the properties that are relevant for the
+         * computer and networking environment it runs on.
+         * XXX unused for now -SH
+         */
+        final Set<Integer> ports = new HashSet<Integer>();
+        /*for (int i = 10025; i < 20000; i++) {
+            ports.add(i);
+        }*/
+        final Set<Integer> availableRevisions =
+                new CopyOnWriteArraySet<Integer>();
+        slave = fac.createSlave(slaveName, Slave.OS.UNDEFINED, myaddress,
+                        ports, availableRevisions);
+
+    }
+
+    /**
+     * Attempt to make a connection to the master through RMI. If the master is
+     * down, that is not a fatal condition, we want to keep trying until we can
+     * reach it. Don't call this except from main().
+     *
+     * @return true if the connection was successful, false otherwise.
+     */
+    private boolean connectToMaster() {
+        try {
+            final Registry registry =
+                    LocateRegistry.getRegistry(serveraddress, serverport,
+                            new SslRMIClientSocketFactory());
+            AbstractSlaveFactory.setMaster(((MasterConnector) registry
+                    .lookup("Master")).registerClient(slave));
+        } catch (final NotBoundException e) {
+            e.printStackTrace();
+            System.out.println("We could connect, but the master has not "
+                    + "exported the Master Object yet." + e);
+            return false;
+        } catch (final RemoteException e) {
+            return false;
+        } catch (final IllegalArgumentException e) {
+            throw new RuntimeException(
+                    "A slave with this name is already connected "
+                            + "to the server! " + e);
+        }
+        return true;
+    }
 }
diff --git a/src/org/torproject/puppetor/rmi/impl/AbstractTaskImpl.java b/src/org/torproject/puppetor/rmi/impl/AbstractTaskImpl.java
index e63eb14..21d1d78 100644
--- a/src/org/torproject/puppetor/rmi/impl/AbstractTaskImpl.java
+++ b/src/org/torproject/puppetor/rmi/impl/AbstractTaskImpl.java
@@ -14,49 +14,49 @@ import org.torproject.puppetor.rmi.TaskResult;
 
 
 public abstract class AbstractTaskImpl implements Task {
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-	
-	/**
-	 * A human-readable name for the task (to be included in log-messages, etc).
-	 */
-	final protected String name;
-
-	/**
-	 * The globally unique id for this task.
-	 */
-	final protected int id;
-
-	public AbstractTaskImpl(final int id, final String name) {
-		this.name = name;
-		this.id = id;
-	}
-
-	public int getId() {
-		return id;
-	}
-
-	public abstract void execute() throws InterruptedException, RemoteException;
-
-	protected void reportResult(final boolean success)
-			throws InterruptedException, RemoteException {
-		reportResult(new TaskResultImpl(success, this, null));
-	}
-	
-	protected void reportResult(final boolean success, final String result)
-			throws InterruptedException, RemoteException {
-		reportResult(new TaskResultImpl(success, this, result));
-	}
-
-	// XXX protect against double-reporting of results -SH
-	protected void reportResult( final TaskResult taskResult)
-			throws InterruptedException, RemoteException {
-		AbstractSlaveFactory.getMaster().reportTaskResult(taskResult);
-	}
-	
-	public void addAdditionalInformation(
-			Collection<String> additionalInformation) {}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * A human-readable name for the task (to be included in log-messages, etc).
+     */
+    final protected String name;
+
+    /**
+     * The globally unique id for this task.
+     */
+    final protected int id;
+
+    public AbstractTaskImpl(final int id, final String name) {
+        this.name = name;
+        this.id = id;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public abstract void execute() throws InterruptedException, RemoteException;
+
+    protected void reportResult(final boolean success)
+            throws InterruptedException, RemoteException {
+        reportResult(new TaskResultImpl(success, this, null));
+    }
+
+    protected void reportResult(final boolean success, final String result)
+            throws InterruptedException, RemoteException {
+        reportResult(new TaskResultImpl(success, this, result));
+    }
+
+    // XXX protect against double-reporting of results -SH
+    protected void reportResult( final TaskResult taskResult)
+            throws InterruptedException, RemoteException {
+        AbstractSlaveFactory.getMaster().reportTaskResult(taskResult);
+    }
+
+    public void addAdditionalInformation(
+            Collection<String> additionalInformation) {}
 
 }
diff --git a/src/org/torproject/puppetor/rmi/impl/AbstractTestImpl.java b/src/org/torproject/puppetor/rmi/impl/AbstractTestImpl.java
index 018fdc0..87757b9 100644
--- a/src/org/torproject/puppetor/rmi/impl/AbstractTestImpl.java
+++ b/src/org/torproject/puppetor/rmi/impl/AbstractTestImpl.java
@@ -13,40 +13,40 @@ import org.torproject.puppetor.rmi.TestExecutor;
 /**
  * Extend <code>AbstractTestImpl</code> for your own tests. Make sure you also add them
  * to the TestRegistration.registerTest method.
- * 
+ *
  * @author Sebastian Hahn
  */
 abstract public class AbstractTestImpl implements Test {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-	
-	/**
-	 * A test description for log messages.
-	 */
-	final private String description;
-
-	/**
-	 * The network that must exist for the test to be executed.
-	 */
-	final private NetworkDescription networkDescription;
-
-	final protected TestExecutor exec;
-	
-	public AbstractTestImpl(final String description, final NetworkDescription network) {
-		this.description = description;
-		networkDescription = network;
-		exec = AbstractMasterFactory.getInstance().
-				getTestExecutorInstance();
-	}
-
-	public final NetworkDescription getNetworkDescription() {
-		return networkDescription;
-	}
-
-	public String getDescription() {
-		return description;
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * A test description for log messages.
+     */
+    final private String description;
+
+    /**
+     * The network that must exist for the test to be executed.
+     */
+    final private NetworkDescription networkDescription;
+
+    final protected TestExecutor exec;
+
+    public AbstractTestImpl(final String description, final NetworkDescription network) {
+        this.description = description;
+        networkDescription = network;
+        exec = AbstractMasterFactory.getInstance().
+                getTestExecutorInstance();
+    }
+
+    public final NetworkDescription getNetworkDescription() {
+        return networkDescription;
+    }
+
+    public String getDescription() {
+        return description;
+    }
 }
diff --git a/src/org/torproject/puppetor/rmi/impl/MasterImpl.java b/src/org/torproject/puppetor/rmi/impl/MasterImpl.java
index 5550486..c1df8f1 100644
--- a/src/org/torproject/puppetor/rmi/impl/MasterImpl.java
+++ b/src/org/torproject/puppetor/rmi/impl/MasterImpl.java
@@ -27,79 +27,79 @@ import org.torproject.puppetor.rmi.TaskResult;
  * @author Sebastian Hahn
  */
 public class MasterImpl extends UnicastRemoteObject implements LocalMaster,
-		RemoteMaster, Master, Unreferenced {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * Store the reference to the connected slave
-	 */
-	private final Slave slave;
-
-	private final BlockingQueue<Task> tasks;
-
-	private final BlockingQueue<TaskResult> completedTasks;
-
-	private final Object completedTasksLock = new Object();
-
-	public Slave getSlave() {
-		return slave;
-	}
-
-	/**
-	 * Check the slave object for validity and save it for further reference
-	 *
-	 * @throws RemoteException
-	 *             RMI
-	 */
-	public MasterImpl(final Slave slave) throws RemoteException {
-		if (slave.getName() == null) {
-			throw new NullPointerException("The slave's name must not be null!");
-		}
-		this.slave = slave;
-		tasks = new LinkedBlockingQueue<Task>();
-		completedTasks = new LinkedBlockingQueue<TaskResult>();
-	}
-
-	public Task getNewTask() throws RemoteException, InterruptedException {
-		return tasks.take();
-	}
-
-	public void reportTaskResult(final TaskResult task) throws RemoteException,
-			InterruptedException {
-		synchronized (completedTasksLock) {
-			completedTasks.put(task);
-			completedTasksLock.notifyAll();
-		}
-	}
-
-	public TaskResult getTaskResult(final int taskId)
-			throws InterruptedException {
-
-		synchronized (completedTasksLock) {
-			TaskResult res;
-			while ((res = completedTasks.peek()) == null
-					|| res.getTask().getId() != taskId) {
-				completedTasksLock.wait();
-			}
-			return completedTasks.take();
-		}
-	}
-
-	/**
-	 * Remove the slave from the Map of slaves and destroy its workqueues.
-	 * Inform the TestExecutor that all still-running tests failed because the
-	 * slave went away.
-	 */
-	public void unreferenced() {
-		AbstractMasterFactory.getInstance().getTestExecutorInstance()
-				.unregisterLocalMaster(this);
-	}
-
-	public void addTask(final Task task) {
-		tasks.add(task);
-	}
+        RemoteMaster, Master, Unreferenced {
+
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Store the reference to the connected slave
+     */
+    private final Slave slave;
+
+    private final BlockingQueue<Task> tasks;
+
+    private final BlockingQueue<TaskResult> completedTasks;
+
+    private final Object completedTasksLock = new Object();
+
+    public Slave getSlave() {
+        return slave;
+    }
+
+    /**
+     * Check the slave object for validity and save it for further reference
+     *
+     * @throws RemoteException
+     *             RMI
+     */
+    public MasterImpl(final Slave slave) throws RemoteException {
+        if (slave.getName() == null) {
+            throw new NullPointerException("The slave's name must not be null!");
+        }
+        this.slave = slave;
+        tasks = new LinkedBlockingQueue<Task>();
+        completedTasks = new LinkedBlockingQueue<TaskResult>();
+    }
+
+    public Task getNewTask() throws RemoteException, InterruptedException {
+        return tasks.take();
+    }
+
+    public void reportTaskResult(final TaskResult task) throws RemoteException,
+            InterruptedException {
+        synchronized (completedTasksLock) {
+            completedTasks.put(task);
+            completedTasksLock.notifyAll();
+        }
+    }
+
+    public TaskResult getTaskResult(final int taskId)
+            throws InterruptedException {
+
+        synchronized (completedTasksLock) {
+            TaskResult res;
+            while ((res = completedTasks.peek()) == null
+                    || res.getTask().getId() != taskId) {
+                completedTasksLock.wait();
+            }
+            return completedTasks.take();
+        }
+    }
+
+    /**
+     * Remove the slave from the Map of slaves and destroy its workqueues.
+     * Inform the TestExecutor that all still-running tests failed because the
+     * slave went away.
+     */
+    public void unreferenced() {
+        AbstractMasterFactory.getInstance().getTestExecutorInstance()
+                .unregisterLocalMaster(this);
+    }
+
+    public void addTask(final Task task) {
+        tasks.add(task);
+    }
 }
diff --git a/src/org/torproject/puppetor/rmi/impl/MasterImplFactory.java b/src/org/torproject/puppetor/rmi/impl/MasterImplFactory.java
index f260982..d758785 100644
--- a/src/org/torproject/puppetor/rmi/impl/MasterImplFactory.java
+++ b/src/org/torproject/puppetor/rmi/impl/MasterImplFactory.java
@@ -21,36 +21,36 @@ import org.torproject.puppetor.rmi.tests.TestRegistration;
  */
 public class MasterImplFactory extends AbstractMasterFactory {
 
-	@Override
-	public Master createMaster(final Slave slave) throws RemoteException {
-		return new MasterImpl(slave);
-	}
-
-	@Override
-	public TestExecutor getTestExecutorInstance() {
-		return TestExecutorImpl.getInstance();
-	}
-
-	@Override
-	public TestRegistration getTestRegistrationInstance() {
-		return TestRegistration.getInstance();
-	}
-
-/*	@Override
-	public Test createPublicNetworkTestForClientsOnly(final String description) {
-		return null;
-		return new AbstractTestImpl(description, new NetworkDescriptionImpl(1, 0, 0));
-	}*/
-
-	/*@Override
-	public Network createNetwork(final String name) {
-		return new NetworkImpl(name);
-	}*/
-
-	@Override
-	public NetworkDescription createNetworkDescription() {
-		// XXX implement -SH
-		return null;
-	}
+    @Override
+    public Master createMaster(final Slave slave) throws RemoteException {
+        return new MasterImpl(slave);
+    }
+
+    @Override
+    public TestExecutor getTestExecutorInstance() {
+        return TestExecutorImpl.getInstance();
+    }
+
+    @Override
+    public TestRegistration getTestRegistrationInstance() {
+        return TestRegistration.getInstance();
+    }
+
+/*    @Override
+    public Test createPublicNetworkTestForClientsOnly(final String description) {
+        return null;
+        return new AbstractTestImpl(description, new NetworkDescriptionImpl(1, 0, 0));
+    }*/
+
+    /*@Override
+    public Network createNetwork(final String name) {
+        return new NetworkImpl(name);
+    }*/
+
+    @Override
+    public NetworkDescription createNetworkDescription() {
+        // XXX implement -SH
+        return null;
+    }
 
 }
diff --git a/src/org/torproject/puppetor/rmi/impl/NetworkDescriptionImpl.java b/src/org/torproject/puppetor/rmi/impl/NetworkDescriptionImpl.java
index e13614e..a148861 100644
--- a/src/org/torproject/puppetor/rmi/impl/NetworkDescriptionImpl.java
+++ b/src/org/torproject/puppetor/rmi/impl/NetworkDescriptionImpl.java
@@ -12,33 +12,33 @@ import org.torproject.puppetor.rmi.NetworkDescription;
 
 public class NetworkDescriptionImpl implements NetworkDescription {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	private final AtomicInteger numProxies;
-	private final AtomicInteger numRouters;
-	private final AtomicInteger numDirectoryAuthorities;
-
-	public int necessaryProxies() {
-		return numProxies.get();
-	}
-
-	public int necessaryRouters() {
-		return numRouters.get();
-	}
-
-	public int necessaryDirectoryAuthorities() {
-		return numDirectoryAuthorities.get();
-	}
-
-	public NetworkDescriptionImpl(final int numProxies, final int numRouters,
-			final int numDirectoryAuthorities) {
-		this.numProxies = new AtomicInteger(numProxies);
-		this.numRouters = new AtomicInteger(numRouters);
-		this.numDirectoryAuthorities =
-				new AtomicInteger(numDirectoryAuthorities);
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    private final AtomicInteger numProxies;
+    private final AtomicInteger numRouters;
+    private final AtomicInteger numDirectoryAuthorities;
+
+    public int necessaryProxies() {
+        return numProxies.get();
+    }
+
+    public int necessaryRouters() {
+        return numRouters.get();
+    }
+
+    public int necessaryDirectoryAuthorities() {
+        return numDirectoryAuthorities.get();
+    }
+
+    public NetworkDescriptionImpl(final int numProxies, final int numRouters,
+            final int numDirectoryAuthorities) {
+        this.numProxies = new AtomicInteger(numProxies);
+        this.numRouters = new AtomicInteger(numRouters);
+        this.numDirectoryAuthorities =
+                new AtomicInteger(numDirectoryAuthorities);
+    }
 
 }
diff --git a/src/org/torproject/puppetor/rmi/impl/NetworkImpl.java b/src/org/torproject/puppetor/rmi/impl/NetworkImpl.java
index aa8fd01..5c69a42 100644
--- a/src/org/torproject/puppetor/rmi/impl/NetworkImpl.java
+++ b/src/org/torproject/puppetor/rmi/impl/NetworkImpl.java
@@ -20,107 +20,107 @@ import org.torproject.puppetor.rmi.TorInstance;
 
 public class NetworkImpl implements Network {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The name of the network, so we can identify it in logs etc.
-	 */
-	final private String name;
-
-	/**
-	 * All the tests that will run with this network. The individual Tor
-	 * instances will have the tests they need to execute broken down further.
-	 */
-	private final List<Test> tests;
-
-	/**
-	 * All the Tor instances running at the slaves that are linked to this
-	 * network.
-	 */
-	private final Set<TorInstance> torInstances;
-
-	private final boolean privateNetwork;
-	
-	public NetworkImpl(final String name, boolean privateNetwork) {
-		this.name = name;
-		tests = new CopyOnWriteArrayList<Test>();
-		torInstances = new CopyOnWriteArraySet<TorInstance>();
-		this.privateNetwork = privateNetwork;
-	}
-
-	public void addTorInstance(final TorInstance torInstance) {
-		torInstances.add(torInstance);
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public void addTest(final Test test) {
-		tests.add(test);
-	}
-
-	public Set<LocalMaster> getMasters() {
-		final Set<LocalMaster> masters = new HashSet<LocalMaster>();
-		for (final TorInstance torInstance : torInstances) {
-			masters.add(torInstance.getMaster());
-		}
-		return Collections.unmodifiableSet(masters);
-	}
-
-	public Set<TorInstance> getDirectoryAuthorities() {
-		final Set<TorInstance> authorities = new HashSet<TorInstance>();
-		for (final TorInstance torInstance : torInstances) {
-			if (torInstance.isDirectoryAuthority()) {
-				authorities.add(torInstance);
-			}
-		}
-		return Collections.unmodifiableSet(authorities);
-	}
-
-	public Set<TorInstance> getProxies() {
-		return Collections.unmodifiableSet(torInstances);
-	}
-
-	public Set<TorInstance> getProxiesOnly() {
-		final Set<TorInstance> proxies = new HashSet<TorInstance>();
-		for (final TorInstance torInstance : torInstances) {
-			if (!torInstance.isRouter()) {
-				proxies.add(torInstance);
-			}
-		}
-		return Collections.unmodifiableSet(proxies);
-	}
-
-	public Set<TorInstance> getRouters() {
-		final Set<TorInstance> routers = new HashSet<TorInstance>();
-		for (final TorInstance torInstance : torInstances) {
-			if (!torInstance.isRouter()) {
-				routers.add(torInstance);
-			}
-		}
-		return Collections.unmodifiableSet(routers);
-	}
-
-	public Set<TorInstance> getRoutersOnly() {
-		final Set<TorInstance> routers = new HashSet<TorInstance>();
-		for (final TorInstance torInstance : torInstances) {
-			if (torInstance.isRouter() && !torInstance.isDirectoryAuthority()) {
-				routers.add(torInstance);
-			}
-		}
-		return Collections.unmodifiableSet(routers);
-	}
-	
-	public List<Test> getTests() {
-		return Collections.unmodifiableList(tests);
-	}
-
-	public boolean isPrivateNetwork() {
-		return privateNetwork;
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The name of the network, so we can identify it in logs etc.
+     */
+    final private String name;
+
+    /**
+     * All the tests that will run with this network. The individual Tor
+     * instances will have the tests they need to execute broken down further.
+     */
+    private final List<Test> tests;
+
+    /**
+     * All the Tor instances running at the slaves that are linked to this
+     * network.
+     */
+    private final Set<TorInstance> torInstances;
+
+    private final boolean privateNetwork;
+
+    public NetworkImpl(final String name, boolean privateNetwork) {
+        this.name = name;
+        tests = new CopyOnWriteArrayList<Test>();
+        torInstances = new CopyOnWriteArraySet<TorInstance>();
+        this.privateNetwork = privateNetwork;
+    }
+
+    public void addTorInstance(final TorInstance torInstance) {
+        torInstances.add(torInstance);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void addTest(final Test test) {
+        tests.add(test);
+    }
+
+    public Set<LocalMaster> getMasters() {
+        final Set<LocalMaster> masters = new HashSet<LocalMaster>();
+        for (final TorInstance torInstance : torInstances) {
+            masters.add(torInstance.getMaster());
+        }
+        return Collections.unmodifiableSet(masters);
+    }
+
+    public Set<TorInstance> getDirectoryAuthorities() {
+        final Set<TorInstance> authorities = new HashSet<TorInstance>();
+        for (final TorInstance torInstance : torInstances) {
+            if (torInstance.isDirectoryAuthority()) {
+                authorities.add(torInstance);
+            }
+        }
+        return Collections.unmodifiableSet(authorities);
+    }
+
+    public Set<TorInstance> getProxies() {
+        return Collections.unmodifiableSet(torInstances);
+    }
+
+    public Set<TorInstance> getProxiesOnly() {
+        final Set<TorInstance> proxies = new HashSet<TorInstance>();
+        for (final TorInstance torInstance : torInstances) {
+            if (!torInstance.isRouter()) {
+                proxies.add(torInstance);
+            }
+        }
+        return Collections.unmodifiableSet(proxies);
+    }
+
+    public Set<TorInstance> getRouters() {
+        final Set<TorInstance> routers = new HashSet<TorInstance>();
+        for (final TorInstance torInstance : torInstances) {
+            if (!torInstance.isRouter()) {
+                routers.add(torInstance);
+            }
+        }
+        return Collections.unmodifiableSet(routers);
+    }
+
+    public Set<TorInstance> getRoutersOnly() {
+        final Set<TorInstance> routers = new HashSet<TorInstance>();
+        for (final TorInstance torInstance : torInstances) {
+            if (torInstance.isRouter() && !torInstance.isDirectoryAuthority()) {
+                routers.add(torInstance);
+            }
+        }
+        return Collections.unmodifiableSet(routers);
+    }
+
+    public List<Test> getTests() {
+        return Collections.unmodifiableList(tests);
+    }
+
+    public boolean isPrivateNetwork() {
+        return privateNetwork;
+    }
 
 }
diff --git a/src/org/torproject/puppetor/rmi/impl/SlaveImpl.java b/src/org/torproject/puppetor/rmi/impl/SlaveImpl.java
index ca532b7..d930161 100644
--- a/src/org/torproject/puppetor/rmi/impl/SlaveImpl.java
+++ b/src/org/torproject/puppetor/rmi/impl/SlaveImpl.java
@@ -24,109 +24,109 @@ import org.torproject.puppetor.rmi.Slave;
  */
 public final class SlaveImpl implements Slave {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The slave's name
-	 */
-	protected final String name;
-
-	/**
-	 * The slave's ip address
-	 */
-	protected final InetAddress ip;
-
-	/**
-	 * The operating system of the slave. See <code>Slave.OS</code>.
-	 */
-	protected final OS os;
-
-	/**
-	 * The ports this slave can open. Currently, we choose to use 256 as a
-	 * maximum that "should be" enough so we don't transfer so much data when
-	 * serializing. See the constructor. Make sure this is immutable.
-	 */
-	protected final Set<Integer> ports;
-
-	/**
-	 * The different revisions of Tor clients this slave can execute.
-	 */
-	protected final Set<Integer> availableRevisions;
-
-	/**
-	 * Create a new <code>Slave</code> with the metrics we currently use,
-	 * meaning it's operating system, it's IP, and the ports it can open. We may
-	 * in the future include other things like Tor executable version numbers
-	 * and the ability to run certain extra scripts.
-	 *
-	 * @param slaveName
-	 *            the slave's name. Each slave must have it's own unique name in
-	 *            a given PuppeTor network.
-	 * @param os
-	 *            the slave's operating system.
-	 * @param ip
-	 *            A String representation of the slave's IP (v4 or v6).
-	 * @param ports
-	 *            All the ports this slave can open. Currently, we pick 256 out
-	 *            of those at random.
-	 * @throws NullPointerException
-	 *             if the slavename is null
-	 */
-	public SlaveImpl(final String slaveName, final OS os, final String ip,
-			final Set<Integer> ports, final Set<Integer> availableRevisions)
-			throws NullPointerException {
-		if (slaveName == null) {
-			throw new NullPointerException("slaveName must not be null");
-		}
-		name = slaveName;
-		this.os = os;
-		try {
-			this.ip = InetAddress.getByName(ip);
-		} catch (final UnknownHostException e) {
-			e.printStackTrace();
-			throw new RuntimeException("You specified an invalid IP address!");
-		}
-
-		if (ports.size() > 256) {
-			final Set<Integer> tmp = new CopyOnWriteArraySet<Integer>();
-			int i = 0;
-			for (final Integer j : tmp) {
-				if (i++ == 256) {
-					break;
-				}
-				tmp.add(j);
-			}
-			this.ports = Collections.unmodifiableSet(tmp);
-		} else {
-			this.ports =
-					Collections
-							.unmodifiableSet(new CopyOnWriteArraySet<Integer>(
-									ports));
-		}
-		this.availableRevisions =
-				Collections.unmodifiableSet(availableRevisions);
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public OS getOS() {
-		return os;
-	}
-
-	public InetAddress getIP() {
-		return ip;
-	}
-
-	public Set<Integer> openablePorts() {
-		return ports;
-	}
-
-	public Set<Integer> availableRevisions() {
-		return availableRevisions;
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The slave's name
+     */
+    protected final String name;
+
+    /**
+     * The slave's ip address
+     */
+    protected final InetAddress ip;
+
+    /**
+     * The operating system of the slave. See <code>Slave.OS</code>.
+     */
+    protected final OS os;
+
+    /**
+     * The ports this slave can open. Currently, we choose to use 256 as a
+     * maximum that "should be" enough so we don't transfer so much data when
+     * serializing. See the constructor. Make sure this is immutable.
+     */
+    protected final Set<Integer> ports;
+
+    /**
+     * The different revisions of Tor clients this slave can execute.
+     */
+    protected final Set<Integer> availableRevisions;
+
+    /**
+     * Create a new <code>Slave</code> with the metrics we currently use,
+     * meaning it's operating system, it's IP, and the ports it can open. We may
+     * in the future include other things like Tor executable version numbers
+     * and the ability to run certain extra scripts.
+     *
+     * @param slaveName
+     *            the slave's name. Each slave must have it's own unique name in
+     *            a given PuppeTor network.
+     * @param os
+     *            the slave's operating system.
+     * @param ip
+     *            A String representation of the slave's IP (v4 or v6).
+     * @param ports
+     *            All the ports this slave can open. Currently, we pick 256 out
+     *            of those at random.
+     * @throws NullPointerException
+     *             if the slavename is null
+     */
+    public SlaveImpl(final String slaveName, final OS os, final String ip,
+            final Set<Integer> ports, final Set<Integer> availableRevisions)
+            throws NullPointerException {
+        if (slaveName == null) {
+            throw new NullPointerException("slaveName must not be null");
+        }
+        name = slaveName;
+        this.os = os;
+        try {
+            this.ip = InetAddress.getByName(ip);
+        } catch (final UnknownHostException e) {
+            e.printStackTrace();
+            throw new RuntimeException("You specified an invalid IP address!");
+        }
+
+        if (ports.size() > 256) {
+            final Set<Integer> tmp = new CopyOnWriteArraySet<Integer>();
+            int i = 0;
+            for (final Integer j : tmp) {
+                if (i++ == 256) {
+                    break;
+                }
+                tmp.add(j);
+            }
+            this.ports = Collections.unmodifiableSet(tmp);
+        } else {
+            this.ports =
+                    Collections
+                            .unmodifiableSet(new CopyOnWriteArraySet<Integer>(
+                                    ports));
+        }
+        this.availableRevisions =
+                Collections.unmodifiableSet(availableRevisions);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public OS getOS() {
+        return os;
+    }
+
+    public InetAddress getIP() {
+        return ip;
+    }
+
+    public Set<Integer> openablePorts() {
+        return ports;
+    }
+
+    public Set<Integer> availableRevisions() {
+        return availableRevisions;
+    }
 }
diff --git a/src/org/torproject/puppetor/rmi/impl/SlaveImplFactory.java b/src/org/torproject/puppetor/rmi/impl/SlaveImplFactory.java
index 0448d69..b5e7912 100644
--- a/src/org/torproject/puppetor/rmi/impl/SlaveImplFactory.java
+++ b/src/org/torproject/puppetor/rmi/impl/SlaveImplFactory.java
@@ -19,14 +19,14 @@ import org.torproject.puppetor.rmi.Slave.OS;
  */
 public class SlaveImplFactory extends AbstractSlaveFactory {
 
-	/**
-	 * @return a new <code>RemotePuppeTorImpl</code> instance.
-	 */
-	@Override
-	public Slave createSlave(final String slaveName, final OS os,
-			final String ip, final Set<Integer> ports,
-			final Set<Integer> availableRevisions) throws NullPointerException {
-		return new SlaveImpl(slaveName, os, ip, ports, availableRevisions);
-	}
+    /**
+     * @return a new <code>RemotePuppeTorImpl</code> instance.
+     */
+    @Override
+    public Slave createSlave(final String slaveName, final OS os,
+            final String ip, final Set<Integer> ports,
+            final Set<Integer> availableRevisions) throws NullPointerException {
+        return new SlaveImpl(slaveName, os, ip, ports, availableRevisions);
+    }
 
 }
diff --git a/src/org/torproject/puppetor/rmi/impl/TaskResultImpl.java b/src/org/torproject/puppetor/rmi/impl/TaskResultImpl.java
index 2c66f9f..82ac6b5 100644
--- a/src/org/torproject/puppetor/rmi/impl/TaskResultImpl.java
+++ b/src/org/torproject/puppetor/rmi/impl/TaskResultImpl.java
@@ -10,42 +10,42 @@ import org.torproject.puppetor.rmi.TaskResult;
 
 public class TaskResultImpl implements TaskResult {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The task this result is used for.
-	 */
-	final private Task task;
-
-	/**
-	 * Store the result of the <code>task</code>, or <code>null</code> if no
-	 * result is necessary.
-	 */
-	final private String result;
-	
-	/**
-	 * Whether the task execution was successful.
-	 */
-	final private boolean wasSuccessful;
-
-	public TaskResultImpl(final boolean success, final Task task, final String result) {
-		wasSuccessful = success;
-		this.task = task;
-		this.result = result;
-	}
-
-	public Task getTask() {
-		return task;
-	}
-
-	public boolean wasSuccessful() {
-		return wasSuccessful;
-	}
-
-	public String getTaskResult() {
-		return result;
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The task this result is used for.
+     */
+    final private Task task;
+
+    /**
+     * Store the result of the <code>task</code>, or <code>null</code> if no
+     * result is necessary.
+     */
+    final private String result;
+
+    /**
+     * Whether the task execution was successful.
+     */
+    final private boolean wasSuccessful;
+
+    public TaskResultImpl(final boolean success, final Task task, final String result) {
+        wasSuccessful = success;
+        this.task = task;
+        this.result = result;
+    }
+
+    public Task getTask() {
+        return task;
+    }
+
+    public boolean wasSuccessful() {
+        return wasSuccessful;
+    }
+
+    public String getTaskResult() {
+        return result;
+    }
 }
diff --git a/src/org/torproject/puppetor/rmi/impl/TestExecutorImpl.java b/src/org/torproject/puppetor/rmi/impl/TestExecutorImpl.java
index ddf7065..86f59c3 100644
--- a/src/org/torproject/puppetor/rmi/impl/TestExecutorImpl.java
+++ b/src/org/torproject/puppetor/rmi/impl/TestExecutorImpl.java
@@ -53,503 +53,503 @@ import org.torproject.puppetor.rmi.tasks.TerminateTask;
  */
 public final class TestExecutorImpl implements TestExecutor {
 
-	/**
-	 * The TestExecutor instance that we'll use
-	 */
-	private static final TestExecutor instance = new TestExecutorImpl();
-
-	/**
-	 * Store all local representations of connections to slaves. Use the
-	 * slave's name as key.
-	 */
-	private static final ConcurrentMap<String, LocalMaster> masters =
-			new ConcurrentHashMap<String, LocalMaster>();
-
-	/**
-	 * Store all registered tests until they are started.
-	 */
-	private static final List<Test> newTests = new Vector<Test>();
-
-	/**
-	 * Store all currently running tests.
-	 */
-	// private static final List<Test> runningTests = new Vector<Test>();
-	/**
-	 * Store all tests that cannot run because the slave's cannot build such a
-	 * network.
-	 */
-	private static final Set<Test> cannotRunTests =
-			new CopyOnWriteArraySet<Test>();
-
-	/**
-	 * Store the networks that are created out of the NetworkDescriptions.
-	 */
-	private static final Set<Network> networks =
-			new CopyOnWriteArraySet<Network>();
-	
-	/**
-	 * Store the currently active network when executing a test. Null indicates
-	 * that either testing hasn't started yet, or is already finished.
-	 */
-	private static Network currentNetwork = null;
-
-	/**
-	 * Store all failed tests.
-	 */
-	private static final Map<Test, TestResult> failedTests =
-			new ConcurrentHashMap<Test, TestResult>();
-
-	/**
-	 * Store all successfully completed tests.
-	 */
-	private static final Map<Test, TestResult> successfulTests =
-			new ConcurrentHashMap<Test, TestResult>();
-
-	/**
-	 * Whether testing has started. This cannot be reset.
-	 */
-	private static AtomicBoolean startedTesting = new AtomicBoolean(false);
-
-	/**
-	 * Store the id for the next task. Every task gets its own unique id > 0. A
-	 * task id equal to 0 indicates that the task was created by the slave to
-	 * stop execution.
-	 */
-	private static AtomicInteger nextTaskId = new AtomicInteger(1);
-
-	/**
-	 * Singleton
-	 */
-	private TestExecutorImpl() {}
-
-	public static TestExecutor getInstance() {
-		return instance;
-	}
-
-	public void registerLocalMaster(final LocalMaster master) {
-		if (startedTesting.get() == true) {
-			throw new RuntimeException("Bug: Testing has started already.");
-		}
-		System.out.println(master.getSlave().getName() + " registered");
-		if (null != masters.putIfAbsent(master.getSlave().getName(), master)) {
-			throw new IllegalArgumentException(master.getSlave().getName()
-					+ " has already registered with this Server");
-		}
-	}
-
-	public void unregisterLocalMaster(final LocalMaster master) {
-		// XXX Don't forget to mark all tests as failed that depended on this
-		// slave if we have already started with testing-SH
-		try {
-			UnicastRemoteObject.unexportObject((RemoteMaster) master, true);
-		} catch (final NoSuchObjectException e) {
-			throw new RuntimeException(
-					"This is a bug. The master should have been exported.");
-		}
-		if (masters.remove(master.getSlave().getName()) == null) {
-			throw new RuntimeException(
-					"This is a bug. unregisterLocalMaster must never be " +
-					"called twice for the same master.");
-		}
-	}
-
-	/**
-	 * Evaluate which tests can be run with the currently connected slaves.
-	 * Cancel all currently running tests and report them as failed if they
-	 * cannot be restarted with the new (reduced) set of connected slaves,
-	 * restart those tests otherwise. XXX This should be merged with
-	 * makeNetworks -SH
-	 */
-	protected void evaluateNetwork() {
-		if (startedTesting.get() == false) {
-			throw new RuntimeException("Bug: Testing has not been started.");
-			// XXX We need to actually check the network.-SH
-		}
-	}
-
-	public void startTesting() {
-		if (startedTesting.get() == true) {
-			throw new RuntimeException("Bug: Testing has started already.");
-		}
-		startedTesting.set(true);
-		// check which tests we can actually execute
-		evaluateNetwork();
-		for (final Test test : newTests) {
-			makeNetworks(test);
-		}
-		// XXX cancelled networks will still appear here...=SH
-		for (final Network nw : networks) {
-			synchronized(TestExecutorImpl.class) {
-				currentNetwork = nw;
-			}
-			try {
-				executeTaskForMasters(nw.getMasters(),
-						CreateNetworkTask.class, null, null);
-				// The network has been created at the slaves. Set up
-				// authorities, routers and proxies.
-
-				Set<TaskResult> authorityResults =
-						new CopyOnWriteArraySet<TaskResult>();
-				executeTaskForTorInstances(nw.getDirectoryAuthorities(),
-						CreateDirectoryAuthorityTask.class, authorityResults,
-						null);
-				executeTaskForTorInstances(nw.getRoutersOnly(),
-						CreateRouterTask.class, null, null);
-				executeTaskForTorInstances(nw.getProxiesOnly(),
-						CreateProxyTask.class, null, null);
-				if(nw.isPrivateNetwork()) {
-					List<String> authorities = new LinkedList<String>();
-					for(TaskResult result : authorityResults) {
-						authorities.add(result.getTaskResult());
-					}
-					executeTaskForMasters(nw.getMasters(),
-							ConfigureAsPrivateTask.class, null, authorities);
-				}
-				// Start the nodes ...
-				executeTaskForMasters(nw.getMasters(), StartNodesTask.class,
-						null, null);
-				// ... and build circuits. This may take some time.
-				executeTaskForMasters(nw.getMasters(), BuildCircuitsTask.class,
-						null, null);
-
-				// Start using the individual tests that were set up for this
-				// network.
-				for (final Test test : nw.getTests()) {
-					test.doTest();
-				}
-
-				// We don't need the network anymore, make sure it's shut down
-				// properly.
-				executeTaskForMasters(nw.getMasters(), ShutdownNodesTask.class,
-						null, null);
-
-			} catch (final TaskExecutionNotSuccessfulException e) {
-				e.printStackTrace();
-				// XXX Log this properly. -SH
-			}
-
-		}
-		// The terminate Task interrupts the slave's main process to
-		// indicate that it should disconnect.
-		synchronized(TestExecutorImpl.class) {
-			currentNetwork = null;
-		}
-		try {
-			executeTaskForMasters(masters.values(), TerminateTask.class, null,
-					null);
-		} catch (TaskExecutionNotSuccessfulException e) {
-			e.printStackTrace();
-			// XXX Log this properly. -SH
-		}
-	}
-
-	//XXX This method and the next look very similar. See what we can do. -SH
-	public void executeTaskForMasters(final Collection<LocalMaster> masters,
-			final Class<? extends Task> taskClass,
-			final Set<TaskResult> taskResults,
-			Collection<String> additionalInformation)
-			throws TaskExecutionNotSuccessfulException {
-
-		if (masters.isEmpty()) {
-			return;
-		}
-		final Set<MasterTask> mTasks = new HashSet<MasterTask>();
-		Constructor<? extends Task> ctor = null;
-		try {
-			ctor = taskClass.getConstructor(int.class, String.class);
-			String networkName = "";
-			synchronized(TestExecutorImpl.class) {
-				if(currentNetwork == null) {
-					networkName = "";
-				} else {
-					networkName = currentNetwork.getName();
-				}
-			}
-			for (final LocalMaster master : masters) {
-				Task task = ctor.newInstance(
-						nextTaskId.getAndIncrement(), networkName);
-				task.addAdditionalInformation(additionalInformation);
-				mTasks.add(new MasterTask(master, task));				
-			}
-
-			final TaskManager man = new TaskManager(mTasks);
-			final FutureTask<Boolean> futureTask =
-				new FutureTask<Boolean>(man);
-			new Thread(futureTask).start();
-			try {
-				if (futureTask.get() == false) {
-					throw new TaskExecutionNotSuccessfulException(man
-							.getTaskResults());
-				}
-			} catch (final InterruptedException e) {
-				throw new TaskExecutionNotSuccessfulException(null);
-			} catch (final ExecutionException e) {
-				// XXX Log this with the cause. Should not happen. -SH
-				e.printStackTrace();
-				throw new TaskExecutionNotSuccessfulException(null);
-			}
-			if(taskResults != null) {
-				taskResults.addAll(man.getTaskResults());
-			}
-		} catch (final SecurityException e) {
-			throw new RuntimeException(
-					"BUG: We're not using a security manager, so where " +
-					"does this exception come from?");
-		} catch (final NoSuchMethodException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Expected constructor undefined!");
-		} catch (final IllegalArgumentException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Expected constructor undefined!");
-		} catch (final InstantiationException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: InstantiationException.");
-		} catch (final IllegalAccessException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Expected constructor undefined!");
-		} catch (final InvocationTargetException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Constructor threw an exception.");
-		}
-	}
-	
-	public Network getCurrentNetwork() {
-		synchronized(TestExecutorImpl.class) {
-			if(currentNetwork == null) {
-				throw new RuntimeException("There is no network currently!");
-			}
-			return currentNetwork;
-		}
-	}
-
-	public void executeTaskForTorInstances(
-			final Collection<TorInstance> torInstances,
-			final Class<? extends Task> taskClass,
-			final Set<TaskResult> taskResults,
-			Collection<String> additionalInformation)
-			throws TaskExecutionNotSuccessfulException {
-
-		if (torInstances.isEmpty()) {
-			return;
-		}
-		final Set<MasterTask> mTasks = new HashSet<MasterTask>();
-		Constructor<? extends Task> ctor = null;
-		try {
-			ctor = taskClass.getConstructor(int.class, String.class,
-							String.class);
-			String networkName = "";
-			synchronized(TestExecutorImpl.class) {
-				if(currentNetwork == null) {
-					networkName = "";
-				} else {
-					networkName = currentNetwork.getName();
-				}
-			}
-			for (final TorInstance torInstance : torInstances) {
-				Task task = ctor.newInstance(nextTaskId.getAndIncrement(),
-						networkName, torInstance.getName());
-				task.addAdditionalInformation(additionalInformation);
-				mTasks.add(new MasterTask(torInstance.getMaster(), task));
-			}
-
-			final TaskManager man = new TaskManager(mTasks);
-			final FutureTask<Boolean> futureTask =
-				new FutureTask<Boolean>(man);
-			new Thread(futureTask).start();
-			try {
-				if (futureTask.get() == false) {
-					throw new TaskExecutionNotSuccessfulException(man
-							.getTaskResults());
-				}
-			} catch (final InterruptedException e) {
-				throw new TaskExecutionNotSuccessfulException(null);
-			} catch (final ExecutionException e) {
-				// XXX Log this with the cause. Should not happen. -SH
-				e.printStackTrace();
-				throw new TaskExecutionNotSuccessfulException(null);
-			}
-			if(taskResults != null) {
-				taskResults.addAll(man.getTaskResults());
-			}
-		} catch (final SecurityException e) {
-			throw new RuntimeException(
-					"BUG: We're not using a security manager, " +
-					"so where does this exception come from?");
-		} catch (final NoSuchMethodException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Expected constructor undefined!");
-		} catch (final IllegalArgumentException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Expected constructor undefined!");
-		} catch (final InstantiationException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: InstantiationException.");
-		} catch (final IllegalAccessException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Expected constructor undefined!");
-		} catch (final InvocationTargetException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Constructor threw an exception.");
-		}
-	}
-
-	protected void makeNetworks(final Test test) {
-		final NetworkDescription nd = test.getNetworkDescription();
-		// XXX we want to do something much smarter here.-SH
-
-		// XXX obviously, the following code adds the specified amount of
-		// authorities, routers, and proxies for each slave, regardless of
-		// capabilities. That isn't good. -SH
-		for (final LocalMaster master : masters.values()) {
-			int necessaryDirectoryAuthorities =
-				nd.necessaryDirectoryAuthorities();
-			boolean onPrivateNetwork = (necessaryDirectoryAuthorities > 0);
-			// XXX the name choosing doesn't make sense. -SH
-			final Network nw =
-					new NetworkImpl(test.getDescription() + " slavename:"
-							+ master.getSlave().getName(), onPrivateNetwork);
-			while (necessaryDirectoryAuthorities >= 1) {
-				TorInstanceImpl authority = new TorInstanceImpl(master, 
-						"Authority" + necessaryDirectoryAuthorities);
-				authority.setDirectoryAuthority(true);
-				authority.setOnPrivateNetwork(onPrivateNetwork);
-				nw.addTorInstance(authority);
-				necessaryDirectoryAuthorities--;
-			}
-			int necessaryRouters = nd.necessaryRouters();
-			while (necessaryRouters >= 1) {
-				TorInstanceImpl router = new TorInstanceImpl(master, "Router" + 
-						necessaryRouters);
-				router.setRouter(true);
-				router.setOnPrivateNetwork(onPrivateNetwork);
-				nw.addTorInstance(router);
-				necessaryRouters--;
-			}
-			int necessaryProxies = nd.necessaryProxies();
-			while (necessaryProxies >= 1) {
-				TorInstanceImpl proxy = new TorInstanceImpl(master, "Proxy" +
-						necessaryProxies); 
-				proxy.setOnPrivateNetwork(onPrivateNetwork);
-				nw.addTorInstance(proxy);
-				necessaryProxies--;
-			}
-			nw.addTest(test);
-			networks.add(nw);
-		}
-	}
-
-	public Map<Test, TestResult> getFailedTests() {
-		return failedTests;
-	}
-
-	public Set<Test> getCannotRunTests() {
-		return cannotRunTests;
-	}
-
-	public Map<Test, TestResult> getSuccessfulTests() {
-		return successfulTests;
-	}
-
-	public void registerTest(final Test test) {
-		if (startedTesting.get() == true) {
-			throw new RuntimeException("Bug: Testing has started already.");
-		}
-		if (test == null) {
-			throw new NullPointerException("tests must not be null");
-		}
-		newTests.add(test);
-	}
-
-	private static class TaskManager implements Callable<Boolean> {
-
-		final private Set<TaskResult> taskResults =
-				new CopyOnWriteArraySet<TaskResult>();
-
-		final private Object taskResultsLock = new Object();
-
-		final private Set<MasterTask> tasks;
-
-		TaskManager(final Set<MasterTask> tasks) {
-			if (tasks.isEmpty()) {
-				throw new IllegalArgumentException("No tasks passed!");
-			}
-			this.tasks =
-					Collections.synchronizedSet(
-							new HashSet<MasterTask>(tasks));
-		}
-
-		public Boolean call() {
-			for (final MasterTask task : tasks) {
-				task.getMaster().addTask(task.getTask());
-				// XXX Asking for trouble here. task could throw an exception!
-				// -SH
-				new Thread(new TaskCollector(task)).start();
-			}
-			try {
-				synchronized (taskResultsLock) {
-					// XXX See above. This needs more thinking -SH
-					while (tasks.size() != taskResults.size()) {
-						taskResultsLock.wait();
-					}
-					// XXX We want some logic here.-SH
-					for (final TaskResult taskResult : taskResults) {
-						if (!taskResult.wasSuccessful()) {
-							return false;
-						}
-					}
-					return true;
-				}
-			} catch (final InterruptedException e) {
-				// XXX figure out a better solution here -SH
-				e.printStackTrace();
-			}
-			return false;
-		}
-
-		public Set<TaskResult> getTaskResults() {
-			return taskResults;
-		}
-
-		private class TaskCollector implements Runnable {
-			private final MasterTask task;
-
-			public void run() {
-				try {
-					final TaskResult taskResult =
-							task.getMaster().getTaskResult(
-									task.getTask().getId());
-					synchronized (taskResultsLock) {
-						taskResults.add(taskResult);
-						taskResultsLock.notifyAll();
-					}
-				} catch (final InterruptedException e) {
-					e.printStackTrace();
-					Thread.currentThread().interrupt();
-				}
-
-			}
-
-			protected TaskCollector(final MasterTask task) {
-				this.task = task;
-			}
-		}
-	}
-
-	private static class MasterTask {
-		final private LocalMaster master;
-		final private Task task;
-
-		MasterTask(final LocalMaster master, final Task task) {
-			this.master = master;
-			this.task = task;
-		}
-
-		public LocalMaster getMaster() {
-			return master;
-		}
-
-		public Task getTask() {
-			return task;
-		}
-	}
+    /**
+     * The TestExecutor instance that we'll use
+     */
+    private static final TestExecutor instance = new TestExecutorImpl();
+
+    /**
+     * Store all local representations of connections to slaves. Use the
+     * slave's name as key.
+     */
+    private static final ConcurrentMap<String, LocalMaster> masters =
+            new ConcurrentHashMap<String, LocalMaster>();
+
+    /**
+     * Store all registered tests until they are started.
+     */
+    private static final List<Test> newTests = new Vector<Test>();
+
+    /**
+     * Store all currently running tests.
+     */
+    // private static final List<Test> runningTests = new Vector<Test>();
+    /**
+     * Store all tests that cannot run because the slave's cannot build such a
+     * network.
+     */
+    private static final Set<Test> cannotRunTests =
+            new CopyOnWriteArraySet<Test>();
+
+    /**
+     * Store the networks that are created out of the NetworkDescriptions.
+     */
+    private static final Set<Network> networks =
+            new CopyOnWriteArraySet<Network>();
+
+    /**
+     * Store the currently active network when executing a test. Null indicates
+     * that either testing hasn't started yet, or is already finished.
+     */
+    private static Network currentNetwork = null;
+
+    /**
+     * Store all failed tests.
+     */
+    private static final Map<Test, TestResult> failedTests =
+            new ConcurrentHashMap<Test, TestResult>();
+
+    /**
+     * Store all successfully completed tests.
+     */
+    private static final Map<Test, TestResult> successfulTests =
+            new ConcurrentHashMap<Test, TestResult>();
+
+    /**
+     * Whether testing has started. This cannot be reset.
+     */
+    private static AtomicBoolean startedTesting = new AtomicBoolean(false);
+
+    /**
+     * Store the id for the next task. Every task gets its own unique id > 0. A
+     * task id equal to 0 indicates that the task was created by the slave to
+     * stop execution.
+     */
+    private static AtomicInteger nextTaskId = new AtomicInteger(1);
+
+    /**
+     * Singleton
+     */
+    private TestExecutorImpl() {}
+
+    public static TestExecutor getInstance() {
+        return instance;
+    }
+
+    public void registerLocalMaster(final LocalMaster master) {
+        if (startedTesting.get() == true) {
+            throw new RuntimeException("Bug: Testing has started already.");
+        }
+        System.out.println(master.getSlave().getName() + " registered");
+        if (null != masters.putIfAbsent(master.getSlave().getName(), master)) {
+            throw new IllegalArgumentException(master.getSlave().getName()
+                    + " has already registered with this Server");
+        }
+    }
+
+    public void unregisterLocalMaster(final LocalMaster master) {
+        // XXX Don't forget to mark all tests as failed that depended on this
+        // slave if we have already started with testing-SH
+        try {
+            UnicastRemoteObject.unexportObject((RemoteMaster) master, true);
+        } catch (final NoSuchObjectException e) {
+            throw new RuntimeException(
+                    "This is a bug. The master should have been exported.");
+        }
+        if (masters.remove(master.getSlave().getName()) == null) {
+            throw new RuntimeException(
+                    "This is a bug. unregisterLocalMaster must never be " +
+                    "called twice for the same master.");
+        }
+    }
+
+    /**
+     * Evaluate which tests can be run with the currently connected slaves.
+     * Cancel all currently running tests and report them as failed if they
+     * cannot be restarted with the new (reduced) set of connected slaves,
+     * restart those tests otherwise. XXX This should be merged with
+     * makeNetworks -SH
+     */
+    protected void evaluateNetwork() {
+        if (startedTesting.get() == false) {
+            throw new RuntimeException("Bug: Testing has not been started.");
+            // XXX We need to actually check the network.-SH
+        }
+    }
+
+    public void startTesting() {
+        if (startedTesting.get() == true) {
+            throw new RuntimeException("Bug: Testing has started already.");
+        }
+        startedTesting.set(true);
+        // check which tests we can actually execute
+        evaluateNetwork();
+        for (final Test test : newTests) {
+            makeNetworks(test);
+        }
+        // XXX cancelled networks will still appear here...=SH
+        for (final Network nw : networks) {
+            synchronized(TestExecutorImpl.class) {
+                currentNetwork = nw;
+            }
+            try {
+                executeTaskForMasters(nw.getMasters(),
+                        CreateNetworkTask.class, null, null);
+                // The network has been created at the slaves. Set up
+                // authorities, routers and proxies.
+
+                Set<TaskResult> authorityResults =
+                        new CopyOnWriteArraySet<TaskResult>();
+                executeTaskForTorInstances(nw.getDirectoryAuthorities(),
+                        CreateDirectoryAuthorityTask.class, authorityResults,
+                        null);
+                executeTaskForTorInstances(nw.getRoutersOnly(),
+                        CreateRouterTask.class, null, null);
+                executeTaskForTorInstances(nw.getProxiesOnly(),
+                        CreateProxyTask.class, null, null);
+                if(nw.isPrivateNetwork()) {
+                    List<String> authorities = new LinkedList<String>();
+                    for(TaskResult result : authorityResults) {
+                        authorities.add(result.getTaskResult());
+                    }
+                    executeTaskForMasters(nw.getMasters(),
+                            ConfigureAsPrivateTask.class, null, authorities);
+                }
+                // Start the nodes ...
+                executeTaskForMasters(nw.getMasters(), StartNodesTask.class,
+                        null, null);
+                // ... and build circuits. This may take some time.
+                executeTaskForMasters(nw.getMasters(), BuildCircuitsTask.class,
+                        null, null);
+
+                // Start using the individual tests that were set up for this
+                // network.
+                for (final Test test : nw.getTests()) {
+                    test.doTest();
+                }
+
+                // We don't need the network anymore, make sure it's shut down
+                // properly.
+                executeTaskForMasters(nw.getMasters(), ShutdownNodesTask.class,
+                        null, null);
+
+            } catch (final TaskExecutionNotSuccessfulException e) {
+                e.printStackTrace();
+                // XXX Log this properly. -SH
+            }
+
+        }
+        // The terminate Task interrupts the slave's main process to
+        // indicate that it should disconnect.
+        synchronized(TestExecutorImpl.class) {
+            currentNetwork = null;
+        }
+        try {
+            executeTaskForMasters(masters.values(), TerminateTask.class, null,
+                    null);
+        } catch (TaskExecutionNotSuccessfulException e) {
+            e.printStackTrace();
+            // XXX Log this properly. -SH
+        }
+    }
+
+    //XXX This method and the next look very similar. See what we can do. -SH
+    public void executeTaskForMasters(final Collection<LocalMaster> masters,
+            final Class<? extends Task> taskClass,
+            final Set<TaskResult> taskResults,
+            Collection<String> additionalInformation)
+            throws TaskExecutionNotSuccessfulException {
+
+        if (masters.isEmpty()) {
+            return;
+        }
+        final Set<MasterTask> mTasks = new HashSet<MasterTask>();
+        Constructor<? extends Task> ctor = null;
+        try {
+            ctor = taskClass.getConstructor(int.class, String.class);
+            String networkName = "";
+            synchronized(TestExecutorImpl.class) {
+                if(currentNetwork == null) {
+                    networkName = "";
+                } else {
+                    networkName = currentNetwork.getName();
+                }
+            }
+            for (final LocalMaster master : masters) {
+                Task task = ctor.newInstance(
+                        nextTaskId.getAndIncrement(), networkName);
+                task.addAdditionalInformation(additionalInformation);
+                mTasks.add(new MasterTask(master, task));
+            }
+
+            final TaskManager man = new TaskManager(mTasks);
+            final FutureTask<Boolean> futureTask =
+                new FutureTask<Boolean>(man);
+            new Thread(futureTask).start();
+            try {
+                if (futureTask.get() == false) {
+                    throw new TaskExecutionNotSuccessfulException(man
+                            .getTaskResults());
+                }
+            } catch (final InterruptedException e) {
+                throw new TaskExecutionNotSuccessfulException(null);
+            } catch (final ExecutionException e) {
+                // XXX Log this with the cause. Should not happen. -SH
+                e.printStackTrace();
+                throw new TaskExecutionNotSuccessfulException(null);
+            }
+            if(taskResults != null) {
+                taskResults.addAll(man.getTaskResults());
+            }
+        } catch (final SecurityException e) {
+            throw new RuntimeException(
+                    "BUG: We're not using a security manager, so where " +
+                    "does this exception come from?");
+        } catch (final NoSuchMethodException e) {
+            e.printStackTrace();
+            throw new RuntimeException("BUG: Expected constructor undefined!");
+        } catch (final IllegalArgumentException e) {
+            e.printStackTrace();
+            throw new RuntimeException("BUG: Expected constructor undefined!");
+        } catch (final InstantiationException e) {
+            e.printStackTrace();
+            throw new RuntimeException("BUG: InstantiationException.");
+        } catch (final IllegalAccessException e) {
+            e.printStackTrace();
+            throw new RuntimeException("BUG: Expected constructor undefined!");
+        } catch (final InvocationTargetException e) {
+            e.printStackTrace();
+            throw new RuntimeException("BUG: Constructor threw an exception.");
+        }
+    }
+
+    public Network getCurrentNetwork() {
+        synchronized(TestExecutorImpl.class) {
+            if(currentNetwork == null) {
+                throw new RuntimeException("There is no network currently!");
+            }
+            return currentNetwork;
+        }
+    }
+
+    public void executeTaskForTorInstances(
+            final Collection<TorInstance> torInstances,
+            final Class<? extends Task> taskClass,
+            final Set<TaskResult> taskResults,
+            Collection<String> additionalInformation)
+            throws TaskExecutionNotSuccessfulException {
+
+        if (torInstances.isEmpty()) {
+            return;
+        }
+        final Set<MasterTask> mTasks = new HashSet<MasterTask>();
+        Constructor<? extends Task> ctor = null;
+        try {
+            ctor = taskClass.getConstructor(int.class, String.class,
+                            String.class);
+            String networkName = "";
+            synchronized(TestExecutorImpl.class) {
+                if(currentNetwork == null) {
+                    networkName = "";
+                } else {
+                    networkName = currentNetwork.getName();
+                }
+            }
+            for (final TorInstance torInstance : torInstances) {
+                Task task = ctor.newInstance(nextTaskId.getAndIncrement(),
+                        networkName, torInstance.getName());
+                task.addAdditionalInformation(additionalInformation);
+                mTasks.add(new MasterTask(torInstance.getMaster(), task));
+            }
+
+            final TaskManager man = new TaskManager(mTasks);
+            final FutureTask<Boolean> futureTask =
+                new FutureTask<Boolean>(man);
+            new Thread(futureTask).start();
+            try {
+                if (futureTask.get() == false) {
+                    throw new TaskExecutionNotSuccessfulException(man
+                            .getTaskResults());
+                }
+            } catch (final InterruptedException e) {
+                throw new TaskExecutionNotSuccessfulException(null);
+            } catch (final ExecutionException e) {
+                // XXX Log this with the cause. Should not happen. -SH
+                e.printStackTrace();
+                throw new TaskExecutionNotSuccessfulException(null);
+            }
+            if(taskResults != null) {
+                taskResults.addAll(man.getTaskResults());
+            }
+        } catch (final SecurityException e) {
+            throw new RuntimeException(
+                    "BUG: We're not using a security manager, " +
+                    "so where does this exception come from?");
+        } catch (final NoSuchMethodException e) {
+            e.printStackTrace();
+            throw new RuntimeException("BUG: Expected constructor undefined!");
+        } catch (final IllegalArgumentException e) {
+            e.printStackTrace();
+            throw new RuntimeException("BUG: Expected constructor undefined!");
+        } catch (final InstantiationException e) {
+            e.printStackTrace();
+            throw new RuntimeException("BUG: InstantiationException.");
+        } catch (final IllegalAccessException e) {
+            e.printStackTrace();
+            throw new RuntimeException("BUG: Expected constructor undefined!");
+        } catch (final InvocationTargetException e) {
+            e.printStackTrace();
+            throw new RuntimeException("BUG: Constructor threw an exception.");
+        }
+    }
+
+    protected void makeNetworks(final Test test) {
+        final NetworkDescription nd = test.getNetworkDescription();
+        // XXX we want to do something much smarter here.-SH
+
+        // XXX obviously, the following code adds the specified amount of
+        // authorities, routers, and proxies for each slave, regardless of
+        // capabilities. That isn't good. -SH
+        for (final LocalMaster master : masters.values()) {
+            int necessaryDirectoryAuthorities =
+                nd.necessaryDirectoryAuthorities();
+            boolean onPrivateNetwork = (necessaryDirectoryAuthorities > 0);
+            // XXX the name choosing doesn't make sense. -SH
+            final Network nw =
+                    new NetworkImpl(test.getDescription() + " slavename:"
+                            + master.getSlave().getName(), onPrivateNetwork);
+            while (necessaryDirectoryAuthorities >= 1) {
+                TorInstanceImpl authority = new TorInstanceImpl(master,
+                        "Authority" + necessaryDirectoryAuthorities);
+                authority.setDirectoryAuthority(true);
+                authority.setOnPrivateNetwork(onPrivateNetwork);
+                nw.addTorInstance(authority);
+                necessaryDirectoryAuthorities--;
+            }
+            int necessaryRouters = nd.necessaryRouters();
+            while (necessaryRouters >= 1) {
+                TorInstanceImpl router = new TorInstanceImpl(master, "Router" +
+                        necessaryRouters);
+                router.setRouter(true);
+                router.setOnPrivateNetwork(onPrivateNetwork);
+                nw.addTorInstance(router);
+                necessaryRouters--;
+            }
+            int necessaryProxies = nd.necessaryProxies();
+            while (necessaryProxies >= 1) {
+                TorInstanceImpl proxy = new TorInstanceImpl(master, "Proxy" +
+                        necessaryProxies);
+                proxy.setOnPrivateNetwork(onPrivateNetwork);
+                nw.addTorInstance(proxy);
+                necessaryProxies--;
+            }
+            nw.addTest(test);
+            networks.add(nw);
+        }
+    }
+
+    public Map<Test, TestResult> getFailedTests() {
+        return failedTests;
+    }
+
+    public Set<Test> getCannotRunTests() {
+        return cannotRunTests;
+    }
+
+    public Map<Test, TestResult> getSuccessfulTests() {
+        return successfulTests;
+    }
+
+    public void registerTest(final Test test) {
+        if (startedTesting.get() == true) {
+            throw new RuntimeException("Bug: Testing has started already.");
+        }
+        if (test == null) {
+            throw new NullPointerException("tests must not be null");
+        }
+        newTests.add(test);
+    }
+
+    private static class TaskManager implements Callable<Boolean> {
+
+        final private Set<TaskResult> taskResults =
+                new CopyOnWriteArraySet<TaskResult>();
+
+        final private Object taskResultsLock = new Object();
+
+        final private Set<MasterTask> tasks;
+
+        TaskManager(final Set<MasterTask> tasks) {
+            if (tasks.isEmpty()) {
+                throw new IllegalArgumentException("No tasks passed!");
+            }
+            this.tasks =
+                    Collections.synchronizedSet(
+                            new HashSet<MasterTask>(tasks));
+        }
+
+        public Boolean call() {
+            for (final MasterTask task : tasks) {
+                task.getMaster().addTask(task.getTask());
+                // XXX Asking for trouble here. task could throw an exception!
+                // -SH
+                new Thread(new TaskCollector(task)).start();
+            }
+            try {
+                synchronized (taskResultsLock) {
+                    // XXX See above. This needs more thinking -SH
+                    while (tasks.size() != taskResults.size()) {
+                        taskResultsLock.wait();
+                    }
+                    // XXX We want some logic here.-SH
+                    for (final TaskResult taskResult : taskResults) {
+                        if (!taskResult.wasSuccessful()) {
+                            return false;
+                        }
+                    }
+                    return true;
+                }
+            } catch (final InterruptedException e) {
+                // XXX figure out a better solution here -SH
+                e.printStackTrace();
+            }
+            return false;
+        }
+
+        public Set<TaskResult> getTaskResults() {
+            return taskResults;
+        }
+
+        private class TaskCollector implements Runnable {
+            private final MasterTask task;
+
+            public void run() {
+                try {
+                    final TaskResult taskResult =
+                            task.getMaster().getTaskResult(
+                                    task.getTask().getId());
+                    synchronized (taskResultsLock) {
+                        taskResults.add(taskResult);
+                        taskResultsLock.notifyAll();
+                    }
+                } catch (final InterruptedException e) {
+                    e.printStackTrace();
+                    Thread.currentThread().interrupt();
+                }
+
+            }
+
+            protected TaskCollector(final MasterTask task) {
+                this.task = task;
+            }
+        }
+    }
+
+    private static class MasterTask {
+        final private LocalMaster master;
+        final private Task task;
+
+        MasterTask(final LocalMaster master, final Task task) {
+            this.master = master;
+            this.task = task;
+        }
+
+        public LocalMaster getMaster() {
+            return master;
+        }
+
+        public Task getTask() {
+            return task;
+        }
+    }
 }
diff --git a/src/org/torproject/puppetor/rmi/impl/TorInstanceImpl.java b/src/org/torproject/puppetor/rmi/impl/TorInstanceImpl.java
index eba41fa..4fea8e9 100644
--- a/src/org/torproject/puppetor/rmi/impl/TorInstanceImpl.java
+++ b/src/org/torproject/puppetor/rmi/impl/TorInstanceImpl.java
@@ -22,154 +22,154 @@ import org.torproject.puppetor.rmi.TorInstance;
  */
 public class TorInstanceImpl implements TorInstance {
 
-	/**
-	 * Saves configuration options in an "option value" format.
-	 */
-	private final List<String> configuration = new LinkedList<String>();
-
-	/**
-	 * The name of this node.
-	 */
-	private final String name;
-
-	/**
-	 * The svn revision of the Tor build. 0 means no specified revision.
-	 */
-	private final AtomicInteger revision = new AtomicInteger(0);
-
-	/**
-	 * Whether we are a bridge authority.
-	 */
-	private final AtomicBoolean bridgeAuthority = new AtomicBoolean(false);
-
-	/**
-	 * Whether we are a directory authority.
-	 */
-	final private AtomicBoolean directoryAuthority = new AtomicBoolean(false);
-
-	/**
-	 * Whether we are an exit.
-	 */
-	private final AtomicBoolean exit = new AtomicBoolean(false);
-
-	/**
-	 * Whether we are a relay.
-	 */
-	private final AtomicBoolean router = new AtomicBoolean(false);
-
-	/**
-	 * Whether we are a bridge.
-	 */
-	private final AtomicBoolean bridge = new AtomicBoolean(false);
-
-	/**
-	 * Whether we are on a private network.
-	 */
-	private AtomicBoolean privateNetwork = new AtomicBoolean(false);
-
-	/**
-	 * Whether the configuration of the tor instance should be replaced by the
-	 * config returned from <code>this.getConfiguration()</code>.
-	 */
-	private AtomicBoolean replaceConfiguration = new AtomicBoolean(false);
-
-	/**
-	 * Provide a link to the slave where this torInstance will run.
-	 */
-	private final LocalMaster master;
-
-	/**
-	 * @param master
-	 * 		the <code>LocalMaster</code> that represents the slave on which
-	 * 		this Tor instance is running.
-	 * @param name
-	 * 		A name to identify the node.
-	 * 		XXX think about duplicates, we need a unique name to identify the
-	 * 		node, but is it smart to use that name as the Tor proxy's name as
-	 * 		well? -SH
-	 */
-	TorInstanceImpl(final LocalMaster master, final String name) {
-		this.master = master;
-		this.name = name;
-	}
-
-	public synchronized List<String> getConfiguration() {
-		return Collections.unmodifiableList(configuration);
-	}
-	
-	public synchronized void addConfiguration(List<String> newConfig) {
-		configuration.addAll(newConfig);
-	}
-
-	public int getRevision() {
-		return revision.get();
-	}
-	
-	public void setRevision(final int revision) {
-		this.revision.set(revision);
-	}
-
-	public boolean isBridgeAuthority() {
-		return bridgeAuthority.get();
-	}
-	
-	public void setBridgeAuthority(boolean isBridgeAuthority) {
-		bridgeAuthority.set(isBridgeAuthority);
-	}
-
-	public boolean isDirectoryAuthority() {
-		return directoryAuthority.get();
-	}
-	
-	public void setDirectoryAuthority(boolean isDirectoryAuthority) {
-		if(isDirectoryAuthority) {
-			setRouter(true);
-		}
-		directoryAuthority.set(isDirectoryAuthority);
-	}
-
-	public boolean isExit() {
-		return exit.get();
-	}
-
-	public boolean isRouter() {
-		return router.get();
-	}
-	
-	public void setRouter(boolean isRouter) {
-		if(!isRouter) {
-			setDirectoryAuthority(false);
-		}
-		router.set(isRouter);
-	}
-
-	public boolean isOnPrivateNetwork() {
-		return privateNetwork.get();
-	}
-	
-	public void setOnPrivateNetwork(boolean onPrivateNetwork) {
-		privateNetwork.set(onPrivateNetwork);
-	}
-
-	public boolean shouldReplaceConfiguration() {
-		return replaceConfiguration.get();
-	}
-
-	public void setShouldReplaceConfiguration(
-			boolean shouldReplaceConfiguration) {
-		replaceConfiguration.set(shouldReplaceConfiguration);
-	}
-
-	public boolean isBridge() {
-		return bridge.get();
-	}
-
-	public LocalMaster getMaster() {
-		return master;
-	}
-
-	public synchronized String getName() {
-		return name;
-	}
+    /**
+     * Saves configuration options in an "option value" format.
+     */
+    private final List<String> configuration = new LinkedList<String>();
+
+    /**
+     * The name of this node.
+     */
+    private final String name;
+
+    /**
+     * The svn revision of the Tor build. 0 means no specified revision.
+     */
+    private final AtomicInteger revision = new AtomicInteger(0);
+
+    /**
+     * Whether we are a bridge authority.
+     */
+    private final AtomicBoolean bridgeAuthority = new AtomicBoolean(false);
+
+    /**
+     * Whether we are a directory authority.
+     */
+    final private AtomicBoolean directoryAuthority = new AtomicBoolean(false);
+
+    /**
+     * Whether we are an exit.
+     */
+    private final AtomicBoolean exit = new AtomicBoolean(false);
+
+    /**
+     * Whether we are a relay.
+     */
+    private final AtomicBoolean router = new AtomicBoolean(false);
+
+    /**
+     * Whether we are a bridge.
+     */
+    private final AtomicBoolean bridge = new AtomicBoolean(false);
+
+    /**
+     * Whether we are on a private network.
+     */
+    private AtomicBoolean privateNetwork = new AtomicBoolean(false);
+
+    /**
+     * Whether the configuration of the tor instance should be replaced by the
+     * config returned from <code>this.getConfiguration()</code>.
+     */
+    private AtomicBoolean replaceConfiguration = new AtomicBoolean(false);
+
+    /**
+     * Provide a link to the slave where this torInstance will run.
+     */
+    private final LocalMaster master;
+
+    /**
+     * @param master
+     *         the <code>LocalMaster</code> that represents the slave on which
+     *         this Tor instance is running.
+     * @param name
+     *         A name to identify the node.
+     *         XXX think about duplicates, we need a unique name to identify the
+     *         node, but is it smart to use that name as the Tor proxy's name as
+     *         well? -SH
+     */
+    TorInstanceImpl(final LocalMaster master, final String name) {
+        this.master = master;
+        this.name = name;
+    }
+
+    public synchronized List<String> getConfiguration() {
+        return Collections.unmodifiableList(configuration);
+    }
+
+    public synchronized void addConfiguration(List<String> newConfig) {
+        configuration.addAll(newConfig);
+    }
+
+    public int getRevision() {
+        return revision.get();
+    }
+
+    public void setRevision(final int revision) {
+        this.revision.set(revision);
+    }
+
+    public boolean isBridgeAuthority() {
+        return bridgeAuthority.get();
+    }
+
+    public void setBridgeAuthority(boolean isBridgeAuthority) {
+        bridgeAuthority.set(isBridgeAuthority);
+    }
+
+    public boolean isDirectoryAuthority() {
+        return directoryAuthority.get();
+    }
+
+    public void setDirectoryAuthority(boolean isDirectoryAuthority) {
+        if(isDirectoryAuthority) {
+            setRouter(true);
+        }
+        directoryAuthority.set(isDirectoryAuthority);
+    }
+
+    public boolean isExit() {
+        return exit.get();
+    }
+
+    public boolean isRouter() {
+        return router.get();
+    }
+
+    public void setRouter(boolean isRouter) {
+        if(!isRouter) {
+            setDirectoryAuthority(false);
+        }
+        router.set(isRouter);
+    }
+
+    public boolean isOnPrivateNetwork() {
+        return privateNetwork.get();
+    }
+
+    public void setOnPrivateNetwork(boolean onPrivateNetwork) {
+        privateNetwork.set(onPrivateNetwork);
+    }
+
+    public boolean shouldReplaceConfiguration() {
+        return replaceConfiguration.get();
+    }
+
+    public void setShouldReplaceConfiguration(
+            boolean shouldReplaceConfiguration) {
+        replaceConfiguration.set(shouldReplaceConfiguration);
+    }
+
+    public boolean isBridge() {
+        return bridge.get();
+    }
+
+    public LocalMaster getMaster() {
+        return master;
+    }
+
+    public synchronized String getName() {
+        return name;
+    }
 
 }
diff --git a/src/org/torproject/puppetor/rmi/tasks/AccessGoogleTask.java b/src/org/torproject/puppetor/rmi/tasks/AccessGoogleTask.java
index e2dae7d..d866223 100644
--- a/src/org/torproject/puppetor/rmi/tasks/AccessGoogleTask.java
+++ b/src/org/torproject/puppetor/rmi/tasks/AccessGoogleTask.java
@@ -18,57 +18,57 @@ import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
 
 
 public class AccessGoogleTask extends AbstractTaskImpl {
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
 
-	/**
-	 * The name of this tor node.
-	 */
-	private final String nodeName;
-	
-	public AccessGoogleTask(final int id, final String networkName,
-			final String nodeName) {
-		super(id, networkName);
-		this.nodeName = nodeName;
-	}
+    /**
+     * The name of this tor node.
+     */
+    private final String nodeName;
 
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		
-		final ClientApplication client =
-			network.createClient("client", "www.google.com", 80, network.getNode(nodeName)
-					.getSocksPort());
+    public AccessGoogleTask(final int id, final String networkName,
+            final String nodeName) {
+        super(id, networkName);
+        this.nodeName = nodeName;
+    }
 
-		// create event listener to listen for client application events
-		final EventListener clientEventListener = new EventListener() {
-	
-			// remember time when request was sent
-			//private long before;
-	
-			public void handleEvent(Event event) {
-				if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
-					//before = System.currentTimeMillis();
-				} else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
-					// XXX Log how long requests took, see whether we need this all -SH
-					;
-				}
-			}
-		};
-	
-		// obtain reference to event manager to be able to respond to events
-		final EventManager manager = network.getEventManager();
-	
-		manager.addEventListener(client.getClientApplicationName(),
-				clientEventListener);
+    @Override
+    public void execute() throws InterruptedException, RemoteException {
+        final Network network = NetworkFactory.getNetworkByName(name);
 
-		// perform at most three request with a timeout of 20 seconds each
-		client.startRequests(3, 20000, true);
-	
-		// block this thread as long as client requests are running, but don't wait forever.
-		reportResult(manager.waitForAnyOccurence(client.getClientApplicationName(),
-				ClientEventType.CLIENT_REQUESTS_PERFORMED, 75000));
-	}
+        final ClientApplication client =
+            network.createClient("client", "www.google.com", 80, network.getNode(nodeName)
+                    .getSocksPort());
+
+        // create event listener to listen for client application events
+        final EventListener clientEventListener = new EventListener() {
+
+            // remember time when request was sent
+            //private long before;
+
+            public void handleEvent(Event event) {
+                if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
+                    //before = System.currentTimeMillis();
+                } else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
+                    // XXX Log how long requests took, see whether we need this all -SH
+                    ;
+                }
+            }
+        };
+
+        // obtain reference to event manager to be able to respond to events
+        final EventManager manager = network.getEventManager();
+
+        manager.addEventListener(client.getClientApplicationName(),
+                clientEventListener);
+
+        // perform at most three request with a timeout of 20 seconds each
+        client.startRequests(3, 20000, true);
+
+        // block this thread as long as client requests are running, but don't wait forever.
+        reportResult(manager.waitForAnyOccurence(client.getClientApplicationName(),
+                ClientEventType.CLIENT_REQUESTS_PERFORMED, 75000));
+    }
 }
diff --git a/src/org/torproject/puppetor/rmi/tasks/AddHiddenServiceTask.java b/src/org/torproject/puppetor/rmi/tasks/AddHiddenServiceTask.java
index cf206bb..7305c32 100644
--- a/src/org/torproject/puppetor/rmi/tasks/AddHiddenServiceTask.java
+++ b/src/org/torproject/puppetor/rmi/tasks/AddHiddenServiceTask.java
@@ -24,83 +24,83 @@ import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
 
 
 public class AddHiddenServiceTask extends AbstractTaskImpl {
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The name of this tor node.
-	 */
-	private final String nodeName;
-	
-	public AddHiddenServiceTask(final int id, final String networkName,
-			final String nodeName) {
-		super(id, networkName);
-		this.nodeName = nodeName;
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		ProxyNode node = network.getNode(nodeName);
-		try {
-			// add hidden service
-			final HiddenService hidServ = node.addHiddenService("hidServ");
-			node.writeConfiguration();
-			node.hup();
-			final EventManager manager = network.getEventManager();
-	
-			// wait for 6 minutes that the proxy has published its first RSD
-			if (!manager.waitForAnyOccurence(node.getNodeName(),
-					HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
-					6L * 60L * 1000L)) {
-				// failed to publish an RSD
-				reportResult(false);
-				return;
-			}
-	
-			// create server application
-			final ServerApplication server =
-					network.createServer("server", hidServ.getServicePort());
-	
-			// create client application, pick one of the nodes.
-			// XXX fix this so we can choose which node we want. -SH
-			Set<String> nodes = network.getAllNodes().keySet();
-			String[] localNodeNames = new String[10];
-			localNodeNames = nodes.toArray(localNodeNames);
-			final ClientApplication client =
-					network.createClient("client",
-							hidServ.determineOnionAddress(), hidServ
-									.getVirtualPort(), network.getNode(localNodeNames[0]).getSocksPort());
-	
-			// register event listener
-			final EventListener clientAndServerEventListener = new EventListener() {
-				public void handleEvent(Event event) {
-					// XXX log -SH
-				}
-			};
-			manager.addEventListener(client.getClientApplicationName(),
-					clientAndServerEventListener);
-			manager.addEventListener(server.getServerApplicationName(),
-					clientAndServerEventListener);
-	
-			// start server
-			server.startListening();
-	
-			// perform at most five request with a timeout of 45 seconds each
-			client.startRequests(5, 45000, true);
-	
-			// wait for request to be performed
-			manager.waitForAnyOccurence(client.getClientApplicationName(),
-					ClientEventType.CLIENT_REQUESTS_PERFORMED);
-			reportResult(true);
-			} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-
-
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The name of this tor node.
+     */
+    private final String nodeName;
+
+    public AddHiddenServiceTask(final int id, final String networkName,
+            final String nodeName) {
+        super(id, networkName);
+        this.nodeName = nodeName;
+    }
+
+    @Override
+    public void execute() throws InterruptedException, RemoteException {
+        final Network network = NetworkFactory.getNetworkByName(name);
+        ProxyNode node = network.getNode(nodeName);
+        try {
+            // add hidden service
+            final HiddenService hidServ = node.addHiddenService("hidServ");
+            node.writeConfiguration();
+            node.hup();
+            final EventManager manager = network.getEventManager();
+
+            // wait for 6 minutes that the proxy has published its first RSD
+            if (!manager.waitForAnyOccurence(node.getNodeName(),
+                    HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
+                    6L * 60L * 1000L)) {
+                // failed to publish an RSD
+                reportResult(false);
+                return;
+            }
+
+            // create server application
+            final ServerApplication server =
+                    network.createServer("server", hidServ.getServicePort());
+
+            // create client application, pick one of the nodes.
+            // XXX fix this so we can choose which node we want. -SH
+            Set<String> nodes = network.getAllNodes().keySet();
+            String[] localNodeNames = new String[10];
+            localNodeNames = nodes.toArray(localNodeNames);
+            final ClientApplication client =
+                    network.createClient("client",
+                            hidServ.determineOnionAddress(), hidServ
+                                    .getVirtualPort(), network.getNode(localNodeNames[0]).getSocksPort());
+
+            // register event listener
+            final EventListener clientAndServerEventListener = new EventListener() {
+                public void handleEvent(Event event) {
+                    // XXX log -SH
+                }
+            };
+            manager.addEventListener(client.getClientApplicationName(),
+                    clientAndServerEventListener);
+            manager.addEventListener(server.getServerApplicationName(),
+                    clientAndServerEventListener);
+
+            // start server
+            server.startListening();
+
+            // perform at most five request with a timeout of 45 seconds each
+            client.startRequests(5, 45000, true);
+
+            // wait for request to be performed
+            manager.waitForAnyOccurence(client.getClientApplicationName(),
+                    ClientEventType.CLIENT_REQUESTS_PERFORMED);
+            reportResult(true);
+            } catch (final PuppeTorException e) {
+            e.printStackTrace();
+            reportResult(false);
+        }
+
+
+    }
 
 }
diff --git a/src/org/torproject/puppetor/rmi/tasks/BuildCircuitsTask.java b/src/org/torproject/puppetor/rmi/tasks/BuildCircuitsTask.java
index 85d0b86..58181df 100644
--- a/src/org/torproject/puppetor/rmi/tasks/BuildCircuitsTask.java
+++ b/src/org/torproject/puppetor/rmi/tasks/BuildCircuitsTask.java
@@ -15,27 +15,27 @@ import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
 
 public class BuildCircuitsTask extends AbstractTaskImpl {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	public BuildCircuitsTask(final int id, final String networkName) {
-		super(id, networkName);
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		try {
-			if (!network.hupUntilUp(50, 10000)) {
-				reportResult(false);
-			}
-		} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-		reportResult(true);
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    public BuildCircuitsTask(final int id, final String networkName) {
+        super(id, networkName);
+    }
+
+    @Override
+    public void execute() throws InterruptedException, RemoteException {
+        final Network network = NetworkFactory.getNetworkByName(name);
+        try {
+            if (!network.hupUntilUp(50, 10000)) {
+                reportResult(false);
+            }
+        } catch (final PuppeTorException e) {
+            e.printStackTrace();
+            reportResult(false);
+        }
+        reportResult(true);
+    }
 
 }
diff --git a/src/org/torproject/puppetor/rmi/tasks/ConfigureAsPrivateTask.java b/src/org/torproject/puppetor/rmi/tasks/ConfigureAsPrivateTask.java
index 2c6aa20..462a921 100644
--- a/src/org/torproject/puppetor/rmi/tasks/ConfigureAsPrivateTask.java
+++ b/src/org/torproject/puppetor/rmi/tasks/ConfigureAsPrivateTask.java
@@ -18,33 +18,33 @@ import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
 
 public class ConfigureAsPrivateTask extends AbstractTaskImpl {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-	
-	final List<String> authorityInformation = new LinkedList<String>();
-
-	public ConfigureAsPrivateTask(final int id, final String networkName) {
-		super(id, networkName);
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		network.configureAsPartOfPrivateNetwork(authorityInformation);
-		try {
-			network.writeConfigurations();
-			reportResult(true);
-		} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-	}
-	
-	@Override
-	public void addAdditionalInformation(
-			Collection<String> additionalInformation) {
-		authorityInformation.addAll(additionalInformation);
-	}
-}
\ No newline at end of file
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    final List<String> authorityInformation = new LinkedList<String>();
+
+    public ConfigureAsPrivateTask(final int id, final String networkName) {
+        super(id, networkName);
+    }
+
+    @Override
+    public void execute() throws InterruptedException, RemoteException {
+        final Network network = NetworkFactory.getNetworkByName(name);
+        network.configureAsPartOfPrivateNetwork(authorityInformation);
+        try {
+            network.writeConfigurations();
+            reportResult(true);
+        } catch (final PuppeTorException e) {
+            e.printStackTrace();
+            reportResult(false);
+        }
+    }
+
+    @Override
+    public void addAdditionalInformation(
+            Collection<String> additionalInformation) {
+        authorityInformation.addAll(additionalInformation);
+    }
+}
diff --git a/src/org/torproject/puppetor/rmi/tasks/CreateDirectoryAuthorityTask.java b/src/org/torproject/puppetor/rmi/tasks/CreateDirectoryAuthorityTask.java
index e3bd07c..89eb437 100644
--- a/src/org/torproject/puppetor/rmi/tasks/CreateDirectoryAuthorityTask.java
+++ b/src/org/torproject/puppetor/rmi/tasks/CreateDirectoryAuthorityTask.java
@@ -16,33 +16,33 @@ import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
 
 public class CreateDirectoryAuthorityTask extends AbstractTaskImpl {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The name of this Tor authority.
-	 */
-	private final String authorityName;
-
-	public CreateDirectoryAuthorityTask(final int id, final String networkName,
-			final String authorityName) {
-		super(id, networkName);
-		this.authorityName = authorityName;
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		DirectoryNode node = network.createDirectory(authorityName);
-		// write configuration of proxy node
-		try {
-			network.writeConfigurations();
-			reportResult(true, node.getDirServerString());
-		} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The name of this Tor authority.
+     */
+    private final String authorityName;
+
+    public CreateDirectoryAuthorityTask(final int id, final String networkName,
+            final String authorityName) {
+        super(id, networkName);
+        this.authorityName = authorityName;
+    }
+
+    @Override
+    public void execute() throws InterruptedException, RemoteException {
+        final Network network = NetworkFactory.getNetworkByName(name);
+        DirectoryNode node = network.createDirectory(authorityName);
+        // write configuration of proxy node
+        try {
+            network.writeConfigurations();
+            reportResult(true, node.getDirServerString());
+        } catch (final PuppeTorException e) {
+            e.printStackTrace();
+            reportResult(false);
+        }
+    }
 }
diff --git a/src/org/torproject/puppetor/rmi/tasks/CreateNetworkTask.java b/src/org/torproject/puppetor/rmi/tasks/CreateNetworkTask.java
index 31fea77..d3246bb 100644
--- a/src/org/torproject/puppetor/rmi/tasks/CreateNetworkTask.java
+++ b/src/org/torproject/puppetor/rmi/tasks/CreateNetworkTask.java
@@ -13,19 +13,19 @@ import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
 
 public class CreateNetworkTask extends AbstractTaskImpl {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	public CreateNetworkTask(final int id, final String name) {
-		super(id, name);
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		NetworkFactory.createNetwork(name);
-		reportResult(true);
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    public CreateNetworkTask(final int id, final String name) {
+        super(id, name);
+    }
+
+    @Override
+    public void execute() throws InterruptedException, RemoteException {
+        NetworkFactory.createNetwork(name);
+        reportResult(true);
+    }
 
 }
diff --git a/src/org/torproject/puppetor/rmi/tasks/CreateProxyTask.java b/src/org/torproject/puppetor/rmi/tasks/CreateProxyTask.java
index 5f4f5c0..64b84b0 100644
--- a/src/org/torproject/puppetor/rmi/tasks/CreateProxyTask.java
+++ b/src/org/torproject/puppetor/rmi/tasks/CreateProxyTask.java
@@ -15,34 +15,34 @@ import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
 
 public class CreateProxyTask extends AbstractTaskImpl {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The name of this tor proxy.
-	 */
-	private final String proxyName;
-
-	public CreateProxyTask(final int id, final String networkName,
-			final String proxyName) {
-		super(id, networkName);
-		this.proxyName = proxyName;
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		network.createProxy(proxyName);
-
-		// write configuration of proxy node
-		try {
-			network.writeConfigurations();
-			reportResult(true);
-		} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The name of this tor proxy.
+     */
+    private final String proxyName;
+
+    public CreateProxyTask(final int id, final String networkName,
+            final String proxyName) {
+        super(id, networkName);
+        this.proxyName = proxyName;
+    }
+
+    @Override
+    public void execute() throws InterruptedException, RemoteException {
+        final Network network = NetworkFactory.getNetworkByName(name);
+        network.createProxy(proxyName);
+
+        // write configuration of proxy node
+        try {
+            network.writeConfigurations();
+            reportResult(true);
+        } catch (final PuppeTorException e) {
+            e.printStackTrace();
+            reportResult(false);
+        }
+    }
 }
diff --git a/src/org/torproject/puppetor/rmi/tasks/CreateRouterTask.java b/src/org/torproject/puppetor/rmi/tasks/CreateRouterTask.java
index 09b74cf..c328f7f 100644
--- a/src/org/torproject/puppetor/rmi/tasks/CreateRouterTask.java
+++ b/src/org/torproject/puppetor/rmi/tasks/CreateRouterTask.java
@@ -15,34 +15,34 @@ import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
 
 public class CreateRouterTask extends AbstractTaskImpl {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The name of this router.
-	 */
-	private final String routerName;
-
-	public CreateRouterTask(final int id, final String networkName,
-			final String routerName) {
-		super(id, networkName);
-		this.routerName = routerName;
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		network.createRouter(routerName);
-		// write configuration of router node
-		try {
-			network.writeConfigurations();
-			reportResult(true);
-		} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The name of this router.
+     */
+    private final String routerName;
+
+    public CreateRouterTask(final int id, final String networkName,
+            final String routerName) {
+        super(id, networkName);
+        this.routerName = routerName;
+    }
+
+    @Override
+    public void execute() throws InterruptedException, RemoteException {
+        final Network network = NetworkFactory.getNetworkByName(name);
+        network.createRouter(routerName);
+        // write configuration of router node
+        try {
+            network.writeConfigurations();
+            reportResult(true);
+        } catch (final PuppeTorException e) {
+            e.printStackTrace();
+            reportResult(false);
+        }
+
+    }
 }
diff --git a/src/org/torproject/puppetor/rmi/tasks/ShutdownNodesTask.java b/src/org/torproject/puppetor/rmi/tasks/ShutdownNodesTask.java
index 7837d5b..f50e0b5 100644
--- a/src/org/torproject/puppetor/rmi/tasks/ShutdownNodesTask.java
+++ b/src/org/torproject/puppetor/rmi/tasks/ShutdownNodesTask.java
@@ -15,24 +15,24 @@ import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
 
 public class ShutdownNodesTask extends AbstractTaskImpl {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	public ShutdownNodesTask(final int id, final String name) {
-		super(id, name);
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		try {
-			network.shutdownNodes();
-			reportResult(true);
-		} catch (final PuppeTorException e) {
-			reportResult(false);
-		}
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    public ShutdownNodesTask(final int id, final String name) {
+        super(id, name);
+    }
+
+    @Override
+    public void execute() throws InterruptedException, RemoteException {
+        final Network network = NetworkFactory.getNetworkByName(name);
+        try {
+            network.shutdownNodes();
+            reportResult(true);
+        } catch (final PuppeTorException e) {
+            reportResult(false);
+        }
+    }
 
 }
diff --git a/src/org/torproject/puppetor/rmi/tasks/StartNodesTask.java b/src/org/torproject/puppetor/rmi/tasks/StartNodesTask.java
index 1049cd5..abc1cd1 100644
--- a/src/org/torproject/puppetor/rmi/tasks/StartNodesTask.java
+++ b/src/org/torproject/puppetor/rmi/tasks/StartNodesTask.java
@@ -15,28 +15,28 @@ import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
 
 public class StartNodesTask extends AbstractTaskImpl {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	public StartNodesTask(final int id, final String networkName) {
-		super(id, networkName);
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		try {
-			if (!network.startNodes(5000)) {
-				// failed to start the proxy
-				reportResult(false);
-			}
-		} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-		reportResult(true);
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    public StartNodesTask(final int id, final String networkName) {
+        super(id, networkName);
+    }
+
+    @Override
+    public void execute() throws InterruptedException, RemoteException {
+        final Network network = NetworkFactory.getNetworkByName(name);
+        try {
+            if (!network.startNodes(5000)) {
+                // failed to start the proxy
+                reportResult(false);
+            }
+        } catch (final PuppeTorException e) {
+            e.printStackTrace();
+            reportResult(false);
+        }
+        reportResult(true);
+    }
 
 }
diff --git a/src/org/torproject/puppetor/rmi/tasks/TerminateTask.java b/src/org/torproject/puppetor/rmi/tasks/TerminateTask.java
index c2e1ac7..4aa194d 100644
--- a/src/org/torproject/puppetor/rmi/tasks/TerminateTask.java
+++ b/src/org/torproject/puppetor/rmi/tasks/TerminateTask.java
@@ -12,19 +12,19 @@ import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
 
 public class TerminateTask extends AbstractTaskImpl {
 
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	public TerminateTask(final int id, final String name) {
-		super(id, name);
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		Thread.currentThread().interrupt();
-		reportResult(true);
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    public TerminateTask(final int id, final String name) {
+        super(id, name);
+    }
+
+    @Override
+    public void execute() throws InterruptedException, RemoteException {
+        Thread.currentThread().interrupt();
+        reportResult(true);
+    }
 
 }
diff --git a/src/org/torproject/puppetor/rmi/tests/AccessGoogleOverPublicTorNetwork.java b/src/org/torproject/puppetor/rmi/tests/AccessGoogleOverPublicTorNetwork.java
index 4eb6b8a..9391024 100644
--- a/src/org/torproject/puppetor/rmi/tests/AccessGoogleOverPublicTorNetwork.java
+++ b/src/org/torproject/puppetor/rmi/tests/AccessGoogleOverPublicTorNetwork.java
@@ -16,25 +16,25 @@ import org.torproject.puppetor.rmi.tasks.AccessGoogleTask;
 
 
 public class AccessGoogleOverPublicTorNetwork extends AbstractTestImpl {
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-	
-	public AccessGoogleOverPublicTorNetwork() {
-		super("Use the public Tor network to connect to google.com and wait" +
-				" for a response", new NetworkDescriptionImpl(1, 0, 0));
-	}
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
 
-	public void doTest() {
-		Network nw = exec.getCurrentNetwork();
-		Set<TorInstance> proxies = nw.getProxies();
-		try {
-			exec.executeTaskForTorInstances(proxies, AccessGoogleTask.class, null, null);
-			System.out.println("Accessing google succeeded");
-		} catch (TaskExecutionNotSuccessfulException e) {
-			System.out.println("Accessing google failed");
-			// Log this properly, especially where we failed
-		}
-	}
-}
\ No newline at end of file
+    public AccessGoogleOverPublicTorNetwork() {
+        super("Use the public Tor network to connect to google.com and wait" +
+                " for a response", new NetworkDescriptionImpl(1, 0, 0));
+    }
+
+    public void doTest() {
+        Network nw = exec.getCurrentNetwork();
+        Set<TorInstance> proxies = nw.getProxies();
+        try {
+            exec.executeTaskForTorInstances(proxies, AccessGoogleTask.class, null, null);
+            System.out.println("Accessing google succeeded");
+        } catch (TaskExecutionNotSuccessfulException e) {
+            System.out.println("Accessing google failed");
+            // Log this properly, especially where we failed
+        }
+    }
+}
diff --git a/src/org/torproject/puppetor/rmi/tests/StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest.java b/src/org/torproject/puppetor/rmi/tests/StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest.java
index b11a7c3..3195270 100644
--- a/src/org/torproject/puppetor/rmi/tests/StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest.java
+++ b/src/org/torproject/puppetor/rmi/tests/StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest.java
@@ -16,26 +16,26 @@ import org.torproject.puppetor.rmi.tasks.AddHiddenServiceTask;
 
 
 public class StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest extends
-		AbstractTestImpl {
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-	
-	public StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest() {
-		super("Create a private Tor network, set up a hidden service, " +
-				"and access it", new NetworkDescriptionImpl(1, 3, 2));
-	}
-	
-	public void doTest() {
-		Network nw = exec.getCurrentNetwork();
-		Set<TorInstance> proxies = nw.getProxiesOnly();
-		try {
-			exec.executeTaskForTorInstances(proxies, AddHiddenServiceTask.class, null, null);
-			System.out.println("Accessing the hidden service succeeded.");
-		} catch (TaskExecutionNotSuccessfulException e) {
-			System.out.println("Accessing the hidden service failed.");
-			// Log this properly, especially where we failed
-		}
-	}
+        AbstractTestImpl {
+    /**
+     * Required for serialization. Needs to change for new released versions.
+     */
+    private static final long serialVersionUID = 1L;
+
+    public StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest() {
+        super("Create a private Tor network, set up a hidden service, " +
+                "and access it", new NetworkDescriptionImpl(1, 3, 2));
+    }
+
+    public void doTest() {
+        Network nw = exec.getCurrentNetwork();
+        Set<TorInstance> proxies = nw.getProxiesOnly();
+        try {
+            exec.executeTaskForTorInstances(proxies, AddHiddenServiceTask.class, null, null);
+            System.out.println("Accessing the hidden service succeeded.");
+        } catch (TaskExecutionNotSuccessfulException e) {
+            System.out.println("Accessing the hidden service failed.");
+            // Log this properly, especially where we failed
+        }
+    }
 }
diff --git a/src/org/torproject/puppetor/rmi/tests/TestRegistration.java b/src/org/torproject/puppetor/rmi/tests/TestRegistration.java
index 0260fdd..85e100d 100644
--- a/src/org/torproject/puppetor/rmi/tests/TestRegistration.java
+++ b/src/org/torproject/puppetor/rmi/tests/TestRegistration.java
@@ -9,25 +9,25 @@ import org.torproject.puppetor.rmi.TestExecutor;
 
 public class TestRegistration {
 
-	private static TestRegistration instance = new TestRegistration();
-	
-	public void registerTests(TestExecutor testExecutor) {
-		testExecutor.registerTest(new AccessGoogleOverPublicTorNetwork());
-		testExecutor.registerTest(new
-				StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest());
-	}
-	
-	/**
-	 * Singleton
-	 */
-	private TestRegistration() {}
-	
-	/**
-	 * @return
-	 * 		the <code>TestRegistration</code> instance.
-	 */
-	public static TestRegistration getInstance() {
-		return instance;
-	}
+    private static TestRegistration instance = new TestRegistration();
+
+    public void registerTests(TestExecutor testExecutor) {
+        testExecutor.registerTest(new AccessGoogleOverPublicTorNetwork());
+        testExecutor.registerTest(new
+                StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest());
+    }
+
+    /**
+     * Singleton
+     */
+    private TestRegistration() {}
+
+    /**
+     * @return
+     *         the <code>TestRegistration</code> instance.
+     */
+    public static TestRegistration getInstance() {
+        return instance;
+    }
 
 }
-- 
1.5.6.5