[or-cvs] [puppetor/master] Convert line endings and file access mask.

Sebastian Hahn sebastian at torproject.org
Tue Feb 3 13:29:29 UTC 2009


644 sounds saner than 755, and \n is nicer than \r\n.
---
 LICENSE                                            |  222 +-
 README                                             |   16 +-
 doc/howto.tex                                      | 1144 +++++-----
 logging.properties                                 |    8 +-
 src/org/torproject/puppetor/ClientApplication.java |  282 ++--
 src/org/torproject/puppetor/DirectoryNode.java     |  164 +-
 src/org/torproject/puppetor/EventListener.java     |  112 +-
 src/org/torproject/puppetor/EventManager.java      |  574 +++---
 src/org/torproject/puppetor/EventType.java         |  104 +-
 src/org/torproject/puppetor/Network.java           | 1648 +++++++-------
 src/org/torproject/puppetor/NetworkFactory.java    |  260 ++--
 src/org/torproject/puppetor/NodeState.java         |  140 +-
 src/org/torproject/puppetor/ProxyNode.java         |  670 +++---
 src/org/torproject/puppetor/PuppeTorException.java |  182 +-
 src/org/torproject/puppetor/RouterNode.java        |  180 +-
 src/org/torproject/puppetor/ServerApplication.java |  194 +-
 .../examples/AccessingPublicWebServerOverTor.java  |  306 ++--
 ...ccessingHiddenServiceOverPrivateTorNetwork.java |  326 ++--
 ...AccessingHiddenServiceOverPublicTorNetwork.java |  360 ++--
 ...AdvertisingHiddenServiceToPublicTorNetwork.java |  286 ++--
 .../puppetor/impl/ClientApplicationImpl.java       |  988 +++++-----
 .../puppetor/impl/DirectoryNodeImpl.java           |  888 ++++----
 .../torproject/puppetor/impl/EventManagerImpl.java | 1562 +++++++-------
 src/org/torproject/puppetor/impl/NetworkImpl.java  | 2330 ++++++++++----------
 .../torproject/puppetor/impl/ProxyNodeImpl.java    | 1528 +++++++-------
 .../torproject/puppetor/impl/RouterNodeImpl.java   |  836 ++++----
 .../puppetor/impl/ServerApplicationImpl.java       |  770 ++++----
 27 files changed, 8040 insertions(+), 8040 deletions(-)
 mode change 100755 => 100644 doc/howto.tex
 mode change 100755 => 100644 doc/logging
 mode change 100755 => 100644 logging.properties
 mode change 100755 => 100644 src/org/torproject/puppetor/ClientApplication.java
 mode change 100755 => 100644 src/org/torproject/puppetor/DirectoryNode.java
 mode change 100755 => 100644 src/org/torproject/puppetor/Event.java
 mode change 100755 => 100644 src/org/torproject/puppetor/EventListener.java
 mode change 100755 => 100644 src/org/torproject/puppetor/EventManager.java
 mode change 100755 => 100644 src/org/torproject/puppetor/EventType.java
 mode change 100755 => 100644 src/org/torproject/puppetor/Network.java
 mode change 100755 => 100644 src/org/torproject/puppetor/NetworkFactory.java
 mode change 100755 => 100644 src/org/torproject/puppetor/NodeState.java
 mode change 100755 => 100644 src/org/torproject/puppetor/ProxyNode.java
 mode change 100755 => 100644 src/org/torproject/puppetor/PuppeTorException.java
 mode change 100755 => 100644 src/org/torproject/puppetor/RouterNode.java
 mode change 100755 => 100644 src/org/torproject/puppetor/ServerApplication.java
 mode change 100755 => 100644 src/org/torproject/puppetor/examples/AccessingPublicWebServerOverTor.java
 mode change 100755 => 100644 src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
 mode change 100755 => 100644 src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
 mode change 100755 => 100644 src/org/torproject/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
 mode change 100755 => 100644 src/org/torproject/puppetor/impl/ClientApplicationImpl.java
 mode change 100755 => 100644 src/org/torproject/puppetor/impl/DirectoryNodeImpl.java
 mode change 100755 => 100644 src/org/torproject/puppetor/impl/EventManagerImpl.java
 mode change 100755 => 100644 src/org/torproject/puppetor/impl/NetworkImpl.java
 mode change 100755 => 100644 src/org/torproject/puppetor/impl/ProxyNodeImpl.java
 mode change 100755 => 100644 src/org/torproject/puppetor/impl/RouterNodeImpl.java
 mode change 100755 => 100644 src/org/torproject/puppetor/impl/ServerApplicationImpl.java

diff --git a/LICENSE b/LICENSE
index 00bebc8..77abd8d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,111 +1,111 @@
-===============================================================================
-PuppeTor - A Java-based Tor Simulator - is distributed under this license:
-
-Copyright (c) 2007, Karsten Loesing
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-    * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-
-    * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
-
-    * Neither the names of the copyright owners nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-===============================================================================
-The Tor controller demonstration code is distributed under this license:
-
-Copyright (c) 2005, Nick Mathewson, Roger Dingledine
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-    * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-
-    * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
-
-    * Neither the names of the copyright owners nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-===============================================================================
-Groovy is distributed under this license:
-
-Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
-
-Redistribution and use of this software and associated documentation
-("Software"), with or without modification, are permitted provided
-that the following conditions are met:
-
-1. Redistributions of source code must retain copyright
-   statements and notices.  Redistributions must also contain a
-   copy of this document.
-
-2. Redistributions in binary form must reproduce the
-   above copyright notice, this list of conditions and the
-   following disclaimer in the documentation and/or other
-   materials provided with the distribution.
-
-3. The name "groovy" must not be used to endorse or promote
-   products derived from this Software without prior written
-   permission of The Codehaus.  For written permission,
-   please contact info at codehaus.org.
-
-4. Products derived from this Software may not be called "groovy"
-   nor may "groovy" appear in their names without prior written
-   permission of The Codehaus. "groovy" is a registered
-   trademark of The Codehaus.
-
-5. Due credit should be given to The Codehaus -
-   http://groovy.codehaus.org/
-
-THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
-``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
-NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
-THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
-INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
-OF THE POSSIBILITY OF SUCH DAMAGE.
+===============================================================================
+PuppeTor - A Java-based Tor Simulator - is distributed under this license:
+
+Copyright (c) 2007, Karsten Loesing
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+    * Neither the names of the copyright owners nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+===============================================================================
+The Tor controller demonstration code is distributed under this license:
+
+Copyright (c) 2005, Nick Mathewson, Roger Dingledine
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+    * Neither the names of the copyright owners nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+===============================================================================
+Groovy is distributed under this license:
+
+Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
+
+Redistribution and use of this software and associated documentation
+("Software"), with or without modification, are permitted provided
+that the following conditions are met:
+
+1. Redistributions of source code must retain copyright
+   statements and notices.  Redistributions must also contain a
+   copy of this document.
+
+2. Redistributions in binary form must reproduce the
+   above copyright notice, this list of conditions and the
+   following disclaimer in the documentation and/or other
+   materials provided with the distribution.
+
+3. The name "groovy" must not be used to endorse or promote
+   products derived from this Software without prior written
+   permission of The Codehaus.  For written permission,
+   please contact info at codehaus.org.
+
+4. Products derived from this Software may not be called "groovy"
+   nor may "groovy" appear in their names without prior written
+   permission of The Codehaus. "groovy" is a registered
+   trademark of The Codehaus.
+
+5. Due credit should be given to The Codehaus -
+   http://groovy.codehaus.org/
+
+THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README b/README
index d55a3d5..bfa5a04 100644
--- a/README
+++ b/README
@@ -1,8 +1,8 @@
-This is a Java framework that facilitates the configuration of a set of local
-Tor processes and the execution of automatic tests based on these processes. The
-intention is to make it easier for developers to analyze Tor's behavior in
-arbitrary network settings and to measure the effects of changes to the Tor
-source code. Due to the automation of configuration and execution, these
-experiments can be done in an unsupervised batch fashion.
-
-For more information, read the how-to document in doc/howto.pdf .
+This is a Java framework that facilitates the configuration of a set of local
+Tor processes and the execution of automatic tests based on these processes. The
+intention is to make it easier for developers to analyze Tor's behavior in
+arbitrary network settings and to measure the effects of changes to the Tor
+source code. Due to the automation of configuration and execution, these
+experiments can be done in an unsupervised batch fashion.
+
+For more information, read the how-to document in doc/howto.pdf .
diff --git a/doc/howto.tex b/doc/howto.tex
old mode 100755
new mode 100644
index 0365bb1..1c30d7e
--- a/doc/howto.tex
+++ b/doc/howto.tex
@@ -1,71 +1,71 @@
-\documentclass{article}%
-\usepackage{graphicx}%
-\usepackage{url}%
-\usepackage{msc}%
-\begin{document}%
-%
-\author{karsten.loesing at gmx.net}%
-\title{PuppeTor\\A Java-based Tor Simulator\\User's Guide}%
-\maketitle%
-%
-
-\section{Introduction}
-
-PuppeTor\footnote{The name signifies the metaphor of a puppeteer playing with
-puppets like this framework plays with Tor processes.} is a Java framework that
-facilitates the
-configuration of a set of local Tor processes and the execution of automatic
-tests based on these processes. The intention is to make it easier for
-developers to
-analyze Tor's behavior in arbitrary network settings and to measure the
-effects of changes to the Tor source code. Due to the automation of
-configuration and execution, these experiments can be done in an unsupervised 
-batch fashion.
-
-An application that makes use of this framework starts with setting up a set of
-pre-defined Tor processes: proxy, router, and directory. Though these
-configurations should work in most settings, they can be altered by adding or
-removing configuration entries. After deciding whether the processes shall
-either create a private Tor network, or connect to the public Tor network,
-processes are started. Now the application can start clients and servers and
-perform requests using the local processes. In doing so, it can measure time
-intervals between events originating from Tor processes and it can synchronize 
-with such events.
-
-There are two typical situations in which this framework can be useful:
-\begin{enumerate}
-\item Developers need to oversee the effects of their changes to the source code.
-Therefore, it is useful to have a clean setting of Tor nodes in a private
-network, so that all nodes are under full control of the developer.
-\item Developers might want to measure the real-world performance of certain Tor
-operations. Hence, they can set up nodes at the edge of the public Tor network
-and conduct performance measurements, maybe in a batch of some hundreds or
-thousands of runs.
-\end{enumerate}
-
-Of course, the applications described here are possible without this framework.
-But this framework has certain advantages over writing own configuration files
-and test scripts:
-\begin{enumerate}
-\item It provides developers with pre-defined configurations of nodes.
-Especially the configuration of nodes in a private network with own directory
-nodes is not a trivial task.
-\item It takes away the need to implement synchronization of a test application
-with events created by Tor processes. This, too, is a non-trivial task and can,
-if not done properly, lead to deadlocks or inconsistent states.
-\item It relieves the developer from the task to collect and merge log files.
-Typically, every Tor process produces its own log file, so that all files might
-need to be merged in chronological order to identify causal dependencies.
-\end{enumerate}
-
-Originally this framework has been designed to perform experiments on Tor hidden
-services. But it should be feasible for experiments on onion routing and other
-Tor services, too. If you have found an alternative usage for it, and maybe
-have changed or extended it to support your own development, please feel free to
-contribute your additions. And please report bugs.
-
-\section{Installation}
-
+\documentclass{article}%
+\usepackage{graphicx}%
+\usepackage{url}%
+\usepackage{msc}%
+\begin{document}%
+%
+\author{karsten.loesing at gmx.net}%
+\title{PuppeTor\\A Java-based Tor Simulator\\User's Guide}%
+\maketitle%
+%
+
+\section{Introduction}
+
+PuppeTor\footnote{The name signifies the metaphor of a puppeteer playing with
+puppets like this framework plays with Tor processes.} is a Java framework that
+facilitates the
+configuration of a set of local Tor processes and the execution of automatic
+tests based on these processes. The intention is to make it easier for
+developers to
+analyze Tor's behavior in arbitrary network settings and to measure the
+effects of changes to the Tor source code. Due to the automation of
+configuration and execution, these experiments can be done in an unsupervised 
+batch fashion.
+
+An application that makes use of this framework starts with setting up a set of
+pre-defined Tor processes: proxy, router, and directory. Though these
+configurations should work in most settings, they can be altered by adding or
+removing configuration entries. After deciding whether the processes shall
+either create a private Tor network, or connect to the public Tor network,
+processes are started. Now the application can start clients and servers and
+perform requests using the local processes. In doing so, it can measure time
+intervals between events originating from Tor processes and it can synchronize 
+with such events.
+
+There are two typical situations in which this framework can be useful:
+\begin{enumerate}
+\item Developers need to oversee the effects of their changes to the source code.
+Therefore, it is useful to have a clean setting of Tor nodes in a private
+network, so that all nodes are under full control of the developer.
+\item Developers might want to measure the real-world performance of certain Tor
+operations. Hence, they can set up nodes at the edge of the public Tor network
+and conduct performance measurements, maybe in a batch of some hundreds or
+thousands of runs.
+\end{enumerate}
+
+Of course, the applications described here are possible without this framework.
+But this framework has certain advantages over writing own configuration files
+and test scripts:
+\begin{enumerate}
+\item It provides developers with pre-defined configurations of nodes.
+Especially the configuration of nodes in a private network with own directory
+nodes is not a trivial task.
+\item It takes away the need to implement synchronization of a test application
+with events created by Tor processes. This, too, is a non-trivial task and can,
+if not done properly, lead to deadlocks or inconsistent states.
+\item It relieves the developer from the task to collect and merge log files.
+Typically, every Tor process produces its own log file, so that all files might
+need to be merged in chronological order to identify causal dependencies.
+\end{enumerate}
+
+Originally this framework has been designed to perform experiments on Tor hidden
+services. But it should be feasible for experiments on onion routing and other
+Tor services, too. If you have found an alternative usage for it, and maybe
+have changed or extended it to support your own development, please feel free to
+contribute your additions. And please report bugs.
+
+\section{Installation}
+
 PuppeTor requires a Tor executable which can be downloaded from the Subversion
 repository on the Tor homepage. Though PuppeTor can also be used with the stable
 Tor versions, its primary intention is to be used for development, so that it
@@ -91,7 +91,7 @@ demonstration code and
 
 Finally, you need to have a Java Runtime Environment version 5 or
 higher installed on your machine.
-
+
 \section{Examples}
 
 This user's guide takes the approach of stepwise introducing features by showing
@@ -99,464 +99,464 @@ typical usage examples. Though these examples might not cover all features, the
 remaining features should be self-explanatory afterwards, maybe with a little
 help of the Javadocs.
 
-\subsection{Accessing a public web server over Tor}
-
-The easiest way to use Tor is to anonymously access a public web server. This is
+\subsection{Accessing a public web server over Tor}
+
+The easiest way to use Tor is to anonymously access a public web server. This is
 what we do in this first example.
 
-You can start the first example with the following command from the
+You can start the first example with the following command from the
 base directory of this framework:
 
-\begin{verbatim}
-java -cp bin:lib/torctl.jar
+\begin{verbatim}
+java -cp bin:lib/torctl.jar
     de.uniba.wiai.lspi.puppetor.examples.
-    AccessingPublicWebServerOverTor
-\end{verbatim}
-
+    AccessingPublicWebServerOverTor
+\end{verbatim}
+
 The network configuration consists only of one proxy node with an open
-SOCKS port and control port (every node needs to open its control
-port, so that we can communicate with it.)
-
-In the following, we will walk through the code that is necessary to configure
-this ``network'' and perform the request to a public web server.
-
-We start with obtaining a \texttt{Network} instance which is the central place
-for each test run:
-
-\begin{verbatim}
-Network network = NetworkFactory.createNetwork("example1");
-\end{verbatim}
-
+SOCKS port and control port (every node needs to open its control
+port, so that we can communicate with it.)
+
+In the following, we will walk through the code that is necessary to configure
+this ``network'' and perform the request to a public web server.
+
+We start with obtaining a \texttt{Network} instance which is the central place
+for each test run:
+
+\begin{verbatim}
+Network network = NetworkFactory.createNetwork("example1");
+\end{verbatim}
+
 Now we are ready to create the Tor proxy that we need to perform our request.
 The node will be assigned two port numbers for its SOCKS and control port
 automatically, starting at 7000 (can be changed for the network).
-The name is given for logging purposes and as event source name:
-
-\begin{verbatim}
-network.createProxy("proxy");
-\end{verbatim}
-
-As we are fine with the pre-defined configuration (at least for the moment), we
-can write the configurations of all nodes (which is only our proxy in this case)
-to \texttt{torrc} files in the nodes' working directories:
-
-\begin{verbatim}
-network.writeConfigurations();
-\end{verbatim}
-
+The name is given for logging purposes and as event source name:
+
+\begin{verbatim}
+network.createProxy("proxy");
+\end{verbatim}
+
+As we are fine with the pre-defined configuration (at least for the moment), we
+can write the configurations of all nodes (which is only our proxy in this case)
+to \texttt{torrc} files in the nodes' working directories:
+
+\begin{verbatim}
+network.writeConfigurations();
+\end{verbatim}
+
 Next we start the nodes of our network. (The reason for separating this step
 from the
-previous one is the possible investigation of the working directory content, if
-required.) Starting nodes can fail for some reason and block our application
-forever. Therefore, we can provide a timeout in milliseconds after which we
-consider the operation as failed. Starting nodes is considered to be complete as
+previous one is the possible investigation of the working directory content, if
+required.) Starting nodes can fail for some reason and block our application
+forever. Therefore, we can provide a timeout in milliseconds after which we
+consider the operation as failed. Starting nodes is considered to be complete as
 soon as all Tor nodes have opened their control ports and we have connected to
-them:
-
-\begin{verbatim}
-network.startNodes(5000);
-\end{verbatim}
-
-From time to time, Tor cannot create a circuit without some kind of launching
-assistance. Especially in a private-network setting, nodes need to reboot in
-order to refresh their directory information and be able to build circuits.
-Hence, we send the nodes a ``HUP'' signal in regular intervals until it tells us
-that it has opened a circuit. In the following operation we can configure how
+them:
+
+\begin{verbatim}
+network.startNodes(5000);
+\end{verbatim}
+
+From time to time, Tor cannot create a circuit without some kind of launching
+assistance. Especially in a private-network setting, nodes need to reboot in
+order to refresh their directory information and be able to build circuits.
+Hence, we send the nodes a ``HUP'' signal in regular intervals until it tells us
+that it has opened a circuit. In the following operation we can configure how
 often we want to send this signal (here: 5 times) and how long we want to wait
-in between (here: 10 seconds):
-
-\begin{verbatim}
-network.hupUntilUp(5, 10000);
-\end{verbatim}
-
-Now our Tor network is running.
-
-Next is the client that will perform requests using our proxy. PuppeTor contains
-a \texttt{ClientApplication} class which can start a thread to perform simple
+in between (here: 10 seconds):
+
+\begin{verbatim}
+network.hupUntilUp(5, 10000);
+\end{verbatim}
+
+Now our Tor network is running.
+
+Next is the client that will perform requests using our proxy. PuppeTor contains
+a \texttt{ClientApplication} class which can start a thread to perform simple
 HTTP GET requests to a given address and port. We provide it with the name that
 is used for logging purposes and as event source, the address and port to send
-the request to, and the SOCKS port of our proxy:
-
-\begin{verbatim}
-ClientApplication client = network.createClient("client",
-    "www.google.com", 80, proxy.getSocksPort());
-\end{verbatim}
-
-Before starting the request we want to register for events coming from this
-client. This is necessary as requests are performed in a separate thread in the
-background, which allows more complex applications to perform multiple requests
-in parallel. Therefore, we implement the interface \texttt{EventListener}
-and its method \texttt{handleEvent(Event)}. In this method we are interested in
-the two events \texttt{ClientEventType.CLIENT\_SENDING\_REQUEST} and
+the request to, and the SOCKS port of our proxy:
+
+\begin{verbatim}
+ClientApplication client = network.createClient("client",
+    "www.google.com", 80, proxy.getSocksPort());
+\end{verbatim}
+
+Before starting the request we want to register for events coming from this
+client. This is necessary as requests are performed in a separate thread in the
+background, which allows more complex applications to perform multiple requests
+in parallel. Therefore, we implement the interface \texttt{EventListener}
+and its method \texttt{handleEvent(Event)}. In this method we are interested in
+the two events \texttt{ClientEventType.CLIENT\_SENDING\_REQUEST} and
 \texttt{ClientEventType.CLIENT\_REPLY\_RECEIVED}.
-What we do with these events is
-application-specific. In our case we record the time of the first event and
-subtract it from the time of the second event to obtain the round-trip time of
-our request:
-
-\begin{verbatim}
+What we do with these events is
+application-specific. In our case we record the time of the first event and
+subtract it from the time of the second event to obtain the round-trip time of
+our request:
+
+\begin{verbatim}
 EventListener clientEventListener =
-    new EventListener() { // ... };
-\end{verbatim}
-
-Next we obtain a reference to the \texttt{EventManager} which handles all
-asynchronous events coming from the Tor processes:
-
-\begin{verbatim}
-EventManager manager = network.getEventManager();
-\end{verbatim}
-
+    new EventListener() { // ... };
+\end{verbatim}
+
+Next we obtain a reference to the \texttt{EventManager} which handles all
+asynchronous events coming from the Tor processes:
+
+\begin{verbatim}
+EventManager manager = network.getEventManager();
+\end{verbatim}
+
 We add our event handler to the event manager with the client name
-as event source:
-
-\begin{verbatim}
+as event source:
+
+\begin{verbatim}
 manager.addEventListener(client.getClientApplicationName(),
-    clientEventListener);
-\end{verbatim}
-
-Finally, we can perform the requests to the public server. Just in case that a
-request fails or times out, we state that we are willing to make three attempts
-with a timeout of 20 seconds each, and that further requests should be aborted
-as soon as one request succeeds (last parameter \texttt{true}).
-
-\begin{verbatim}
-client.performRequest(3, 20000, true);
-\end{verbatim}
-
-Due to the asynchronous performing of requests, we need to explicitly wait for
-the requests to be completed. We can do this by invoking a method on the event
-manager that blocks the invoking thread until a certain event is received. In
-this case we want to be blocked until the event
+    clientEventListener);
+\end{verbatim}
+
+Finally, we can perform the requests to the public server. Just in case that a
+request fails or times out, we state that we are willing to make three attempts
+with a timeout of 20 seconds each, and that further requests should be aborted
+as soon as one request succeeds (last parameter \texttt{true}).
+
+\begin{verbatim}
+client.performRequest(3, 20000, true);
+\end{verbatim}
+
+Due to the asynchronous performing of requests, we need to explicitly wait for
+the requests to be completed. We can do this by invoking a method on the event
+manager that blocks the invoking thread until a certain event is received. In
+this case we want to be blocked until the event
 \texttt{ClientEventType.CLIENT\_REQUESTS\_PERFORMED} is received from our
-client:
-
-\begin{verbatim}
-manager.waitForAnyOccurence(client,
-    ClientEventType.CLIENT_REQUESTS_PERFORMED);
-\end{verbatim}
-
-Next we shut down the network containing our proxy:
-
-\begin{verbatim}
-network.shutdownNodes();
-\end{verbatim}
+client:
+
+\begin{verbatim}
+manager.waitForAnyOccurence(client,
+    ClientEventType.CLIENT_REQUESTS_PERFORMED);
+\end{verbatim}
+
+Next we shut down the network containing our proxy:
+
+\begin{verbatim}
+network.shutdownNodes();
+\end{verbatim}
 
 At last we need to explicitly shut down the JVM. The reason is that some objects
 are accessible via RMI, and even though we are not using RMI in this examples,
 Java keeps a non-daemon thread running that prevents the JVM from exiting on
-its own.
+its own.
 
-\begin{verbatim}
+\begin{verbatim}
 System.exit(0);
-\end{verbatim}
+\end{verbatim}
 
 A typical output of example 1 might look like the following:
-
-\begin{verbatim}
+
+\begin{verbatim}
 Successfully started the node!
 Successfully built circuits!
 Request took 1228 milliseconds
-Goodbye.
-\end{verbatim}
-
-\subsection{Advertising hidden service to public Tor network}
-
+Goodbye.
+\end{verbatim}
+
+\subsection{Advertising hidden service to public Tor network}
+
 The second example (\texttt{AdvertisingHiddenServiceToPublicTorNetwork})
 advertises a hidden service to the public Tor network, but does not perform any
 requests to it. In fact this can be a useful test to measure publication times
-of rendezvous service descriptors.
-
-The network configuration again consists only of one proxy node.
-
-We start again with creating a network and a proxy node.
-
-\begin{verbatim}
-Network network = NetworkFactory.createNetwork("example2");
-ProxyNode proxy = network.createProxy("proxy");
-\end{verbatim}
-
-Next we extend the configuration of the proxy by a hidden service entry. The
+of rendezvous service descriptors.
+
+The network configuration again consists only of one proxy node.
+
+We start again with creating a network and a proxy node.
+
+\begin{verbatim}
+Network network = NetworkFactory.createNetwork("example2");
+ProxyNode proxy = network.createProxy("proxy");
+\end{verbatim}
+
+Next we extend the configuration of the proxy by a hidden service entry. The
 hidden service gets the name \texttt{hidServ} which is used for its working
 directory and for logging purposes. It expects that the real service will run
 on the next free port (here: \texttt{7002}) and announces to Tor
-clients that it runs on the virtual port 80 (default value):
-
-\begin{verbatim}
-proxy.addHiddenService("hidServ");
-\end{verbatim}
-
+clients that it runs on the virtual port 80 (default value):
+
+\begin{verbatim}
+proxy.addHiddenService("hidServ");
+\end{verbatim}
+
 The next step stays the same as in the previous example. We write the
-configuration of our proxy to its \texttt{torrc} file.
-
-\begin{verbatim}
-network.writeConfigurations();
-\end{verbatim}
-
-Before starting the proxy, we create and register an event listener that informs
-us about two events: when Tor opens a circuit
+configuration of our proxy to its \texttt{torrc} file.
+
+\begin{verbatim}
+network.writeConfigurations();
+\end{verbatim}
+
+Before starting the proxy, we create and register an event listener that informs
+us about two events: when Tor opens a circuit
 (\texttt{NodeEventType.NODE\_CIRCUIT\_OPENED}) and when it has published a
 rendezvous service descriptor
-(\texttt{HiddenServiceEventType.BOB\_DESC\_PUBLISHED\_RECEIVED}).
-
-\begin{verbatim}
+(\texttt{HiddenServiceEventType.BOB\_DESC\_PUBLISHED\_RECEIVED}).
+
+\begin{verbatim}
 EventListener proxyEventListener =
-    new EventListener() { // ... };
-\end{verbatim}
-
-Again we register this event listener at the event manager, but this time with
-the proxy as event subject.
-
-\begin{verbatim}
-EventManager manager = network.getEventManager();
+    new EventListener() { // ... };
+\end{verbatim}
+
+Again we register this event listener at the event manager, but this time with
+the proxy as event subject.
+
+\begin{verbatim}
+EventManager manager = network.getEventManager();
 manager.addEventListener(proxy.getNodeName(),
-    proxyEventListener);
-\end{verbatim}
-
-The next steps are similar to the first example. We need to start the proxy and
-maybe send ``HUP'' signals until it has built a circuit.
-
-\begin{verbatim}
-network.startNodes(5000);
-network.hupUntilUp(5, 10000);
-\end{verbatim}
-
-As there is no event we could wait for, we fall asleep for two minutes to
-observe publication of rendezvous service descriptors.
-
-\begin{verbatim}
-Thread.sleep(2L * 60L * 1000L);
-\end{verbatim}
-
-At the end we shut down the proxy and the JVM.
-
-\begin{verbatim}
+    proxyEventListener);
+\end{verbatim}
+
+The next steps are similar to the first example. We need to start the proxy and
+maybe send ``HUP'' signals until it has built a circuit.
+
+\begin{verbatim}
+network.startNodes(5000);
+network.hupUntilUp(5, 10000);
+\end{verbatim}
+
+As there is no event we could wait for, we fall asleep for two minutes to
+observe publication of rendezvous service descriptors.
+
+\begin{verbatim}
+Thread.sleep(2L * 60L * 1000L);
+\end{verbatim}
+
+At the end we shut down the proxy and the JVM.
+
+\begin{verbatim}
 network.shutdownNodes();
-System.exit(0);
-\end{verbatim}
-
-A typical output of example 2 might look like the following:
-
-\begin{verbatim}
+System.exit(0);
+\end{verbatim}
+
+A typical output of example 2 might look like the following:
+
+\begin{verbatim}
 Successfully started the node!
 Successfully built circuits!
 Waiting for 2 minutes and observing RSD publications...
 RSD published 45128 milliseconds after first circuit was opened
 RSD published 51117 milliseconds after first circuit was opened
 RSD published 51118 milliseconds after first circuit was opened
-Goodbye.
-\end{verbatim}
-
-\subsection{Advertising and accessing hidden service over public Tor network}
-
+Goodbye.
+\end{verbatim}
+
+\subsection{Advertising and accessing hidden service over public Tor network}
+
 This third example
 (\texttt{AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork}) is in fact
-the first for which this framework originally has
-been built. The setting consists of two proxies that are connected to the
-public Tor network, a server that is connected as hidden server to the first
-proxy, and a client that performs requests using the second proxy. This scenario
-can be used to measure real-world round-trip times for requests to hidden
-services.
-
-The network configuration consists of two proxy nodes and looks like this:
-\begin{itemize}
-\item Proxy \texttt{proxy1}.
-\item Proxy \texttt{proxy2}.
-\item Server that is registered as hidden server at \texttt{proxy1}.
-\item Client that performs requests using the SOCKS port of \texttt{proxy2}.
-\end{itemize}
-
-The example starts similar to the previous example with creating and configuring
-two proxy nodes. A hidden service is configured at the first proxy. Then
+the first for which this framework originally has
+been built. The setting consists of two proxies that are connected to the
+public Tor network, a server that is connected as hidden server to the first
+proxy, and a client that performs requests using the second proxy. This scenario
+can be used to measure real-world round-trip times for requests to hidden
+services.
+
+The network configuration consists of two proxy nodes and looks like this:
+\begin{itemize}
+\item Proxy \texttt{proxy1}.
+\item Proxy \texttt{proxy2}.
+\item Server that is registered as hidden server at \texttt{proxy1}.
+\item Client that performs requests using the SOCKS port of \texttt{proxy2}.
+\end{itemize}
+
+The example starts similar to the previous example with creating and configuring
+two proxy nodes. A hidden service is configured at the first proxy. Then
 configurations are written, nodes are started and HUP'ed until all of them have
-built a circuit.
-
-\begin{verbatim}
-Network network = NetworkFactory.createNetwork("example3");
-ProxyNode proxy1 = network.createProxy("proxy1");
-ProxyNode proxy2 = network.createProxy("proxy2");
-HiddenService hidServ1 = proxy1.addHiddenService("hidServ");
-network.writeConfigurations();
-network.startNodes(5000);
-network.hupUntilUp(5, 10000);
-\end{verbatim}
-
-In contrast to the previous example we want to wait until the first proxy has
-successfully uploaded a rendezvous service descriptor to the directory before
-performing a request. From experience we can say that hidden service
-initialization takes most part of the time. We want to wait for this event from
-the first proxy as source for at most 3 minutes:
-
-\begin{verbatim}
-EventManager manager = network.getEventManager();
+built a circuit.
+
+\begin{verbatim}
+Network network = NetworkFactory.createNetwork("example3");
+ProxyNode proxy1 = network.createProxy("proxy1");
+ProxyNode proxy2 = network.createProxy("proxy2");
+HiddenService hidServ1 = proxy1.addHiddenService("hidServ");
+network.writeConfigurations();
+network.startNodes(5000);
+network.hupUntilUp(5, 10000);
+\end{verbatim}
+
+In contrast to the previous example we want to wait until the first proxy has
+successfully uploaded a rendezvous service descriptor to the directory before
+performing a request. From experience we can say that hidden service
+initialization takes most part of the time. We want to wait for this event from
+the first proxy as source for at most 3 minutes:
+
+\begin{verbatim}
+EventManager manager = network.getEventManager();
 manager.waitForAnyOccurence(proxy1,
-    HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED
-    3L * 60L * 1000L);
-\end{verbatim}
-
-As soon as the first descriptor has been published, we can initialize both, 
-server and client. In contrast to the first example the client will perform an
-empty HTTP GET on the hidden server's onion address instead of a public web
-server. The server will answer with an empty HTTP OK:
-
-\begin{verbatim}
+    HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED
+    3L * 60L * 1000L);
+\end{verbatim}
+
+As soon as the first descriptor has been published, we can initialize both, 
+server and client. In contrast to the first example the client will perform an
+empty HTTP GET on the hidden server's onion address instead of a public web
+server. The server will answer with an empty HTTP OK:
+
+\begin{verbatim}
 ServerApplication server = network.createServer("server",
     hidServ1.getServicePort());
 ClientApplication client = network.createClient("client",
     hidServ1.determineOnionAddress(), hidServ1.getVirtualPort(),
     proxy2.getSocksPort());
-\end{verbatim}
-
-For convenience we create a single event listener for both, client and server.
-It can distinguish client and server events by their type. We are interested in
+\end{verbatim}
+
+For convenience we create a single event listener for both, client and server.
+It can distinguish client and server events by their type. We are interested in
 the events \texttt{ClientEventType.CLIENT\_SENDING\_REQUEST} which is fired
-from the
+from the
 client before sending a request,
-\texttt{ServerEventType.SERVER\_RECEIVING\_REQUEST\_SENDING\_REPLY}
-which is fired by the server when a request is received, and
-\texttt{ClientEventType.CLIENT\_REPLY\_RECEIVED}
-which is again fired by the client when it has received a reply. However, we
-need to register this event listener for both event sources, client and server.
-
-\begin{verbatim}
+\texttt{ServerEventType.SERVER\_RECEIVING\_REQUEST\_SENDING\_REPLY}
+which is fired by the server when a request is received, and
+\texttt{ClientEventType.CLIENT\_REPLY\_RECEIVED}
+which is again fired by the client when it has received a reply. However, we
+need to register this event listener for both event sources, client and server.
+
+\begin{verbatim}
 EventListener clientAndServerEventListener =
-    new EventListener() { // ... };
+    new EventListener() { // ... };
 manager.addEventListener(client.getClientApplicationName(),
-    clientAndServerEventListener);
+    clientAndServerEventListener);
 manager.addEventListener(server.getServerApplicationName(),
-    clientAndServerEventListener);
-\end{verbatim}
-
-Now we instruct the server to listen for incoming requests and the client to
-perform such requests.
-
-\begin{verbatim}
-server.startListening();
-client.performRequest(5, 45000, true);
-\end{verbatim}
-
-We wait for the client to complete all requests.
-
-\begin{verbatim}
-manager.waitForAnyOccurence(client,
-    ClientEventType.CLIENT_REQUESTS_PERFORMED);
-\end{verbatim}
-
-Finally, we shut down all nodes and the JVM.
-
-\begin{verbatim}
+    clientAndServerEventListener);
+\end{verbatim}
+
+Now we instruct the server to listen for incoming requests and the client to
+perform such requests.
+
+\begin{verbatim}
+server.startListening();
+client.performRequest(5, 45000, true);
+\end{verbatim}
+
+We wait for the client to complete all requests.
+
+\begin{verbatim}
+manager.waitForAnyOccurence(client,
+    ClientEventType.CLIENT_REQUESTS_PERFORMED);
+\end{verbatim}
+
+Finally, we shut down all nodes and the JVM.
+
+\begin{verbatim}
 network.shutdownNodes();
-System.exit(0);
-\end{verbatim}
-
-A typical output of example 3 might look like the following:
-
-\begin{verbatim}
+System.exit(0);
+\end{verbatim}
+
+A typical output of example 3 might look like the following:
+
+\begin{verbatim}
 Successfully started nodes!
 Successfully built circuits!
 Successfully published an RSD!
 Request took 14941 milliseconds from client to server!
 Request took 15927 milliseconds for the round-trip and
     986 milliseconds from server to client!
-Goodbye.
-\end{verbatim}
-
-\subsection{Advertising and accessing hidden service over private Tor network}
-
+Goodbye.
+\end{verbatim}
+
+\subsection{Advertising and accessing hidden service over private Tor network}
+
 The fourth example
 (\texttt{AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork}) is a
-little more complex. It involves setting up a private
-Tor network including own directory nodes. The overall task stays the same as in
-the previous example: perform a request to a hidden service and see what
-happens. In this case performance does not mean too much, because there is
-no network latency on the local host. But in contrast to a public-network
-setting we could observe what happens on all routers and directory
-nodes during the request.
-
-The network configuration now consists of three router nodes, which are used for
-performing onion routing and misused as proxies, two directory nodes, a client,
-and a server:
-\begin{itemize}
-\item Router \texttt{router1}.
-\item Router \texttt{router2}.
-\item Router \texttt{router3}.
-\item Directory \texttt{dir1}.
-\item Directory \texttt{dir2}.
-\item Server that is registered as hidden server at \texttt{router1}.
-\item Client that performs requests using the SOCKS port of \texttt{router3}.
-\end{itemize}
-
-Creating and configuring these nodes is done similar to all previous examples
-and requires no further explanation:
-
-\begin{verbatim}
-Network network = NetworkFactory.createNetwork("example4");
-RouterNode router1 = network.createRouter("router1");
-network.createRouter("router2");
-RouterNode router3 = network.createRouter("router3");
-network.createDirectory("dir1");
-network.createDirectory("dir2");
-HiddenService hidServ1 = router1.addHiddenService("hidServ");
-\end{verbatim}
-
-However, the next step does require further explanation. The main difficulty
+little more complex. It involves setting up a private
+Tor network including own directory nodes. The overall task stays the same as in
+the previous example: perform a request to a hidden service and see what
+happens. In this case performance does not mean too much, because there is
+no network latency on the local host. But in contrast to a public-network
+setting we could observe what happens on all routers and directory
+nodes during the request.
+
+The network configuration now consists of three router nodes, which are used for
+performing onion routing and misused as proxies, two directory nodes, a client,
+and a server:
+\begin{itemize}
+\item Router \texttt{router1}.
+\item Router \texttt{router2}.
+\item Router \texttt{router3}.
+\item Directory \texttt{dir1}.
+\item Directory \texttt{dir2}.
+\item Server that is registered as hidden server at \texttt{router1}.
+\item Client that performs requests using the SOCKS port of \texttt{router3}.
+\end{itemize}
+
+Creating and configuring these nodes is done similar to all previous examples
+and requires no further explanation:
+
+\begin{verbatim}
+Network network = NetworkFactory.createNetwork("example4");
+RouterNode router1 = network.createRouter("router1");
+network.createRouter("router2");
+RouterNode router3 = network.createRouter("router3");
+network.createDirectory("dir1");
+network.createDirectory("dir2");
+HiddenService hidServ1 = router1.addHiddenService("hidServ");
+\end{verbatim}
+
+However, the next step does require further explanation. The main difficulty
 of setting up a private Tor network
-lies in the fact that all nodes need to be configured so that they accept our
-own directory nodes instead of the pre-configured directory nodes from the
-public Tor network. This configuration requires the fingerprints of all
-directory nodes. These fingerprints are written to disk by the directory nodes
+lies in the fact that all nodes need to be configured so that they accept our
+own directory nodes instead of the pre-configured directory nodes from the
+public Tor network. This configuration requires the fingerprints of all
+directory nodes. These fingerprints are written to disk by the directory nodes
 as soon as they are started. But the directories need to be configured with
-these fingerprints before
-being started, too, in order to prevent them from becoming part of the public
-Tor network. And now we have the chicken or the egg dilemma.
-
-The non-trivial solution is to configure the directory nodes with a fake
-directory configuration and start them using the \texttt{--list-fingerprint}
-option. Hence they write a \texttt{fingerprint} file to disk and shut down
-immediately. This fingerprint can be read, and all nodes can be
-configured to use the directory using this fingerprint.
-
-A second, non-trivial task is to authorize routers and directory nodes.
-Therefore, an authoritative directory needs to know all fingerprints of
+these fingerprints before
+being started, too, in order to prevent them from becoming part of the public
+Tor network. And now we have the chicken or the egg dilemma.
+
+The non-trivial solution is to configure the directory nodes with a fake
+directory configuration and start them using the \texttt{--list-fingerprint}
+option. Hence they write a \texttt{fingerprint} file to disk and shut down
+immediately. This fingerprint can be read, and all nodes can be
+configured to use the directory using this fingerprint.
+
+A second, non-trivial task is to authorize routers and directory nodes.
+Therefore, an authoritative directory needs to know all fingerprints of
 authorized nodes, also before starting the directory nodes. They are stored in
-the \texttt{approved-routers} file in the
-working directory of the directory node.
-
+the \texttt{approved-routers} file in the
+working directory of the directory node.
+
 The complete configuration task is encapsulated in the following method for
-convenience.
-
-\begin{verbatim}
-network.configureAsPrivateNetwork();
-\end{verbatim}
-
-Afterwards, the process is more or less similar to the previous example:
-
-\begin{verbatim}
-network.writeConfigurations();
-network.startNodes(15000);
-network.hupUntilUp(10, 10000);
-EventManager manager = network.getEventManager();
+convenience.
+
+\begin{verbatim}
+network.configureAsPrivateNetwork();
+\end{verbatim}
+
+Afterwards, the process is more or less similar to the previous example:
+
+\begin{verbatim}
+network.writeConfigurations();
+network.startNodes(15000);
+network.hupUntilUp(10, 10000);
+EventManager manager = network.getEventManager();
 manager.waitForAnyOccurence(router1.getNodeName(),
-    HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
+    HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
     3L * 60L * 1000L);
 ServerApplication server = network.createServer("server",
-    hidServ1.getServicePort());
+    hidServ1.getServicePort());
 ClientApplication client = network.createClient("client",
     hidServ1.getServicePort(), hidServ1.getVirtualPort(),
-    router3.getSocksPort());
+    router3.getSocksPort());
 EventListener clientAndServerEventListener =
-    new EventListener() { // ... };
-manager.addEventListener(client, clientAndServerEventListener);
-manager.addEventListener(server, clientAndServerEventListener);
-server.startListening();
-client.performRequest(5, 45000, true);
-manager.waitForAnyOccurence(client,
-    ClientEventType.CLIENT_REQUESTS_PERFORMED);
-network.shutdownNodes();
-\end{verbatim}
-
-A typical output of example 4 might look like the following:
-
-\begin{verbatim}
+    new EventListener() { // ... };
+manager.addEventListener(client, clientAndServerEventListener);
+manager.addEventListener(server, clientAndServerEventListener);
+server.startListening();
+client.performRequest(5, 45000, true);
+manager.waitForAnyOccurence(client,
+    ClientEventType.CLIENT_REQUESTS_PERFORMED);
+network.shutdownNodes();
+\end{verbatim}
+
+A typical output of example 4 might look like the following:
+
+\begin{verbatim}
 Successfully started nodes!
 Successfully built circuits!
 Successfully published an RSD!
@@ -565,8 +565,8 @@ Handling event: Sending request.
 Handling event: Receiving request.
 Handling event: Received response.
 Handling event: Requests performed.
-Goodbye.
-\end{verbatim}
+Goodbye.
+\end{verbatim}
 
 \subsection{Controlling a network using the Groovy shell}
 
@@ -612,7 +612,7 @@ location of an RMI policy file and the codebase:
 \end{itemize}
 
 The typical output of the first part of example 5 should look like the
-following:
+following:
 
 \begin{verbatim}
 Successfully started nodes!
@@ -782,129 +782,129 @@ Further, the directories in the remote network would neither have to rewrite
 their configurations nor be HUP'ed. The advantage of using proxies instead of
 routers is that they don't leave behind orphaned router descriptors as this
 example does.
-
-\section{Events and messages for hidden services}
-
-PuppeTor contains a number of pre-defined event types to observe how Tor behaves
-when setting up and accessing a hidden service. Therefore, PuppeTor parses the
-log statements coming from all started Tor processes and decides whether to fire
-an event or not. All necessary log statements are contained in the current Tor
+
+\section{Events and messages for hidden services}
+
+PuppeTor contains a number of pre-defined event types to observe how Tor behaves
+when setting up and accessing a hidden service. Therefore, PuppeTor parses the
+log statements coming from all started Tor processes and decides whether to fire
+an event or not. All necessary log statements are contained in the current Tor
 trunk on log level \texttt{INFO} or higher. Figure \ref{setup} shows the events
-and exchanged
-messages for setting up a hidden service, figure \ref{download} the descriptor
-download, and figure \ref{connect} the connection establishment.
-
-\begin{figure}
-\caption{Hidden service setup\label{setup}}
-\begin{msc}{Hidden service setup}
-\declinst{a}{Alice's OP}{}
-\declinst{r}{Rendezvous Point}{}
-\declinst{d}{Dir/HSDir}{}
-\declinst{i}{Introduction Point}{}
-\declinst{b}{Bob's OP}{}
-\action{BOB\_BUILT\_INTRO\_CIRC\_SENDING\_ESTABLISH\_INTRO}{b}
-\nextlevel[2]
-\mess{ESTABLISH\_INTRO}{b}{i}[1]
-\nextlevel[2]
-\action{IPO\_RECEIVED\_ESTABLISH\_INTRO\_SENDING\_INTRO\_ESTABLISHED}{i}
-\nextlevel[2]
-\mess{INTRO\_ESTABLISHED}{i}{b}[1]
-\nextlevel[2]
-\action{BOB\_INTRO\_ESTABLISHED\_RECEIVED}{b}
-\nextlevel[2]
-\action{BOB\_SENDING\_PUBLISH\_DESC}{b}
-\nextlevel[2]
-\mess{POST}{b}{d}[1]
-\nextlevel[2]
-\action{DIR\_PUBLISH\_DESC\_RECEIVED}{d}
-\nextlevel[2]
-\mess{OK}{d}{b}[1]
-\nextlevel[2]
-\action{BOB\_DESC\_PUBLISHED\_RECEIVED}{b}
-\nextlevel[2]
-\end{msc}
-\end{figure}
-
-\begin{figure}
-\caption{Hidden service decriptor download\label{download}}
-\begin{msc}{Hidden service decriptor download}
-\declinst{a}{Alice's OP}{}
-\declinst{r}{Rendezvous Point}{}
-\declinst{d}{Dir/HSDir}{}
-\declinst{i}{Introduction Point}{}
-\declinst{b}{Bob's OP}{}
-\action{ALICE\_ONION\_REQUEST\_RECEIVED}{a}
-\nextlevel[2]
-\action{ALICE\_SENDING\_FETCH\_DESC}{a}
-\nextlevel[2]
-\mess{GET}{a}{d}[1]
-\nextlevel[2]
-\action{DIR\_FETCH\_DESC\_RECEIVED}{d}
-\nextlevel[2]
-\mess{OK}{d}{a}[1]
-\nextlevel[2]
-\action{ALICE\_DESC\_FETCHED\_RECEIVED}{a}
-\nextlevel[2]
-\end{msc}
-\end{figure}
-
-\begin{figure}
-\caption{Hidden service connection establishment\label{connect}}
-\begin{msc}{Hidden service connection establishment}
-\declinst{a}{Alice's OP}{}
-\declinst{r}{Rendezvous Point}{}
-\declinst{d}{Dir/HSDir}{}
-\declinst{i}{Introduction Point}{}
-\declinst{b}{Bob's OP}{}
-\action{ALICE\_BUILT\_REND\_CIRC\_SENDING\_ESTABLISH\_RENDEZVOUS}{a}
-\nextlevel[2]
-\mess{ESTABLISH\_RENDEZVOUS}{a}{r}[1]
-\nextlevel[2]
-\action{RPO\_RECEIVED\_ESTABLISH\_RENDEZVOUS\_SENDING\_RENDEZVOUS\_ESTABLISHED}{r}
-\nextlevel[2]
-\mess{RENDEZVOUS\_ESTABLISHED}{r}{a}[1]
-\nextlevel[2]
-\action{ALICE\_RENDEZVOUS\_ESTABLISHED\_RECEIVED}{a}
-\nextlevel[2]
-\action{ALICE\_BUILT\_INTRO\_CIRC}{a}
-\nextlevel[2]
-\action{ALICE\_SENDING\_INTRODUCE1}{a}
-\nextlevel[2]
-\mess{INTRODUCE1}{a}{i}[1]
-\nextlevel[2]
-\action{IPO\_RECEIVED\_INTRODUCE1\_SENDING\_INTRODUCE2\_AND\_INTRODUCE\_ACK}{i}
-\nextlevel[2]
-\mess{INTRODUCE2}{i}{b}[1]
-\mess{INTRODUCE\_ACK}{i}{a}[1]
-\nextlevel[2]
-\action{ALICE\_INTRODUCE\_ACK\_RECEIVED}{a}
-\action{BOB\_INTRODUCE2\_RECEIVED}{b}
-\nextlevel[2]
-\action{BOB\_BUILT\_REND\_CIRC\_SENDING\_RENDEZVOUS1}{b}
-\nextlevel[2]
-\mess{RENDEZVOUS1}{b}{r}[1]
-\nextlevel[2]
-\action{RPO\_RECEIVING\_RENDEZVOUS1\_SENDING\_RENDEZVOUS2}{r}
-\nextlevel[2]
-\mess{RENDEZVOUS2}{r}{a}[1]
-\nextlevel[2]
-\action{ALICE\_RENDEZVOUS2\_RECEIVED\_APP\_CONN\_OPENED}{a}
-\nextlevel[2]
-\action{BOB\_APP\_CONN\_OPENED}{b}
-\nextlevel[2]
-\nextlevel
-\end{msc}
-\end{figure}
-
-\section{Known issues}
-
-There is a small list of problems and open questions that require more
-investigation:
-
-\begin{itemize}
-\item When logging to stdout is set to something lower than \texttt{notice}, the
-application does not succeed.
-\item Fight the TODOs\ldots
-\end{itemize}
-
+and exchanged
+messages for setting up a hidden service, figure \ref{download} the descriptor
+download, and figure \ref{connect} the connection establishment.
+
+\begin{figure}
+\caption{Hidden service setup\label{setup}}
+\begin{msc}{Hidden service setup}
+\declinst{a}{Alice's OP}{}
+\declinst{r}{Rendezvous Point}{}
+\declinst{d}{Dir/HSDir}{}
+\declinst{i}{Introduction Point}{}
+\declinst{b}{Bob's OP}{}
+\action{BOB\_BUILT\_INTRO\_CIRC\_SENDING\_ESTABLISH\_INTRO}{b}
+\nextlevel[2]
+\mess{ESTABLISH\_INTRO}{b}{i}[1]
+\nextlevel[2]
+\action{IPO\_RECEIVED\_ESTABLISH\_INTRO\_SENDING\_INTRO\_ESTABLISHED}{i}
+\nextlevel[2]
+\mess{INTRO\_ESTABLISHED}{i}{b}[1]
+\nextlevel[2]
+\action{BOB\_INTRO\_ESTABLISHED\_RECEIVED}{b}
+\nextlevel[2]
+\action{BOB\_SENDING\_PUBLISH\_DESC}{b}
+\nextlevel[2]
+\mess{POST}{b}{d}[1]
+\nextlevel[2]
+\action{DIR\_PUBLISH\_DESC\_RECEIVED}{d}
+\nextlevel[2]
+\mess{OK}{d}{b}[1]
+\nextlevel[2]
+\action{BOB\_DESC\_PUBLISHED\_RECEIVED}{b}
+\nextlevel[2]
+\end{msc}
+\end{figure}
+
+\begin{figure}
+\caption{Hidden service decriptor download\label{download}}
+\begin{msc}{Hidden service decriptor download}
+\declinst{a}{Alice's OP}{}
+\declinst{r}{Rendezvous Point}{}
+\declinst{d}{Dir/HSDir}{}
+\declinst{i}{Introduction Point}{}
+\declinst{b}{Bob's OP}{}
+\action{ALICE\_ONION\_REQUEST\_RECEIVED}{a}
+\nextlevel[2]
+\action{ALICE\_SENDING\_FETCH\_DESC}{a}
+\nextlevel[2]
+\mess{GET}{a}{d}[1]
+\nextlevel[2]
+\action{DIR\_FETCH\_DESC\_RECEIVED}{d}
+\nextlevel[2]
+\mess{OK}{d}{a}[1]
+\nextlevel[2]
+\action{ALICE\_DESC\_FETCHED\_RECEIVED}{a}
+\nextlevel[2]
+\end{msc}
+\end{figure}
+
+\begin{figure}
+\caption{Hidden service connection establishment\label{connect}}
+\begin{msc}{Hidden service connection establishment}
+\declinst{a}{Alice's OP}{}
+\declinst{r}{Rendezvous Point}{}
+\declinst{d}{Dir/HSDir}{}
+\declinst{i}{Introduction Point}{}
+\declinst{b}{Bob's OP}{}
+\action{ALICE\_BUILT\_REND\_CIRC\_SENDING\_ESTABLISH\_RENDEZVOUS}{a}
+\nextlevel[2]
+\mess{ESTABLISH\_RENDEZVOUS}{a}{r}[1]
+\nextlevel[2]
+\action{RPO\_RECEIVED\_ESTABLISH\_RENDEZVOUS\_SENDING\_RENDEZVOUS\_ESTABLISHED}{r}
+\nextlevel[2]
+\mess{RENDEZVOUS\_ESTABLISHED}{r}{a}[1]
+\nextlevel[2]
+\action{ALICE\_RENDEZVOUS\_ESTABLISHED\_RECEIVED}{a}
+\nextlevel[2]
+\action{ALICE\_BUILT\_INTRO\_CIRC}{a}
+\nextlevel[2]
+\action{ALICE\_SENDING\_INTRODUCE1}{a}
+\nextlevel[2]
+\mess{INTRODUCE1}{a}{i}[1]
+\nextlevel[2]
+\action{IPO\_RECEIVED\_INTRODUCE1\_SENDING\_INTRODUCE2\_AND\_INTRODUCE\_ACK}{i}
+\nextlevel[2]
+\mess{INTRODUCE2}{i}{b}[1]
+\mess{INTRODUCE\_ACK}{i}{a}[1]
+\nextlevel[2]
+\action{ALICE\_INTRODUCE\_ACK\_RECEIVED}{a}
+\action{BOB\_INTRODUCE2\_RECEIVED}{b}
+\nextlevel[2]
+\action{BOB\_BUILT\_REND\_CIRC\_SENDING\_RENDEZVOUS1}{b}
+\nextlevel[2]
+\mess{RENDEZVOUS1}{b}{r}[1]
+\nextlevel[2]
+\action{RPO\_RECEIVING\_RENDEZVOUS1\_SENDING\_RENDEZVOUS2}{r}
+\nextlevel[2]
+\mess{RENDEZVOUS2}{r}{a}[1]
+\nextlevel[2]
+\action{ALICE\_RENDEZVOUS2\_RECEIVED\_APP\_CONN\_OPENED}{a}
+\nextlevel[2]
+\action{BOB\_APP\_CONN\_OPENED}{b}
+\nextlevel[2]
+\nextlevel
+\end{msc}
+\end{figure}
+
+\section{Known issues}
+
+There is a small list of problems and open questions that require more
+investigation:
+
+\begin{itemize}
+\item When logging to stdout is set to something lower than \texttt{notice}, the
+application does not succeed.
+\item Fight the TODOs\ldots
+\end{itemize}
+
 \end{document} 
diff --git a/doc/logging b/doc/logging
old mode 100755
new mode 100644
diff --git a/logging.properties b/logging.properties
old mode 100755
new mode 100644
index ae61850..52d399c
--- a/logging.properties
+++ b/logging.properties
@@ -1,4 +1,4 @@
-handlers= java.util.logging.ConsoleHandler
-java.util.logging.ConsoleHandler.level = FINEST
-java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
-.level= FINE
+handlers= java.util.logging.ConsoleHandler
+java.util.logging.ConsoleHandler.level = FINEST
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+.level= FINE
diff --git a/src/org/torproject/puppetor/ClientApplication.java b/src/org/torproject/puppetor/ClientApplication.java
old mode 100755
new mode 100644
index 9d3c065..2c40298
--- a/src/org/torproject/puppetor/ClientApplication.java
+++ b/src/org/torproject/puppetor/ClientApplication.java
@@ -1,141 +1,141 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor;
-
-import java.rmi.Remote;
-import java.rmi.RemoteException;
-
-/**
- * The <code>ClientApplication</code> can be used to simulate simple
- * <code>HTTP GET</code> requests by a virtual local client. Therefore, an
- * address and a port are given to which the client shall connect. Requests are
- * performed by a background thread, so that multiple requests could be
- * performed at the same time.
- * 
- * @author kloesing
- */
-public interface ClientApplication extends Remote {
-
-	/**
-	 * <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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void startRequests(int retries, long timeoutForEachRetry,
-			boolean stopOnSuccess) throws RemoteException;
-
-	/**
-	 * Stops all requests that are currently running.
-	 * 
-	 * @throws IllegalStateException
-	 *             Thrown if no requests have been started before.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void stopRequest() throws RemoteException;
-
-	/**
-	 * Returns the name of this client.
-	 * 
-	 * @return The name of this client.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract String getClientApplicationName() throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract int getSocksPort() throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract String getTargetName() throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract int getTargetPort() throws RemoteException;
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * The <code>ClientApplication</code> can be used to simulate simple
+ * <code>HTTP GET</code> requests by a virtual local client. Therefore, an
+ * address and a port are given to which the client shall connect. Requests are
+ * performed by a background thread, so that multiple requests could be
+ * performed at the same time.
+ * 
+ * @author kloesing
+ */
+public interface ClientApplication extends Remote {
+
+	/**
+	 * <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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void startRequests(int retries, long timeoutForEachRetry,
+			boolean stopOnSuccess) throws RemoteException;
+
+	/**
+	 * Stops all requests that are currently running.
+	 * 
+	 * @throws IllegalStateException
+	 *             Thrown if no requests have been started before.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void stopRequest() throws RemoteException;
+
+	/**
+	 * Returns the name of this client.
+	 * 
+	 * @return The name of this client.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract String getClientApplicationName() throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract int getSocksPort() throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract String getTargetName() throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract int getTargetPort() throws RemoteException;
+}
diff --git a/src/org/torproject/puppetor/DirectoryNode.java b/src/org/torproject/puppetor/DirectoryNode.java
old mode 100755
new mode 100644
index 628ef96..cfc7327
--- a/src/org/torproject/puppetor/DirectoryNode.java
+++ b/src/org/torproject/puppetor/DirectoryNode.java
@@ -1,82 +1,82 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor;
-
-import java.rmi.RemoteException;
-import java.util.Set;
-
-/**
- * A <code>DirectoryNode</code> represents a Tor process that acts as
- * <code>RouterNode</code> and which is further a directory authoritative
- * server for the (private) Tor network. It inherits most of the configuration
- * and behavior from <code>RouterNode</code> and adds some directory-specific
- * configurations and behavior.
- * 
- * @author kloesing
- */
-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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract String getDirServerString() throws PuppeTorException,
-			RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public void addApprovedRouters(Set<String> approvedRouters)
-			throws RemoteException;
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor;
+
+import java.rmi.RemoteException;
+import java.util.Set;
+
+/**
+ * A <code>DirectoryNode</code> represents a Tor process that acts as
+ * <code>RouterNode</code> and which is further a directory authoritative
+ * server for the (private) Tor network. It inherits most of the configuration
+ * and behavior from <code>RouterNode</code> and adds some directory-specific
+ * configurations and behavior.
+ * 
+ * @author kloesing
+ */
+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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract String getDirServerString() throws PuppeTorException,
+			RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public void addApprovedRouters(Set<String> approvedRouters)
+			throws RemoteException;
+}
diff --git a/src/org/torproject/puppetor/Event.java b/src/org/torproject/puppetor/Event.java
old mode 100755
new mode 100644
diff --git a/src/org/torproject/puppetor/EventListener.java b/src/org/torproject/puppetor/EventListener.java
old mode 100755
new mode 100644
index 1d57c00..31ee3ef
--- a/src/org/torproject/puppetor/EventListener.java
+++ b/src/org/torproject/puppetor/EventListener.java
@@ -1,56 +1,56 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor;
-
-import java.rmi.Remote;
-import java.rmi.RemoteException;
-
-/**
- * This interface must be implemented by any object in a test application that
- * shall be registered as event listener.
- * 
- * @author kloesing
- */
-public interface EventListener extends Remote {
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public void handleEvent(Event event) throws RemoteException;
-
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * This interface must be implemented by any object in a test application that
+ * shall be registered as event listener.
+ * 
+ * @author kloesing
+ */
+public interface EventListener extends Remote {
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public void handleEvent(Event event) throws RemoteException;
+
+}
diff --git a/src/org/torproject/puppetor/EventManager.java b/src/org/torproject/puppetor/EventManager.java
old mode 100755
new mode 100644
index 0e9d7b1..c04bb27
--- a/src/org/torproject/puppetor/EventManager.java
+++ b/src/org/torproject/puppetor/EventManager.java
@@ -1,287 +1,287 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor;
-
-import java.rmi.Remote;
-import java.rmi.RemoteException;
-import java.util.List;
-
-/**
- * The <code>EventManager</code> is the central place for a test run to manage
- * asynchronous events by Tor processes and client or server applications
- * running as threads in the background. A test application can either register
- * event listeners to be notified asynchronously about events when they occur,
- * or synchronize with an event by being blocked until a certain event occurs.
- * 
- * @author kloesing
- */
-public interface EventManager extends Remote {
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract List<Event> addEventListener(String source,
-			EventListener listener) throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void addEventListener(EventListener listener)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract List<Event> getEventHistory(String source)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract boolean hasEventOccured(String source, EventType eventType)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void removeEventListener(EventListener listener)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void waitForAnyOccurence(String source, EventType eventType)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract boolean waitForAnyOccurence(String source,
-			EventType eventType, long maximumTimeToWaitInMillis)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void waitForNextOccurence(String source, EventType eventType)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract boolean waitForNextOccurence(String source,
-			EventType eventType, long maximumTimeToWaitInMillis)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void registerEventTypePattern(String patternString,
-			EventType eventType) throws RemoteException;
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.List;
+
+/**
+ * The <code>EventManager</code> is the central place for a test run to manage
+ * asynchronous events by Tor processes and client or server applications
+ * running as threads in the background. A test application can either register
+ * event listeners to be notified asynchronously about events when they occur,
+ * or synchronize with an event by being blocked until a certain event occurs.
+ * 
+ * @author kloesing
+ */
+public interface EventManager extends Remote {
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract List<Event> addEventListener(String source,
+			EventListener listener) throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void addEventListener(EventListener listener)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract List<Event> getEventHistory(String source)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract boolean hasEventOccured(String source, EventType eventType)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void removeEventListener(EventListener listener)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void waitForAnyOccurence(String source, EventType eventType)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract boolean waitForAnyOccurence(String source,
+			EventType eventType, long maximumTimeToWaitInMillis)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void waitForNextOccurence(String source, EventType eventType)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract boolean waitForNextOccurence(String source,
+			EventType eventType, long maximumTimeToWaitInMillis)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void registerEventTypePattern(String patternString,
+			EventType eventType) throws RemoteException;
+}
diff --git a/src/org/torproject/puppetor/EventType.java b/src/org/torproject/puppetor/EventType.java
old mode 100755
new mode 100644
index f60c477..9b37709
--- a/src/org/torproject/puppetor/EventType.java
+++ b/src/org/torproject/puppetor/EventType.java
@@ -1,52 +1,52 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor;
-
-import java.io.Serializable;
-
-/**
- * The super interface of possible event types that are fired on a state change
- * of an asynchronous system component, e.g. a Tor process or a client/server
- * application running as thread in the background.
- * 
- * @author kloesing
- */
-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();
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor;
+
+import java.io.Serializable;
+
+/**
+ * The super interface of possible event types that are fired on a state change
+ * of an asynchronous system component, e.g. a Tor process or a client/server
+ * application running as thread in the background.
+ * 
+ * @author kloesing
+ */
+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();
+}
diff --git a/src/org/torproject/puppetor/Network.java b/src/org/torproject/puppetor/Network.java
old mode 100755
new mode 100644
index 44eb2c7..78d45a9
--- a/src/org/torproject/puppetor/Network.java
+++ b/src/org/torproject/puppetor/Network.java
@@ -1,824 +1,824 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor;
-
-import java.io.File;
-import java.rmi.Remote;
-import java.rmi.RemoteException;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A Network instance constitutes the central object of any test run and is
- * aware of the node configuration. It creates all nodes for this configuration
- * and is able to perform common operations on these nodes. Apart from the
- * factory methods, all other operations could also be performed manually by an
- * application using the appropriate interfaces.
- * 
- * @author kloesing
- */
-public interface Network extends Remote {
-
-	/**
-	 * <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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void configureAsPrivateNetwork() throws PuppeTorException,
-			RemoteException;
-
-	/**
-	 * <p>
-	 * Merges this network with another private Tor network by exchanging
-	 * directory strings and router fingerprints. Afterwards, the nodes in both
-	 * networks will consider the two networks a single, larger private Tor
-	 * network.
-	 * </p>
-	 * 
-	 * <p>
-	 * The configuration is done in two steps:
-	 * <ol>
-	 * <li>Directory strings of all directory nodes in this network are added
-	 * to the configurations of all nodes in the other network and vice versa.</li>
-	 * <li>Router fingerprints of all router and directory nodes in this
-	 * network are added to the <code>approved-routers</code> files of all
-	 * directory nodes in the other network and vice versa.</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 merging!</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>
-	 * Note that this operation is only effective if there are directory nodes
-	 * in either of the two networks. Otherwise, no information will be
-	 * exchanged between the two networks. Applications need to ensure that
-	 * there are in total enough directory nodes (2) and router nodes (3) in
-	 * both networks to allow normal operation.
-	 * </p>
-	 * 
-	 * @param remoteNetwork
-	 *            The remote network to merge this network with.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while reading directory
-	 *             strings or router fingerprints, while writing the new
-	 */
-	public abstract void configureAsInterconnectedPrivateNetwork(
-			Network remoteNetwork) throws RemoteException, PuppeTorException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract ClientApplication createClient(
-			String clientApplicationName, String targetAddress, int targetPort,
-			int socksPort) throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract DirectoryNode createDirectory(String nodeName)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract DirectoryNode createDirectory(String nodeName,
-			String serverIpAddress) throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract DirectoryNode createDirectory(String nodeName,
-			int controlPort, int socksPort, int orPort, int dirPort)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract DirectoryNode createDirectory(String nodeName,
-			int controlPort, int socksPort, int orPort, int dirPort,
-			String serverIpAddress) throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract ProxyNode createProxy(String nodeName)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract ProxyNode createProxy(String nodeName, int controlPort,
-			int socksPort) throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract RouterNode createRouter(String nodeName)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract RouterNode createRouter(String nodeName, int controlPort,
-			int socksPort, int orPort, int dirPort) throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract RouterNode createRouter(String nodeName,
-			String serverIpAddress) throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract RouterNode createRouter(String nodeName, int controlPort,
-			int socksPort, int orPort, int dirPort, String serverIpAddress)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract ServerApplication createServer(String serverApplicationName)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract ServerApplication createServer(
-			String serverApplicationName, int serverPort)
-			throws RemoteException;
-
-	/**
-	 * Returns a reference on the (single) event manager for this network.
-	 * 
-	 * @return Reference on the (single) event manager for this network.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract EventManager getEventManager() throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract Map<String, DirectoryNode> getAllDirectoryNodes()
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract Map<String, RouterNode> getAllRouterNodes()
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract Map<String, ProxyNode> getAllProxyNodes()
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract Map<String, ProxyNode> getAllNodes() throws RemoteException;
-
-	/**
-	 * 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>.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract ProxyNode getNode(String nodeName) throws RemoteException;
-
-	/**
-	 * <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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract boolean hupUntilUp(int tries, long hupInterval)
-			throws PuppeTorException, RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void hupAllNodes() throws PuppeTorException,
-			RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void hupAllDirectories() throws PuppeTorException,
-			RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void shutdownNodes() throws PuppeTorException,
-			RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract boolean startNodes(long maximumTimeToWaitInMillis)
-			throws PuppeTorException, RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void writeConfigurations() throws PuppeTorException,
-			RemoteException;
-
-	/**
-	 * Returns the working directory of this network configuration which is in
-	 * <code>test-env/networkName/</code>.
-	 * 
-	 * @return Working directory of this network.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract File getWorkingDirectory() throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract List<String> getTemplateConfiguration(
-			Class<? extends ProxyNode> nodeClass) throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void addTemplateConfiguration(
-			Class<? extends ProxyNode> nodeClass,
-			String templateConfigurationString) throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void removeTemplateConfiguration(
-			Class<? extends ProxyNode> nodeClass,
-			String templateConfigurationKey) throws RemoteException;
-
-	/**
-	 * Returns the name of this network.
-	 * 
-	 * @return The name of this network.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract String getNetworkName() throws RemoteException;
-
-	/**
-	 * Binds the network at the local <code>rmiregistry</code> to make it
-	 * remotely available and returns whether binding was successful.
-	 * 
-	 * @return <code>true</code> if binding was successful, <code>false</code>
-	 *         otherwise.
-	 * @throws PuppeTorException
-	 *             Thrown if an error occurs while binding to the
-	 *             <code>rmiregistry</code>.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely (though this
-	 *             might never happen as the object is not bound, yet).
-	 */
-	public abstract boolean bindAtRmiregistry() throws RemoteException;
-
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor;
+
+import java.io.File;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A Network instance constitutes the central object of any test run and is
+ * aware of the node configuration. It creates all nodes for this configuration
+ * and is able to perform common operations on these nodes. Apart from the
+ * factory methods, all other operations could also be performed manually by an
+ * application using the appropriate interfaces.
+ * 
+ * @author kloesing
+ */
+public interface Network extends Remote {
+
+	/**
+	 * <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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void configureAsPrivateNetwork() throws PuppeTorException,
+			RemoteException;
+
+	/**
+	 * <p>
+	 * Merges this network with another private Tor network by exchanging
+	 * directory strings and router fingerprints. Afterwards, the nodes in both
+	 * networks will consider the two networks a single, larger private Tor
+	 * network.
+	 * </p>
+	 * 
+	 * <p>
+	 * The configuration is done in two steps:
+	 * <ol>
+	 * <li>Directory strings of all directory nodes in this network are added
+	 * to the configurations of all nodes in the other network and vice versa.</li>
+	 * <li>Router fingerprints of all router and directory nodes in this
+	 * network are added to the <code>approved-routers</code> files of all
+	 * directory nodes in the other network and vice versa.</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 merging!</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>
+	 * Note that this operation is only effective if there are directory nodes
+	 * in either of the two networks. Otherwise, no information will be
+	 * exchanged between the two networks. Applications need to ensure that
+	 * there are in total enough directory nodes (2) and router nodes (3) in
+	 * both networks to allow normal operation.
+	 * </p>
+	 * 
+	 * @param remoteNetwork
+	 *            The remote network to merge this network with.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 * @throws PuppeTorException
+	 *             Thrown if an I/O problem occurs while reading directory
+	 *             strings or router fingerprints, while writing the new
+	 */
+	public abstract void configureAsInterconnectedPrivateNetwork(
+			Network remoteNetwork) throws RemoteException, PuppeTorException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract ClientApplication createClient(
+			String clientApplicationName, String targetAddress, int targetPort,
+			int socksPort) throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract DirectoryNode createDirectory(String nodeName)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract DirectoryNode createDirectory(String nodeName,
+			String serverIpAddress) throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract DirectoryNode createDirectory(String nodeName,
+			int controlPort, int socksPort, int orPort, int dirPort)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract DirectoryNode createDirectory(String nodeName,
+			int controlPort, int socksPort, int orPort, int dirPort,
+			String serverIpAddress) throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract ProxyNode createProxy(String nodeName)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract ProxyNode createProxy(String nodeName, int controlPort,
+			int socksPort) throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract RouterNode createRouter(String nodeName)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract RouterNode createRouter(String nodeName, int controlPort,
+			int socksPort, int orPort, int dirPort) throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract RouterNode createRouter(String nodeName,
+			String serverIpAddress) throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract RouterNode createRouter(String nodeName, int controlPort,
+			int socksPort, int orPort, int dirPort, String serverIpAddress)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract ServerApplication createServer(String serverApplicationName)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract ServerApplication createServer(
+			String serverApplicationName, int serverPort)
+			throws RemoteException;
+
+	/**
+	 * Returns a reference on the (single) event manager for this network.
+	 * 
+	 * @return Reference on the (single) event manager for this network.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract EventManager getEventManager() throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract Map<String, DirectoryNode> getAllDirectoryNodes()
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract Map<String, RouterNode> getAllRouterNodes()
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract Map<String, ProxyNode> getAllProxyNodes()
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract Map<String, ProxyNode> getAllNodes() throws RemoteException;
+
+	/**
+	 * 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>.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract ProxyNode getNode(String nodeName) throws RemoteException;
+
+	/**
+	 * <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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract boolean hupUntilUp(int tries, long hupInterval)
+			throws PuppeTorException, RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void hupAllNodes() throws PuppeTorException,
+			RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void hupAllDirectories() throws PuppeTorException,
+			RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void shutdownNodes() throws PuppeTorException,
+			RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract boolean startNodes(long maximumTimeToWaitInMillis)
+			throws PuppeTorException, RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void writeConfigurations() throws PuppeTorException,
+			RemoteException;
+
+	/**
+	 * Returns the working directory of this network configuration which is in
+	 * <code>test-env/networkName/</code>.
+	 * 
+	 * @return Working directory of this network.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract File getWorkingDirectory() throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract List<String> getTemplateConfiguration(
+			Class<? extends ProxyNode> nodeClass) throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void addTemplateConfiguration(
+			Class<? extends ProxyNode> nodeClass,
+			String templateConfigurationString) throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void removeTemplateConfiguration(
+			Class<? extends ProxyNode> nodeClass,
+			String templateConfigurationKey) throws RemoteException;
+
+	/**
+	 * Returns the name of this network.
+	 * 
+	 * @return The name of this network.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract String getNetworkName() throws RemoteException;
+
+	/**
+	 * Binds the network at the local <code>rmiregistry</code> to make it
+	 * remotely available and returns whether binding was successful.
+	 * 
+	 * @return <code>true</code> if binding was successful, <code>false</code>
+	 *         otherwise.
+	 * @throws PuppeTorException
+	 *             Thrown if an error occurs while binding to the
+	 *             <code>rmiregistry</code>.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely (though this
+	 *             might never happen as the object is not bound, yet).
+	 */
+	public abstract boolean bindAtRmiregistry() throws RemoteException;
+
+}
diff --git a/src/org/torproject/puppetor/NetworkFactory.java b/src/org/torproject/puppetor/NetworkFactory.java
old mode 100755
new mode 100644
index eaad307..2b8244b
--- a/src/org/torproject/puppetor/NetworkFactory.java
+++ b/src/org/torproject/puppetor/NetworkFactory.java
@@ -1,130 +1,130 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor;
-
-import java.net.MalformedURLException;
-import java.rmi.Naming;
-import java.rmi.NotBoundException;
-import java.rmi.RemoteException;
-
-import org.torproject.puppetor.impl.NetworkImpl;
-
-
-/**
- * The <code>NetworkFactory</code> is a concrete factory that can create
- * <code>Network</code> instances.
- * 
- * TODO At the moment, this class uses the concrete class NetworkImpl to
- * implement its only factory method. If we want to make this a real abstract
- * factory, we need to replace the concrete constructor by reading the class
- * name of the class implementing Network from a property file and invoking its
- * constructor using reflection. Currently, this is the only place where we
- * reference a class from the impl package.
- * 
- * @author kloesing
- */
-public abstract class NetworkFactory {
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public static Network createNetwork(String networkName)
-			throws RemoteException {
-		return new NetworkImpl(networkName);
-	}
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public static Network createNetwork(String networkName, int startPort)
-			throws RemoteException {
-		return new NetworkImpl(networkName, startPort);
-	}
-
-	/**
-	 * Connects to a remote network and returns a reference on it.
-	 * 
-	 * @param remoteHost
-	 *            The IP address and port of the remote <code>rmiregistry</code>.
-	 * @param bindingName
-	 *            The name under which the other network is bound at the remote
-	 *            <code>rmiregistry</code>.
-	 * @return Reference on the remote network.
-	 * @throws PuppeTorException
-	 *             Thrown if an error occurs when looking up the remote network
-	 *             at the remote <code>rmiregistry</code>.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public static Network connectToNetwork(String remoteHost, String bindingName)
-			throws PuppeTorException, RemoteException {
-		Network remoteNetwork = null;
-		try {
-			remoteNetwork = (Network) Naming.lookup("rmi://" + remoteHost + "/"
-					+ bindingName);
-		} catch (MalformedURLException e) {
-			PuppeTorException ex = new PuppeTorException(
-					"Cannot connect to remote network!", e);
-			throw ex;
-		} catch (NotBoundException e) {
-			PuppeTorException ex = new PuppeTorException(
-					"Cannot connect to remote network!", e);
-			throw ex;
-		}
-		return remoteNetwork;
-	}
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor;
+
+import java.net.MalformedURLException;
+import java.rmi.Naming;
+import java.rmi.NotBoundException;
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.impl.NetworkImpl;
+
+
+/**
+ * The <code>NetworkFactory</code> is a concrete factory that can create
+ * <code>Network</code> instances.
+ * 
+ * TODO At the moment, this class uses the concrete class NetworkImpl to
+ * implement its only factory method. If we want to make this a real abstract
+ * factory, we need to replace the concrete constructor by reading the class
+ * name of the class implementing Network from a property file and invoking its
+ * constructor using reflection. Currently, this is the only place where we
+ * reference a class from the impl package.
+ * 
+ * @author kloesing
+ */
+public abstract class NetworkFactory {
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public static Network createNetwork(String networkName)
+			throws RemoteException {
+		return new NetworkImpl(networkName);
+	}
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public static Network createNetwork(String networkName, int startPort)
+			throws RemoteException {
+		return new NetworkImpl(networkName, startPort);
+	}
+
+	/**
+	 * Connects to a remote network and returns a reference on it.
+	 * 
+	 * @param remoteHost
+	 *            The IP address and port of the remote <code>rmiregistry</code>.
+	 * @param bindingName
+	 *            The name under which the other network is bound at the remote
+	 *            <code>rmiregistry</code>.
+	 * @return Reference on the remote network.
+	 * @throws PuppeTorException
+	 *             Thrown if an error occurs when looking up the remote network
+	 *             at the remote <code>rmiregistry</code>.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public static Network connectToNetwork(String remoteHost, String bindingName)
+			throws PuppeTorException, RemoteException {
+		Network remoteNetwork = null;
+		try {
+			remoteNetwork = (Network) Naming.lookup("rmi://" + remoteHost + "/"
+					+ bindingName);
+		} catch (MalformedURLException e) {
+			PuppeTorException ex = new PuppeTorException(
+					"Cannot connect to remote network!", e);
+			throw ex;
+		} catch (NotBoundException e) {
+			PuppeTorException ex = new PuppeTorException(
+					"Cannot connect to remote network!", e);
+			throw ex;
+		}
+		return remoteNetwork;
+	}
+}
diff --git a/src/org/torproject/puppetor/NodeState.java b/src/org/torproject/puppetor/NodeState.java
old mode 100755
new mode 100644
index 2805c7b..a8ce7fa
--- a/src/org/torproject/puppetor/NodeState.java
+++ b/src/org/torproject/puppetor/NodeState.java
@@ -1,70 +1,70 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor;
-
-/**
- * The <code>NodeState</code> constitutes the state of a single Tor node. In
- * contrast to <code>EventType</code> the node states depend only on the
- * methods that have been invoked on these objects, and not on asynchronous
- * state changes. Most operations of <code>ProxyNode</code> and its subclasses
- * require a certain <code>NodeState</code> as precondition and may ensure
- * another <code>NodeState</code> as postcondition. There is a prescribed
- * order of states.
- * 
- * @author kloesing
- */
-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 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 had been started and shut down. It cannot be started at a later
-	 * time anymore.
-	 */
-	SHUT_DOWN
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor;
+
+/**
+ * The <code>NodeState</code> constitutes the state of a single Tor node. In
+ * contrast to <code>EventType</code> the node states depend only on the
+ * methods that have been invoked on these objects, and not on asynchronous
+ * state changes. Most operations of <code>ProxyNode</code> and its subclasses
+ * require a certain <code>NodeState</code> as precondition and may ensure
+ * another <code>NodeState</code> as postcondition. There is a prescribed
+ * order of states.
+ * 
+ * @author kloesing
+ */
+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 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 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
old mode 100755
new mode 100644
index a2875e1..a78c297
--- a/src/org/torproject/puppetor/ProxyNode.java
+++ b/src/org/torproject/puppetor/ProxyNode.java
@@ -1,335 +1,335 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor;
-
-import java.rmi.Remote;
-import java.rmi.RemoteException;
-import java.util.List;
-
-/**
- * <p>
- * A <code>ProxyNode</code> represents a Tor process that is configured as
- * onion proxy, i.e. to relay traffic from a local application to the Tor
- * network and vice versa, and does not route traffic on behalf of remote
- * applications. It is the superclass for other node types that extend the
- * configuration of a </code>ProxyNode</code>.
- * </p>
- * 
- * <p>
- * <b>Pay extra attention when using in private network!</b> Using proxy nodes
- * in private networks in the same way as router nodes will fail! Tor has two
- * different strategies for downloading network status documents: Directory
- * caches (router nodes) download these documents after every HUP signal and
- * then accept all contained router entries. But directory clients (proxy nodes)
- * only download network status documents, if the most recent download lies at
- * least 30 minutes in the past, and then accept only those of the contained
- * router entries that are at least 10 minutes old. However, when starting all
- * nodes of a private network at once, directories cannot contain 10 minutes old
- * router descriptors. You have at least the following options to cope with this
- * problem:
- * </p>
- * 
- * <ul>
- * <li>Use router nodes instead of proxy nodes,</li>
- * <li>start proxy nodes with a delay of at least 10 minutes to be sure that
- * the router descriptors stored at directory authorities will be accepted by
- * directory clients, or</li>
- * <li>change the constants <code>ESTIMATED_PROPAGATION_TIME</code> and
- * <code>NETWORKSTATUS_CLIENT_DL_INTERVAL</code> in Tor to values smaller than
- * your overall HUP time for starting the network.</li>
- * </ul>
- * 
- * @author kloesing
- */
-public interface ProxyNode extends Remote {
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract HiddenService addHiddenService(String serviceName,
-			int servicePort, int virtualPort) throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract HiddenService addHiddenService(String serviceName,
-			int servicePort) throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract HiddenService addHiddenService(String serviceName)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void addConfiguration(String configurationString)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void addConfigurations(List<String> configurationStrings)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void replaceConfiguration(String configurationString)
-			throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void removeConfiguration(String configurationKey)
-			throws RemoteException;
-
-	/**
-	 * Returns the name of this node.
-	 * 
-	 * @return The name of this node.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract String getNodeName() throws RemoteException;
-
-	/**
-	 * Returns the state of this node.
-	 * 
-	 * @return The state of this node.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract NodeState getNodeState() throws RemoteException;
-
-	/**
-	 * 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>.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void hup() throws PuppeTorException, RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void shutdown() throws PuppeTorException, RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract boolean startNode(long maximumTimeToWaitInMillis)
-			throws PuppeTorException, RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void writeConfiguration() throws PuppeTorException,
-			RemoteException;
-
-	/**
-	 * Returns the SOCKS port of this node.
-	 * 
-	 * @return The SOCKS port of this node.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract int getSocksPort() throws RemoteException;
-
-	/**
-	 * Returns the control port of this node.
-	 * 
-	 * @return The control port of this node.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract int getControlPort() throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract List<String> getConfiguration() throws RemoteException;
-
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.List;
+
+/**
+ * <p>
+ * A <code>ProxyNode</code> represents a Tor process that is configured as
+ * onion proxy, i.e. to relay traffic from a local application to the Tor
+ * network and vice versa, and does not route traffic on behalf of remote
+ * applications. It is the superclass for other node types that extend the
+ * configuration of a </code>ProxyNode</code>.
+ * </p>
+ * 
+ * <p>
+ * <b>Pay extra attention when using in private network!</b> Using proxy nodes
+ * in private networks in the same way as router nodes will fail! Tor has two
+ * different strategies for downloading network status documents: Directory
+ * caches (router nodes) download these documents after every HUP signal and
+ * then accept all contained router entries. But directory clients (proxy nodes)
+ * only download network status documents, if the most recent download lies at
+ * least 30 minutes in the past, and then accept only those of the contained
+ * router entries that are at least 10 minutes old. However, when starting all
+ * nodes of a private network at once, directories cannot contain 10 minutes old
+ * router descriptors. You have at least the following options to cope with this
+ * problem:
+ * </p>
+ * 
+ * <ul>
+ * <li>Use router nodes instead of proxy nodes,</li>
+ * <li>start proxy nodes with a delay of at least 10 minutes to be sure that
+ * the router descriptors stored at directory authorities will be accepted by
+ * directory clients, or</li>
+ * <li>change the constants <code>ESTIMATED_PROPAGATION_TIME</code> and
+ * <code>NETWORKSTATUS_CLIENT_DL_INTERVAL</code> in Tor to values smaller than
+ * your overall HUP time for starting the network.</li>
+ * </ul>
+ * 
+ * @author kloesing
+ */
+public interface ProxyNode extends Remote {
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract HiddenService addHiddenService(String serviceName,
+			int servicePort, int virtualPort) throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract HiddenService addHiddenService(String serviceName,
+			int servicePort) throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract HiddenService addHiddenService(String serviceName)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void addConfiguration(String configurationString)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void addConfigurations(List<String> configurationStrings)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void replaceConfiguration(String configurationString)
+			throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void removeConfiguration(String configurationKey)
+			throws RemoteException;
+
+	/**
+	 * Returns the name of this node.
+	 * 
+	 * @return The name of this node.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract String getNodeName() throws RemoteException;
+
+	/**
+	 * Returns the state of this node.
+	 * 
+	 * @return The state of this node.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract NodeState getNodeState() throws RemoteException;
+
+	/**
+	 * 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>.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void hup() throws PuppeTorException, RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void shutdown() throws PuppeTorException, RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract boolean startNode(long maximumTimeToWaitInMillis)
+			throws PuppeTorException, RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void writeConfiguration() throws PuppeTorException,
+			RemoteException;
+
+	/**
+	 * Returns the SOCKS port of this node.
+	 * 
+	 * @return The SOCKS port of this node.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract int getSocksPort() throws RemoteException;
+
+	/**
+	 * Returns the control port of this node.
+	 * 
+	 * @return The control port of this node.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract int getControlPort() throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract List<String> getConfiguration() throws RemoteException;
+
+}
diff --git a/src/org/torproject/puppetor/PuppeTorException.java b/src/org/torproject/puppetor/PuppeTorException.java
old mode 100755
new mode 100644
index f12f675..73f08ff
--- a/src/org/torproject/puppetor/PuppeTorException.java
+++ b/src/org/torproject/puppetor/PuppeTorException.java
@@ -1,91 +1,91 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor;
-
-/**
- * The <code>PuppeTorException</code> comprises all kinds of checked
- * exceptions that occur when interacting with the JVM-external Tor processes or
- * with the local file system. Any occurence of this exception denotes either a
- * configuration problem that can only be solved outside of the JVM, or an
- * unexpected problem. In contrast to this, all kinds of programming errors of
- * an application using this API (invoking a method with wrong parameter values,
- * in wrong state, etc.) will instead cause appropriate runtime exceptions from
- * the Java API.
- * 
- * @author kloesing
- */
- at 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> 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(String message, 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(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(Throwable cause) {
-		super(cause);
-	}
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor;
+
+/**
+ * The <code>PuppeTorException</code> comprises all kinds of checked
+ * exceptions that occur when interacting with the JVM-external Tor processes or
+ * with the local file system. Any occurence of this exception denotes either a
+ * configuration problem that can only be solved outside of the JVM, or an
+ * unexpected problem. In contrast to this, all kinds of programming errors of
+ * an application using this API (invoking a method with wrong parameter values,
+ * in wrong state, etc.) will instead cause appropriate runtime exceptions from
+ * the Java API.
+ * 
+ * @author kloesing
+ */
+ at 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> 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(String message, 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(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(Throwable cause) {
+		super(cause);
+	}
+}
diff --git a/src/org/torproject/puppetor/RouterNode.java b/src/org/torproject/puppetor/RouterNode.java
old mode 100755
new mode 100644
index 0fc8eec..d876892
--- a/src/org/torproject/puppetor/RouterNode.java
+++ b/src/org/torproject/puppetor/RouterNode.java
@@ -1,90 +1,90 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor;
-
-import java.rmi.RemoteException;
-
-/**
- * A <code>RouterNode</code> represents a Tor process that is configured to
- * both, relay traffic from a local application to the Tor network and to route
- * traffic on behalf of remote applications. It inherits most of its
- * configuration and behavior from its superclass <code>ProxyNode</code> and
- * adds some router-specific configurations and behavior.
- * 
- * @author kloesing
- */
-public interface RouterNode extends ProxyNode {
-
-	/**
-	 * Returns the dir port of this node.
-	 * 
-	 * @return The dir port of this node.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract int getDirPort() throws RemoteException;
-
-	/**
-	 * Returns the onion port of this node.
-	 * 
-	 * @return The onion port of this node.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract int getOrPort() throws RemoteException;
-
-	/**
-	 * <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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract String getFingerprint() throws PuppeTorException,
-			RemoteException;
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor;
+
+import java.rmi.RemoteException;
+
+/**
+ * A <code>RouterNode</code> represents a Tor process that is configured to
+ * both, relay traffic from a local application to the Tor network and to route
+ * traffic on behalf of remote applications. It inherits most of its
+ * configuration and behavior from its superclass <code>ProxyNode</code> and
+ * adds some router-specific configurations and behavior.
+ * 
+ * @author kloesing
+ */
+public interface RouterNode extends ProxyNode {
+
+	/**
+	 * Returns the dir port of this node.
+	 * 
+	 * @return The dir port of this node.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract int getDirPort() throws RemoteException;
+
+	/**
+	 * Returns the onion port of this node.
+	 * 
+	 * @return The onion port of this node.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract int getOrPort() throws RemoteException;
+
+	/**
+	 * <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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract String getFingerprint() throws PuppeTorException,
+			RemoteException;
+}
diff --git a/src/org/torproject/puppetor/ServerApplication.java b/src/org/torproject/puppetor/ServerApplication.java
old mode 100755
new mode 100644
index a7bba32..7e77652
--- a/src/org/torproject/puppetor/ServerApplication.java
+++ b/src/org/torproject/puppetor/ServerApplication.java
@@ -1,97 +1,97 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor;
-
-import java.rmi.Remote;
-import java.rmi.RemoteException;
-
-/**
- * The <code>ServerApplication</code> can be used as simple HTTP server that
- * answers all <code>HTTP GET</code> requests by empty <code>HTTP OK</code>
- * replies. Therefore, a thread will be started to listen for incoming requests
- * in the background.
- * 
- * @author kloesing
- */
-public interface ServerApplication extends Remote {
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void startListening() throws RemoteException;
-
-	/**
-	 * 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract void stopListening() throws RemoteException;
-
-	/**
-	 * Returns whether this server is currently in listening state.
-	 * 
-	 * @return The listening state of this server.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract boolean isListening() throws RemoteException;
-
-	/**
-	 * Returns the name of this server.
-	 * 
-	 * @return The name of this server.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract String getServerApplicationName() throws RemoteException;
-
-	/**
-	 * Returns the port on which this server listens.
-	 * 
-	 * @return The port on which this server listens.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public abstract int getServerPort() throws RemoteException;
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * The <code>ServerApplication</code> can be used as simple HTTP server that
+ * answers all <code>HTTP GET</code> requests by empty <code>HTTP OK</code>
+ * replies. Therefore, a thread will be started to listen for incoming requests
+ * in the background.
+ * 
+ * @author kloesing
+ */
+public interface ServerApplication extends Remote {
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void startListening() throws RemoteException;
+
+	/**
+	 * 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract void stopListening() throws RemoteException;
+
+	/**
+	 * Returns whether this server is currently in listening state.
+	 * 
+	 * @return The listening state of this server.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract boolean isListening() throws RemoteException;
+
+	/**
+	 * Returns the name of this server.
+	 * 
+	 * @return The name of this server.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract String getServerApplicationName() throws RemoteException;
+
+	/**
+	 * Returns the port on which this server listens.
+	 * 
+	 * @return The port on which this server listens.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public abstract int getServerPort() throws RemoteException;
+}
diff --git a/src/org/torproject/puppetor/examples/AccessingPublicWebServerOverTor.java b/src/org/torproject/puppetor/examples/AccessingPublicWebServerOverTor.java
old mode 100755
new mode 100644
index c1fa9fd..2d8317e
--- a/src/org/torproject/puppetor/examples/AccessingPublicWebServerOverTor.java
+++ b/src/org/torproject/puppetor/examples/AccessingPublicWebServerOverTor.java
@@ -1,153 +1,153 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor.examples;
-
-import java.rmi.RemoteException;
-
-import org.torproject.puppetor.ClientApplication;
-import org.torproject.puppetor.ClientEventType;
-import org.torproject.puppetor.Event;
-import org.torproject.puppetor.EventListener;
-import org.torproject.puppetor.EventManager;
-import org.torproject.puppetor.Network;
-import org.torproject.puppetor.NetworkFactory;
-import org.torproject.puppetor.ProxyNode;
-import org.torproject.puppetor.PuppeTorException;
-
-
-/**
- * Example for accessing a public web server (here: <code>www.google.com</code>)
- * over Tor to measure the access time.
- * 
- * @author kloesing
- */
-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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public static void main(String[] args) throws PuppeTorException,
-			RemoteException {
-
-		// though we only need a single proxy, we always need to create a
-		// network to initialize a test case.
-		Network network = NetworkFactory.createNetwork("example1");
-
-		// create a single proxy node with name "proxy"
-		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
-		ClientApplication client = network.createClient("client",
-				"www.google.com", 80, proxy.getSocksPort());
-
-		// create event listener to listen for client application events
-		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
-		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 (InterruptedException e) {
-		}
-
-		// shut down proxy
-		network.shutdownNodes();
-
-		// wait another second before exiting the application
-		try {
-			Thread.sleep(1000);
-		} catch (InterruptedException e) {
-		}
-
-		// Shut down the JVM
-		System.out.println("Goodbye.");
-		System.exit(0);
-	}
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor.examples;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.ClientApplication;
+import org.torproject.puppetor.ClientEventType;
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.ProxyNode;
+import org.torproject.puppetor.PuppeTorException;
+
+
+/**
+ * Example for accessing a public web server (here: <code>www.google.com</code>)
+ * over Tor to measure the access time.
+ * 
+ * @author kloesing
+ */
+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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public static void main(String[] args) throws PuppeTorException,
+			RemoteException {
+
+		// though we only need a single proxy, we always need to create a
+		// network to initialize a test case.
+		Network network = NetworkFactory.createNetwork("example1");
+
+		// create a single proxy node with name "proxy"
+		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
+		ClientApplication client = network.createClient("client",
+				"www.google.com", 80, proxy.getSocksPort());
+
+		// create event listener to listen for client application events
+		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
+		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 (InterruptedException e) {
+		}
+
+		// shut down proxy
+		network.shutdownNodes();
+
+		// wait another second before exiting the application
+		try {
+			Thread.sleep(1000);
+		} catch (InterruptedException e) {
+		}
+
+		// Shut down the JVM
+		System.out.println("Goodbye.");
+		System.exit(0);
+	}
+}
diff --git a/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java b/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
old mode 100755
new mode 100644
index 485713c..79c16b0
--- a/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
+++ b/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
@@ -1,163 +1,163 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor.examples;
-
-import java.rmi.RemoteException;
-
-import org.torproject.puppetor.ClientApplication;
-import org.torproject.puppetor.ClientEventType;
-import org.torproject.puppetor.Event;
-import org.torproject.puppetor.EventListener;
-import org.torproject.puppetor.EventManager;
-import org.torproject.puppetor.HiddenService;
-import org.torproject.puppetor.HiddenServiceEventType;
-import org.torproject.puppetor.Network;
-import org.torproject.puppetor.NetworkFactory;
-import org.torproject.puppetor.PuppeTorException;
-import org.torproject.puppetor.RouterNode;
-import org.torproject.puppetor.ServerApplication;
-
-
-/**
- * Example for advertising and accessing a hidden service over a private Tor
- * network.
- * 
- * @author kloesing
- */
-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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public static void main(String[] args) throws PuppeTorException,
-			RemoteException {
-
-		// create a network to initialize the test case
-		Network network = NetworkFactory.createNetwork("example4");
-
-		// create three router nodes
-		RouterNode router1 = network.createRouter("router1");
-		network.createRouter("router2");
-		RouterNode router3 = network.createRouter("router3");
-
-		// create only one directory node
-		network.createDirectory("dir1");
-
-		// add hidden service
-		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 (6 retries, 12 minutes timeout
-		// each)
-		if (!network.hupUntilUp(6, 12L * 60L * 1000L)) {
-
-			// 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
-		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
-		ServerApplication server = network.createServer("server", hidServ1
-				.getServicePort());
-
-		// create client application
-		ClientApplication client = network.createClient("client", hidServ1
-				.determineOnionAddress(), hidServ1.getVirtualPort(), router3
-				.getSocksPort());
-
-		// register event listener
-		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);
-	}
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor.examples;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.ClientApplication;
+import org.torproject.puppetor.ClientEventType;
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.HiddenService;
+import org.torproject.puppetor.HiddenServiceEventType;
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.RouterNode;
+import org.torproject.puppetor.ServerApplication;
+
+
+/**
+ * Example for advertising and accessing a hidden service over a private Tor
+ * network.
+ * 
+ * @author kloesing
+ */
+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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public static void main(String[] args) throws PuppeTorException,
+			RemoteException {
+
+		// create a network to initialize the test case
+		Network network = NetworkFactory.createNetwork("example4");
+
+		// create three router nodes
+		RouterNode router1 = network.createRouter("router1");
+		network.createRouter("router2");
+		RouterNode router3 = network.createRouter("router3");
+
+		// create only one directory node
+		network.createDirectory("dir1");
+
+		// add hidden service
+		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 (6 retries, 12 minutes timeout
+		// each)
+		if (!network.hupUntilUp(6, 12L * 60L * 1000L)) {
+
+			// 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
+		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
+		ServerApplication server = network.createServer("server", hidServ1
+				.getServicePort());
+
+		// create client application
+		ClientApplication client = network.createClient("client", hidServ1
+				.determineOnionAddress(), hidServ1.getVirtualPort(), router3
+				.getSocksPort());
+
+		// register event listener
+		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
old mode 100755
new mode 100644
index ca80137..a22a42f
--- a/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
+++ b/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
@@ -1,180 +1,180 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor.examples;
-
-import java.rmi.RemoteException;
-
-import org.torproject.puppetor.ClientApplication;
-import org.torproject.puppetor.ClientEventType;
-import org.torproject.puppetor.Event;
-import org.torproject.puppetor.EventListener;
-import org.torproject.puppetor.EventManager;
-import org.torproject.puppetor.HiddenService;
-import org.torproject.puppetor.HiddenServiceEventType;
-import org.torproject.puppetor.Network;
-import org.torproject.puppetor.NetworkFactory;
-import org.torproject.puppetor.ProxyNode;
-import org.torproject.puppetor.PuppeTorException;
-import org.torproject.puppetor.ServerApplication;
-import org.torproject.puppetor.ServerEventType;
-
-
-/**
- * Example for advertising and accessing a hidden service over the public Tor
- * network.
- * 
- * @author kloesing
- */
-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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public static void main(String[] args) throws PuppeTorException,
-			RemoteException {
-
-		// create a network to initialize the test case
-		Network network = NetworkFactory.createNetwork("example3");
-
-		// create two proxy nodes
-		ProxyNode proxy1 = network.createProxy("proxy1");
-		ProxyNode proxy2 = network.createProxy("proxy2");
-
-		// add hidden service to the configuration of proxy1
-		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
-		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
-		ServerApplication server = network.createServer("server", hidServ1
-				.getServicePort());
-
-		// create client application
-		ClientApplication client = network.createClient("client", hidServ1
-				.determineOnionAddress(), hidServ1.getVirtualPort(), proxy2
-				.getSocksPort());
-
-		// create event listener to listen for client and server application
-		// events
-		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);
-	}
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor.examples;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.ClientApplication;
+import org.torproject.puppetor.ClientEventType;
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.HiddenService;
+import org.torproject.puppetor.HiddenServiceEventType;
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.ProxyNode;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.ServerApplication;
+import org.torproject.puppetor.ServerEventType;
+
+
+/**
+ * Example for advertising and accessing a hidden service over the public Tor
+ * network.
+ * 
+ * @author kloesing
+ */
+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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public static void main(String[] args) throws PuppeTorException,
+			RemoteException {
+
+		// create a network to initialize the test case
+		Network network = NetworkFactory.createNetwork("example3");
+
+		// create two proxy nodes
+		ProxyNode proxy1 = network.createProxy("proxy1");
+		ProxyNode proxy2 = network.createProxy("proxy2");
+
+		// add hidden service to the configuration of proxy1
+		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
+		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
+		ServerApplication server = network.createServer("server", hidServ1
+				.getServicePort());
+
+		// create client application
+		ClientApplication client = network.createClient("client", hidServ1
+				.determineOnionAddress(), hidServ1.getVirtualPort(), proxy2
+				.getSocksPort());
+
+		// create event listener to listen for client and server application
+		// events
+		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
old mode 100755
new mode 100644
index a0e3875..1343c62
--- a/src/org/torproject/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
+++ b/src/org/torproject/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
@@ -1,143 +1,143 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor.examples;
-
-import java.rmi.RemoteException;
-
-import org.torproject.puppetor.Event;
-import org.torproject.puppetor.EventListener;
-import org.torproject.puppetor.EventManager;
-import org.torproject.puppetor.HiddenServiceEventType;
-import org.torproject.puppetor.Network;
-import org.torproject.puppetor.NetworkFactory;
-import org.torproject.puppetor.NodeEventType;
-import org.torproject.puppetor.ProxyNode;
-import org.torproject.puppetor.PuppeTorException;
-
-
-/**
- * Example for advertising a hidden service to the public Tor network and
- * observing the publication of rendezvous service descriptors.
- * 
- * @author kloesing
- */
-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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public static void main(String[] args) throws PuppeTorException,
-			RemoteException {
-
-		// create a network to initialize the test case
-		Network network = NetworkFactory.createNetwork("example2");
-		
-		// create a single proxy node
-		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
-		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
-		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 (InterruptedException e) {
-			// do nothing
-		}
-
-		// shut down proxy
-		network.shutdownNodes();
-
-		// Shut down the JVM
-		System.out.println("Goodbye.");
-		System.exit(0);
-	}
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor.examples;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.HiddenServiceEventType;
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.NodeEventType;
+import org.torproject.puppetor.ProxyNode;
+import org.torproject.puppetor.PuppeTorException;
+
+
+/**
+ * Example for advertising a hidden service to the public Tor network and
+ * observing the publication of rendezvous service descriptors.
+ * 
+ * @author kloesing
+ */
+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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public static void main(String[] args) throws PuppeTorException,
+			RemoteException {
+
+		// create a network to initialize the test case
+		Network network = NetworkFactory.createNetwork("example2");
+		
+		// create a single proxy node
+		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
+		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
+		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 (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
old mode 100755
new mode 100644
index ce7bddb..ca996f7
--- a/src/org/torproject/puppetor/impl/ClientApplicationImpl.java
+++ b/src/org/torproject/puppetor/impl/ClientApplicationImpl.java
@@ -1,494 +1,494 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor.impl;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintStream;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.Socket;
-import java.net.SocketTimeoutException;
-import java.net.Proxy.Type;
-import java.rmi.RemoteException;
-import java.rmi.server.UnicastRemoteObject;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.torproject.puppetor.ClientApplication;
-import org.torproject.puppetor.ClientEventType;
-
-
-/**
- * Implementation of <code>ClientApplication</code>.
- * 
- * @author kloesing
- */
- at SuppressWarnings("serial")
-public class ClientApplicationImpl extends UnicastRemoteObject 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 int retries;
-
-		/**
-		 * Flag that determines whether requests shall be stopped after the
-		 * first successful reply (<code>true</code>), or not (<code>false</code>).
-		 */
-		private boolean stopOnSuccess;
-
-		/**
-		 * Timeout in milliseconds for each retry.
-		 */
-		private 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(int retries, long timeoutForEachRetry,
-				boolean stopOnSuccess) {
-
-			// log entering
-			logger
-					.entering(this.getClass().getName(), "RequestThread",
-							new Object[] { retries, timeoutForEachRetry,
-									stopOnSuccess });
-
-			// check parameters
-			if (retries < 0 || timeoutForEachRetry < 0) {
-				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
-			this.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
-				InetSocketAddress isa = new InetSocketAddress("127.0.0.1",
-						socksPort);
-				Proxy p = new Proxy(Type.SOCKS, isa);
-
-				// create target address for socket -- don't resolve the target
-				// name to an IP address!
-				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
-					long timeBeforeConnectionAttempt = System
-							.currentTimeMillis();
-
-					// send event to event manager
-					eventManager.observeInternalEvent(
-							timeBeforeConnectionAttempt,
-							ClientApplicationImpl.this
-									.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
-						PrintStream out = new PrintStream(s.getOutputStream());
-						out.print("GET / HTTP/1.0\r\n\r\n");
-
-						// open input stream to read reply
-						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(),
-								ClientApplicationImpl.this
-										.getClientApplicationName(),
-								ClientEventType.CLIENT_REPLY_RECEIVED,
-								"Received response.");
-
-						// if we should stop on success, stop further connection
-						// attempts
-						if (this.stopOnSuccess) {
-							this.connected = false;
-						}
-
-						// clean up socket
-						in.close();
-						out.close();
-						s.close();
-
-					} catch (SocketTimeoutException e) {
-
-						// log warning
-						logger.log(Level.WARNING,
-								"Connection to remote server timed out!", e);
-
-						// send event to event manager
-						eventManager.observeInternalEvent(System
-								.currentTimeMillis(),
-								ClientApplicationImpl.this
-										.getClientApplicationName(),
-								ClientEventType.CLIENT_GAVE_UP_REQUEST,
-								"Giving up request.");
-
-						// try again immediately, if there are retries left
-
-					} catch (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(),
-								ClientApplicationImpl.this
-										.getClientApplicationName(),
-								ClientEventType.CLIENT_GAVE_UP_REQUEST,
-								"Giving up request.");
-
-						// wait for the rest of the timeout
-						long timeOfTimeoutLeft = timeBeforeConnectionAttempt
-								+ this.timeoutForEachRetry
-								- System.currentTimeMillis();
-						if (timeOfTimeoutLeft > 0) {
-							try {
-								Thread.sleep(timeOfTimeoutLeft);
-							} catch (InterruptedException ex) {
-								// do nothing
-							}
-						}
-
-					} finally {
-
-						// close connection
-						try {
-
-							// try to close socket
-							logger.log(Level.FINER, "Trying to close socket.");
-							s.close();
-							logger.log(Level.FINE, "Socket closed.");
-
-						} catch (Exception e1) {
-
-							// log warning
-							logger.log(Level.WARNING,
-									"Exception when trying to close socket!",
-									e1);
-						}
-					}
-				}
-
-			} catch (Exception e) {
-
-				// log that we have been interrupted
-				logger.log(Level.WARNING, "Client has been interrupted!", e);
-
-			} finally {
-
-				// we are done here
-				logger.log(Level.FINE, "Requests performed!");
-
-				// send event to event manager
-				eventManager.observeInternalEvent(System.currentTimeMillis(),
-						ClientApplicationImpl.this.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
-			this.connected = false;
-			this.interrupt();
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "stopRequest");
-		}
-	}
-
-	/**
-	 * Name of this client application that is used as logger name of this node.
-	 */
-	private String clientApplicationName;
-
-	/**
-	 * Thread that performs the requests in the background.
-	 */
-	private RequestThread clientThread;
-
-	/**
-	 * Event manager that handles all events concerning this client application.
-	 */
-	private EventManagerImpl eventManager;
-
-	/**
-	 * Logger for this client which is called "client." plus the name of this
-	 * client application.
-	 */
-	private Logger logger;
-
-	/**
-	 * SOCKS port of the local Tor node to which requests are sent.
-	 */
-	private int socksPort;
-
-	/**
-	 * Target name for the requests sent by this client; can be a publicly
-	 * available URL or an onion address.
-	 */
-	private 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 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	ClientApplicationImpl(NetworkImpl network, String clientApplicationName,
-			String targetName, int targetPort, int socksPort)
-			throws RemoteException {
-
-		// check if clientApplicationName can be used as logger name
-		if (clientApplicationName == null
-				|| clientApplicationName.length() == 0) {
-			throw new IllegalArgumentException(
-					"Invalid clientApplicationName: " + clientApplicationName);
-		}
-
-		// create logger
-		this.logger = Logger.getLogger("client." + clientApplicationName);
-
-		// log entering
-		this.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) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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
-		this.eventManager = network.getEventManagerImpl();
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "ClientApplicationImpl");
-	}
-
-	public synchronized void startRequests(int retries,
-			long timeoutForEachRetry, boolean stopOnSuccess) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "performRequest",
-				new Object[] { retries, timeoutForEachRetry, stopOnSuccess });
-
-		// check parameters
-		if (retries <= 0 || timeoutForEachRetry < 0) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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 (this.clientThread != null) {
-			IllegalStateException e = new IllegalStateException(
-					"Another request has already been started!");
-			this.logger
-					.throwing(this.getClass().getName(), "performRequest", e);
-			throw e;
-		}
-
-		// create a thread that performs requests in the background
-		this.clientThread = new RequestThread(retries, timeoutForEachRetry,
-				stopOnSuccess);
-		this.clientThread.setName("Request Thread");
-		this.clientThread.setDaemon(true);
-		this.clientThread.start();
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "performRequest");
-	}
-
-	public synchronized void stopRequest() {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "stopRequest");
-
-		// check if a request is running
-		if (this.clientThread == null) {
-			IllegalStateException e = new IllegalStateException("Cannot stop "
-					+ "request, because no request has been started!");
-			this.logger.throwing(this.getClass().getName(), "stopRequest", e);
-			throw e;
-		}
-
-		// log this event
-		this.logger.log(Level.FINE, "Shutting down client");
-
-		// interrupt thread
-		this.clientThread.stopRequest();
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "stopRequest");
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": clientApplicationName=\""
-				+ this.clientApplicationName + "\", targetAddress=\""
-				+ this.targetName + "\", targetPort=" + this.targetPort
-				+ ", socksPort=" + this.socksPort;
-	}
-
-	public String getClientApplicationName() {
-		return clientApplicationName;
-	}
-
-	public int getSocksPort() {
-		return socksPort;
-	}
-
-	public String getTargetName() {
-		return targetName;
-	}
-
-	public int getTargetPort() {
-		return targetPort;
-	}
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor.impl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.net.Proxy.Type;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.torproject.puppetor.ClientApplication;
+import org.torproject.puppetor.ClientEventType;
+
+
+/**
+ * Implementation of <code>ClientApplication</code>.
+ * 
+ * @author kloesing
+ */
+ at SuppressWarnings("serial")
+public class ClientApplicationImpl extends UnicastRemoteObject 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 int retries;
+
+		/**
+		 * Flag that determines whether requests shall be stopped after the
+		 * first successful reply (<code>true</code>), or not (<code>false</code>).
+		 */
+		private boolean stopOnSuccess;
+
+		/**
+		 * Timeout in milliseconds for each retry.
+		 */
+		private 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(int retries, long timeoutForEachRetry,
+				boolean stopOnSuccess) {
+
+			// log entering
+			logger
+					.entering(this.getClass().getName(), "RequestThread",
+							new Object[] { retries, timeoutForEachRetry,
+									stopOnSuccess });
+
+			// check parameters
+			if (retries < 0 || timeoutForEachRetry < 0) {
+				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
+			this.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
+				InetSocketAddress isa = new InetSocketAddress("127.0.0.1",
+						socksPort);
+				Proxy p = new Proxy(Type.SOCKS, isa);
+
+				// create target address for socket -- don't resolve the target
+				// name to an IP address!
+				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
+					long timeBeforeConnectionAttempt = System
+							.currentTimeMillis();
+
+					// send event to event manager
+					eventManager.observeInternalEvent(
+							timeBeforeConnectionAttempt,
+							ClientApplicationImpl.this
+									.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
+						PrintStream out = new PrintStream(s.getOutputStream());
+						out.print("GET / HTTP/1.0\r\n\r\n");
+
+						// open input stream to read reply
+						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(),
+								ClientApplicationImpl.this
+										.getClientApplicationName(),
+								ClientEventType.CLIENT_REPLY_RECEIVED,
+								"Received response.");
+
+						// if we should stop on success, stop further connection
+						// attempts
+						if (this.stopOnSuccess) {
+							this.connected = false;
+						}
+
+						// clean up socket
+						in.close();
+						out.close();
+						s.close();
+
+					} catch (SocketTimeoutException e) {
+
+						// log warning
+						logger.log(Level.WARNING,
+								"Connection to remote server timed out!", e);
+
+						// send event to event manager
+						eventManager.observeInternalEvent(System
+								.currentTimeMillis(),
+								ClientApplicationImpl.this
+										.getClientApplicationName(),
+								ClientEventType.CLIENT_GAVE_UP_REQUEST,
+								"Giving up request.");
+
+						// try again immediately, if there are retries left
+
+					} catch (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(),
+								ClientApplicationImpl.this
+										.getClientApplicationName(),
+								ClientEventType.CLIENT_GAVE_UP_REQUEST,
+								"Giving up request.");
+
+						// wait for the rest of the timeout
+						long timeOfTimeoutLeft = timeBeforeConnectionAttempt
+								+ this.timeoutForEachRetry
+								- System.currentTimeMillis();
+						if (timeOfTimeoutLeft > 0) {
+							try {
+								Thread.sleep(timeOfTimeoutLeft);
+							} catch (InterruptedException ex) {
+								// do nothing
+							}
+						}
+
+					} finally {
+
+						// close connection
+						try {
+
+							// try to close socket
+							logger.log(Level.FINER, "Trying to close socket.");
+							s.close();
+							logger.log(Level.FINE, "Socket closed.");
+
+						} catch (Exception e1) {
+
+							// log warning
+							logger.log(Level.WARNING,
+									"Exception when trying to close socket!",
+									e1);
+						}
+					}
+				}
+
+			} catch (Exception e) {
+
+				// log that we have been interrupted
+				logger.log(Level.WARNING, "Client has been interrupted!", e);
+
+			} finally {
+
+				// we are done here
+				logger.log(Level.FINE, "Requests performed!");
+
+				// send event to event manager
+				eventManager.observeInternalEvent(System.currentTimeMillis(),
+						ClientApplicationImpl.this.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
+			this.connected = false;
+			this.interrupt();
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "stopRequest");
+		}
+	}
+
+	/**
+	 * Name of this client application that is used as logger name of this node.
+	 */
+	private String clientApplicationName;
+
+	/**
+	 * Thread that performs the requests in the background.
+	 */
+	private RequestThread clientThread;
+
+	/**
+	 * Event manager that handles all events concerning this client application.
+	 */
+	private EventManagerImpl eventManager;
+
+	/**
+	 * Logger for this client which is called "client." plus the name of this
+	 * client application.
+	 */
+	private Logger logger;
+
+	/**
+	 * SOCKS port of the local Tor node to which requests are sent.
+	 */
+	private int socksPort;
+
+	/**
+	 * Target name for the requests sent by this client; can be a publicly
+	 * available URL or an onion address.
+	 */
+	private 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 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	ClientApplicationImpl(NetworkImpl network, String clientApplicationName,
+			String targetName, int targetPort, int socksPort)
+			throws RemoteException {
+
+		// check if clientApplicationName can be used as logger name
+		if (clientApplicationName == null
+				|| clientApplicationName.length() == 0) {
+			throw new IllegalArgumentException(
+					"Invalid clientApplicationName: " + clientApplicationName);
+		}
+
+		// create logger
+		this.logger = Logger.getLogger("client." + clientApplicationName);
+
+		// log entering
+		this.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) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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
+		this.eventManager = network.getEventManagerImpl();
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "ClientApplicationImpl");
+	}
+
+	public synchronized void startRequests(int retries,
+			long timeoutForEachRetry, boolean stopOnSuccess) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "performRequest",
+				new Object[] { retries, timeoutForEachRetry, stopOnSuccess });
+
+		// check parameters
+		if (retries <= 0 || timeoutForEachRetry < 0) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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 (this.clientThread != null) {
+			IllegalStateException e = new IllegalStateException(
+					"Another request has already been started!");
+			this.logger
+					.throwing(this.getClass().getName(), "performRequest", e);
+			throw e;
+		}
+
+		// create a thread that performs requests in the background
+		this.clientThread = new RequestThread(retries, timeoutForEachRetry,
+				stopOnSuccess);
+		this.clientThread.setName("Request Thread");
+		this.clientThread.setDaemon(true);
+		this.clientThread.start();
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "performRequest");
+	}
+
+	public synchronized void stopRequest() {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "stopRequest");
+
+		// check if a request is running
+		if (this.clientThread == null) {
+			IllegalStateException e = new IllegalStateException("Cannot stop "
+					+ "request, because no request has been started!");
+			this.logger.throwing(this.getClass().getName(), "stopRequest", e);
+			throw e;
+		}
+
+		// log this event
+		this.logger.log(Level.FINE, "Shutting down client");
+
+		// interrupt thread
+		this.clientThread.stopRequest();
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "stopRequest");
+	}
+
+	@Override
+	public String toString() {
+		return this.getClass().getSimpleName() + ": clientApplicationName=\""
+				+ this.clientApplicationName + "\", targetAddress=\""
+				+ this.targetName + "\", targetPort=" + this.targetPort
+				+ ", socksPort=" + this.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
old mode 100755
new mode 100644
index a7c2a25..53927cb
--- a/src/org/torproject/puppetor/impl/DirectoryNodeImpl.java
+++ b/src/org/torproject/puppetor/impl/DirectoryNodeImpl.java
@@ -1,444 +1,444 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor.impl;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.rmi.RemoteException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.logging.Level;
-
-import org.torproject.puppetor.DirectoryNode;
-import org.torproject.puppetor.PuppeTorException;
-
-
-/**
- * Implementation of <code>DirectoryNode</code>.
- * 
- * @author kloesing
- */
- at SuppressWarnings("serial")
-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.
-	 */
-	public class GenerateCertificateThread extends Thread {
-
-		@Override
-		public void run() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "run");
-
-			// run tor-gencert
-			ProcessBuilder processBuilder = new ProcessBuilder(
-					torGencertExecutable.getPath(), "--create-identity-key"// );
-					, "--passphrase-fd", "0");
-			File workingDirectory = new File(DirectoryNodeImpl.this.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 (IOException e) {
-				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);
-				DirectoryNodeImpl.this.setCaughtException(ex);
-				return;
-			}
-
-			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
-					tmpProcess.getOutputStream()));
-			try {
-				writer.write("somepassword\n");
-				writer.close();
-			} catch (IOException e1) {
-				System.out.println("Exception at write! " + e1.getMessage());
-				e1.printStackTrace();
-			}
-
-			InputStream stderr = tmpProcess.getInputStream();
-			InputStreamReader isr = new InputStreamReader(stderr);
-			BufferedReader br = new BufferedReader(isr);
-			String line = null;
-			try {
-				while ((line = br.readLine()) != null)
-					;
-			} catch (IOException e1) {
-				e1.printStackTrace();
-			}
-
-			// wait for process to terminate
-			int exitValue = 0;
-			try {
-				exitValue = tmpProcess.waitFor();
-			} catch (InterruptedException e) {
-				PuppeTorException ex = new PuppeTorException(
-						"Interrupted while waiting for tor-gencert process to exit!",
-						e);
-				logger.log(Level.WARNING,
-						"tor-gencert process was interrupted!", ex);
-				DirectoryNodeImpl.this.setCaughtException(ex);
-				return;
-			}
-
-			if (exitValue != 0) {
-				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);
-				DirectoryNodeImpl.this.setCaughtException(ex);
-				return;
-			}
-
-			// read fingerprint from file
-			File authorityCertificateFile = new File(workingDirectory
-					.getAbsolutePath()
-					+ File.separator + "authority_certificate");
-			String identity;
-			try {
-				BufferedReader br2 = new BufferedReader(new FileReader(
-						authorityCertificateFile));
-				while ((line = br2.readLine()) != null
-						&& !line.startsWith("fingerprint "))
-					;
-				if (line == null) {
-					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);
-					DirectoryNodeImpl.this.setCaughtException(ex);
-					return;
-				}
-				identity = line.substring(line.indexOf(" ") + 1);
-				br2.close();
-			} catch (IOException e) {
-				PuppeTorException ex = new PuppeTorException(
-						"Could not read fingerprint from file!", e);
-				logger.log(Level.WARNING, "Could not read fingerprint file!",
-						ex);
-				DirectoryNodeImpl.this.setCaughtException(ex);
-				return;
-			}
-
-			DirectoryNodeImpl.this.setV3Identity(identity);
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "run");
-		}
-	}
-
-	/**
-	 * Set of routers that are approved by this directory node.
-	 */
-	private 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 RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while writing the temporary
-	 *             <code>approved-routers</code> file.
-	 */
-	DirectoryNodeImpl(NetworkImpl network, String nodeName, int controlPort,
-			int socksPort, int orPort, int dirPort, String serverIpAddress)
-			throws RemoteException {
-
-		// create superclass instance; parameter checking is done in super
-		// constructor
-		super(network, nodeName, controlPort, socksPort, orPort, dirPort,
-				serverIpAddress);
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "DirectoryNodeImpl",
-				new Object[] { network, nodeName, controlPort, socksPort,
-						orPort, dirPort });
-
-		// initialize attribute
-		this.approvedRouters = new TreeSet<String>();
-
-		// extend configuration by template configuration of directory nodes
-		this.configuration.addAll(templateConfiguration);
-
-		// log exiting
-		this.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(String v3Identity) {
-		// log entering
-		this.logger.entering(this.getClass().getName(), "setV3Identity",
-				v3Identity);
-
-		// remember fingerprint and notify all waiting threads
-		this.v3Identity = v3Identity;
-		this.notifyAll();
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "setV3Identity");
-	}
-
-	/**
-	 * The generated v3 identity string.
-	 */
-	private String v3Identity;
-
-	public synchronized String getV3Identity() throws PuppeTorException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "getV3Identity");
-
-		// wait until either the v3 identity has been determined or an exception
-		// was caught
-		while (this.v3Identity == null && this.caughtException == null) {
-
-			try {
-				wait(500);
-			} catch (InterruptedException e) {
-				// do nothing
-			}
-		}
-
-		if (this.caughtException != null) {
-			this.logger.throwing(this.getClass().getName(), "getV3Identity",
-					this.caughtException);
-			throw this.caughtException;
-		}
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "getV3Identity",
-				this.v3Identity);
-		return this.v3Identity;
-	}
-
-	public synchronized String getDirServerString() throws PuppeTorException,
-			RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "getDirServerString");
-
-		// determine fingerprint
-		String fingerprint = this.getFingerprint();
-
-		// cut off router nickname
-		fingerprint = fingerprint.substring(fingerprint.indexOf(" ") + 1);
-
-		// determine v3 identity
-		String determinedV3Identity = this.getV3Identity();
-
-		// put everything together
-		String dirServerString = "DirServer " + this.nodeName + " v3ident="
-				+ determinedV3Identity + " orport=" + this.orPort + " "
-				+ this.serverIpAddress + ":" + this.dirPort + " " + fingerprint;
-
-		// log exiting and return dir server string
-		this.logger.exiting(this.getClass().getName(), "getDirServerString",
-				dirServerString);
-		return dirServerString;
-	}
-
-	public void addApprovedRouters(Set<String> routers) throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "addApprovedRouters",
-				routers);
-
-		// check parameter
-		if (routers == null) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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)
-		this.approvedRouters.addAll(routers);
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "addApprovedRouters");
-	}
-
-	@Override
-	protected synchronized void determineFingerprint() {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "determineFingerprint");
-
-		// start a thread to generate the directory's certificate
-		GenerateCertificateThread certificateThread = new GenerateCertificateThread();
-		certificateThread.setName(nodeName + " Certificate Generator");
-		certificateThread.start();
-
-		// wait (non-blocking) for the v3 identity string
-		try {
-			this.getV3Identity();
-		} catch (PuppeTorException e1) {
-			PuppeTorException ex = new PuppeTorException(
-					"Could not read v3 identity string!", e1);
-			this.caughtException = ex;
-			return;
-		}
-
-		// create an empty approved-routers file to make Tor happy
-		try {
-			new File(this.workingDir.getAbsolutePath() + File.separator
-					+ "approved-routers").createNewFile();
-		} catch (IOException e) {
-			PuppeTorException ex = new PuppeTorException(
-					"Could not write empty approved-routers file!", e);
-			this.caughtException = ex;
-			return;
-		}
-
-		// invoke overwritten method
-		super.determineFingerprint();
-	}
-
-	@Override
-	public synchronized void writeConfiguration() throws PuppeTorException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "writeConfiguration");
-
-		// write approved-routers file
-		try {
-			File approvedRoutersFile = new File(this.workingDir
-					.getAbsolutePath()
-					+ File.separator + "approved-routers");
-			BufferedWriter bw = new BufferedWriter(new FileWriter(
-					approvedRoutersFile));
-			for (String approvedRouter : this.approvedRouters) {
-				bw.write(approvedRouter + "\n");
-			}
-			bw.close();
-		} catch (IOException e) {
-			PuppeTorException ex = new PuppeTorException(e);
-			this.logger.throwing(this.getClass().getName(),
-					"writeConfiguration", ex);
-			throw ex;
-		}
-
-		// invoke overridden method
-		super.writeConfiguration();
-
-		// log exiting
-		this.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");
-	}
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor.impl;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.logging.Level;
+
+import org.torproject.puppetor.DirectoryNode;
+import org.torproject.puppetor.PuppeTorException;
+
+
+/**
+ * Implementation of <code>DirectoryNode</code>.
+ * 
+ * @author kloesing
+ */
+ at SuppressWarnings("serial")
+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.
+	 */
+	public class GenerateCertificateThread extends Thread {
+
+		@Override
+		public void run() {
+
+			// log entering
+			logger.entering(this.getClass().getName(), "run");
+
+			// run tor-gencert
+			ProcessBuilder processBuilder = new ProcessBuilder(
+					torGencertExecutable.getPath(), "--create-identity-key"// );
+					, "--passphrase-fd", "0");
+			File workingDirectory = new File(DirectoryNodeImpl.this.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 (IOException e) {
+				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);
+				DirectoryNodeImpl.this.setCaughtException(ex);
+				return;
+			}
+
+			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
+					tmpProcess.getOutputStream()));
+			try {
+				writer.write("somepassword\n");
+				writer.close();
+			} catch (IOException e1) {
+				System.out.println("Exception at write! " + e1.getMessage());
+				e1.printStackTrace();
+			}
+
+			InputStream stderr = tmpProcess.getInputStream();
+			InputStreamReader isr = new InputStreamReader(stderr);
+			BufferedReader br = new BufferedReader(isr);
+			String line = null;
+			try {
+				while ((line = br.readLine()) != null)
+					;
+			} catch (IOException e1) {
+				e1.printStackTrace();
+			}
+
+			// wait for process to terminate
+			int exitValue = 0;
+			try {
+				exitValue = tmpProcess.waitFor();
+			} catch (InterruptedException e) {
+				PuppeTorException ex = new PuppeTorException(
+						"Interrupted while waiting for tor-gencert process to exit!",
+						e);
+				logger.log(Level.WARNING,
+						"tor-gencert process was interrupted!", ex);
+				DirectoryNodeImpl.this.setCaughtException(ex);
+				return;
+			}
+
+			if (exitValue != 0) {
+				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);
+				DirectoryNodeImpl.this.setCaughtException(ex);
+				return;
+			}
+
+			// read fingerprint from file
+			File authorityCertificateFile = new File(workingDirectory
+					.getAbsolutePath()
+					+ File.separator + "authority_certificate");
+			String identity;
+			try {
+				BufferedReader br2 = new BufferedReader(new FileReader(
+						authorityCertificateFile));
+				while ((line = br2.readLine()) != null
+						&& !line.startsWith("fingerprint "))
+					;
+				if (line == null) {
+					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);
+					DirectoryNodeImpl.this.setCaughtException(ex);
+					return;
+				}
+				identity = line.substring(line.indexOf(" ") + 1);
+				br2.close();
+			} catch (IOException e) {
+				PuppeTorException ex = new PuppeTorException(
+						"Could not read fingerprint from file!", e);
+				logger.log(Level.WARNING, "Could not read fingerprint file!",
+						ex);
+				DirectoryNodeImpl.this.setCaughtException(ex);
+				return;
+			}
+
+			DirectoryNodeImpl.this.setV3Identity(identity);
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "run");
+		}
+	}
+
+	/**
+	 * Set of routers that are approved by this directory node.
+	 */
+	private 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 RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 * @throws PuppeTorException
+	 *             Thrown if an I/O problem occurs while writing the temporary
+	 *             <code>approved-routers</code> file.
+	 */
+	DirectoryNodeImpl(NetworkImpl network, String nodeName, int controlPort,
+			int socksPort, int orPort, int dirPort, String serverIpAddress)
+			throws RemoteException {
+
+		// create superclass instance; parameter checking is done in super
+		// constructor
+		super(network, nodeName, controlPort, socksPort, orPort, dirPort,
+				serverIpAddress);
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "DirectoryNodeImpl",
+				new Object[] { network, nodeName, controlPort, socksPort,
+						orPort, dirPort });
+
+		// initialize attribute
+		this.approvedRouters = new TreeSet<String>();
+
+		// extend configuration by template configuration of directory nodes
+		this.configuration.addAll(templateConfiguration);
+
+		// log exiting
+		this.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(String v3Identity) {
+		// log entering
+		this.logger.entering(this.getClass().getName(), "setV3Identity",
+				v3Identity);
+
+		// remember fingerprint and notify all waiting threads
+		this.v3Identity = v3Identity;
+		this.notifyAll();
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "setV3Identity");
+	}
+
+	/**
+	 * The generated v3 identity string.
+	 */
+	private String v3Identity;
+
+	public synchronized String getV3Identity() throws PuppeTorException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "getV3Identity");
+
+		// wait until either the v3 identity has been determined or an exception
+		// was caught
+		while (this.v3Identity == null && this.caughtException == null) {
+
+			try {
+				wait(500);
+			} catch (InterruptedException e) {
+				// do nothing
+			}
+		}
+
+		if (this.caughtException != null) {
+			this.logger.throwing(this.getClass().getName(), "getV3Identity",
+					this.caughtException);
+			throw this.caughtException;
+		}
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "getV3Identity",
+				this.v3Identity);
+		return this.v3Identity;
+	}
+
+	public synchronized String getDirServerString() throws PuppeTorException,
+			RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "getDirServerString");
+
+		// determine fingerprint
+		String fingerprint = this.getFingerprint();
+
+		// cut off router nickname
+		fingerprint = fingerprint.substring(fingerprint.indexOf(" ") + 1);
+
+		// determine v3 identity
+		String determinedV3Identity = this.getV3Identity();
+
+		// put everything together
+		String dirServerString = "DirServer " + this.nodeName + " v3ident="
+				+ determinedV3Identity + " orport=" + this.orPort + " "
+				+ this.serverIpAddress + ":" + this.dirPort + " " + fingerprint;
+
+		// log exiting and return dir server string
+		this.logger.exiting(this.getClass().getName(), "getDirServerString",
+				dirServerString);
+		return dirServerString;
+	}
+
+	public void addApprovedRouters(Set<String> routers) throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "addApprovedRouters",
+				routers);
+
+		// check parameter
+		if (routers == null) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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)
+		this.approvedRouters.addAll(routers);
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "addApprovedRouters");
+	}
+
+	@Override
+	protected synchronized void determineFingerprint() {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "determineFingerprint");
+
+		// start a thread to generate the directory's certificate
+		GenerateCertificateThread certificateThread = new GenerateCertificateThread();
+		certificateThread.setName(nodeName + " Certificate Generator");
+		certificateThread.start();
+
+		// wait (non-blocking) for the v3 identity string
+		try {
+			this.getV3Identity();
+		} catch (PuppeTorException e1) {
+			PuppeTorException ex = new PuppeTorException(
+					"Could not read v3 identity string!", e1);
+			this.caughtException = ex;
+			return;
+		}
+
+		// create an empty approved-routers file to make Tor happy
+		try {
+			new File(this.workingDir.getAbsolutePath() + File.separator
+					+ "approved-routers").createNewFile();
+		} catch (IOException e) {
+			PuppeTorException ex = new PuppeTorException(
+					"Could not write empty approved-routers file!", e);
+			this.caughtException = ex;
+			return;
+		}
+
+		// invoke overwritten method
+		super.determineFingerprint();
+	}
+
+	@Override
+	public synchronized void writeConfiguration() throws PuppeTorException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "writeConfiguration");
+
+		// write approved-routers file
+		try {
+			File approvedRoutersFile = new File(this.workingDir
+					.getAbsolutePath()
+					+ File.separator + "approved-routers");
+			BufferedWriter bw = new BufferedWriter(new FileWriter(
+					approvedRoutersFile));
+			for (String approvedRouter : this.approvedRouters) {
+				bw.write(approvedRouter + "\n");
+			}
+			bw.close();
+		} catch (IOException e) {
+			PuppeTorException ex = new PuppeTorException(e);
+			this.logger.throwing(this.getClass().getName(),
+					"writeConfiguration", ex);
+			throw ex;
+		}
+
+		// invoke overridden method
+		super.writeConfiguration();
+
+		// log exiting
+		this.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");
+	}
+}
diff --git a/src/org/torproject/puppetor/impl/EventManagerImpl.java b/src/org/torproject/puppetor/impl/EventManagerImpl.java
old mode 100755
new mode 100644
index 3263163..d163ac8
--- a/src/org/torproject/puppetor/impl/EventManagerImpl.java
+++ b/src/org/torproject/puppetor/impl/EventManagerImpl.java
@@ -1,781 +1,781 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor.impl;
-
-import java.rmi.RemoteException;
-import java.rmi.server.UnicastRemoteObject;
-import java.text.ParsePosition;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.Map.Entry;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.torproject.puppetor.Event;
-import org.torproject.puppetor.EventListener;
-import org.torproject.puppetor.EventManager;
-import org.torproject.puppetor.EventType;
-import org.torproject.puppetor.HiddenServiceEventType;
-import org.torproject.puppetor.NodeEventType;
-
-
-/**
- * Implementation of <code>EventManager</code>.
- * 
- * @author kloesing
- */
- at SuppressWarnings("serial")
-public class EventManagerImpl extends UnicastRemoteObject implements
-		EventManager {
-
-	/**
-	 * Registered event handlers for specific sources.
-	 */
-	private Map<String, Set<EventListener>> eventHandlers;
-
-	/**
-	 * Registered event handlers for all sources.
-	 */
-	private Set<EventListener> eventHandlersForAllSources;
-
-	/**
-	 * Logger for this event manager which is called "event." plus the name of
-	 * the network.
-	 */
-	private Logger logger;
-
-	/**
-	 * Events observed so far.
-	 */
-	private 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 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	EventManagerImpl(String networkName) throws RemoteException {
-
-		// check if networkName can be used as logger name
-		if (networkName == null || networkName.length() == 0) {
-			throw new IllegalArgumentException("Invalid networkName: "
-					+ networkName);
-		}
-
-		// create logger
-		this.logger = Logger.getLogger("event." + networkName);
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "EventManagerImpl",
-				networkName);
-
-		// create data structures
-		this.observedEvents = new HashMap<String, List<Event>>();
-		this.eventHandlers = new HashMap<String, Set<EventListener>>();
-		this.eventHandlersForAllSources = new HashSet<EventListener>();
-		this.eventSources = new HashSet<String>();
-
-		// start thread to parse events
-		Thread eventParseThread = new Thread() {
-			@Override
-			public void run() {
-				while (true) {
-					parseNextEvent();
-				}
-			}
-		};
-		eventParseThread.setDaemon(true);
-		eventParseThread.start();
-
-		// initialize event type patterns
-		initializeEventTypePatterns();
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "EventManagerImpl");
-	}
-
-	public synchronized List<Event> addEventListener(String source,
-			EventListener listener) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "addEventListener",
-				new Object[] { source, listener });
-
-		// check parameters
-		if (source == null || listener == null
-				|| !this.eventSources.contains(source)) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.logger.throwing(this.getClass().getName(), "addEventListener",
-					e);
-			throw e;
-		}
-
-		// if necessary, create new event listener set for source
-		if (!this.eventHandlers.containsKey(source)) {
-			this.eventHandlers.put(source, new HashSet<EventListener>());
-		}
-
-		// add listener
-		this.eventHandlers.get(source).add(listener);
-
-		// log change
-		this.logger
-				.log(Level.FINE, "Added event listener for source " + source);
-
-		// log exiting and return
-		List<Event> result = getEventHistory(source);
-		this.logger.exiting(this.getClass().getName(), "addEventListener",
-				result);
-		return result;
-	}
-
-	public synchronized void addEventListener(EventListener listener) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "addEventListener",
-				new Object[] { listener });
-
-		// check parameters
-		if (listener == null) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.logger.throwing(this.getClass().getName(), "addEventListener",
-					e);
-			throw e;
-		}
-
-		// add listener
-		this.eventHandlersForAllSources.add(listener);
-
-		// log change
-		this.logger.log(Level.FINE, "Added event listener for all sources.");
-
-		// log exiting and return
-		this.logger.exiting(this.getClass().getName(), "addEventListener");
-		return;
-	}
-
-	public synchronized List<Event> getEventHistory(String source) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "getEventHistory",
-				source);
-
-		// check parameter
-		if (source == null || !this.eventSources.contains(source)) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.logger.throwing(this.getClass().getName(), "getEventHistory",
-					e);
-			throw e;
-		}
-
-		// prepare result
-		List<Event> result = new ArrayList<Event>();
-
-		// did we already observe events for this source?
-		if (this.observedEvents.containsKey(source)) {
-			// yes, add all events to result list
-			result.addAll(this.observedEvents.get(source));
-		}
-
-		// log exiting and return result
-		this.logger.exiting(this.getClass().getName(), "getEventHistory",
-				result);
-		return result;
-	}
-
-	public synchronized boolean hasEventOccured(String source, EventType type) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "hasEventOccured",
-				new Object[] { source, type });
-
-		// check parameters
-		if (source == null || type == null
-				|| !this.eventSources.contains(source)) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.logger.throwing(this.getClass().getName(), "hasEventOccured",
-					e);
-			throw e;
-		}
-
-		// determine result
-		boolean result = false;
-		if (this.observedEvents.containsKey(source)) {
-			for (Event event : this.observedEvents.get(source)) {
-				if (event.getType() == type) {
-					result = true;
-					break;
-				}
-			}
-		}
-
-		// log exiting and return result
-		this.logger.exiting(this.getClass().getName(), "hasEventOccured",
-				result);
-		return result;
-	}
-
-	/**
-	 * An ordered list of all log statements that are still unparsed.
-	 */
-	private 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(String source, String logMessage) {
-		this.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.
-	 */
-	synchronized void observeInternalEvent(long occurrenceTime, String source,
-			EventType type, String message) {
-
-		if (!this.eventSources.contains(source)) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.logger.throwing(this.getClass().getName(),
-					"observeInternalEvent", e);
-			throw e;
-		}
-
-		this.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 (this.unparsedLogStatements.isEmpty()) {
-				try {
-					wait();
-				} catch (InterruptedException e) {
-				}
-			}
-			event = this.unparsedLogStatements.remove(0);
-		}
-
-		// does the event contain a known source? if not, discard it
-		if (!this.eventSources.contains(event.getSource())) {
-			this.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 {
-			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...
-			 */
-			Calendar c = Calendar.getInstance();
-			int currentYear = c.get(Calendar.YEAR);
-
-			// try to apply one of the event type patterns
-			for (Entry<Pattern, EventType> entry : eventTypePatterns.entrySet()) {
-				Matcher matcher = entry.getKey().matcher(line);
-				if (matcher.find()) {
-					SimpleDateFormat sdf = new SimpleDateFormat(
-							"MMM dd HH:mm:ss.SSS", Locale.US);
-					Date logTime = sdf.parse(line, new ParsePosition(0));
-					c.setTimeInMillis(logTime.getTime());
-					c.set(Calendar.YEAR, currentYear);
-					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(String patternString,
-			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(Event event) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "observeEvent", event);
-
-		String source = event.getSource();
-		this.logger.log(Level.FINE, "Observed event " + event + " from source "
-				+ source + "!");
-
-		// remember observed event
-		if (!this.observedEvents.containsKey(event.getSource())) {
-			this.observedEvents.put(source, new ArrayList<Event>());
-		}
-		this.observedEvents.get(source).add(event);
-
-		// notify waiting threads
-		notifyAll();
-
-		// inform event listeners
-		if (this.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
-			Set<EventListener> copyOfEventHandlers = new HashSet<EventListener>(
-					this.eventHandlers.get(source));
-
-			for (EventListener eventHandler : copyOfEventHandlers) {
-
-				this.logger.log(Level.FINE, "Informing event listener "
-						+ eventHandler + " about recently observed event "
-						+ event + " from source " + source + "!");
-				try {
-					eventHandler.handleEvent(event);
-				} catch (RemoteException e) {
-					this.logger.log(Level.WARNING,
-							"Cannot inform remote event listener about "
-									+ "event! Removing event listener!", e);
-					this.eventHandlers.remove(eventHandler);
-				}
-			}
-		}
-
-		// 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
-		Set<EventListener> copyOfEventHandlersForAllSources = new HashSet<EventListener>(
-				this.eventHandlersForAllSources);
-
-		for (EventListener eventHandler : copyOfEventHandlersForAllSources) {
-
-			this.logger.log(Level.FINE, "Informing event listener "
-					+ eventHandler + " about recently observed event " + event
-					+ " from source " + source + "!");
-			try {
-				eventHandler.handleEvent(event);
-			} catch (RemoteException e) {
-				this.logger.log(Level.WARNING,
-						"Cannot inform remote event listener about "
-								+ "event! Removing event listener!", e);
-				this.eventHandlers.remove(eventHandler);
-			}
-		}
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "observeEvent");
-	}
-
-	public synchronized void removeEventListener(EventListener eventListener) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "removeEventListener",
-				eventListener);
-
-		// check parameters
-		if (eventListener == null) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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 (Set<EventListener> set : eventHandlers.values()) {
-			if (set.remove(eventListener)) {
-				logger.log(Level.FINE, "Removed event listener!");
-			}
-		}
-
-		// remove from event listeners for all sources
-		if (this.eventHandlersForAllSources.remove(eventListener)) {
-			logger.log(Level.FINE, "Removed event listener!");
-		}
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "removeEventListener");
-	}
-
-	public synchronized void waitForAnyOccurence(String source, EventType event) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "waitForAnyOccurence",
-				new Object[] { source, event });
-
-		// check parameters
-		if (source == null || event == null
-				|| !this.eventSources.contains(source)) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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
-		this.logger.exiting(this.getClass().getName(), "waitForAnyOccurence");
-
-	}
-
-	public synchronized boolean waitForAnyOccurence(String source,
-			EventType event, long maximumTimeToWaitInMillis) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "waitForAnyOccurence",
-				new Object[] { source, event, maximumTimeToWaitInMillis });
-
-		// check parameters
-		if (source == null || event == null
-				|| !this.eventSources.contains(source)) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.logger.throwing(this.getClass().getName(),
-					"waitForAnyOccurence", e);
-			throw e;
-		}
-
-		// check if we have already observed the event
-		if (this.hasEventOccured(source, event)) {
-			this.logger.log(Level.FINE, "Waiting for any occurence of event "
-					+ event + " returned immediately!");
-			this.logger.exiting(this.getClass().getName(),
-					"waitForAnyOccurence", true);
-			return true;
-		}
-
-		// invoke method that waits for next occurence of the event
-		boolean result = waitForNextOccurence(source, event,
-				maximumTimeToWaitInMillis);
-
-		// log exiting and return result
-		this.logger.exiting(this.getClass().getName(), "waitForAnyOccurence",
-				result);
-		return result;
-	}
-
-	public synchronized void waitForNextOccurence(String source, EventType event) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "waitForNextOccurence",
-				new Object[] { source, event });
-
-		// check parameters
-		if (source == null || event == null
-				|| !this.eventSources.contains(source)) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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
-		this.logger.exiting(this.getClass().getName(), "waitForNextOccurence");
-	}
-
-	public synchronized boolean waitForNextOccurence(String source,
-			EventType type, long maximumTimeToWaitInMillis) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "waitForNextOccurence",
-				new Object[] { source, type, maximumTimeToWaitInMillis });
-
-		// check parameters
-		if (source == null || type == null
-				|| !this.eventSources.contains(source)) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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 (!this.hasEventOccured(source, type)) {
-
-				this.logger.log(Level.FINEST,
-						"We will wait infinetely for the next occurence of "
-								+ "event type " + type + " from source "
-								+ source + "...");
-				try {
-					wait();
-				} catch (InterruptedException e) {
-					// don't handle
-				}
-
-				this.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...");
-			}
-
-			this.logger.log(Level.FINE, "Waiting for occurence of event type "
-					+ type + " succeeded!");
-
-			// log exiting and return result
-			this.logger.exiting(this.getClass().getName(),
-					"waitForNextOccurence", true);
-			return true;
-
-		} else {
-
-			// wait for the given time at most
-			long endOfTime = System.currentTimeMillis()
-					+ maximumTimeToWaitInMillis;
-			long timeLeft = 0;
-			while (!this.hasEventOccured(source, type)
-					&& (timeLeft = endOfTime - System.currentTimeMillis()) > 0) {
-
-				this.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 (InterruptedException e) {
-					// don't handle
-				}
-
-				this.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
-			boolean result = this.hasEventOccured(source, type);
-
-			this.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
-			this.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(String source) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "addEventSource",
-				source);
-
-		// check if source name is unique in this network
-		if (this.eventSources.contains(source)) {
-			IllegalArgumentException e = new IllegalArgumentException(
-					"There is already an event source with name " + source
-							+ " in this network!");
-			this.logger
-					.throwing(this.getClass().getName(), "addEventSource", e);
-			throw e;
-		}
-
-		// add event source name
-		this.eventSources.add(source);
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "addEventSource");
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName();
-	}
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor.impl;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.EventType;
+import org.torproject.puppetor.HiddenServiceEventType;
+import org.torproject.puppetor.NodeEventType;
+
+
+/**
+ * Implementation of <code>EventManager</code>.
+ * 
+ * @author kloesing
+ */
+ at SuppressWarnings("serial")
+public class EventManagerImpl extends UnicastRemoteObject implements
+		EventManager {
+
+	/**
+	 * Registered event handlers for specific sources.
+	 */
+	private Map<String, Set<EventListener>> eventHandlers;
+
+	/**
+	 * Registered event handlers for all sources.
+	 */
+	private Set<EventListener> eventHandlersForAllSources;
+
+	/**
+	 * Logger for this event manager which is called "event." plus the name of
+	 * the network.
+	 */
+	private Logger logger;
+
+	/**
+	 * Events observed so far.
+	 */
+	private 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 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	EventManagerImpl(String networkName) throws RemoteException {
+
+		// check if networkName can be used as logger name
+		if (networkName == null || networkName.length() == 0) {
+			throw new IllegalArgumentException("Invalid networkName: "
+					+ networkName);
+		}
+
+		// create logger
+		this.logger = Logger.getLogger("event." + networkName);
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "EventManagerImpl",
+				networkName);
+
+		// create data structures
+		this.observedEvents = new HashMap<String, List<Event>>();
+		this.eventHandlers = new HashMap<String, Set<EventListener>>();
+		this.eventHandlersForAllSources = new HashSet<EventListener>();
+		this.eventSources = new HashSet<String>();
+
+		// start thread to parse events
+		Thread eventParseThread = new Thread() {
+			@Override
+			public void run() {
+				while (true) {
+					parseNextEvent();
+				}
+			}
+		};
+		eventParseThread.setDaemon(true);
+		eventParseThread.start();
+
+		// initialize event type patterns
+		initializeEventTypePatterns();
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "EventManagerImpl");
+	}
+
+	public synchronized List<Event> addEventListener(String source,
+			EventListener listener) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "addEventListener",
+				new Object[] { source, listener });
+
+		// check parameters
+		if (source == null || listener == null
+				|| !this.eventSources.contains(source)) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.logger.throwing(this.getClass().getName(), "addEventListener",
+					e);
+			throw e;
+		}
+
+		// if necessary, create new event listener set for source
+		if (!this.eventHandlers.containsKey(source)) {
+			this.eventHandlers.put(source, new HashSet<EventListener>());
+		}
+
+		// add listener
+		this.eventHandlers.get(source).add(listener);
+
+		// log change
+		this.logger
+				.log(Level.FINE, "Added event listener for source " + source);
+
+		// log exiting and return
+		List<Event> result = getEventHistory(source);
+		this.logger.exiting(this.getClass().getName(), "addEventListener",
+				result);
+		return result;
+	}
+
+	public synchronized void addEventListener(EventListener listener) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "addEventListener",
+				new Object[] { listener });
+
+		// check parameters
+		if (listener == null) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.logger.throwing(this.getClass().getName(), "addEventListener",
+					e);
+			throw e;
+		}
+
+		// add listener
+		this.eventHandlersForAllSources.add(listener);
+
+		// log change
+		this.logger.log(Level.FINE, "Added event listener for all sources.");
+
+		// log exiting and return
+		this.logger.exiting(this.getClass().getName(), "addEventListener");
+		return;
+	}
+
+	public synchronized List<Event> getEventHistory(String source) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "getEventHistory",
+				source);
+
+		// check parameter
+		if (source == null || !this.eventSources.contains(source)) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.logger.throwing(this.getClass().getName(), "getEventHistory",
+					e);
+			throw e;
+		}
+
+		// prepare result
+		List<Event> result = new ArrayList<Event>();
+
+		// did we already observe events for this source?
+		if (this.observedEvents.containsKey(source)) {
+			// yes, add all events to result list
+			result.addAll(this.observedEvents.get(source));
+		}
+
+		// log exiting and return result
+		this.logger.exiting(this.getClass().getName(), "getEventHistory",
+				result);
+		return result;
+	}
+
+	public synchronized boolean hasEventOccured(String source, EventType type) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "hasEventOccured",
+				new Object[] { source, type });
+
+		// check parameters
+		if (source == null || type == null
+				|| !this.eventSources.contains(source)) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.logger.throwing(this.getClass().getName(), "hasEventOccured",
+					e);
+			throw e;
+		}
+
+		// determine result
+		boolean result = false;
+		if (this.observedEvents.containsKey(source)) {
+			for (Event event : this.observedEvents.get(source)) {
+				if (event.getType() == type) {
+					result = true;
+					break;
+				}
+			}
+		}
+
+		// log exiting and return result
+		this.logger.exiting(this.getClass().getName(), "hasEventOccured",
+				result);
+		return result;
+	}
+
+	/**
+	 * An ordered list of all log statements that are still unparsed.
+	 */
+	private 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(String source, String logMessage) {
+		this.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.
+	 */
+	synchronized void observeInternalEvent(long occurrenceTime, String source,
+			EventType type, String message) {
+
+		if (!this.eventSources.contains(source)) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.logger.throwing(this.getClass().getName(),
+					"observeInternalEvent", e);
+			throw e;
+		}
+
+		this.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 (this.unparsedLogStatements.isEmpty()) {
+				try {
+					wait();
+				} catch (InterruptedException e) {
+				}
+			}
+			event = this.unparsedLogStatements.remove(0);
+		}
+
+		// does the event contain a known source? if not, discard it
+		if (!this.eventSources.contains(event.getSource())) {
+			this.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 {
+			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...
+			 */
+			Calendar c = Calendar.getInstance();
+			int currentYear = c.get(Calendar.YEAR);
+
+			// try to apply one of the event type patterns
+			for (Entry<Pattern, EventType> entry : eventTypePatterns.entrySet()) {
+				Matcher matcher = entry.getKey().matcher(line);
+				if (matcher.find()) {
+					SimpleDateFormat sdf = new SimpleDateFormat(
+							"MMM dd HH:mm:ss.SSS", Locale.US);
+					Date logTime = sdf.parse(line, new ParsePosition(0));
+					c.setTimeInMillis(logTime.getTime());
+					c.set(Calendar.YEAR, currentYear);
+					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(String patternString,
+			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(Event event) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "observeEvent", event);
+
+		String source = event.getSource();
+		this.logger.log(Level.FINE, "Observed event " + event + " from source "
+				+ source + "!");
+
+		// remember observed event
+		if (!this.observedEvents.containsKey(event.getSource())) {
+			this.observedEvents.put(source, new ArrayList<Event>());
+		}
+		this.observedEvents.get(source).add(event);
+
+		// notify waiting threads
+		notifyAll();
+
+		// inform event listeners
+		if (this.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
+			Set<EventListener> copyOfEventHandlers = new HashSet<EventListener>(
+					this.eventHandlers.get(source));
+
+			for (EventListener eventHandler : copyOfEventHandlers) {
+
+				this.logger.log(Level.FINE, "Informing event listener "
+						+ eventHandler + " about recently observed event "
+						+ event + " from source " + source + "!");
+				try {
+					eventHandler.handleEvent(event);
+				} catch (RemoteException e) {
+					this.logger.log(Level.WARNING,
+							"Cannot inform remote event listener about "
+									+ "event! Removing event listener!", e);
+					this.eventHandlers.remove(eventHandler);
+				}
+			}
+		}
+
+		// 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
+		Set<EventListener> copyOfEventHandlersForAllSources = new HashSet<EventListener>(
+				this.eventHandlersForAllSources);
+
+		for (EventListener eventHandler : copyOfEventHandlersForAllSources) {
+
+			this.logger.log(Level.FINE, "Informing event listener "
+					+ eventHandler + " about recently observed event " + event
+					+ " from source " + source + "!");
+			try {
+				eventHandler.handleEvent(event);
+			} catch (RemoteException e) {
+				this.logger.log(Level.WARNING,
+						"Cannot inform remote event listener about "
+								+ "event! Removing event listener!", e);
+				this.eventHandlers.remove(eventHandler);
+			}
+		}
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "observeEvent");
+	}
+
+	public synchronized void removeEventListener(EventListener eventListener) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "removeEventListener",
+				eventListener);
+
+		// check parameters
+		if (eventListener == null) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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 (Set<EventListener> set : eventHandlers.values()) {
+			if (set.remove(eventListener)) {
+				logger.log(Level.FINE, "Removed event listener!");
+			}
+		}
+
+		// remove from event listeners for all sources
+		if (this.eventHandlersForAllSources.remove(eventListener)) {
+			logger.log(Level.FINE, "Removed event listener!");
+		}
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "removeEventListener");
+	}
+
+	public synchronized void waitForAnyOccurence(String source, EventType event) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "waitForAnyOccurence",
+				new Object[] { source, event });
+
+		// check parameters
+		if (source == null || event == null
+				|| !this.eventSources.contains(source)) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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
+		this.logger.exiting(this.getClass().getName(), "waitForAnyOccurence");
+
+	}
+
+	public synchronized boolean waitForAnyOccurence(String source,
+			EventType event, long maximumTimeToWaitInMillis) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "waitForAnyOccurence",
+				new Object[] { source, event, maximumTimeToWaitInMillis });
+
+		// check parameters
+		if (source == null || event == null
+				|| !this.eventSources.contains(source)) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.logger.throwing(this.getClass().getName(),
+					"waitForAnyOccurence", e);
+			throw e;
+		}
+
+		// check if we have already observed the event
+		if (this.hasEventOccured(source, event)) {
+			this.logger.log(Level.FINE, "Waiting for any occurence of event "
+					+ event + " returned immediately!");
+			this.logger.exiting(this.getClass().getName(),
+					"waitForAnyOccurence", true);
+			return true;
+		}
+
+		// invoke method that waits for next occurence of the event
+		boolean result = waitForNextOccurence(source, event,
+				maximumTimeToWaitInMillis);
+
+		// log exiting and return result
+		this.logger.exiting(this.getClass().getName(), "waitForAnyOccurence",
+				result);
+		return result;
+	}
+
+	public synchronized void waitForNextOccurence(String source, EventType event) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "waitForNextOccurence",
+				new Object[] { source, event });
+
+		// check parameters
+		if (source == null || event == null
+				|| !this.eventSources.contains(source)) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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
+		this.logger.exiting(this.getClass().getName(), "waitForNextOccurence");
+	}
+
+	public synchronized boolean waitForNextOccurence(String source,
+			EventType type, long maximumTimeToWaitInMillis) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "waitForNextOccurence",
+				new Object[] { source, type, maximumTimeToWaitInMillis });
+
+		// check parameters
+		if (source == null || type == null
+				|| !this.eventSources.contains(source)) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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 (!this.hasEventOccured(source, type)) {
+
+				this.logger.log(Level.FINEST,
+						"We will wait infinetely for the next occurence of "
+								+ "event type " + type + " from source "
+								+ source + "...");
+				try {
+					wait();
+				} catch (InterruptedException e) {
+					// don't handle
+				}
+
+				this.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...");
+			}
+
+			this.logger.log(Level.FINE, "Waiting for occurence of event type "
+					+ type + " succeeded!");
+
+			// log exiting and return result
+			this.logger.exiting(this.getClass().getName(),
+					"waitForNextOccurence", true);
+			return true;
+
+		} else {
+
+			// wait for the given time at most
+			long endOfTime = System.currentTimeMillis()
+					+ maximumTimeToWaitInMillis;
+			long timeLeft = 0;
+			while (!this.hasEventOccured(source, type)
+					&& (timeLeft = endOfTime - System.currentTimeMillis()) > 0) {
+
+				this.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 (InterruptedException e) {
+					// don't handle
+				}
+
+				this.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
+			boolean result = this.hasEventOccured(source, type);
+
+			this.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
+			this.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(String source) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "addEventSource",
+				source);
+
+		// check if source name is unique in this network
+		if (this.eventSources.contains(source)) {
+			IllegalArgumentException e = new IllegalArgumentException(
+					"There is already an event source with name " + source
+							+ " in this network!");
+			this.logger
+					.throwing(this.getClass().getName(), "addEventSource", e);
+			throw e;
+		}
+
+		// add event source name
+		this.eventSources.add(source);
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "addEventSource");
+	}
+
+	@Override
+	public String toString() {
+		return this.getClass().getSimpleName();
+	}
+}
diff --git a/src/org/torproject/puppetor/impl/NetworkImpl.java b/src/org/torproject/puppetor/impl/NetworkImpl.java
old mode 100755
new mode 100644
index fb96c10..4dd3ba9
--- a/src/org/torproject/puppetor/impl/NetworkImpl.java
+++ b/src/org/torproject/puppetor/impl/NetworkImpl.java
@@ -1,1165 +1,1165 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor.impl;
-
-import java.io.File;
-import java.net.MalformedURLException;
-import java.rmi.Naming;
-import java.rmi.RMISecurityManager;
-import java.rmi.RemoteException;
-import java.rmi.server.UnicastRemoteObject;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.torproject.puppetor.ClientApplication;
-import org.torproject.puppetor.DirectoryNode;
-import org.torproject.puppetor.Event;
-import org.torproject.puppetor.EventListener;
-import org.torproject.puppetor.EventManager;
-import org.torproject.puppetor.Network;
-import org.torproject.puppetor.NodeEventType;
-import org.torproject.puppetor.NodeState;
-import org.torproject.puppetor.ProxyNode;
-import org.torproject.puppetor.PuppeTorException;
-import org.torproject.puppetor.RouterNode;
-import org.torproject.puppetor.ServerApplication;
-
-
-/**
- * Implementation of <code>Network</code>.
- * 
- * @author kloesing
- */
- at SuppressWarnings("serial")
-public class NetworkImpl extends UnicastRemoteObject 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 long maximumTimeToWaitInMillis;
-
-		/**
-		 * The node for which the Tor process shall be started.
-		 */
-		private 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(ProxyNode node, 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
-				this.success = this.node
-						.startNode(this.maximumTimeToWaitInMillis);
-			} catch (PuppeTorException e) {
-				logger.log(Level.SEVERE,
-						"Caught an exception while starting node "
-								+ node.toString() + "!");
-				this.caughtException = e;
-			} catch (RemoteException e) {
-				logger.log(Level.SEVERE,
-						"Caught a remote exception while starting node "
-								+ node.toString() + "!");
-				this.caughtException = e;
-			}
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "run");
-		}
-	}
-
-	/**
-	 * Event manager to which all events concerning this network are notified.
-	 */
-	private EventManagerImpl eventManager;
-
-	/**
-	 * Logger for this network which is called "network." plus the name of this
-	 * network.
-	 */
-	private Logger logger;
-
-	/**
-	 * Contains the name of this network configuration which is the String
-	 * conversion of System.currentTimeMillis() of the network creation time.
-	 */
-	private String networkName;
-
-	/**
-	 * All nodes contained in this network.
-	 */
-	private Map<String, ProxyNode> nodes;
-
-	/**
-	 * Directory that contains status information of all nodes contained in this
-	 * network.
-	 */
-	private 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>.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public NetworkImpl(String networkName, int startPort)
-			throws RemoteException {
-
-		// 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
-		this.portCounter = startPort;
-
-		// log exiting
-		this.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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	public NetworkImpl(String networkName) throws RemoteException {
-
-		// check if networkName can be used as logger name
-		if (networkName == null || networkName.length() == 0) {
-			throw new IllegalArgumentException("Invalid networkName: "
-					+ networkName);
-		}
-
-		// create logger
-		this.logger = Logger.getLogger("network." + networkName);
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "NetworkImpl",
-				networkName);
-
-		// TODO is this necessary?!
-		this.logger.setLevel(Level.ALL);
-
-		// remember parameter
-		this.networkName = networkName;
-
-		// create working directory
-		this.workingDir = new File("test-env/" + System.currentTimeMillis());
-		this.workingDir.mkdirs();
-		this.logger.log(Level.FINE, "Created working directory \""
-				+ this.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
-		this.nodes = new HashMap<String, ProxyNode>();
-
-		// create event manager
-		this.eventManager = new EventManagerImpl(this.networkName);
-
-		// log exiting
-		this.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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	private boolean allNodesUp() throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "allNodesUp");
-
-		// fail on first node that is not up
-		for (ProxyNode node : this.nodes.values()) {
-			if (!eventManager.hasEventOccured(node.getNodeName(),
-					NodeEventType.NODE_CIRCUIT_OPENED)) {
-
-				// log exiting and return false
-				this.logger.exiting(this.getClass().getName(), "allNodesUp");
-				return false;
-			}
-		}
-
-		// log exiting and return true
-		this.logger.exiting(this.getClass().getName(), "allNodesUp");
-		return true;
-	}
-
-	public void configureAsPrivateNetwork() throws PuppeTorException,
-			RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(),
-				"configureAsPrivateNetwork");
-
-		for (ProxyNode node : this.nodes.values()) {
-			if (node.getNodeState() == NodeState.CONFIGURING) {
-				// add to configuration
-				node.addConfiguration("TestingTorNetwork 1");
-			}
-		}
-
-		// read DirServer strings for all directories
-		List<String> authorizedDirectoriesFingerprints = new ArrayList<String>();
-		for (ProxyNode node : this.nodes.values()) {
-			if (node instanceof DirectoryNode) {
-				DirectoryNode dirNode = (DirectoryNode) node;
-				authorizedDirectoriesFingerprints.add(dirNode
-						.getDirServerString());
-			}
-		}
-
-		// configure nodes
-		for (ProxyNode node : this.nodes.values()) {
-			if (node.getNodeState() == NodeState.CONFIGURING) {
-
-				// add to configuration
-				node.addConfigurations(authorizedDirectoriesFingerprints);
-			}
-		}
-
-		// read fingerprints for all directories and routers
-		HashSet<String> approvedRoutersFingerprints = new HashSet<String>();
-		for (ProxyNode node : this.nodes.values()) {
-			if (node instanceof RouterNode) {
-				RouterNode routerOrDirNode = (RouterNode) node;
-				approvedRoutersFingerprints.add(routerOrDirNode
-						.getFingerprint());
-			}
-		}
-
-		// write fingerprints for all directories and routers to the
-		// approved-routers file
-		for (ProxyNode node : this.nodes.values()) {
-			if (node instanceof DirectoryNode) {
-				DirectoryNode dirNode = (DirectoryNode) node;
-				dirNode.addApprovedRouters(approvedRoutersFingerprints);
-			}
-		}
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(),
-				"configureAsPrivateNetwork");
-	}
-
-	public ClientApplication createClient(String clientApplicationName,
-			String targetAddress, int targetPort, int socksPort)
-			throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "createClient",
-				new Object[] { clientApplicationName, targetAddress,
-						targetPort, socksPort });
-
-		// create client; parameter checking is done in constructor
-		ClientApplicationImpl client = new ClientApplicationImpl(this,
-				clientApplicationName, targetAddress, targetPort, socksPort);
-
-		// add name to event manager as event source
-		this.eventManager.addEventSource(clientApplicationName);
-
-		// log exiting and return client
-		this.logger.exiting(this.getClass().getName(), "createClient", client);
-		return client;
-	}
-
-	public DirectoryNode createDirectory(String nodeName, int controlPort,
-			int socksPort, int orPort, int dirPort, String serverIpAddress)
-			throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "createDirectory",
-				new Object[] { nodeName, controlPort, socksPort, orPort,
-						dirPort, serverIpAddress });
-
-		// create directory node; parameter checking is done in constructor
-		DirectoryNode dir = new DirectoryNodeImpl(this, nodeName, controlPort,
-				socksPort, orPort, dirPort, serverIpAddress);
-
-		// add new directory node to nodes collection
-		this.nodes.put(nodeName, dir);
-
-		// add name to event manager as event source
-		this.eventManager.addEventSource(nodeName);
-
-		// log exiting and return directory node
-		this.logger.exiting(this.getClass().getName(), "createDirectory", dir);
-		return dir;
-	}
-
-	public DirectoryNode createDirectory(String nodeName, int controlPort,
-			int socksPort, int orPort, int dirPort) throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "createDirectory",
-				new Object[] { nodeName, controlPort, socksPort, orPort,
-						dirPort });
-
-		// invoke overloaded method
-		DirectoryNode dir = this.createDirectory(nodeName, controlPort,
-				socksPort, orPort, dirPort, "127.0.0.1");
-
-		// log exiting and return directory node
-		this.logger.exiting(this.getClass().getName(), "createDirectory", dir);
-		return dir;
-	}
-
-	public DirectoryNode createDirectory(String nodeName, String serverIpAddress)
-			throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "createDirectory",
-				new Object[] { nodeName, serverIpAddress });
-
-		// invoke overloaded method
-		DirectoryNode dir = this.createDirectory(nodeName, this.portCounter++,
-				this.portCounter++, this.portCounter++, this.portCounter++,
-				serverIpAddress);
-
-		// log exiting and return directory node
-		this.logger.exiting(this.getClass().getName(), "createDirectory", dir);
-		return dir;
-	}
-
-	public DirectoryNode createDirectory(String nodeName)
-			throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "createDirectory",
-				nodeName);
-
-		// invoke overloaded method
-		DirectoryNode dir = this.createDirectory(nodeName, this.portCounter++,
-				this.portCounter++, this.portCounter++, this.portCounter++,
-				"127.0.0.1");
-
-		// log exiting and return directory node
-		this.logger.exiting(this.getClass().getName(), "createDirectory", dir);
-		return dir;
-	}
-
-	public ProxyNode createProxy(String nodeName, int controlPort, int socksPort)
-			throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "createProxy",
-				new Object[] { nodeName, controlPort, socksPort });
-
-		// create proxy node; parameter checking is done in constructor
-		ProxyNode proxy = new ProxyNodeImpl(this, nodeName, controlPort,
-				socksPort);
-
-		// add new proxy node to nodes collection
-		this.nodes.put(nodeName, proxy);
-
-		// add name to event manager as event source
-		this.eventManager.addEventSource(nodeName);
-
-		// log exiting and return proxy node
-		this.logger.exiting(this.getClass().getName(), "createProxy", proxy);
-		return proxy;
-	}
-
-	public ProxyNode createProxy(String nodeName) throws RemoteException {
-
-		// log entering
-		this.logger
-				.entering(this.getClass().getName(), "createProxy", nodeName);
-
-		// invoke overloaded method
-		ProxyNode proxy = this.createProxy(nodeName, this.portCounter++,
-				this.portCounter++);
-
-		// log exiting and return proxy node
-		this.logger.exiting(this.getClass().getName(), "createProxy", proxy);
-		return proxy;
-	}
-
-	public RouterNode createRouter(String nodeName, int controlPort,
-			int socksPort, int orPort, int dirPort, String serverIpAddress)
-			throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "createRouter",
-				new Object[] { nodeName, controlPort, socksPort, orPort,
-						dirPort, serverIpAddress });
-
-		// create router node; parameter checking is done in constructor
-		RouterNode router = new RouterNodeImpl(this, nodeName, controlPort,
-				socksPort, orPort, dirPort, serverIpAddress);
-
-		// add new router node to nodes collection
-		this.nodes.put(nodeName, router);
-
-		// add name to event manager as event source
-		this.eventManager.addEventSource(nodeName);
-
-		// log exiting and return router node
-		this.logger.exiting(this.getClass().getName(), "createRouter", router);
-		return router;
-	}
-
-	public RouterNode createRouter(String nodeName, int controlPort,
-			int socksPort, int orPort, int dirPort) throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "createRouter",
-				new Object[] { nodeName, controlPort, socksPort, orPort,
-						dirPort });
-
-		// invoke overloaded method
-		DirectoryNode dir = this.createDirectory(nodeName, controlPort,
-				socksPort, orPort, dirPort, "127.0.0.1");
-
-		// log exiting and return directory node
-		this.logger.exiting(this.getClass().getName(), "createRouter", dir);
-		return dir;
-	}
-
-	public RouterNode createRouter(String nodeName, String serverIpAddress)
-			throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "createRouter",
-				new Object[] { nodeName, serverIpAddress });
-
-		// invoke overloaded method
-		RouterNode dir = this.createRouter(nodeName, this.portCounter++,
-				this.portCounter++, this.portCounter++, this.portCounter++,
-				serverIpAddress);
-
-		// log exiting and return directory node
-		this.logger.exiting(this.getClass().getName(), "createRouter", dir);
-		return dir;
-	}
-
-	public RouterNode createRouter(String nodeName) throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "createRouter",
-				nodeName);
-
-		// invoke overloaded method
-		RouterNode router = this.createRouter(nodeName, this.portCounter++,
-				this.portCounter++, this.portCounter++, this.portCounter++,
-				"127.0.0.1");
-
-		// log exiting and return router node
-		this.logger.exiting(this.getClass().getName(), "createRouter", router);
-		return router;
-	}
-
-	public ServerApplication createServer(String serverApplicationName,
-			int serverPort) throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "createServer",
-				new Object[] { serverApplicationName, serverPort });
-
-		// create server; parameter checking is done in constructor
-		ServerApplicationImpl server = new ServerApplicationImpl(this,
-				serverApplicationName, serverPort);
-
-		// add name to event manager as event source
-		this.eventManager.addEventSource(serverApplicationName);
-
-		// log exiting and return server
-		this.logger.exiting(this.getClass().getName(), "createServer", server);
-		return server;
-	}
-
-	public ServerApplication createServer(String serverApplicationName)
-			throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "createServer",
-				serverApplicationName);
-
-		// invoke overloaded method
-		ServerApplication server = this.createServer(serverApplicationName,
-				this.portCounter++);
-
-		// log exiting and return server
-		this.logger.exiting(this.getClass().getName(), "createServer", server);
-		return server;
-	}
-
-	public EventManager getEventManager() {
-		return this.eventManager;
-	}
-
-	/**
-	 * Returns the implementation instance of the event manager of this network.
-	 * 
-	 * @return The implementation instance of the event manager of this network.
-	 */
-	EventManagerImpl getEventManagerImpl() {
-		return this.eventManager;
-	}
-
-	public File getWorkingDirectory() {
-		return this.workingDir;
-	}
-
-	public ProxyNode getNode(String nodeName) {
-		return this.nodes.get(nodeName);
-	}
-
-	public Map<String, ProxyNode> getAllProxyNodes() {
-		Map<String, ProxyNode> result = new HashMap<String, ProxyNode>();
-		for (String nodeName : this.nodes.keySet()) {
-			ProxyNode node = this.nodes.get(nodeName);
-			if (!(node instanceof RouterNode)) {
-				result.put(nodeName, node);
-			}
-		}
-		return result;
-	}
-
-	public Map<String, RouterNode> getAllRouterNodes() {
-		Map<String, RouterNode> result = new HashMap<String, RouterNode>();
-		for (String nodeName : this.nodes.keySet()) {
-			ProxyNode node = this.nodes.get(nodeName);
-			if (node instanceof RouterNode && !(node instanceof DirectoryNode)) {
-				result.put(nodeName, (RouterNode) node);
-			}
-		}
-		return result;
-	}
-
-	public Map<String, DirectoryNode> getAllDirectoryNodes() {
-		Map<String, DirectoryNode> result = new HashMap<String, DirectoryNode>();
-		for (String nodeName : this.nodes.keySet()) {
-			ProxyNode node = this.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(int tries, long hupInterval)
-			throws PuppeTorException, RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "hupUntilUp",
-				new Object[] { tries, hupInterval });
-
-		// check if all nodes are running
-		for (ProxyNode node : this.nodes.values()) {
-			if (node.getNodeState() != NodeState.RUNNING) {
-				IllegalStateException e = new IllegalStateException(
-						"All nodes must be running before sending them HUP "
-								+ "commands!");
-				this.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
-			this.logger.exiting(this.getClass().getName(), "hupUntilUp", true);
-			return true;
-		}
-
-		// create and register a new event handler for each node
-		final Thread sleepingThread = Thread.currentThread();
-		for (ProxyNode node : this.nodes.values()) {
-			eventManager.addEventListener(node.getNodeName(),
-					new EventListener() {
-						public void handleEvent(Event event) {
-							if (event.getType() == NodeEventType.NODE_CIRCUIT_OPENED) {
-								sleepingThread.interrupt();
-								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 sleep
-			long endOfSleeping = System.currentTimeMillis() + hupInterval;
-			long now;
-
-			// unless all nodes have reported to be up, wait for the given
-			// maximum time
-			while ((now = System.currentTimeMillis()) < endOfSleeping) {
-
-				// sleep
-				try {
-					Thread.sleep(endOfSleeping - now);
-				} catch (InterruptedException e) {
-					// do nothing about it
-				}
-
-				// check if nodes are up now
-				if (allNodesUp()) {
-
-					// log exiting and return true
-					this.logger.exiting(this.getClass().getName(),
-							"hupUntilUp", true);
-					return true;
-				}
-			}
-
-			this.logger.log(Level.FINE, "Sending HUP to nodes");
-			// send a HUP signal to all nodes
-			for (ProxyNode node : this.nodes.values()) {
-				this.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
-		this.logger.exiting(this.getClass().getName(), "hupUntilUp", false);
-		return false;
-	}
-
-	public void hupAllNodes() throws PuppeTorException, RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "hupAllNodes");
-
-		// check if all nodes are running
-		for (ProxyNode node : this.nodes.values()) {
-			if (node.getNodeState() != NodeState.RUNNING) {
-				IllegalStateException e = new IllegalStateException(
-						"All nodes must be running before sending them HUP "
-								+ "commands!");
-				this.logger.throwing(this.getClass().getName(), "hupAllNodes",
-						e);
-				throw e;
-			}
-		}
-
-		// send a HUP signal to all nodes
-		for (ProxyNode node : this.nodes.values()) {
-			this.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
-		this.logger.exiting(this.getClass().getName(), "hupAllNodes");
-	}
-
-	public void hupAllDirectories() throws PuppeTorException, RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "hupAllDirectories");
-
-		// check if all directory nodes are running
-		for (ProxyNode node : this.nodes.values()) {
-			if (node instanceof DirectoryNode
-					&& node.getNodeState() != NodeState.RUNNING) {
-				IllegalStateException e = new IllegalStateException(
-						"All directory nodes must be running before sending "
-								+ "them HUP commands!");
-				this.logger.throwing(this.getClass().getName(),
-						"hupAllDirectories", e);
-				throw e;
-			}
-		}
-
-		// send a HUP signal to all nodes
-		for (ProxyNode node : this.nodes.values()) {
-			if (node instanceof DirectoryNode) {
-				this.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
-		this.logger.exiting(this.getClass().getName(), "hupAllDirectories");
-	}
-
-	public void shutdownNodes() throws PuppeTorException, RemoteException {
-
-		// log entering
-		this.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 (ProxyNode node : this.nodes.values()) {
-			if (node.getNodeState() == NodeState.RUNNING) {
-				try {
-					node.shutdown();
-				} catch (PuppeTorException e) {
-					if (firstCaughtException == null) {
-						firstCaughtException = e;
-					}
-				}
-			}
-		}
-
-		// if an exception was caught during shutting down nodes, throw the
-		// first caught exception
-		if (firstCaughtException != null) {
-			this.logger.throwing(this.getClass().getName(), "shutdownNodes",
-					firstCaughtException);
-			throw firstCaughtException;
-		}
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "shutdownNodes");
-	}
-
-	public boolean startNodes(long maximumTimeToWaitInMillis)
-			throws PuppeTorException, RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "startNodes",
-				maximumTimeToWaitInMillis);
-
-		// check node states
-		for (ProxyNode node : this.nodes.values()) {
-			if (node.getNodeState() != NodeState.CONFIGURATION_WRITTEN) {
-				IllegalStateException e = new IllegalStateException(
-						"All configurations must be written before starting "
-								+ "nodes!");
-				this.logger
-						.throwing(this.getClass().getName(), "startNodes", e);
-				throw e;
-			}
-		}
-
-		// check parameter
-		if (maximumTimeToWaitInMillis < 0) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.logger.throwing(this.getClass().getName(), "startNodes", e);
-			throw e;
-		}
-
-		// remember time when we begin starting the nodes
-		long before = System.currentTimeMillis();
-
-		// start nodes in parallel
-		Set<NodeStarter> allNodeStarters = new HashSet<NodeStarter>();
-		for (ProxyNode node : this.nodes.values()) {
-			NodeStarter nodeStarter = new NodeStarter(node,
-					maximumTimeToWaitInMillis);
-			allNodeStarters.add(nodeStarter);
-			nodeStarter.start();
-		}
-
-		// wait for all node starts to complete
-		for (NodeStarter nodeStarter : allNodeStarters) {
-
-			// join node starts one after the other
-			try {
-				nodeStarter.join();
-			} catch (InterruptedException e) {
-				// this happens?! we have some kind of problem here!
-				this.logger.log(Level.WARNING,
-						"Interrupt while joining node starter!");
-
-				// log exiting and return false
-				this.logger.exiting(this.getClass().getName(), "startNodes",
-						false);
-				return false;
-			}
-
-			// if any thread has caught an exception, throw that exception now
-			Exception caughtException = nodeStarter.caughtException;
-			if (caughtException != null) {
-				PuppeTorException ex = new PuppeTorException(
-						"Exception while starting node "
-								+ nodeStarter.node.getNodeName(),
-						caughtException);
-				this.logger.throwing(this.getClass().getName(), "startNodes",
-						ex);
-				throw ex;
-			}
-
-			// if node start did not succeed in the given time, fail
-			if (!nodeStarter.success) {
-				this.logger.log(Level.WARNING,
-						"Starting nodes was not successful in "
-								+ (maximumTimeToWaitInMillis / 1000)
-								+ " seconds.", this.networkName);
-
-				// log exiting and return false
-				this.logger.exiting(this.getClass().getName(), "startNodes",
-						false);
-				return false;
-			}
-		}
-
-		// determine how long we took to start all nodes
-		long after = System.currentTimeMillis();
-		this.logger.log(Level.FINE, "Starting nodes was successful and took "
-				+ ((after - before) / 1000) + " seconds.", this.networkName);
-
-		// log exiting and return true
-		this.logger.exiting(this.getClass().getName(), "startNodes", true);
-		return true;
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": networkName=\""
-				+ this.networkName;
-	}
-
-	public String getNetworkName() {
-		return this.networkName;
-	}
-
-	public void writeConfigurations() throws PuppeTorException, RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "writeConfigurations");
-
-		// write configurations for all nodes
-		for (ProxyNode node : this.nodes.values()) {
-			node.writeConfiguration();
-		}
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "writeConfigurations");
-	}
-
-	public void configureAsInterconnectedPrivateNetwork(Network remoteNetwork)
-			throws PuppeTorException, RemoteException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "mergeNetworks",
-				remoteNetwork);
-
-		// collect dir strings from this and the remote network
-		List<String> ourDirServerStrings = new ArrayList<String>();
-		for (DirectoryNode directory : this.getAllDirectoryNodes().values()) {
-			ourDirServerStrings.add(directory.getDirServerString());
-		}
-		List<String> remoteDirServerStrings = new ArrayList<String>();
-		for (DirectoryNode directory : remoteNetwork.getAllDirectoryNodes()
-				.values()) {
-			remoteDirServerStrings.add(directory.getDirServerString());
-		}
-
-		// add dir strings of local directories to all nodes of the other
-		// network and vice versa
-		for (ProxyNode node : remoteNetwork.getAllNodes().values()) {
-			node.addConfigurations(ourDirServerStrings);
-		}
-		for (ProxyNode node : this.getAllNodes().values()) {
-			node.addConfigurations(remoteDirServerStrings);
-		}
-
-		// collect router fingerprints from all routers in this and the remote
-		// network
-		Set<String> ourApprovedRoutersStrings = new TreeSet<String>();
-		for (RouterNode router : this.getAllRouterNodes().values()) {
-			ourApprovedRoutersStrings.add(router.getFingerprint());
-		}
-		Set<String> remoteApprovedRoutersStrings = new TreeSet<String>();
-		for (RouterNode router : remoteNetwork.getAllRouterNodes().values()) {
-			remoteApprovedRoutersStrings.add(router.getFingerprint());
-		}
-
-		// add the fingerprints of local routers and directories to the
-		// directories of the other network and vice versa
-		for (DirectoryNode node : remoteNetwork.getAllDirectoryNodes().values()) {
-			node.addApprovedRouters(ourApprovedRoutersStrings);
-		}
-		for (DirectoryNode node : this.getAllDirectoryNodes().values()) {
-			node.addApprovedRouters(remoteApprovedRoutersStrings);
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "mergeNetworks");
-	}
-
-	public void addTemplateConfiguration(Class<? extends ProxyNode> nodeClass,
-			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(" ")) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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 {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.logger.throwing(this.getClass().getName(),
-					"addTemplateConfiguration", e);
-			throw e;
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "addTemplateConfiguration");
-	}
-
-	public List<String> getTemplateConfiguration(
-			Class<? extends ProxyNode> nodeClass) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "getTemplateConfiguration",
-				nodeClass);
-
-		// check parameter
-		if (nodeClass == null) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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 {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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(
-			Class<? extends ProxyNode> nodeClass,
-			String templateConfigurationKey) {
-
-		// log entering
-		logger.entering(this.getClass().getName(),
-				"removeTemplateConfiguration", new Object[] { nodeClass,
-						templateConfigurationKey });
-
-		// check parameters
-		if (nodeClass == null || templateConfigurationKey == null
-				|| templateConfigurationKey.length() < 1) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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 {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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
-		List<String> configurationStringsToRemove = new ArrayList<String>();
-		for (String currentConfigurationString : templateConfig) {
-			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");
-	}
-
-	public boolean bindAtRmiregistry() throws RemoteException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "bindAtRmiregistry");
-
-		// set the RMISecurityManager
-		System.setSecurityManager(new RMISecurityManager());
-
-		// bind the network to the rmiregistry
-		try {
-			Naming.rebind("//127.0.0.1/" + networkName, this);
-		} catch (MalformedURLException e) {
-			this.logger.log(Level.WARNING,
-					"URL to bind this network to is malformed!", e);
-			logger.exiting(this.getClass().getName(), "bindAtRmiregistry",
-					false);
-			return false;
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "bindAtRmiregistry", true);
-		return true;
-	}
-
-	/**
-	 * Returns the current port number and increments it afterwards.
-	 * 
-	 * @return The current port number.
-	 */
-	int getNextPortNumber() {
-		return this.portCounter++;
-	}
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor.impl;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.rmi.Naming;
+import java.rmi.RMISecurityManager;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.torproject.puppetor.ClientApplication;
+import org.torproject.puppetor.DirectoryNode;
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NodeEventType;
+import org.torproject.puppetor.NodeState;
+import org.torproject.puppetor.ProxyNode;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.RouterNode;
+import org.torproject.puppetor.ServerApplication;
+
+
+/**
+ * Implementation of <code>Network</code>.
+ * 
+ * @author kloesing
+ */
+ at SuppressWarnings("serial")
+public class NetworkImpl extends UnicastRemoteObject 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 long maximumTimeToWaitInMillis;
+
+		/**
+		 * The node for which the Tor process shall be started.
+		 */
+		private 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(ProxyNode node, 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
+				this.success = this.node
+						.startNode(this.maximumTimeToWaitInMillis);
+			} catch (PuppeTorException e) {
+				logger.log(Level.SEVERE,
+						"Caught an exception while starting node "
+								+ node.toString() + "!");
+				this.caughtException = e;
+			} catch (RemoteException e) {
+				logger.log(Level.SEVERE,
+						"Caught a remote exception while starting node "
+								+ node.toString() + "!");
+				this.caughtException = e;
+			}
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "run");
+		}
+	}
+
+	/**
+	 * Event manager to which all events concerning this network are notified.
+	 */
+	private EventManagerImpl eventManager;
+
+	/**
+	 * Logger for this network which is called "network." plus the name of this
+	 * network.
+	 */
+	private Logger logger;
+
+	/**
+	 * Contains the name of this network configuration which is the String
+	 * conversion of System.currentTimeMillis() of the network creation time.
+	 */
+	private String networkName;
+
+	/**
+	 * All nodes contained in this network.
+	 */
+	private Map<String, ProxyNode> nodes;
+
+	/**
+	 * Directory that contains status information of all nodes contained in this
+	 * network.
+	 */
+	private 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>.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public NetworkImpl(String networkName, int startPort)
+			throws RemoteException {
+
+		// 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
+		this.portCounter = startPort;
+
+		// log exiting
+		this.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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	public NetworkImpl(String networkName) throws RemoteException {
+
+		// check if networkName can be used as logger name
+		if (networkName == null || networkName.length() == 0) {
+			throw new IllegalArgumentException("Invalid networkName: "
+					+ networkName);
+		}
+
+		// create logger
+		this.logger = Logger.getLogger("network." + networkName);
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "NetworkImpl",
+				networkName);
+
+		// TODO is this necessary?!
+		this.logger.setLevel(Level.ALL);
+
+		// remember parameter
+		this.networkName = networkName;
+
+		// create working directory
+		this.workingDir = new File("test-env/" + System.currentTimeMillis());
+		this.workingDir.mkdirs();
+		this.logger.log(Level.FINE, "Created working directory \""
+				+ this.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
+		this.nodes = new HashMap<String, ProxyNode>();
+
+		// create event manager
+		this.eventManager = new EventManagerImpl(this.networkName);
+
+		// log exiting
+		this.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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	private boolean allNodesUp() throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "allNodesUp");
+
+		// fail on first node that is not up
+		for (ProxyNode node : this.nodes.values()) {
+			if (!eventManager.hasEventOccured(node.getNodeName(),
+					NodeEventType.NODE_CIRCUIT_OPENED)) {
+
+				// log exiting and return false
+				this.logger.exiting(this.getClass().getName(), "allNodesUp");
+				return false;
+			}
+		}
+
+		// log exiting and return true
+		this.logger.exiting(this.getClass().getName(), "allNodesUp");
+		return true;
+	}
+
+	public void configureAsPrivateNetwork() throws PuppeTorException,
+			RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(),
+				"configureAsPrivateNetwork");
+
+		for (ProxyNode node : this.nodes.values()) {
+			if (node.getNodeState() == NodeState.CONFIGURING) {
+				// add to configuration
+				node.addConfiguration("TestingTorNetwork 1");
+			}
+		}
+
+		// read DirServer strings for all directories
+		List<String> authorizedDirectoriesFingerprints = new ArrayList<String>();
+		for (ProxyNode node : this.nodes.values()) {
+			if (node instanceof DirectoryNode) {
+				DirectoryNode dirNode = (DirectoryNode) node;
+				authorizedDirectoriesFingerprints.add(dirNode
+						.getDirServerString());
+			}
+		}
+
+		// configure nodes
+		for (ProxyNode node : this.nodes.values()) {
+			if (node.getNodeState() == NodeState.CONFIGURING) {
+
+				// add to configuration
+				node.addConfigurations(authorizedDirectoriesFingerprints);
+			}
+		}
+
+		// read fingerprints for all directories and routers
+		HashSet<String> approvedRoutersFingerprints = new HashSet<String>();
+		for (ProxyNode node : this.nodes.values()) {
+			if (node instanceof RouterNode) {
+				RouterNode routerOrDirNode = (RouterNode) node;
+				approvedRoutersFingerprints.add(routerOrDirNode
+						.getFingerprint());
+			}
+		}
+
+		// write fingerprints for all directories and routers to the
+		// approved-routers file
+		for (ProxyNode node : this.nodes.values()) {
+			if (node instanceof DirectoryNode) {
+				DirectoryNode dirNode = (DirectoryNode) node;
+				dirNode.addApprovedRouters(approvedRoutersFingerprints);
+			}
+		}
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(),
+				"configureAsPrivateNetwork");
+	}
+
+	public ClientApplication createClient(String clientApplicationName,
+			String targetAddress, int targetPort, int socksPort)
+			throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "createClient",
+				new Object[] { clientApplicationName, targetAddress,
+						targetPort, socksPort });
+
+		// create client; parameter checking is done in constructor
+		ClientApplicationImpl client = new ClientApplicationImpl(this,
+				clientApplicationName, targetAddress, targetPort, socksPort);
+
+		// add name to event manager as event source
+		this.eventManager.addEventSource(clientApplicationName);
+
+		// log exiting and return client
+		this.logger.exiting(this.getClass().getName(), "createClient", client);
+		return client;
+	}
+
+	public DirectoryNode createDirectory(String nodeName, int controlPort,
+			int socksPort, int orPort, int dirPort, String serverIpAddress)
+			throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "createDirectory",
+				new Object[] { nodeName, controlPort, socksPort, orPort,
+						dirPort, serverIpAddress });
+
+		// create directory node; parameter checking is done in constructor
+		DirectoryNode dir = new DirectoryNodeImpl(this, nodeName, controlPort,
+				socksPort, orPort, dirPort, serverIpAddress);
+
+		// add new directory node to nodes collection
+		this.nodes.put(nodeName, dir);
+
+		// add name to event manager as event source
+		this.eventManager.addEventSource(nodeName);
+
+		// log exiting and return directory node
+		this.logger.exiting(this.getClass().getName(), "createDirectory", dir);
+		return dir;
+	}
+
+	public DirectoryNode createDirectory(String nodeName, int controlPort,
+			int socksPort, int orPort, int dirPort) throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "createDirectory",
+				new Object[] { nodeName, controlPort, socksPort, orPort,
+						dirPort });
+
+		// invoke overloaded method
+		DirectoryNode dir = this.createDirectory(nodeName, controlPort,
+				socksPort, orPort, dirPort, "127.0.0.1");
+
+		// log exiting and return directory node
+		this.logger.exiting(this.getClass().getName(), "createDirectory", dir);
+		return dir;
+	}
+
+	public DirectoryNode createDirectory(String nodeName, String serverIpAddress)
+			throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "createDirectory",
+				new Object[] { nodeName, serverIpAddress });
+
+		// invoke overloaded method
+		DirectoryNode dir = this.createDirectory(nodeName, this.portCounter++,
+				this.portCounter++, this.portCounter++, this.portCounter++,
+				serverIpAddress);
+
+		// log exiting and return directory node
+		this.logger.exiting(this.getClass().getName(), "createDirectory", dir);
+		return dir;
+	}
+
+	public DirectoryNode createDirectory(String nodeName)
+			throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "createDirectory",
+				nodeName);
+
+		// invoke overloaded method
+		DirectoryNode dir = this.createDirectory(nodeName, this.portCounter++,
+				this.portCounter++, this.portCounter++, this.portCounter++,
+				"127.0.0.1");
+
+		// log exiting and return directory node
+		this.logger.exiting(this.getClass().getName(), "createDirectory", dir);
+		return dir;
+	}
+
+	public ProxyNode createProxy(String nodeName, int controlPort, int socksPort)
+			throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "createProxy",
+				new Object[] { nodeName, controlPort, socksPort });
+
+		// create proxy node; parameter checking is done in constructor
+		ProxyNode proxy = new ProxyNodeImpl(this, nodeName, controlPort,
+				socksPort);
+
+		// add new proxy node to nodes collection
+		this.nodes.put(nodeName, proxy);
+
+		// add name to event manager as event source
+		this.eventManager.addEventSource(nodeName);
+
+		// log exiting and return proxy node
+		this.logger.exiting(this.getClass().getName(), "createProxy", proxy);
+		return proxy;
+	}
+
+	public ProxyNode createProxy(String nodeName) throws RemoteException {
+
+		// log entering
+		this.logger
+				.entering(this.getClass().getName(), "createProxy", nodeName);
+
+		// invoke overloaded method
+		ProxyNode proxy = this.createProxy(nodeName, this.portCounter++,
+				this.portCounter++);
+
+		// log exiting and return proxy node
+		this.logger.exiting(this.getClass().getName(), "createProxy", proxy);
+		return proxy;
+	}
+
+	public RouterNode createRouter(String nodeName, int controlPort,
+			int socksPort, int orPort, int dirPort, String serverIpAddress)
+			throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "createRouter",
+				new Object[] { nodeName, controlPort, socksPort, orPort,
+						dirPort, serverIpAddress });
+
+		// create router node; parameter checking is done in constructor
+		RouterNode router = new RouterNodeImpl(this, nodeName, controlPort,
+				socksPort, orPort, dirPort, serverIpAddress);
+
+		// add new router node to nodes collection
+		this.nodes.put(nodeName, router);
+
+		// add name to event manager as event source
+		this.eventManager.addEventSource(nodeName);
+
+		// log exiting and return router node
+		this.logger.exiting(this.getClass().getName(), "createRouter", router);
+		return router;
+	}
+
+	public RouterNode createRouter(String nodeName, int controlPort,
+			int socksPort, int orPort, int dirPort) throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "createRouter",
+				new Object[] { nodeName, controlPort, socksPort, orPort,
+						dirPort });
+
+		// invoke overloaded method
+		DirectoryNode dir = this.createDirectory(nodeName, controlPort,
+				socksPort, orPort, dirPort, "127.0.0.1");
+
+		// log exiting and return directory node
+		this.logger.exiting(this.getClass().getName(), "createRouter", dir);
+		return dir;
+	}
+
+	public RouterNode createRouter(String nodeName, String serverIpAddress)
+			throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "createRouter",
+				new Object[] { nodeName, serverIpAddress });
+
+		// invoke overloaded method
+		RouterNode dir = this.createRouter(nodeName, this.portCounter++,
+				this.portCounter++, this.portCounter++, this.portCounter++,
+				serverIpAddress);
+
+		// log exiting and return directory node
+		this.logger.exiting(this.getClass().getName(), "createRouter", dir);
+		return dir;
+	}
+
+	public RouterNode createRouter(String nodeName) throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "createRouter",
+				nodeName);
+
+		// invoke overloaded method
+		RouterNode router = this.createRouter(nodeName, this.portCounter++,
+				this.portCounter++, this.portCounter++, this.portCounter++,
+				"127.0.0.1");
+
+		// log exiting and return router node
+		this.logger.exiting(this.getClass().getName(), "createRouter", router);
+		return router;
+	}
+
+	public ServerApplication createServer(String serverApplicationName,
+			int serverPort) throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "createServer",
+				new Object[] { serverApplicationName, serverPort });
+
+		// create server; parameter checking is done in constructor
+		ServerApplicationImpl server = new ServerApplicationImpl(this,
+				serverApplicationName, serverPort);
+
+		// add name to event manager as event source
+		this.eventManager.addEventSource(serverApplicationName);
+
+		// log exiting and return server
+		this.logger.exiting(this.getClass().getName(), "createServer", server);
+		return server;
+	}
+
+	public ServerApplication createServer(String serverApplicationName)
+			throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "createServer",
+				serverApplicationName);
+
+		// invoke overloaded method
+		ServerApplication server = this.createServer(serverApplicationName,
+				this.portCounter++);
+
+		// log exiting and return server
+		this.logger.exiting(this.getClass().getName(), "createServer", server);
+		return server;
+	}
+
+	public EventManager getEventManager() {
+		return this.eventManager;
+	}
+
+	/**
+	 * Returns the implementation instance of the event manager of this network.
+	 * 
+	 * @return The implementation instance of the event manager of this network.
+	 */
+	EventManagerImpl getEventManagerImpl() {
+		return this.eventManager;
+	}
+
+	public File getWorkingDirectory() {
+		return this.workingDir;
+	}
+
+	public ProxyNode getNode(String nodeName) {
+		return this.nodes.get(nodeName);
+	}
+
+	public Map<String, ProxyNode> getAllProxyNodes() {
+		Map<String, ProxyNode> result = new HashMap<String, ProxyNode>();
+		for (String nodeName : this.nodes.keySet()) {
+			ProxyNode node = this.nodes.get(nodeName);
+			if (!(node instanceof RouterNode)) {
+				result.put(nodeName, node);
+			}
+		}
+		return result;
+	}
+
+	public Map<String, RouterNode> getAllRouterNodes() {
+		Map<String, RouterNode> result = new HashMap<String, RouterNode>();
+		for (String nodeName : this.nodes.keySet()) {
+			ProxyNode node = this.nodes.get(nodeName);
+			if (node instanceof RouterNode && !(node instanceof DirectoryNode)) {
+				result.put(nodeName, (RouterNode) node);
+			}
+		}
+		return result;
+	}
+
+	public Map<String, DirectoryNode> getAllDirectoryNodes() {
+		Map<String, DirectoryNode> result = new HashMap<String, DirectoryNode>();
+		for (String nodeName : this.nodes.keySet()) {
+			ProxyNode node = this.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(int tries, long hupInterval)
+			throws PuppeTorException, RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "hupUntilUp",
+				new Object[] { tries, hupInterval });
+
+		// check if all nodes are running
+		for (ProxyNode node : this.nodes.values()) {
+			if (node.getNodeState() != NodeState.RUNNING) {
+				IllegalStateException e = new IllegalStateException(
+						"All nodes must be running before sending them HUP "
+								+ "commands!");
+				this.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
+			this.logger.exiting(this.getClass().getName(), "hupUntilUp", true);
+			return true;
+		}
+
+		// create and register a new event handler for each node
+		final Thread sleepingThread = Thread.currentThread();
+		for (ProxyNode node : this.nodes.values()) {
+			eventManager.addEventListener(node.getNodeName(),
+					new EventListener() {
+						public void handleEvent(Event event) {
+							if (event.getType() == NodeEventType.NODE_CIRCUIT_OPENED) {
+								sleepingThread.interrupt();
+								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 sleep
+			long endOfSleeping = System.currentTimeMillis() + hupInterval;
+			long now;
+
+			// unless all nodes have reported to be up, wait for the given
+			// maximum time
+			while ((now = System.currentTimeMillis()) < endOfSleeping) {
+
+				// sleep
+				try {
+					Thread.sleep(endOfSleeping - now);
+				} catch (InterruptedException e) {
+					// do nothing about it
+				}
+
+				// check if nodes are up now
+				if (allNodesUp()) {
+
+					// log exiting and return true
+					this.logger.exiting(this.getClass().getName(),
+							"hupUntilUp", true);
+					return true;
+				}
+			}
+
+			this.logger.log(Level.FINE, "Sending HUP to nodes");
+			// send a HUP signal to all nodes
+			for (ProxyNode node : this.nodes.values()) {
+				this.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
+		this.logger.exiting(this.getClass().getName(), "hupUntilUp", false);
+		return false;
+	}
+
+	public void hupAllNodes() throws PuppeTorException, RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "hupAllNodes");
+
+		// check if all nodes are running
+		for (ProxyNode node : this.nodes.values()) {
+			if (node.getNodeState() != NodeState.RUNNING) {
+				IllegalStateException e = new IllegalStateException(
+						"All nodes must be running before sending them HUP "
+								+ "commands!");
+				this.logger.throwing(this.getClass().getName(), "hupAllNodes",
+						e);
+				throw e;
+			}
+		}
+
+		// send a HUP signal to all nodes
+		for (ProxyNode node : this.nodes.values()) {
+			this.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
+		this.logger.exiting(this.getClass().getName(), "hupAllNodes");
+	}
+
+	public void hupAllDirectories() throws PuppeTorException, RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "hupAllDirectories");
+
+		// check if all directory nodes are running
+		for (ProxyNode node : this.nodes.values()) {
+			if (node instanceof DirectoryNode
+					&& node.getNodeState() != NodeState.RUNNING) {
+				IllegalStateException e = new IllegalStateException(
+						"All directory nodes must be running before sending "
+								+ "them HUP commands!");
+				this.logger.throwing(this.getClass().getName(),
+						"hupAllDirectories", e);
+				throw e;
+			}
+		}
+
+		// send a HUP signal to all nodes
+		for (ProxyNode node : this.nodes.values()) {
+			if (node instanceof DirectoryNode) {
+				this.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
+		this.logger.exiting(this.getClass().getName(), "hupAllDirectories");
+	}
+
+	public void shutdownNodes() throws PuppeTorException, RemoteException {
+
+		// log entering
+		this.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 (ProxyNode node : this.nodes.values()) {
+			if (node.getNodeState() == NodeState.RUNNING) {
+				try {
+					node.shutdown();
+				} catch (PuppeTorException e) {
+					if (firstCaughtException == null) {
+						firstCaughtException = e;
+					}
+				}
+			}
+		}
+
+		// if an exception was caught during shutting down nodes, throw the
+		// first caught exception
+		if (firstCaughtException != null) {
+			this.logger.throwing(this.getClass().getName(), "shutdownNodes",
+					firstCaughtException);
+			throw firstCaughtException;
+		}
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "shutdownNodes");
+	}
+
+	public boolean startNodes(long maximumTimeToWaitInMillis)
+			throws PuppeTorException, RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "startNodes",
+				maximumTimeToWaitInMillis);
+
+		// check node states
+		for (ProxyNode node : this.nodes.values()) {
+			if (node.getNodeState() != NodeState.CONFIGURATION_WRITTEN) {
+				IllegalStateException e = new IllegalStateException(
+						"All configurations must be written before starting "
+								+ "nodes!");
+				this.logger
+						.throwing(this.getClass().getName(), "startNodes", e);
+				throw e;
+			}
+		}
+
+		// check parameter
+		if (maximumTimeToWaitInMillis < 0) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.logger.throwing(this.getClass().getName(), "startNodes", e);
+			throw e;
+		}
+
+		// remember time when we begin starting the nodes
+		long before = System.currentTimeMillis();
+
+		// start nodes in parallel
+		Set<NodeStarter> allNodeStarters = new HashSet<NodeStarter>();
+		for (ProxyNode node : this.nodes.values()) {
+			NodeStarter nodeStarter = new NodeStarter(node,
+					maximumTimeToWaitInMillis);
+			allNodeStarters.add(nodeStarter);
+			nodeStarter.start();
+		}
+
+		// wait for all node starts to complete
+		for (NodeStarter nodeStarter : allNodeStarters) {
+
+			// join node starts one after the other
+			try {
+				nodeStarter.join();
+			} catch (InterruptedException e) {
+				// this happens?! we have some kind of problem here!
+				this.logger.log(Level.WARNING,
+						"Interrupt while joining node starter!");
+
+				// log exiting and return false
+				this.logger.exiting(this.getClass().getName(), "startNodes",
+						false);
+				return false;
+			}
+
+			// if any thread has caught an exception, throw that exception now
+			Exception caughtException = nodeStarter.caughtException;
+			if (caughtException != null) {
+				PuppeTorException ex = new PuppeTorException(
+						"Exception while starting node "
+								+ nodeStarter.node.getNodeName(),
+						caughtException);
+				this.logger.throwing(this.getClass().getName(), "startNodes",
+						ex);
+				throw ex;
+			}
+
+			// if node start did not succeed in the given time, fail
+			if (!nodeStarter.success) {
+				this.logger.log(Level.WARNING,
+						"Starting nodes was not successful in "
+								+ (maximumTimeToWaitInMillis / 1000)
+								+ " seconds.", this.networkName);
+
+				// log exiting and return false
+				this.logger.exiting(this.getClass().getName(), "startNodes",
+						false);
+				return false;
+			}
+		}
+
+		// determine how long we took to start all nodes
+		long after = System.currentTimeMillis();
+		this.logger.log(Level.FINE, "Starting nodes was successful and took "
+				+ ((after - before) / 1000) + " seconds.", this.networkName);
+
+		// log exiting and return true
+		this.logger.exiting(this.getClass().getName(), "startNodes", true);
+		return true;
+	}
+
+	@Override
+	public String toString() {
+		return this.getClass().getSimpleName() + ": networkName=\""
+				+ this.networkName;
+	}
+
+	public String getNetworkName() {
+		return this.networkName;
+	}
+
+	public void writeConfigurations() throws PuppeTorException, RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "writeConfigurations");
+
+		// write configurations for all nodes
+		for (ProxyNode node : this.nodes.values()) {
+			node.writeConfiguration();
+		}
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "writeConfigurations");
+	}
+
+	public void configureAsInterconnectedPrivateNetwork(Network remoteNetwork)
+			throws PuppeTorException, RemoteException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "mergeNetworks",
+				remoteNetwork);
+
+		// collect dir strings from this and the remote network
+		List<String> ourDirServerStrings = new ArrayList<String>();
+		for (DirectoryNode directory : this.getAllDirectoryNodes().values()) {
+			ourDirServerStrings.add(directory.getDirServerString());
+		}
+		List<String> remoteDirServerStrings = new ArrayList<String>();
+		for (DirectoryNode directory : remoteNetwork.getAllDirectoryNodes()
+				.values()) {
+			remoteDirServerStrings.add(directory.getDirServerString());
+		}
+
+		// add dir strings of local directories to all nodes of the other
+		// network and vice versa
+		for (ProxyNode node : remoteNetwork.getAllNodes().values()) {
+			node.addConfigurations(ourDirServerStrings);
+		}
+		for (ProxyNode node : this.getAllNodes().values()) {
+			node.addConfigurations(remoteDirServerStrings);
+		}
+
+		// collect router fingerprints from all routers in this and the remote
+		// network
+		Set<String> ourApprovedRoutersStrings = new TreeSet<String>();
+		for (RouterNode router : this.getAllRouterNodes().values()) {
+			ourApprovedRoutersStrings.add(router.getFingerprint());
+		}
+		Set<String> remoteApprovedRoutersStrings = new TreeSet<String>();
+		for (RouterNode router : remoteNetwork.getAllRouterNodes().values()) {
+			remoteApprovedRoutersStrings.add(router.getFingerprint());
+		}
+
+		// add the fingerprints of local routers and directories to the
+		// directories of the other network and vice versa
+		for (DirectoryNode node : remoteNetwork.getAllDirectoryNodes().values()) {
+			node.addApprovedRouters(ourApprovedRoutersStrings);
+		}
+		for (DirectoryNode node : this.getAllDirectoryNodes().values()) {
+			node.addApprovedRouters(remoteApprovedRoutersStrings);
+		}
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "mergeNetworks");
+	}
+
+	public void addTemplateConfiguration(Class<? extends ProxyNode> nodeClass,
+			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(" ")) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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 {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.logger.throwing(this.getClass().getName(),
+					"addTemplateConfiguration", e);
+			throw e;
+		}
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "addTemplateConfiguration");
+	}
+
+	public List<String> getTemplateConfiguration(
+			Class<? extends ProxyNode> nodeClass) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "getTemplateConfiguration",
+				nodeClass);
+
+		// check parameter
+		if (nodeClass == null) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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 {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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(
+			Class<? extends ProxyNode> nodeClass,
+			String templateConfigurationKey) {
+
+		// log entering
+		logger.entering(this.getClass().getName(),
+				"removeTemplateConfiguration", new Object[] { nodeClass,
+						templateConfigurationKey });
+
+		// check parameters
+		if (nodeClass == null || templateConfigurationKey == null
+				|| templateConfigurationKey.length() < 1) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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 {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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
+		List<String> configurationStringsToRemove = new ArrayList<String>();
+		for (String currentConfigurationString : templateConfig) {
+			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");
+	}
+
+	public boolean bindAtRmiregistry() throws RemoteException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "bindAtRmiregistry");
+
+		// set the RMISecurityManager
+		System.setSecurityManager(new RMISecurityManager());
+
+		// bind the network to the rmiregistry
+		try {
+			Naming.rebind("//127.0.0.1/" + networkName, this);
+		} catch (MalformedURLException e) {
+			this.logger.log(Level.WARNING,
+					"URL to bind this network to is malformed!", e);
+			logger.exiting(this.getClass().getName(), "bindAtRmiregistry",
+					false);
+			return false;
+		}
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "bindAtRmiregistry", true);
+		return true;
+	}
+
+	/**
+	 * Returns the current port number and increments it afterwards.
+	 * 
+	 * @return The current port number.
+	 */
+	int getNextPortNumber() {
+		return this.portCounter++;
+	}
+}
diff --git a/src/org/torproject/puppetor/impl/ProxyNodeImpl.java b/src/org/torproject/puppetor/impl/ProxyNodeImpl.java
old mode 100755
new mode 100644
index 083a6d8..c349333
--- a/src/org/torproject/puppetor/impl/ProxyNodeImpl.java
+++ b/src/org/torproject/puppetor/impl/ProxyNodeImpl.java
@@ -1,764 +1,764 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor.impl;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.Socket;
-import java.rmi.RemoteException;
-import java.rmi.server.UnicastRemoteObject;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.torproject.puppetor.HiddenService;
-import org.torproject.puppetor.NodeEventType;
-import org.torproject.puppetor.NodeState;
-import org.torproject.puppetor.ProxyNode;
-import org.torproject.puppetor.PuppeTorException;
-
-import net.freehaven.tor.control.TorControlConnection;
-
-/**
- * Implementation of <code>ProxyNode</code>.
- * 
- * @author kloesing
- */
- at SuppressWarnings("serial")
-public class ProxyNodeImpl extends UnicastRemoteObject 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 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 this.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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	ProxyNodeImpl(NetworkImpl network, String nodeName, int controlPort,
-			int socksPort) throws RemoteException {
-
-		// make sure that nodeName is a valid logger name
-		if (nodeName == null || nodeName.length() < 1 || nodeName.length() > 19
-				|| !nodeName.matches("[a-zA-Z0-9]*")) {
-			String reason = "\"" + nodeName + "\" is not a valid node name!";
-			IllegalArgumentException e = new IllegalArgumentException(reason);
-			throw e;
-		}
-
-		// create logger
-		this.logger = Logger.getLogger(nodeName + "."
-				+ this.getClass().getName());
-
-		this.logger.setLevel(Level.ALL);
-
-		// log entering
-		this.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) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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
-		this.eventManager = network.getEventManagerImpl();
-
-		// create working directory
-		this.workingDir = new File(this.network.getWorkingDirectory()
-				.getAbsolutePath()
-				+ File.separator + nodeName + File.separator);
-		this.workingDir.mkdirs();
-		this.logger.log(Level.FINE, "Created working directory \""
-				+ this.workingDir.getAbsolutePath() + "\"");
-
-		// create reference on config file
-		this.configFile = new File(this.workingDir.getAbsolutePath()
-				+ File.separator + "torrc");
-
-		// initialize configuration
-		this.configuration = new ArrayList<String>(templateConfiguration);
-		this.configuration.add("ControlPort " + controlPort);
-		this.configuration.add("SocksPort " + socksPort);
-
-		// initialize state
-		this.nodeState = NodeState.CONFIGURING;
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "ProxyNodeImpl");
-	}
-
-	public void addConfiguration(String configurationString) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "addConfiguration",
-				configurationString);
-
-		// check parameter
-		if (configurationString == null || configurationString.length() < 1
-				|| !configurationString.contains(" ")) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.logger.throwing(this.getClass().getName(), "addConfiguration",
-					e);
-			throw e;
-		}
-
-		// add configuration string
-		this.configuration.add(configurationString);
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "addConfiguration");
-	}
-
-	public void addConfigurations(List<String> configurationStrings) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "addConfigurations",
-				configurationStrings);
-
-		// check parameter
-		if (configurationStrings == null) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.logger.throwing(this.getClass().getName(),
-					"addConfigurations", e);
-			throw e;
-		}
-
-		// add configuration strings one by one
-		for (String conf : configurationStrings) {
-			this.addConfiguration(conf);
-		}
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "addConfigurations");
-	}
-
-	public void replaceConfiguration(String configurationString) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "replaceConfiguration",
-				configurationString);
-
-		// check parameter
-		if (configurationString == null || configurationString.length() < 1
-				|| !configurationString.contains(" ")) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.logger.throwing(this.getClass().getName(),
-					"replaceConfiguration", e);
-			throw e;
-		}
-
-		// extract configuration key
-		String configurationKey = configurationString.substring(0,
-				configurationString.indexOf(" "));
-
-		// iterate over existing configuration strings and replace the first
-		// occurrence of configuration key with new configuration string
-		Iterator<String> it = this.configuration.listIterator();
-		boolean replaced = false;
-		for (int counter = 0; !replaced && it.hasNext(); counter++) {
-			String currentConfigurationString = it.next();
-			String currentConfigurationKey = currentConfigurationString
-					.substring(0, currentConfigurationString.indexOf(" "));
-			if (currentConfigurationKey.equals(configurationKey)) {
-				this.configuration.set(counter, configurationString);
-				replaced = true;
-			}
-		}
-
-		// if no such configuration key was found, append the configuration
-		// string
-		if (!replaced) {
-			this.configuration.add(configurationString);
-		}
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "replaceConfiguration");
-	}
-
-	public void removeConfiguration(String configurationKey)
-			throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "deleteConfiguration",
-				configurationKey);
-
-		// check parameter
-		if (configurationKey == null || configurationKey.length() < 1) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.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
-		List<String> configurationStringsToRemove = new ArrayList<String>();
-		for (String currentConfigurationString : this.configuration) {
-			String currentConfigurationKey = currentConfigurationString
-					.substring(0, currentConfigurationString.indexOf(" "));
-			if (currentConfigurationKey.equals(configurationKey)) {
-				configurationStringsToRemove.add(currentConfigurationString);
-			}
-		}
-		this.configuration.removeAll(configurationStringsToRemove);
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "deleteConfiguration");
-	}
-
-	public synchronized HiddenService addHiddenService(String serviceName,
-			int servicePort, int virtualPort) throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "addHiddenService",
-				new Object[] { serviceName, servicePort, virtualPort });
-
-		// create hidden service object; parameter checking is done in
-		// constructor
-		HiddenService result = new HiddenServiceImpl(this, serviceName,
-				servicePort, virtualPort);
-
-		// add hidden service using Tor controller
-		this.configuration.add("HiddenServiceDir "
-				+ workingDir.getAbsolutePath() + File.separator + serviceName
-				+ "\nHiddenServicePort " + virtualPort + " 127.0.0.1:"
-				+ servicePort);
-
-		// log exiting and return hidden service object
-		this.logger.exiting(this.getClass().getName(), "addHiddenService",
-				result);
-		return result;
-	}
-
-	public synchronized HiddenService addHiddenService(String serviceName,
-			int servicePort) throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "addHiddenService",
-				new Object[] { serviceName, servicePort });
-
-		// invoke overloaded method
-		HiddenService result = this.addHiddenService(serviceName, servicePort,
-				80);
-
-		// log exiting and return hidden service
-		this.logger.exiting(this.getClass().getName(), "addHiddenService",
-				result);
-		return result;
-	}
-
-	public synchronized HiddenService addHiddenService(String serviceName)
-			throws RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "addHiddenService",
-				serviceName);
-
-		// invoke overloaded method
-		HiddenService result = this.addHiddenService(serviceName, this.network
-				.getNextPortNumber(), 80);
-
-		// log exiting and return hidden service
-		this.logger.exiting(this.getClass().getName(), "addHiddenService",
-				result);
-		return result;
-	}
-
-	public String getNodeName() {
-		return this.nodeName;
-	}
-
-	public synchronized NodeState getNodeState() {
-		return this.nodeState;
-	}
-
-	public synchronized void hup() throws PuppeTorException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "hup");
-
-		// check state
-		if (this.nodeState != NodeState.RUNNING || this.conn == null) {
-			IllegalStateException e = new IllegalStateException(
-					"Cannot hup a process when it's not running or there is "
-							+ "no connection to its control port!");
-			this.logger.throwing(this.getClass().getName(), "hup", e);
-			throw e;
-		}
-
-		// send HUP signal to Tor process
-		try {
-			this.conn.signal("HUP");
-		} catch (IOException e) {
-			PuppeTorException ex = new PuppeTorException(
-					"Could not send the command HUP to the Tor process!", e);
-			this.logger.throwing(this.getClass().getName(), "hup", ex);
-			throw ex;
-		} catch (NullPointerException e) {
-			// TODO sometimes, this throws a NullPointerException...
-			this.logger.log(Level.SEVERE, "is conn null? "
-					+ (this.conn == null));
-			this.logger.log(Level.SEVERE,
-					"NullPointerException while sending HUP signal to node "
-							+ this.toString());
-			throw e;
-		}
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "hup");
-	}
-
-	public synchronized void shutdown() throws PuppeTorException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "shutdown");
-
-		// check state
-		if (this.nodeState != NodeState.RUNNING) {
-			IllegalStateException e = new IllegalStateException();
-			this.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 {
-			this.conn.shutdownTor("SHUTDOWN");
-			this.conn.shutdownTor("SHUTDOWN");
-		} catch (IOException e) {
-			PuppeTorException ex = new PuppeTorException(
-					"Could not send shutdown command to Tor process!", e);
-			this.logger.throwing(this.getClass().getName(), "shutdown", ex);
-			throw ex;
-		}
-
-		// change state
-		this.nodeState = NodeState.SHUT_DOWN;
-
-		// fire event
-		eventManager.observeInternalEvent(System.currentTimeMillis(), this
-				.getNodeName(), NodeEventType.NODE_STOPPED, "Node stopped.");
-
-		// log exiting
-		this.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.
-	 * This class is not threadsafe.
-	 * 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 class ProcessWaiter extends Thread {
-
-		/** The process to wait for. */
-		private Process process;
-
-		/** The exit value or 0 if the process is still running. */
-		private int 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(Process process) {
-			this.process = process;
-		}
-
-		@Override
-		public void run() {
-			try {
-				this.exitValue = process.waitFor();
-			} catch (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 synchronized int waitFor(long timeoutInMillis) {
-			try {
-				sleep(timeoutInMillis);
-			} catch (InterruptedException e) {
-			}
-			this.interrupt();
-			return this.exitValue;
-		}
-	}
-
-	public synchronized boolean startNode(long maximumTimeToWaitInMillis)
-			throws PuppeTorException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "startNode",
-				maximumTimeToWaitInMillis);
-
-		// check state
-		if (this.nodeState != NodeState.CONFIGURATION_WRITTEN) {
-			String reason = "Node is not in state "
-					+ "NodeState.CONFIGURATION_WRITTEN!";
-			IllegalStateException e = new IllegalStateException(reason);
-			this.logger.throwing(this.getClass().getName(), "startNode", e);
-			throw e;
-		}
-
-		// start process
-		ProcessBuilder processBuilder = new ProcessBuilder(torExecutable
-				.getPath(), "-f", "torrc");
-		processBuilder.directory(this.workingDir);
-		processBuilder.redirectErrorStream(true);
-		try {
-			this.torProcess = processBuilder.start();
-			this.logger.log(Level.FINE, "Started Tor process successfully!");
-		} catch (IOException e) {
-			String reason = "Could not start Tor process!";
-			PuppeTorException ex = new PuppeTorException(reason, e);
-			this.logger.throwing(this.getClass().getName(), "startNode", ex);
-			throw ex;
-		}
-
-		// start thread to parse output
-		final BufferedReader br = new BufferedReader(new InputStreamReader(
-				this.torProcess.getInputStream()));
-		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 (nodeState == 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(this.nodeName + " Output Parser");
-		outputThread.start();
-		this.logger.log(Level.FINE, "Started thread to parse output!");
-
-		// add shutdown hook that kills the process on JVM exit
-		final Process p = this.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");
-			}
-		});
-		this.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
-		ProcessWaiter waiter = new ProcessWaiter(this.torProcess);
-		waiter.start();
-		int exitValue = waiter.waitFor(1000);
-		if (exitValue != 0) {
-			// Tor did not manage to start correctly
-			this.logger.log(Level.WARNING, "Could not start Tor process! Tor "
-					+ "exited with exit value " + exitValue
-					+ "! Please go check the config options in "
-					+ this.configFile + " manually!");
-
-			// log exiting
-			this.logger.exiting(this.getClass().getName(), "startNode", false);
-			return false;
-		}
-
-		// wait for Tor to open the control port
-		this.logger.log(Level.FINER,
-				"Waiting for Tor to open its control port...");
-		if (!this.eventManager.waitForAnyOccurence(this.nodeName,
-				NodeEventType.NODE_CONTROL_PORT_OPENED,
-				maximumTimeToWaitInMillis)) {
-
-			// Tor did not open its control port
-			this.logger.log(Level.WARNING, "Tor node " + this.nodeName
-					+ " did not manage to open its control port within "
-					+ maximumTimeToWaitInMillis + " milliseconds!");
-
-			// log exiting
-			this.logger.exiting(this.getClass().getName(), "startNode", false);
-			return false;
-		}
-		this.logger.log(Level.FINE,
-				"Tor has successfully opened its control port and told us "
-						+ "about that!");
-
-		// connect to the controller
-		this.logger.log(Level.FINER, "Connecting to control port...");
-		try {
-			Socket controlSocket = new java.net.Socket("127.0.0.1", controlPort);
-			this.conn = TorControlConnection.getConnection(controlSocket);
-			this.conn.authenticate(new byte[0]);
-		} catch (IOException e) {
-			String reason = "Could not connect to control port " + controlPort
-					+ "!";
-			PuppeTorException ex = new PuppeTorException(reason, e);
-			this.logger.throwing(this.getClass().getName(), "startNode", ex);
-			throw ex;
-		}
-		this.logger.log(Level.FINE, "Connected to control port successfully!");
-
-		// set state to RUNNING
-		this.nodeState = NodeState.RUNNING;
-
-		// fire event
-		eventManager.observeInternalEvent(System.currentTimeMillis(), this
-				.getNodeName(), NodeEventType.NODE_STARTED, "Node started.");
-
-		// log exiting and return with success
-		this.logger.exiting(this.getClass().getName(), "startNode", true);
-		return true;
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": nodeName=\""
-				+ this.nodeName + "\", controlPort=" + this.controlPort
-				+ ", socksPort=" + this.socksPort;
-	}
-
-	public synchronized void writeConfiguration() throws PuppeTorException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "writeConfiguration");
-
-		// write config file
-		try {
-			BufferedWriter bw = new BufferedWriter(new FileWriter(
-					this.configFile));
-			for (String c : this.configuration) {
-				bw.write(c + "\n");
-			}
-			bw.close();
-		} catch (IOException e) {
-			PuppeTorException ex = new PuppeTorException(
-					"Could not write configuration file!", e);
-			this.logger.throwing(this.getClass().getName(),
-					"writeConfigurationFile", ex);
-			throw ex;
-		}
-
-		// change state, if necessary
-		if (this.nodeState == NodeState.CONFIGURING) {
-			this.nodeState = NodeState.CONFIGURATION_WRITTEN;
-		}
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "writeConfiguration");
-	}
-
-	public int getSocksPort() {
-		return this.socksPort;
-	}
-
-	public int getControlPort() {
-		return this.controlPort;
-	}
-
-	public List<String> getConfiguration() {
-		return new ArrayList<String>(this.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");
-
-	}
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor.impl;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.Socket;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.torproject.puppetor.HiddenService;
+import org.torproject.puppetor.NodeEventType;
+import org.torproject.puppetor.NodeState;
+import org.torproject.puppetor.ProxyNode;
+import org.torproject.puppetor.PuppeTorException;
+
+import net.freehaven.tor.control.TorControlConnection;
+
+/**
+ * Implementation of <code>ProxyNode</code>.
+ * 
+ * @author kloesing
+ */
+ at SuppressWarnings("serial")
+public class ProxyNodeImpl extends UnicastRemoteObject 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 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 this.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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	ProxyNodeImpl(NetworkImpl network, String nodeName, int controlPort,
+			int socksPort) throws RemoteException {
+
+		// make sure that nodeName is a valid logger name
+		if (nodeName == null || nodeName.length() < 1 || nodeName.length() > 19
+				|| !nodeName.matches("[a-zA-Z0-9]*")) {
+			String reason = "\"" + nodeName + "\" is not a valid node name!";
+			IllegalArgumentException e = new IllegalArgumentException(reason);
+			throw e;
+		}
+
+		// create logger
+		this.logger = Logger.getLogger(nodeName + "."
+				+ this.getClass().getName());
+
+		this.logger.setLevel(Level.ALL);
+
+		// log entering
+		this.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) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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
+		this.eventManager = network.getEventManagerImpl();
+
+		// create working directory
+		this.workingDir = new File(this.network.getWorkingDirectory()
+				.getAbsolutePath()
+				+ File.separator + nodeName + File.separator);
+		this.workingDir.mkdirs();
+		this.logger.log(Level.FINE, "Created working directory \""
+				+ this.workingDir.getAbsolutePath() + "\"");
+
+		// create reference on config file
+		this.configFile = new File(this.workingDir.getAbsolutePath()
+				+ File.separator + "torrc");
+
+		// initialize configuration
+		this.configuration = new ArrayList<String>(templateConfiguration);
+		this.configuration.add("ControlPort " + controlPort);
+		this.configuration.add("SocksPort " + socksPort);
+
+		// initialize state
+		this.nodeState = NodeState.CONFIGURING;
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "ProxyNodeImpl");
+	}
+
+	public void addConfiguration(String configurationString) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "addConfiguration",
+				configurationString);
+
+		// check parameter
+		if (configurationString == null || configurationString.length() < 1
+				|| !configurationString.contains(" ")) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.logger.throwing(this.getClass().getName(), "addConfiguration",
+					e);
+			throw e;
+		}
+
+		// add configuration string
+		this.configuration.add(configurationString);
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "addConfiguration");
+	}
+
+	public void addConfigurations(List<String> configurationStrings) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "addConfigurations",
+				configurationStrings);
+
+		// check parameter
+		if (configurationStrings == null) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.logger.throwing(this.getClass().getName(),
+					"addConfigurations", e);
+			throw e;
+		}
+
+		// add configuration strings one by one
+		for (String conf : configurationStrings) {
+			this.addConfiguration(conf);
+		}
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "addConfigurations");
+	}
+
+	public void replaceConfiguration(String configurationString) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "replaceConfiguration",
+				configurationString);
+
+		// check parameter
+		if (configurationString == null || configurationString.length() < 1
+				|| !configurationString.contains(" ")) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.logger.throwing(this.getClass().getName(),
+					"replaceConfiguration", e);
+			throw e;
+		}
+
+		// extract configuration key
+		String configurationKey = configurationString.substring(0,
+				configurationString.indexOf(" "));
+
+		// iterate over existing configuration strings and replace the first
+		// occurrence of configuration key with new configuration string
+		Iterator<String> it = this.configuration.listIterator();
+		boolean replaced = false;
+		for (int counter = 0; !replaced && it.hasNext(); counter++) {
+			String currentConfigurationString = it.next();
+			String currentConfigurationKey = currentConfigurationString
+					.substring(0, currentConfigurationString.indexOf(" "));
+			if (currentConfigurationKey.equals(configurationKey)) {
+				this.configuration.set(counter, configurationString);
+				replaced = true;
+			}
+		}
+
+		// if no such configuration key was found, append the configuration
+		// string
+		if (!replaced) {
+			this.configuration.add(configurationString);
+		}
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "replaceConfiguration");
+	}
+
+	public void removeConfiguration(String configurationKey)
+			throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "deleteConfiguration",
+				configurationKey);
+
+		// check parameter
+		if (configurationKey == null || configurationKey.length() < 1) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.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
+		List<String> configurationStringsToRemove = new ArrayList<String>();
+		for (String currentConfigurationString : this.configuration) {
+			String currentConfigurationKey = currentConfigurationString
+					.substring(0, currentConfigurationString.indexOf(" "));
+			if (currentConfigurationKey.equals(configurationKey)) {
+				configurationStringsToRemove.add(currentConfigurationString);
+			}
+		}
+		this.configuration.removeAll(configurationStringsToRemove);
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "deleteConfiguration");
+	}
+
+	public synchronized HiddenService addHiddenService(String serviceName,
+			int servicePort, int virtualPort) throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "addHiddenService",
+				new Object[] { serviceName, servicePort, virtualPort });
+
+		// create hidden service object; parameter checking is done in
+		// constructor
+		HiddenService result = new HiddenServiceImpl(this, serviceName,
+				servicePort, virtualPort);
+
+		// add hidden service using Tor controller
+		this.configuration.add("HiddenServiceDir "
+				+ workingDir.getAbsolutePath() + File.separator + serviceName
+				+ "\nHiddenServicePort " + virtualPort + " 127.0.0.1:"
+				+ servicePort);
+
+		// log exiting and return hidden service object
+		this.logger.exiting(this.getClass().getName(), "addHiddenService",
+				result);
+		return result;
+	}
+
+	public synchronized HiddenService addHiddenService(String serviceName,
+			int servicePort) throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "addHiddenService",
+				new Object[] { serviceName, servicePort });
+
+		// invoke overloaded method
+		HiddenService result = this.addHiddenService(serviceName, servicePort,
+				80);
+
+		// log exiting and return hidden service
+		this.logger.exiting(this.getClass().getName(), "addHiddenService",
+				result);
+		return result;
+	}
+
+	public synchronized HiddenService addHiddenService(String serviceName)
+			throws RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "addHiddenService",
+				serviceName);
+
+		// invoke overloaded method
+		HiddenService result = this.addHiddenService(serviceName, this.network
+				.getNextPortNumber(), 80);
+
+		// log exiting and return hidden service
+		this.logger.exiting(this.getClass().getName(), "addHiddenService",
+				result);
+		return result;
+	}
+
+	public String getNodeName() {
+		return this.nodeName;
+	}
+
+	public synchronized NodeState getNodeState() {
+		return this.nodeState;
+	}
+
+	public synchronized void hup() throws PuppeTorException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "hup");
+
+		// check state
+		if (this.nodeState != NodeState.RUNNING || this.conn == null) {
+			IllegalStateException e = new IllegalStateException(
+					"Cannot hup a process when it's not running or there is "
+							+ "no connection to its control port!");
+			this.logger.throwing(this.getClass().getName(), "hup", e);
+			throw e;
+		}
+
+		// send HUP signal to Tor process
+		try {
+			this.conn.signal("HUP");
+		} catch (IOException e) {
+			PuppeTorException ex = new PuppeTorException(
+					"Could not send the command HUP to the Tor process!", e);
+			this.logger.throwing(this.getClass().getName(), "hup", ex);
+			throw ex;
+		} catch (NullPointerException e) {
+			// TODO sometimes, this throws a NullPointerException...
+			this.logger.log(Level.SEVERE, "is conn null? "
+					+ (this.conn == null));
+			this.logger.log(Level.SEVERE,
+					"NullPointerException while sending HUP signal to node "
+							+ this.toString());
+			throw e;
+		}
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "hup");
+	}
+
+	public synchronized void shutdown() throws PuppeTorException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "shutdown");
+
+		// check state
+		if (this.nodeState != NodeState.RUNNING) {
+			IllegalStateException e = new IllegalStateException();
+			this.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 {
+			this.conn.shutdownTor("SHUTDOWN");
+			this.conn.shutdownTor("SHUTDOWN");
+		} catch (IOException e) {
+			PuppeTorException ex = new PuppeTorException(
+					"Could not send shutdown command to Tor process!", e);
+			this.logger.throwing(this.getClass().getName(), "shutdown", ex);
+			throw ex;
+		}
+
+		// change state
+		this.nodeState = NodeState.SHUT_DOWN;
+
+		// fire event
+		eventManager.observeInternalEvent(System.currentTimeMillis(), this
+				.getNodeName(), NodeEventType.NODE_STOPPED, "Node stopped.");
+
+		// log exiting
+		this.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.
+	 * This class is not threadsafe.
+	 * 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 class ProcessWaiter extends Thread {
+
+		/** The process to wait for. */
+		private Process process;
+
+		/** The exit value or 0 if the process is still running. */
+		private int 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(Process process) {
+			this.process = process;
+		}
+
+		@Override
+		public void run() {
+			try {
+				this.exitValue = process.waitFor();
+			} catch (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 synchronized int waitFor(long timeoutInMillis) {
+			try {
+				sleep(timeoutInMillis);
+			} catch (InterruptedException e) {
+			}
+			this.interrupt();
+			return this.exitValue;
+		}
+	}
+
+	public synchronized boolean startNode(long maximumTimeToWaitInMillis)
+			throws PuppeTorException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "startNode",
+				maximumTimeToWaitInMillis);
+
+		// check state
+		if (this.nodeState != NodeState.CONFIGURATION_WRITTEN) {
+			String reason = "Node is not in state "
+					+ "NodeState.CONFIGURATION_WRITTEN!";
+			IllegalStateException e = new IllegalStateException(reason);
+			this.logger.throwing(this.getClass().getName(), "startNode", e);
+			throw e;
+		}
+
+		// start process
+		ProcessBuilder processBuilder = new ProcessBuilder(torExecutable
+				.getPath(), "-f", "torrc");
+		processBuilder.directory(this.workingDir);
+		processBuilder.redirectErrorStream(true);
+		try {
+			this.torProcess = processBuilder.start();
+			this.logger.log(Level.FINE, "Started Tor process successfully!");
+		} catch (IOException e) {
+			String reason = "Could not start Tor process!";
+			PuppeTorException ex = new PuppeTorException(reason, e);
+			this.logger.throwing(this.getClass().getName(), "startNode", ex);
+			throw ex;
+		}
+
+		// start thread to parse output
+		final BufferedReader br = new BufferedReader(new InputStreamReader(
+				this.torProcess.getInputStream()));
+		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 (nodeState == 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(this.nodeName + " Output Parser");
+		outputThread.start();
+		this.logger.log(Level.FINE, "Started thread to parse output!");
+
+		// add shutdown hook that kills the process on JVM exit
+		final Process p = this.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");
+			}
+		});
+		this.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
+		ProcessWaiter waiter = new ProcessWaiter(this.torProcess);
+		waiter.start();
+		int exitValue = waiter.waitFor(1000);
+		if (exitValue != 0) {
+			// Tor did not manage to start correctly
+			this.logger.log(Level.WARNING, "Could not start Tor process! Tor "
+					+ "exited with exit value " + exitValue
+					+ "! Please go check the config options in "
+					+ this.configFile + " manually!");
+
+			// log exiting
+			this.logger.exiting(this.getClass().getName(), "startNode", false);
+			return false;
+		}
+
+		// wait for Tor to open the control port
+		this.logger.log(Level.FINER,
+				"Waiting for Tor to open its control port...");
+		if (!this.eventManager.waitForAnyOccurence(this.nodeName,
+				NodeEventType.NODE_CONTROL_PORT_OPENED,
+				maximumTimeToWaitInMillis)) {
+
+			// Tor did not open its control port
+			this.logger.log(Level.WARNING, "Tor node " + this.nodeName
+					+ " did not manage to open its control port within "
+					+ maximumTimeToWaitInMillis + " milliseconds!");
+
+			// log exiting
+			this.logger.exiting(this.getClass().getName(), "startNode", false);
+			return false;
+		}
+		this.logger.log(Level.FINE,
+				"Tor has successfully opened its control port and told us "
+						+ "about that!");
+
+		// connect to the controller
+		this.logger.log(Level.FINER, "Connecting to control port...");
+		try {
+			Socket controlSocket = new java.net.Socket("127.0.0.1", controlPort);
+			this.conn = TorControlConnection.getConnection(controlSocket);
+			this.conn.authenticate(new byte[0]);
+		} catch (IOException e) {
+			String reason = "Could not connect to control port " + controlPort
+					+ "!";
+			PuppeTorException ex = new PuppeTorException(reason, e);
+			this.logger.throwing(this.getClass().getName(), "startNode", ex);
+			throw ex;
+		}
+		this.logger.log(Level.FINE, "Connected to control port successfully!");
+
+		// set state to RUNNING
+		this.nodeState = NodeState.RUNNING;
+
+		// fire event
+		eventManager.observeInternalEvent(System.currentTimeMillis(), this
+				.getNodeName(), NodeEventType.NODE_STARTED, "Node started.");
+
+		// log exiting and return with success
+		this.logger.exiting(this.getClass().getName(), "startNode", true);
+		return true;
+	}
+
+	@Override
+	public String toString() {
+		return this.getClass().getSimpleName() + ": nodeName=\""
+				+ this.nodeName + "\", controlPort=" + this.controlPort
+				+ ", socksPort=" + this.socksPort;
+	}
+
+	public synchronized void writeConfiguration() throws PuppeTorException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "writeConfiguration");
+
+		// write config file
+		try {
+			BufferedWriter bw = new BufferedWriter(new FileWriter(
+					this.configFile));
+			for (String c : this.configuration) {
+				bw.write(c + "\n");
+			}
+			bw.close();
+		} catch (IOException e) {
+			PuppeTorException ex = new PuppeTorException(
+					"Could not write configuration file!", e);
+			this.logger.throwing(this.getClass().getName(),
+					"writeConfigurationFile", ex);
+			throw ex;
+		}
+
+		// change state, if necessary
+		if (this.nodeState == NodeState.CONFIGURING) {
+			this.nodeState = NodeState.CONFIGURATION_WRITTEN;
+		}
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "writeConfiguration");
+	}
+
+	public int getSocksPort() {
+		return this.socksPort;
+	}
+
+	public int getControlPort() {
+		return this.controlPort;
+	}
+
+	public List<String> getConfiguration() {
+		return new ArrayList<String>(this.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");
+
+	}
+}
diff --git a/src/org/torproject/puppetor/impl/RouterNodeImpl.java b/src/org/torproject/puppetor/impl/RouterNodeImpl.java
old mode 100755
new mode 100644
index a0e57ea..ce12d56
--- a/src/org/torproject/puppetor/impl/RouterNodeImpl.java
+++ b/src/org/torproject/puppetor/impl/RouterNodeImpl.java
@@ -1,418 +1,418 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor.impl;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.rmi.RemoteException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.regex.Pattern;
-
-import org.torproject.puppetor.PuppeTorException;
-import org.torproject.puppetor.RouterNode;
-
-
-/**
- * Implementation of <code>RouterNode</code>.
- * 
- * @author kloesing
- */
- at SuppressWarnings("serial")
-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
-			File tempConfigFile = new File(RouterNodeImpl.this.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
-			List<String> copyOfConfig = new ArrayList<String>(
-					RouterNodeImpl.this.configuration);
-			String fakeDirServerString = "DirServer "
-					+ RouterNodeImpl.this.nodeName + " 127.0.0.1:"
-					+ RouterNodeImpl.this.dirPort
-					+ " 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000";
-			copyOfConfig.add(fakeDirServerString);
-
-			// write config file
-			try {
-				BufferedWriter bw = new BufferedWriter(new FileWriter(
-						tempConfigFile));
-				for (String c : copyOfConfig) {
-					bw.write(c + "\n");
-				}
-				bw.close();
-			} catch (IOException e) {
-				PuppeTorException ex = new PuppeTorException(
-						"Could not write configuration file!", e);
-				logger.log(Level.WARNING, "Could not start Tor process!", ex);
-				RouterNodeImpl.this.setCaughtException(ex);
-				return;
-			}
-
-			// start process with option --list-fingerprint
-			ProcessBuilder processBuilder = new ProcessBuilder(torExecutable
-					.getPath(), "--list-fingerprint", "-f", "torrc.tmp");
-			processBuilder.directory(RouterNodeImpl.this.workingDir);
-			processBuilder.redirectErrorStream(true);
-			Process tmpProcess = null;
-			try {
-				tmpProcess = processBuilder.start();
-			} catch (IOException e) {
-				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);
-				RouterNodeImpl.this.setCaughtException(ex);
-				return;
-			}
-
-			// wait for process to terminate
-			int exitValue = 0;
-			try {
-				exitValue = tmpProcess.waitFor();
-			} catch (InterruptedException e) {
-				PuppeTorException ex = new PuppeTorException(
-						"Interrupted while waiting for Tor process to exit!", e);
-				logger.log(Level.WARNING,
-						"Temporary Tor process was interrupted!", ex);
-				RouterNodeImpl.this.setCaughtException(ex);
-				return;
-			}
-
-			if (exitValue != 0) {
-				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);
-				RouterNodeImpl.this.setCaughtException(ex);
-				return;
-			}
-
-			// read fingerprint from file
-			File fingerprintFile = new File(RouterNodeImpl.this.workingDir
-					.getAbsolutePath()
-					+ File.separator + "fingerprint");
-			try {
-				BufferedReader br2 = new BufferedReader(new FileReader(
-						fingerprintFile));
-				RouterNodeImpl.this.setFingerprint(br2.readLine());
-				br2.close();
-			} catch (IOException e) {
-				PuppeTorException ex = new PuppeTorException(
-						"Could not read fingerprint from file!", e);
-				logger.log(Level.WARNING, "Could not read fingerprint file!",
-						ex);
-				RouterNodeImpl.this.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(String fingerprint) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "setFingerprint",
-				fingerprint);
-
-		// remember fingerprint and notify all waiting threads
-		this.fingerprint = fingerprint;
-		this.notifyAll();
-
-		// log exiting
-		this.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(
-			PuppeTorException caughtException) {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "setCaughtException",
-				caughtException);
-
-		// remember caught exception and notify all waiting threads
-		this.caughtException = caughtException;
-		this.notifyAll();
-
-		// log exiting
-		this.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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	RouterNodeImpl(NetworkImpl network, String nodeName, int controlPort,
-			int socksPort, int orPort, int dirPort, String serverIpAddress)
-			throws RemoteException {
-
-		// create superclass instance; parameter checking is done in super
-		// constructor
-		super(network, nodeName, controlPort, socksPort);
-
-		// log entering
-		this.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()) {
-			IllegalArgumentException e = new IllegalArgumentException(
-					"nodeName=" + nodeName + ", controlPort=" + controlPort
-							+ ", socksPort=" + socksPort + ", orPort="
-							+ orPort + ", dirPort=" + dirPort
-							+ ", serverIpAddress='" + serverIpAddress + "'");
-			this.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
-		this.configuration.addAll(templateConfiguration);
-
-		// add further configuration to make this node a router node
-		this.configuration.add("ORPort " + orPort);
-		this.configuration.add("Nickname " + nodeName);
-
-		// all routers mirror the directory
-		this.configuration.add("DirPort " + dirPort);
-
-		// the address of this node should be manually specified and not guessed
-		// by Tor
-		this.configuration.add("Address " + serverIpAddress);
-
-		// the OR port may only be contacted locally
-		this.configuration.add("ORListenAddress " + serverIpAddress);
-
-		// offer directory only locally (either by being an authority, or by
-		// mirroring it)
-		this.configuration.add("DirListenAddress " + serverIpAddress);
-
-		// start a thread to determine the node's fingerprint in the background
-		this.determineFingerprint();
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "RouterNodeImpl");
-	}
-
-	public synchronized String getFingerprint() throws PuppeTorException,
-			RemoteException {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "getFingerprint");
-
-		// wait until either the fingerprint has been determined or an exception
-		// was caught
-		while (this.fingerprint == null && this.caughtException == null) {
-			try {
-				wait();
-			} catch (InterruptedException e) {
-				// do nothing
-			}
-		}
-
-		if (this.caughtException != null) {
-			this.logger.throwing(this.getClass().getName(), "getFingerprint",
-					this.caughtException);
-			throw this.caughtException;
-		}
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "getFingerprint",
-				this.fingerprint);
-		return this.fingerprint;
-	}
-
-	@Override
-	public String toString() {
-		return super.toString() + ", orPort=" + this.orPort + ", dirPort="
-				+ this.dirPort;
-	}
-
-	public int getDirPort() {
-		return this.dirPort;
-	}
-
-	public int getOrPort() {
-		return this.orPort;
-	}
-
-	/**
-	 * Determines the fingerprint of this node by starting a background thread
-	 * that performs this operation.
-	 */
-	protected synchronized void determineFingerprint() {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "determineFingerprint");
-
-		// start a thread to determine this node's fingerprint
-		FingerprintThread fingerprintThread = new FingerprintThread();
-		fingerprintThread.setName(nodeName + " Fingerprint Resolver");
-		fingerprintThread.start();
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "determineFingerprint");
-	}
-
-	/**
-	 * Template configuration of router nodes.
-	 */
-	static List<String> templateConfiguration;
-
-	static {
-		templateConfiguration = new ArrayList<String>();
-
-		templateConfiguration.add("ContactInfo wont at reply.org");
-		templateConfiguration.add("HidServDirectoryV2 1");
-	}
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor.impl;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.regex.Pattern;
+
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.RouterNode;
+
+
+/**
+ * Implementation of <code>RouterNode</code>.
+ * 
+ * @author kloesing
+ */
+ at SuppressWarnings("serial")
+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
+			File tempConfigFile = new File(RouterNodeImpl.this.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
+			List<String> copyOfConfig = new ArrayList<String>(
+					RouterNodeImpl.this.configuration);
+			String fakeDirServerString = "DirServer "
+					+ RouterNodeImpl.this.nodeName + " 127.0.0.1:"
+					+ RouterNodeImpl.this.dirPort
+					+ " 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000";
+			copyOfConfig.add(fakeDirServerString);
+
+			// write config file
+			try {
+				BufferedWriter bw = new BufferedWriter(new FileWriter(
+						tempConfigFile));
+				for (String c : copyOfConfig) {
+					bw.write(c + "\n");
+				}
+				bw.close();
+			} catch (IOException e) {
+				PuppeTorException ex = new PuppeTorException(
+						"Could not write configuration file!", e);
+				logger.log(Level.WARNING, "Could not start Tor process!", ex);
+				RouterNodeImpl.this.setCaughtException(ex);
+				return;
+			}
+
+			// start process with option --list-fingerprint
+			ProcessBuilder processBuilder = new ProcessBuilder(torExecutable
+					.getPath(), "--list-fingerprint", "-f", "torrc.tmp");
+			processBuilder.directory(RouterNodeImpl.this.workingDir);
+			processBuilder.redirectErrorStream(true);
+			Process tmpProcess = null;
+			try {
+				tmpProcess = processBuilder.start();
+			} catch (IOException e) {
+				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);
+				RouterNodeImpl.this.setCaughtException(ex);
+				return;
+			}
+
+			// wait for process to terminate
+			int exitValue = 0;
+			try {
+				exitValue = tmpProcess.waitFor();
+			} catch (InterruptedException e) {
+				PuppeTorException ex = new PuppeTorException(
+						"Interrupted while waiting for Tor process to exit!", e);
+				logger.log(Level.WARNING,
+						"Temporary Tor process was interrupted!", ex);
+				RouterNodeImpl.this.setCaughtException(ex);
+				return;
+			}
+
+			if (exitValue != 0) {
+				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);
+				RouterNodeImpl.this.setCaughtException(ex);
+				return;
+			}
+
+			// read fingerprint from file
+			File fingerprintFile = new File(RouterNodeImpl.this.workingDir
+					.getAbsolutePath()
+					+ File.separator + "fingerprint");
+			try {
+				BufferedReader br2 = new BufferedReader(new FileReader(
+						fingerprintFile));
+				RouterNodeImpl.this.setFingerprint(br2.readLine());
+				br2.close();
+			} catch (IOException e) {
+				PuppeTorException ex = new PuppeTorException(
+						"Could not read fingerprint from file!", e);
+				logger.log(Level.WARNING, "Could not read fingerprint file!",
+						ex);
+				RouterNodeImpl.this.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(String fingerprint) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "setFingerprint",
+				fingerprint);
+
+		// remember fingerprint and notify all waiting threads
+		this.fingerprint = fingerprint;
+		this.notifyAll();
+
+		// log exiting
+		this.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(
+			PuppeTorException caughtException) {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "setCaughtException",
+				caughtException);
+
+		// remember caught exception and notify all waiting threads
+		this.caughtException = caughtException;
+		this.notifyAll();
+
+		// log exiting
+		this.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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	RouterNodeImpl(NetworkImpl network, String nodeName, int controlPort,
+			int socksPort, int orPort, int dirPort, String serverIpAddress)
+			throws RemoteException {
+
+		// create superclass instance; parameter checking is done in super
+		// constructor
+		super(network, nodeName, controlPort, socksPort);
+
+		// log entering
+		this.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()) {
+			IllegalArgumentException e = new IllegalArgumentException(
+					"nodeName=" + nodeName + ", controlPort=" + controlPort
+							+ ", socksPort=" + socksPort + ", orPort="
+							+ orPort + ", dirPort=" + dirPort
+							+ ", serverIpAddress='" + serverIpAddress + "'");
+			this.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
+		this.configuration.addAll(templateConfiguration);
+
+		// add further configuration to make this node a router node
+		this.configuration.add("ORPort " + orPort);
+		this.configuration.add("Nickname " + nodeName);
+
+		// all routers mirror the directory
+		this.configuration.add("DirPort " + dirPort);
+
+		// the address of this node should be manually specified and not guessed
+		// by Tor
+		this.configuration.add("Address " + serverIpAddress);
+
+		// the OR port may only be contacted locally
+		this.configuration.add("ORListenAddress " + serverIpAddress);
+
+		// offer directory only locally (either by being an authority, or by
+		// mirroring it)
+		this.configuration.add("DirListenAddress " + serverIpAddress);
+
+		// start a thread to determine the node's fingerprint in the background
+		this.determineFingerprint();
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "RouterNodeImpl");
+	}
+
+	public synchronized String getFingerprint() throws PuppeTorException,
+			RemoteException {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "getFingerprint");
+
+		// wait until either the fingerprint has been determined or an exception
+		// was caught
+		while (this.fingerprint == null && this.caughtException == null) {
+			try {
+				wait();
+			} catch (InterruptedException e) {
+				// do nothing
+			}
+		}
+
+		if (this.caughtException != null) {
+			this.logger.throwing(this.getClass().getName(), "getFingerprint",
+					this.caughtException);
+			throw this.caughtException;
+		}
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "getFingerprint",
+				this.fingerprint);
+		return this.fingerprint;
+	}
+
+	@Override
+	public String toString() {
+		return super.toString() + ", orPort=" + this.orPort + ", dirPort="
+				+ this.dirPort;
+	}
+
+	public int getDirPort() {
+		return this.dirPort;
+	}
+
+	public int getOrPort() {
+		return this.orPort;
+	}
+
+	/**
+	 * Determines the fingerprint of this node by starting a background thread
+	 * that performs this operation.
+	 */
+	protected synchronized void determineFingerprint() {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "determineFingerprint");
+
+		// start a thread to determine this node's fingerprint
+		FingerprintThread fingerprintThread = new FingerprintThread();
+		fingerprintThread.setName(nodeName + " Fingerprint Resolver");
+		fingerprintThread.start();
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "determineFingerprint");
+	}
+
+	/**
+	 * Template configuration of router nodes.
+	 */
+	static List<String> templateConfiguration;
+
+	static {
+		templateConfiguration = new ArrayList<String>();
+
+		templateConfiguration.add("ContactInfo wont at reply.org");
+		templateConfiguration.add("HidServDirectoryV2 1");
+	}
+}
diff --git a/src/org/torproject/puppetor/impl/ServerApplicationImpl.java b/src/org/torproject/puppetor/impl/ServerApplicationImpl.java
old mode 100755
new mode 100644
index 321d34e..361dfae
--- a/src/org/torproject/puppetor/impl/ServerApplicationImpl.java
+++ b/src/org/torproject/puppetor/impl/ServerApplicationImpl.java
@@ -1,385 +1,385 @@
-/*
- * Copyright (c) 2007, Karsten Loesing
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * 
- *     * Neither the names of the copyright owners nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.torproject.puppetor.impl;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.rmi.RemoteException;
-import java.rmi.server.UnicastRemoteObject;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.torproject.puppetor.ServerApplication;
-import org.torproject.puppetor.ServerEventType;
-
-
-/**
- * Implementation of <code>ServerApplication</code>.
- * 
- * @author kloesing
- */
- at SuppressWarnings("serial")
-public class ServerApplicationImpl extends UnicastRemoteObject 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(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)
-				BufferedReader in = new BufferedReader(new InputStreamReader(
-						handleSocket.getInputStream()));
-				in.read();
-
-				// send event to event manager
-				eventManager.observeInternalEvent(System.currentTimeMillis(),
-						ServerApplicationImpl.this.getServerApplicationName(),
-						ServerEventType.SERVER_RECEIVING_REQUEST_SENDING_REPLY,
-						"Receiving request.");
-
-				// write response
-				PrintStream out = new PrintStream(handleSocket
-						.getOutputStream());
-				out.print("HTTP/1.0 200 OK\r\n");
-
-			} catch (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 (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
-			this.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 (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 (Exception e) {
-						logger
-								.log(
-										Level.SEVERE,
-										"Exception while accepting socket requests! Stopping listening!",
-										e);
-						break;
-					}
-					new HandlerThread(incomingConnection).start();
-				}
-
-			} catch (Exception e) {
-
-				// log that we have been interrupted
-				logger.log(Level.WARNING, "Server has been interrupted!", e);
-			}
-
-			// mark as disconnected
-			this.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
-			this.connected = false;
-			this.interrupt();
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "stopListening");
-		}
-	}
-
-	/**
-	 * Event manager that handles all events concerning this server application.
-	 */
-	private EventManagerImpl eventManager;
-
-	/**
-	 * Logger for this server which is called "server." plus the name of this
-	 * server application.
-	 */
-	private Logger logger;
-
-	/**
-	 * Name of this server application that is used as logger name of this node.
-	 */
-	private String serverApplicationName;
-
-	/**
-	 * Port on which this server will listen for incoming requests.
-	 */
-	private 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.
-	 * @throws RemoteException
-	 *             Thrown if an error occurs when accessed remotely.
-	 */
-	ServerApplicationImpl(NetworkImpl network, String serverApplicationName,
-			int serverPort) throws RemoteException {
-
-		// check if serverApplicationName can be used as logger name
-		if (serverApplicationName == null
-				|| serverApplicationName.length() == 0) {
-			throw new IllegalArgumentException(
-					"Invalid serverApplicationName: " + serverApplicationName);
-		}
-
-		// create logger
-		this.logger = Logger.getLogger("server." + serverApplicationName);
-
-		// log entering
-		this.logger.entering(this.getClass().getName(),
-				"ServerApplicationImpl", new Object[] { network,
-						serverApplicationName, serverPort });
-
-		// check parameters
-		if (network == null || serverPort < 0 || serverPort > 65535) {
-			IllegalArgumentException e = new IllegalArgumentException();
-			this.logger.throwing(this.getClass().getName(),
-					"ServerApplicationImpl", e);
-			throw e;
-		}
-
-		// remember parameters
-		this.serverApplicationName = serverApplicationName;
-		this.serverPort = serverPort;
-
-		// obtain reference on event manager
-		this.eventManager = network.getEventManagerImpl();
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "ServerApplicationImpl");
-	}
-
-	public synchronized void startListening() {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "listen");
-
-		// check if we are already listening
-		if (this.serverThread != null) {
-			IllegalStateException e = new IllegalStateException(
-					"We are already listening!");
-			this.logger.throwing(this.getClass().getName(), "listen", e);
-			throw e;
-		}
-
-		// create a thread that listens in the background
-		this.serverThread = new ListenThread();
-		this.serverThread.setName("Reply Thread");
-		this.serverThread.setDaemon(true);
-		this.serverThread.start();
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "listen");
-	}
-
-	public synchronized void stopListening() {
-
-		// log entering
-		this.logger.entering(this.getClass().getName(), "stopListening");
-
-		// check if we are listening
-		if (this.serverThread == null) {
-			IllegalStateException e = new IllegalStateException(
-					"We are not listening!");
-			this.logger.throwing(this.getClass().getName(), "stopListening", e);
-			throw e;
-		}
-
-		// log this event
-		this.logger.log(Level.FINE, "Shutting down server");
-
-		// interrupt thread
-		this.serverThread.interrupt();
-
-		// unset listening thread
-		this.serverThread = null;
-
-		// log exiting
-		this.logger.exiting(this.getClass().getName(), "stopListening");
-	}
-
-	public synchronized boolean isListening() {
-		return (this.serverThread != null);
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": serverApplicationName=\""
-				+ this.serverApplicationName + "\", serverPort="
-				+ this.serverPort;
-	}
-
-	public String getServerApplicationName() {
-		return this.serverApplicationName;
-	}
-
-	public int getServerPort() {
-		return this.serverPort;
-	}
-}
+/*
+ * Copyright (c) 2007, Karsten Loesing
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * 
+ *     * Neither the names of the copyright owners nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.torproject.puppetor.impl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.torproject.puppetor.ServerApplication;
+import org.torproject.puppetor.ServerEventType;
+
+
+/**
+ * Implementation of <code>ServerApplication</code>.
+ * 
+ * @author kloesing
+ */
+ at SuppressWarnings("serial")
+public class ServerApplicationImpl extends UnicastRemoteObject 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(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)
+				BufferedReader in = new BufferedReader(new InputStreamReader(
+						handleSocket.getInputStream()));
+				in.read();
+
+				// send event to event manager
+				eventManager.observeInternalEvent(System.currentTimeMillis(),
+						ServerApplicationImpl.this.getServerApplicationName(),
+						ServerEventType.SERVER_RECEIVING_REQUEST_SENDING_REPLY,
+						"Receiving request.");
+
+				// write response
+				PrintStream out = new PrintStream(handleSocket
+						.getOutputStream());
+				out.print("HTTP/1.0 200 OK\r\n");
+
+			} catch (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 (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
+			this.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 (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 (Exception e) {
+						logger
+								.log(
+										Level.SEVERE,
+										"Exception while accepting socket requests! Stopping listening!",
+										e);
+						break;
+					}
+					new HandlerThread(incomingConnection).start();
+				}
+
+			} catch (Exception e) {
+
+				// log that we have been interrupted
+				logger.log(Level.WARNING, "Server has been interrupted!", e);
+			}
+
+			// mark as disconnected
+			this.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
+			this.connected = false;
+			this.interrupt();
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "stopListening");
+		}
+	}
+
+	/**
+	 * Event manager that handles all events concerning this server application.
+	 */
+	private EventManagerImpl eventManager;
+
+	/**
+	 * Logger for this server which is called "server." plus the name of this
+	 * server application.
+	 */
+	private Logger logger;
+
+	/**
+	 * Name of this server application that is used as logger name of this node.
+	 */
+	private String serverApplicationName;
+
+	/**
+	 * Port on which this server will listen for incoming requests.
+	 */
+	private 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.
+	 * @throws RemoteException
+	 *             Thrown if an error occurs when accessed remotely.
+	 */
+	ServerApplicationImpl(NetworkImpl network, String serverApplicationName,
+			int serverPort) throws RemoteException {
+
+		// check if serverApplicationName can be used as logger name
+		if (serverApplicationName == null
+				|| serverApplicationName.length() == 0) {
+			throw new IllegalArgumentException(
+					"Invalid serverApplicationName: " + serverApplicationName);
+		}
+
+		// create logger
+		this.logger = Logger.getLogger("server." + serverApplicationName);
+
+		// log entering
+		this.logger.entering(this.getClass().getName(),
+				"ServerApplicationImpl", new Object[] { network,
+						serverApplicationName, serverPort });
+
+		// check parameters
+		if (network == null || serverPort < 0 || serverPort > 65535) {
+			IllegalArgumentException e = new IllegalArgumentException();
+			this.logger.throwing(this.getClass().getName(),
+					"ServerApplicationImpl", e);
+			throw e;
+		}
+
+		// remember parameters
+		this.serverApplicationName = serverApplicationName;
+		this.serverPort = serverPort;
+
+		// obtain reference on event manager
+		this.eventManager = network.getEventManagerImpl();
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "ServerApplicationImpl");
+	}
+
+	public synchronized void startListening() {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "listen");
+
+		// check if we are already listening
+		if (this.serverThread != null) {
+			IllegalStateException e = new IllegalStateException(
+					"We are already listening!");
+			this.logger.throwing(this.getClass().getName(), "listen", e);
+			throw e;
+		}
+
+		// create a thread that listens in the background
+		this.serverThread = new ListenThread();
+		this.serverThread.setName("Reply Thread");
+		this.serverThread.setDaemon(true);
+		this.serverThread.start();
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "listen");
+	}
+
+	public synchronized void stopListening() {
+
+		// log entering
+		this.logger.entering(this.getClass().getName(), "stopListening");
+
+		// check if we are listening
+		if (this.serverThread == null) {
+			IllegalStateException e = new IllegalStateException(
+					"We are not listening!");
+			this.logger.throwing(this.getClass().getName(), "stopListening", e);
+			throw e;
+		}
+
+		// log this event
+		this.logger.log(Level.FINE, "Shutting down server");
+
+		// interrupt thread
+		this.serverThread.interrupt();
+
+		// unset listening thread
+		this.serverThread = null;
+
+		// log exiting
+		this.logger.exiting(this.getClass().getName(), "stopListening");
+	}
+
+	public synchronized boolean isListening() {
+		return (this.serverThread != null);
+	}
+
+	@Override
+	public String toString() {
+		return this.getClass().getSimpleName() + ": serverApplicationName=\""
+				+ this.serverApplicationName + "\", serverPort="
+				+ this.serverPort;
+	}
+
+	public String getServerApplicationName() {
+		return this.serverApplicationName;
+	}
+
+	public int getServerPort() {
+		return this.serverPort;
+	}
+}
-- 
1.5.6.5



More information about the tor-commits mailing list