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

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



644 sounds saner than 755, and \n is nicer than \r\n.
---
 LICENSE                                            |  222 +-
 README                                             |   16 +-
 logging.properties                                 |    8 +-
 .../wiai/lspi/puppetor/ClientApplication.java      |  252 ++--
 src/de/uniba/wiai/lspi/puppetor/DirectoryNode.java |  150 +-
 src/de/uniba/wiai/lspi/puppetor/EventManager.java  |  514 +++---
 src/de/uniba/wiai/lspi/puppetor/EventType.java     |  104 +-
 src/de/uniba/wiai/lspi/puppetor/Network.java       | 1366 ++++++------
 .../uniba/wiai/lspi/puppetor/NetworkFactory.java   |  216 +-
 src/de/uniba/wiai/lspi/puppetor/NodeState.java     |  140 +-
 src/de/uniba/wiai/lspi/puppetor/ProxyNode.java     |  590 +++---
 .../wiai/lspi/puppetor/PuppeTorException.java      |  182 +-
 src/de/uniba/wiai/lspi/puppetor/RouterNode.java    |  162 +-
 .../wiai/lspi/puppetor/ServerApplication.java      |  168 +-
 .../examples/AccessingPublicWebServerOverTor.java  |  290 ++--
 ...ccessingHiddenServiceOverPrivateTorNetwork.java |  316 ++--
 ...AccessingHiddenServiceOverPublicTorNetwork.java |  350 ++--
 ...AdvertisingHiddenServiceToPublicTorNetwork.java |  272 ++--
 .../lspi/puppetor/impl/ClientApplicationImpl.java  |  966 +++++-----
 .../wiai/lspi/puppetor/impl/DirectoryNodeImpl.java |  914 ++++----
 .../wiai/lspi/puppetor/impl/EventManagerImpl.java  | 1510 +++++++-------
 .../uniba/wiai/lspi/puppetor/impl/NetworkImpl.java | 2216 ++++++++++----------
 .../wiai/lspi/puppetor/impl/ProxyNodeImpl.java     | 1520 +++++++-------
 .../wiai/lspi/puppetor/impl/RouterNodeImpl.java    |  856 ++++----
 .../lspi/puppetor/impl/ServerApplicationImpl.java  |  754 ++++----
 25 files changed, 7027 insertions(+), 7027 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/de/uniba/wiai/lspi/puppetor/ClientApplication.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/DirectoryNode.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/Event.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/EventListener.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/EventManager.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/EventType.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/Network.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/NetworkFactory.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/NodeState.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/ProxyNode.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/PuppeTorException.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/RouterNode.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/ServerApplication.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/examples/AccessingPublicWebServerOverTor.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/impl/ClientApplicationImpl.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/impl/DirectoryNodeImpl.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/impl/EventManagerImpl.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/impl/NetworkImpl.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/impl/ProxyNodeImpl.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/impl/RouterNodeImpl.java
 mode change 100755 => 100644 src/de/uniba/wiai/lspi/puppetor/impl/ServerApplicationImpl.java
 mode change 100755 => 100644 tools/create_keystores.sh

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@xxxxxxxxxxxxx
-
-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@xxxxxxxxxxxxx
+
+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
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/de/uniba/wiai/lspi/puppetor/ClientApplication.java b/src/de/uniba/wiai/lspi/puppetor/ClientApplication.java
old mode 100755
new mode 100644
index 97caf68..093fc99
--- a/src/de/uniba/wiai/lspi/puppetor/ClientApplication.java
+++ b/src/de/uniba/wiai/lspi/puppetor/ClientApplication.java
@@ -1,126 +1,126 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor;
-
-/**
- * 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 {
-
-	/**
-	 * <p>
-	 * Performs one or more HTTP requests to a previously provided address and
-	 * port. All requests are performed by a thread in the background, so that
-	 * this method returns immediately. That thread will try for
-	 * <code>retries</code> times to make the request with a timeout of
-	 * <code>timeoutForEachRetry</code> milliseconds each. If an attempt is
-	 * not successful, the thread nevertheless waits for the timeout to expire
-	 * before performing the next attempt. If <code>stopOnSuccess</code> is
-	 * set to <code>true</code>, the thread will quit performing requests
-	 * immediately after the first successful request.
-	 * </p>
-	 *
-	 * <p>
-	 * For each sent request the application fires a
-	 * <event>ClientEventType.CLIENT_SENDING_REQUEST</code> event. On receiving
-	 * a reply it fires an event of type <code>ClientEventType.CLIENT_REPLY_RECEIVED</code>,
-	 * if a request is not successful or times out, a <code>ClientEventType.CLIENT_GAVE_UP_REQUEST</code>
-	 * event is fired. After all requests have been performed (either
-	 * successfully, or not) an event of type <code>ClientEventType.CLIENT_REQUESTS_PERFORMED</code>
-	 * is fired.
-	 * </p>
-	 *
-	 * TODO may this method only be invoked once?!
-	 *
-	 * @param retries
-	 *            The number of retries that this client will perform. Must be
-	 *            <code>1</code> or greater.
-	 * @param timeoutForEachRetry
-	 *            The timeout for each request. If a request is not successful,
-	 *            the thread nevertheless waits for the timeout to expire. Must
-	 *            not be negative.
-	 * @param stopOnSuccess
-	 *            If set to <code>true</code>, the client quits performing
-	 *            requests after the first successful request, if <code>false</code>
-	 *            it continues until all <code>retries</code> have been
-	 *            processed.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract void startRequests(int retries, long timeoutForEachRetry,
-			boolean stopOnSuccess);
-
-	/**
-	 * Stops all requests that are currently running.
-	 *
-	 * @throws IllegalStateException
-	 *             Thrown if no requests have been started before.
-	 */
-	public abstract void stopRequest();
-
-	/**
-	 * Returns the name of this client.
-	 *
-	 * @return The name of this client.
-	 */
-	public abstract String getClientApplicationName();
-
-	/**
-	 * Returns the SOCKS port of the local Tor node to which requests are sent.
-	 *
-	 * @return The SOCKS port of the local Tor node to which requests are sent.
-	 */
-	public abstract int getSocksPort();
-
-	/**
-	 * Returns the target name for the requests sent by this client; can be
-	 * either a server name/address or an onion address.
-	 *
-	 * @return The target name for the requests sent by this client.
-	 */
-	public abstract String getTargetName();
-
-	/**
-	 * Returns the target port for the requests sent by this client; can be
-	 * either a server port or a virtual port of a hidden service.
-	 *
-	 * @return The target port for the requests sent by this client.
-	 */
-	public abstract int getTargetPort();
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor;
+
+/**
+ * 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 {
+
+	/**
+	 * <p>
+	 * Performs one or more HTTP requests to a previously provided address and
+	 * port. All requests are performed by a thread in the background, so that
+	 * this method returns immediately. That thread will try for
+	 * <code>retries</code> times to make the request with a timeout of
+	 * <code>timeoutForEachRetry</code> milliseconds each. If an attempt is
+	 * not successful, the thread nevertheless waits for the timeout to expire
+	 * before performing the next attempt. If <code>stopOnSuccess</code> is
+	 * set to <code>true</code>, the thread will quit performing requests
+	 * immediately after the first successful request.
+	 * </p>
+	 *
+	 * <p>
+	 * For each sent request the application fires a
+	 * <event>ClientEventType.CLIENT_SENDING_REQUEST</code> event. On receiving
+	 * a reply it fires an event of type <code>ClientEventType.CLIENT_REPLY_RECEIVED</code>,
+	 * if a request is not successful or times out, a <code>ClientEventType.CLIENT_GAVE_UP_REQUEST</code>
+	 * event is fired. After all requests have been performed (either
+	 * successfully, or not) an event of type <code>ClientEventType.CLIENT_REQUESTS_PERFORMED</code>
+	 * is fired.
+	 * </p>
+	 *
+	 * TODO may this method only be invoked once?!
+	 *
+	 * @param retries
+	 *            The number of retries that this client will perform. Must be
+	 *            <code>1</code> or greater.
+	 * @param timeoutForEachRetry
+	 *            The timeout for each request. If a request is not successful,
+	 *            the thread nevertheless waits for the timeout to expire. Must
+	 *            not be negative.
+	 * @param stopOnSuccess
+	 *            If set to <code>true</code>, the client quits performing
+	 *            requests after the first successful request, if <code>false</code>
+	 *            it continues until all <code>retries</code> have been
+	 *            processed.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for either of the
+	 *             parameters.
+	 */
+	public abstract void startRequests(int retries, long timeoutForEachRetry,
+			boolean stopOnSuccess);
+
+	/**
+	 * Stops all requests that are currently running.
+	 *
+	 * @throws IllegalStateException
+	 *             Thrown if no requests have been started before.
+	 */
+	public abstract void stopRequest();
+
+	/**
+	 * Returns the name of this client.
+	 *
+	 * @return The name of this client.
+	 */
+	public abstract String getClientApplicationName();
+
+	/**
+	 * Returns the SOCKS port of the local Tor node to which requests are sent.
+	 *
+	 * @return The SOCKS port of the local Tor node to which requests are sent.
+	 */
+	public abstract int getSocksPort();
+
+	/**
+	 * Returns the target name for the requests sent by this client; can be
+	 * either a server name/address or an onion address.
+	 *
+	 * @return The target name for the requests sent by this client.
+	 */
+	public abstract String getTargetName();
+
+	/**
+	 * Returns the target port for the requests sent by this client; can be
+	 * either a server port or a virtual port of a hidden service.
+	 *
+	 * @return The target port for the requests sent by this client.
+	 */
+	public abstract int getTargetPort();
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/DirectoryNode.java b/src/de/uniba/wiai/lspi/puppetor/DirectoryNode.java
old mode 100755
new mode 100644
index 4a6f739..7bb94cf
--- a/src/de/uniba/wiai/lspi/puppetor/DirectoryNode.java
+++ b/src/de/uniba/wiai/lspi/puppetor/DirectoryNode.java
@@ -1,75 +1,75 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor;
-
-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.
-	 */
-	public abstract String getDirServerString() throws PuppeTorException;
-
-	/**
-	 * Adds the given (possibly empty) set of onion router fingerprints to the
-	 * set of approved routers to confirm to directory clients, that the given
-	 * routers can be trusted. Changes are only stored locally and not written
-	 * to the <code>approved-routers</code> file to disk which will be done
-	 * when writing the configuration of this node.
-	 *
-	 * @param approvedRouters
-	 *            The set of approved routers to be added. Each provided string
-	 *            must be formatted as
-	 *            <code>nickname 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed as parameter.
-	 */
-	public void addApprovedRouters(Set<String> approvedRouters);
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor;
+
+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.
+	 */
+	public abstract String getDirServerString() throws PuppeTorException;
+
+	/**
+	 * Adds the given (possibly empty) set of onion router fingerprints to the
+	 * set of approved routers to confirm to directory clients, that the given
+	 * routers can be trusted. Changes are only stored locally and not written
+	 * to the <code>approved-routers</code> file to disk which will be done
+	 * when writing the configuration of this node.
+	 *
+	 * @param approvedRouters
+	 *            The set of approved routers to be added. Each provided string
+	 *            must be formatted as
+	 *            <code>nickname 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000</code>.
+	 * @throws IllegalArgumentException
+	 *             Thrown if <code>null</code> is passed as parameter.
+	 */
+	public void addApprovedRouters(Set<String> approvedRouters);
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/Event.java b/src/de/uniba/wiai/lspi/puppetor/Event.java
old mode 100755
new mode 100644
diff --git a/src/de/uniba/wiai/lspi/puppetor/EventListener.java b/src/de/uniba/wiai/lspi/puppetor/EventListener.java
old mode 100755
new mode 100644
diff --git a/src/de/uniba/wiai/lspi/puppetor/EventManager.java b/src/de/uniba/wiai/lspi/puppetor/EventManager.java
old mode 100755
new mode 100644
index 5ae74fe..ebbac42
--- a/src/de/uniba/wiai/lspi/puppetor/EventManager.java
+++ b/src/de/uniba/wiai/lspi/puppetor/EventManager.java
@@ -1,257 +1,257 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor;
-
-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 {
-
-	/**
-	 * Registers the given <code>listener</code> as event listener for events
-	 * originating from the given <code>source</code>. This method returns a
-	 * list of all previously fired events by this source, so that each event
-	 * fired by this source is either included in the returned list or
-	 * signalized in a later invocation on the event listener, but not in both.
-	 * This prevents race conditions by eliminating the gap between registration
-	 * of an event handler and asking if an event has been fired before
-	 * registering.
-	 *
-	 * @param source
-	 *            The name of the source of events that the listener is
-	 *            interested in. May not be <code>null</code> and must be the
-	 *            name of a previously created node, client, or server.
-	 * @param listener
-	 *            The listener that wants to be notified about events from the
-	 *            given <code>source</code>. If the <code>listener</code>
-	 *            is already registered for the same <code>source</code>,
-	 *            nothing happens, i.e. the <code>listener</code> will not
-	 *            receive multiple invocations for the same event. May not be
-	 *            <code>null</code>.
-	 * @return A list of all previously fired events for the given
-	 *         <code>source</code>. If no event has been fired before, an
-	 *         empty list is returned instead of <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed for either of the
-	 *             parameters or if the <code>source</code> is unknown.
-	 */
-	public abstract List<Event> addEventListener(String source,
-			EventListener listener);
-
-	/**
-	 * Registers the given <code>listener</code> as event listener for future
-	 * events originating from any source.
-	 *
-	 * @param listener
-	 *            The listener that wants to be notified about events from the
-	 *            given <code>source</code>. If the <code>listener</code>
-	 *            is already registered for all sources, nothing happens, i.e.
-	 *            the <code>listener</code> will not receive multiple
-	 *            invocations for the same event. May not be <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed for the parameter.
-	 */
-	public abstract void addEventListener(EventListener listener);
-
-	/**
-	 * Returns the list of all previously observed events from the given
-	 * <code>source</code>.
-	 *
-	 * @param source
-	 *            The source of the events that the invoking thread is
-	 *            interested in. May not be <code>null</code> and must be the
-	 *            name of a previously created node, client, or server.
-	 * @return List of all previously observed events from the given
-	 *         <code>source</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed as parameter or if
-	 *             the <code>source</code> is unknown.
-	 */
-	public abstract List<Event> getEventHistory(String source);
-
-	/**
-	 * Returns whether the given <code>eventType</code> has been observed from
-	 * the given <code>source</code> before, or not.
-	 *
-	 * @param source
-	 *            The source of the event that the invoking thread is interested
-	 *            in. May not be <code>null</code> and must be the name of a
-	 *            previously created node, client, or server.
-	 * @param eventType
-	 *            The event type that the invoking thread is interested in. May
-	 *            not be <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed for either of the
-	 *             parameters or if <code>source</code> is unknown.
-	 * @return <code>true</code> if the event has been observed from the
-	 *         source before, <code>false</code> otherwise.
-	 */
-	public abstract boolean hasEventOccured(String source, EventType eventType);
-
-	/**
-	 * Removes the given <code>listener</code> as event listener from all
-	 * previously registered sources. If this listener is not registered for any
-	 * source, nothing happens.
-	 *
-	 * @param listener
-	 *            The listener that shall be removed from the list of registered
-	 *            listeners. May not be <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed as parameter.
-	 */
-	public abstract void removeEventListener(EventListener listener);
-
-	/**
-	 * Checks if the given <code>eventType</code> has been observed from the
-	 * given <code>source</code> before; if not, blocks the invoking thread
-	 * until the next event of this type is fired from that source. Note that
-	 * this method does not restrict waiting to a timeout, so that it could
-	 * potentially block forever!
-	 *
-	 * @param source
-	 *            The source of the event that the invoking thread is willing to
-	 *            wait for. May not be <code>null</code> and must be the name
-	 *            of a previously created node, client, or server.
-	 * @param eventType
-	 *            The event type that the invoking thread is willing to wait for
-	 *            from the given <code>source</code>. May not be
-	 *            <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed for either of the
-	 *             parameters or if the <code>source</code> is unknown.
-	 */
-	public abstract void waitForAnyOccurence(String source, EventType eventType);
-
-	/**
-	 * Checks if the given <code>eventType</code> has been observed from the
-	 * given <code>source</code> before; if not, blocks the invoking thread
-	 * until the next event of this type is fired from that source or the given
-	 * timeout of <code>maximumTimeToWaitInMillis</code> milliseconds has
-	 * expired.
-	 *
-	 * @param source
-	 *            The source of the event that the invoking thread is willing to
-	 *            wait for. May not be <code>null</code> and must be the name
-	 *            of a previously created node, client, or server.
-	 * @param eventType
-	 *            The event type that the invoking thread is willing to wait for
-	 *            from the given <code>source</code>. May not be
-	 *            <code>null</code>.
-	 * @param maximumTimeToWaitInMillis
-	 *            The maximum time to wait in milliseconds. A positive value or
-	 *            zero restricts waiting to this time. If this value is
-	 *            negative, we will wait potentially forever.
-	 * @return <code>true</code> if an event of the given type has been fired
-	 *         by the <code>source</code> within the given timeout,
-	 *         <code>false</code> otherwise.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is passed for either of the
-	 *             parameters or if the <code>source</code> is unknown.
-	 */
-	public abstract boolean waitForAnyOccurence(String source,
-			EventType eventType, long maximumTimeToWaitInMillis);
-
-	/**
-	 * Blocks the invoking thread until the next <code>event</code> is fired
-	 * from the given <code>source</code>. This method only waits for the
-	 * next occurence of an event, regardless of previous occurrences. Note that
-	 * this method does not restrict waiting to a timeout, so that it could
-	 * potentially block forever!
-	 *
-	 * @param source
-	 *            The source of the event that the invoking thread is willing to
-	 *            wait for. May not be <code>null</code> and must be the name
-	 *            of a previously created node, client, or server.
-	 * @param eventType
-	 *            The event type that the invoking thread is willing to wait for
-	 *            from the given <code>source</code>. May not be
-	 *            <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> is passed for either of the
-	 *             parameters or if the <code>source</code> is unknown.
-	 */
-	public abstract void waitForNextOccurence(String source, EventType eventType);
-
-	/**
-	 * Blocks the invoking thread until the next <code>event</code> is fired
-	 * from the given <code>source</code> or the given timeout of
-	 * <code>maximumTimeToWaitInMillis</code> milliseconds has expired. This
-	 * method only waits for the next occurence of an event, regardless of
-	 * previous occurrences.
-	 *
-	 * @param source
-	 *            The source of the event that the invoking thread is willing to
-	 *            wait for. May not be <code>null</code> and must be the name
-	 *            of a previously created node, client, or server.
-	 * @param eventType
-	 *            The event type that the invoking thread is willing to wait for
-	 *            from the given <code>source</code>. May not be
-	 *            <code>null</code>.
-	 * @param maximumTimeToWaitInMillis
-	 *            The maximum time to wait in milliseconds. A positive value or
-	 *            zero restricts waiting to this time. If this value is
-	 *            negative, we will wait potentially forever.
-	 * @return <code>true</code> if an event of the given type has been fired
-	 *         by the <code>source</code> within the given timeout,
-	 *         <code>false</code> otherwise.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is passed for either of the
-	 *             parameters or if the <code>source</code> is unknown.
-	 */
-	public abstract boolean waitForNextOccurence(String source,
-			EventType eventType, long maximumTimeToWaitInMillis);
-
-	/**
-	 * Registers a new event type by passing a pattern string that can be
-	 * applied to a regular expression when parsing Tor log statements. This is
-	 * useful for log statements that are only included in modified Tor
-	 * versions. Therefore, the event type may be an instance of a self-defined
-	 * class that implements <code>EventType</code>.
-	 *
-	 * @param patternString
-	 *            The pattern string that will be used for parsing Tor log
-	 *            statements; the syntax corresponds to java.util.regex.Pattern.
-	 * @param eventType
-	 *            The event type of the event that will be fired when a log
-	 *            statement was parsed that includes the given pattern.
-	 */
-	public abstract void registerEventTypePattern(String patternString,
-			EventType eventType);
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor;
+
+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 {
+
+	/**
+	 * Registers the given <code>listener</code> as event listener for events
+	 * originating from the given <code>source</code>. This method returns a
+	 * list of all previously fired events by this source, so that each event
+	 * fired by this source is either included in the returned list or
+	 * signalized in a later invocation on the event listener, but not in both.
+	 * This prevents race conditions by eliminating the gap between registration
+	 * of an event handler and asking if an event has been fired before
+	 * registering.
+	 *
+	 * @param source
+	 *            The name of the source of events that the listener is
+	 *            interested in. May not be <code>null</code> and must be the
+	 *            name of a previously created node, client, or server.
+	 * @param listener
+	 *            The listener that wants to be notified about events from the
+	 *            given <code>source</code>. If the <code>listener</code>
+	 *            is already registered for the same <code>source</code>,
+	 *            nothing happens, i.e. the <code>listener</code> will not
+	 *            receive multiple invocations for the same event. May not be
+	 *            <code>null</code>.
+	 * @return A list of all previously fired events for the given
+	 *         <code>source</code>. If no event has been fired before, an
+	 *         empty list is returned instead of <code>null</code>.
+	 * @throws IllegalArgumentException
+	 *             Thrown if <code>null</code> is passed for either of the
+	 *             parameters or if the <code>source</code> is unknown.
+	 */
+	public abstract List<Event> addEventListener(String source,
+			EventListener listener);
+
+	/**
+	 * Registers the given <code>listener</code> as event listener for future
+	 * events originating from any source.
+	 *
+	 * @param listener
+	 *            The listener that wants to be notified about events from the
+	 *            given <code>source</code>. If the <code>listener</code>
+	 *            is already registered for all sources, nothing happens, i.e.
+	 *            the <code>listener</code> will not receive multiple
+	 *            invocations for the same event. May not be <code>null</code>.
+	 * @throws IllegalArgumentException
+	 *             Thrown if <code>null</code> is passed for the parameter.
+	 */
+	public abstract void addEventListener(EventListener listener);
+
+	/**
+	 * Returns the list of all previously observed events from the given
+	 * <code>source</code>.
+	 *
+	 * @param source
+	 *            The source of the events that the invoking thread is
+	 *            interested in. May not be <code>null</code> and must be the
+	 *            name of a previously created node, client, or server.
+	 * @return List of all previously observed events from the given
+	 *         <code>source</code>.
+	 * @throws IllegalArgumentException
+	 *             Thrown if <code>null</code> is passed as parameter or if
+	 *             the <code>source</code> is unknown.
+	 */
+	public abstract List<Event> getEventHistory(String source);
+
+	/**
+	 * Returns whether the given <code>eventType</code> has been observed from
+	 * the given <code>source</code> before, or not.
+	 *
+	 * @param source
+	 *            The source of the event that the invoking thread is interested
+	 *            in. May not be <code>null</code> and must be the name of a
+	 *            previously created node, client, or server.
+	 * @param eventType
+	 *            The event type that the invoking thread is interested in. May
+	 *            not be <code>null</code>.
+	 * @throws IllegalArgumentException
+	 *             Thrown if <code>null</code> is passed for either of the
+	 *             parameters or if <code>source</code> is unknown.
+	 * @return <code>true</code> if the event has been observed from the
+	 *         source before, <code>false</code> otherwise.
+	 */
+	public abstract boolean hasEventOccured(String source, EventType eventType);
+
+	/**
+	 * Removes the given <code>listener</code> as event listener from all
+	 * previously registered sources. If this listener is not registered for any
+	 * source, nothing happens.
+	 *
+	 * @param listener
+	 *            The listener that shall be removed from the list of registered
+	 *            listeners. May not be <code>null</code>.
+	 * @throws IllegalArgumentException
+	 *             Thrown if <code>null</code> is passed as parameter.
+	 */
+	public abstract void removeEventListener(EventListener listener);
+
+	/**
+	 * Checks if the given <code>eventType</code> has been observed from the
+	 * given <code>source</code> before; if not, blocks the invoking thread
+	 * until the next event of this type is fired from that source. Note that
+	 * this method does not restrict waiting to a timeout, so that it could
+	 * potentially block forever!
+	 *
+	 * @param source
+	 *            The source of the event that the invoking thread is willing to
+	 *            wait for. May not be <code>null</code> and must be the name
+	 *            of a previously created node, client, or server.
+	 * @param eventType
+	 *            The event type that the invoking thread is willing to wait for
+	 *            from the given <code>source</code>. May not be
+	 *            <code>null</code>.
+	 * @throws IllegalArgumentException
+	 *             Thrown if <code>null</code> is passed for either of the
+	 *             parameters or if the <code>source</code> is unknown.
+	 */
+	public abstract void waitForAnyOccurence(String source, EventType eventType);
+
+	/**
+	 * Checks if the given <code>eventType</code> has been observed from the
+	 * given <code>source</code> before; if not, blocks the invoking thread
+	 * until the next event of this type is fired from that source or the given
+	 * timeout of <code>maximumTimeToWaitInMillis</code> milliseconds has
+	 * expired.
+	 *
+	 * @param source
+	 *            The source of the event that the invoking thread is willing to
+	 *            wait for. May not be <code>null</code> and must be the name
+	 *            of a previously created node, client, or server.
+	 * @param eventType
+	 *            The event type that the invoking thread is willing to wait for
+	 *            from the given <code>source</code>. May not be
+	 *            <code>null</code>.
+	 * @param maximumTimeToWaitInMillis
+	 *            The maximum time to wait in milliseconds. A positive value or
+	 *            zero restricts waiting to this time. If this value is
+	 *            negative, we will wait potentially forever.
+	 * @return <code>true</code> if an event of the given type has been fired
+	 *         by the <code>source</code> within the given timeout,
+	 *         <code>false</code> otherwise.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is passed for either of the
+	 *             parameters or if the <code>source</code> is unknown.
+	 */
+	public abstract boolean waitForAnyOccurence(String source,
+			EventType eventType, long maximumTimeToWaitInMillis);
+
+	/**
+	 * Blocks the invoking thread until the next <code>event</code> is fired
+	 * from the given <code>source</code>. This method only waits for the
+	 * next occurence of an event, regardless of previous occurrences. Note that
+	 * this method does not restrict waiting to a timeout, so that it could
+	 * potentially block forever!
+	 *
+	 * @param source
+	 *            The source of the event that the invoking thread is willing to
+	 *            wait for. May not be <code>null</code> and must be the name
+	 *            of a previously created node, client, or server.
+	 * @param eventType
+	 *            The event type that the invoking thread is willing to wait for
+	 *            from the given <code>source</code>. May not be
+	 *            <code>null</code>.
+	 * @throws IllegalArgumentException
+	 *             Thrown if <code>null</code> is passed for either of the
+	 *             parameters or if the <code>source</code> is unknown.
+	 */
+	public abstract void waitForNextOccurence(String source, EventType eventType);
+
+	/**
+	 * Blocks the invoking thread until the next <code>event</code> is fired
+	 * from the given <code>source</code> or the given timeout of
+	 * <code>maximumTimeToWaitInMillis</code> milliseconds has expired. This
+	 * method only waits for the next occurence of an event, regardless of
+	 * previous occurrences.
+	 *
+	 * @param source
+	 *            The source of the event that the invoking thread is willing to
+	 *            wait for. May not be <code>null</code> and must be the name
+	 *            of a previously created node, client, or server.
+	 * @param eventType
+	 *            The event type that the invoking thread is willing to wait for
+	 *            from the given <code>source</code>. May not be
+	 *            <code>null</code>.
+	 * @param maximumTimeToWaitInMillis
+	 *            The maximum time to wait in milliseconds. A positive value or
+	 *            zero restricts waiting to this time. If this value is
+	 *            negative, we will wait potentially forever.
+	 * @return <code>true</code> if an event of the given type has been fired
+	 *         by the <code>source</code> within the given timeout,
+	 *         <code>false</code> otherwise.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is passed for either of the
+	 *             parameters or if the <code>source</code> is unknown.
+	 */
+	public abstract boolean waitForNextOccurence(String source,
+			EventType eventType, long maximumTimeToWaitInMillis);
+
+	/**
+	 * Registers a new event type by passing a pattern string that can be
+	 * applied to a regular expression when parsing Tor log statements. This is
+	 * useful for log statements that are only included in modified Tor
+	 * versions. Therefore, the event type may be an instance of a self-defined
+	 * class that implements <code>EventType</code>.
+	 *
+	 * @param patternString
+	 *            The pattern string that will be used for parsing Tor log
+	 *            statements; the syntax corresponds to java.util.regex.Pattern.
+	 * @param eventType
+	 *            The event type of the event that will be fired when a log
+	 *            statement was parsed that includes the given pattern.
+	 */
+	public abstract void registerEventTypePattern(String patternString,
+			EventType eventType);
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/EventType.java b/src/de/uniba/wiai/lspi/puppetor/EventType.java
old mode 100755
new mode 100644
index c1d8b15..13836f3
--- a/src/de/uniba/wiai/lspi/puppetor/EventType.java
+++ b/src/de/uniba/wiai/lspi/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 de.uniba.wiai.lspi.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 de.uniba.wiai.lspi.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/de/uniba/wiai/lspi/puppetor/Network.java b/src/de/uniba/wiai/lspi/puppetor/Network.java
old mode 100755
new mode 100644
index 1219b4d..f2569f9
--- a/src/de/uniba/wiai/lspi/puppetor/Network.java
+++ b/src/de/uniba/wiai/lspi/puppetor/Network.java
@@ -1,683 +1,683 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor;
-
-import java.io.File;
-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 {
-
-	/**
-	 * <p>
-	 * Configures this network as private Tor network by exchanging directory
-	 * strings and router fingerprints between the nodes of this network.
-	 * Afterwards, the nodes will be able to run a private Tor network,
-	 * separated from public directory servers and onion routers.
-	 * </p>
-	 *
-	 * <p>
-	 * The configuration is done in two steps:
-	 * <ol>
-	 * <li>Directory strings of directory nodes are added to the configurations
-	 * of all nodes in the other network.</li>
-	 * <li>Router fingerprints of all router and directory nodes are added to
-	 * the <code>approved-routers</code> files of the directory nodes.</li>
-	 * </ol>
-	 * </p>
-	 *
-	 * <p>
-	 * This operation may be invoked in any state of the contained nodes.
-	 * However, a network that does not have directory nodes of its own but
-	 * relies on directory nodes of a merged network <b>should not be started
-	 * before being configured as private network!</b> Otherwise it would
-	 * connect to the public Tor network before being merged with the other
-	 * private Tor network. However, it may also be invoked at a later time,
-	 * e.g. to admit new nodes.
-	 * </p>
-	 *
-	 * <p>
-	 * This operation does not write any configurations to disk and neither
-	 * starts a nodes nor sends HUP signals to running nodes. These operations
-	 * are left to the application, so that they have more control over the
-	 * network behavior.
-	 * </p>
-	 *
-	 * <p>
-	 * Applications need to ensure that there are enough directory nodes (2) and
-	 * router nodes (3) in the network to allow normal operation.
-	 * </p>
-	 *
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while determining the nodes'
-	 *             fingerprints.
-	 */
-	public abstract void configureAsPrivateNetwork() throws PuppeTorException;
-
-	/**
-	 * XXX document properly -SH
-	 */
-	public void configureAsPartOfPrivateNetwork(
-			List<String> authorizedDirectoriesFingerprints);
-
-	
-	/**
-	 * Creates a new client application, but does not yet perform a request.
-	 *
-	 * @param clientApplicationName
-	 *            The name for this client application, which is used for
-	 *            logging purposes and as event source. May neither be
-	 *            <code>null</code> or a zero-length string. The name needs to
-	 *            be unique in this network.
-	 * @param targetAddress
-	 *            The target for requests sent by this client application. Can
-	 *            be a publicly available URL or an onion address. May neither
-	 *            be <code>null</code> or a zero-length string.
-	 * @param targetPort
-	 *            The TCP port for requests sent by this client application. If
-	 *            the target address is an onion address, this port is the
-	 *            virtual port that the hidden service has announced. May not be
-	 *            negative or greater than 65535.
-	 * @param socksPort
-	 *            The TCP port on which a local Tor process is waiting for
-	 *            incoming SOCKS requests. May not be negative or greater than
-	 *            65535.
-	 * @return Reference to the created client application.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract ClientApplication createClient(
-			String clientApplicationName, String targetAddress, int targetPort,
-			int socksPort);
-
-	/**
-	 * Creates a new directory node with automatically assigned ports and adds
-	 * it to the network, but does not yet write its configuration to disk or
-	 * start the corresponding Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @return Reference to the created directory node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given as node name.
-	 */
-	public abstract DirectoryNode createDirectory(String nodeName);
-
-	/**
-	 * Creates a new directory node with automatically assigned ports that will
-	 * listen on the given IP address and adds it to the network, but does not
-	 * yet write its configuration to disk or start the corresponding Tor
-	 * process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @param serverIpAddress
-	 *            The IP address on which the node will listen. Must be a valid
-	 *            IP v4 address in dotted decimal notation. May not be
-	 *            <code>null</code>.
-	 * @return Reference to the created directory node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given as node name.
-	 */
-	public abstract DirectoryNode createDirectory(String nodeName,
-			String serverIpAddress);
-
-	/**
-	 * Creates a new directory node and adds it to the network, but does not yet
-	 * write its configuration to disk or start the corresponding Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @param controlPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for a controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming SOCKS requests. May not be negative or greater
-	 *            than 65535.
-	 * @param orPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming requests from other onion routers. May not be
-	 *            negative or greater than 65535.
-	 * @param dirPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming directory requests. May not be negative or
-	 *            greater than 65535.
-	 * @return Reference to the created directory node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract DirectoryNode createDirectory(String nodeName,
-			int controlPort, int socksPort, int orPort, int dirPort);
-
-	/**
-	 * Creates a new directory node that will listen on the given IP address and
-	 * adds it to the network, but does not yet write its configuration to disk
-	 * or start the corresponding Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @param controlPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for a controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming SOCKS requests. May not be negative or greater
-	 *            than 65535.
-	 * @param orPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming requests from other onion routers. May not be
-	 *            negative or greater than 65535.
-	 * @param dirPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming directory requests. May not be negative or
-	 *            greater than 65535.
-	 * @param serverIpAddress
-	 *            The IP address on which the node will listen. Must be a valid
-	 *            IP v4 address in dotted decimal notation. May not be
-	 *            <code>null</code>.
-	 * @return Reference to the created directory node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract DirectoryNode createDirectory(String nodeName,
-			int controlPort, int socksPort, int orPort, int dirPort,
-			String serverIpAddress);
-
-	/**
-	 * Creates a new <code>ProxyNode</code> with automatically assigned ports
-	 * and adds it to the network, but does not yet write its configuration to
-	 * disk or start the corresponding Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, and as event source. May
-	 *            neither be <code>null</code> or have zero or more than 19
-	 *            alpha-numeric characters. The node name needs to be unique in
-	 *            this network.
-	 * @return Reference to the created proxy node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given as node name.
-	 */
-	public abstract ProxyNode createProxy(String nodeName);
-
-	/**
-	 * Creates a new <code>ProxyNode</code> and adds it to the network, but
-	 * does not yet write its configuration to disk or start the corresponding
-	 * Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, and as event source. May
-	 *            neither be <code>null</code> or have zero or more than 19
-	 *            alpha-numeric characters. The node name needs to be unique in
-	 *            this network.
-	 * @param controlPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for a controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming SOCKS requests. May not be negative or greater
-	 *            than 65535.
-	 * @return Reference to the created proxy node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract ProxyNode createProxy(String nodeName, int controlPort,
-			int socksPort);
-
-	/**
-	 * Creates a new <code>RouterNode</code> with automatically assigned ports
-	 * and adds it to the network, but does not yet write its configuration to
-	 * disk or start the corresponding Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @return Reference to the created router node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given as node name.
-	 */
-	public abstract RouterNode createRouter(String nodeName);
-
-	/**
-	 * Creates a new <code>RouterNode</code> and adds it to the network, but
-	 * does not yet write its configuration to disk or start the corresponding
-	 * Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @param controlPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for a controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming SOCKS requests. May not be negative or greater
-	 *            than 65535.
-	 * @param orPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming requests from other onion routers. May not be
-	 *            negative or greater than 65535.
-	 * @param dirPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming directory requests which in fact are requests for
-	 *            the mirrored directory. May not be negative or greater than
-	 *            65535.
-	 * @return Reference to the created router node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract RouterNode createRouter(String nodeName, int controlPort,
-			int socksPort, int orPort, int dirPort);
-
-	/**
-	 * Creates a new <code>RouterNode</code> with automatically assigned ports
-	 * that will listen on the given IP address and adds it to the network, but
-	 * does not yet write its configuration to disk or start the corresponding
-	 * Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @param serverIpAddress
-	 *            The IP address on which the node will listen. Must be a valid
-	 *            IP v4 address in dotted decimal notation. May not be
-	 *            <code>null</code>.
-	 * @return Reference to the created router node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given as node name.
-	 */
-	public abstract RouterNode createRouter(String nodeName,
-			String serverIpAddress);
-
-	/**
-	 * Creates a new <code>RouterNode</code> that will listen on the given IP
-	 * address and adds it to the network, but does not yet write its
-	 * configuration to disk or start the corresponding Tor process.
-	 *
-	 * @param nodeName
-	 *            The name for this node, which is used as name for the working
-	 *            directory, for logging purposes, as node nickname, and as
-	 *            event source. May neither be <code>null</code> or have zero
-	 *            or more than 19 alpha-numeric characters. The node name needs
-	 *            to be unique in this network.
-	 * @param controlPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for a controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming SOCKS requests. May not be negative or greater
-	 *            than 65535.
-	 * @param orPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming requests from other onion routers. May not be
-	 *            negative or greater than 65535.
-	 * @param dirPort
-	 *            The TCP port on which the corresponding Tor process will wait
-	 *            for incoming directory requests which in fact are requests for
-	 *            the mirrored directory. May not be negative or greater than
-	 *            65535.
-	 * @param serverIpAddress
-	 *            The IP address on which the node will listen. Must be a valid
-	 *            IP v4 address in dotted decimal notation. May not be
-	 *            <code>null</code>.
-	 * @return Reference to the created router node.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract RouterNode createRouter(String nodeName, int controlPort,
-			int socksPort, int orPort, int dirPort, String serverIpAddress);
-
-	/**
-	 * Creates a new <code>ServerApplication</code> with automatically
-	 * assigned ports, but does not start listening for incoming requests.
-	 *
-	 * @param serverApplicationName
-	 *            The name for this server application, which is used for
-	 *            logging purposes and as event source. May neither be
-	 *            <code>null</code> or a zero-length string. The name needs to
-	 *            be unique in this network.
-	 * @return Reference to the created server application.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given as server application
-	 *             name.
-	 */
-	public abstract ServerApplication createServer(String serverApplicationName);
-
-	/**
-	 * Creates a new <code>ServerApplication</code>, but does not start
-	 * listening for incoming requests.
-	 *
-	 * @param serverApplicationName
-	 *            The name for this server application, which is used for
-	 *            logging purposes and as event source. May neither be
-	 *            <code>null</code> or a zero-length string. The name needs to
-	 *            be unique in this network.
-	 * @param serverPort
-	 *            The TCP port on which the server will wait for incoming
-	 *            requests. May not be negative or greater than 65535.
-	 * @return Reference to the created server application.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract ServerApplication createServer(
-			String serverApplicationName, int serverPort);
-
-	/**
-	 * Returns a reference on the (single) event manager for this network.
-	 *
-	 * @return Reference on the (single) event manager for this network.
-	 */
-	public abstract EventManager getEventManager();
-
-	/**
-	 * Returns (a copy of) the map containing the names of all directory nodes
-	 * as keys and the corresponding directory nodes as values.
-	 *
-	 * @return Map containing all directory nodes.
-	 */
-	public abstract Map<String, DirectoryNode> getAllDirectoryNodes();
-
-	/**
-	 * Returns (a copy of) the map containing the names of all router nodes
-	 * (only those that are not acting as directory nodes at the same time) as
-	 * keys and the corresponding router nodes as values.
-	 *
-	 * @return Map containing all router nodes.
-	 */
-	public abstract Map<String, RouterNode> getAllRouterNodes();
-
-	/**
-	 * Returns (a copy of) the map containing the names of all proxy nodes (only
-	 * those that are not acting as router or directory nodes at the same time)
-	 * as keys and the corresponding proxy nodes as values.
-	 *
-	 * @return Map containing all proxy nodes.
-	 */
-	public abstract Map<String, ProxyNode> getAllProxyNodes();
-
-	/**
-	 * Returns (a copy of) the map containing the names of all nodes as keys and
-	 * the corresponding proxy nodes as values.
-	 *
-	 * @return Map containing all nodes.
-	 */
-	public abstract Map<String, ProxyNode> getAllNodes();
-
-	/**
-	 * Returns the node with name <code>nodeName</code> or <code>null</code>
-	 * if no such node exists.
-	 *
-	 * @param nodeName
-	 *            The node name to look up.
-	 * @return The node with name <code>nodeName</code>.
-	 */
-	public abstract ProxyNode getNode(String nodeName);
-
-	/**
-	 * <p>
-	 * Sends a HUP signal to all nodes in the network in regular intervals and
-	 * blocks the invoking thread until all nodes have reported to have
-	 * successfully opened a circuit.
-	 * </p>
-	 *
-	 * <p>
-	 * First, the method waits for <code>hupInterval</code> milliseconds for
-	 * the nodes to have successfully opened a circuit. If they do not succeed
-	 * within this time, a HUP signal is sent to all nodes and the method waits
-	 * for another <code>hupInterval</code> milliseconds. In total, the method
-	 * sends at most <code>tries</code> HUP signals before giving up and
-	 * returning with <code>false</code>. Thus, the maximum waiting time is
-	 * <code>(tries + 1)</code> times <code>hupInterval</code>. As soon as
-	 * all nodes have successfully opened circuits, the method returns with
-	 * <code>true</code>. This operation can only be invoked, if all nodes in
-	 * the network are in state <code>NodeState.RUNNING</code>.
-	 * </p>
-	 *
-	 * @param tries
-	 *            The maximum number of HUP signals that are sent to the Tor
-	 *            processes. Negative values are not allowed. A value of zero
-	 *            means to wait only for the given time of
-	 *            <code>hupInterval</code> milliseconds without sending a HUP
-	 *            signal. Typical values depend on the network being a public or
-	 *            private Tor network and range about 3 to 5 tries.
-	 * @param hupInterval
-	 *            The time in milliseconds that the method will wait between
-	 *            sending HUP signals. Negative values are not allowed.
-	 *            Typically, values should not be smaller than 5 seconds to
-	 *            permit Tor to stabilize.
-	 * @throws IllegalStateException
-	 *             Thrown if at least one node is not in state
-	 *             <code>NodeState.RUNNING</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if a negative value is given for either
-	 *             <code>tries</code> or <code>hupInterval</code>.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while sending HUP signals.
-	 * @return <code>true</code> if all nodes have reported to have
-	 *         successfully opened a circuit, <code>false</code> otherwise.
-	 */
-	public abstract boolean hupUntilUp(int tries, long hupInterval)
-			throws PuppeTorException;
-
-	/**
-	 * Sends a HUP signal to all nodes in the network once. This operation can
-	 * only be invoked, if all nodes in the network are in state
-	 * <code>NodeState.RUNNING</code>.
-	 *
-	 * @throws IllegalStateException
-	 *             Thrown if at least one node is not in state
-	 *             <code>NodeState.RUNNING</code>.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while sending HUP signals.
-	 */
-	public abstract void hupAllNodes() throws PuppeTorException;
-
-	/**
-	 * Sends a HUP signal to all directory nodes in the network once. This
-	 * operation can only be invoked, if all directory nodes in the network are
-	 * in state <code>NodeState.RUNNING</code>.
-	 *
-	 * @throws IllegalStateException
-	 *             Thrown if at least one directory node is not in state
-	 *             <code>NodeState.RUNNING</code>.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while sending HUP signals.
-	 */
-	public abstract void hupAllDirectories() throws PuppeTorException;
-
-	/**
-	 * Attempts to shut down all running nodes. The method blocks until all
-	 * shutdown requests have been sent and either returns, or throws the first
-	 * exception that has been observed when shutting down nodes. The method can
-	 * be assumed to return very quickly. If there are no running nodes in this
-	 * network, this operation has no effect.
-	 *
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while shutting down the
-	 *             nodes.
-	 */
-	public abstract void shutdownNodes() throws PuppeTorException;
-
-	/**
-	 * Attempts to start all nodes within a given timeout of
-	 * <code>maximumTimeToWaitInMillis</code> milliseconds. The method returns
-	 * as soon as all nodes have started and opened their control port so that
-	 * we can connect to them. It returns a boolean that states whether the
-	 * operation was either successful or has timed out. This operation can only
-	 * be invoked, if all nodes in the network have written their configuration,
-	 * i.e. are not in state <code>NodeState.CONFIGURING</code> anymore.
-	 *
-	 * @param maximumTimeToWaitInMillis
-	 *            The maximum time to wait in milliseconds. A positive value or
-	 *            zero restricts waiting to this time. Negative values are not
-	 *            allowed. Typical values are in the range of a few seconds.
-	 * @return <code>true</code> if all nodes could be started successfully,
-	 *         <code>false</code> if a timeout has occured.
-	 * @throws IllegalStateException
-	 *             Thrown if at least one node in the network is still in state
-	 *             <code>NodeState.CONFIGURING</code>.
-	 * @throws IllegalArgumentException
-	 *             Thrown if a negative value is given for
-	 *             <code>maximumTimeToWaitInMillis</code>.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while starting the nodes.
-	 */
-	public abstract boolean startNodes(long maximumTimeToWaitInMillis)
-			throws PuppeTorException;
-
-	/**
-	 * Writes the configurations for all nodes in the network to disk, including
-	 * <code>torrc</code> and <code>approved-routers</code> files. This
-	 * method is assumed to return very quickly. In case of a private network,
-	 * <code>configureAsPrivateNetwork</code> should be invoked in advance to
-	 * this method!
-	 *
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while writing to the nodes'
-	 *             working directories.
-	 */
-	public abstract void writeConfigurations() throws PuppeTorException;
-
-	/**
-	 * Returns the working directory of this network configuration which is in
-	 * <code>test-env/networkName/</code>.
-	 *
-	 * @return Working directory of this network.
-	 */
-	public abstract File getWorkingDirectory();
-
-	/**
-	 * Returns all configuration strings of the template of a node class that
-	 * will be added to future instances of this node class and its subclasses.
-	 * Note that the result only contains those configuration strings that are
-	 * added by this node class to possible superclasses and that parameterized
-	 * configuration strings, e.g. port configurations, are not included.
-	 *
-	 * @param nodeClass
-	 *            The class which will be configured with the returned template
-	 *            configuration; may not be <code>null</code>.
-	 * @return The template configuration for the given node class.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for the parameter.
-	 */
-	public abstract List<String> getTemplateConfiguration(
-			Class<? extends ProxyNode> nodeClass);
-
-	/**
-	 * Adds a configuration string to the template of a node class, so that it
-	 * will be added to future instances of this node class and its subclasses.
-	 *
-	 * @param nodeClass
-	 *            The class of nodes of which future instances will have the
-	 *            given configuration string; may not be <code>null</code>.
-	 * @param templateConfigurationString
-	 *            The configuration string to add; may neither be
-	 *            <code>null</code> nor a zero-length string, and must consist
-	 *            of configuration key and value.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract void addTemplateConfiguration(
-			Class<? extends ProxyNode> nodeClass,
-			String templateConfigurationString);
-
-	/**
-	 * Removes a configuration string from the template of a node class, so that
-	 * it will not be added to future instances of this node class and its
-	 * subclasses.
-	 *
-	 * @param nodeClass
-	 *            The class of nodes of which future instances will have the
-	 *            given configuration string; may not be <code>null</code>.
-	 * @param templateConfigurationKey
-	 *            The configuration key to remove; may neither be
-	 *            <code>null</code> nor a zero-length key.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract void removeTemplateConfiguration(
-			Class<? extends ProxyNode> nodeClass,
-			String templateConfigurationKey);
-
-	/**
-	 * Returns the name of this network.
-	 *
-	 * @return The name of this network.
-	 */
-	public abstract String getNetworkName();
-
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor;
+
+import java.io.File;
+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 {
+
+	/**
+	 * <p>
+	 * Configures this network as private Tor network by exchanging directory
+	 * strings and router fingerprints between the nodes of this network.
+	 * Afterwards, the nodes will be able to run a private Tor network,
+	 * separated from public directory servers and onion routers.
+	 * </p>
+	 *
+	 * <p>
+	 * The configuration is done in two steps:
+	 * <ol>
+	 * <li>Directory strings of directory nodes are added to the configurations
+	 * of all nodes in the other network.</li>
+	 * <li>Router fingerprints of all router and directory nodes are added to
+	 * the <code>approved-routers</code> files of the directory nodes.</li>
+	 * </ol>
+	 * </p>
+	 *
+	 * <p>
+	 * This operation may be invoked in any state of the contained nodes.
+	 * However, a network that does not have directory nodes of its own but
+	 * relies on directory nodes of a merged network <b>should not be started
+	 * before being configured as private network!</b> Otherwise it would
+	 * connect to the public Tor network before being merged with the other
+	 * private Tor network. However, it may also be invoked at a later time,
+	 * e.g. to admit new nodes.
+	 * </p>
+	 *
+	 * <p>
+	 * This operation does not write any configurations to disk and neither
+	 * starts a nodes nor sends HUP signals to running nodes. These operations
+	 * are left to the application, so that they have more control over the
+	 * network behavior.
+	 * </p>
+	 *
+	 * <p>
+	 * Applications need to ensure that there are enough directory nodes (2) and
+	 * router nodes (3) in the network to allow normal operation.
+	 * </p>
+	 *
+	 * @throws PuppeTorException
+	 *             Thrown if an I/O problem occurs while determining the nodes'
+	 *             fingerprints.
+	 */
+	public abstract void configureAsPrivateNetwork() throws PuppeTorException;
+
+	/**
+	 * XXX document properly -SH
+	 */
+	public void configureAsPartOfPrivateNetwork(
+			List<String> authorizedDirectoriesFingerprints);
+
+	
+	/**
+	 * Creates a new client application, but does not yet perform a request.
+	 *
+	 * @param clientApplicationName
+	 *            The name for this client application, which is used for
+	 *            logging purposes and as event source. May neither be
+	 *            <code>null</code> or a zero-length string. The name needs to
+	 *            be unique in this network.
+	 * @param targetAddress
+	 *            The target for requests sent by this client application. Can
+	 *            be a publicly available URL or an onion address. May neither
+	 *            be <code>null</code> or a zero-length string.
+	 * @param targetPort
+	 *            The TCP port for requests sent by this client application. If
+	 *            the target address is an onion address, this port is the
+	 *            virtual port that the hidden service has announced. May not be
+	 *            negative or greater than 65535.
+	 * @param socksPort
+	 *            The TCP port on which a local Tor process is waiting for
+	 *            incoming SOCKS requests. May not be negative or greater than
+	 *            65535.
+	 * @return Reference to the created client application.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for either of the
+	 *             parameters.
+	 */
+	public abstract ClientApplication createClient(
+			String clientApplicationName, String targetAddress, int targetPort,
+			int socksPort);
+
+	/**
+	 * Creates a new directory node with automatically assigned ports and adds
+	 * it to the network, but does not yet write its configuration to disk or
+	 * start the corresponding Tor process.
+	 *
+	 * @param nodeName
+	 *            The name for this node, which is used as name for the working
+	 *            directory, for logging purposes, as node nickname, and as
+	 *            event source. May neither be <code>null</code> or have zero
+	 *            or more than 19 alpha-numeric characters. The node name needs
+	 *            to be unique in this network.
+	 * @return Reference to the created directory node.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given as node name.
+	 */
+	public abstract DirectoryNode createDirectory(String nodeName);
+
+	/**
+	 * Creates a new directory node with automatically assigned ports that will
+	 * listen on the given IP address and adds it to the network, but does not
+	 * yet write its configuration to disk or start the corresponding Tor
+	 * process.
+	 *
+	 * @param nodeName
+	 *            The name for this node, which is used as name for the working
+	 *            directory, for logging purposes, as node nickname, and as
+	 *            event source. May neither be <code>null</code> or have zero
+	 *            or more than 19 alpha-numeric characters. The node name needs
+	 *            to be unique in this network.
+	 * @param serverIpAddress
+	 *            The IP address on which the node will listen. Must be a valid
+	 *            IP v4 address in dotted decimal notation. May not be
+	 *            <code>null</code>.
+	 * @return Reference to the created directory node.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given as node name.
+	 */
+	public abstract DirectoryNode createDirectory(String nodeName,
+			String serverIpAddress);
+
+	/**
+	 * Creates a new directory node and adds it to the network, but does not yet
+	 * write its configuration to disk or start the corresponding Tor process.
+	 *
+	 * @param nodeName
+	 *            The name for this node, which is used as name for the working
+	 *            directory, for logging purposes, as node nickname, and as
+	 *            event source. May neither be <code>null</code> or have zero
+	 *            or more than 19 alpha-numeric characters. The node name needs
+	 *            to be unique in this network.
+	 * @param controlPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for a controller. May not be negative or greater than 65535.
+	 * @param socksPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for incoming SOCKS requests. May not be negative or greater
+	 *            than 65535.
+	 * @param orPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for incoming requests from other onion routers. May not be
+	 *            negative or greater than 65535.
+	 * @param dirPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for incoming directory requests. May not be negative or
+	 *            greater than 65535.
+	 * @return Reference to the created directory node.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for either of the
+	 *             parameters.
+	 */
+	public abstract DirectoryNode createDirectory(String nodeName,
+			int controlPort, int socksPort, int orPort, int dirPort);
+
+	/**
+	 * Creates a new directory node that will listen on the given IP address and
+	 * adds it to the network, but does not yet write its configuration to disk
+	 * or start the corresponding Tor process.
+	 *
+	 * @param nodeName
+	 *            The name for this node, which is used as name for the working
+	 *            directory, for logging purposes, as node nickname, and as
+	 *            event source. May neither be <code>null</code> or have zero
+	 *            or more than 19 alpha-numeric characters. The node name needs
+	 *            to be unique in this network.
+	 * @param controlPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for a controller. May not be negative or greater than 65535.
+	 * @param socksPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for incoming SOCKS requests. May not be negative or greater
+	 *            than 65535.
+	 * @param orPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for incoming requests from other onion routers. May not be
+	 *            negative or greater than 65535.
+	 * @param dirPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for incoming directory requests. May not be negative or
+	 *            greater than 65535.
+	 * @param serverIpAddress
+	 *            The IP address on which the node will listen. Must be a valid
+	 *            IP v4 address in dotted decimal notation. May not be
+	 *            <code>null</code>.
+	 * @return Reference to the created directory node.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for either of the
+	 *             parameters.
+	 */
+	public abstract DirectoryNode createDirectory(String nodeName,
+			int controlPort, int socksPort, int orPort, int dirPort,
+			String serverIpAddress);
+
+	/**
+	 * Creates a new <code>ProxyNode</code> with automatically assigned ports
+	 * and adds it to the network, but does not yet write its configuration to
+	 * disk or start the corresponding Tor process.
+	 *
+	 * @param nodeName
+	 *            The name for this node, which is used as name for the working
+	 *            directory, for logging purposes, and as event source. May
+	 *            neither be <code>null</code> or have zero or more than 19
+	 *            alpha-numeric characters. The node name needs to be unique in
+	 *            this network.
+	 * @return Reference to the created proxy node.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given as node name.
+	 */
+	public abstract ProxyNode createProxy(String nodeName);
+
+	/**
+	 * Creates a new <code>ProxyNode</code> and adds it to the network, but
+	 * does not yet write its configuration to disk or start the corresponding
+	 * Tor process.
+	 *
+	 * @param nodeName
+	 *            The name for this node, which is used as name for the working
+	 *            directory, for logging purposes, and as event source. May
+	 *            neither be <code>null</code> or have zero or more than 19
+	 *            alpha-numeric characters. The node name needs to be unique in
+	 *            this network.
+	 * @param controlPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for a controller. May not be negative or greater than 65535.
+	 * @param socksPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for incoming SOCKS requests. May not be negative or greater
+	 *            than 65535.
+	 * @return Reference to the created proxy node.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for either of the
+	 *             parameters.
+	 */
+	public abstract ProxyNode createProxy(String nodeName, int controlPort,
+			int socksPort);
+
+	/**
+	 * Creates a new <code>RouterNode</code> with automatically assigned ports
+	 * and adds it to the network, but does not yet write its configuration to
+	 * disk or start the corresponding Tor process.
+	 *
+	 * @param nodeName
+	 *            The name for this node, which is used as name for the working
+	 *            directory, for logging purposes, as node nickname, and as
+	 *            event source. May neither be <code>null</code> or have zero
+	 *            or more than 19 alpha-numeric characters. The node name needs
+	 *            to be unique in this network.
+	 * @return Reference to the created router node.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given as node name.
+	 */
+	public abstract RouterNode createRouter(String nodeName);
+
+	/**
+	 * Creates a new <code>RouterNode</code> and adds it to the network, but
+	 * does not yet write its configuration to disk or start the corresponding
+	 * Tor process.
+	 *
+	 * @param nodeName
+	 *            The name for this node, which is used as name for the working
+	 *            directory, for logging purposes, as node nickname, and as
+	 *            event source. May neither be <code>null</code> or have zero
+	 *            or more than 19 alpha-numeric characters. The node name needs
+	 *            to be unique in this network.
+	 * @param controlPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for a controller. May not be negative or greater than 65535.
+	 * @param socksPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for incoming SOCKS requests. May not be negative or greater
+	 *            than 65535.
+	 * @param orPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for incoming requests from other onion routers. May not be
+	 *            negative or greater than 65535.
+	 * @param dirPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for incoming directory requests which in fact are requests for
+	 *            the mirrored directory. May not be negative or greater than
+	 *            65535.
+	 * @return Reference to the created router node.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for either of the
+	 *             parameters.
+	 */
+	public abstract RouterNode createRouter(String nodeName, int controlPort,
+			int socksPort, int orPort, int dirPort);
+
+	/**
+	 * Creates a new <code>RouterNode</code> with automatically assigned ports
+	 * that will listen on the given IP address and adds it to the network, but
+	 * does not yet write its configuration to disk or start the corresponding
+	 * Tor process.
+	 *
+	 * @param nodeName
+	 *            The name for this node, which is used as name for the working
+	 *            directory, for logging purposes, as node nickname, and as
+	 *            event source. May neither be <code>null</code> or have zero
+	 *            or more than 19 alpha-numeric characters. The node name needs
+	 *            to be unique in this network.
+	 * @param serverIpAddress
+	 *            The IP address on which the node will listen. Must be a valid
+	 *            IP v4 address in dotted decimal notation. May not be
+	 *            <code>null</code>.
+	 * @return Reference to the created router node.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given as node name.
+	 */
+	public abstract RouterNode createRouter(String nodeName,
+			String serverIpAddress);
+
+	/**
+	 * Creates a new <code>RouterNode</code> that will listen on the given IP
+	 * address and adds it to the network, but does not yet write its
+	 * configuration to disk or start the corresponding Tor process.
+	 *
+	 * @param nodeName
+	 *            The name for this node, which is used as name for the working
+	 *            directory, for logging purposes, as node nickname, and as
+	 *            event source. May neither be <code>null</code> or have zero
+	 *            or more than 19 alpha-numeric characters. The node name needs
+	 *            to be unique in this network.
+	 * @param controlPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for a controller. May not be negative or greater than 65535.
+	 * @param socksPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for incoming SOCKS requests. May not be negative or greater
+	 *            than 65535.
+	 * @param orPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for incoming requests from other onion routers. May not be
+	 *            negative or greater than 65535.
+	 * @param dirPort
+	 *            The TCP port on which the corresponding Tor process will wait
+	 *            for incoming directory requests which in fact are requests for
+	 *            the mirrored directory. May not be negative or greater than
+	 *            65535.
+	 * @param serverIpAddress
+	 *            The IP address on which the node will listen. Must be a valid
+	 *            IP v4 address in dotted decimal notation. May not be
+	 *            <code>null</code>.
+	 * @return Reference to the created router node.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for either of the
+	 *             parameters.
+	 */
+	public abstract RouterNode createRouter(String nodeName, int controlPort,
+			int socksPort, int orPort, int dirPort, String serverIpAddress);
+
+	/**
+	 * Creates a new <code>ServerApplication</code> with automatically
+	 * assigned ports, but does not start listening for incoming requests.
+	 *
+	 * @param serverApplicationName
+	 *            The name for this server application, which is used for
+	 *            logging purposes and as event source. May neither be
+	 *            <code>null</code> or a zero-length string. The name needs to
+	 *            be unique in this network.
+	 * @return Reference to the created server application.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given as server application
+	 *             name.
+	 */
+	public abstract ServerApplication createServer(String serverApplicationName);
+
+	/**
+	 * Creates a new <code>ServerApplication</code>, but does not start
+	 * listening for incoming requests.
+	 *
+	 * @param serverApplicationName
+	 *            The name for this server application, which is used for
+	 *            logging purposes and as event source. May neither be
+	 *            <code>null</code> or a zero-length string. The name needs to
+	 *            be unique in this network.
+	 * @param serverPort
+	 *            The TCP port on which the server will wait for incoming
+	 *            requests. May not be negative or greater than 65535.
+	 * @return Reference to the created server application.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for either of the
+	 *             parameters.
+	 */
+	public abstract ServerApplication createServer(
+			String serverApplicationName, int serverPort);
+
+	/**
+	 * Returns a reference on the (single) event manager for this network.
+	 *
+	 * @return Reference on the (single) event manager for this network.
+	 */
+	public abstract EventManager getEventManager();
+
+	/**
+	 * Returns (a copy of) the map containing the names of all directory nodes
+	 * as keys and the corresponding directory nodes as values.
+	 *
+	 * @return Map containing all directory nodes.
+	 */
+	public abstract Map<String, DirectoryNode> getAllDirectoryNodes();
+
+	/**
+	 * Returns (a copy of) the map containing the names of all router nodes
+	 * (only those that are not acting as directory nodes at the same time) as
+	 * keys and the corresponding router nodes as values.
+	 *
+	 * @return Map containing all router nodes.
+	 */
+	public abstract Map<String, RouterNode> getAllRouterNodes();
+
+	/**
+	 * Returns (a copy of) the map containing the names of all proxy nodes (only
+	 * those that are not acting as router or directory nodes at the same time)
+	 * as keys and the corresponding proxy nodes as values.
+	 *
+	 * @return Map containing all proxy nodes.
+	 */
+	public abstract Map<String, ProxyNode> getAllProxyNodes();
+
+	/**
+	 * Returns (a copy of) the map containing the names of all nodes as keys and
+	 * the corresponding proxy nodes as values.
+	 *
+	 * @return Map containing all nodes.
+	 */
+	public abstract Map<String, ProxyNode> getAllNodes();
+
+	/**
+	 * Returns the node with name <code>nodeName</code> or <code>null</code>
+	 * if no such node exists.
+	 *
+	 * @param nodeName
+	 *            The node name to look up.
+	 * @return The node with name <code>nodeName</code>.
+	 */
+	public abstract ProxyNode getNode(String nodeName);
+
+	/**
+	 * <p>
+	 * Sends a HUP signal to all nodes in the network in regular intervals and
+	 * blocks the invoking thread until all nodes have reported to have
+	 * successfully opened a circuit.
+	 * </p>
+	 *
+	 * <p>
+	 * First, the method waits for <code>hupInterval</code> milliseconds for
+	 * the nodes to have successfully opened a circuit. If they do not succeed
+	 * within this time, a HUP signal is sent to all nodes and the method waits
+	 * for another <code>hupInterval</code> milliseconds. In total, the method
+	 * sends at most <code>tries</code> HUP signals before giving up and
+	 * returning with <code>false</code>. Thus, the maximum waiting time is
+	 * <code>(tries + 1)</code> times <code>hupInterval</code>. As soon as
+	 * all nodes have successfully opened circuits, the method returns with
+	 * <code>true</code>. This operation can only be invoked, if all nodes in
+	 * the network are in state <code>NodeState.RUNNING</code>.
+	 * </p>
+	 *
+	 * @param tries
+	 *            The maximum number of HUP signals that are sent to the Tor
+	 *            processes. Negative values are not allowed. A value of zero
+	 *            means to wait only for the given time of
+	 *            <code>hupInterval</code> milliseconds without sending a HUP
+	 *            signal. Typical values depend on the network being a public or
+	 *            private Tor network and range about 3 to 5 tries.
+	 * @param hupInterval
+	 *            The time in milliseconds that the method will wait between
+	 *            sending HUP signals. Negative values are not allowed.
+	 *            Typically, values should not be smaller than 5 seconds to
+	 *            permit Tor to stabilize.
+	 * @throws IllegalStateException
+	 *             Thrown if at least one node is not in state
+	 *             <code>NodeState.RUNNING</code>.
+	 * @throws IllegalArgumentException
+	 *             Thrown if a negative value is given for either
+	 *             <code>tries</code> or <code>hupInterval</code>.
+	 * @throws PuppeTorException
+	 *             Thrown if an I/O problem occurs while sending HUP signals.
+	 * @return <code>true</code> if all nodes have reported to have
+	 *         successfully opened a circuit, <code>false</code> otherwise.
+	 */
+	public abstract boolean hupUntilUp(int tries, long hupInterval)
+			throws PuppeTorException;
+
+	/**
+	 * Sends a HUP signal to all nodes in the network once. This operation can
+	 * only be invoked, if all nodes in the network are in state
+	 * <code>NodeState.RUNNING</code>.
+	 *
+	 * @throws IllegalStateException
+	 *             Thrown if at least one node is not in state
+	 *             <code>NodeState.RUNNING</code>.
+	 * @throws PuppeTorException
+	 *             Thrown if an I/O problem occurs while sending HUP signals.
+	 */
+	public abstract void hupAllNodes() throws PuppeTorException;
+
+	/**
+	 * Sends a HUP signal to all directory nodes in the network once. This
+	 * operation can only be invoked, if all directory nodes in the network are
+	 * in state <code>NodeState.RUNNING</code>.
+	 *
+	 * @throws IllegalStateException
+	 *             Thrown if at least one directory node is not in state
+	 *             <code>NodeState.RUNNING</code>.
+	 * @throws PuppeTorException
+	 *             Thrown if an I/O problem occurs while sending HUP signals.
+	 */
+	public abstract void hupAllDirectories() throws PuppeTorException;
+
+	/**
+	 * Attempts to shut down all running nodes. The method blocks until all
+	 * shutdown requests have been sent and either returns, or throws the first
+	 * exception that has been observed when shutting down nodes. The method can
+	 * be assumed to return very quickly. If there are no running nodes in this
+	 * network, this operation has no effect.
+	 *
+	 * @throws PuppeTorException
+	 *             Thrown if an I/O problem occurs while shutting down the
+	 *             nodes.
+	 */
+	public abstract void shutdownNodes() throws PuppeTorException;
+
+	/**
+	 * Attempts to start all nodes within a given timeout of
+	 * <code>maximumTimeToWaitInMillis</code> milliseconds. The method returns
+	 * as soon as all nodes have started and opened their control port so that
+	 * we can connect to them. It returns a boolean that states whether the
+	 * operation was either successful or has timed out. This operation can only
+	 * be invoked, if all nodes in the network have written their configuration,
+	 * i.e. are not in state <code>NodeState.CONFIGURING</code> anymore.
+	 *
+	 * @param maximumTimeToWaitInMillis
+	 *            The maximum time to wait in milliseconds. A positive value or
+	 *            zero restricts waiting to this time. Negative values are not
+	 *            allowed. Typical values are in the range of a few seconds.
+	 * @return <code>true</code> if all nodes could be started successfully,
+	 *         <code>false</code> if a timeout has occured.
+	 * @throws IllegalStateException
+	 *             Thrown if at least one node in the network is still in state
+	 *             <code>NodeState.CONFIGURING</code>.
+	 * @throws IllegalArgumentException
+	 *             Thrown if a negative value is given for
+	 *             <code>maximumTimeToWaitInMillis</code>.
+	 * @throws PuppeTorException
+	 *             Thrown if an I/O problem occurs while starting the nodes.
+	 */
+	public abstract boolean startNodes(long maximumTimeToWaitInMillis)
+			throws PuppeTorException;
+
+	/**
+	 * Writes the configurations for all nodes in the network to disk, including
+	 * <code>torrc</code> and <code>approved-routers</code> files. This
+	 * method is assumed to return very quickly. In case of a private network,
+	 * <code>configureAsPrivateNetwork</code> should be invoked in advance to
+	 * this method!
+	 *
+	 * @throws PuppeTorException
+	 *             Thrown if an I/O problem occurs while writing to the nodes'
+	 *             working directories.
+	 */
+	public abstract void writeConfigurations() throws PuppeTorException;
+
+	/**
+	 * Returns the working directory of this network configuration which is in
+	 * <code>test-env/networkName/</code>.
+	 *
+	 * @return Working directory of this network.
+	 */
+	public abstract File getWorkingDirectory();
+
+	/**
+	 * Returns all configuration strings of the template of a node class that
+	 * will be added to future instances of this node class and its subclasses.
+	 * Note that the result only contains those configuration strings that are
+	 * added by this node class to possible superclasses and that parameterized
+	 * configuration strings, e.g. port configurations, are not included.
+	 *
+	 * @param nodeClass
+	 *            The class which will be configured with the returned template
+	 *            configuration; may not be <code>null</code>.
+	 * @return The template configuration for the given node class.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for the parameter.
+	 */
+	public abstract List<String> getTemplateConfiguration(
+			Class<? extends ProxyNode> nodeClass);
+
+	/**
+	 * Adds a configuration string to the template of a node class, so that it
+	 * will be added to future instances of this node class and its subclasses.
+	 *
+	 * @param nodeClass
+	 *            The class of nodes of which future instances will have the
+	 *            given configuration string; may not be <code>null</code>.
+	 * @param templateConfigurationString
+	 *            The configuration string to add; may neither be
+	 *            <code>null</code> nor a zero-length string, and must consist
+	 *            of configuration key and value.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for either of the
+	 *             parameters.
+	 */
+	public abstract void addTemplateConfiguration(
+			Class<? extends ProxyNode> nodeClass,
+			String templateConfigurationString);
+
+	/**
+	 * Removes a configuration string from the template of a node class, so that
+	 * it will not be added to future instances of this node class and its
+	 * subclasses.
+	 *
+	 * @param nodeClass
+	 *            The class of nodes of which future instances will have the
+	 *            given configuration string; may not be <code>null</code>.
+	 * @param templateConfigurationKey
+	 *            The configuration key to remove; may neither be
+	 *            <code>null</code> nor a zero-length key.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for either of the
+	 *             parameters.
+	 */
+	public abstract void removeTemplateConfiguration(
+			Class<? extends ProxyNode> nodeClass,
+			String templateConfigurationKey);
+
+	/**
+	 * Returns the name of this network.
+	 *
+	 * @return The name of this network.
+	 */
+	public abstract String getNetworkName();
+
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/NetworkFactory.java b/src/de/uniba/wiai/lspi/puppetor/NetworkFactory.java
old mode 100755
new mode 100644
index 7d8abda..35fe2d9
--- a/src/de/uniba/wiai/lspi/puppetor/NetworkFactory.java
+++ b/src/de/uniba/wiai/lspi/puppetor/NetworkFactory.java
@@ -1,108 +1,108 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor;
-
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import de.uniba.wiai.lspi.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 {
-
-	final private static ConcurrentMap<String, Network> networks =
-			new ConcurrentHashMap<String, Network>();
-
-	/**
-	 * Creates a new network that is required for a test run. The new network is
-	 * initially unpopulated and creates its own working directory at
-	 * test-env/randomTestID/. The network automatically assigns port numbers to
-	 * newly created nodes starting at <code>7000</code>.
-	 *
-	 * @param networkName
-	 *            Name of this network configuration.
-	 * @return A new network instance.
-	 */
-	public static Network createNetwork(final String networkName) {
-		final Network network = new NetworkImpl(networkName);
-		networks.put(networkName, network);
-		return network;
-	}
-
-	/**
-	 * Creates a new network that is required for a test run. The new network is
-	 * initially unpopulated and creates its own working directory at
-	 * test-env/randomTestID/. The network automatically assigns port numbers to
-	 * newly created nodes starting at <code>startPort</code>.
-	 *
-	 * @param networkName
-	 *            Name of this network configuration.
-	 * @param startPort
-	 *            The initial value for automatically assigned port numbers of
-	 *            nodes created by this <code>Network</code>; must be a value
-	 *            between <code>1024</code> and <code>65535</code>.
-	 *            Applications need to ensure that there are enough ports left
-	 *            to the maximum number port <code>65535</code> for all
-	 *            created nodes.
-	 * @return A new network instance.
-	 */
-	public static Network createNetwork(final String networkName,
-			final int startPort) {
-		final Network network = new NetworkImpl(networkName, startPort);
-		networks.put(networkName, network);
-		return network;
-	}
-
-	/**
-	 *
-	 */
-	public static Network getNetworkByName(final String networkName) {
-		final Network network = networks.get(networkName);
-		if (network == null) {
-			throw new IllegalStateException("BUG: The network with name "
-					+ networkName + " must exist and be non-null!");
-		}
-		return network;
-	}
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import de.uniba.wiai.lspi.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 {
+
+	final private static ConcurrentMap<String, Network> networks =
+			new ConcurrentHashMap<String, Network>();
+
+	/**
+	 * Creates a new network that is required for a test run. The new network is
+	 * initially unpopulated and creates its own working directory at
+	 * test-env/randomTestID/. The network automatically assigns port numbers to
+	 * newly created nodes starting at <code>7000</code>.
+	 *
+	 * @param networkName
+	 *            Name of this network configuration.
+	 * @return A new network instance.
+	 */
+	public static Network createNetwork(final String networkName) {
+		final Network network = new NetworkImpl(networkName);
+		networks.put(networkName, network);
+		return network;
+	}
+
+	/**
+	 * Creates a new network that is required for a test run. The new network is
+	 * initially unpopulated and creates its own working directory at
+	 * test-env/randomTestID/. The network automatically assigns port numbers to
+	 * newly created nodes starting at <code>startPort</code>.
+	 *
+	 * @param networkName
+	 *            Name of this network configuration.
+	 * @param startPort
+	 *            The initial value for automatically assigned port numbers of
+	 *            nodes created by this <code>Network</code>; must be a value
+	 *            between <code>1024</code> and <code>65535</code>.
+	 *            Applications need to ensure that there are enough ports left
+	 *            to the maximum number port <code>65535</code> for all
+	 *            created nodes.
+	 * @return A new network instance.
+	 */
+	public static Network createNetwork(final String networkName,
+			final int startPort) {
+		final Network network = new NetworkImpl(networkName, startPort);
+		networks.put(networkName, network);
+		return network;
+	}
+
+	/**
+	 *
+	 */
+	public static Network getNetworkByName(final String networkName) {
+		final Network network = networks.get(networkName);
+		if (network == null) {
+			throw new IllegalStateException("BUG: The network with name "
+					+ networkName + " must exist and be non-null!");
+		}
+		return network;
+	}
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/NodeState.java b/src/de/uniba/wiai/lspi/puppetor/NodeState.java
old mode 100755
new mode 100644
index 3c5ca43..2d06c93
--- a/src/de/uniba/wiai/lspi/puppetor/NodeState.java
+++ b/src/de/uniba/wiai/lspi/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 de.uniba.wiai.lspi.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 de.uniba.wiai.lspi.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/de/uniba/wiai/lspi/puppetor/ProxyNode.java b/src/de/uniba/wiai/lspi/puppetor/ProxyNode.java
old mode 100755
new mode 100644
index 9846603..490c646
--- a/src/de/uniba/wiai/lspi/puppetor/ProxyNode.java
+++ b/src/de/uniba/wiai/lspi/puppetor/ProxyNode.java
@@ -1,295 +1,295 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor;
-
-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 {
-
-	/**
-	 * Adds the entries for a hidden service to the configuration of this node.
-	 *
-	 * @param serviceName
-	 *            Name of the hidden service that will be used as name for the
-	 *            hidden service directory. May neither be <code>null</code>
-	 *            or a zero-length string.
-	 * @param servicePort
-	 *            The TCP port on which the service will be available for
-	 *            requests. This can, but need not be different from the virtual
-	 *            port that is announced to clients. May not be negative or
-	 *            greater than 65535.
-	 * @param virtualPort
-	 *            The virtual TCP port that this hidden service runs on as it is
-	 *            announced to clients. May not be negative or greater than
-	 *            65535.
-	 * @return <code>HiddenService</code> object containing the configuration
-	 *         of the created hidden service.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract HiddenService addHiddenService(String serviceName,
-			int servicePort, int virtualPort);
-
-	/**
-	 * Adds the entries for a hidden service with virtual port 80 to the
-	 * configuration of this node.
-	 *
-	 * @param serviceName
-	 *            Name of the hidden service that will be used as name for the
-	 *            hidden service directory. May neither be <code>null</code>
-	 *            or a zero-length string.
-	 * @param servicePort
-	 *            The TCP port on which the service will be available for
-	 *            requests. This can, but need not be different from the virtual
-	 *            port that is announced to clients. May not be negative or
-	 *            greater than 65535.
-	 * @return <code>HiddenService</code> object containing the configuration
-	 *         of the created hidden service.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	public abstract HiddenService addHiddenService(String serviceName,
-			int servicePort);
-
-	/**
-	 * Adds the entries for a hidden service with an automatically assigned
-	 * service port and virtual port 80 to the configuration of this node.
-	 *
-	 * service port automatically assigned virtual port 80
-	 *
-	 * @param serviceName
-	 *            Name of the hidden service that will be used as name for the
-	 *            hidden service directory. May neither be <code>null</code>
-	 *            or a zero-length string.
-	 * @return <code>HiddenService</code> object containing the configuration
-	 *         of the created hidden service.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for the parameter.
-	 */
-	public abstract HiddenService addHiddenService(String serviceName);
-
-	/**
-	 * Adds the given configuration string, consisting of "<configuration key>
-	 * <configuration value>", to the configuration of this node.
-	 *
-	 * @param configurationString
-	 *            The configuration string to be added.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given configuration string is either
-	 *             <code>null</code>, a zero-length string, or does not
-	 *             consist of configuration key and value.
-	 */
-	public abstract void addConfiguration(String configurationString);
-
-	/**
-	 * Adds the given configuration strings, each consisting of "<configuration
-	 * key> <configuration value>", to the configuration of this node.
-	 *
-	 * @param configurationStrings
-	 *            A list of the configuration strings to be added.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given list is <code>null</code>, or any of
-	 *             the contained strings is either <code>null</code>, a
-	 *             zero-length string, or does not consist of configuration key
-	 *             and value.
-	 */
-	public abstract void addConfigurations(List<String> configurationStrings);
-
-	/**
-	 * Replaces the first configuration string, consisting of "<configuration
-	 * key> <configuration value>", that contains the same configuration key as
-	 * <code>configurationString</code> by this new configuration string; if
-	 * multiple occurrences of the given configuration key are found, only the
-	 * first occurrence is replaced; if no configuration can be found, the
-	 * configuration string is appended.
-	 *
-	 * @param configurationString
-	 *            The replacing configuration string.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given configuration string is either
-	 *             <code>null</code>, a zero-length string, or does not
-	 *             consist of configuration key and value.
-	 */
-	public abstract void replaceConfiguration(String configurationString);
-
-	/**
-	 * Removes all configuration strings containing the given configuration key
-	 * in "<configuration key> <configuration value>", regardless of their
-	 * configuration value.
-	 *
-	 * @param configurationKey
-	 *            The configuration key to remove.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given configuration key is either
-	 *             <code>null</code> or a zero-length key.
-	 */
-	public abstract void removeConfiguration(String configurationKey);
-
-	/**
-	 * Returns the name of this node.
-	 *
-	 * @return The name of this node.
-	 */
-	public abstract String getNodeName();
-
-	/**
-	 * Returns the state of this node.
-	 *
-	 * @return The state of this node.
-	 */
-	public abstract NodeState getNodeState();
-
-	/**
-	 * Sends a HUP command to the process via its control port to restart it;
-	 * can only be done if the node has already been started, i.e. is in state
-	 * <code>NodeState.RUNNING</code>!
-	 *
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while sending the HUP signal.
-	 * @throws IllegalStateException
-	 *             Thrown if node is not in state <code>NodeState.RUNNING</code>.
-	 */
-	public abstract void hup() throws PuppeTorException;
-
-	/**
-	 * Shuts down the Tor process corresponding to this node immediately. This
-	 * is done by sending the <code>SHUTDOWN</code> signal twice, so that
-	 * those nodes extending <code>ProxyNode</code> which have opened their OR
-	 * port shutdown immediately, too.
-	 *
-	 * @throws IllegalStateException
-	 *             Thrown if this node is not in state
-	 *             <code>NodeState.RUNNING</code>.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while sending the
-	 *             <code>SHUTDOWN</code> signal.
-	 */
-	public abstract void shutdown() throws PuppeTorException;
-
-	/**
-	 * Starts the Tor process for this node and connects to the control port as
-	 * soon as it is opened. <b>In order for this method to succeed it is
-	 * absolutely necessary, that logging on the console is not changed in the
-	 * configuration of this node to a higher level than NOTICE, because the
-	 * output is parsed to see when the control port is opened.</b>
-	 *
-	 * @param maximumTimeToWaitInMillis
-	 *            Maximum time in milliseconds we will wait for the Tor process
-	 *            to be started and the control port being opened. If this value
-	 *            is negative or zero, we will wait potentially forever.
-	 * @return <code>true</code> if the node could be started successfully,
-	 *         <code>false</code> otherwise.
-	 * @throws IllegalStateException
-	 *             Thrown if node is not in state
-	 *             <code>NodeState.CONFIGURATION_WRITTEN</code>, i.e. if
-	 *             either configuration has not been written or the process has
-	 *             already been started.
-	 * @throws PuppeTorException
-	 *             Thrown if either the process could not be started, or the
-	 *             connection to the control port could not be established.
-	 */
-	public abstract boolean startNode(long maximumTimeToWaitInMillis)
-			throws PuppeTorException;
-
-	/**
-	 * Writes the configuration of this node to the <code>torrc</code> file in
-	 * its working directory and changes the state to
-	 * <code>NodeState.CONFIGURATION_WRITTEN</code>, if it was in state
-	 * <code>NodeState.CONFIGURING</code> before.
-	 *
-	 * @throws PuppeTorException
-	 *             Thrown if the configuration file <code>torrc</code> cannot
-	 *             be written to disk.
-	 */
-	public abstract void writeConfiguration() throws PuppeTorException;
-
-	/**
-	 * Returns the SOCKS port of this node.
-	 *
-	 * @return The SOCKS port of this node.
-	 */
-	public abstract int getSocksPort();
-
-	/**
-	 * Returns the control port of this node.
-	 *
-	 * @return The control port of this node.
-	 */
-	public abstract int getControlPort();
-
-	/**
-	 * Returns (a copy of) the list of strings containing the configuration of
-	 * this node.
-	 *
-	 * @return (A copy of) the list of strings containing the configuration of
-	 *         this node.
-	 */
-	public abstract List<String> getConfiguration();
-
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor;
+
+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 {
+
+	/**
+	 * Adds the entries for a hidden service to the configuration of this node.
+	 *
+	 * @param serviceName
+	 *            Name of the hidden service that will be used as name for the
+	 *            hidden service directory. May neither be <code>null</code>
+	 *            or a zero-length string.
+	 * @param servicePort
+	 *            The TCP port on which the service will be available for
+	 *            requests. This can, but need not be different from the virtual
+	 *            port that is announced to clients. May not be negative or
+	 *            greater than 65535.
+	 * @param virtualPort
+	 *            The virtual TCP port that this hidden service runs on as it is
+	 *            announced to clients. May not be negative or greater than
+	 *            65535.
+	 * @return <code>HiddenService</code> object containing the configuration
+	 *         of the created hidden service.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for either of the
+	 *             parameters.
+	 */
+	public abstract HiddenService addHiddenService(String serviceName,
+			int servicePort, int virtualPort);
+
+	/**
+	 * Adds the entries for a hidden service with virtual port 80 to the
+	 * configuration of this node.
+	 *
+	 * @param serviceName
+	 *            Name of the hidden service that will be used as name for the
+	 *            hidden service directory. May neither be <code>null</code>
+	 *            or a zero-length string.
+	 * @param servicePort
+	 *            The TCP port on which the service will be available for
+	 *            requests. This can, but need not be different from the virtual
+	 *            port that is announced to clients. May not be negative or
+	 *            greater than 65535.
+	 * @return <code>HiddenService</code> object containing the configuration
+	 *         of the created hidden service.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for either of the
+	 *             parameters.
+	 */
+	public abstract HiddenService addHiddenService(String serviceName,
+			int servicePort);
+
+	/**
+	 * Adds the entries for a hidden service with an automatically assigned
+	 * service port and virtual port 80 to the configuration of this node.
+	 *
+	 * service port automatically assigned virtual port 80
+	 *
+	 * @param serviceName
+	 *            Name of the hidden service that will be used as name for the
+	 *            hidden service directory. May neither be <code>null</code>
+	 *            or a zero-length string.
+	 * @return <code>HiddenService</code> object containing the configuration
+	 *         of the created hidden service.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for the parameter.
+	 */
+	public abstract HiddenService addHiddenService(String serviceName);
+
+	/**
+	 * Adds the given configuration string, consisting of "<configuration key>
+	 * <configuration value>", to the configuration of this node.
+	 *
+	 * @param configurationString
+	 *            The configuration string to be added.
+	 * @throws IllegalArgumentException
+	 *             Thrown if the given configuration string is either
+	 *             <code>null</code>, a zero-length string, or does not
+	 *             consist of configuration key and value.
+	 */
+	public abstract void addConfiguration(String configurationString);
+
+	/**
+	 * Adds the given configuration strings, each consisting of "<configuration
+	 * key> <configuration value>", to the configuration of this node.
+	 *
+	 * @param configurationStrings
+	 *            A list of the configuration strings to be added.
+	 * @throws IllegalArgumentException
+	 *             Thrown if the given list is <code>null</code>, or any of
+	 *             the contained strings is either <code>null</code>, a
+	 *             zero-length string, or does not consist of configuration key
+	 *             and value.
+	 */
+	public abstract void addConfigurations(List<String> configurationStrings);
+
+	/**
+	 * Replaces the first configuration string, consisting of "<configuration
+	 * key> <configuration value>", that contains the same configuration key as
+	 * <code>configurationString</code> by this new configuration string; if
+	 * multiple occurrences of the given configuration key are found, only the
+	 * first occurrence is replaced; if no configuration can be found, the
+	 * configuration string is appended.
+	 *
+	 * @param configurationString
+	 *            The replacing configuration string.
+	 * @throws IllegalArgumentException
+	 *             Thrown if the given configuration string is either
+	 *             <code>null</code>, a zero-length string, or does not
+	 *             consist of configuration key and value.
+	 */
+	public abstract void replaceConfiguration(String configurationString);
+
+	/**
+	 * Removes all configuration strings containing the given configuration key
+	 * in "<configuration key> <configuration value>", regardless of their
+	 * configuration value.
+	 *
+	 * @param configurationKey
+	 *            The configuration key to remove.
+	 * @throws IllegalArgumentException
+	 *             Thrown if the given configuration key is either
+	 *             <code>null</code> or a zero-length key.
+	 */
+	public abstract void removeConfiguration(String configurationKey);
+
+	/**
+	 * Returns the name of this node.
+	 *
+	 * @return The name of this node.
+	 */
+	public abstract String getNodeName();
+
+	/**
+	 * Returns the state of this node.
+	 *
+	 * @return The state of this node.
+	 */
+	public abstract NodeState getNodeState();
+
+	/**
+	 * Sends a HUP command to the process via its control port to restart it;
+	 * can only be done if the node has already been started, i.e. is in state
+	 * <code>NodeState.RUNNING</code>!
+	 *
+	 * @throws PuppeTorException
+	 *             Thrown if an I/O problem occurs while sending the HUP signal.
+	 * @throws IllegalStateException
+	 *             Thrown if node is not in state <code>NodeState.RUNNING</code>.
+	 */
+	public abstract void hup() throws PuppeTorException;
+
+	/**
+	 * Shuts down the Tor process corresponding to this node immediately. This
+	 * is done by sending the <code>SHUTDOWN</code> signal twice, so that
+	 * those nodes extending <code>ProxyNode</code> which have opened their OR
+	 * port shutdown immediately, too.
+	 *
+	 * @throws IllegalStateException
+	 *             Thrown if this node is not in state
+	 *             <code>NodeState.RUNNING</code>.
+	 * @throws PuppeTorException
+	 *             Thrown if an I/O problem occurs while sending the
+	 *             <code>SHUTDOWN</code> signal.
+	 */
+	public abstract void shutdown() throws PuppeTorException;
+
+	/**
+	 * Starts the Tor process for this node and connects to the control port as
+	 * soon as it is opened. <b>In order for this method to succeed it is
+	 * absolutely necessary, that logging on the console is not changed in the
+	 * configuration of this node to a higher level than NOTICE, because the
+	 * output is parsed to see when the control port is opened.</b>
+	 *
+	 * @param maximumTimeToWaitInMillis
+	 *            Maximum time in milliseconds we will wait for the Tor process
+	 *            to be started and the control port being opened. If this value
+	 *            is negative or zero, we will wait potentially forever.
+	 * @return <code>true</code> if the node could be started successfully,
+	 *         <code>false</code> otherwise.
+	 * @throws IllegalStateException
+	 *             Thrown if node is not in state
+	 *             <code>NodeState.CONFIGURATION_WRITTEN</code>, i.e. if
+	 *             either configuration has not been written or the process has
+	 *             already been started.
+	 * @throws PuppeTorException
+	 *             Thrown if either the process could not be started, or the
+	 *             connection to the control port could not be established.
+	 */
+	public abstract boolean startNode(long maximumTimeToWaitInMillis)
+			throws PuppeTorException;
+
+	/**
+	 * Writes the configuration of this node to the <code>torrc</code> file in
+	 * its working directory and changes the state to
+	 * <code>NodeState.CONFIGURATION_WRITTEN</code>, if it was in state
+	 * <code>NodeState.CONFIGURING</code> before.
+	 *
+	 * @throws PuppeTorException
+	 *             Thrown if the configuration file <code>torrc</code> cannot
+	 *             be written to disk.
+	 */
+	public abstract void writeConfiguration() throws PuppeTorException;
+
+	/**
+	 * Returns the SOCKS port of this node.
+	 *
+	 * @return The SOCKS port of this node.
+	 */
+	public abstract int getSocksPort();
+
+	/**
+	 * Returns the control port of this node.
+	 *
+	 * @return The control port of this node.
+	 */
+	public abstract int getControlPort();
+
+	/**
+	 * Returns (a copy of) the list of strings containing the configuration of
+	 * this node.
+	 *
+	 * @return (A copy of) the list of strings containing the configuration of
+	 *         this node.
+	 */
+	public abstract List<String> getConfiguration();
+
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/PuppeTorException.java b/src/de/uniba/wiai/lspi/puppetor/PuppeTorException.java
old mode 100755
new mode 100644
index 5a78d76..7f16e9e
--- a/src/de/uniba/wiai/lspi/puppetor/PuppeTorException.java
+++ b/src/de/uniba/wiai/lspi/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 de.uniba.wiai.lspi.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
- */
-@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(final String message, final Throwable cause) {
-		super(message, cause);
-	}
-
-	/**
-	 * Creates a <code>PuppeTorException</code> with the given detail
-	 * <code>message</code>, but without a <code>cause</code>.
-	 *
-	 * @param message
-	 *            The detail message of this exception.
-	 */
-	public PuppeTorException(final String message) {
-		super(message);
-	}
-
-	/**
-	 * Creates a <code>PuppeTorException</code> with the given
-	 * <code>cause</code>, but without a detail message.
-	 *
-	 * @param cause
-	 *            The cause for this exception.
-	 */
-	public PuppeTorException(final 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 de.uniba.wiai.lspi.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
+ */
+@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(final String message, final Throwable cause) {
+		super(message, cause);
+	}
+
+	/**
+	 * Creates a <code>PuppeTorException</code> with the given detail
+	 * <code>message</code>, but without a <code>cause</code>.
+	 *
+	 * @param message
+	 *            The detail message of this exception.
+	 */
+	public PuppeTorException(final String message) {
+		super(message);
+	}
+
+	/**
+	 * Creates a <code>PuppeTorException</code> with the given
+	 * <code>cause</code>, but without a detail message.
+	 *
+	 * @param cause
+	 *            The cause for this exception.
+	 */
+	public PuppeTorException(final Throwable cause) {
+		super(cause);
+	}
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/RouterNode.java b/src/de/uniba/wiai/lspi/puppetor/RouterNode.java
old mode 100755
new mode 100644
index d73f409..48f721f
--- a/src/de/uniba/wiai/lspi/puppetor/RouterNode.java
+++ b/src/de/uniba/wiai/lspi/puppetor/RouterNode.java
@@ -1,81 +1,81 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor;
-
-/**
- * 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.
-	 */
-	public abstract int getDirPort();
-
-	/**
-	 * Returns the onion port of this node.
-	 *
-	 * @return The onion port of this node.
-	 */
-	public abstract int getOrPort();
-
-	/**
-	 * <p>
-	 * Returns the fingerprint string of this node, formatted like
-	 * <code>nickname 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000</code>.
-	 * </p>
-	 *
-	 * <p>
-	 * The fingerprint is determined by a background thread that is started as
-	 * soon as the node is instantiated. If this background thread has not
-	 * finished when this method is invoked, the invoking thread will be blocked
-	 * until the fingerprint is available (or determining it has failed,
-	 * whereupon an exception will be thrown).
-	 * </p>
-	 *
-	 * @return The fingerprint of this node.
-	 * @throws PuppeTorException
-	 *             Thrown if either the temporary <code>torrc.temp</code>
-	 *             configuration file cannot be written, the Tor process cannot
-	 *             be started temporarily, or the fingerprint file cannot be
-	 *             read.
-	 */
-	public abstract String getFingerprint() throws PuppeTorException;
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor;
+
+/**
+ * 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.
+	 */
+	public abstract int getDirPort();
+
+	/**
+	 * Returns the onion port of this node.
+	 *
+	 * @return The onion port of this node.
+	 */
+	public abstract int getOrPort();
+
+	/**
+	 * <p>
+	 * Returns the fingerprint string of this node, formatted like
+	 * <code>nickname 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000</code>.
+	 * </p>
+	 *
+	 * <p>
+	 * The fingerprint is determined by a background thread that is started as
+	 * soon as the node is instantiated. If this background thread has not
+	 * finished when this method is invoked, the invoking thread will be blocked
+	 * until the fingerprint is available (or determining it has failed,
+	 * whereupon an exception will be thrown).
+	 * </p>
+	 *
+	 * @return The fingerprint of this node.
+	 * @throws PuppeTorException
+	 *             Thrown if either the temporary <code>torrc.temp</code>
+	 *             configuration file cannot be written, the Tor process cannot
+	 *             be started temporarily, or the fingerprint file cannot be
+	 *             read.
+	 */
+	public abstract String getFingerprint() throws PuppeTorException;
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/ServerApplication.java b/src/de/uniba/wiai/lspi/puppetor/ServerApplication.java
old mode 100755
new mode 100644
index 3f78611..07bd717
--- a/src/de/uniba/wiai/lspi/puppetor/ServerApplication.java
+++ b/src/de/uniba/wiai/lspi/puppetor/ServerApplication.java
@@ -1,84 +1,84 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor;
-
-/**
- * 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 {
-
-	/**
-	 * Starts listening for incoming <code>HTTP GET</code> requests from
-	 * clients. Any incoming request is answered by an empty
-	 * <code>HTTP OK</code> reply. This method may only be invoked when the
-	 * server is currently not in listening state!
-	 *
-	 * @throws IllegalStateException
-	 *             Thrown if the server is currently not in listening state.
-	 */
-	public abstract void startListening();
-
-	/**
-	 * Stops listening for requests. This method may only be invoked when the
-	 * server is currently in listening state!
-	 *
-	 * @throws IllegalStateException
-	 *             Thrown if the server is currently in listening state.
-	 */
-	public abstract void stopListening();
-
-	/**
-	 * Returns whether this server is currently in listening state.
-	 *
-	 * @return The listening state of this server.
-	 */
-	public abstract boolean isListening();
-
-	/**
-	 * Returns the name of this server.
-	 *
-	 * @return The name of this server.
-	 */
-	public abstract String getServerApplicationName();
-
-	/**
-	 * Returns the port on which this server listens.
-	 *
-	 * @return The port on which this server listens.
-	 */
-	public abstract int getServerPort();
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor;
+
+/**
+ * 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 {
+
+	/**
+	 * Starts listening for incoming <code>HTTP GET</code> requests from
+	 * clients. Any incoming request is answered by an empty
+	 * <code>HTTP OK</code> reply. This method may only be invoked when the
+	 * server is currently not in listening state!
+	 *
+	 * @throws IllegalStateException
+	 *             Thrown if the server is currently not in listening state.
+	 */
+	public abstract void startListening();
+
+	/**
+	 * Stops listening for requests. This method may only be invoked when the
+	 * server is currently in listening state!
+	 *
+	 * @throws IllegalStateException
+	 *             Thrown if the server is currently in listening state.
+	 */
+	public abstract void stopListening();
+
+	/**
+	 * Returns whether this server is currently in listening state.
+	 *
+	 * @return The listening state of this server.
+	 */
+	public abstract boolean isListening();
+
+	/**
+	 * Returns the name of this server.
+	 *
+	 * @return The name of this server.
+	 */
+	public abstract String getServerApplicationName();
+
+	/**
+	 * Returns the port on which this server listens.
+	 *
+	 * @return The port on which this server listens.
+	 */
+	public abstract int getServerPort();
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/examples/AccessingPublicWebServerOverTor.java b/src/de/uniba/wiai/lspi/puppetor/examples/AccessingPublicWebServerOverTor.java
old mode 100755
new mode 100644
index e8b93c0..81a7573
--- a/src/de/uniba/wiai/lspi/puppetor/examples/AccessingPublicWebServerOverTor.java
+++ b/src/de/uniba/wiai/lspi/puppetor/examples/AccessingPublicWebServerOverTor.java
@@ -1,145 +1,145 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor.examples;
-
-import de.uniba.wiai.lspi.puppetor.ClientApplication;
-import de.uniba.wiai.lspi.puppetor.ClientEventType;
-import de.uniba.wiai.lspi.puppetor.Event;
-import de.uniba.wiai.lspi.puppetor.EventListener;
-import de.uniba.wiai.lspi.puppetor.EventManager;
-import de.uniba.wiai.lspi.puppetor.Network;
-import de.uniba.wiai.lspi.puppetor.NetworkFactory;
-import de.uniba.wiai.lspi.puppetor.ProxyNode;
-import de.uniba.wiai.lspi.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.
-	 */
-	public static void main(final String[] args) throws PuppeTorException {
-
-		// though we only need a single proxy, we always need to create a
-		// network to initialize a test case.
-		final Network network = NetworkFactory.createNetwork("example1");
-
-		// create a single proxy node with name "proxy"
-		final ProxyNode proxy = network.createProxy("proxy");
-
-		// write configuration of proxy node
-		network.writeConfigurations();
-
-		// start proxy node and wait until it has opened a circuit with a
-		// timeout of 5 seconds
-		if (!network.startNodes(5000)) {
-
-			// failed to start the proxy
-			System.out.println("Failed to start the node!");
-			return;
-		}
-		System.out.println("Successfully started the node!");
-
-		// hup until proxy has built circuits (5 retries, 10 seconds timeout
-		// each)
-		if (!network.hupUntilUp(5, 10000)) {
-
-			// failed to build circuits
-			System.out.println("Failed to build circuits!");
-			System.exit(0);
-		}
-		System.out.println("Successfully built circuits!");
-
-		// create client application
-		final ClientApplication client =
-				network.createClient("client", "www.google.com", 80, proxy
-						.getSocksPort());
-
-		// create event listener to listen for client application events
-		final EventListener clientEventListener = new EventListener() {
-
-			// remember time when request was sent
-			private long before;
-
-			public void handleEvent(Event event) {
-				if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
-					before = System.currentTimeMillis();
-				} else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
-					System.out.println("Request took "
-							+ (System.currentTimeMillis() - before)
-							+ " milliseconds");
-				}
-			}
-		};
-
-		// obtain reference to event manager to be able to respond to events
-		final EventManager manager = network.getEventManager();
-
-		// register event handler for client application events
-		manager.addEventListener(client.getClientApplicationName(),
-				clientEventListener);
-
-		// perform at most three request with a timeout of 20 seconds each
-		client.startRequests(3, 20000, true);
-
-		// block this thread as long as client requests are running
-		manager.waitForAnyOccurence(client.getClientApplicationName(),
-				ClientEventType.CLIENT_REQUESTS_PERFORMED);
-
-		// wait a second before shutting down the proxy
-		try {
-			Thread.sleep(1000);
-		} catch (final InterruptedException e) {}
-
-		// shut down proxy
-		network.shutdownNodes();
-
-		// wait another second before exiting the application
-		try {
-			Thread.sleep(1000);
-		} catch (final InterruptedException e) {}
-
-		// Shut down the JVM
-		System.out.println("Goodbye.");
-	}
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor.examples;
+
+import de.uniba.wiai.lspi.puppetor.ClientApplication;
+import de.uniba.wiai.lspi.puppetor.ClientEventType;
+import de.uniba.wiai.lspi.puppetor.Event;
+import de.uniba.wiai.lspi.puppetor.EventListener;
+import de.uniba.wiai.lspi.puppetor.EventManager;
+import de.uniba.wiai.lspi.puppetor.Network;
+import de.uniba.wiai.lspi.puppetor.NetworkFactory;
+import de.uniba.wiai.lspi.puppetor.ProxyNode;
+import de.uniba.wiai.lspi.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.
+	 */
+	public static void main(final String[] args) throws PuppeTorException {
+
+		// though we only need a single proxy, we always need to create a
+		// network to initialize a test case.
+		final Network network = NetworkFactory.createNetwork("example1");
+
+		// create a single proxy node with name "proxy"
+		final ProxyNode proxy = network.createProxy("proxy");
+
+		// write configuration of proxy node
+		network.writeConfigurations();
+
+		// start proxy node and wait until it has opened a circuit with a
+		// timeout of 5 seconds
+		if (!network.startNodes(5000)) {
+
+			// failed to start the proxy
+			System.out.println("Failed to start the node!");
+			return;
+		}
+		System.out.println("Successfully started the node!");
+
+		// hup until proxy has built circuits (5 retries, 10 seconds timeout
+		// each)
+		if (!network.hupUntilUp(5, 10000)) {
+
+			// failed to build circuits
+			System.out.println("Failed to build circuits!");
+			System.exit(0);
+		}
+		System.out.println("Successfully built circuits!");
+
+		// create client application
+		final ClientApplication client =
+				network.createClient("client", "www.google.com", 80, proxy
+						.getSocksPort());
+
+		// create event listener to listen for client application events
+		final EventListener clientEventListener = new EventListener() {
+
+			// remember time when request was sent
+			private long before;
+
+			public void handleEvent(Event event) {
+				if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
+					before = System.currentTimeMillis();
+				} else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
+					System.out.println("Request took "
+							+ (System.currentTimeMillis() - before)
+							+ " milliseconds");
+				}
+			}
+		};
+
+		// obtain reference to event manager to be able to respond to events
+		final EventManager manager = network.getEventManager();
+
+		// register event handler for client application events
+		manager.addEventListener(client.getClientApplicationName(),
+				clientEventListener);
+
+		// perform at most three request with a timeout of 20 seconds each
+		client.startRequests(3, 20000, true);
+
+		// block this thread as long as client requests are running
+		manager.waitForAnyOccurence(client.getClientApplicationName(),
+				ClientEventType.CLIENT_REQUESTS_PERFORMED);
+
+		// wait a second before shutting down the proxy
+		try {
+			Thread.sleep(1000);
+		} catch (final InterruptedException e) {}
+
+		// shut down proxy
+		network.shutdownNodes();
+
+		// wait another second before exiting the application
+		try {
+			Thread.sleep(1000);
+		} catch (final InterruptedException e) {}
+
+		// Shut down the JVM
+		System.out.println("Goodbye.");
+	}
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java b/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
old mode 100755
new mode 100644
index 0136c7e..10f9cd1
--- a/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
+++ b/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
@@ -1,158 +1,158 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor.examples;
-
-import de.uniba.wiai.lspi.puppetor.ClientApplication;
-import de.uniba.wiai.lspi.puppetor.ClientEventType;
-import de.uniba.wiai.lspi.puppetor.Event;
-import de.uniba.wiai.lspi.puppetor.EventListener;
-import de.uniba.wiai.lspi.puppetor.EventManager;
-import de.uniba.wiai.lspi.puppetor.HiddenService;
-import de.uniba.wiai.lspi.puppetor.HiddenServiceEventType;
-import de.uniba.wiai.lspi.puppetor.Network;
-import de.uniba.wiai.lspi.puppetor.NetworkFactory;
-import de.uniba.wiai.lspi.puppetor.PuppeTorException;
-import de.uniba.wiai.lspi.puppetor.RouterNode;
-import de.uniba.wiai.lspi.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.
-	 */
-	public static void main(final String[] args) throws PuppeTorException {
-
-		// create a network to initialize the test case
-		final Network network = NetworkFactory.createNetwork("example4");
-
-		// create three router nodes
-		final RouterNode router1 = network.createRouter("router1");
-		network.createRouter("router2");
-		final RouterNode router3 = network.createRouter("router3");
-
-		// create only one directory node
-		network.createDirectory("dir1");
-
-		// add hidden service
-		final HiddenService hidServ1 = router1.addHiddenService("hidServ");
-
-		// configure nodes of this network to be part of a private network
-		network.configureAsPrivateNetwork();
-
-		// write node configurations
-		network.writeConfigurations();
-
-		// start nodes and wait until they have opened a circuit with a timeout
-		// of 5 seconds
-		if (!network.startNodes(5000)) {
-
-			// failed to start the nodes
-			System.out.println("Failed to start nodes!");
-			System.exit(0);
-		}
-		System.out.println("Successfully started nodes!");
-
-		// hup until nodes have built circuits (60 retries, 10 seconds timeout
-		// each)
-		if (!network.hupUntilUp(60, 10000)) {
-
-			// failed to build circuits
-			System.out.println("Failed to build circuits!");
-			System.exit(0);
-		}
-		System.out.println("Successfully built circuits!");
-
-		// obtain reference to event manager to be able to respond to events
-		final EventManager manager = network.getEventManager();
-
-		// wait for 1 hour that the proxy has published its first RSD
-		if (!manager.waitForAnyOccurence(router1.getNodeName(),
-				HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
-				1L * 60L * 60L * 1000L)) {
-			// failed to publish an RSD
-			System.out.println("Failed to publish an RSD!");
-			System.exit(0);
-		}
-		System.out.println("Successfully published an RSD!");
-
-		// create server application
-		final ServerApplication server =
-				network.createServer("server", hidServ1.getServicePort());
-
-		// create client application
-		final ClientApplication client =
-				network.createClient("client",
-						hidServ1.determineOnionAddress(), hidServ1
-								.getVirtualPort(), router3.getSocksPort());
-
-		// register event listener
-		final EventListener clientAndServerEventListener = new EventListener() {
-			public void handleEvent(Event event) {
-				System.out.println("Handling event: " + event.getMessage());
-			}
-		};
-		manager.addEventListener(client.getClientApplicationName(),
-				clientAndServerEventListener);
-		manager.addEventListener(server.getServerApplicationName(),
-				clientAndServerEventListener);
-
-		// start server
-		server.startListening();
-		System.out.println("Started server");
-
-		// perform at most five request with a timeout of 45 seconds each
-		client.startRequests(5, 45000, true);
-
-		// wait for request to be performed
-		manager.waitForAnyOccurence(client.getClientApplicationName(),
-				ClientEventType.CLIENT_REQUESTS_PERFORMED);
-
-		// shut down nodes
-		network.shutdownNodes();
-
-		// Shut down the JVM
-		System.out.println("Goodbye.");
-		System.exit(0);
-	}
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor.examples;
+
+import de.uniba.wiai.lspi.puppetor.ClientApplication;
+import de.uniba.wiai.lspi.puppetor.ClientEventType;
+import de.uniba.wiai.lspi.puppetor.Event;
+import de.uniba.wiai.lspi.puppetor.EventListener;
+import de.uniba.wiai.lspi.puppetor.EventManager;
+import de.uniba.wiai.lspi.puppetor.HiddenService;
+import de.uniba.wiai.lspi.puppetor.HiddenServiceEventType;
+import de.uniba.wiai.lspi.puppetor.Network;
+import de.uniba.wiai.lspi.puppetor.NetworkFactory;
+import de.uniba.wiai.lspi.puppetor.PuppeTorException;
+import de.uniba.wiai.lspi.puppetor.RouterNode;
+import de.uniba.wiai.lspi.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.
+	 */
+	public static void main(final String[] args) throws PuppeTorException {
+
+		// create a network to initialize the test case
+		final Network network = NetworkFactory.createNetwork("example4");
+
+		// create three router nodes
+		final RouterNode router1 = network.createRouter("router1");
+		network.createRouter("router2");
+		final RouterNode router3 = network.createRouter("router3");
+
+		// create only one directory node
+		network.createDirectory("dir1");
+
+		// add hidden service
+		final HiddenService hidServ1 = router1.addHiddenService("hidServ");
+
+		// configure nodes of this network to be part of a private network
+		network.configureAsPrivateNetwork();
+
+		// write node configurations
+		network.writeConfigurations();
+
+		// start nodes and wait until they have opened a circuit with a timeout
+		// of 5 seconds
+		if (!network.startNodes(5000)) {
+
+			// failed to start the nodes
+			System.out.println("Failed to start nodes!");
+			System.exit(0);
+		}
+		System.out.println("Successfully started nodes!");
+
+		// hup until nodes have built circuits (60 retries, 10 seconds timeout
+		// each)
+		if (!network.hupUntilUp(60, 10000)) {
+
+			// failed to build circuits
+			System.out.println("Failed to build circuits!");
+			System.exit(0);
+		}
+		System.out.println("Successfully built circuits!");
+
+		// obtain reference to event manager to be able to respond to events
+		final EventManager manager = network.getEventManager();
+
+		// wait for 1 hour that the proxy has published its first RSD
+		if (!manager.waitForAnyOccurence(router1.getNodeName(),
+				HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
+				1L * 60L * 60L * 1000L)) {
+			// failed to publish an RSD
+			System.out.println("Failed to publish an RSD!");
+			System.exit(0);
+		}
+		System.out.println("Successfully published an RSD!");
+
+		// create server application
+		final ServerApplication server =
+				network.createServer("server", hidServ1.getServicePort());
+
+		// create client application
+		final ClientApplication client =
+				network.createClient("client",
+						hidServ1.determineOnionAddress(), hidServ1
+								.getVirtualPort(), router3.getSocksPort());
+
+		// register event listener
+		final EventListener clientAndServerEventListener = new EventListener() {
+			public void handleEvent(Event event) {
+				System.out.println("Handling event: " + event.getMessage());
+			}
+		};
+		manager.addEventListener(client.getClientApplicationName(),
+				clientAndServerEventListener);
+		manager.addEventListener(server.getServerApplicationName(),
+				clientAndServerEventListener);
+
+		// start server
+		server.startListening();
+		System.out.println("Started server");
+
+		// perform at most five request with a timeout of 45 seconds each
+		client.startRequests(5, 45000, true);
+
+		// wait for request to be performed
+		manager.waitForAnyOccurence(client.getClientApplicationName(),
+				ClientEventType.CLIENT_REQUESTS_PERFORMED);
+
+		// shut down nodes
+		network.shutdownNodes();
+
+		// Shut down the JVM
+		System.out.println("Goodbye.");
+		System.exit(0);
+	}
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java b/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
old mode 100755
new mode 100644
index 5a521f5..c639e3d
--- a/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
+++ b/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
@@ -1,175 +1,175 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor.examples;
-
-import de.uniba.wiai.lspi.puppetor.ClientApplication;
-import de.uniba.wiai.lspi.puppetor.ClientEventType;
-import de.uniba.wiai.lspi.puppetor.Event;
-import de.uniba.wiai.lspi.puppetor.EventListener;
-import de.uniba.wiai.lspi.puppetor.EventManager;
-import de.uniba.wiai.lspi.puppetor.HiddenService;
-import de.uniba.wiai.lspi.puppetor.HiddenServiceEventType;
-import de.uniba.wiai.lspi.puppetor.Network;
-import de.uniba.wiai.lspi.puppetor.NetworkFactory;
-import de.uniba.wiai.lspi.puppetor.ProxyNode;
-import de.uniba.wiai.lspi.puppetor.PuppeTorException;
-import de.uniba.wiai.lspi.puppetor.ServerApplication;
-import de.uniba.wiai.lspi.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.
-	 */
-	public static void main(final String[] args) throws PuppeTorException {
-
-		// create a network to initialize the test case
-		final Network network = NetworkFactory.createNetwork("example3");
-
-		// create two proxy nodes
-		final ProxyNode proxy1 = network.createProxy("proxy1");
-		final ProxyNode proxy2 = network.createProxy("proxy2");
-
-		// add hidden service to the configuration of proxy1
-		final HiddenService hidServ1 = proxy1.addHiddenService("hidServ");
-
-		// write configuration of proxy nodes
-		network.writeConfigurations();
-
-		// start nodes and wait until they have opened a circuit with a timeout
-		// of 5 seconds
-		if (!network.startNodes(5000)) {
-
-			// failed to start the proxy
-			System.out.println("Failed to start nodes!");
-			System.exit(0);
-		}
-		System.out.println("Successfully started nodes!");
-
-		// hup until nodes have built circuits (5 retries, 10 seconds timeout
-		// each)
-		if (!network.hupUntilUp(5, 10000)) {
-
-			// failed to build circuits
-			System.out.println("Failed to build circuits!");
-			System.exit(0);
-		}
-		System.out.println("Successfully built circuits!");
-
-		// obtain reference to event manager to be able to respond to events
-		final EventManager manager = network.getEventManager();
-
-		// wait for 3 minutes that the proxy has published its first RSD
-		if (!manager.waitForAnyOccurence(proxy1.getNodeName(),
-				HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
-				3L * 60L * 1000L)) {
-
-			// failed to publish an RSD
-			System.out.println("Failed to publish an RSD!");
-			System.exit(0);
-		}
-		System.out.println("Successfully published an RSD!");
-
-		// create server application
-		final ServerApplication server =
-				network.createServer("server", hidServ1.getServicePort());
-
-		// create client application
-		final ClientApplication client =
-				network.createClient("client",
-						hidServ1.determineOnionAddress(), hidServ1
-								.getVirtualPort(), proxy2.getSocksPort());
-
-		// create event listener to listen for client and server application
-		// events
-		final EventListener clientAndServerEventListener = new EventListener() {
-
-			private long requestReceivedAtServer;
-
-			// remember time when request was sent and when it was received
-			private long requestSentFromClient;
-
-			public void handleEvent(Event event) {
-				if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
-					requestSentFromClient = event.getOccurrenceTime();
-				} else if (event.getType() == ServerEventType.SERVER_RECEIVING_REQUEST_SENDING_REPLY) {
-					requestReceivedAtServer = event.getOccurrenceTime();
-					System.out.println("Request took "
-							+ (requestReceivedAtServer - requestSentFromClient)
-							+ " milliseconds from client to server!");
-				} else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
-					System.out
-							.println("Request took "
-									+ (event.getOccurrenceTime() - requestSentFromClient)
-									+ " milliseconds for the round-trip and "
-									+ (event.getOccurrenceTime() - requestReceivedAtServer)
-									+ " milliseconds from server to client!");
-				}
-			}
-		};
-
-		// register event handler for client and server application events
-		manager.addEventListener(client.getClientApplicationName(),
-				clientAndServerEventListener);
-		manager.addEventListener(server.getServerApplicationName(),
-				clientAndServerEventListener);
-
-		// start server
-		server.startListening();
-
-		// perform at most five request with a timeout of 45 seconds each
-		client.startRequests(5, 45000, true);
-
-		// block this thread as long as client requests are running
-		manager.waitForAnyOccurence(client.getClientApplicationName(),
-				ClientEventType.CLIENT_REQUESTS_PERFORMED);
-
-		// shut down proxy
-		network.shutdownNodes();
-
-		// Shut down the JVM
-		System.out.println("Goodbye.");
-		System.exit(0);
-	}
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor.examples;
+
+import de.uniba.wiai.lspi.puppetor.ClientApplication;
+import de.uniba.wiai.lspi.puppetor.ClientEventType;
+import de.uniba.wiai.lspi.puppetor.Event;
+import de.uniba.wiai.lspi.puppetor.EventListener;
+import de.uniba.wiai.lspi.puppetor.EventManager;
+import de.uniba.wiai.lspi.puppetor.HiddenService;
+import de.uniba.wiai.lspi.puppetor.HiddenServiceEventType;
+import de.uniba.wiai.lspi.puppetor.Network;
+import de.uniba.wiai.lspi.puppetor.NetworkFactory;
+import de.uniba.wiai.lspi.puppetor.ProxyNode;
+import de.uniba.wiai.lspi.puppetor.PuppeTorException;
+import de.uniba.wiai.lspi.puppetor.ServerApplication;
+import de.uniba.wiai.lspi.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.
+	 */
+	public static void main(final String[] args) throws PuppeTorException {
+
+		// create a network to initialize the test case
+		final Network network = NetworkFactory.createNetwork("example3");
+
+		// create two proxy nodes
+		final ProxyNode proxy1 = network.createProxy("proxy1");
+		final ProxyNode proxy2 = network.createProxy("proxy2");
+
+		// add hidden service to the configuration of proxy1
+		final HiddenService hidServ1 = proxy1.addHiddenService("hidServ");
+
+		// write configuration of proxy nodes
+		network.writeConfigurations();
+
+		// start nodes and wait until they have opened a circuit with a timeout
+		// of 5 seconds
+		if (!network.startNodes(5000)) {
+
+			// failed to start the proxy
+			System.out.println("Failed to start nodes!");
+			System.exit(0);
+		}
+		System.out.println("Successfully started nodes!");
+
+		// hup until nodes have built circuits (5 retries, 10 seconds timeout
+		// each)
+		if (!network.hupUntilUp(5, 10000)) {
+
+			// failed to build circuits
+			System.out.println("Failed to build circuits!");
+			System.exit(0);
+		}
+		System.out.println("Successfully built circuits!");
+
+		// obtain reference to event manager to be able to respond to events
+		final EventManager manager = network.getEventManager();
+
+		// wait for 3 minutes that the proxy has published its first RSD
+		if (!manager.waitForAnyOccurence(proxy1.getNodeName(),
+				HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
+				3L * 60L * 1000L)) {
+
+			// failed to publish an RSD
+			System.out.println("Failed to publish an RSD!");
+			System.exit(0);
+		}
+		System.out.println("Successfully published an RSD!");
+
+		// create server application
+		final ServerApplication server =
+				network.createServer("server", hidServ1.getServicePort());
+
+		// create client application
+		final ClientApplication client =
+				network.createClient("client",
+						hidServ1.determineOnionAddress(), hidServ1
+								.getVirtualPort(), proxy2.getSocksPort());
+
+		// create event listener to listen for client and server application
+		// events
+		final EventListener clientAndServerEventListener = new EventListener() {
+
+			private long requestReceivedAtServer;
+
+			// remember time when request was sent and when it was received
+			private long requestSentFromClient;
+
+			public void handleEvent(Event event) {
+				if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
+					requestSentFromClient = event.getOccurrenceTime();
+				} else if (event.getType() == ServerEventType.SERVER_RECEIVING_REQUEST_SENDING_REPLY) {
+					requestReceivedAtServer = event.getOccurrenceTime();
+					System.out.println("Request took "
+							+ (requestReceivedAtServer - requestSentFromClient)
+							+ " milliseconds from client to server!");
+				} else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
+					System.out
+							.println("Request took "
+									+ (event.getOccurrenceTime() - requestSentFromClient)
+									+ " milliseconds for the round-trip and "
+									+ (event.getOccurrenceTime() - requestReceivedAtServer)
+									+ " milliseconds from server to client!");
+				}
+			}
+		};
+
+		// register event handler for client and server application events
+		manager.addEventListener(client.getClientApplicationName(),
+				clientAndServerEventListener);
+		manager.addEventListener(server.getServerApplicationName(),
+				clientAndServerEventListener);
+
+		// start server
+		server.startListening();
+
+		// perform at most five request with a timeout of 45 seconds each
+		client.startRequests(5, 45000, true);
+
+		// block this thread as long as client requests are running
+		manager.waitForAnyOccurence(client.getClientApplicationName(),
+				ClientEventType.CLIENT_REQUESTS_PERFORMED);
+
+		// shut down proxy
+		network.shutdownNodes();
+
+		// Shut down the JVM
+		System.out.println("Goodbye.");
+		System.exit(0);
+	}
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java b/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
old mode 100755
new mode 100644
index 0c7564c..0091585
--- a/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
+++ b/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
@@ -1,136 +1,136 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor.examples;
-
-import de.uniba.wiai.lspi.puppetor.Event;
-import de.uniba.wiai.lspi.puppetor.EventListener;
-import de.uniba.wiai.lspi.puppetor.EventManager;
-import de.uniba.wiai.lspi.puppetor.HiddenServiceEventType;
-import de.uniba.wiai.lspi.puppetor.Network;
-import de.uniba.wiai.lspi.puppetor.NetworkFactory;
-import de.uniba.wiai.lspi.puppetor.NodeEventType;
-import de.uniba.wiai.lspi.puppetor.ProxyNode;
-import de.uniba.wiai.lspi.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.
-	 */
-	public static void main(final String[] args) throws PuppeTorException {
-		// create a network to initialize the test case
-		final Network network = NetworkFactory.createNetwork("example2");
-
-		// create a single proxy node
-		final ProxyNode proxy = network.createProxy("proxy");
-
-		// add hidden service to the configuration
-		proxy.addHiddenService("hidServ");
-
-		// write configuration of proxy node
-		network.writeConfigurations();
-
-		// create event listener to listen for events from our proxy
-		final EventListener proxyEventListener = new EventListener() {
-
-			// remember time when request was sent
-			private long circuitOpened = -1;
-
-			public void handleEvent(Event event) {
-				if (event.getType() == NodeEventType.NODE_CIRCUIT_OPENED) {
-					if (circuitOpened == -1) {
-						circuitOpened = System.currentTimeMillis();
-					}
-				} else if (event.getType() == HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED) {
-					System.out.println("RSD published "
-							+ (System.currentTimeMillis() - circuitOpened)
-							+ " milliseconds after first circuit was opened");
-				}
-			}
-		};
-
-		// obtain reference to event manager to be able to respond to events
-		final EventManager manager = network.getEventManager();
-
-		// register event handler for proxy events
-		manager.addEventListener(proxy.getNodeName(), proxyEventListener);
-
-		// start proxy node and wait until it has opened a circuit with a
-		// timeout of 5 seconds
-		if (!network.startNodes(5000)) {
-
-			// failed to start the proxy
-			System.out.println("Failed to start the node!");
-			System.exit(0);
-		}
-		System.out.println("Successfully started the node!");
-
-		// hup until proxy has built circuits (5 retries, 10 seconds timeout
-		// each)
-		if (!network.hupUntilUp(5, 10000)) {
-
-			// failed to build circuits
-			System.out.println("Failed to build circuits!");
-			System.exit(0);
-		}
-		System.out.println("Successfully built circuits!");
-
-		// let it run for 2 minutes and observe when RSDs are published...
-		System.out
-				.println("Waiting for 2 minutes and observing RSD publications...");
-
-		try {
-			Thread.sleep(2L * 60L * 1000L);
-		} catch (final InterruptedException e) {
-			// do nothing
-		}
-
-		// shut down proxy
-		network.shutdownNodes();
-
-		// Shut down the JVM
-		System.out.println("Goodbye.");
-		System.exit(0);
-	}
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor.examples;
+
+import de.uniba.wiai.lspi.puppetor.Event;
+import de.uniba.wiai.lspi.puppetor.EventListener;
+import de.uniba.wiai.lspi.puppetor.EventManager;
+import de.uniba.wiai.lspi.puppetor.HiddenServiceEventType;
+import de.uniba.wiai.lspi.puppetor.Network;
+import de.uniba.wiai.lspi.puppetor.NetworkFactory;
+import de.uniba.wiai.lspi.puppetor.NodeEventType;
+import de.uniba.wiai.lspi.puppetor.ProxyNode;
+import de.uniba.wiai.lspi.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.
+	 */
+	public static void main(final String[] args) throws PuppeTorException {
+		// create a network to initialize the test case
+		final Network network = NetworkFactory.createNetwork("example2");
+
+		// create a single proxy node
+		final ProxyNode proxy = network.createProxy("proxy");
+
+		// add hidden service to the configuration
+		proxy.addHiddenService("hidServ");
+
+		// write configuration of proxy node
+		network.writeConfigurations();
+
+		// create event listener to listen for events from our proxy
+		final EventListener proxyEventListener = new EventListener() {
+
+			// remember time when request was sent
+			private long circuitOpened = -1;
+
+			public void handleEvent(Event event) {
+				if (event.getType() == NodeEventType.NODE_CIRCUIT_OPENED) {
+					if (circuitOpened == -1) {
+						circuitOpened = System.currentTimeMillis();
+					}
+				} else if (event.getType() == HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED) {
+					System.out.println("RSD published "
+							+ (System.currentTimeMillis() - circuitOpened)
+							+ " milliseconds after first circuit was opened");
+				}
+			}
+		};
+
+		// obtain reference to event manager to be able to respond to events
+		final EventManager manager = network.getEventManager();
+
+		// register event handler for proxy events
+		manager.addEventListener(proxy.getNodeName(), proxyEventListener);
+
+		// start proxy node and wait until it has opened a circuit with a
+		// timeout of 5 seconds
+		if (!network.startNodes(5000)) {
+
+			// failed to start the proxy
+			System.out.println("Failed to start the node!");
+			System.exit(0);
+		}
+		System.out.println("Successfully started the node!");
+
+		// hup until proxy has built circuits (5 retries, 10 seconds timeout
+		// each)
+		if (!network.hupUntilUp(5, 10000)) {
+
+			// failed to build circuits
+			System.out.println("Failed to build circuits!");
+			System.exit(0);
+		}
+		System.out.println("Successfully built circuits!");
+
+		// let it run for 2 minutes and observe when RSDs are published...
+		System.out
+				.println("Waiting for 2 minutes and observing RSD publications...");
+
+		try {
+			Thread.sleep(2L * 60L * 1000L);
+		} catch (final InterruptedException e) {
+			// do nothing
+		}
+
+		// shut down proxy
+		network.shutdownNodes();
+
+		// Shut down the JVM
+		System.out.println("Goodbye.");
+		System.exit(0);
+	}
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/impl/ClientApplicationImpl.java b/src/de/uniba/wiai/lspi/puppetor/impl/ClientApplicationImpl.java
old mode 100755
new mode 100644
index df7667f..f42a37a
--- a/src/de/uniba/wiai/lspi/puppetor/impl/ClientApplicationImpl.java
+++ b/src/de/uniba/wiai/lspi/puppetor/impl/ClientApplicationImpl.java
@@ -1,483 +1,483 @@
-/*
- * 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 de.uniba.wiai.lspi.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.util.logging.Level;
-import java.util.logging.Logger;
-
-import de.uniba.wiai.lspi.puppetor.ClientApplication;
-import de.uniba.wiai.lspi.puppetor.ClientEventType;
-
-/**
- * Implementation of <code>ClientApplication</code>.
- *
- * @author kloesing
- */
-public class ClientApplicationImpl implements ClientApplication {
-
-	/**
-	 * Internal thread class that is used to perform requests.
-	 */
-	private class RequestThread extends Thread {
-
-		/**
-		 * Flag to remember whether requests are performed at the moment (<code>true</code>),
-		 * or have been stopped (<code>false</code>).
-		 */
-		private boolean connected;
-
-		/**
-		 * Number of retries to be performed.
-		 */
-		private final int retries;
-
-		/**
-		 * Flag that determines whether requests shall be stopped after the
-		 * first successful reply (<code>true</code>), or not (<code>false</code>).
-		 */
-		private final boolean stopOnSuccess;
-
-		/**
-		 * Timeout in milliseconds for each retry.
-		 */
-		private final long timeoutForEachRetry;
-
-		/**
-		 * Creates a new thread, but does not start performing requests, yet.
-		 *
-		 * @param retries
-		 *            Number of retries to be performed.
-		 * @param timeoutForEachRetry
-		 *            Timeout in milliseconds for each retry.
-		 * @param stopOnSuccess
-		 *            Flag that determines whether requests shall be stopped
-		 *            after the first successful reply (<code>true</code>),
-		 *            or not (<code>false</code>).
-		 */
-		RequestThread(final int retries, final long timeoutForEachRetry,
-				final boolean stopOnSuccess) {
-
-			// log entering
-			logger
-					.entering(this.getClass().getName(), "RequestThread",
-							new Object[] { retries, timeoutForEachRetry,
-									stopOnSuccess });
-
-			// check parameters
-			if (retries < 0 || timeoutForEachRetry < 0) {
-				final IllegalArgumentException e =
-						new IllegalArgumentException();
-				logger.throwing(this.getClass().getName(), "RequestThread", e);
-				throw e;
-			}
-
-			// remember parameters
-			this.retries = retries;
-			this.timeoutForEachRetry = timeoutForEachRetry;
-			this.stopOnSuccess = stopOnSuccess;
-
-			// start connected
-			connected = true;
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "RequestThread");
-		}
-
-		/**
-		 * Perform one or more requests.
-		 */
-		@Override
-		public void run() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "run");
-
-			try {
-
-				// set Tor as proxy
-				final InetSocketAddress isa =
-						new InetSocketAddress("127.0.0.1", socksPort);
-				final Proxy p = new Proxy(Type.SOCKS, isa);
-
-				// create target address for socket -- don't resolve the target
-				// name to an IP address!
-				final InetSocketAddress hs =
-						InetSocketAddress.createUnresolved(targetName,
-								targetPort);
-
-				// start retry loop
-				for (int i = 0; connected && i < retries; i++) {
-
-					// log this try
-					logger.log(Level.FINE, "Trying to perform request");
-
-					// remember when we started
-					final long timeBeforeConnectionAttempt =
-							System.currentTimeMillis();
-
-					// send event to event manager
-					eventManager.observeInternalEvent(
-							timeBeforeConnectionAttempt,
-							getClientApplicationName(),
-							ClientEventType.CLIENT_SENDING_REQUEST,
-							"Sending request.");
-
-					Socket s = null;
-					try {
-
-						// create new socket using Tor as proxy
-						s = new Socket(p);
-
-						// try to connect to remote server
-						s.connect(hs, (int) timeoutForEachRetry);
-
-						// open output stream to write request
-						final PrintStream out =
-								new PrintStream(s.getOutputStream());
-						out.print("GET / HTTP/1.0\r\n\r\n");
-
-						// open input stream to read reply
-						final BufferedReader in =
-								new BufferedReader(new InputStreamReader(s
-										.getInputStream()));
-
-						// only read the first char in the response; this method
-						// blocks until there is a response
-						in.read();
-
-						// send event to event manager
-						eventManager.observeInternalEvent(System
-								.currentTimeMillis(),
-								getClientApplicationName(),
-								ClientEventType.CLIENT_REPLY_RECEIVED,
-								"Received response.");
-
-						// if we should stop on success, stop further connection
-						// attempts
-						if (stopOnSuccess) {
-							connected = false;
-						}
-
-						// clean up socket
-						in.close();
-						out.close();
-						s.close();
-
-					} catch (final SocketTimeoutException e) {
-
-						// log warning
-						logger.log(Level.WARNING,
-								"Connection to remote server timed out!", e);
-
-						// send event to event manager
-						eventManager.observeInternalEvent(System
-								.currentTimeMillis(),
-								getClientApplicationName(),
-								ClientEventType.CLIENT_GAVE_UP_REQUEST,
-								"Giving up request.");
-
-						// try again immediately, if there are retries left
-
-					} catch (final IOException e) {
-
-						// log warning
-						logger.log(Level.WARNING,
-								"Connection to remote server could not be "
-										+ "established!", e);
-
-						// send event to event manager
-						eventManager.observeInternalEvent(System
-								.currentTimeMillis(),
-								getClientApplicationName(),
-								ClientEventType.CLIENT_GAVE_UP_REQUEST,
-								"Giving up request.");
-
-						// wait for the rest of the timeout
-						final long timeOfTimeoutLeft =
-								timeBeforeConnectionAttempt
-										+ timeoutForEachRetry
-										- System.currentTimeMillis();
-						if (timeOfTimeoutLeft > 0) {
-							try {
-								Thread.sleep(timeOfTimeoutLeft);
-							} catch (final InterruptedException ex) {
-								// do nothing
-							}
-						}
-
-					} finally {
-						if (s != null) {
-							// close connection
-							try {
-
-								// try to close socket
-								logger.log(Level.FINER,
-										"Trying to close socket.");
-								s.close();
-								logger.log(Level.FINE, "Socket closed.");
-
-							} catch (final IOException e) {
-
-								// log warning
-								logger.log(Level.WARNING,
-									"Exception when trying to close socket!",
-									e);
-							}
-						}
-					}
-				}
-
-			} finally {
-
-				// we are done here
-				logger.log(Level.FINE, "Requests performed!");
-
-				// send event to event manager
-				eventManager.observeInternalEvent(System.currentTimeMillis(),
-						getClientApplicationName(),
-						ClientEventType.CLIENT_REQUESTS_PERFORMED,
-						"Requests performed.");
-
-				// log exiting
-				logger.exiting(this.getClass().getName(), "run");
-			}
-		}
-
-		/**
-		 * Immediately stops this and all possibly subsequent requests.
-		 */
-		public void stopRequest() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "stopRequest");
-
-			// change connected state to false and interrupt thread
-			connected = false;
-			interrupt();
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "stopRequest");
-		}
-	}
-
-	/**
-	 * Name of this client application that is used as logger name of this node.
-	 */
-	private final String clientApplicationName;
-
-	/**
-	 * Thread that performs the requests in the background.
-	 */
-	private RequestThread clientThread;
-
-	/**
-	 * Event manager that handles all events concerning this client application.
-	 */
-	private final EventManagerImpl eventManager;
-
-	/**
-	 * Logger for this client which is called "client." plus the name of this
-	 * client application.
-	 */
-	private final Logger logger;
-
-	/**
-	 * SOCKS port of the local Tor node to which requests are sent.
-	 */
-	private final int socksPort;
-
-	/**
-	 * Target name for the requests sent by this client; can be a publicly
-	 * available URL or an onion address.
-	 */
-	private final String targetName;
-
-	/**
-	 * Target port for the requests sent by this client; can be either a server
-	 * port or a virtual port of a hidden service.
-	 */
-	private final int targetPort;
-
-	/**
-	 * Creates a new HTTP client within this JVM, but does not start sending
-	 * requests.
-	 *
-	 * @param network
-	 *            Network to which this HTTP client belongs; at the moment this
-	 *            is only used to determine the event manager instance.
-	 * @param clientApplicationName
-	 *            Name of this client that is used as part of the logger name.
-	 * @param targetName
-	 *            Target name for requests; can be either a server name/address
-	 *            or an onion address.
-	 * @param targetPort
-	 *            Target port for requests; can be either a server port or a
-	 *            virtual port of a hidden service.
-	 * @param socksPort
-	 *            SOCKS port of the local Tor node.
-	 * @throws IllegalArgumentException
-	 *             If at least one of the parameters is <code>null</code> or
-	 *             has an invalid value.
-	 */
-	ClientApplicationImpl(final NetworkImpl network,
-			final String clientApplicationName, final String targetName,
-			final int targetPort, final int socksPort) {
-
-		// check if clientApplicationName can be used as logger name
-		if (clientApplicationName == null
-				|| clientApplicationName.length() == 0) {
-			throw new IllegalArgumentException(
-					"Invalid clientApplicationName: " + clientApplicationName);
-		}
-
-		// create logger
-		logger = Logger.getLogger("client." + clientApplicationName);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "ClientApplicationImpl",
-				new Object[] { network, clientApplicationName, targetName,
-						targetPort, socksPort });
-
-		// check parameters
-		if (network == null || targetName == null || targetName.length() == 0
-				|| targetPort < 0 || targetPort > 65535 || socksPort < 0
-				|| socksPort > 65535) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "ClientApplicationImpl",
-					e);
-			throw e;
-		}
-
-		// remember parameters
-		this.clientApplicationName = clientApplicationName;
-		this.targetName = targetName;
-		this.targetPort = targetPort;
-		this.socksPort = socksPort;
-
-		// obtain and store reference on event manager
-		eventManager = network.getEventManagerImpl();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "ClientApplicationImpl");
-	}
-
-	public synchronized void startRequests(final int retries,
-			final long timeoutForEachRetry, final boolean stopOnSuccess) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "performRequest",
-				new Object[] { retries, timeoutForEachRetry, stopOnSuccess });
-
-		// check parameters
-		if (retries <= 0 || timeoutForEachRetry < 0) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "performRequest", e);
-			throw e;
-		}
-
-		// check if we already have started a request (TODO change this to allow
-		// multiple requests in parallel? would be possible)
-		if (clientThread != null) {
-			final IllegalStateException e =
-					new IllegalStateException(
-							"Another request has already been started!");
-			logger.throwing(this.getClass().getName(), "performRequest", e);
-			throw e;
-		}
-
-		// create a thread that performs requests in the background
-		clientThread =
-				new RequestThread(retries, timeoutForEachRetry, stopOnSuccess);
-		clientThread.setName("Request Thread");
-		clientThread.setDaemon(true);
-		clientThread.start();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "performRequest");
-	}
-
-	public synchronized void stopRequest() {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "stopRequest");
-
-		// check if a request is running
-		if (clientThread == null) {
-			final IllegalStateException e =
-					new IllegalStateException("Cannot stop "
-							+ "request, because no request has been started!");
-			logger.throwing(this.getClass().getName(), "stopRequest", e);
-			throw e;
-		}
-
-		// log this event
-		logger.log(Level.FINE, "Shutting down client");
-
-		// interrupt thread
-		clientThread.stopRequest();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "stopRequest");
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": clientApplicationName=\""
-				+ clientApplicationName + "\", targetAddress=\"" + targetName
-				+ "\", targetPort=" + targetPort + ", socksPort=" + socksPort;
-	}
-
-	public String getClientApplicationName() {
-		return clientApplicationName;
-	}
-
-	public int getSocksPort() {
-		return socksPort;
-	}
-
-	public String getTargetName() {
-		return targetName;
-	}
-
-	public int getTargetPort() {
-		return targetPort;
-	}
-}
+/*
+ * 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 de.uniba.wiai.lspi.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.util.logging.Level;
+import java.util.logging.Logger;
+
+import de.uniba.wiai.lspi.puppetor.ClientApplication;
+import de.uniba.wiai.lspi.puppetor.ClientEventType;
+
+/**
+ * Implementation of <code>ClientApplication</code>.
+ *
+ * @author kloesing
+ */
+public class ClientApplicationImpl implements ClientApplication {
+
+	/**
+	 * Internal thread class that is used to perform requests.
+	 */
+	private class RequestThread extends Thread {
+
+		/**
+		 * Flag to remember whether requests are performed at the moment (<code>true</code>),
+		 * or have been stopped (<code>false</code>).
+		 */
+		private boolean connected;
+
+		/**
+		 * Number of retries to be performed.
+		 */
+		private final int retries;
+
+		/**
+		 * Flag that determines whether requests shall be stopped after the
+		 * first successful reply (<code>true</code>), or not (<code>false</code>).
+		 */
+		private final boolean stopOnSuccess;
+
+		/**
+		 * Timeout in milliseconds for each retry.
+		 */
+		private final long timeoutForEachRetry;
+
+		/**
+		 * Creates a new thread, but does not start performing requests, yet.
+		 *
+		 * @param retries
+		 *            Number of retries to be performed.
+		 * @param timeoutForEachRetry
+		 *            Timeout in milliseconds for each retry.
+		 * @param stopOnSuccess
+		 *            Flag that determines whether requests shall be stopped
+		 *            after the first successful reply (<code>true</code>),
+		 *            or not (<code>false</code>).
+		 */
+		RequestThread(final int retries, final long timeoutForEachRetry,
+				final boolean stopOnSuccess) {
+
+			// log entering
+			logger
+					.entering(this.getClass().getName(), "RequestThread",
+							new Object[] { retries, timeoutForEachRetry,
+									stopOnSuccess });
+
+			// check parameters
+			if (retries < 0 || timeoutForEachRetry < 0) {
+				final IllegalArgumentException e =
+						new IllegalArgumentException();
+				logger.throwing(this.getClass().getName(), "RequestThread", e);
+				throw e;
+			}
+
+			// remember parameters
+			this.retries = retries;
+			this.timeoutForEachRetry = timeoutForEachRetry;
+			this.stopOnSuccess = stopOnSuccess;
+
+			// start connected
+			connected = true;
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "RequestThread");
+		}
+
+		/**
+		 * Perform one or more requests.
+		 */
+		@Override
+		public void run() {
+
+			// log entering
+			logger.entering(this.getClass().getName(), "run");
+
+			try {
+
+				// set Tor as proxy
+				final InetSocketAddress isa =
+						new InetSocketAddress("127.0.0.1", socksPort);
+				final Proxy p = new Proxy(Type.SOCKS, isa);
+
+				// create target address for socket -- don't resolve the target
+				// name to an IP address!
+				final InetSocketAddress hs =
+						InetSocketAddress.createUnresolved(targetName,
+								targetPort);
+
+				// start retry loop
+				for (int i = 0; connected && i < retries; i++) {
+
+					// log this try
+					logger.log(Level.FINE, "Trying to perform request");
+
+					// remember when we started
+					final long timeBeforeConnectionAttempt =
+							System.currentTimeMillis();
+
+					// send event to event manager
+					eventManager.observeInternalEvent(
+							timeBeforeConnectionAttempt,
+							getClientApplicationName(),
+							ClientEventType.CLIENT_SENDING_REQUEST,
+							"Sending request.");
+
+					Socket s = null;
+					try {
+
+						// create new socket using Tor as proxy
+						s = new Socket(p);
+
+						// try to connect to remote server
+						s.connect(hs, (int) timeoutForEachRetry);
+
+						// open output stream to write request
+						final PrintStream out =
+								new PrintStream(s.getOutputStream());
+						out.print("GET / HTTP/1.0\r\n\r\n");
+
+						// open input stream to read reply
+						final BufferedReader in =
+								new BufferedReader(new InputStreamReader(s
+										.getInputStream()));
+
+						// only read the first char in the response; this method
+						// blocks until there is a response
+						in.read();
+
+						// send event to event manager
+						eventManager.observeInternalEvent(System
+								.currentTimeMillis(),
+								getClientApplicationName(),
+								ClientEventType.CLIENT_REPLY_RECEIVED,
+								"Received response.");
+
+						// if we should stop on success, stop further connection
+						// attempts
+						if (stopOnSuccess) {
+							connected = false;
+						}
+
+						// clean up socket
+						in.close();
+						out.close();
+						s.close();
+
+					} catch (final SocketTimeoutException e) {
+
+						// log warning
+						logger.log(Level.WARNING,
+								"Connection to remote server timed out!", e);
+
+						// send event to event manager
+						eventManager.observeInternalEvent(System
+								.currentTimeMillis(),
+								getClientApplicationName(),
+								ClientEventType.CLIENT_GAVE_UP_REQUEST,
+								"Giving up request.");
+
+						// try again immediately, if there are retries left
+
+					} catch (final IOException e) {
+
+						// log warning
+						logger.log(Level.WARNING,
+								"Connection to remote server could not be "
+										+ "established!", e);
+
+						// send event to event manager
+						eventManager.observeInternalEvent(System
+								.currentTimeMillis(),
+								getClientApplicationName(),
+								ClientEventType.CLIENT_GAVE_UP_REQUEST,
+								"Giving up request.");
+
+						// wait for the rest of the timeout
+						final long timeOfTimeoutLeft =
+								timeBeforeConnectionAttempt
+										+ timeoutForEachRetry
+										- System.currentTimeMillis();
+						if (timeOfTimeoutLeft > 0) {
+							try {
+								Thread.sleep(timeOfTimeoutLeft);
+							} catch (final InterruptedException ex) {
+								// do nothing
+							}
+						}
+
+					} finally {
+						if (s != null) {
+							// close connection
+							try {
+
+								// try to close socket
+								logger.log(Level.FINER,
+										"Trying to close socket.");
+								s.close();
+								logger.log(Level.FINE, "Socket closed.");
+
+							} catch (final IOException e) {
+
+								// log warning
+								logger.log(Level.WARNING,
+									"Exception when trying to close socket!",
+									e);
+							}
+						}
+					}
+				}
+
+			} finally {
+
+				// we are done here
+				logger.log(Level.FINE, "Requests performed!");
+
+				// send event to event manager
+				eventManager.observeInternalEvent(System.currentTimeMillis(),
+						getClientApplicationName(),
+						ClientEventType.CLIENT_REQUESTS_PERFORMED,
+						"Requests performed.");
+
+				// log exiting
+				logger.exiting(this.getClass().getName(), "run");
+			}
+		}
+
+		/**
+		 * Immediately stops this and all possibly subsequent requests.
+		 */
+		public void stopRequest() {
+
+			// log entering
+			logger.entering(this.getClass().getName(), "stopRequest");
+
+			// change connected state to false and interrupt thread
+			connected = false;
+			interrupt();
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "stopRequest");
+		}
+	}
+
+	/**
+	 * Name of this client application that is used as logger name of this node.
+	 */
+	private final String clientApplicationName;
+
+	/**
+	 * Thread that performs the requests in the background.
+	 */
+	private RequestThread clientThread;
+
+	/**
+	 * Event manager that handles all events concerning this client application.
+	 */
+	private final EventManagerImpl eventManager;
+
+	/**
+	 * Logger for this client which is called "client." plus the name of this
+	 * client application.
+	 */
+	private final Logger logger;
+
+	/**
+	 * SOCKS port of the local Tor node to which requests are sent.
+	 */
+	private final int socksPort;
+
+	/**
+	 * Target name for the requests sent by this client; can be a publicly
+	 * available URL or an onion address.
+	 */
+	private final String targetName;
+
+	/**
+	 * Target port for the requests sent by this client; can be either a server
+	 * port or a virtual port of a hidden service.
+	 */
+	private final int targetPort;
+
+	/**
+	 * Creates a new HTTP client within this JVM, but does not start sending
+	 * requests.
+	 *
+	 * @param network
+	 *            Network to which this HTTP client belongs; at the moment this
+	 *            is only used to determine the event manager instance.
+	 * @param clientApplicationName
+	 *            Name of this client that is used as part of the logger name.
+	 * @param targetName
+	 *            Target name for requests; can be either a server name/address
+	 *            or an onion address.
+	 * @param targetPort
+	 *            Target port for requests; can be either a server port or a
+	 *            virtual port of a hidden service.
+	 * @param socksPort
+	 *            SOCKS port of the local Tor node.
+	 * @throws IllegalArgumentException
+	 *             If at least one of the parameters is <code>null</code> or
+	 *             has an invalid value.
+	 */
+	ClientApplicationImpl(final NetworkImpl network,
+			final String clientApplicationName, final String targetName,
+			final int targetPort, final int socksPort) {
+
+		// check if clientApplicationName can be used as logger name
+		if (clientApplicationName == null
+				|| clientApplicationName.length() == 0) {
+			throw new IllegalArgumentException(
+					"Invalid clientApplicationName: " + clientApplicationName);
+		}
+
+		// create logger
+		logger = Logger.getLogger("client." + clientApplicationName);
+
+		// log entering
+		logger.entering(this.getClass().getName(), "ClientApplicationImpl",
+				new Object[] { network, clientApplicationName, targetName,
+						targetPort, socksPort });
+
+		// check parameters
+		if (network == null || targetName == null || targetName.length() == 0
+				|| targetPort < 0 || targetPort > 65535 || socksPort < 0
+				|| socksPort > 65535) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "ClientApplicationImpl",
+					e);
+			throw e;
+		}
+
+		// remember parameters
+		this.clientApplicationName = clientApplicationName;
+		this.targetName = targetName;
+		this.targetPort = targetPort;
+		this.socksPort = socksPort;
+
+		// obtain and store reference on event manager
+		eventManager = network.getEventManagerImpl();
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "ClientApplicationImpl");
+	}
+
+	public synchronized void startRequests(final int retries,
+			final long timeoutForEachRetry, final boolean stopOnSuccess) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "performRequest",
+				new Object[] { retries, timeoutForEachRetry, stopOnSuccess });
+
+		// check parameters
+		if (retries <= 0 || timeoutForEachRetry < 0) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "performRequest", e);
+			throw e;
+		}
+
+		// check if we already have started a request (TODO change this to allow
+		// multiple requests in parallel? would be possible)
+		if (clientThread != null) {
+			final IllegalStateException e =
+					new IllegalStateException(
+							"Another request has already been started!");
+			logger.throwing(this.getClass().getName(), "performRequest", e);
+			throw e;
+		}
+
+		// create a thread that performs requests in the background
+		clientThread =
+				new RequestThread(retries, timeoutForEachRetry, stopOnSuccess);
+		clientThread.setName("Request Thread");
+		clientThread.setDaemon(true);
+		clientThread.start();
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "performRequest");
+	}
+
+	public synchronized void stopRequest() {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "stopRequest");
+
+		// check if a request is running
+		if (clientThread == null) {
+			final IllegalStateException e =
+					new IllegalStateException("Cannot stop "
+							+ "request, because no request has been started!");
+			logger.throwing(this.getClass().getName(), "stopRequest", e);
+			throw e;
+		}
+
+		// log this event
+		logger.log(Level.FINE, "Shutting down client");
+
+		// interrupt thread
+		clientThread.stopRequest();
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "stopRequest");
+	}
+
+	@Override
+	public String toString() {
+		return this.getClass().getSimpleName() + ": clientApplicationName=\""
+				+ clientApplicationName + "\", targetAddress=\"" + targetName
+				+ "\", targetPort=" + targetPort + ", socksPort=" + socksPort;
+	}
+
+	public String getClientApplicationName() {
+		return clientApplicationName;
+	}
+
+	public int getSocksPort() {
+		return socksPort;
+	}
+
+	public String getTargetName() {
+		return targetName;
+	}
+
+	public int getTargetPort() {
+		return targetPort;
+	}
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/impl/DirectoryNodeImpl.java b/src/de/uniba/wiai/lspi/puppetor/impl/DirectoryNodeImpl.java
old mode 100755
new mode 100644
index a8552bd..180dc1d
--- a/src/de/uniba/wiai/lspi/puppetor/impl/DirectoryNodeImpl.java
+++ b/src/de/uniba/wiai/lspi/puppetor/impl/DirectoryNodeImpl.java
@@ -1,457 +1,457 @@
-/*
- * 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 de.uniba.wiai.lspi.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.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.logging.Level;
-
-import de.uniba.wiai.lspi.puppetor.DirectoryNode;
-import de.uniba.wiai.lspi.puppetor.PuppeTorException;
-
-/**
- * Implementation of <code>DirectoryNode</code>.
- *
- * @author kloesing
- */
-public class DirectoryNodeImpl extends RouterNodeImpl implements DirectoryNode {
-
-	/**
-	 * Executable file for generating v3 directory authority certificates.
-	 *
-	 * TODO make this configurable!
-	 */
-	protected static final File torGencertExecutable = new File("tor-gencert");
-
-	/**
-	 * Internal thread class that is used to generate v3 directory authority
-	 * certificates in parallel, which can take a few seconds.
-	 */
-	private class GenerateCertificateThread extends Thread {
-
-		@Override
-		public void run() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "run");
-
-			// run tor-gencert
-			final ProcessBuilder processBuilder =
-					new ProcessBuilder(torGencertExecutable.getPath(),
-							"--create-identity-key"// );
-							, "--passphrase-fd", "0");
-			final File workingDirectory =
-					new File(workingDir.getAbsolutePath() + File.separator
-							+ "keys" + File.separator);
-
-			// create working directory
-			workingDirectory.mkdirs();
-
-			processBuilder.directory(workingDirectory);
-			processBuilder.redirectErrorStream(true);
-			Process tmpProcess = null;
-			try {
-				tmpProcess = processBuilder.start();
-			} catch (final IOException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not start tor-gencert process for generating a "
-										+ "v3 directory authority certificate!",
-								e);
-				logger.log(Level.WARNING, "Could not start tor-gencert "
-						+ "process for generating a v3 directory authority "
-						+ "certificate!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			final BufferedWriter writer =
-					new BufferedWriter(new OutputStreamWriter(tmpProcess
-							.getOutputStream()));
-			try {
-				writer.write("somepassword\n");
-				writer.close();
-			} catch (final IOException e1) {
-				System.out.println("Exception at write! " + e1.getMessage());
-				e1.printStackTrace();
-			}
-			// final InputStream read = tmpProcess.getErrorStream();
-
-			final InputStream stderr = tmpProcess.getInputStream();
-			final InputStreamReader isr = new InputStreamReader(stderr);
-			final BufferedReader br = new BufferedReader(isr);
-			String line = null;
-			try {
-				while ((line = br.readLine()) != null) {
-					;
-				}
-			} catch (final IOException e1) {
-				e1.printStackTrace();
-			}
-
-			// wait for process to terminate
-			int exitValue = 0;
-			try {
-				exitValue = tmpProcess.waitFor();
-			} catch (final InterruptedException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Interrupted while waiting for tor-gencert process to exit!",
-								e);
-				logger.log(Level.WARNING,
-						"tor-gencert process was interrupted!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			if (exitValue != 0) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not start tor-gencert process! tor-gencert exited with "
-										+ "exit value " + exitValue + "!");
-				logger.log(Level.WARNING,
-						"Could not start tor-gencert process!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			// read fingerprint from file
-			final File authorityCertificateFile =
-					new File(workingDirectory.getAbsolutePath()
-							+ File.separator + "authority_certificate");
-			String identity;
-			try {
-				final BufferedReader br2 =
-						new BufferedReader(new FileReader(
-								authorityCertificateFile));
-				while ((line = br2.readLine()) != null
-						&& !line.startsWith("fingerprint ")) {
-					;
-				}
-				if (line == null) {
-					final PuppeTorException ex =
-							new PuppeTorException(
-									"Could not find fingerprint line in file "
-											+ "authority_certificate!");
-					logger.log(Level.WARNING,
-							"Could not find fingerprint line in file "
-									+ "authority_certificate!", ex);
-					setCaughtException(ex);
-					return;
-				}
-				identity = line.substring(line.indexOf(" ") + 1);
-				br2.close();
-			} catch (final IOException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not read fingerprint from file!", e);
-				logger.log(Level.WARNING, "Could not read fingerprint file!",
-						ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			setV3Identity(identity);
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "run");
-		}
-	}
-
-	/**
-	 * Set of routers that are approved by this directory node.
-	 */
-	private final SortedSet<String> approvedRouters;
-
-	/**
-	 * Creates a <code>DirectoryNodeImpl</code> and adds it to the given
-	 * <code>network</code>, but does not yet write its configuration to disk
-	 * or start the corresponding Tor process.
-	 *
-	 * @param network
-	 *            Network configuration to which this node belongs.
-	 * @param nodeName
-	 *            The name of the new node which may only consist of between 1
-	 *            and 19 alpha-numeric characters.
-	 * @param controlPort
-	 *            Port on which the Tor node will be listening for us as its
-	 *            controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            Port on which the Tor node will be listening for SOCKS
-	 *            connection requests. May not be negative or greater than
-	 *            65535.
-	 * @param orPort
-	 *            Port on which the Tor node will be listening for onion
-	 *            requests by other Tor nodes. May not be negative or greater
-	 *            than 65535.
-	 * @param dirPort
-	 *            Port on which the Tor node will be listening for directory
-	 *            requests from other Tor nodes. May not be negative or greater
-	 *            than 65535.
-	 * @param serverIpAddress
-	 *            The IP address on which the node will listen. Must be a valid
-	 *            IP v4 address in dotted decimal notation. May not be
-	 *            <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             If at least one of the parameters is <code>null</code> or
-	 *             has an invalid value.
-	 * @throws PuppeTorException
-	 *             Thrown if an I/O problem occurs while writing the temporary
-	 *             <code>approved-routers</code> file.
-	 */
-	DirectoryNodeImpl(final NetworkImpl network, final String nodeName,
-			final int controlPort, final int socksPort, final int orPort,
-			final int dirPort, final String serverIpAddress) {
-		// create superclass instance; parameter checking is done in super
-		// constructor
-		super(network, nodeName, controlPort, socksPort, orPort, dirPort,
-				serverIpAddress);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "DirectoryNodeImpl",
-				new Object[] { network, nodeName, controlPort, socksPort,
-						orPort, dirPort });
-
-		// initialize attribute
-		approvedRouters = new TreeSet<String>();
-
-		// extend configuration by template configuration of directory nodes
-		configuration.addAll(templateConfiguration);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "DirectoryNodeImpl");
-	}
-
-	/**
-	 * Invoked by the certificate generating thread: sets the generated v3
-	 * identity string.
-	 *
-	 * @param v3Identity
-	 *            The generated v3 identity string.
-	 */
-	private synchronized void setV3Identity(final String v3Identity) {
-		// log entering
-		logger.entering(this.getClass().getName(), "setV3Identity", v3Identity);
-
-		// remember fingerprint and notify all waiting threads
-		this.v3Identity = v3Identity;
-		notifyAll();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "setV3Identity");
-	}
-
-	/**
-	 * The generated v3 identity string.
-	 */
-	private String v3Identity;
-
-	public synchronized String getV3Identity() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "getV3Identity");
-
-		// wait until either the v3 identity has been determined or an exception
-		// was caught
-		while (v3Identity == null && caughtException == null) {
-
-			try {
-				wait(500);
-			} catch (final InterruptedException e) {
-				// do nothing
-			}
-		}
-
-		if (caughtException != null) {
-			logger.throwing(this.getClass().getName(), "getV3Identity",
-					caughtException);
-			throw caughtException;
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "getV3Identity", v3Identity);
-		return v3Identity;
-	}
-
-	public synchronized String getDirServerString() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "getDirServerString");
-
-		// determine fingerprint
-		String fingerprint = getFingerprint();
-
-		// cut off router nickname
-		fingerprint = fingerprint.substring(fingerprint.indexOf(" ") + 1);
-
-		// determine v3 identity
-		final String determinedV3Identity = getV3Identity();
-
-		// put everything together
-		final String dirServerString =
-				"DirServer " + nodeName + " v3ident=" + determinedV3Identity
-						+ " orport=" + orPort + " " + serverIpAddress + ":"
-						+ dirPort + " " + fingerprint;
-
-		// log exiting and return dir server string
-		logger.exiting(this.getClass().getName(), "getDirServerString",
-				dirServerString);
-		return dirServerString;
-	}
-
-	public void addApprovedRouters(final Set<String> routers) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addApprovedRouters",
-				routers);
-
-		// check parameter
-		if (routers == null) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "addApprovedRouters", e);
-			throw e;
-		}
-
-		// add the given approved router strings to the sorted set of already
-		// known strings (if any)
-		approvedRouters.addAll(routers);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "addApprovedRouters");
-	}
-
-	@Override
-	protected synchronized void determineFingerprint() {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "determineFingerprint");
-
-		// start a thread to generate the directory's certificate
-		final GenerateCertificateThread certificateThread =
-				new GenerateCertificateThread();
-		certificateThread.setName(nodeName + " Certificate Generator");
-		certificateThread.start();
-
-		// wait (non-blocking) for the v3 identity string
-		try {
-			getV3Identity();
-		} catch (final PuppeTorException e1) {
-			final PuppeTorException ex =
-					new PuppeTorException("Could not read v3 identity string!",
-							e1);
-			caughtException = ex;
-			return;
-		}
-
-		// create an empty approved-routers file to make Tor happy
-		try {
-			new File(workingDir.getAbsolutePath() + File.separator
-					+ "approved-routers").createNewFile();
-		} catch (final IOException e) {
-			final PuppeTorException ex =
-					new PuppeTorException(
-							"Could not write empty approved-routers file!", e);
-			caughtException = ex;
-			return;
-		}
-
-		// invoke overwritten method
-		super.determineFingerprint();
-	}
-
-	@Override
-	public synchronized void writeConfiguration() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "writeConfiguration");
-
-		// write approved-routers file
-		try {
-			final File approvedRoutersFile =
-					new File(workingDir.getAbsolutePath() + File.separator
-							+ "approved-routers");
-			final BufferedWriter bw =
-					new BufferedWriter(new FileWriter(approvedRoutersFile));
-			for (final String approvedRouter : approvedRouters) {
-				bw.write(approvedRouter + "\n");
-			}
-			bw.close();
-		} catch (final IOException e) {
-			final PuppeTorException ex = new PuppeTorException(e);
-			logger
-					.throwing(this.getClass().getName(), "writeConfiguration",
-							ex);
-			throw ex;
-		}
-
-		// invoke overridden method
-		super.writeConfiguration();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "writeConfiguration");
-	}
-
-	/**
-	 * Template configuration of directory nodes.
-	 */
-	static List<String> templateConfiguration;
-
-	static {
-		templateConfiguration = new ArrayList<String>();
-
-		// configure this node as an authoritative directory
-		templateConfiguration.add("AuthoritativeDirectory 1");
-		templateConfiguration.add("V2AuthoritativeDirectory 1");
-		templateConfiguration.add("V3AuthoritativeDirectory 1");
-		templateConfiguration.add("DirAllowPrivateAddresses 1");
-		templateConfiguration.add("MinUptimeHidServDirectoryV2 0 minutes");
-
-		// TODO This is now contained in proposal 135.
-		// templateConfiguration.add("AuthDirMaxServersPerAddr 0");
-		// templateConfiguration.add("AuthDirMaxServersPerAuthAddr 0");
-		// templateConfiguration.add("V3AuthVotingInterval 5 minutes");
-		// templateConfiguration.add("V3AuthVoteDelay 20 seconds");
-		// templateConfiguration.add("V3AuthDistDelay 20 seconds");
-	}
-}
+/*
+ * 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 de.uniba.wiai.lspi.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.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.logging.Level;
+
+import de.uniba.wiai.lspi.puppetor.DirectoryNode;
+import de.uniba.wiai.lspi.puppetor.PuppeTorException;
+
+/**
+ * Implementation of <code>DirectoryNode</code>.
+ *
+ * @author kloesing
+ */
+public class DirectoryNodeImpl extends RouterNodeImpl implements DirectoryNode {
+
+	/**
+	 * Executable file for generating v3 directory authority certificates.
+	 *
+	 * TODO make this configurable!
+	 */
+	protected static final File torGencertExecutable = new File("tor-gencert");
+
+	/**
+	 * Internal thread class that is used to generate v3 directory authority
+	 * certificates in parallel, which can take a few seconds.
+	 */
+	private class GenerateCertificateThread extends Thread {
+
+		@Override
+		public void run() {
+
+			// log entering
+			logger.entering(this.getClass().getName(), "run");
+
+			// run tor-gencert
+			final ProcessBuilder processBuilder =
+					new ProcessBuilder(torGencertExecutable.getPath(),
+							"--create-identity-key"// );
+							, "--passphrase-fd", "0");
+			final File workingDirectory =
+					new File(workingDir.getAbsolutePath() + File.separator
+							+ "keys" + File.separator);
+
+			// create working directory
+			workingDirectory.mkdirs();
+
+			processBuilder.directory(workingDirectory);
+			processBuilder.redirectErrorStream(true);
+			Process tmpProcess = null;
+			try {
+				tmpProcess = processBuilder.start();
+			} catch (final IOException e) {
+				final PuppeTorException ex =
+						new PuppeTorException(
+								"Could not start tor-gencert process for generating a "
+										+ "v3 directory authority certificate!",
+								e);
+				logger.log(Level.WARNING, "Could not start tor-gencert "
+						+ "process for generating a v3 directory authority "
+						+ "certificate!", ex);
+				setCaughtException(ex);
+				return;
+			}
+
+			final BufferedWriter writer =
+					new BufferedWriter(new OutputStreamWriter(tmpProcess
+							.getOutputStream()));
+			try {
+				writer.write("somepassword\n");
+				writer.close();
+			} catch (final IOException e1) {
+				System.out.println("Exception at write! " + e1.getMessage());
+				e1.printStackTrace();
+			}
+			// final InputStream read = tmpProcess.getErrorStream();
+
+			final InputStream stderr = tmpProcess.getInputStream();
+			final InputStreamReader isr = new InputStreamReader(stderr);
+			final BufferedReader br = new BufferedReader(isr);
+			String line = null;
+			try {
+				while ((line = br.readLine()) != null) {
+					;
+				}
+			} catch (final IOException e1) {
+				e1.printStackTrace();
+			}
+
+			// wait for process to terminate
+			int exitValue = 0;
+			try {
+				exitValue = tmpProcess.waitFor();
+			} catch (final InterruptedException e) {
+				final PuppeTorException ex =
+						new PuppeTorException(
+								"Interrupted while waiting for tor-gencert process to exit!",
+								e);
+				logger.log(Level.WARNING,
+						"tor-gencert process was interrupted!", ex);
+				setCaughtException(ex);
+				return;
+			}
+
+			if (exitValue != 0) {
+				final PuppeTorException ex =
+						new PuppeTorException(
+								"Could not start tor-gencert process! tor-gencert exited with "
+										+ "exit value " + exitValue + "!");
+				logger.log(Level.WARNING,
+						"Could not start tor-gencert process!", ex);
+				setCaughtException(ex);
+				return;
+			}
+
+			// read fingerprint from file
+			final File authorityCertificateFile =
+					new File(workingDirectory.getAbsolutePath()
+							+ File.separator + "authority_certificate");
+			String identity;
+			try {
+				final BufferedReader br2 =
+						new BufferedReader(new FileReader(
+								authorityCertificateFile));
+				while ((line = br2.readLine()) != null
+						&& !line.startsWith("fingerprint ")) {
+					;
+				}
+				if (line == null) {
+					final PuppeTorException ex =
+							new PuppeTorException(
+									"Could not find fingerprint line in file "
+											+ "authority_certificate!");
+					logger.log(Level.WARNING,
+							"Could not find fingerprint line in file "
+									+ "authority_certificate!", ex);
+					setCaughtException(ex);
+					return;
+				}
+				identity = line.substring(line.indexOf(" ") + 1);
+				br2.close();
+			} catch (final IOException e) {
+				final PuppeTorException ex =
+						new PuppeTorException(
+								"Could not read fingerprint from file!", e);
+				logger.log(Level.WARNING, "Could not read fingerprint file!",
+						ex);
+				setCaughtException(ex);
+				return;
+			}
+
+			setV3Identity(identity);
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "run");
+		}
+	}
+
+	/**
+	 * Set of routers that are approved by this directory node.
+	 */
+	private final SortedSet<String> approvedRouters;
+
+	/**
+	 * Creates a <code>DirectoryNodeImpl</code> and adds it to the given
+	 * <code>network</code>, but does not yet write its configuration to disk
+	 * or start the corresponding Tor process.
+	 *
+	 * @param network
+	 *            Network configuration to which this node belongs.
+	 * @param nodeName
+	 *            The name of the new node which may only consist of between 1
+	 *            and 19 alpha-numeric characters.
+	 * @param controlPort
+	 *            Port on which the Tor node will be listening for us as its
+	 *            controller. May not be negative or greater than 65535.
+	 * @param socksPort
+	 *            Port on which the Tor node will be listening for SOCKS
+	 *            connection requests. May not be negative or greater than
+	 *            65535.
+	 * @param orPort
+	 *            Port on which the Tor node will be listening for onion
+	 *            requests by other Tor nodes. May not be negative or greater
+	 *            than 65535.
+	 * @param dirPort
+	 *            Port on which the Tor node will be listening for directory
+	 *            requests from other Tor nodes. May not be negative or greater
+	 *            than 65535.
+	 * @param serverIpAddress
+	 *            The IP address on which the node will listen. Must be a valid
+	 *            IP v4 address in dotted decimal notation. May not be
+	 *            <code>null</code>.
+	 * @throws IllegalArgumentException
+	 *             If at least one of the parameters is <code>null</code> or
+	 *             has an invalid value.
+	 * @throws PuppeTorException
+	 *             Thrown if an I/O problem occurs while writing the temporary
+	 *             <code>approved-routers</code> file.
+	 */
+	DirectoryNodeImpl(final NetworkImpl network, final String nodeName,
+			final int controlPort, final int socksPort, final int orPort,
+			final int dirPort, final String serverIpAddress) {
+		// create superclass instance; parameter checking is done in super
+		// constructor
+		super(network, nodeName, controlPort, socksPort, orPort, dirPort,
+				serverIpAddress);
+
+		// log entering
+		logger.entering(this.getClass().getName(), "DirectoryNodeImpl",
+				new Object[] { network, nodeName, controlPort, socksPort,
+						orPort, dirPort });
+
+		// initialize attribute
+		approvedRouters = new TreeSet<String>();
+
+		// extend configuration by template configuration of directory nodes
+		configuration.addAll(templateConfiguration);
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "DirectoryNodeImpl");
+	}
+
+	/**
+	 * Invoked by the certificate generating thread: sets the generated v3
+	 * identity string.
+	 *
+	 * @param v3Identity
+	 *            The generated v3 identity string.
+	 */
+	private synchronized void setV3Identity(final String v3Identity) {
+		// log entering
+		logger.entering(this.getClass().getName(), "setV3Identity", v3Identity);
+
+		// remember fingerprint and notify all waiting threads
+		this.v3Identity = v3Identity;
+		notifyAll();
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "setV3Identity");
+	}
+
+	/**
+	 * The generated v3 identity string.
+	 */
+	private String v3Identity;
+
+	public synchronized String getV3Identity() throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "getV3Identity");
+
+		// wait until either the v3 identity has been determined or an exception
+		// was caught
+		while (v3Identity == null && caughtException == null) {
+
+			try {
+				wait(500);
+			} catch (final InterruptedException e) {
+				// do nothing
+			}
+		}
+
+		if (caughtException != null) {
+			logger.throwing(this.getClass().getName(), "getV3Identity",
+					caughtException);
+			throw caughtException;
+		}
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "getV3Identity", v3Identity);
+		return v3Identity;
+	}
+
+	public synchronized String getDirServerString() throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "getDirServerString");
+
+		// determine fingerprint
+		String fingerprint = getFingerprint();
+
+		// cut off router nickname
+		fingerprint = fingerprint.substring(fingerprint.indexOf(" ") + 1);
+
+		// determine v3 identity
+		final String determinedV3Identity = getV3Identity();
+
+		// put everything together
+		final String dirServerString =
+				"DirServer " + nodeName + " v3ident=" + determinedV3Identity
+						+ " orport=" + orPort + " " + serverIpAddress + ":"
+						+ dirPort + " " + fingerprint;
+
+		// log exiting and return dir server string
+		logger.exiting(this.getClass().getName(), "getDirServerString",
+				dirServerString);
+		return dirServerString;
+	}
+
+	public void addApprovedRouters(final Set<String> routers) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "addApprovedRouters",
+				routers);
+
+		// check parameter
+		if (routers == null) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "addApprovedRouters", e);
+			throw e;
+		}
+
+		// add the given approved router strings to the sorted set of already
+		// known strings (if any)
+		approvedRouters.addAll(routers);
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "addApprovedRouters");
+	}
+
+	@Override
+	protected synchronized void determineFingerprint() {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "determineFingerprint");
+
+		// start a thread to generate the directory's certificate
+		final GenerateCertificateThread certificateThread =
+				new GenerateCertificateThread();
+		certificateThread.setName(nodeName + " Certificate Generator");
+		certificateThread.start();
+
+		// wait (non-blocking) for the v3 identity string
+		try {
+			getV3Identity();
+		} catch (final PuppeTorException e1) {
+			final PuppeTorException ex =
+					new PuppeTorException("Could not read v3 identity string!",
+							e1);
+			caughtException = ex;
+			return;
+		}
+
+		// create an empty approved-routers file to make Tor happy
+		try {
+			new File(workingDir.getAbsolutePath() + File.separator
+					+ "approved-routers").createNewFile();
+		} catch (final IOException e) {
+			final PuppeTorException ex =
+					new PuppeTorException(
+							"Could not write empty approved-routers file!", e);
+			caughtException = ex;
+			return;
+		}
+
+		// invoke overwritten method
+		super.determineFingerprint();
+	}
+
+	@Override
+	public synchronized void writeConfiguration() throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "writeConfiguration");
+
+		// write approved-routers file
+		try {
+			final File approvedRoutersFile =
+					new File(workingDir.getAbsolutePath() + File.separator
+							+ "approved-routers");
+			final BufferedWriter bw =
+					new BufferedWriter(new FileWriter(approvedRoutersFile));
+			for (final String approvedRouter : approvedRouters) {
+				bw.write(approvedRouter + "\n");
+			}
+			bw.close();
+		} catch (final IOException e) {
+			final PuppeTorException ex = new PuppeTorException(e);
+			logger
+					.throwing(this.getClass().getName(), "writeConfiguration",
+							ex);
+			throw ex;
+		}
+
+		// invoke overridden method
+		super.writeConfiguration();
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "writeConfiguration");
+	}
+
+	/**
+	 * Template configuration of directory nodes.
+	 */
+	static List<String> templateConfiguration;
+
+	static {
+		templateConfiguration = new ArrayList<String>();
+
+		// configure this node as an authoritative directory
+		templateConfiguration.add("AuthoritativeDirectory 1");
+		templateConfiguration.add("V2AuthoritativeDirectory 1");
+		templateConfiguration.add("V3AuthoritativeDirectory 1");
+		templateConfiguration.add("DirAllowPrivateAddresses 1");
+		templateConfiguration.add("MinUptimeHidServDirectoryV2 0 minutes");
+
+		// TODO This is now contained in proposal 135.
+		// templateConfiguration.add("AuthDirMaxServersPerAddr 0");
+		// templateConfiguration.add("AuthDirMaxServersPerAuthAddr 0");
+		// templateConfiguration.add("V3AuthVotingInterval 5 minutes");
+		// templateConfiguration.add("V3AuthVoteDelay 20 seconds");
+		// templateConfiguration.add("V3AuthDistDelay 20 seconds");
+	}
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/impl/EventManagerImpl.java b/src/de/uniba/wiai/lspi/puppetor/impl/EventManagerImpl.java
old mode 100755
new mode 100644
index 79c2c2b..26cbe00
--- a/src/de/uniba/wiai/lspi/puppetor/impl/EventManagerImpl.java
+++ b/src/de/uniba/wiai/lspi/puppetor/impl/EventManagerImpl.java
@@ -1,755 +1,755 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor.impl;
-
-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 de.uniba.wiai.lspi.puppetor.Event;
-import de.uniba.wiai.lspi.puppetor.EventListener;
-import de.uniba.wiai.lspi.puppetor.EventManager;
-import de.uniba.wiai.lspi.puppetor.EventType;
-import de.uniba.wiai.lspi.puppetor.HiddenServiceEventType;
-import de.uniba.wiai.lspi.puppetor.NodeEventType;
-
-/**
- * Implementation of <code>EventManager</code>.
- *
- * @author kloesing
- */
-public class EventManagerImpl implements EventManager {
-
-	/**
-	 * Registered event handlers for specific sources.
-	 */
-	private final Map<String, Set<EventListener>> eventHandlers;
-
-	/**
-	 * Registered event handlers for all sources.
-	 */
-	private final Set<EventListener> eventHandlersForAllSources;
-
-	/**
-	 * Logger for this event manager which is called "event." plus the name of
-	 * the network.
-	 */
-	private final Logger logger;
-
-	/**
-	 * Events observed so far.
-	 */
-	private final Map<String, List<Event>> observedEvents;
-
-	/**
-	 * Set of all registered event sources. This is required to ensure that
-	 * requests for events from a given source specify valid event sources.
-	 */
-	private final Set<String> eventSources;
-
-	/**
-	 * Creates a new <code>EventManagerImpl</code> for the network with name
-	 * <code>networkName</code> and initializes it.
-	 *
-	 * @param networkName
-	 *            Name of this event manager that is used as part of the logger
-	 *            name.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given <code>networkName</code> is either
-	 *             <code>null</code> or a zero-length string.
-	 */
-	EventManagerImpl(final String networkName) {
-
-		// check if networkName can be used as logger name
-		if (networkName == null || networkName.length() == 0) {
-			throw new IllegalArgumentException("Invalid networkName: "
-					+ networkName);
-		}
-
-		// create logger
-		logger = Logger.getLogger("event." + networkName);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "EventManagerImpl",
-				networkName);
-
-		// create data structures
-		observedEvents = new HashMap<String, List<Event>>();
-		eventHandlers = new HashMap<String, Set<EventListener>>();
-		eventHandlersForAllSources = new HashSet<EventListener>();
-		eventSources = new HashSet<String>();
-
-		// start thread to parse events
-		final Thread eventParseThread = new Thread() {
-			@Override
-			public void run() {
-				while (true) {
-					parseNextEvent();
-				}
-			}
-		};
-		eventParseThread.setDaemon(true);
-		eventParseThread.start();
-
-		// initialize event type patterns
-		initializeEventTypePatterns();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "EventManagerImpl");
-	}
-
-	public synchronized List<Event> addEventListener(final String source,
-			final EventListener listener) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addEventListener",
-				new Object[] { source, listener });
-
-		// check parameters
-		if (source == null || listener == null
-				|| !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "addEventListener", e);
-			throw e;
-		}
-
-		// if necessary, create new event listener set for source
-		if (!eventHandlers.containsKey(source)) {
-			eventHandlers.put(source, new HashSet<EventListener>());
-		}
-
-		// add listener
-		eventHandlers.get(source).add(listener);
-
-		// log change
-		logger.log(Level.FINE, "Added event listener for source " + source);
-
-		// log exiting and return
-		final List<Event> result = getEventHistory(source);
-		logger.exiting(this.getClass().getName(), "addEventListener", result);
-		return result;
-	}
-
-	public synchronized void addEventListener(final EventListener listener) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addEventListener",
-				new Object[] { listener });
-
-		// check parameters
-		if (listener == null) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "addEventListener", e);
-			throw e;
-		}
-
-		// add listener
-		eventHandlersForAllSources.add(listener);
-
-		// log change
-		logger.log(Level.FINE, "Added event listener for all sources.");
-
-		// log exiting and return
-		logger.exiting(this.getClass().getName(), "addEventListener");
-		return;
-	}
-
-	public synchronized List<Event> getEventHistory(final String source) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "getEventHistory", source);
-
-		// check parameter
-		if (source == null || !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "getEventHistory", e);
-			throw e;
-		}
-
-		// prepare result
-		final List<Event> result = new ArrayList<Event>();
-
-		// did we already observe events for this source?
-		if (observedEvents.containsKey(source)) {
-			// yes, add all events to result list
-			result.addAll(observedEvents.get(source));
-		}
-
-		// log exiting and return result
-		logger.exiting(this.getClass().getName(), "getEventHistory", result);
-		return result;
-	}
-
-	public synchronized boolean hasEventOccured(final String source,
-			final EventType type) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "hasEventOccured",
-				new Object[] { source, type });
-
-		// check parameters
-		if (source == null || type == null || !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "hasEventOccured", e);
-			throw e;
-		}
-
-		// determine result
-		boolean result = false;
-		if (observedEvents.containsKey(source)) {
-			for (final Event event : observedEvents.get(source)) {
-				if (event.getType() == type) {
-					result = true;
-					break;
-				}
-			}
-		}
-
-		// log exiting and return result
-		logger.exiting(this.getClass().getName(), "hasEventOccured", result);
-		return result;
-	}
-
-	/**
-	 * An ordered list of all log statements that are still unparsed.
-	 */
-	private final List<EventImpl> unparsedLogStatements =
-			new LinkedList<EventImpl>();
-
-	/**
-	 * Stores the occurrence of an unparsed Tor log events that might result in
-	 * an event. All such unparsed events are later parsed by a background
-	 * thread in invocation order. Then, the occurrence time and the event type
-	 * will be parsed from the log message; if the log message does not contain
-	 * anything of interest, the event will be discarded.
-	 *
-	 * @param source
-	 *            The event source.
-	 * @param logMessage
-	 *            The log message.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the source is unknown.
-	 */
-	synchronized void observeUnparsedEvent(final String source,
-			final String logMessage) {
-		unparsedLogStatements.add(new EventImpl(source, logMessage));
-		notifyAll();
-	}
-
-	/**
-	 * Add an internal event to the event queue.
-	 *
-	 * @param occurrenceTime
-	 *            The occurrence time of the event.
-	 * @param source
-	 *            The event source.
-	 * @param type
-	 *            The event type.
-	 * @param message
-	 *            The event message.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the source is unknown.
-	 */
-	public synchronized void observeInternalEvent(final long occurrenceTime,
-			final String source, final EventType type, final String message) {
-
-		if (!eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "observeInternalEvent",
-					e);
-			throw e;
-		}
-
-		unparsedLogStatements.add(new EventImpl(occurrenceTime, source, type,
-				message));
-		notifyAll();
-	}
-
-	/**
-	 * Parses a log statement coming from Tor and decides whether it is
-	 * interesting for us.
-	 */
-	void parseNextEvent() {
-
-		// wait for the next event in the queue
-		EventImpl event = null;
-		synchronized (this) {
-			while (unparsedLogStatements.isEmpty()) {
-				try {
-					wait();
-				} catch (final InterruptedException e) {}
-			}
-			event = unparsedLogStatements.remove(0);
-		}
-
-		// does the event contain a known source? if not, discard it
-		if (!eventSources.contains(event.getSource())) {
-			logger.log(Level.WARNING,
-					"Unknown event source while parsing an event: "
-							+ event.getSource());
-			return;
-		}
-
-		// does the event require parsing? if not, process immediately
-		if (event.getType() != null) {
-			observeEvent(event);
-		} else {
-			final String line = event.getMessage();
-
-			/*
-			 * the logging output of Tor does not contain a year component; put
-			 * in the year at the time of parsing (which happens approx. 10 ms
-			 * after the logging took place) in the good hope that this tool is
-			 * not run at midnight on New Year's Eve...
-			 */
-			final Calendar c = Calendar.getInstance();
-			final int currentYear = c.get(Calendar.YEAR);
-
-			// try to apply one of the event type patterns
-			for (final Entry<Pattern, EventType> entry : eventTypePatterns
-					.entrySet()) {
-				final Matcher matcher = entry.getKey().matcher(line);
-				if (matcher.find()) {
-					final SimpleDateFormat sdf =
-							new SimpleDateFormat("MMM dd HH:mm:ss.SSS",
-									Locale.US);
-					final Date logTime = sdf.parse(line, new ParsePosition(0));
-					c.setTimeInMillis(logTime.getTime());
-					c.set(Calendar.YEAR, currentYear);
-					final long t = c.getTimeInMillis();
-					event.setOccurenceTime(t);
-					event.setType(entry.getValue());
-					observeEvent(event);
-					break;
-				}
-			}
-		}
-	}
-
-	/**
-	 * Map of all patterns, that should be included when parsing log statements
-	 * coming from Tor, and the respective event types of the events that should
-	 * be fired.
-	 */
-	Map<Pattern, EventType> eventTypePatterns;
-
-	public synchronized void registerEventTypePattern(
-			final String patternString, final EventType eventType) {
-		eventTypePatterns.put(Pattern.compile(patternString), eventType);
-	}
-
-	/**
-	 * Initializes the parsing engine with the standard log message patterns
-	 * that should be included in current Tor. Any further patterns need to be
-	 * added by the test application manually.
-	 */
-	private void initializeEventTypePatterns() {
-		eventTypePatterns = new HashMap<Pattern, EventType>();
-		registerEventTypePattern("Opening Control listener on .*",
-				NodeEventType.NODE_CONTROL_PORT_OPENED);
-		registerEventTypePattern("Tor has successfully opened a circuit. "
-				+ "Looks like client functionality is working.",
-				NodeEventType.NODE_CIRCUIT_OPENED);
-		registerEventTypePattern(
-				"Established circuit .* as introduction "
-						+ "point for service .*",
-				HiddenServiceEventType.BOB_BUILT_INTRO_CIRC_SENDING_ESTABLISH_INTRO);
-		registerEventTypePattern("Received INTRO_ESTABLISHED cell on "
-				+ "circuit .* for service .*",
-				HiddenServiceEventType.BOB_INTRO_ESTABLISHED_RECEIVED);
-		registerEventTypePattern("Sending publish request for hidden "
-				+ "service .*", HiddenServiceEventType.BOB_SENDING_PUBLISH_DESC);
-		registerEventTypePattern("Uploaded rendezvous descriptor",
-				HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED);
-		registerEventTypePattern("Received INTRODUCE2 cell for service .* "
-				+ "on circ .*", HiddenServiceEventType.BOB_INTRODUCE2_RECEIVED);
-		registerEventTypePattern("Done building circuit .* to rendezvous "
-				+ "with cookie .* for service .*",
-				HiddenServiceEventType.BOB_BUILT_REND_CIRC_SENDING_RENDEZVOUS1);
-		registerEventTypePattern("begin is for rendezvous",
-				HiddenServiceEventType.BOB_APP_CONN_OPENED);
-		registerEventTypePattern("Got a hidden service request for ID '.*'",
-				HiddenServiceEventType.ALICE_ONION_REQUEST_RECEIVED);
-		registerEventTypePattern("Fetching rendezvous descriptor for "
-				+ "service .*", HiddenServiceEventType.ALICE_SENDING_FETCH_DESC);
-		registerEventTypePattern("Received rendezvous descriptor",
-				HiddenServiceEventType.ALICE_DESC_FETCHED_RECEIVED);
-		registerEventTypePattern(
-				"Sending an ESTABLISH_RENDEZVOUS cell",
-				HiddenServiceEventType.ALICE_BUILT_REND_CIRC_SENDING_ESTABLISH_RENDEZVOUS);
-		registerEventTypePattern("Got rendezvous ack. This circuit is now "
-				+ "ready for rendezvous",
-				HiddenServiceEventType.ALICE_RENDEZVOUS_ESTABLISHED_RECEIVED);
-		registerEventTypePattern("introcirc is open",
-				HiddenServiceEventType.ALICE_BUILT_INTRO_CIRC);
-		registerEventTypePattern("Sending an INTRODUCE1 cell",
-				HiddenServiceEventType.ALICE_SENDING_INTRODUCE1);
-		registerEventTypePattern("Received ack. Telling rend circ",
-				HiddenServiceEventType.ALICE_INTRODUCE_ACK_RECEIVED);
-		registerEventTypePattern(
-				"Got RENDEZVOUS2 cell from hidden service",
-				HiddenServiceEventType.ALICE_RENDEZVOUS2_RECEIVED_APP_CONN_OPENED);
-		registerEventTypePattern("Handling rendezvous descriptor post",
-				HiddenServiceEventType.DIR_PUBLISH_DESC_RECEIVED);
-		registerEventTypePattern("Handling rendezvous descriptor get",
-				HiddenServiceEventType.DIR_FETCH_DESC_RECEIVED);
-		registerEventTypePattern(
-				"Received an ESTABLISH_INTRO request on circuit .*",
-				HiddenServiceEventType.IPO_RECEIVED_ESTABLISH_INTRO_SENDING_INTRO_ESTABLISHED);
-		registerEventTypePattern(
-				"Received an INTRODUCE1 request on circuit .*",
-				HiddenServiceEventType.IPO_RECEIVED_INTRODUCE1_SENDING_INTRODUCE2_AND_INTRODUCE_ACK);
-		registerEventTypePattern(
-				"Received an ESTABLISH_RENDEZVOUS request on circuit .*",
-				HiddenServiceEventType.RPO_RECEIVED_ESTABLISH_RENDEZVOUS_SENDING_RENDEZVOUS_ESTABLISHED);
-		registerEventTypePattern(
-				"Got request for rendezvous from circuit .* to cookie .*",
-				HiddenServiceEventType.RPO_RECEIVING_RENDEZVOUS1_SENDING_RENDEZVOUS2);
-	}
-
-	/**
-	 * Stores the given <code>event</code> from <code>source</code> to the
-	 * event history and propagates its occurrence to all registered event
-	 * handlers.
-	 *
-	 * @param event
-	 *            The observed event.
-	 */
-	private synchronized void observeEvent(final Event event) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "observeEvent", event);
-
-		final String source = event.getSource();
-		logger.log(Level.FINE, "Observed event " + event + " from source "
-				+ source + "!");
-
-		// remember observed event
-		if (!observedEvents.containsKey(event.getSource())) {
-			observedEvents.put(source, new ArrayList<Event>());
-		}
-		observedEvents.get(source).add(event);
-
-		// notify waiting threads
-		notifyAll();
-
-		// inform event listeners
-		if (eventHandlers.containsKey(source)) {
-
-			// make a copy of the event handler set, because some event handlers
-			// might want to remove themselves from this set while handling the
-			// event
-			final Set<EventListener> copyOfEventHandlers =
-					new HashSet<EventListener>(eventHandlers.get(source));
-
-			for (final EventListener eventHandler : copyOfEventHandlers) {
-
-				logger.log(Level.FINE, "Informing event listener "
-						+ eventHandler + " about recently observed event "
-						+ event + " from source " + source + "!");
-				eventHandler.handleEvent(event);
-
-			}
-		}
-
-		// make a copy of the event handler set for all sources, because some
-		// event handlers might want to remove themselves from this set while
-		// handling the event
-		final Set<EventListener> copyOfEventHandlersForAllSources =
-				new HashSet<EventListener>(eventHandlersForAllSources);
-
-		for (final EventListener eventHandler : copyOfEventHandlersForAllSources) {
-
-			logger.log(Level.FINE, "Informing event listener " + eventHandler
-					+ " about recently observed event " + event
-					+ " from source " + source + "!");
-			eventHandler.handleEvent(event);
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "observeEvent");
-	}
-
-	public synchronized void removeEventListener(
-			final EventListener eventListener) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "removeEventListener",
-				eventListener);
-
-		// check parameters
-		if (eventListener == null) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger
-					.throwing(this.getClass().getName(), "removeEventListener",
-							e);
-			throw e;
-		}
-
-		// don't know to which source this listener has been added (may to more
-		// than one), so remove it from all possible sets
-		for (final Set<EventListener> set : eventHandlers.values()) {
-			if (set.remove(eventListener)) {
-				logger.log(Level.FINE, "Removed event listener!");
-			}
-		}
-
-		// remove from event listeners for all sources
-		if (eventHandlersForAllSources.remove(eventListener)) {
-			logger.log(Level.FINE, "Removed event listener!");
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "removeEventListener");
-	}
-
-	public synchronized void waitForAnyOccurence(final String source,
-			final EventType event) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "waitForAnyOccurence",
-				new Object[] { source, event });
-
-		// check parameters
-		if (source == null || event == null || !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger
-					.throwing(this.getClass().getName(), "waitForAnyOccurence",
-							e);
-			throw e;
-		}
-
-		// invoke overloaded method with maximumTimeToWaitInMillis of -1L which
-		// means to wait forever
-		waitForAnyOccurence(source, event, -1L);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "waitForAnyOccurence");
-
-	}
-
-	public synchronized boolean waitForAnyOccurence(final String source,
-			final EventType event, final long maximumTimeToWaitInMillis) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "waitForAnyOccurence",
-				new Object[] { source, event, maximumTimeToWaitInMillis });
-
-		// check parameters
-		if (source == null || event == null || !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger
-					.throwing(this.getClass().getName(), "waitForAnyOccurence",
-							e);
-			throw e;
-		}
-
-		// check if we have already observed the event
-		if (hasEventOccured(source, event)) {
-			logger.log(Level.FINE, "Waiting for any occurence of event "
-					+ event + " returned immediately!");
-			logger.exiting(this.getClass().getName(), "waitForAnyOccurence",
-					true);
-			return true;
-		}
-
-		// invoke method that waits for next occurence of the event
-		final boolean result =
-				waitForNextOccurence(source, event, maximumTimeToWaitInMillis);
-
-		// log exiting and return result
-		logger
-				.exiting(this.getClass().getName(), "waitForAnyOccurence",
-						result);
-		return result;
-	}
-
-	public synchronized void waitForNextOccurence(final String source,
-			final EventType event) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "waitForNextOccurence",
-				new Object[] { source, event });
-
-		// check parameters
-		if (source == null || event == null || !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "waitForNextOccurence",
-					e);
-			throw e;
-		}
-
-		// invoke overloaded method with maximumTimeToWaitInMillis of -1L which
-		// means to wait forever
-		waitForNextOccurence(source, event, -1L);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "waitForNextOccurence");
-	}
-
-	public synchronized boolean waitForNextOccurence(final String source,
-			final EventType type, final long maximumTimeToWaitInMillis) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "waitForNextOccurence",
-				new Object[] { source, type, maximumTimeToWaitInMillis });
-
-		// check parameters
-		if (source == null || type == null || !eventSources.contains(source)) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "waitForNextOccurence",
-					e);
-			throw e;
-		}
-
-		// distinguish between negative waiting time (wait forever) and zero or
-		// positive waiting time
-		if (maximumTimeToWaitInMillis < 0) {
-
-			// wait forever
-			while (!hasEventOccured(source, type)) {
-
-				logger.log(Level.FINEST,
-						"We will wait infinetely for the next occurence of "
-								+ "event type " + type + " from source "
-								+ source + "...");
-				try {
-					wait();
-				} catch (final InterruptedException e) {
-					// don't handle
-				}
-
-				logger.log(Level.FINEST,
-						"We have been notified about an observed event while "
-								+ "waiting for events of type " + type
-								+ " from source " + source
-								+ "; need to check whether the observed event "
-								+ "is what we are looking for...");
-			}
-
-			logger.log(Level.FINE, "Waiting for occurence of event type "
-					+ type + " succeeded!");
-
-			// log exiting and return result
-			logger.exiting(this.getClass().getName(), "waitForNextOccurence",
-					true);
-			return true;
-
-		}
-
-		// wait for the given time at most
-		final long endOfTime =
-				System.currentTimeMillis() + maximumTimeToWaitInMillis;
-		long timeLeft = 0;
-		while (!hasEventOccured(source, type)
-				&& (timeLeft = endOfTime - System.currentTimeMillis()) > 0) {
-
-			logger.log(Level.FINEST, "We will wait for " + timeLeft
-					+ " milliseconds for the next occurence of event type "
-					+ type + " from source " + source + "...");
-
-			try {
-				wait(timeLeft);
-			} catch (final InterruptedException e) {
-				// don't handle
-			}
-
-			logger.log(Level.FINEST,
-					"We have been notified about an observed event while "
-							+ "waiting for events of type " + type
-							+ " from source " + source
-							+ "; need to check whether the observed event "
-							+ "is what we are looking for...");
-		}
-
-		// determine result
-		final boolean result = hasEventOccured(source, type);
-
-		logger.log(Level.FINE, "Waiting for next occurence of event type "
-				+ type + " from source " + source
-				+ (result ? " succeeded!" : " did not succeed!"));
-
-		// log exiting and return result
-		logger.exiting(this.getClass().getName(), "waitForNextOccurence",
-				result);
-		return result;
-	}
-
-	/**
-	 * Adds the given <code>source</code> as possible event source.
-	 *
-	 * @param source
-	 *            The name of the node, client, or server to add.
-	 * @throws IllegalArgumentException
-	 *             Thrown if there is already an event source with this name.
-	 */
-	void addEventSource(final String source) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addEventSource", source);
-
-		// check if source name is unique in this network
-		if (eventSources.contains(source)) {
-			final IllegalArgumentException e =
-					new IllegalArgumentException(
-							"There is already an event source with name "
-									+ source + " in this network!");
-			logger.throwing(this.getClass().getName(), "addEventSource", e);
-			throw e;
-		}
-
-		// add event source name
-		eventSources.add(source);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "addEventSource");
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName();
-	}
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor.impl;
+
+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 de.uniba.wiai.lspi.puppetor.Event;
+import de.uniba.wiai.lspi.puppetor.EventListener;
+import de.uniba.wiai.lspi.puppetor.EventManager;
+import de.uniba.wiai.lspi.puppetor.EventType;
+import de.uniba.wiai.lspi.puppetor.HiddenServiceEventType;
+import de.uniba.wiai.lspi.puppetor.NodeEventType;
+
+/**
+ * Implementation of <code>EventManager</code>.
+ *
+ * @author kloesing
+ */
+public class EventManagerImpl implements EventManager {
+
+	/**
+	 * Registered event handlers for specific sources.
+	 */
+	private final Map<String, Set<EventListener>> eventHandlers;
+
+	/**
+	 * Registered event handlers for all sources.
+	 */
+	private final Set<EventListener> eventHandlersForAllSources;
+
+	/**
+	 * Logger for this event manager which is called "event." plus the name of
+	 * the network.
+	 */
+	private final Logger logger;
+
+	/**
+	 * Events observed so far.
+	 */
+	private final Map<String, List<Event>> observedEvents;
+
+	/**
+	 * Set of all registered event sources. This is required to ensure that
+	 * requests for events from a given source specify valid event sources.
+	 */
+	private final Set<String> eventSources;
+
+	/**
+	 * Creates a new <code>EventManagerImpl</code> for the network with name
+	 * <code>networkName</code> and initializes it.
+	 *
+	 * @param networkName
+	 *            Name of this event manager that is used as part of the logger
+	 *            name.
+	 * @throws IllegalArgumentException
+	 *             Thrown if the given <code>networkName</code> is either
+	 *             <code>null</code> or a zero-length string.
+	 */
+	EventManagerImpl(final String networkName) {
+
+		// check if networkName can be used as logger name
+		if (networkName == null || networkName.length() == 0) {
+			throw new IllegalArgumentException("Invalid networkName: "
+					+ networkName);
+		}
+
+		// create logger
+		logger = Logger.getLogger("event." + networkName);
+
+		// log entering
+		logger.entering(this.getClass().getName(), "EventManagerImpl",
+				networkName);
+
+		// create data structures
+		observedEvents = new HashMap<String, List<Event>>();
+		eventHandlers = new HashMap<String, Set<EventListener>>();
+		eventHandlersForAllSources = new HashSet<EventListener>();
+		eventSources = new HashSet<String>();
+
+		// start thread to parse events
+		final Thread eventParseThread = new Thread() {
+			@Override
+			public void run() {
+				while (true) {
+					parseNextEvent();
+				}
+			}
+		};
+		eventParseThread.setDaemon(true);
+		eventParseThread.start();
+
+		// initialize event type patterns
+		initializeEventTypePatterns();
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "EventManagerImpl");
+	}
+
+	public synchronized List<Event> addEventListener(final String source,
+			final EventListener listener) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "addEventListener",
+				new Object[] { source, listener });
+
+		// check parameters
+		if (source == null || listener == null
+				|| !eventSources.contains(source)) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "addEventListener", e);
+			throw e;
+		}
+
+		// if necessary, create new event listener set for source
+		if (!eventHandlers.containsKey(source)) {
+			eventHandlers.put(source, new HashSet<EventListener>());
+		}
+
+		// add listener
+		eventHandlers.get(source).add(listener);
+
+		// log change
+		logger.log(Level.FINE, "Added event listener for source " + source);
+
+		// log exiting and return
+		final List<Event> result = getEventHistory(source);
+		logger.exiting(this.getClass().getName(), "addEventListener", result);
+		return result;
+	}
+
+	public synchronized void addEventListener(final EventListener listener) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "addEventListener",
+				new Object[] { listener });
+
+		// check parameters
+		if (listener == null) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "addEventListener", e);
+			throw e;
+		}
+
+		// add listener
+		eventHandlersForAllSources.add(listener);
+
+		// log change
+		logger.log(Level.FINE, "Added event listener for all sources.");
+
+		// log exiting and return
+		logger.exiting(this.getClass().getName(), "addEventListener");
+		return;
+	}
+
+	public synchronized List<Event> getEventHistory(final String source) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "getEventHistory", source);
+
+		// check parameter
+		if (source == null || !eventSources.contains(source)) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "getEventHistory", e);
+			throw e;
+		}
+
+		// prepare result
+		final List<Event> result = new ArrayList<Event>();
+
+		// did we already observe events for this source?
+		if (observedEvents.containsKey(source)) {
+			// yes, add all events to result list
+			result.addAll(observedEvents.get(source));
+		}
+
+		// log exiting and return result
+		logger.exiting(this.getClass().getName(), "getEventHistory", result);
+		return result;
+	}
+
+	public synchronized boolean hasEventOccured(final String source,
+			final EventType type) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "hasEventOccured",
+				new Object[] { source, type });
+
+		// check parameters
+		if (source == null || type == null || !eventSources.contains(source)) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "hasEventOccured", e);
+			throw e;
+		}
+
+		// determine result
+		boolean result = false;
+		if (observedEvents.containsKey(source)) {
+			for (final Event event : observedEvents.get(source)) {
+				if (event.getType() == type) {
+					result = true;
+					break;
+				}
+			}
+		}
+
+		// log exiting and return result
+		logger.exiting(this.getClass().getName(), "hasEventOccured", result);
+		return result;
+	}
+
+	/**
+	 * An ordered list of all log statements that are still unparsed.
+	 */
+	private final List<EventImpl> unparsedLogStatements =
+			new LinkedList<EventImpl>();
+
+	/**
+	 * Stores the occurrence of an unparsed Tor log events that might result in
+	 * an event. All such unparsed events are later parsed by a background
+	 * thread in invocation order. Then, the occurrence time and the event type
+	 * will be parsed from the log message; if the log message does not contain
+	 * anything of interest, the event will be discarded.
+	 *
+	 * @param source
+	 *            The event source.
+	 * @param logMessage
+	 *            The log message.
+	 * @throws IllegalArgumentException
+	 *             Thrown if the source is unknown.
+	 */
+	synchronized void observeUnparsedEvent(final String source,
+			final String logMessage) {
+		unparsedLogStatements.add(new EventImpl(source, logMessage));
+		notifyAll();
+	}
+
+	/**
+	 * Add an internal event to the event queue.
+	 *
+	 * @param occurrenceTime
+	 *            The occurrence time of the event.
+	 * @param source
+	 *            The event source.
+	 * @param type
+	 *            The event type.
+	 * @param message
+	 *            The event message.
+	 * @throws IllegalArgumentException
+	 *             Thrown if the source is unknown.
+	 */
+	public synchronized void observeInternalEvent(final long occurrenceTime,
+			final String source, final EventType type, final String message) {
+
+		if (!eventSources.contains(source)) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "observeInternalEvent",
+					e);
+			throw e;
+		}
+
+		unparsedLogStatements.add(new EventImpl(occurrenceTime, source, type,
+				message));
+		notifyAll();
+	}
+
+	/**
+	 * Parses a log statement coming from Tor and decides whether it is
+	 * interesting for us.
+	 */
+	void parseNextEvent() {
+
+		// wait for the next event in the queue
+		EventImpl event = null;
+		synchronized (this) {
+			while (unparsedLogStatements.isEmpty()) {
+				try {
+					wait();
+				} catch (final InterruptedException e) {}
+			}
+			event = unparsedLogStatements.remove(0);
+		}
+
+		// does the event contain a known source? if not, discard it
+		if (!eventSources.contains(event.getSource())) {
+			logger.log(Level.WARNING,
+					"Unknown event source while parsing an event: "
+							+ event.getSource());
+			return;
+		}
+
+		// does the event require parsing? if not, process immediately
+		if (event.getType() != null) {
+			observeEvent(event);
+		} else {
+			final String line = event.getMessage();
+
+			/*
+			 * the logging output of Tor does not contain a year component; put
+			 * in the year at the time of parsing (which happens approx. 10 ms
+			 * after the logging took place) in the good hope that this tool is
+			 * not run at midnight on New Year's Eve...
+			 */
+			final Calendar c = Calendar.getInstance();
+			final int currentYear = c.get(Calendar.YEAR);
+
+			// try to apply one of the event type patterns
+			for (final Entry<Pattern, EventType> entry : eventTypePatterns
+					.entrySet()) {
+				final Matcher matcher = entry.getKey().matcher(line);
+				if (matcher.find()) {
+					final SimpleDateFormat sdf =
+							new SimpleDateFormat("MMM dd HH:mm:ss.SSS",
+									Locale.US);
+					final Date logTime = sdf.parse(line, new ParsePosition(0));
+					c.setTimeInMillis(logTime.getTime());
+					c.set(Calendar.YEAR, currentYear);
+					final long t = c.getTimeInMillis();
+					event.setOccurenceTime(t);
+					event.setType(entry.getValue());
+					observeEvent(event);
+					break;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Map of all patterns, that should be included when parsing log statements
+	 * coming from Tor, and the respective event types of the events that should
+	 * be fired.
+	 */
+	Map<Pattern, EventType> eventTypePatterns;
+
+	public synchronized void registerEventTypePattern(
+			final String patternString, final EventType eventType) {
+		eventTypePatterns.put(Pattern.compile(patternString), eventType);
+	}
+
+	/**
+	 * Initializes the parsing engine with the standard log message patterns
+	 * that should be included in current Tor. Any further patterns need to be
+	 * added by the test application manually.
+	 */
+	private void initializeEventTypePatterns() {
+		eventTypePatterns = new HashMap<Pattern, EventType>();
+		registerEventTypePattern("Opening Control listener on .*",
+				NodeEventType.NODE_CONTROL_PORT_OPENED);
+		registerEventTypePattern("Tor has successfully opened a circuit. "
+				+ "Looks like client functionality is working.",
+				NodeEventType.NODE_CIRCUIT_OPENED);
+		registerEventTypePattern(
+				"Established circuit .* as introduction "
+						+ "point for service .*",
+				HiddenServiceEventType.BOB_BUILT_INTRO_CIRC_SENDING_ESTABLISH_INTRO);
+		registerEventTypePattern("Received INTRO_ESTABLISHED cell on "
+				+ "circuit .* for service .*",
+				HiddenServiceEventType.BOB_INTRO_ESTABLISHED_RECEIVED);
+		registerEventTypePattern("Sending publish request for hidden "
+				+ "service .*", HiddenServiceEventType.BOB_SENDING_PUBLISH_DESC);
+		registerEventTypePattern("Uploaded rendezvous descriptor",
+				HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED);
+		registerEventTypePattern("Received INTRODUCE2 cell for service .* "
+				+ "on circ .*", HiddenServiceEventType.BOB_INTRODUCE2_RECEIVED);
+		registerEventTypePattern("Done building circuit .* to rendezvous "
+				+ "with cookie .* for service .*",
+				HiddenServiceEventType.BOB_BUILT_REND_CIRC_SENDING_RENDEZVOUS1);
+		registerEventTypePattern("begin is for rendezvous",
+				HiddenServiceEventType.BOB_APP_CONN_OPENED);
+		registerEventTypePattern("Got a hidden service request for ID '.*'",
+				HiddenServiceEventType.ALICE_ONION_REQUEST_RECEIVED);
+		registerEventTypePattern("Fetching rendezvous descriptor for "
+				+ "service .*", HiddenServiceEventType.ALICE_SENDING_FETCH_DESC);
+		registerEventTypePattern("Received rendezvous descriptor",
+				HiddenServiceEventType.ALICE_DESC_FETCHED_RECEIVED);
+		registerEventTypePattern(
+				"Sending an ESTABLISH_RENDEZVOUS cell",
+				HiddenServiceEventType.ALICE_BUILT_REND_CIRC_SENDING_ESTABLISH_RENDEZVOUS);
+		registerEventTypePattern("Got rendezvous ack. This circuit is now "
+				+ "ready for rendezvous",
+				HiddenServiceEventType.ALICE_RENDEZVOUS_ESTABLISHED_RECEIVED);
+		registerEventTypePattern("introcirc is open",
+				HiddenServiceEventType.ALICE_BUILT_INTRO_CIRC);
+		registerEventTypePattern("Sending an INTRODUCE1 cell",
+				HiddenServiceEventType.ALICE_SENDING_INTRODUCE1);
+		registerEventTypePattern("Received ack. Telling rend circ",
+				HiddenServiceEventType.ALICE_INTRODUCE_ACK_RECEIVED);
+		registerEventTypePattern(
+				"Got RENDEZVOUS2 cell from hidden service",
+				HiddenServiceEventType.ALICE_RENDEZVOUS2_RECEIVED_APP_CONN_OPENED);
+		registerEventTypePattern("Handling rendezvous descriptor post",
+				HiddenServiceEventType.DIR_PUBLISH_DESC_RECEIVED);
+		registerEventTypePattern("Handling rendezvous descriptor get",
+				HiddenServiceEventType.DIR_FETCH_DESC_RECEIVED);
+		registerEventTypePattern(
+				"Received an ESTABLISH_INTRO request on circuit .*",
+				HiddenServiceEventType.IPO_RECEIVED_ESTABLISH_INTRO_SENDING_INTRO_ESTABLISHED);
+		registerEventTypePattern(
+				"Received an INTRODUCE1 request on circuit .*",
+				HiddenServiceEventType.IPO_RECEIVED_INTRODUCE1_SENDING_INTRODUCE2_AND_INTRODUCE_ACK);
+		registerEventTypePattern(
+				"Received an ESTABLISH_RENDEZVOUS request on circuit .*",
+				HiddenServiceEventType.RPO_RECEIVED_ESTABLISH_RENDEZVOUS_SENDING_RENDEZVOUS_ESTABLISHED);
+		registerEventTypePattern(
+				"Got request for rendezvous from circuit .* to cookie .*",
+				HiddenServiceEventType.RPO_RECEIVING_RENDEZVOUS1_SENDING_RENDEZVOUS2);
+	}
+
+	/**
+	 * Stores the given <code>event</code> from <code>source</code> to the
+	 * event history and propagates its occurrence to all registered event
+	 * handlers.
+	 *
+	 * @param event
+	 *            The observed event.
+	 */
+	private synchronized void observeEvent(final Event event) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "observeEvent", event);
+
+		final String source = event.getSource();
+		logger.log(Level.FINE, "Observed event " + event + " from source "
+				+ source + "!");
+
+		// remember observed event
+		if (!observedEvents.containsKey(event.getSource())) {
+			observedEvents.put(source, new ArrayList<Event>());
+		}
+		observedEvents.get(source).add(event);
+
+		// notify waiting threads
+		notifyAll();
+
+		// inform event listeners
+		if (eventHandlers.containsKey(source)) {
+
+			// make a copy of the event handler set, because some event handlers
+			// might want to remove themselves from this set while handling the
+			// event
+			final Set<EventListener> copyOfEventHandlers =
+					new HashSet<EventListener>(eventHandlers.get(source));
+
+			for (final EventListener eventHandler : copyOfEventHandlers) {
+
+				logger.log(Level.FINE, "Informing event listener "
+						+ eventHandler + " about recently observed event "
+						+ event + " from source " + source + "!");
+				eventHandler.handleEvent(event);
+
+			}
+		}
+
+		// make a copy of the event handler set for all sources, because some
+		// event handlers might want to remove themselves from this set while
+		// handling the event
+		final Set<EventListener> copyOfEventHandlersForAllSources =
+				new HashSet<EventListener>(eventHandlersForAllSources);
+
+		for (final EventListener eventHandler : copyOfEventHandlersForAllSources) {
+
+			logger.log(Level.FINE, "Informing event listener " + eventHandler
+					+ " about recently observed event " + event
+					+ " from source " + source + "!");
+			eventHandler.handleEvent(event);
+		}
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "observeEvent");
+	}
+
+	public synchronized void removeEventListener(
+			final EventListener eventListener) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "removeEventListener",
+				eventListener);
+
+		// check parameters
+		if (eventListener == null) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger
+					.throwing(this.getClass().getName(), "removeEventListener",
+							e);
+			throw e;
+		}
+
+		// don't know to which source this listener has been added (may to more
+		// than one), so remove it from all possible sets
+		for (final Set<EventListener> set : eventHandlers.values()) {
+			if (set.remove(eventListener)) {
+				logger.log(Level.FINE, "Removed event listener!");
+			}
+		}
+
+		// remove from event listeners for all sources
+		if (eventHandlersForAllSources.remove(eventListener)) {
+			logger.log(Level.FINE, "Removed event listener!");
+		}
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "removeEventListener");
+	}
+
+	public synchronized void waitForAnyOccurence(final String source,
+			final EventType event) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "waitForAnyOccurence",
+				new Object[] { source, event });
+
+		// check parameters
+		if (source == null || event == null || !eventSources.contains(source)) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger
+					.throwing(this.getClass().getName(), "waitForAnyOccurence",
+							e);
+			throw e;
+		}
+
+		// invoke overloaded method with maximumTimeToWaitInMillis of -1L which
+		// means to wait forever
+		waitForAnyOccurence(source, event, -1L);
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "waitForAnyOccurence");
+
+	}
+
+	public synchronized boolean waitForAnyOccurence(final String source,
+			final EventType event, final long maximumTimeToWaitInMillis) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "waitForAnyOccurence",
+				new Object[] { source, event, maximumTimeToWaitInMillis });
+
+		// check parameters
+		if (source == null || event == null || !eventSources.contains(source)) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger
+					.throwing(this.getClass().getName(), "waitForAnyOccurence",
+							e);
+			throw e;
+		}
+
+		// check if we have already observed the event
+		if (hasEventOccured(source, event)) {
+			logger.log(Level.FINE, "Waiting for any occurence of event "
+					+ event + " returned immediately!");
+			logger.exiting(this.getClass().getName(), "waitForAnyOccurence",
+					true);
+			return true;
+		}
+
+		// invoke method that waits for next occurence of the event
+		final boolean result =
+				waitForNextOccurence(source, event, maximumTimeToWaitInMillis);
+
+		// log exiting and return result
+		logger
+				.exiting(this.getClass().getName(), "waitForAnyOccurence",
+						result);
+		return result;
+	}
+
+	public synchronized void waitForNextOccurence(final String source,
+			final EventType event) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "waitForNextOccurence",
+				new Object[] { source, event });
+
+		// check parameters
+		if (source == null || event == null || !eventSources.contains(source)) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "waitForNextOccurence",
+					e);
+			throw e;
+		}
+
+		// invoke overloaded method with maximumTimeToWaitInMillis of -1L which
+		// means to wait forever
+		waitForNextOccurence(source, event, -1L);
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "waitForNextOccurence");
+	}
+
+	public synchronized boolean waitForNextOccurence(final String source,
+			final EventType type, final long maximumTimeToWaitInMillis) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "waitForNextOccurence",
+				new Object[] { source, type, maximumTimeToWaitInMillis });
+
+		// check parameters
+		if (source == null || type == null || !eventSources.contains(source)) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "waitForNextOccurence",
+					e);
+			throw e;
+		}
+
+		// distinguish between negative waiting time (wait forever) and zero or
+		// positive waiting time
+		if (maximumTimeToWaitInMillis < 0) {
+
+			// wait forever
+			while (!hasEventOccured(source, type)) {
+
+				logger.log(Level.FINEST,
+						"We will wait infinetely for the next occurence of "
+								+ "event type " + type + " from source "
+								+ source + "...");
+				try {
+					wait();
+				} catch (final InterruptedException e) {
+					// don't handle
+				}
+
+				logger.log(Level.FINEST,
+						"We have been notified about an observed event while "
+								+ "waiting for events of type " + type
+								+ " from source " + source
+								+ "; need to check whether the observed event "
+								+ "is what we are looking for...");
+			}
+
+			logger.log(Level.FINE, "Waiting for occurence of event type "
+					+ type + " succeeded!");
+
+			// log exiting and return result
+			logger.exiting(this.getClass().getName(), "waitForNextOccurence",
+					true);
+			return true;
+
+		}
+
+		// wait for the given time at most
+		final long endOfTime =
+				System.currentTimeMillis() + maximumTimeToWaitInMillis;
+		long timeLeft = 0;
+		while (!hasEventOccured(source, type)
+				&& (timeLeft = endOfTime - System.currentTimeMillis()) > 0) {
+
+			logger.log(Level.FINEST, "We will wait for " + timeLeft
+					+ " milliseconds for the next occurence of event type "
+					+ type + " from source " + source + "...");
+
+			try {
+				wait(timeLeft);
+			} catch (final InterruptedException e) {
+				// don't handle
+			}
+
+			logger.log(Level.FINEST,
+					"We have been notified about an observed event while "
+							+ "waiting for events of type " + type
+							+ " from source " + source
+							+ "; need to check whether the observed event "
+							+ "is what we are looking for...");
+		}
+
+		// determine result
+		final boolean result = hasEventOccured(source, type);
+
+		logger.log(Level.FINE, "Waiting for next occurence of event type "
+				+ type + " from source " + source
+				+ (result ? " succeeded!" : " did not succeed!"));
+
+		// log exiting and return result
+		logger.exiting(this.getClass().getName(), "waitForNextOccurence",
+				result);
+		return result;
+	}
+
+	/**
+	 * Adds the given <code>source</code> as possible event source.
+	 *
+	 * @param source
+	 *            The name of the node, client, or server to add.
+	 * @throws IllegalArgumentException
+	 *             Thrown if there is already an event source with this name.
+	 */
+	void addEventSource(final String source) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "addEventSource", source);
+
+		// check if source name is unique in this network
+		if (eventSources.contains(source)) {
+			final IllegalArgumentException e =
+					new IllegalArgumentException(
+							"There is already an event source with name "
+									+ source + " in this network!");
+			logger.throwing(this.getClass().getName(), "addEventSource", e);
+			throw e;
+		}
+
+		// add event source name
+		eventSources.add(source);
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "addEventSource");
+	}
+
+	@Override
+	public String toString() {
+		return this.getClass().getSimpleName();
+	}
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/impl/NetworkImpl.java b/src/de/uniba/wiai/lspi/puppetor/impl/NetworkImpl.java
old mode 100755
new mode 100644
index a042f62..3503bd0
--- a/src/de/uniba/wiai/lspi/puppetor/impl/NetworkImpl.java
+++ b/src/de/uniba/wiai/lspi/puppetor/impl/NetworkImpl.java
@@ -1,1108 +1,1108 @@
-/*
- * 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 de.uniba.wiai.lspi.puppetor.impl;
-
-import java.io.File;
-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.concurrent.ConcurrentHashMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import de.uniba.wiai.lspi.puppetor.ClientApplication;
-import de.uniba.wiai.lspi.puppetor.DirectoryNode;
-import de.uniba.wiai.lspi.puppetor.Event;
-import de.uniba.wiai.lspi.puppetor.EventListener;
-import de.uniba.wiai.lspi.puppetor.EventManager;
-import de.uniba.wiai.lspi.puppetor.Network;
-import de.uniba.wiai.lspi.puppetor.NodeEventType;
-import de.uniba.wiai.lspi.puppetor.NodeState;
-import de.uniba.wiai.lspi.puppetor.ProxyNode;
-import de.uniba.wiai.lspi.puppetor.PuppeTorException;
-import de.uniba.wiai.lspi.puppetor.RouterNode;
-import de.uniba.wiai.lspi.puppetor.ServerApplication;
-
-/**
- * Implementation of <code>Network</code>.
- *
- * @author kloesing
- */
-public class NetworkImpl implements Network {
-
-	/**
-	 * Internal thread class that is used to start Tor processes in parallel.
-	 */
-	private class NodeStarter extends Thread {
-
-		/**
-		 * The exception, if one is caught while trying to start the node.
-		 */
-		Exception caughtException;
-
-		/**
-		 * The maximum time to wait for the Tor process to start in
-		 * milliseconds.
-		 */
-		private final long maximumTimeToWaitInMillis;
-
-		/**
-		 * The node for which the Tor process shall be started.
-		 */
-		private final ProxyNode node;
-
-		/**
-		 * Flag that denotes whether starting the Tor process was successful.
-		 */
-		boolean success = false;
-
-		/**
-		 * Creates a new <code>NodeStarter</code> for node <code>node</code>
-		 * that will wait for <code>maximumTimeToWaitInMillis</code>
-		 * milliseconds to start a Tor process, but that is not started, yet.
-		 *
-		 * @param node
-		 *            The node for which the Tor process shall be started.
-		 * @param maximumTimeToWaitInMillis
-		 *            The maximum time to wait for the Tor process to start in
-		 *            milliseconds.
-		 */
-		NodeStarter(final ProxyNode node, final long maximumTimeToWaitInMillis) {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "NodeStarter",
-					new Object[] { node, maximumTimeToWaitInMillis });
-
-			// store parameters
-			this.node = node;
-			this.maximumTimeToWaitInMillis = maximumTimeToWaitInMillis;
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "NodeStarter");
-		}
-
-		@Override
-		public void run() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "run");
-
-			try {
-				// try to start node
-				success = node.startNode(maximumTimeToWaitInMillis);
-			} catch (final PuppeTorException e) {
-				logger.log(Level.SEVERE,
-						"Caught an exception while starting node "
-								+ node.toString() + "!");
-				caughtException = e;
-			}
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "run");
-		}
-	}
-
-	/**
-	 * Event manager to which all events concerning this network are notified.
-	 */
-	private final EventManagerImpl eventManager;
-
-	/**
-	 * Logger for this network which is called "network." plus the name of this
-	 * network.
-	 */
-	private final Logger logger;
-
-	/**
-	 * Contains the name of this network configuration which is the String
-	 * conversion of System.currentTimeMillis() of the network creation time.
-	 */
-	private final String networkName;
-
-	/**
-	 * All nodes contained in this network.
-	 */
-	private final Map<String, ProxyNode> nodes;
-
-	/**
-	 * Directory that contains status information of all nodes contained in this
-	 * network.
-	 */
-	private final File workingDir;
-
-	/**
-	 * The counter for automatically assigned port numbers created by this
-	 * <code>Network</code>.
-	 */
-	private int portCounter = 7000;
-
-	/**
-	 * Creates an initially unpopulated Tor network and creates a new working
-	 * directory for it at test-env/randomTestID/.
-	 *
-	 * @param networkName
-	 *            Name of this network configuration. May neither be
-	 *            <code>null</code> or a zero-length string.
-	 * @param startPort
-	 *            The initial value for automatically assigned port numbers of
-	 *            nodes created by this <code>Network</code>; must be a value
-	 *            between 1024 and 65535.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given <code>networkName</code> is either
-	 *             <code>null</code> or a zero-length string, or if an illegal
-	 *             number is given for <code>startPort</code>.
-	 */
-	public NetworkImpl(final String networkName, final int startPort) {
-		// initialize using overloaded constructor
-		this(networkName);
-
-		// check if start port is valid
-		if (startPort < 1024 || startPort > 65535) {
-			throw new IllegalArgumentException("Invalid startPort: "
-					+ startPort);
-		}
-
-		// remember parameter
-		portCounter = startPort;
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "NetworkImpl");
-	}
-
-	/**
-	 * Creates an initially unpopulated Tor network and creates a new working
-	 * directory for it at test-env/randomTestID/.
-	 *
-	 * @param networkName
-	 *            Name of this network configuration. May neither be
-	 *            <code>null</code> or a zero-length string.
-	 * @throws IllegalArgumentException
-	 *             Thrown if the given <code>networkName</code> is either
-	 *             <code>null</code> or a zero-length string.
-	 */
-	public NetworkImpl(final String networkName) {
-
-		// check if networkName can be used as logger name
-		if (networkName == null || networkName.length() == 0) {
-			throw new IllegalArgumentException("Invalid networkName: "
-					+ networkName);
-		}
-
-		// create logger
-		logger = Logger.getLogger("network." + networkName);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "NetworkImpl", networkName);
-
-		// TODO is this necessary?!
-		logger.setLevel(Level.ALL);
-
-		// remember parameter
-		this.networkName = networkName;
-
-		// create working directory
-		workingDir = new File("test-env/" + System.currentTimeMillis());
-		workingDir.mkdirs();
-		logger.log(Level.FINE, "Created working directory \""
-				+ workingDir.getAbsolutePath() + "\"");
-
-		// TODO if we want to log to file, set this... somehow...
-		// this.logFile = new File(this.workingDir.getAbsolutePath()
-		// + "\\events.log");
-
-		// initialize data structures
-		nodes = new ConcurrentHashMap<String, ProxyNode>();
-
-		// create event manager
-		eventManager = new EventManagerImpl(this.networkName);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "NetworkImpl");
-	}
-
-	/**
-	 * Returns whether all nodes in this network are up, or not.
-	 *
-	 * @return <code>true</code> if all nodes are up, <code>false</code> if
-	 *         at least one node is not up.
-	 */
-	private boolean allNodesUp() {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "allNodesUp");
-
-		// fail on first node that is not up
-		for (final ProxyNode node : nodes.values()) {
-			if (!eventManager.hasEventOccured(node.getNodeName(),
-					NodeEventType.NODE_CIRCUIT_OPENED)) {
-
-				// log exiting and return false
-				logger.exiting(this.getClass().getName(), "allNodesUp");
-				return false;
-			}
-		}
-
-		// log exiting and return true
-		logger.exiting(this.getClass().getName(), "allNodesUp");
-		return true;
-	}
-
-	public void configureAsPrivateNetwork() throws PuppeTorException {
-		// log entering
-		logger.entering(this.getClass().getName(), "configureAsPrivateNetwork");
-
-		// read DirServer strings for all directories
-		final List<String> authorizedDirectoriesFingerprints =
-				new ArrayList<String>();
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof DirectoryNode) {
-				final DirectoryNode dirNode = (DirectoryNode) node;
-				authorizedDirectoriesFingerprints.add(dirNode
-						.getDirServerString());
-			}
-		}
-
-		this.configureAsPartOfPrivateNetwork(authorizedDirectoriesFingerprints);
-
-		// read fingerprints for all directories and routers
-		final HashSet<String> approvedRoutersFingerprints =
-				new HashSet<String>();
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof RouterNode) {
-				final RouterNode routerOrDirNode = (RouterNode) node;
-				approvedRoutersFingerprints.add(routerOrDirNode
-						.getFingerprint());
-			}
-		}
-
-		// write fingerprints for all directories and routers to the
-		// approved-routers file
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof DirectoryNode) {
-				final DirectoryNode dirNode = (DirectoryNode) node;
-				dirNode.addApprovedRouters(approvedRoutersFingerprints);
-			}
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "configureAsPrivateNetwork");
-	}
-
-	public void configureAsPartOfPrivateNetwork(
-			List<String> authorizedDirectoriesFingerprints) {
-			//throws PuppeTorException {
-		// log entering
-		logger.entering(this.getClass().getName(), "configureAsPartOfPrivateNetwork");
-
-		for (final ProxyNode node : nodes.values()) {
-			if (node.getNodeState() == NodeState.CONFIGURING ||
-					node.getNodeState() == NodeState.CONFIGURATION_WRITTEN) {
-				// add to configuration
-				node.addConfiguration("TestingTorNetwork 1");
-			}
-		}
-
-		// configure nodes
-		for (final ProxyNode node : nodes.values()) {
-			if (node.getNodeState() == NodeState.CONFIGURING ||
-					node.getNodeState() == NodeState.CONFIGURATION_WRITTEN) {
-				// add to configuration
-				node.addConfigurations(authorizedDirectoriesFingerprints);
-			}
-		}
-
-/*		// read fingerprints for all directories and routers
-		final HashSet<String> approvedRoutersFingerprints =
-				new HashSet<String>();
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof RouterNode) {
-				final RouterNode routerOrDirNode = (RouterNode) node;
-				approvedRoutersFingerprints.add(routerOrDirNode
-						.getFingerprint());
-			}
-		}
-
-		// write fingerprints for all directories and routers to the
-		// approved-routers file
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof DirectoryNode) {
-				final DirectoryNode dirNode = (DirectoryNode) node;
-				dirNode.addApprovedRouters(approvedRoutersFingerprints);
-			}
-		}
-*/
-		// log exiting
-		logger.exiting(this.getClass().getName(), "configureAsPartOfPrivateNetwork");
-	}
-
-	public ClientApplication createClient(final String clientApplicationName,
-			final String targetAddress, final int targetPort,
-			final int socksPort) {
-		// log entering
-		logger.entering(this.getClass().getName(), "createClient",
-				new Object[] { clientApplicationName, targetAddress,
-						targetPort, socksPort });
-
-		// create client; parameter checking is done in constructor
-		final ClientApplicationImpl client =
-				new ClientApplicationImpl(this, clientApplicationName,
-						targetAddress, targetPort, socksPort);
-
-		// add name to event manager as event source
-		eventManager.addEventSource(clientApplicationName);
-
-		// log exiting and return client
-		logger.exiting(this.getClass().getName(), "createClient", client);
-		return client;
-	}
-
-	public DirectoryNode createDirectory(final String nodeName,
-			final int controlPort, final int socksPort, final int orPort,
-			final int dirPort, final String serverIpAddress) {
-		// log entering
-		logger.entering(this.getClass().getName(), "createDirectory",
-				new Object[] { nodeName, controlPort, socksPort, orPort,
-						dirPort, serverIpAddress });
-
-		// create directory node; parameter checking is done in constructor
-		final DirectoryNode dir =
-				new DirectoryNodeImpl(this, nodeName, controlPort, socksPort,
-						orPort, dirPort, serverIpAddress);
-
-		// add new directory node to nodes collection
-		nodes.put(nodeName, dir);
-
-		// add name to event manager as event source
-		eventManager.addEventSource(nodeName);
-
-		// log exiting and return directory node
-		logger.exiting(this.getClass().getName(), "createDirectory", dir);
-		return dir;
-	}
-
-	public DirectoryNode createDirectory(final String nodeName,
-			final int controlPort, final int socksPort, final int orPort,
-			final int dirPort) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createDirectory",
-				new Object[] { nodeName, controlPort, socksPort, orPort,
-						dirPort });
-
-		// invoke overloaded method
-		final DirectoryNode dir =
-				this.createDirectory(nodeName, controlPort, socksPort, orPort,
-						dirPort, "127.0.0.1");
-
-		// log exiting and return directory node
-		logger.exiting(this.getClass().getName(), "createDirectory", dir);
-		return dir;
-	}
-
-	public DirectoryNode createDirectory(final String nodeName,
-			final String serverIpAddress) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createDirectory",
-				new Object[] { nodeName, serverIpAddress });
-
-		// invoke overloaded method
-		final DirectoryNode dir =
-				this.createDirectory(nodeName, portCounter++, portCounter++,
-						portCounter++, portCounter++, serverIpAddress);
-
-		// log exiting and return directory node
-		logger.exiting(this.getClass().getName(), "createDirectory", dir);
-		return dir;
-	}
-
-	public DirectoryNode createDirectory(final String nodeName) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createDirectory", nodeName);
-
-		// invoke overloaded method
-		final DirectoryNode dir =
-				this.createDirectory(nodeName, portCounter++, portCounter++,
-						portCounter++, portCounter++, "127.0.0.1");
-
-		// log exiting and return directory node
-		logger.exiting(this.getClass().getName(), "createDirectory", dir);
-		return dir;
-	}
-
-	public ProxyNode createProxy(final String nodeName, final int controlPort,
-			final int socksPort) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createProxy", new Object[] {
-				nodeName, controlPort, socksPort });
-
-		// create proxy node; parameter checking is done in constructor
-		final ProxyNode proxy =
-				new ProxyNodeImpl(this, nodeName, controlPort, socksPort);
-
-		// add new proxy node to nodes collection
-		nodes.put(nodeName, proxy);
-
-		// add name to event manager as event source
-		eventManager.addEventSource(nodeName);
-
-		// log exiting and return proxy node
-		logger.exiting(this.getClass().getName(), "createProxy", proxy);
-		return proxy;
-	}
-
-	public ProxyNode createProxy(final String nodeName) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createProxy", nodeName);
-
-		// invoke overloaded method
-		final ProxyNode proxy =
-				this.createProxy(nodeName, portCounter++, portCounter++);
-
-		// log exiting and return proxy node
-		logger.exiting(this.getClass().getName(), "createProxy", proxy);
-		return proxy;
-	}
-
-	public RouterNode createRouter(final String nodeName,
-			final int controlPort, final int socksPort, final int orPort,
-			final int dirPort, final String serverIpAddress) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createRouter",
-				new Object[] { nodeName, controlPort, socksPort, orPort,
-						dirPort, serverIpAddress });
-
-		// create router node; parameter checking is done in constructor
-		final RouterNode router =
-				new RouterNodeImpl(this, nodeName, controlPort, socksPort,
-						orPort, dirPort, serverIpAddress);
-
-		// add new router node to nodes collection
-		nodes.put(nodeName, router);
-
-		// add name to event manager as event source
-		eventManager.addEventSource(nodeName);
-
-		// log exiting and return router node
-		logger.exiting(this.getClass().getName(), "createRouter", router);
-		return router;
-	}
-
-	public RouterNode createRouter(final String nodeName,
-			final int controlPort, final int socksPort, final int orPort,
-			final int dirPort) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createRouter",
-				new Object[] { nodeName, controlPort, socksPort, orPort,
-						dirPort });
-
-		// invoke overloaded method
-		final DirectoryNode dir =
-				this.createDirectory(nodeName, controlPort, socksPort, orPort,
-						dirPort, "127.0.0.1");
-
-		// log exiting and return directory node
-		logger.exiting(this.getClass().getName(), "createRouter", dir);
-		return dir;
-	}
-
-	public RouterNode createRouter(final String nodeName,
-			final String serverIpAddress) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createRouter",
-				new Object[] { nodeName, serverIpAddress });
-
-		// invoke overloaded method
-		final RouterNode dir =
-				this.createRouter(nodeName, portCounter++, portCounter++,
-						portCounter++, portCounter++, serverIpAddress);
-
-		// log exiting and return directory node
-		logger.exiting(this.getClass().getName(), "createRouter", dir);
-		return dir;
-	}
-
-	public RouterNode createRouter(final String nodeName) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createRouter", nodeName);
-
-		// invoke overloaded method
-		final RouterNode router =
-				this.createRouter(nodeName, portCounter++, portCounter++,
-						portCounter++, portCounter++, "127.0.0.1");
-
-		// log exiting and return router node
-		logger.exiting(this.getClass().getName(), "createRouter", router);
-		return router;
-	}
-
-	public ServerApplication createServer(final String serverApplicationName,
-			final int serverPort) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createServer",
-				new Object[] { serverApplicationName, serverPort });
-
-		// create server; parameter checking is done in constructor
-		final ServerApplicationImpl server =
-				new ServerApplicationImpl(this, serverApplicationName,
-						serverPort);
-
-		// add name to event manager as event source
-		eventManager.addEventSource(serverApplicationName);
-
-		// log exiting and return server
-		logger.exiting(this.getClass().getName(), "createServer", server);
-		return server;
-	}
-
-	public ServerApplication createServer(final String serverApplicationName) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "createServer",
-				serverApplicationName);
-
-		// invoke overloaded method
-		final ServerApplication server =
-				this.createServer(serverApplicationName, portCounter++);
-
-		// log exiting and return server
-		logger.exiting(this.getClass().getName(), "createServer", server);
-		return server;
-	}
-
-	public EventManager getEventManager() {
-		return eventManager;
-	}
-
-	/**
-	 * Returns the implementation instance of the event manager of this network.
-	 *
-	 * @return The implementation instance of the event manager of this network.
-	 */
-	public EventManagerImpl getEventManagerImpl() {
-		return eventManager;
-	}
-
-	public File getWorkingDirectory() {
-		return workingDir;
-	}
-
-	public ProxyNode getNode(final String nodeName) {
-		return nodes.get(nodeName);
-	}
-
-	public Map<String, ProxyNode> getAllProxyNodes() {
-		final Map<String, ProxyNode> result = new HashMap<String, ProxyNode>();
-		for (final String nodeName : nodes.keySet()) {
-			final ProxyNode node = nodes.get(nodeName);
-			if (!(node instanceof RouterNode)) {
-				result.put(nodeName, node);
-			}
-		}
-		return result;
-	}
-
-	public Map<String, RouterNode> getAllRouterNodes() {
-		final Map<String, RouterNode> result =
-				new HashMap<String, RouterNode>();
-		for (final String nodeName : nodes.keySet()) {
-			final ProxyNode node = nodes.get(nodeName);
-			if (node instanceof RouterNode && !(node instanceof DirectoryNode)) {
-				result.put(nodeName, (RouterNode) node);
-			}
-		}
-		return result;
-	}
-
-	public Map<String, DirectoryNode> getAllDirectoryNodes() {
-		final Map<String, DirectoryNode> result =
-				new HashMap<String, DirectoryNode>();
-		for (final String nodeName : nodes.keySet()) {
-			final ProxyNode node = nodes.get(nodeName);
-			if (node instanceof DirectoryNode) {
-				result.put(nodeName, (DirectoryNode) node);
-			}
-		}
-		return result;
-	}
-
-	public Map<String, ProxyNode> getAllNodes() {
-		return new HashMap<String, ProxyNode>(nodes);
-	}
-
-	public boolean hupUntilUp(final int tries, final long hupInterval)
-			throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "hupUntilUp", new Object[] {
-				tries, hupInterval });
-
-		// check if all nodes are running
-		for (final ProxyNode node : nodes.values()) {
-			if (node.getNodeState() != NodeState.RUNNING) {
-				final IllegalStateException e =
-						new IllegalStateException(
-								"All nodes must be running before sending them HUP "
-										+ "commands!");
-				logger.throwing(this.getClass().getName(), "hupUntilUp", e);
-				throw e;
-			}
-		}
-
-		// check if nodes are already up; if so, return immediately
-		if (allNodesUp()) {
-
-			// log exiting and return true
-			logger.exiting(this.getClass().getName(), "hupUntilUp", true);
-			return true;
-		}
-
-		final Object hupLock = new Object();
-		// create and register a new event handler for each node
-		for (final ProxyNode node : nodes.values()) {
-			eventManager.addEventListener(node.getNodeName(),
-					new EventListener() {
-						public void handleEvent(final Event event) {
-							if (event.getType() == NodeEventType.NODE_CIRCUIT_OPENED) {
-								synchronized(hupLock) {
-									hupLock.notify();	
-								}
-								eventManager.removeEventListener(this);
-							}
-						}
-					});
-		}
-
-		// walk through wait-check-hup loop until there are no tries left
-		for (int i = 0; i < tries; i++) {
-
-			// determine how long to try waiting for the hangup
-			final long endOfSleeping = System.currentTimeMillis() + hupInterval;
-
-			// unless all nodes have reported to be up, wait for the given
-			// maximum time
-			while (System.currentTimeMillis() < endOfSleeping) {
-
-				synchronized(hupLock) {
-					// check if nodes are up now
-					if (allNodesUp()) {
-
-						// log exiting and return true
-						logger.exiting(this.getClass().getName(), "hupUntilUp",
-								true);
-						return true;
-					}
-					// sleep
-					try {
-						hupLock.wait(hupInterval);
-					} catch (final InterruptedException e) {
-						// do nothing about it
-					}
-				}
-			}
-
-			logger.log(Level.FINE, "Sending HUP to nodes");
-			// send a HUP signal to all nodes
-			for (final ProxyNode node : nodes.values()) {
-				logger
-						.log(Level.FINE, "Sending HUP to node "
-								+ node.toString());
-				node.hup();
-			}
-
-			// continue in loop
-		}
-
-		// no retries left and not all nodes are up; log exiting and return
-		// failure
-		logger.exiting(this.getClass().getName(), "hupUntilUp", false);
-		return false;
-	}
-
-	public void hupAllNodes() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "hupAllNodes");
-
-		// check if all nodes are running
-		for (final ProxyNode node : nodes.values()) {
-			if (node.getNodeState() != NodeState.RUNNING) {
-				final IllegalStateException e =
-						new IllegalStateException(
-								"All nodes must be running before sending them HUP "
-										+ "commands!");
-				logger.throwing(this.getClass().getName(), "hupAllNodes", e);
-				throw e;
-			}
-		}
-
-		// send a HUP signal to all nodes
-		for (final ProxyNode node : nodes.values()) {
-			logger.log(Level.FINE, "Sending HUP to node " + node.toString());
-			node.hup();
-		}
-
-		// no retries left and not all nodes are up; log exiting and return
-		// failure
-		logger.exiting(this.getClass().getName(), "hupAllNodes");
-	}
-
-	public void hupAllDirectories() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "hupAllDirectories");
-
-		// check if all directory nodes are running
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof DirectoryNode
-					&& node.getNodeState() != NodeState.RUNNING) {
-				final IllegalStateException e =
-						new IllegalStateException(
-								"All directory nodes must be running before sending "
-										+ "them HUP commands!");
-				logger.throwing(this.getClass().getName(), "hupAllDirectories",
-						e);
-				throw e;
-			}
-		}
-
-		// send a HUP signal to all nodes
-		for (final ProxyNode node : nodes.values()) {
-			if (node instanceof DirectoryNode) {
-				logger
-						.log(Level.FINE, "Sending HUP to node "
-								+ node.toString());
-				node.hup();
-			}
-		}
-
-		// no retries left and not all nodes are up; log exiting and return
-		// failure
-		logger.exiting(this.getClass().getName(), "hupAllDirectories");
-	}
-
-	public void shutdownNodes() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "shutdownNodes");
-
-		// iteratively shut down all running nodes; if an exception is caught,
-		// continue shutting down the other nodes and throw the first exception
-		// subsequently
-		PuppeTorException firstCaughtException = null;
-		for (final ProxyNode node : nodes.values()) {
-			if (node.getNodeState() == NodeState.RUNNING) {
-				try {
-					node.shutdown();
-				} catch (final PuppeTorException e) {
-					if (firstCaughtException == null) {
-						firstCaughtException = e;
-					}
-				}
-			}
-		}
-
-		// if an exception was caught during shutting down nodes, throw the
-		// first caught exception
-		if (firstCaughtException != null) {
-			logger.throwing(this.getClass().getName(), "shutdownNodes",
-					firstCaughtException);
-			throw firstCaughtException;
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "shutdownNodes");
-	}
-
-	public boolean startNodes(final long maximumTimeToWaitInMillis)
-			throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "startNodes",
-				maximumTimeToWaitInMillis);
-
-		// check node states
-		for (final ProxyNode node : nodes.values()) {
-			if (node.getNodeState() != NodeState.CONFIGURATION_WRITTEN) {
-				final IllegalStateException e =
-						new IllegalStateException(
-								"All configurations must be written before starting "
-										+ "nodes!");
-				logger.throwing(this.getClass().getName(), "startNodes", e);
-				throw e;
-			}
-		}
-
-		// check parameter
-		if (maximumTimeToWaitInMillis < 0) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "startNodes", e);
-			throw e;
-		}
-
-		// remember time when we begin starting the nodes
-		final long before = System.currentTimeMillis();
-
-		// start nodes in parallel
-		final Set<NodeStarter> allNodeStarters = new HashSet<NodeStarter>();
-		for (final ProxyNode node : nodes.values()) {
-			final NodeStarter nodeStarter =
-					new NodeStarter(node, maximumTimeToWaitInMillis);
-			allNodeStarters.add(nodeStarter);
-			nodeStarter.start();
-		}
-
-		// wait for all node starts to complete
-		for (final NodeStarter nodeStarter : allNodeStarters) {
-
-			// join node starts one after the other
-			try {
-				nodeStarter.join();
-			} catch (final InterruptedException e) {
-				// this happens?! we have some kind of problem here!
-				logger.log(Level.WARNING,
-						"Interrupt while joining node starter!");
-
-				// log exiting and return false
-				logger.exiting(this.getClass().getName(), "startNodes", false);
-				return false;
-			}
-
-			// if any thread has caught an exception, throw that exception now
-			final Exception caughtException = nodeStarter.caughtException;
-			if (caughtException != null) {
-				final PuppeTorException ex =
-						new PuppeTorException("Exception while starting node "
-								+ nodeStarter.node.getNodeName(),
-								caughtException);
-				logger.throwing(this.getClass().getName(), "startNodes", ex);
-				throw ex;
-			}
-
-			// if node start did not succeed in the given time, fail
-			if (!nodeStarter.success) {
-				logger.log(Level.WARNING,
-						"Starting nodes was not successful in "
-								+ maximumTimeToWaitInMillis / 1000
-								+ " seconds.", networkName);
-
-				// log exiting and return false
-				logger.exiting(this.getClass().getName(), "startNodes", false);
-				return false;
-			}
-		}
-
-		// determine how long we took to start all nodes
-		final long after = System.currentTimeMillis();
-		logger.log(Level.FINE, "Starting nodes was successful and took "
-				+ (after - before) / 1000 + " seconds.", networkName);
-
-		// log exiting and return true
-		logger.exiting(this.getClass().getName(), "startNodes", true);
-		return true;
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": networkName=\""
-				+ networkName;
-	}
-
-	public String getNetworkName() {
-		return networkName;
-	}
-
-	public void writeConfigurations() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "writeConfigurations");
-
-		// write configurations for all nodes
-		for (final ProxyNode node : nodes.values()) {
-			node.writeConfiguration();
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "writeConfigurations");
-	}
-
-	public void addTemplateConfiguration(
-			final Class<? extends ProxyNode> nodeClass,
-			final String templateConfigurationString) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addTemplateConfiguration",
-				new Object[] { nodeClass, templateConfigurationString });
-
-		// check parameters
-		if (nodeClass == null || templateConfigurationString == null
-				|| templateConfigurationString.length() < 1
-				|| !templateConfigurationString.contains(" ")) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(),
-					"addTemplateConfiguration", e);
-			throw e;
-		}
-
-		// add template string to appropriate template configuration
-		if (nodeClass == ProxyNode.class) {
-			ProxyNodeImpl.templateConfiguration
-					.add(templateConfigurationString);
-		} else if (nodeClass == RouterNode.class) {
-			RouterNodeImpl.templateConfiguration
-					.add(templateConfigurationString);
-		} else if (nodeClass == DirectoryNode.class) {
-			DirectoryNodeImpl.templateConfiguration
-					.add(templateConfigurationString);
-		} else {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(),
-					"addTemplateConfiguration", e);
-			throw e;
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "addTemplateConfiguration");
-	}
-
-	public List<String> getTemplateConfiguration(
-			final Class<? extends ProxyNode> nodeClass) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "getTemplateConfiguration",
-				nodeClass);
-
-		// check parameter
-		if (nodeClass == null) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(),
-					"getTemplateConfiguration", e);
-			throw e;
-		}
-
-		// obtain reference on appropriate template configuration
-		List<String> result = null;
-		if (nodeClass == ProxyNode.class) {
-			result = new ArrayList<String>(ProxyNodeImpl.templateConfiguration);
-		} else if (nodeClass == RouterNode.class) {
-			result =
-					new ArrayList<String>(RouterNodeImpl.templateConfiguration);
-		} else if (nodeClass == DirectoryNode.class) {
-			result =
-					new ArrayList<String>(
-							DirectoryNodeImpl.templateConfiguration);
-		} else {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(),
-					"getTemplateConfiguration", e);
-			throw e;
-		}
-
-		// log exiting and return result
-		logger.exiting(this.getClass().getName(), "getTemplateConfiguration",
-				result);
-		return result;
-	}
-
-	public void removeTemplateConfiguration(
-			final Class<? extends ProxyNode> nodeClass,
-			final String templateConfigurationKey) {
-
-		// log entering
-		logger.entering(this.getClass().getName(),
-				"removeTemplateConfiguration", new Object[] { nodeClass,
-						templateConfigurationKey });
-
-		// check parameters
-		if (nodeClass == null || templateConfigurationKey == null
-				|| templateConfigurationKey.length() < 1) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(),
-					"removeTemplateConfiguration", e);
-			throw e;
-		}
-
-		// obtain reference on appropriate template configuration
-		List<String> templateConfig = null;
-		if (nodeClass == ProxyNode.class) {
-			templateConfig = ProxyNodeImpl.templateConfiguration;
-		} else if (nodeClass == RouterNode.class) {
-			templateConfig = RouterNodeImpl.templateConfiguration;
-		} else if (nodeClass == DirectoryNode.class) {
-			templateConfig = DirectoryNodeImpl.templateConfiguration;
-		} else {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(),
-					"removeTemplateConfiguration", e);
-			throw e;
-		}
-
-		// iterate over existing template configuration strings and remove all
-		// configuration strings that have the given configuration key
-		final List<String> configurationStringsToRemove =
-				new ArrayList<String>();
-		for (final String currentConfigurationString : templateConfig) {
-			final String currentConfigurationKey =
-					currentConfigurationString.substring(0,
-							currentConfigurationString.indexOf(" "));
-			if (currentConfigurationKey.equals(templateConfigurationKey)) {
-				configurationStringsToRemove.add(currentConfigurationString);
-			}
-		}
-		templateConfig.removeAll(configurationStringsToRemove);
-
-		// log exiting
-		logger
-				.exiting(this.getClass().getName(),
-						"removeTemplateConfiguration");
-	}
-
-	/**
-	 * Returns the current port number and increments it afterwards.
-	 *
-	 * @return The current port number.
-	 */
-	int getNextPortNumber() {
-		return portCounter++;
-	}
-}
+/*
+ * 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 de.uniba.wiai.lspi.puppetor.impl;
+
+import java.io.File;
+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.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import de.uniba.wiai.lspi.puppetor.ClientApplication;
+import de.uniba.wiai.lspi.puppetor.DirectoryNode;
+import de.uniba.wiai.lspi.puppetor.Event;
+import de.uniba.wiai.lspi.puppetor.EventListener;
+import de.uniba.wiai.lspi.puppetor.EventManager;
+import de.uniba.wiai.lspi.puppetor.Network;
+import de.uniba.wiai.lspi.puppetor.NodeEventType;
+import de.uniba.wiai.lspi.puppetor.NodeState;
+import de.uniba.wiai.lspi.puppetor.ProxyNode;
+import de.uniba.wiai.lspi.puppetor.PuppeTorException;
+import de.uniba.wiai.lspi.puppetor.RouterNode;
+import de.uniba.wiai.lspi.puppetor.ServerApplication;
+
+/**
+ * Implementation of <code>Network</code>.
+ *
+ * @author kloesing
+ */
+public class NetworkImpl implements Network {
+
+	/**
+	 * Internal thread class that is used to start Tor processes in parallel.
+	 */
+	private class NodeStarter extends Thread {
+
+		/**
+		 * The exception, if one is caught while trying to start the node.
+		 */
+		Exception caughtException;
+
+		/**
+		 * The maximum time to wait for the Tor process to start in
+		 * milliseconds.
+		 */
+		private final long maximumTimeToWaitInMillis;
+
+		/**
+		 * The node for which the Tor process shall be started.
+		 */
+		private final ProxyNode node;
+
+		/**
+		 * Flag that denotes whether starting the Tor process was successful.
+		 */
+		boolean success = false;
+
+		/**
+		 * Creates a new <code>NodeStarter</code> for node <code>node</code>
+		 * that will wait for <code>maximumTimeToWaitInMillis</code>
+		 * milliseconds to start a Tor process, but that is not started, yet.
+		 *
+		 * @param node
+		 *            The node for which the Tor process shall be started.
+		 * @param maximumTimeToWaitInMillis
+		 *            The maximum time to wait for the Tor process to start in
+		 *            milliseconds.
+		 */
+		NodeStarter(final ProxyNode node, final long maximumTimeToWaitInMillis) {
+
+			// log entering
+			logger.entering(this.getClass().getName(), "NodeStarter",
+					new Object[] { node, maximumTimeToWaitInMillis });
+
+			// store parameters
+			this.node = node;
+			this.maximumTimeToWaitInMillis = maximumTimeToWaitInMillis;
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "NodeStarter");
+		}
+
+		@Override
+		public void run() {
+
+			// log entering
+			logger.entering(this.getClass().getName(), "run");
+
+			try {
+				// try to start node
+				success = node.startNode(maximumTimeToWaitInMillis);
+			} catch (final PuppeTorException e) {
+				logger.log(Level.SEVERE,
+						"Caught an exception while starting node "
+								+ node.toString() + "!");
+				caughtException = e;
+			}
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "run");
+		}
+	}
+
+	/**
+	 * Event manager to which all events concerning this network are notified.
+	 */
+	private final EventManagerImpl eventManager;
+
+	/**
+	 * Logger for this network which is called "network." plus the name of this
+	 * network.
+	 */
+	private final Logger logger;
+
+	/**
+	 * Contains the name of this network configuration which is the String
+	 * conversion of System.currentTimeMillis() of the network creation time.
+	 */
+	private final String networkName;
+
+	/**
+	 * All nodes contained in this network.
+	 */
+	private final Map<String, ProxyNode> nodes;
+
+	/**
+	 * Directory that contains status information of all nodes contained in this
+	 * network.
+	 */
+	private final File workingDir;
+
+	/**
+	 * The counter for automatically assigned port numbers created by this
+	 * <code>Network</code>.
+	 */
+	private int portCounter = 7000;
+
+	/**
+	 * Creates an initially unpopulated Tor network and creates a new working
+	 * directory for it at test-env/randomTestID/.
+	 *
+	 * @param networkName
+	 *            Name of this network configuration. May neither be
+	 *            <code>null</code> or a zero-length string.
+	 * @param startPort
+	 *            The initial value for automatically assigned port numbers of
+	 *            nodes created by this <code>Network</code>; must be a value
+	 *            between 1024 and 65535.
+	 * @throws IllegalArgumentException
+	 *             Thrown if the given <code>networkName</code> is either
+	 *             <code>null</code> or a zero-length string, or if an illegal
+	 *             number is given for <code>startPort</code>.
+	 */
+	public NetworkImpl(final String networkName, final int startPort) {
+		// initialize using overloaded constructor
+		this(networkName);
+
+		// check if start port is valid
+		if (startPort < 1024 || startPort > 65535) {
+			throw new IllegalArgumentException("Invalid startPort: "
+					+ startPort);
+		}
+
+		// remember parameter
+		portCounter = startPort;
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "NetworkImpl");
+	}
+
+	/**
+	 * Creates an initially unpopulated Tor network and creates a new working
+	 * directory for it at test-env/randomTestID/.
+	 *
+	 * @param networkName
+	 *            Name of this network configuration. May neither be
+	 *            <code>null</code> or a zero-length string.
+	 * @throws IllegalArgumentException
+	 *             Thrown if the given <code>networkName</code> is either
+	 *             <code>null</code> or a zero-length string.
+	 */
+	public NetworkImpl(final String networkName) {
+
+		// check if networkName can be used as logger name
+		if (networkName == null || networkName.length() == 0) {
+			throw new IllegalArgumentException("Invalid networkName: "
+					+ networkName);
+		}
+
+		// create logger
+		logger = Logger.getLogger("network." + networkName);
+
+		// log entering
+		logger.entering(this.getClass().getName(), "NetworkImpl", networkName);
+
+		// TODO is this necessary?!
+		logger.setLevel(Level.ALL);
+
+		// remember parameter
+		this.networkName = networkName;
+
+		// create working directory
+		workingDir = new File("test-env/" + System.currentTimeMillis());
+		workingDir.mkdirs();
+		logger.log(Level.FINE, "Created working directory \""
+				+ workingDir.getAbsolutePath() + "\"");
+
+		// TODO if we want to log to file, set this... somehow...
+		// this.logFile = new File(this.workingDir.getAbsolutePath()
+		// + "\\events.log");
+
+		// initialize data structures
+		nodes = new ConcurrentHashMap<String, ProxyNode>();
+
+		// create event manager
+		eventManager = new EventManagerImpl(this.networkName);
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "NetworkImpl");
+	}
+
+	/**
+	 * Returns whether all nodes in this network are up, or not.
+	 *
+	 * @return <code>true</code> if all nodes are up, <code>false</code> if
+	 *         at least one node is not up.
+	 */
+	private boolean allNodesUp() {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "allNodesUp");
+
+		// fail on first node that is not up
+		for (final ProxyNode node : nodes.values()) {
+			if (!eventManager.hasEventOccured(node.getNodeName(),
+					NodeEventType.NODE_CIRCUIT_OPENED)) {
+
+				// log exiting and return false
+				logger.exiting(this.getClass().getName(), "allNodesUp");
+				return false;
+			}
+		}
+
+		// log exiting and return true
+		logger.exiting(this.getClass().getName(), "allNodesUp");
+		return true;
+	}
+
+	public void configureAsPrivateNetwork() throws PuppeTorException {
+		// log entering
+		logger.entering(this.getClass().getName(), "configureAsPrivateNetwork");
+
+		// read DirServer strings for all directories
+		final List<String> authorizedDirectoriesFingerprints =
+				new ArrayList<String>();
+		for (final ProxyNode node : nodes.values()) {
+			if (node instanceof DirectoryNode) {
+				final DirectoryNode dirNode = (DirectoryNode) node;
+				authorizedDirectoriesFingerprints.add(dirNode
+						.getDirServerString());
+			}
+		}
+
+		this.configureAsPartOfPrivateNetwork(authorizedDirectoriesFingerprints);
+
+		// read fingerprints for all directories and routers
+		final HashSet<String> approvedRoutersFingerprints =
+				new HashSet<String>();
+		for (final ProxyNode node : nodes.values()) {
+			if (node instanceof RouterNode) {
+				final RouterNode routerOrDirNode = (RouterNode) node;
+				approvedRoutersFingerprints.add(routerOrDirNode
+						.getFingerprint());
+			}
+		}
+
+		// write fingerprints for all directories and routers to the
+		// approved-routers file
+		for (final ProxyNode node : nodes.values()) {
+			if (node instanceof DirectoryNode) {
+				final DirectoryNode dirNode = (DirectoryNode) node;
+				dirNode.addApprovedRouters(approvedRoutersFingerprints);
+			}
+		}
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "configureAsPrivateNetwork");
+	}
+
+	public void configureAsPartOfPrivateNetwork(
+			List<String> authorizedDirectoriesFingerprints) {
+			//throws PuppeTorException {
+		// log entering
+		logger.entering(this.getClass().getName(), "configureAsPartOfPrivateNetwork");
+
+		for (final ProxyNode node : nodes.values()) {
+			if (node.getNodeState() == NodeState.CONFIGURING ||
+					node.getNodeState() == NodeState.CONFIGURATION_WRITTEN) {
+				// add to configuration
+				node.addConfiguration("TestingTorNetwork 1");
+			}
+		}
+
+		// configure nodes
+		for (final ProxyNode node : nodes.values()) {
+			if (node.getNodeState() == NodeState.CONFIGURING ||
+					node.getNodeState() == NodeState.CONFIGURATION_WRITTEN) {
+				// add to configuration
+				node.addConfigurations(authorizedDirectoriesFingerprints);
+			}
+		}
+
+/*		// read fingerprints for all directories and routers
+		final HashSet<String> approvedRoutersFingerprints =
+				new HashSet<String>();
+		for (final ProxyNode node : nodes.values()) {
+			if (node instanceof RouterNode) {
+				final RouterNode routerOrDirNode = (RouterNode) node;
+				approvedRoutersFingerprints.add(routerOrDirNode
+						.getFingerprint());
+			}
+		}
+
+		// write fingerprints for all directories and routers to the
+		// approved-routers file
+		for (final ProxyNode node : nodes.values()) {
+			if (node instanceof DirectoryNode) {
+				final DirectoryNode dirNode = (DirectoryNode) node;
+				dirNode.addApprovedRouters(approvedRoutersFingerprints);
+			}
+		}
+*/
+		// log exiting
+		logger.exiting(this.getClass().getName(), "configureAsPartOfPrivateNetwork");
+	}
+
+	public ClientApplication createClient(final String clientApplicationName,
+			final String targetAddress, final int targetPort,
+			final int socksPort) {
+		// log entering
+		logger.entering(this.getClass().getName(), "createClient",
+				new Object[] { clientApplicationName, targetAddress,
+						targetPort, socksPort });
+
+		// create client; parameter checking is done in constructor
+		final ClientApplicationImpl client =
+				new ClientApplicationImpl(this, clientApplicationName,
+						targetAddress, targetPort, socksPort);
+
+		// add name to event manager as event source
+		eventManager.addEventSource(clientApplicationName);
+
+		// log exiting and return client
+		logger.exiting(this.getClass().getName(), "createClient", client);
+		return client;
+	}
+
+	public DirectoryNode createDirectory(final String nodeName,
+			final int controlPort, final int socksPort, final int orPort,
+			final int dirPort, final String serverIpAddress) {
+		// log entering
+		logger.entering(this.getClass().getName(), "createDirectory",
+				new Object[] { nodeName, controlPort, socksPort, orPort,
+						dirPort, serverIpAddress });
+
+		// create directory node; parameter checking is done in constructor
+		final DirectoryNode dir =
+				new DirectoryNodeImpl(this, nodeName, controlPort, socksPort,
+						orPort, dirPort, serverIpAddress);
+
+		// add new directory node to nodes collection
+		nodes.put(nodeName, dir);
+
+		// add name to event manager as event source
+		eventManager.addEventSource(nodeName);
+
+		// log exiting and return directory node
+		logger.exiting(this.getClass().getName(), "createDirectory", dir);
+		return dir;
+	}
+
+	public DirectoryNode createDirectory(final String nodeName,
+			final int controlPort, final int socksPort, final int orPort,
+			final int dirPort) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "createDirectory",
+				new Object[] { nodeName, controlPort, socksPort, orPort,
+						dirPort });
+
+		// invoke overloaded method
+		final DirectoryNode dir =
+				this.createDirectory(nodeName, controlPort, socksPort, orPort,
+						dirPort, "127.0.0.1");
+
+		// log exiting and return directory node
+		logger.exiting(this.getClass().getName(), "createDirectory", dir);
+		return dir;
+	}
+
+	public DirectoryNode createDirectory(final String nodeName,
+			final String serverIpAddress) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "createDirectory",
+				new Object[] { nodeName, serverIpAddress });
+
+		// invoke overloaded method
+		final DirectoryNode dir =
+				this.createDirectory(nodeName, portCounter++, portCounter++,
+						portCounter++, portCounter++, serverIpAddress);
+
+		// log exiting and return directory node
+		logger.exiting(this.getClass().getName(), "createDirectory", dir);
+		return dir;
+	}
+
+	public DirectoryNode createDirectory(final String nodeName) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "createDirectory", nodeName);
+
+		// invoke overloaded method
+		final DirectoryNode dir =
+				this.createDirectory(nodeName, portCounter++, portCounter++,
+						portCounter++, portCounter++, "127.0.0.1");
+
+		// log exiting and return directory node
+		logger.exiting(this.getClass().getName(), "createDirectory", dir);
+		return dir;
+	}
+
+	public ProxyNode createProxy(final String nodeName, final int controlPort,
+			final int socksPort) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "createProxy", new Object[] {
+				nodeName, controlPort, socksPort });
+
+		// create proxy node; parameter checking is done in constructor
+		final ProxyNode proxy =
+				new ProxyNodeImpl(this, nodeName, controlPort, socksPort);
+
+		// add new proxy node to nodes collection
+		nodes.put(nodeName, proxy);
+
+		// add name to event manager as event source
+		eventManager.addEventSource(nodeName);
+
+		// log exiting and return proxy node
+		logger.exiting(this.getClass().getName(), "createProxy", proxy);
+		return proxy;
+	}
+
+	public ProxyNode createProxy(final String nodeName) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "createProxy", nodeName);
+
+		// invoke overloaded method
+		final ProxyNode proxy =
+				this.createProxy(nodeName, portCounter++, portCounter++);
+
+		// log exiting and return proxy node
+		logger.exiting(this.getClass().getName(), "createProxy", proxy);
+		return proxy;
+	}
+
+	public RouterNode createRouter(final String nodeName,
+			final int controlPort, final int socksPort, final int orPort,
+			final int dirPort, final String serverIpAddress) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "createRouter",
+				new Object[] { nodeName, controlPort, socksPort, orPort,
+						dirPort, serverIpAddress });
+
+		// create router node; parameter checking is done in constructor
+		final RouterNode router =
+				new RouterNodeImpl(this, nodeName, controlPort, socksPort,
+						orPort, dirPort, serverIpAddress);
+
+		// add new router node to nodes collection
+		nodes.put(nodeName, router);
+
+		// add name to event manager as event source
+		eventManager.addEventSource(nodeName);
+
+		// log exiting and return router node
+		logger.exiting(this.getClass().getName(), "createRouter", router);
+		return router;
+	}
+
+	public RouterNode createRouter(final String nodeName,
+			final int controlPort, final int socksPort, final int orPort,
+			final int dirPort) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "createRouter",
+				new Object[] { nodeName, controlPort, socksPort, orPort,
+						dirPort });
+
+		// invoke overloaded method
+		final DirectoryNode dir =
+				this.createDirectory(nodeName, controlPort, socksPort, orPort,
+						dirPort, "127.0.0.1");
+
+		// log exiting and return directory node
+		logger.exiting(this.getClass().getName(), "createRouter", dir);
+		return dir;
+	}
+
+	public RouterNode createRouter(final String nodeName,
+			final String serverIpAddress) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "createRouter",
+				new Object[] { nodeName, serverIpAddress });
+
+		// invoke overloaded method
+		final RouterNode dir =
+				this.createRouter(nodeName, portCounter++, portCounter++,
+						portCounter++, portCounter++, serverIpAddress);
+
+		// log exiting and return directory node
+		logger.exiting(this.getClass().getName(), "createRouter", dir);
+		return dir;
+	}
+
+	public RouterNode createRouter(final String nodeName) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "createRouter", nodeName);
+
+		// invoke overloaded method
+		final RouterNode router =
+				this.createRouter(nodeName, portCounter++, portCounter++,
+						portCounter++, portCounter++, "127.0.0.1");
+
+		// log exiting and return router node
+		logger.exiting(this.getClass().getName(), "createRouter", router);
+		return router;
+	}
+
+	public ServerApplication createServer(final String serverApplicationName,
+			final int serverPort) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "createServer",
+				new Object[] { serverApplicationName, serverPort });
+
+		// create server; parameter checking is done in constructor
+		final ServerApplicationImpl server =
+				new ServerApplicationImpl(this, serverApplicationName,
+						serverPort);
+
+		// add name to event manager as event source
+		eventManager.addEventSource(serverApplicationName);
+
+		// log exiting and return server
+		logger.exiting(this.getClass().getName(), "createServer", server);
+		return server;
+	}
+
+	public ServerApplication createServer(final String serverApplicationName) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "createServer",
+				serverApplicationName);
+
+		// invoke overloaded method
+		final ServerApplication server =
+				this.createServer(serverApplicationName, portCounter++);
+
+		// log exiting and return server
+		logger.exiting(this.getClass().getName(), "createServer", server);
+		return server;
+	}
+
+	public EventManager getEventManager() {
+		return eventManager;
+	}
+
+	/**
+	 * Returns the implementation instance of the event manager of this network.
+	 *
+	 * @return The implementation instance of the event manager of this network.
+	 */
+	public EventManagerImpl getEventManagerImpl() {
+		return eventManager;
+	}
+
+	public File getWorkingDirectory() {
+		return workingDir;
+	}
+
+	public ProxyNode getNode(final String nodeName) {
+		return nodes.get(nodeName);
+	}
+
+	public Map<String, ProxyNode> getAllProxyNodes() {
+		final Map<String, ProxyNode> result = new HashMap<String, ProxyNode>();
+		for (final String nodeName : nodes.keySet()) {
+			final ProxyNode node = nodes.get(nodeName);
+			if (!(node instanceof RouterNode)) {
+				result.put(nodeName, node);
+			}
+		}
+		return result;
+	}
+
+	public Map<String, RouterNode> getAllRouterNodes() {
+		final Map<String, RouterNode> result =
+				new HashMap<String, RouterNode>();
+		for (final String nodeName : nodes.keySet()) {
+			final ProxyNode node = nodes.get(nodeName);
+			if (node instanceof RouterNode && !(node instanceof DirectoryNode)) {
+				result.put(nodeName, (RouterNode) node);
+			}
+		}
+		return result;
+	}
+
+	public Map<String, DirectoryNode> getAllDirectoryNodes() {
+		final Map<String, DirectoryNode> result =
+				new HashMap<String, DirectoryNode>();
+		for (final String nodeName : nodes.keySet()) {
+			final ProxyNode node = nodes.get(nodeName);
+			if (node instanceof DirectoryNode) {
+				result.put(nodeName, (DirectoryNode) node);
+			}
+		}
+		return result;
+	}
+
+	public Map<String, ProxyNode> getAllNodes() {
+		return new HashMap<String, ProxyNode>(nodes);
+	}
+
+	public boolean hupUntilUp(final int tries, final long hupInterval)
+			throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "hupUntilUp", new Object[] {
+				tries, hupInterval });
+
+		// check if all nodes are running
+		for (final ProxyNode node : nodes.values()) {
+			if (node.getNodeState() != NodeState.RUNNING) {
+				final IllegalStateException e =
+						new IllegalStateException(
+								"All nodes must be running before sending them HUP "
+										+ "commands!");
+				logger.throwing(this.getClass().getName(), "hupUntilUp", e);
+				throw e;
+			}
+		}
+
+		// check if nodes are already up; if so, return immediately
+		if (allNodesUp()) {
+
+			// log exiting and return true
+			logger.exiting(this.getClass().getName(), "hupUntilUp", true);
+			return true;
+		}
+
+		final Object hupLock = new Object();
+		// create and register a new event handler for each node
+		for (final ProxyNode node : nodes.values()) {
+			eventManager.addEventListener(node.getNodeName(),
+					new EventListener() {
+						public void handleEvent(final Event event) {
+							if (event.getType() == NodeEventType.NODE_CIRCUIT_OPENED) {
+								synchronized(hupLock) {
+									hupLock.notify();	
+								}
+								eventManager.removeEventListener(this);
+							}
+						}
+					});
+		}
+
+		// walk through wait-check-hup loop until there are no tries left
+		for (int i = 0; i < tries; i++) {
+
+			// determine how long to try waiting for the hangup
+			final long endOfSleeping = System.currentTimeMillis() + hupInterval;
+
+			// unless all nodes have reported to be up, wait for the given
+			// maximum time
+			while (System.currentTimeMillis() < endOfSleeping) {
+
+				synchronized(hupLock) {
+					// check if nodes are up now
+					if (allNodesUp()) {
+
+						// log exiting and return true
+						logger.exiting(this.getClass().getName(), "hupUntilUp",
+								true);
+						return true;
+					}
+					// sleep
+					try {
+						hupLock.wait(hupInterval);
+					} catch (final InterruptedException e) {
+						// do nothing about it
+					}
+				}
+			}
+
+			logger.log(Level.FINE, "Sending HUP to nodes");
+			// send a HUP signal to all nodes
+			for (final ProxyNode node : nodes.values()) {
+				logger
+						.log(Level.FINE, "Sending HUP to node "
+								+ node.toString());
+				node.hup();
+			}
+
+			// continue in loop
+		}
+
+		// no retries left and not all nodes are up; log exiting and return
+		// failure
+		logger.exiting(this.getClass().getName(), "hupUntilUp", false);
+		return false;
+	}
+
+	public void hupAllNodes() throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "hupAllNodes");
+
+		// check if all nodes are running
+		for (final ProxyNode node : nodes.values()) {
+			if (node.getNodeState() != NodeState.RUNNING) {
+				final IllegalStateException e =
+						new IllegalStateException(
+								"All nodes must be running before sending them HUP "
+										+ "commands!");
+				logger.throwing(this.getClass().getName(), "hupAllNodes", e);
+				throw e;
+			}
+		}
+
+		// send a HUP signal to all nodes
+		for (final ProxyNode node : nodes.values()) {
+			logger.log(Level.FINE, "Sending HUP to node " + node.toString());
+			node.hup();
+		}
+
+		// no retries left and not all nodes are up; log exiting and return
+		// failure
+		logger.exiting(this.getClass().getName(), "hupAllNodes");
+	}
+
+	public void hupAllDirectories() throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "hupAllDirectories");
+
+		// check if all directory nodes are running
+		for (final ProxyNode node : nodes.values()) {
+			if (node instanceof DirectoryNode
+					&& node.getNodeState() != NodeState.RUNNING) {
+				final IllegalStateException e =
+						new IllegalStateException(
+								"All directory nodes must be running before sending "
+										+ "them HUP commands!");
+				logger.throwing(this.getClass().getName(), "hupAllDirectories",
+						e);
+				throw e;
+			}
+		}
+
+		// send a HUP signal to all nodes
+		for (final ProxyNode node : nodes.values()) {
+			if (node instanceof DirectoryNode) {
+				logger
+						.log(Level.FINE, "Sending HUP to node "
+								+ node.toString());
+				node.hup();
+			}
+		}
+
+		// no retries left and not all nodes are up; log exiting and return
+		// failure
+		logger.exiting(this.getClass().getName(), "hupAllDirectories");
+	}
+
+	public void shutdownNodes() throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "shutdownNodes");
+
+		// iteratively shut down all running nodes; if an exception is caught,
+		// continue shutting down the other nodes and throw the first exception
+		// subsequently
+		PuppeTorException firstCaughtException = null;
+		for (final ProxyNode node : nodes.values()) {
+			if (node.getNodeState() == NodeState.RUNNING) {
+				try {
+					node.shutdown();
+				} catch (final PuppeTorException e) {
+					if (firstCaughtException == null) {
+						firstCaughtException = e;
+					}
+				}
+			}
+		}
+
+		// if an exception was caught during shutting down nodes, throw the
+		// first caught exception
+		if (firstCaughtException != null) {
+			logger.throwing(this.getClass().getName(), "shutdownNodes",
+					firstCaughtException);
+			throw firstCaughtException;
+		}
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "shutdownNodes");
+	}
+
+	public boolean startNodes(final long maximumTimeToWaitInMillis)
+			throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "startNodes",
+				maximumTimeToWaitInMillis);
+
+		// check node states
+		for (final ProxyNode node : nodes.values()) {
+			if (node.getNodeState() != NodeState.CONFIGURATION_WRITTEN) {
+				final IllegalStateException e =
+						new IllegalStateException(
+								"All configurations must be written before starting "
+										+ "nodes!");
+				logger.throwing(this.getClass().getName(), "startNodes", e);
+				throw e;
+			}
+		}
+
+		// check parameter
+		if (maximumTimeToWaitInMillis < 0) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "startNodes", e);
+			throw e;
+		}
+
+		// remember time when we begin starting the nodes
+		final long before = System.currentTimeMillis();
+
+		// start nodes in parallel
+		final Set<NodeStarter> allNodeStarters = new HashSet<NodeStarter>();
+		for (final ProxyNode node : nodes.values()) {
+			final NodeStarter nodeStarter =
+					new NodeStarter(node, maximumTimeToWaitInMillis);
+			allNodeStarters.add(nodeStarter);
+			nodeStarter.start();
+		}
+
+		// wait for all node starts to complete
+		for (final NodeStarter nodeStarter : allNodeStarters) {
+
+			// join node starts one after the other
+			try {
+				nodeStarter.join();
+			} catch (final InterruptedException e) {
+				// this happens?! we have some kind of problem here!
+				logger.log(Level.WARNING,
+						"Interrupt while joining node starter!");
+
+				// log exiting and return false
+				logger.exiting(this.getClass().getName(), "startNodes", false);
+				return false;
+			}
+
+			// if any thread has caught an exception, throw that exception now
+			final Exception caughtException = nodeStarter.caughtException;
+			if (caughtException != null) {
+				final PuppeTorException ex =
+						new PuppeTorException("Exception while starting node "
+								+ nodeStarter.node.getNodeName(),
+								caughtException);
+				logger.throwing(this.getClass().getName(), "startNodes", ex);
+				throw ex;
+			}
+
+			// if node start did not succeed in the given time, fail
+			if (!nodeStarter.success) {
+				logger.log(Level.WARNING,
+						"Starting nodes was not successful in "
+								+ maximumTimeToWaitInMillis / 1000
+								+ " seconds.", networkName);
+
+				// log exiting and return false
+				logger.exiting(this.getClass().getName(), "startNodes", false);
+				return false;
+			}
+		}
+
+		// determine how long we took to start all nodes
+		final long after = System.currentTimeMillis();
+		logger.log(Level.FINE, "Starting nodes was successful and took "
+				+ (after - before) / 1000 + " seconds.", networkName);
+
+		// log exiting and return true
+		logger.exiting(this.getClass().getName(), "startNodes", true);
+		return true;
+	}
+
+	@Override
+	public String toString() {
+		return this.getClass().getSimpleName() + ": networkName=\""
+				+ networkName;
+	}
+
+	public String getNetworkName() {
+		return networkName;
+	}
+
+	public void writeConfigurations() throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "writeConfigurations");
+
+		// write configurations for all nodes
+		for (final ProxyNode node : nodes.values()) {
+			node.writeConfiguration();
+		}
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "writeConfigurations");
+	}
+
+	public void addTemplateConfiguration(
+			final Class<? extends ProxyNode> nodeClass,
+			final String templateConfigurationString) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "addTemplateConfiguration",
+				new Object[] { nodeClass, templateConfigurationString });
+
+		// check parameters
+		if (nodeClass == null || templateConfigurationString == null
+				|| templateConfigurationString.length() < 1
+				|| !templateConfigurationString.contains(" ")) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(),
+					"addTemplateConfiguration", e);
+			throw e;
+		}
+
+		// add template string to appropriate template configuration
+		if (nodeClass == ProxyNode.class) {
+			ProxyNodeImpl.templateConfiguration
+					.add(templateConfigurationString);
+		} else if (nodeClass == RouterNode.class) {
+			RouterNodeImpl.templateConfiguration
+					.add(templateConfigurationString);
+		} else if (nodeClass == DirectoryNode.class) {
+			DirectoryNodeImpl.templateConfiguration
+					.add(templateConfigurationString);
+		} else {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(),
+					"addTemplateConfiguration", e);
+			throw e;
+		}
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "addTemplateConfiguration");
+	}
+
+	public List<String> getTemplateConfiguration(
+			final Class<? extends ProxyNode> nodeClass) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "getTemplateConfiguration",
+				nodeClass);
+
+		// check parameter
+		if (nodeClass == null) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(),
+					"getTemplateConfiguration", e);
+			throw e;
+		}
+
+		// obtain reference on appropriate template configuration
+		List<String> result = null;
+		if (nodeClass == ProxyNode.class) {
+			result = new ArrayList<String>(ProxyNodeImpl.templateConfiguration);
+		} else if (nodeClass == RouterNode.class) {
+			result =
+					new ArrayList<String>(RouterNodeImpl.templateConfiguration);
+		} else if (nodeClass == DirectoryNode.class) {
+			result =
+					new ArrayList<String>(
+							DirectoryNodeImpl.templateConfiguration);
+		} else {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(),
+					"getTemplateConfiguration", e);
+			throw e;
+		}
+
+		// log exiting and return result
+		logger.exiting(this.getClass().getName(), "getTemplateConfiguration",
+				result);
+		return result;
+	}
+
+	public void removeTemplateConfiguration(
+			final Class<? extends ProxyNode> nodeClass,
+			final String templateConfigurationKey) {
+
+		// log entering
+		logger.entering(this.getClass().getName(),
+				"removeTemplateConfiguration", new Object[] { nodeClass,
+						templateConfigurationKey });
+
+		// check parameters
+		if (nodeClass == null || templateConfigurationKey == null
+				|| templateConfigurationKey.length() < 1) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(),
+					"removeTemplateConfiguration", e);
+			throw e;
+		}
+
+		// obtain reference on appropriate template configuration
+		List<String> templateConfig = null;
+		if (nodeClass == ProxyNode.class) {
+			templateConfig = ProxyNodeImpl.templateConfiguration;
+		} else if (nodeClass == RouterNode.class) {
+			templateConfig = RouterNodeImpl.templateConfiguration;
+		} else if (nodeClass == DirectoryNode.class) {
+			templateConfig = DirectoryNodeImpl.templateConfiguration;
+		} else {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(),
+					"removeTemplateConfiguration", e);
+			throw e;
+		}
+
+		// iterate over existing template configuration strings and remove all
+		// configuration strings that have the given configuration key
+		final List<String> configurationStringsToRemove =
+				new ArrayList<String>();
+		for (final String currentConfigurationString : templateConfig) {
+			final String currentConfigurationKey =
+					currentConfigurationString.substring(0,
+							currentConfigurationString.indexOf(" "));
+			if (currentConfigurationKey.equals(templateConfigurationKey)) {
+				configurationStringsToRemove.add(currentConfigurationString);
+			}
+		}
+		templateConfig.removeAll(configurationStringsToRemove);
+
+		// log exiting
+		logger
+				.exiting(this.getClass().getName(),
+						"removeTemplateConfiguration");
+	}
+
+	/**
+	 * Returns the current port number and increments it afterwards.
+	 *
+	 * @return The current port number.
+	 */
+	int getNextPortNumber() {
+		return portCounter++;
+	}
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/impl/ProxyNodeImpl.java b/src/de/uniba/wiai/lspi/puppetor/impl/ProxyNodeImpl.java
old mode 100755
new mode 100644
index 76cf436..1549472
--- a/src/de/uniba/wiai/lspi/puppetor/impl/ProxyNodeImpl.java
+++ b/src/de/uniba/wiai/lspi/puppetor/impl/ProxyNodeImpl.java
@@ -1,760 +1,760 @@
-/*
- * 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 de.uniba.wiai.lspi.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.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import net.freehaven.tor.control.TorControlConnection;
-import de.uniba.wiai.lspi.puppetor.HiddenService;
-import de.uniba.wiai.lspi.puppetor.NodeEventType;
-import de.uniba.wiai.lspi.puppetor.NodeState;
-import de.uniba.wiai.lspi.puppetor.ProxyNode;
-import de.uniba.wiai.lspi.puppetor.PuppeTorException;
-
-/**
- * Implementation of <code>ProxyNode</code>.
- *
- * @author kloesing
- */
-public class ProxyNodeImpl implements ProxyNode {
-
-	/**
-	 * Executable file containing Tor.
-	 *
-	 * TODO make this configurable!
-	 */
-	protected static final File torExecutable = new File("tor");
-
-	/**
-	 * The <code>torrc</code> configuration file of this Tor node.
-	 */
-	protected File configFile;
-
-	/**
-	 * Collects all configuration strings for this node during the configuration
-	 * phase in the order they are added.
-	 */
-	protected List<String> configuration;
-
-	/**
-	 * Connection via Tor controller.
-	 */
-	protected TorControlConnection conn;
-
-	/**
-	 * Port on which the Tor node will be listening for us as its controller.
-	 */
-	protected int controlPort;
-
-	/**
-	 * Event manager to which all events concerning this node are notified.
-	 */
-	private final EventManagerImpl eventManager;
-
-	/**
-	 * Logger for this node which is called "node." plus the name of this node.
-	 */
-	protected Logger logger;
-
-	/**
-	 * Network to which this node belongs.
-	 */
-	protected NetworkImpl network;
-
-	/**
-	 * Name of this node that is used as part of the working directory, as
-	 * logger name of this node, and as event source.
-	 */
-	protected String nodeName;
-
-	/**
-	 * The state of this node.
-	 */
-	protected NodeState nodeState = NodeState.CONFIGURING;
-
-	/**
-	 * Port on which the Tor node will be listening for SOCKS connection
-	 * requests.
-	 */
-	protected int socksPort;
-
-	/**
-	 * The running Tor process that belongs to this node.
-	 */
-	protected Process torProcess;
-
-	/**
-	 * Directory in which all information concerning this node is stored.
-	 */
-	protected File workingDir;
-
-	/**
-	 * Returns this node's working directory.
-	 *
-	 * @return This node's working directory.
-	 */
-	File getWorkingDir() {
-		return workingDir;
-	}
-
-	/**
-	 * Creates a new <code>ProxyNodeImpl</code> and adds it to the given
-	 * <code>network</code>, but does not yet write its configuration to disk
-	 * or start the corresponding Tor process.
-	 *
-	 * @param network
-	 *            Network configuration to which this node belongs.
-	 * @param nodeName
-	 *            The name of the new node which may only consist of between 1
-	 *            and 19 alpha-numeric characters.
-	 * @param controlPort
-	 *            Port on which the Tor node will be listening for us as its
-	 *            controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            Port on which the Tor node will be listening for SOCKS
-	 *            connection requests. May not be negative or greater than
-	 *            65535.
-	 * @throws IllegalArgumentException
-	 *             If at least one of the parameters is <code>null</code> or
-	 *             has an invalid value.
-	 */
-	ProxyNodeImpl(final NetworkImpl network, final String nodeName,
-			final int controlPort, final int socksPort) {
-
-		// make sure that nodeName is a valid logger name
-		if (nodeName == null || nodeName.length() < 1 || nodeName.length() > 19
-				|| !nodeName.matches("[a-zA-Z0-9]*")) {
-			final String reason =
-					"\"" + nodeName + "\" is not a valid node name!";
-			final IllegalArgumentException e =
-					new IllegalArgumentException(reason);
-			throw e;
-		}
-
-		// create logger
-		logger = Logger.getLogger(nodeName + "." + this.getClass().getName());
-
-		logger.setLevel(Level.ALL);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "ProxyNodeImpl",
-				new Object[] { network, nodeName, controlPort, socksPort });
-
-		// check remaining parameters
-		if (network == null || controlPort < 0 || controlPort > 65535
-				|| socksPort < 0 || socksPort > 65535) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "ProxyNodeImpl", e);
-			throw e;
-		}
-
-		// store parameter values
-		this.network = network;
-		this.nodeName = nodeName;
-		this.controlPort = controlPort;
-		this.socksPort = socksPort;
-
-		// obtain reference on event manager from network
-		eventManager = network.getEventManagerImpl();
-
-		// create working directory
-		workingDir =
-				new File(this.network.getWorkingDirectory().getAbsolutePath()
-						+ File.separator + nodeName + File.separator);
-		workingDir.mkdirs();
-		logger.log(Level.FINE, "Created working directory \""
-				+ workingDir.getAbsolutePath() + "\"");
-
-		// create reference on config file
-		configFile =
-				new File(workingDir.getAbsolutePath() + File.separator
-						+ "torrc");
-
-		// initialize configuration
-		configuration = new ArrayList<String>(templateConfiguration);
-		configuration.add("ControlPort " + controlPort);
-		configuration.add("SocksPort " + socksPort);
-
-		// initialize state
-		nodeState = NodeState.CONFIGURING;
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "ProxyNodeImpl");
-	}
-
-	public void addConfiguration(final String configurationString) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addConfiguration",
-				configurationString);
-
-		// check parameter
-		if (configurationString == null || configurationString.length() < 1
-				|| !configurationString.contains(" ")) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "addConfiguration", e);
-			throw e;
-		}
-
-		// add configuration string
-		configuration.add(configurationString);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "addConfiguration");
-	}
-
-	public void addConfigurations(final List<String> configurationStrings) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addConfigurations",
-				configurationStrings);
-
-		// check parameter
-		if (configurationStrings == null) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "addConfigurations", e);
-			throw e;
-		}
-
-		// add configuration strings one by one
-		for (final String conf : configurationStrings) {
-			addConfiguration(conf);
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "addConfigurations");
-	}
-
-	public void replaceConfiguration(final String configurationString) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "replaceConfiguration",
-				configurationString);
-
-		// check parameter
-		if (configurationString == null || configurationString.length() < 1
-				|| !configurationString.contains(" ")) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "replaceConfiguration",
-					e);
-			throw e;
-		}
-
-		// extract configuration key
-		final String configurationKey =
-				configurationString.substring(0, configurationString
-						.indexOf(" "));
-
-		// iterate over existing configuration strings and replace the first
-		// occurrence of configuration key with new configuration string
-		final Iterator<String> it = configuration.listIterator();
-		boolean replaced = false;
-		for (int counter = 0; !replaced && it.hasNext(); counter++) {
-			final String currentConfigurationString = it.next();
-			final String currentConfigurationKey =
-					currentConfigurationString.substring(0,
-							currentConfigurationString.indexOf(" "));
-			if (currentConfigurationKey.equals(configurationKey)) {
-				configuration.set(counter, configurationString);
-				replaced = true;
-			}
-		}
-
-		// if no such configuration key was found, append the configuration
-		// string
-		if (!replaced) {
-			configuration.add(configurationString);
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "replaceConfiguration");
-	}
-
-	public void removeConfiguration(final String configurationKey) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "deleteConfiguration",
-				configurationKey);
-
-		// check parameter
-		if (configurationKey == null || configurationKey.length() < 1) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger
-					.throwing(this.getClass().getName(), "deleteConfiguration",
-							e);
-			throw e;
-		}
-
-		// iterate over existing configuration strings and remove all
-		// configuration strings that have the given configuration key
-		final List<String> configurationStringsToRemove =
-				new ArrayList<String>();
-		for (final String currentConfigurationString : configuration) {
-			final String currentConfigurationKey =
-					currentConfigurationString.substring(0,
-							currentConfigurationString.indexOf(" "));
-			if (currentConfigurationKey.equals(configurationKey)) {
-				configurationStringsToRemove.add(currentConfigurationString);
-			}
-		}
-		configuration.removeAll(configurationStringsToRemove);
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "deleteConfiguration");
-	}
-
-	public synchronized HiddenService addHiddenService(
-			final String serviceName, final int servicePort,
-			final int virtualPort) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addHiddenService",
-				new Object[] { serviceName, servicePort, virtualPort });
-
-		// create hidden service object; parameter checking is done in
-		// constructor
-		final HiddenService result =
-				new HiddenServiceImpl(this, serviceName, servicePort,
-						virtualPort);
-
-		// add hidden service using Tor controller
-		configuration.add("HiddenServiceDir " + workingDir.getAbsolutePath()
-				+ File.separator + serviceName + "\nHiddenServicePort "
-				+ virtualPort + " 127.0.0.1:" + servicePort);
-
-		// log exiting and return hidden service object
-		logger.exiting(this.getClass().getName(), "addHiddenService", result);
-		return result;
-	}
-
-	public synchronized HiddenService addHiddenService(
-			final String serviceName, final int servicePort) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addHiddenService",
-				new Object[] { serviceName, servicePort });
-
-		// invoke overloaded method
-		final HiddenService result =
-				this.addHiddenService(serviceName, servicePort, 80);
-
-		// log exiting and return hidden service
-		logger.exiting(this.getClass().getName(), "addHiddenService", result);
-		return result;
-	}
-
-	public synchronized HiddenService addHiddenService(final String serviceName) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "addHiddenService",
-				serviceName);
-
-		// invoke overloaded method
-		final HiddenService result =
-				this.addHiddenService(serviceName, network.getNextPortNumber(),
-						80);
-
-		// log exiting and return hidden service
-		logger.exiting(this.getClass().getName(), "addHiddenService", result);
-		return result;
-	}
-
-	public String getNodeName() {
-		return nodeName;
-	}
-
-	public synchronized NodeState getNodeState() {
-		return nodeState;
-	}
-
-	public synchronized void hup() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "hup");
-
-		// check state
-		if (nodeState != NodeState.RUNNING || conn == null) {
-			final IllegalStateException e =
-					new IllegalStateException(
-							"Cannot hup a process when it's not running or there is "
-									+ "no connection to its control port!");
-			logger.throwing(this.getClass().getName(), "hup", e);
-			throw e;
-		}
-
-		// send HUP signal to Tor process
-		try {
-			conn.signal("HUP");
-		} catch (final IOException e) {
-			final PuppeTorException ex =
-					new PuppeTorException(
-							"Could not send the command HUP to the Tor process!",
-							e);
-			logger.throwing(this.getClass().getName(), "hup", ex);
-			throw ex;
-		}
-		// log exiting
-		logger.exiting(this.getClass().getName(), "hup");
-	}
-
-	public synchronized void shutdown() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "shutdown");
-
-		// check state
-		if (nodeState != NodeState.RUNNING) {
-			final IllegalStateException e = new IllegalStateException();
-			logger.throwing(this.getClass().getName(), "shutdown", e);
-			throw e;
-		}
-
-		// we cannot simply kill the Tor process, because we have established a
-		// controller connection to it which would interpret a closed socket as
-		// failure and throw a RuntimeException
-		try {
-			conn.shutdownTor("SHUTDOWN");
-		} catch (final IOException e) {
-			final PuppeTorException ex =
-					new PuppeTorException(
-							"Could not send shutdown command to Tor process!",
-							e);
-			logger.throwing(this.getClass().getName(), "shutdown", ex);
-			throw ex;
-		}
-
-		// change state
-		nodeState = NodeState.SHUT_DOWN;
-
-		// fire event
-		eventManager.observeInternalEvent(System.currentTimeMillis(),
-				getNodeName(), NodeEventType.NODE_STOPPED, "Node stopped.");
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "shutdown");
-	}
-
-	/**
-	 * Helper thread that waits for a given time for a given process to
-	 * potentially terminate in order to find out if there are problems. If
-	 * either the process terminates cleanly within this timeout, or does not
-	 * terminate, the exit value will be 0; otherwise it will contain the exit
-	 * code of the terminated process. This functionality is added, because it
-	 * is not provided by Process. XXX Some stuff in here looks still dodgy.
-	 * What happens if we get an InterruptedException in run and thus don't set
-	 * exitValue?-SH
-	 */
-	private static class ProcessWaiter extends Thread {
-
-		/** The process to wait for. */
-		private final Process process;
-
-		/** The exit value or 0 if the process is still running. */
-		private final AtomicInteger exitValue;
-
-		/**
-		 * Creates a new <code>ProcessWaiter</code> for process
-		 * <code>process</code>, but does not start it, yet.
-		 *
-		 * @param process
-		 *            The process to wait for.
-		 */
-		ProcessWaiter(final Process process) {
-			this.process = process;
-			exitValue = new AtomicInteger(0);
-		}
-
-		@Override
-		public void run() {
-			try {
-				exitValue.set(process.waitFor());
-			} catch (final InterruptedException e) {}
-		}
-
-		/**
-		 * Causes the current thread to wait until the process has terminated or
-		 * the <code>timeoutInMillis</code> has expired. This method returns
-		 * immediately if the subprocess has already terminated.
-		 *
-		 * @param timeoutInMillis
-		 *            The maximum time to wait for the process to terminate.
-		 * @return The exit value of the terminated process or 0 if the process
-		 *         is still running.
-		 */
-		public int waitFor(final long timeoutInMillis) {
-			try {
-				sleep(timeoutInMillis);
-			} catch (final InterruptedException e) {}
-			interrupt();
-			return exitValue.get();
-		}
-	}
-
-	public synchronized boolean startNode(final long maximumTimeToWaitInMillis)
-			throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "startNode",
-				maximumTimeToWaitInMillis);
-
-		// check state
-		if (nodeState != NodeState.CONFIGURATION_WRITTEN) {
-			final String reason =
-					"Node is not in state "
-							+ "NodeState.CONFIGURATION_WRITTEN!";
-			final IllegalStateException e = new IllegalStateException(reason);
-			logger.throwing(this.getClass().getName(), "startNode", e);
-			throw e;
-		}
-
-		// start process
-		final ProcessBuilder processBuilder =
-				new ProcessBuilder(torExecutable.getPath(), "-f", "torrc");
-		processBuilder.directory(workingDir);
-		processBuilder.redirectErrorStream(true);
-		try {
-			torProcess = processBuilder.start();
-			logger.log(Level.FINE, "Started Tor process successfully!");
-		} catch (final IOException e) {
-			final String reason = "Could not start Tor process!";
-			final PuppeTorException ex = new PuppeTorException(reason, e);
-			logger.throwing(this.getClass().getName(), "startNode", ex);
-			throw ex;
-		}
-
-		// start thread to parse output
-		final BufferedReader br =
-				new BufferedReader(new InputStreamReader(torProcess
-						.getInputStream()));
-		final Thread outputThread = new Thread() {
-			@Override
-			public void run() {
-
-				// log entering
-				logger.entering(this.getClass().getName(), "run");
-
-				// read output from Tor to parse it
-				String line = null;
-				try {
-					while ((line = br.readLine()) != null) {
-						eventManager.observeUnparsedEvent(ProxyNodeImpl.this
-								.getNodeName(), line);
-					}
-				} catch (IOException e) {
-
-					// only print out a warning for this exception if this node
-					// is running; otherwise, silently ignore it...
-					if (getNodeState() == NodeState.RUNNING) {
-						String reason =
-								"IOException when reading output from Tor "
-										+ "process "
-										+ ProxyNodeImpl.this.getNodeName()
-										+ "!";
-						logger.log(Level.WARNING, reason, e);
-					}
-				}
-
-				// log exiting
-				logger.exiting(this.getClass().getName(), "run");
-			}
-		};
-		outputThread.setDaemon(true);
-		outputThread.setName(nodeName + " Output Parser");
-		outputThread.start();
-		logger.log(Level.FINE, "Started thread to parse output!");
-
-		// add shutdown hook that kills the process on JVM exit
-		final Process p = torProcess;
-		Runtime.getRuntime().addShutdownHook(new Thread() {
-			@Override
-			public void run() {
-
-				// log entering
-				logger.entering(this.getClass().getName(), "run");
-
-				// destroy Tor process
-				p.destroy();
-
-				// log exiting
-				logger.exiting(this.getClass().getName(), "run");
-			}
-		});
-		logger.log(Level.FINER,
-				"Started shutdown hook that will destroy the Tor process on "
-						+ "JVM exit!");
-
-		// wait to see if the process is started or exited immediately; wait for
-		// one second to be sure that Tor terminates if there is an error,
-		// especially if the computer is very busy and many nodes are created
-		final ProcessWaiter waiter = new ProcessWaiter(torProcess);
-		waiter.start();
-		final int exitValue = waiter.waitFor(1000);
-		if (exitValue != 0) {
-			// Tor did not manage to start correctly
-			logger.log(Level.WARNING, "Could not start Tor process! Tor "
-					+ "exited with exit value " + exitValue
-					+ "! Please go check the config options in " + configFile
-					+ " manually!");
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "startNode", false);
-			return false;
-		}
-
-		// wait for Tor to open the control port
-		logger.log(Level.FINER, "Waiting for Tor to open its control port...");
-		if (!eventManager.waitForAnyOccurence(nodeName,
-				NodeEventType.NODE_CONTROL_PORT_OPENED,
-				maximumTimeToWaitInMillis)) {
-
-			// Tor did not open its control port
-			logger.log(Level.WARNING, "Tor node " + nodeName
-					+ " did not manage to open its control port within "
-					+ maximumTimeToWaitInMillis + " milliseconds!");
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "startNode", false);
-			return false;
-		}
-		logger.log(Level.FINE,
-				"Tor has successfully opened its control port and told us "
-						+ "about that!");
-
-		// connect to the controller
-		logger.log(Level.FINER, "Connecting to control port...");
-		try {
-			final Socket controlSocket =
-					new java.net.Socket("127.0.0.1", controlPort);
-			conn = TorControlConnection.getConnection(controlSocket);
-			conn.authenticate(new byte[0]);
-		} catch (final IOException e) {
-			final String reason =
-					"Could not connect to control port " + controlPort + "!";
-			final PuppeTorException ex = new PuppeTorException(reason, e);
-			logger.throwing(this.getClass().getName(), "startNode", ex);
-			throw ex;
-		}
-		logger.log(Level.FINE, "Connected to control port successfully!");
-
-		// set state to RUNNING
-		nodeState = NodeState.RUNNING;
-
-		// fire event
-		eventManager.observeInternalEvent(System.currentTimeMillis(),
-				getNodeName(), NodeEventType.NODE_STARTED, "Node started.");
-
-		// log exiting and return with success
-		logger.exiting(this.getClass().getName(), "startNode", true);
-		return true;
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": nodeName=\"" + nodeName
-				+ "\", controlPort=" + controlPort + ", socksPort=" + socksPort;
-	}
-
-	public synchronized void writeConfiguration() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "writeConfiguration");
-
-		// write config file
-		try {
-			final BufferedWriter bw =
-					new BufferedWriter(new FileWriter(configFile));
-			for (final String c : configuration) {
-				bw.write(c + "\n");
-			}
-			bw.close();
-		} catch (final IOException e) {
-			final PuppeTorException ex =
-					new PuppeTorException(
-							"Could not write configuration file!", e);
-			logger.throwing(this.getClass().getName(),
-					"writeConfigurationFile", ex);
-			throw ex;
-		}
-
-		// change state, if necessary
-		if (nodeState == NodeState.CONFIGURING) {
-			nodeState = NodeState.CONFIGURATION_WRITTEN;
-		}
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "writeConfiguration");
-	}
-
-	public int getSocksPort() {
-		return socksPort;
-	}
-
-	public int getControlPort() {
-		return controlPort;
-	}
-
-	public List<String> getConfiguration() {
-		return new ArrayList<String>(configuration);
-	}
-
-	/**
-	 * Template configuration of proxy nodes.
-	 */
-	static List<String> templateConfiguration;
-
-	static {
-		templateConfiguration = new ArrayList<String>();
-
-		templateConfiguration.add("DataDirectory .");
-		templateConfiguration.add("SafeLogging 0");
-		templateConfiguration.add("UseEntryGuards 0");
-
-		templateConfiguration.add("Log info stdout");
-		templateConfiguration.add("Log info file log");
-
-		// TODO This is now contained in proposal 135.
-		// templateConfiguration.add("EnforceDistinctSubnets 0");
-		// templateConfiguration.add("ClientDNSRejectInternalAddresses 0");
-	}
-}
+/*
+ * 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 de.uniba.wiai.lspi.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.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import net.freehaven.tor.control.TorControlConnection;
+import de.uniba.wiai.lspi.puppetor.HiddenService;
+import de.uniba.wiai.lspi.puppetor.NodeEventType;
+import de.uniba.wiai.lspi.puppetor.NodeState;
+import de.uniba.wiai.lspi.puppetor.ProxyNode;
+import de.uniba.wiai.lspi.puppetor.PuppeTorException;
+
+/**
+ * Implementation of <code>ProxyNode</code>.
+ *
+ * @author kloesing
+ */
+public class ProxyNodeImpl implements ProxyNode {
+
+	/**
+	 * Executable file containing Tor.
+	 *
+	 * TODO make this configurable!
+	 */
+	protected static final File torExecutable = new File("tor");
+
+	/**
+	 * The <code>torrc</code> configuration file of this Tor node.
+	 */
+	protected File configFile;
+
+	/**
+	 * Collects all configuration strings for this node during the configuration
+	 * phase in the order they are added.
+	 */
+	protected List<String> configuration;
+
+	/**
+	 * Connection via Tor controller.
+	 */
+	protected TorControlConnection conn;
+
+	/**
+	 * Port on which the Tor node will be listening for us as its controller.
+	 */
+	protected int controlPort;
+
+	/**
+	 * Event manager to which all events concerning this node are notified.
+	 */
+	private final EventManagerImpl eventManager;
+
+	/**
+	 * Logger for this node which is called "node." plus the name of this node.
+	 */
+	protected Logger logger;
+
+	/**
+	 * Network to which this node belongs.
+	 */
+	protected NetworkImpl network;
+
+	/**
+	 * Name of this node that is used as part of the working directory, as
+	 * logger name of this node, and as event source.
+	 */
+	protected String nodeName;
+
+	/**
+	 * The state of this node.
+	 */
+	protected NodeState nodeState = NodeState.CONFIGURING;
+
+	/**
+	 * Port on which the Tor node will be listening for SOCKS connection
+	 * requests.
+	 */
+	protected int socksPort;
+
+	/**
+	 * The running Tor process that belongs to this node.
+	 */
+	protected Process torProcess;
+
+	/**
+	 * Directory in which all information concerning this node is stored.
+	 */
+	protected File workingDir;
+
+	/**
+	 * Returns this node's working directory.
+	 *
+	 * @return This node's working directory.
+	 */
+	File getWorkingDir() {
+		return workingDir;
+	}
+
+	/**
+	 * Creates a new <code>ProxyNodeImpl</code> and adds it to the given
+	 * <code>network</code>, but does not yet write its configuration to disk
+	 * or start the corresponding Tor process.
+	 *
+	 * @param network
+	 *            Network configuration to which this node belongs.
+	 * @param nodeName
+	 *            The name of the new node which may only consist of between 1
+	 *            and 19 alpha-numeric characters.
+	 * @param controlPort
+	 *            Port on which the Tor node will be listening for us as its
+	 *            controller. May not be negative or greater than 65535.
+	 * @param socksPort
+	 *            Port on which the Tor node will be listening for SOCKS
+	 *            connection requests. May not be negative or greater than
+	 *            65535.
+	 * @throws IllegalArgumentException
+	 *             If at least one of the parameters is <code>null</code> or
+	 *             has an invalid value.
+	 */
+	ProxyNodeImpl(final NetworkImpl network, final String nodeName,
+			final int controlPort, final int socksPort) {
+
+		// make sure that nodeName is a valid logger name
+		if (nodeName == null || nodeName.length() < 1 || nodeName.length() > 19
+				|| !nodeName.matches("[a-zA-Z0-9]*")) {
+			final String reason =
+					"\"" + nodeName + "\" is not a valid node name!";
+			final IllegalArgumentException e =
+					new IllegalArgumentException(reason);
+			throw e;
+		}
+
+		// create logger
+		logger = Logger.getLogger(nodeName + "." + this.getClass().getName());
+
+		logger.setLevel(Level.ALL);
+
+		// log entering
+		logger.entering(this.getClass().getName(), "ProxyNodeImpl",
+				new Object[] { network, nodeName, controlPort, socksPort });
+
+		// check remaining parameters
+		if (network == null || controlPort < 0 || controlPort > 65535
+				|| socksPort < 0 || socksPort > 65535) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "ProxyNodeImpl", e);
+			throw e;
+		}
+
+		// store parameter values
+		this.network = network;
+		this.nodeName = nodeName;
+		this.controlPort = controlPort;
+		this.socksPort = socksPort;
+
+		// obtain reference on event manager from network
+		eventManager = network.getEventManagerImpl();
+
+		// create working directory
+		workingDir =
+				new File(this.network.getWorkingDirectory().getAbsolutePath()
+						+ File.separator + nodeName + File.separator);
+		workingDir.mkdirs();
+		logger.log(Level.FINE, "Created working directory \""
+				+ workingDir.getAbsolutePath() + "\"");
+
+		// create reference on config file
+		configFile =
+				new File(workingDir.getAbsolutePath() + File.separator
+						+ "torrc");
+
+		// initialize configuration
+		configuration = new ArrayList<String>(templateConfiguration);
+		configuration.add("ControlPort " + controlPort);
+		configuration.add("SocksPort " + socksPort);
+
+		// initialize state
+		nodeState = NodeState.CONFIGURING;
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "ProxyNodeImpl");
+	}
+
+	public void addConfiguration(final String configurationString) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "addConfiguration",
+				configurationString);
+
+		// check parameter
+		if (configurationString == null || configurationString.length() < 1
+				|| !configurationString.contains(" ")) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "addConfiguration", e);
+			throw e;
+		}
+
+		// add configuration string
+		configuration.add(configurationString);
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "addConfiguration");
+	}
+
+	public void addConfigurations(final List<String> configurationStrings) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "addConfigurations",
+				configurationStrings);
+
+		// check parameter
+		if (configurationStrings == null) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "addConfigurations", e);
+			throw e;
+		}
+
+		// add configuration strings one by one
+		for (final String conf : configurationStrings) {
+			addConfiguration(conf);
+		}
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "addConfigurations");
+	}
+
+	public void replaceConfiguration(final String configurationString) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "replaceConfiguration",
+				configurationString);
+
+		// check parameter
+		if (configurationString == null || configurationString.length() < 1
+				|| !configurationString.contains(" ")) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "replaceConfiguration",
+					e);
+			throw e;
+		}
+
+		// extract configuration key
+		final String configurationKey =
+				configurationString.substring(0, configurationString
+						.indexOf(" "));
+
+		// iterate over existing configuration strings and replace the first
+		// occurrence of configuration key with new configuration string
+		final Iterator<String> it = configuration.listIterator();
+		boolean replaced = false;
+		for (int counter = 0; !replaced && it.hasNext(); counter++) {
+			final String currentConfigurationString = it.next();
+			final String currentConfigurationKey =
+					currentConfigurationString.substring(0,
+							currentConfigurationString.indexOf(" "));
+			if (currentConfigurationKey.equals(configurationKey)) {
+				configuration.set(counter, configurationString);
+				replaced = true;
+			}
+		}
+
+		// if no such configuration key was found, append the configuration
+		// string
+		if (!replaced) {
+			configuration.add(configurationString);
+		}
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "replaceConfiguration");
+	}
+
+	public void removeConfiguration(final String configurationKey) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "deleteConfiguration",
+				configurationKey);
+
+		// check parameter
+		if (configurationKey == null || configurationKey.length() < 1) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger
+					.throwing(this.getClass().getName(), "deleteConfiguration",
+							e);
+			throw e;
+		}
+
+		// iterate over existing configuration strings and remove all
+		// configuration strings that have the given configuration key
+		final List<String> configurationStringsToRemove =
+				new ArrayList<String>();
+		for (final String currentConfigurationString : configuration) {
+			final String currentConfigurationKey =
+					currentConfigurationString.substring(0,
+							currentConfigurationString.indexOf(" "));
+			if (currentConfigurationKey.equals(configurationKey)) {
+				configurationStringsToRemove.add(currentConfigurationString);
+			}
+		}
+		configuration.removeAll(configurationStringsToRemove);
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "deleteConfiguration");
+	}
+
+	public synchronized HiddenService addHiddenService(
+			final String serviceName, final int servicePort,
+			final int virtualPort) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "addHiddenService",
+				new Object[] { serviceName, servicePort, virtualPort });
+
+		// create hidden service object; parameter checking is done in
+		// constructor
+		final HiddenService result =
+				new HiddenServiceImpl(this, serviceName, servicePort,
+						virtualPort);
+
+		// add hidden service using Tor controller
+		configuration.add("HiddenServiceDir " + workingDir.getAbsolutePath()
+				+ File.separator + serviceName + "\nHiddenServicePort "
+				+ virtualPort + " 127.0.0.1:" + servicePort);
+
+		// log exiting and return hidden service object
+		logger.exiting(this.getClass().getName(), "addHiddenService", result);
+		return result;
+	}
+
+	public synchronized HiddenService addHiddenService(
+			final String serviceName, final int servicePort) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "addHiddenService",
+				new Object[] { serviceName, servicePort });
+
+		// invoke overloaded method
+		final HiddenService result =
+				this.addHiddenService(serviceName, servicePort, 80);
+
+		// log exiting and return hidden service
+		logger.exiting(this.getClass().getName(), "addHiddenService", result);
+		return result;
+	}
+
+	public synchronized HiddenService addHiddenService(final String serviceName) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "addHiddenService",
+				serviceName);
+
+		// invoke overloaded method
+		final HiddenService result =
+				this.addHiddenService(serviceName, network.getNextPortNumber(),
+						80);
+
+		// log exiting and return hidden service
+		logger.exiting(this.getClass().getName(), "addHiddenService", result);
+		return result;
+	}
+
+	public String getNodeName() {
+		return nodeName;
+	}
+
+	public synchronized NodeState getNodeState() {
+		return nodeState;
+	}
+
+	public synchronized void hup() throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "hup");
+
+		// check state
+		if (nodeState != NodeState.RUNNING || conn == null) {
+			final IllegalStateException e =
+					new IllegalStateException(
+							"Cannot hup a process when it's not running or there is "
+									+ "no connection to its control port!");
+			logger.throwing(this.getClass().getName(), "hup", e);
+			throw e;
+		}
+
+		// send HUP signal to Tor process
+		try {
+			conn.signal("HUP");
+		} catch (final IOException e) {
+			final PuppeTorException ex =
+					new PuppeTorException(
+							"Could not send the command HUP to the Tor process!",
+							e);
+			logger.throwing(this.getClass().getName(), "hup", ex);
+			throw ex;
+		}
+		// log exiting
+		logger.exiting(this.getClass().getName(), "hup");
+	}
+
+	public synchronized void shutdown() throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "shutdown");
+
+		// check state
+		if (nodeState != NodeState.RUNNING) {
+			final IllegalStateException e = new IllegalStateException();
+			logger.throwing(this.getClass().getName(), "shutdown", e);
+			throw e;
+		}
+
+		// we cannot simply kill the Tor process, because we have established a
+		// controller connection to it which would interpret a closed socket as
+		// failure and throw a RuntimeException
+		try {
+			conn.shutdownTor("SHUTDOWN");
+		} catch (final IOException e) {
+			final PuppeTorException ex =
+					new PuppeTorException(
+							"Could not send shutdown command to Tor process!",
+							e);
+			logger.throwing(this.getClass().getName(), "shutdown", ex);
+			throw ex;
+		}
+
+		// change state
+		nodeState = NodeState.SHUT_DOWN;
+
+		// fire event
+		eventManager.observeInternalEvent(System.currentTimeMillis(),
+				getNodeName(), NodeEventType.NODE_STOPPED, "Node stopped.");
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "shutdown");
+	}
+
+	/**
+	 * Helper thread that waits for a given time for a given process to
+	 * potentially terminate in order to find out if there are problems. If
+	 * either the process terminates cleanly within this timeout, or does not
+	 * terminate, the exit value will be 0; otherwise it will contain the exit
+	 * code of the terminated process. This functionality is added, because it
+	 * is not provided by Process. XXX Some stuff in here looks still dodgy.
+	 * What happens if we get an InterruptedException in run and thus don't set
+	 * exitValue?-SH
+	 */
+	private static class ProcessWaiter extends Thread {
+
+		/** The process to wait for. */
+		private final Process process;
+
+		/** The exit value or 0 if the process is still running. */
+		private final AtomicInteger exitValue;
+
+		/**
+		 * Creates a new <code>ProcessWaiter</code> for process
+		 * <code>process</code>, but does not start it, yet.
+		 *
+		 * @param process
+		 *            The process to wait for.
+		 */
+		ProcessWaiter(final Process process) {
+			this.process = process;
+			exitValue = new AtomicInteger(0);
+		}
+
+		@Override
+		public void run() {
+			try {
+				exitValue.set(process.waitFor());
+			} catch (final InterruptedException e) {}
+		}
+
+		/**
+		 * Causes the current thread to wait until the process has terminated or
+		 * the <code>timeoutInMillis</code> has expired. This method returns
+		 * immediately if the subprocess has already terminated.
+		 *
+		 * @param timeoutInMillis
+		 *            The maximum time to wait for the process to terminate.
+		 * @return The exit value of the terminated process or 0 if the process
+		 *         is still running.
+		 */
+		public int waitFor(final long timeoutInMillis) {
+			try {
+				sleep(timeoutInMillis);
+			} catch (final InterruptedException e) {}
+			interrupt();
+			return exitValue.get();
+		}
+	}
+
+	public synchronized boolean startNode(final long maximumTimeToWaitInMillis)
+			throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "startNode",
+				maximumTimeToWaitInMillis);
+
+		// check state
+		if (nodeState != NodeState.CONFIGURATION_WRITTEN) {
+			final String reason =
+					"Node is not in state "
+							+ "NodeState.CONFIGURATION_WRITTEN!";
+			final IllegalStateException e = new IllegalStateException(reason);
+			logger.throwing(this.getClass().getName(), "startNode", e);
+			throw e;
+		}
+
+		// start process
+		final ProcessBuilder processBuilder =
+				new ProcessBuilder(torExecutable.getPath(), "-f", "torrc");
+		processBuilder.directory(workingDir);
+		processBuilder.redirectErrorStream(true);
+		try {
+			torProcess = processBuilder.start();
+			logger.log(Level.FINE, "Started Tor process successfully!");
+		} catch (final IOException e) {
+			final String reason = "Could not start Tor process!";
+			final PuppeTorException ex = new PuppeTorException(reason, e);
+			logger.throwing(this.getClass().getName(), "startNode", ex);
+			throw ex;
+		}
+
+		// start thread to parse output
+		final BufferedReader br =
+				new BufferedReader(new InputStreamReader(torProcess
+						.getInputStream()));
+		final Thread outputThread = new Thread() {
+			@Override
+			public void run() {
+
+				// log entering
+				logger.entering(this.getClass().getName(), "run");
+
+				// read output from Tor to parse it
+				String line = null;
+				try {
+					while ((line = br.readLine()) != null) {
+						eventManager.observeUnparsedEvent(ProxyNodeImpl.this
+								.getNodeName(), line);
+					}
+				} catch (IOException e) {
+
+					// only print out a warning for this exception if this node
+					// is running; otherwise, silently ignore it...
+					if (getNodeState() == NodeState.RUNNING) {
+						String reason =
+								"IOException when reading output from Tor "
+										+ "process "
+										+ ProxyNodeImpl.this.getNodeName()
+										+ "!";
+						logger.log(Level.WARNING, reason, e);
+					}
+				}
+
+				// log exiting
+				logger.exiting(this.getClass().getName(), "run");
+			}
+		};
+		outputThread.setDaemon(true);
+		outputThread.setName(nodeName + " Output Parser");
+		outputThread.start();
+		logger.log(Level.FINE, "Started thread to parse output!");
+
+		// add shutdown hook that kills the process on JVM exit
+		final Process p = torProcess;
+		Runtime.getRuntime().addShutdownHook(new Thread() {
+			@Override
+			public void run() {
+
+				// log entering
+				logger.entering(this.getClass().getName(), "run");
+
+				// destroy Tor process
+				p.destroy();
+
+				// log exiting
+				logger.exiting(this.getClass().getName(), "run");
+			}
+		});
+		logger.log(Level.FINER,
+				"Started shutdown hook that will destroy the Tor process on "
+						+ "JVM exit!");
+
+		// wait to see if the process is started or exited immediately; wait for
+		// one second to be sure that Tor terminates if there is an error,
+		// especially if the computer is very busy and many nodes are created
+		final ProcessWaiter waiter = new ProcessWaiter(torProcess);
+		waiter.start();
+		final int exitValue = waiter.waitFor(1000);
+		if (exitValue != 0) {
+			// Tor did not manage to start correctly
+			logger.log(Level.WARNING, "Could not start Tor process! Tor "
+					+ "exited with exit value " + exitValue
+					+ "! Please go check the config options in " + configFile
+					+ " manually!");
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "startNode", false);
+			return false;
+		}
+
+		// wait for Tor to open the control port
+		logger.log(Level.FINER, "Waiting for Tor to open its control port...");
+		if (!eventManager.waitForAnyOccurence(nodeName,
+				NodeEventType.NODE_CONTROL_PORT_OPENED,
+				maximumTimeToWaitInMillis)) {
+
+			// Tor did not open its control port
+			logger.log(Level.WARNING, "Tor node " + nodeName
+					+ " did not manage to open its control port within "
+					+ maximumTimeToWaitInMillis + " milliseconds!");
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "startNode", false);
+			return false;
+		}
+		logger.log(Level.FINE,
+				"Tor has successfully opened its control port and told us "
+						+ "about that!");
+
+		// connect to the controller
+		logger.log(Level.FINER, "Connecting to control port...");
+		try {
+			final Socket controlSocket =
+					new java.net.Socket("127.0.0.1", controlPort);
+			conn = TorControlConnection.getConnection(controlSocket);
+			conn.authenticate(new byte[0]);
+		} catch (final IOException e) {
+			final String reason =
+					"Could not connect to control port " + controlPort + "!";
+			final PuppeTorException ex = new PuppeTorException(reason, e);
+			logger.throwing(this.getClass().getName(), "startNode", ex);
+			throw ex;
+		}
+		logger.log(Level.FINE, "Connected to control port successfully!");
+
+		// set state to RUNNING
+		nodeState = NodeState.RUNNING;
+
+		// fire event
+		eventManager.observeInternalEvent(System.currentTimeMillis(),
+				getNodeName(), NodeEventType.NODE_STARTED, "Node started.");
+
+		// log exiting and return with success
+		logger.exiting(this.getClass().getName(), "startNode", true);
+		return true;
+	}
+
+	@Override
+	public String toString() {
+		return this.getClass().getSimpleName() + ": nodeName=\"" + nodeName
+				+ "\", controlPort=" + controlPort + ", socksPort=" + socksPort;
+	}
+
+	public synchronized void writeConfiguration() throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "writeConfiguration");
+
+		// write config file
+		try {
+			final BufferedWriter bw =
+					new BufferedWriter(new FileWriter(configFile));
+			for (final String c : configuration) {
+				bw.write(c + "\n");
+			}
+			bw.close();
+		} catch (final IOException e) {
+			final PuppeTorException ex =
+					new PuppeTorException(
+							"Could not write configuration file!", e);
+			logger.throwing(this.getClass().getName(),
+					"writeConfigurationFile", ex);
+			throw ex;
+		}
+
+		// change state, if necessary
+		if (nodeState == NodeState.CONFIGURING) {
+			nodeState = NodeState.CONFIGURATION_WRITTEN;
+		}
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "writeConfiguration");
+	}
+
+	public int getSocksPort() {
+		return socksPort;
+	}
+
+	public int getControlPort() {
+		return controlPort;
+	}
+
+	public List<String> getConfiguration() {
+		return new ArrayList<String>(configuration);
+	}
+
+	/**
+	 * Template configuration of proxy nodes.
+	 */
+	static List<String> templateConfiguration;
+
+	static {
+		templateConfiguration = new ArrayList<String>();
+
+		templateConfiguration.add("DataDirectory .");
+		templateConfiguration.add("SafeLogging 0");
+		templateConfiguration.add("UseEntryGuards 0");
+
+		templateConfiguration.add("Log info stdout");
+		templateConfiguration.add("Log info file log");
+
+		// TODO This is now contained in proposal 135.
+		// templateConfiguration.add("EnforceDistinctSubnets 0");
+		// templateConfiguration.add("ClientDNSRejectInternalAddresses 0");
+	}
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/impl/RouterNodeImpl.java b/src/de/uniba/wiai/lspi/puppetor/impl/RouterNodeImpl.java
old mode 100755
new mode 100644
index 84a90e4..7a69ef7
--- a/src/de/uniba/wiai/lspi/puppetor/impl/RouterNodeImpl.java
+++ b/src/de/uniba/wiai/lspi/puppetor/impl/RouterNodeImpl.java
@@ -1,428 +1,428 @@
-/*
- * 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 de.uniba.wiai.lspi.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.util.ArrayList;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.regex.Pattern;
-
-import de.uniba.wiai.lspi.puppetor.PuppeTorException;
-import de.uniba.wiai.lspi.puppetor.RouterNode;
-
-/**
- * Implementation of <code>RouterNode</code>.
- *
- * @author kloesing
- */
-public class RouterNodeImpl extends ProxyNodeImpl implements RouterNode {
-
-	/**
-	 * Internal thread class that is used to determine fingerprints in parallel,
-	 * which can take a few seconds.
-	 */
-	private class FingerprintThread extends Thread {
-
-		@Override
-		public void run() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "run");
-
-			// create file reference for temporary config file
-			final File tempConfigFile =
-					new File(workingDir.getAbsolutePath() + File.separator
-							+ "torrc.tmp");
-
-			// compose a modified config file, including a DirServer option with
-			// false fingerprint; this is necessary, because otherwise Tor
-			// would not accept that this router node might have a private IP
-			// address, but connects to the public directory servers
-			final List<String> copyOfConfig =
-					new ArrayList<String>(configuration);
-			final String fakeDirServerString =
-					"DirServer "
-							+ nodeName
-							+ " 127.0.0.1:"
-							+ dirPort
-							+ " 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000";
-			copyOfConfig.add(fakeDirServerString);
-
-			// write config file
-			try {
-				final BufferedWriter bw =
-						new BufferedWriter(new FileWriter(tempConfigFile));
-				for (final String c : copyOfConfig) {
-					bw.write(c + "\n");
-				}
-				bw.close();
-			} catch (final IOException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not write configuration file!", e);
-				logger.log(Level.WARNING, "Could not start Tor process!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			// start process with option --list-fingerprint
-			final ProcessBuilder processBuilder =
-					new ProcessBuilder(torExecutable.getPath(),
-							"--list-fingerprint", "-f", "torrc.tmp");
-			processBuilder.directory(workingDir);
-			processBuilder.redirectErrorStream(true);
-			Process tmpProcess = null;
-			try {
-				tmpProcess = processBuilder.start();
-			} catch (final IOException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not start Tor process temporarily with "
-										+ "--list-fingerprint option!", e);
-				logger.log(Level.WARNING, "Could not start Tor process!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			// wait for process to terminate
-			int exitValue = 0;
-			try {
-				exitValue = tmpProcess.waitFor();
-			} catch (final InterruptedException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Interrupted while waiting for Tor process to exit!",
-								e);
-				logger.log(Level.WARNING,
-						"Temporary Tor process was interrupted!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			if (exitValue != 0) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not start Tor process temporarily with "
-										+ "--list-fingerprint option! Tor exited with "
-										+ "exit value "
-										+ exitValue
-										+ "! Please go check the config options in "
-										+ tempConfigFile + " manually!");
-				logger.log(Level.WARNING, "Could not start Tor process!", ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			// read fingerprint from file
-			final File fingerprintFile =
-					new File(workingDir.getAbsolutePath() + File.separator
-							+ "fingerprint");
-			try {
-				final BufferedReader br2 =
-						new BufferedReader(new FileReader(fingerprintFile));
-				setFingerprint(br2.readLine());
-				br2.close();
-			} catch (final IOException e) {
-				final PuppeTorException ex =
-						new PuppeTorException(
-								"Could not read fingerprint from file!", e);
-				logger.log(Level.WARNING, "Could not read fingerprint file!",
-						ex);
-				setCaughtException(ex);
-				return;
-			}
-
-			// delete temporary config file
-			tempConfigFile.delete();
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "run");
-		}
-	}
-
-	/**
-	 * Invoked by the fingerprint thread: sets the determined fingerprint string
-	 *
-	 * @param fingerprint
-	 *            The determined fingerprint string.
-	 */
-	private synchronized void setFingerprint(final String fingerprint) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "setFingerprint",
-				fingerprint);
-
-		// remember fingerprint and notify all waiting threads
-		this.fingerprint = fingerprint;
-		notifyAll();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "setFingerprint");
-	}
-
-	/**
-	 * Invoked by the fingerprint thread: sets the exception that occurred when
-	 * trying to determine the fingerprint.
-	 *
-	 * @param caughtException
-	 *            The exception that occurred when trying to determine the
-	 *            fingerprint.
-	 */
-	protected synchronized void setCaughtException(
-			final PuppeTorException caughtException) {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "setCaughtException",
-				caughtException);
-
-		// remember caught exception and notify all waiting threads
-		this.caughtException = caughtException;
-		notifyAll();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "setCaughtException");
-	}
-
-	/**
-	 * Port on which the Tor node will be listening for directory requests from
-	 * other Tor nodes.
-	 */
-	protected int dirPort;
-
-	/**
-	 * The IP v4 address on which the node will listen in dotted decimal
-	 * notation.
-	 */
-	protected String serverIpAddress;
-
-	/**
-	 * The fingerprint of this node that is determined as hash value of its
-	 * onion key. It is initialized with <code>null</code> and set by the
-	 * fingerprint thread as soon as it is determined.
-	 */
-	private String fingerprint;
-
-	/**
-	 * The exception that was caught when determining the fingerprint of this
-	 * node, if any.
-	 */
-	protected PuppeTorException caughtException;
-
-	/**
-	 * The pattern for valid IP v4 addresses in dotted decimal notation.
-	 */
-	private static final Pattern validIpAddressPattern =
-			Pattern
-					.compile("([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"
-							+ "(\\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){1,3}");
-
-	/**
-	 * Port on which the Tor node will be listening for onion requests by other
-	 * Tor nodes.
-	 */
-	protected int orPort;
-
-	/**
-	 * Creates a new <code>RouterNodeImpl</code> and adds it to the given
-	 * <code>network</code>, but does not yet write its configuration to disk
-	 * or start the corresponding Tor process.
-	 *
-	 * @param network
-	 *            Network configuration to which this node belongs.
-	 * @param nodeName
-	 *            The name of the new node which may only consist of between 1
-	 *            and 19 alpha-numeric characters.
-	 * @param controlPort
-	 *            Port on which the Tor node will be listening for us as its
-	 *            controller. May not be negative or greater than 65535.
-	 * @param socksPort
-	 *            Port on which the Tor node will be listening for SOCKS
-	 *            connection requests. May not be negative or greater than
-	 *            65535.
-	 * @param orPort
-	 *            Port on which the Tor node will be listening for onion
-	 *            requests by other Tor nodes. May not be negative or greater
-	 *            than 65535.
-	 * @param dirPort
-	 *            Port on which the Tor node will be listening for directory
-	 *            requests from other Tor nodes. May not be negative or greater
-	 *            than 65535.
-	 * @param serverIpAddress
-	 *            The IP address on which the node will listen. Must be a valid
-	 *            IP v4 address in dotted decimal notation. May not be
-	 *            <code>null</code>.
-	 * @throws IllegalArgumentException
-	 *             If at least one of the parameters is <code>null</code> or
-	 *             has an invalid value.
-	 */
-	RouterNodeImpl(final NetworkImpl network, final String nodeName,
-			final int controlPort, final int socksPort, final int orPort,
-			final int dirPort, final String serverIpAddress) {
-
-		// create superclass instance; parameter checking is done in super
-		// constructor
-		super(network, nodeName, controlPort, socksPort);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "RouterNodeImpl",
-				new Object[] { network, nodeName, controlPort, socksPort,
-						orPort, dirPort, serverIpAddress });
-
-		// check parameters
-		if (orPort < 0 || orPort > 65535 || dirPort < 0 || dirPort > 65535
-				|| serverIpAddress == null
-				|| !validIpAddressPattern.matcher(serverIpAddress).matches()) {
-			final IllegalArgumentException e =
-					new IllegalArgumentException("nodeName=" + nodeName
-							+ ", controlPort=" + controlPort + ", socksPort="
-							+ socksPort + ", orPort=" + orPort + ", dirPort="
-							+ dirPort + ", serverIpAddress='" + serverIpAddress
-							+ "'");
-			logger.throwing(this.getClass().getName(), "RouterNodeImpl", e);
-			throw e;
-		}
-
-		// remember parameters
-		this.orPort = orPort;
-		this.dirPort = dirPort;
-		this.serverIpAddress = serverIpAddress;
-
-		// extend configuration by template configuration of router nodes
-		configuration.addAll(templateConfiguration);
-
-		// add further configuration to make this node a router node
-		configuration.add("ORPort " + orPort);
-		configuration.add("Nickname " + nodeName);
-
-		// all routers mirror the directory
-		configuration.add("DirPort " + dirPort);
-
-		// the address of this node should be manually specified and not guessed
-		// by Tor
-		configuration.add("Address " + serverIpAddress);
-
-		// the OR port may only be contacted locally
-		configuration.add("ORListenAddress " + serverIpAddress);
-
-		// offer directory only locally (either by being an authority, or by
-		// mirroring it)
-		configuration.add("DirListenAddress " + serverIpAddress);
-
-		// start a thread to determine the node's fingerprint in the background
-		determineFingerprint();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "RouterNodeImpl");
-	}
-
-	public synchronized String getFingerprint() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "getFingerprint");
-
-		// wait until either the fingerprint has been determined or an exception
-		// was caught
-		while (fingerprint == null && caughtException == null) {
-			try {
-				wait();
-			} catch (final InterruptedException e) {
-				// do nothing
-			}
-		}
-
-		if (caughtException != null) {
-			logger.throwing(this.getClass().getName(), "getFingerprint",
-					caughtException);
-			throw caughtException;
-		}
-
-		// log exiting
-		logger
-				.exiting(this.getClass().getName(), "getFingerprint",
-						fingerprint);
-		return fingerprint;
-	}
-
-	@Override
-	public String toString() {
-		return super.toString() + ", orPort=" + orPort + ", dirPort=" + dirPort;
-	}
-
-	public int getDirPort() {
-		return dirPort;
-	}
-
-	public int getOrPort() {
-		return orPort;
-	}
-
-	/**
-	 * Determines the fingerprint of this node by starting a background thread
-	 * that performs this operation.
-	 */
-	protected synchronized void determineFingerprint() {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "determineFingerprint");
-
-		// start a thread to determine this node's fingerprint
-		final FingerprintThread fingerprintThread = new FingerprintThread();
-		fingerprintThread.setName(nodeName + " Fingerprint Resolver");
-		fingerprintThread.start();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "determineFingerprint");
-	}
-
-	/**
-	 * Template configuration of router nodes.
-	 */
-	static List<String> templateConfiguration;
-
-	static {
-		templateConfiguration = new ArrayList<String>();
-
-		templateConfiguration.add("ContactInfo wont@xxxxxxxxx");
-		templateConfiguration.add("HidServDirectoryV2 1");
-
-		// TODO This is now contained in proposal 135.
-		// templateConfiguration.add("ExitPolicyRejectPrivate 0");
-		// templateConfiguration.add("AssumeReachable 1");
-		// templateConfiguration.add("ServerDNSAllowBrokenResolvConf 1");
-	}
-}
+/*
+ * 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 de.uniba.wiai.lspi.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.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.regex.Pattern;
+
+import de.uniba.wiai.lspi.puppetor.PuppeTorException;
+import de.uniba.wiai.lspi.puppetor.RouterNode;
+
+/**
+ * Implementation of <code>RouterNode</code>.
+ *
+ * @author kloesing
+ */
+public class RouterNodeImpl extends ProxyNodeImpl implements RouterNode {
+
+	/**
+	 * Internal thread class that is used to determine fingerprints in parallel,
+	 * which can take a few seconds.
+	 */
+	private class FingerprintThread extends Thread {
+
+		@Override
+		public void run() {
+
+			// log entering
+			logger.entering(this.getClass().getName(), "run");
+
+			// create file reference for temporary config file
+			final File tempConfigFile =
+					new File(workingDir.getAbsolutePath() + File.separator
+							+ "torrc.tmp");
+
+			// compose a modified config file, including a DirServer option with
+			// false fingerprint; this is necessary, because otherwise Tor
+			// would not accept that this router node might have a private IP
+			// address, but connects to the public directory servers
+			final List<String> copyOfConfig =
+					new ArrayList<String>(configuration);
+			final String fakeDirServerString =
+					"DirServer "
+							+ nodeName
+							+ " 127.0.0.1:"
+							+ dirPort
+							+ " 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000";
+			copyOfConfig.add(fakeDirServerString);
+
+			// write config file
+			try {
+				final BufferedWriter bw =
+						new BufferedWriter(new FileWriter(tempConfigFile));
+				for (final String c : copyOfConfig) {
+					bw.write(c + "\n");
+				}
+				bw.close();
+			} catch (final IOException e) {
+				final PuppeTorException ex =
+						new PuppeTorException(
+								"Could not write configuration file!", e);
+				logger.log(Level.WARNING, "Could not start Tor process!", ex);
+				setCaughtException(ex);
+				return;
+			}
+
+			// start process with option --list-fingerprint
+			final ProcessBuilder processBuilder =
+					new ProcessBuilder(torExecutable.getPath(),
+							"--list-fingerprint", "-f", "torrc.tmp");
+			processBuilder.directory(workingDir);
+			processBuilder.redirectErrorStream(true);
+			Process tmpProcess = null;
+			try {
+				tmpProcess = processBuilder.start();
+			} catch (final IOException e) {
+				final PuppeTorException ex =
+						new PuppeTorException(
+								"Could not start Tor process temporarily with "
+										+ "--list-fingerprint option!", e);
+				logger.log(Level.WARNING, "Could not start Tor process!", ex);
+				setCaughtException(ex);
+				return;
+			}
+
+			// wait for process to terminate
+			int exitValue = 0;
+			try {
+				exitValue = tmpProcess.waitFor();
+			} catch (final InterruptedException e) {
+				final PuppeTorException ex =
+						new PuppeTorException(
+								"Interrupted while waiting for Tor process to exit!",
+								e);
+				logger.log(Level.WARNING,
+						"Temporary Tor process was interrupted!", ex);
+				setCaughtException(ex);
+				return;
+			}
+
+			if (exitValue != 0) {
+				final PuppeTorException ex =
+						new PuppeTorException(
+								"Could not start Tor process temporarily with "
+										+ "--list-fingerprint option! Tor exited with "
+										+ "exit value "
+										+ exitValue
+										+ "! Please go check the config options in "
+										+ tempConfigFile + " manually!");
+				logger.log(Level.WARNING, "Could not start Tor process!", ex);
+				setCaughtException(ex);
+				return;
+			}
+
+			// read fingerprint from file
+			final File fingerprintFile =
+					new File(workingDir.getAbsolutePath() + File.separator
+							+ "fingerprint");
+			try {
+				final BufferedReader br2 =
+						new BufferedReader(new FileReader(fingerprintFile));
+				setFingerprint(br2.readLine());
+				br2.close();
+			} catch (final IOException e) {
+				final PuppeTorException ex =
+						new PuppeTorException(
+								"Could not read fingerprint from file!", e);
+				logger.log(Level.WARNING, "Could not read fingerprint file!",
+						ex);
+				setCaughtException(ex);
+				return;
+			}
+
+			// delete temporary config file
+			tempConfigFile.delete();
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "run");
+		}
+	}
+
+	/**
+	 * Invoked by the fingerprint thread: sets the determined fingerprint string
+	 *
+	 * @param fingerprint
+	 *            The determined fingerprint string.
+	 */
+	private synchronized void setFingerprint(final String fingerprint) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "setFingerprint",
+				fingerprint);
+
+		// remember fingerprint and notify all waiting threads
+		this.fingerprint = fingerprint;
+		notifyAll();
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "setFingerprint");
+	}
+
+	/**
+	 * Invoked by the fingerprint thread: sets the exception that occurred when
+	 * trying to determine the fingerprint.
+	 *
+	 * @param caughtException
+	 *            The exception that occurred when trying to determine the
+	 *            fingerprint.
+	 */
+	protected synchronized void setCaughtException(
+			final PuppeTorException caughtException) {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "setCaughtException",
+				caughtException);
+
+		// remember caught exception and notify all waiting threads
+		this.caughtException = caughtException;
+		notifyAll();
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "setCaughtException");
+	}
+
+	/**
+	 * Port on which the Tor node will be listening for directory requests from
+	 * other Tor nodes.
+	 */
+	protected int dirPort;
+
+	/**
+	 * The IP v4 address on which the node will listen in dotted decimal
+	 * notation.
+	 */
+	protected String serverIpAddress;
+
+	/**
+	 * The fingerprint of this node that is determined as hash value of its
+	 * onion key. It is initialized with <code>null</code> and set by the
+	 * fingerprint thread as soon as it is determined.
+	 */
+	private String fingerprint;
+
+	/**
+	 * The exception that was caught when determining the fingerprint of this
+	 * node, if any.
+	 */
+	protected PuppeTorException caughtException;
+
+	/**
+	 * The pattern for valid IP v4 addresses in dotted decimal notation.
+	 */
+	private static final Pattern validIpAddressPattern =
+			Pattern
+					.compile("([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"
+							+ "(\\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){1,3}");
+
+	/**
+	 * Port on which the Tor node will be listening for onion requests by other
+	 * Tor nodes.
+	 */
+	protected int orPort;
+
+	/**
+	 * Creates a new <code>RouterNodeImpl</code> and adds it to the given
+	 * <code>network</code>, but does not yet write its configuration to disk
+	 * or start the corresponding Tor process.
+	 *
+	 * @param network
+	 *            Network configuration to which this node belongs.
+	 * @param nodeName
+	 *            The name of the new node which may only consist of between 1
+	 *            and 19 alpha-numeric characters.
+	 * @param controlPort
+	 *            Port on which the Tor node will be listening for us as its
+	 *            controller. May not be negative or greater than 65535.
+	 * @param socksPort
+	 *            Port on which the Tor node will be listening for SOCKS
+	 *            connection requests. May not be negative or greater than
+	 *            65535.
+	 * @param orPort
+	 *            Port on which the Tor node will be listening for onion
+	 *            requests by other Tor nodes. May not be negative or greater
+	 *            than 65535.
+	 * @param dirPort
+	 *            Port on which the Tor node will be listening for directory
+	 *            requests from other Tor nodes. May not be negative or greater
+	 *            than 65535.
+	 * @param serverIpAddress
+	 *            The IP address on which the node will listen. Must be a valid
+	 *            IP v4 address in dotted decimal notation. May not be
+	 *            <code>null</code>.
+	 * @throws IllegalArgumentException
+	 *             If at least one of the parameters is <code>null</code> or
+	 *             has an invalid value.
+	 */
+	RouterNodeImpl(final NetworkImpl network, final String nodeName,
+			final int controlPort, final int socksPort, final int orPort,
+			final int dirPort, final String serverIpAddress) {
+
+		// create superclass instance; parameter checking is done in super
+		// constructor
+		super(network, nodeName, controlPort, socksPort);
+
+		// log entering
+		logger.entering(this.getClass().getName(), "RouterNodeImpl",
+				new Object[] { network, nodeName, controlPort, socksPort,
+						orPort, dirPort, serverIpAddress });
+
+		// check parameters
+		if (orPort < 0 || orPort > 65535 || dirPort < 0 || dirPort > 65535
+				|| serverIpAddress == null
+				|| !validIpAddressPattern.matcher(serverIpAddress).matches()) {
+			final IllegalArgumentException e =
+					new IllegalArgumentException("nodeName=" + nodeName
+							+ ", controlPort=" + controlPort + ", socksPort="
+							+ socksPort + ", orPort=" + orPort + ", dirPort="
+							+ dirPort + ", serverIpAddress='" + serverIpAddress
+							+ "'");
+			logger.throwing(this.getClass().getName(), "RouterNodeImpl", e);
+			throw e;
+		}
+
+		// remember parameters
+		this.orPort = orPort;
+		this.dirPort = dirPort;
+		this.serverIpAddress = serverIpAddress;
+
+		// extend configuration by template configuration of router nodes
+		configuration.addAll(templateConfiguration);
+
+		// add further configuration to make this node a router node
+		configuration.add("ORPort " + orPort);
+		configuration.add("Nickname " + nodeName);
+
+		// all routers mirror the directory
+		configuration.add("DirPort " + dirPort);
+
+		// the address of this node should be manually specified and not guessed
+		// by Tor
+		configuration.add("Address " + serverIpAddress);
+
+		// the OR port may only be contacted locally
+		configuration.add("ORListenAddress " + serverIpAddress);
+
+		// offer directory only locally (either by being an authority, or by
+		// mirroring it)
+		configuration.add("DirListenAddress " + serverIpAddress);
+
+		// start a thread to determine the node's fingerprint in the background
+		determineFingerprint();
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "RouterNodeImpl");
+	}
+
+	public synchronized String getFingerprint() throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "getFingerprint");
+
+		// wait until either the fingerprint has been determined or an exception
+		// was caught
+		while (fingerprint == null && caughtException == null) {
+			try {
+				wait();
+			} catch (final InterruptedException e) {
+				// do nothing
+			}
+		}
+
+		if (caughtException != null) {
+			logger.throwing(this.getClass().getName(), "getFingerprint",
+					caughtException);
+			throw caughtException;
+		}
+
+		// log exiting
+		logger
+				.exiting(this.getClass().getName(), "getFingerprint",
+						fingerprint);
+		return fingerprint;
+	}
+
+	@Override
+	public String toString() {
+		return super.toString() + ", orPort=" + orPort + ", dirPort=" + dirPort;
+	}
+
+	public int getDirPort() {
+		return dirPort;
+	}
+
+	public int getOrPort() {
+		return orPort;
+	}
+
+	/**
+	 * Determines the fingerprint of this node by starting a background thread
+	 * that performs this operation.
+	 */
+	protected synchronized void determineFingerprint() {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "determineFingerprint");
+
+		// start a thread to determine this node's fingerprint
+		final FingerprintThread fingerprintThread = new FingerprintThread();
+		fingerprintThread.setName(nodeName + " Fingerprint Resolver");
+		fingerprintThread.start();
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "determineFingerprint");
+	}
+
+	/**
+	 * Template configuration of router nodes.
+	 */
+	static List<String> templateConfiguration;
+
+	static {
+		templateConfiguration = new ArrayList<String>();
+
+		templateConfiguration.add("ContactInfo wont@xxxxxxxxx");
+		templateConfiguration.add("HidServDirectoryV2 1");
+
+		// TODO This is now contained in proposal 135.
+		// templateConfiguration.add("ExitPolicyRejectPrivate 0");
+		// templateConfiguration.add("AssumeReachable 1");
+		// templateConfiguration.add("ServerDNSAllowBrokenResolvConf 1");
+	}
+}
diff --git a/src/de/uniba/wiai/lspi/puppetor/impl/ServerApplicationImpl.java b/src/de/uniba/wiai/lspi/puppetor/impl/ServerApplicationImpl.java
old mode 100755
new mode 100644
index 86dbcfb..ee0e2e6
--- a/src/de/uniba/wiai/lspi/puppetor/impl/ServerApplicationImpl.java
+++ b/src/de/uniba/wiai/lspi/puppetor/impl/ServerApplicationImpl.java
@@ -1,377 +1,377 @@
-/*
- * 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 de.uniba.wiai.lspi.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.util.logging.Level;
-import java.util.logging.Logger;
-
-import de.uniba.wiai.lspi.puppetor.ServerApplication;
-import de.uniba.wiai.lspi.puppetor.ServerEventType;
-
-/**
- * Implementation of <code>ServerApplication</code>.
- *
- * @author kloesing
- */
-public class ServerApplicationImpl implements ServerApplication {
-
-	/**
-	 * Internal thread class that is used to process an incoming request.
-	 */
-	private class HandlerThread extends Thread {
-
-		/**
-		 * Accepted socket on which the request came in.
-		 */
-		private Socket handleSocket = null;
-
-		/**
-		 * Creates a new thread to handle the request coming in on
-		 * <code>handleSocket</code>, but does not start handling it.
-		 *
-		 * @param handleSocket
-		 *            Accepted socket on which the request came in.
-		 */
-		public HandlerThread(final Socket handleSocket) {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "HandlerThread",
-					handleSocket);
-
-			// remember parameter
-			this.handleSocket = handleSocket;
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "HandlerThread");
-		}
-
-		@Override
-		public void run() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "run");
-
-			try {
-
-				// wait for request (don't mind the content)
-				final BufferedReader in =
-						new BufferedReader(new InputStreamReader(handleSocket
-								.getInputStream()));
-				in.read();
-
-				// send event to event manager
-				eventManager.observeInternalEvent(System.currentTimeMillis(),
-						getServerApplicationName(),
-						ServerEventType.SERVER_RECEIVING_REQUEST_SENDING_REPLY,
-						"Receiving request.");
-
-				// write response
-				final PrintStream out =
-						new PrintStream(handleSocket.getOutputStream());
-				out.print("HTTP/1.0 200 OK\r\n");
-
-			} catch (final IOException e) {
-				logger.log(Level.SEVERE,
-						"I/O exception while handling incoming request!");
-				// we can't do more, because nobody takes notice of this thread.
-
-				// log exiting
-				logger.exiting(this.getClass().getName(), "run");
-				return;
-				// TODO do we need more?
-			}
-
-			// close socket
-			try {
-				handleSocket.close();
-			} catch (final IOException e) {
-				logger
-						.log(Level.WARNING,
-								"I/O exception while closing socket!");
-
-				// log exiting
-				logger.exiting(this.getClass().getName(), "run");
-				return;
-			}
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "run");
-		}
-	}
-
-	/**
-	 * Internal thread class that is used to listen for requests.
-	 */
-	private class ListenThread extends Thread {
-
-		/**
-		 * Flag to remember whether this thread listens for requests at the
-		 * moment (<code>true</code>), or has been stopped (<code>false</code>).
-		 */
-		private boolean connected;
-
-		/**
-		 * Creates a new thread to listen for requests, but does not start
-		 * listening, yet.
-		 */
-		ListenThread() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "ListenThread");
-
-			// start connected
-			connected = true;
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "ListenThread");
-		}
-
-		@Override
-		public void run() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "run");
-
-			try {
-
-				// create server socket
-				ServerSocket serverSocket = null;
-				try {
-					serverSocket = new ServerSocket(serverPort);
-				} catch (final IOException ioe) {
-					logger.log(Level.SEVERE,
-							"Can't open server socket on port " + serverPort
-									+ "!");
-
-					// log exiting
-					logger.exiting(this.getClass().getName(), "run");
-					return;
-				}
-
-				// as long as we are connected, accept incoming requests
-				logger.log(Level.FINE, "Listening on port " + serverPort
-						+ "...");
-				while (connected) {
-					Socket incomingConnection = null;
-					try {
-						incomingConnection = serverSocket.accept();
-					} catch (final Exception e) {
-						logger
-								.log(
-										Level.SEVERE,
-										"Exception while accepting socket requests! Stopping listening!",
-										e);
-						break;
-					}
-					new HandlerThread(incomingConnection).start();
-				}
-
-			} catch (final Exception e) {
-
-				// log that we have been interrupted
-				logger.log(Level.WARNING, "Server has been interrupted!", e);
-			}
-
-			// mark as disconnected
-			connected = false;
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "run");
-		}
-
-		/**
-		 * Stops listening on server socket.
-		 */
-		public void stopListening() {
-
-			// log entering
-			logger.entering(this.getClass().getName(), "stopListening");
-
-			// change connected state to false and interrupt thread
-			connected = false;
-			interrupt();
-
-			// log exiting
-			logger.exiting(this.getClass().getName(), "stopListening");
-		}
-	}
-
-	/**
-	 * Event manager that handles all events concerning this server application.
-	 */
-	private final EventManagerImpl eventManager;
-
-	/**
-	 * Logger for this server which is called "server." plus the name of this
-	 * server application.
-	 */
-	private final Logger logger;
-
-	/**
-	 * Name of this server application that is used as logger name of this node.
-	 */
-	private final String serverApplicationName;
-
-	/**
-	 * Port on which this server will listen for incoming requests.
-	 */
-	private final int serverPort;
-
-	/**
-	 * Thread that listens for requests in the background.
-	 */
-	private Thread serverThread;
-
-	/**
-	 * Creates a new HTTP server application within this JVM, but does not yet
-	 * listen for incoming requests.
-	 *
-	 * @param network
-	 *            Network to which this HTTP server belongs; at the moment this
-	 *            is only used to determine the event manager instance.
-	 * @param serverApplicationName
-	 *            Name of this server that is used as part of the logger name.
-	 * @param serverPort
-	 *            Port on which this server will listen for incoming requests.
-	 * @throws IllegalArgumentException
-	 *             If at least one of the parameters is <code>null</code> or
-	 *             has an invalid value.
-	 */
-	ServerApplicationImpl(final NetworkImpl network,
-			final String serverApplicationName, final int serverPort) {
-
-		// check if serverApplicationName can be used as logger name
-		if (serverApplicationName == null
-				|| serverApplicationName.length() == 0) {
-			throw new IllegalArgumentException(
-					"Invalid serverApplicationName: " + serverApplicationName);
-		}
-
-		// create logger
-		logger = Logger.getLogger("server." + serverApplicationName);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "ServerApplicationImpl",
-				new Object[] { network, serverApplicationName, serverPort });
-
-		// check parameters
-		if (network == null || serverPort < 0 || serverPort > 65535) {
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "ServerApplicationImpl",
-					e);
-			throw e;
-		}
-
-		// remember parameters
-		this.serverApplicationName = serverApplicationName;
-		this.serverPort = serverPort;
-
-		// obtain reference on event manager
-		eventManager = network.getEventManagerImpl();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "ServerApplicationImpl");
-	}
-
-	public synchronized void startListening() {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "listen");
-
-		// check if we are already listening
-		if (serverThread != null) {
-			final IllegalStateException e =
-					new IllegalStateException("We are already listening!");
-			logger.throwing(this.getClass().getName(), "listen", e);
-			throw e;
-		}
-
-		// create a thread that listens in the background
-		serverThread = new ListenThread();
-		serverThread.setName("Reply Thread");
-		serverThread.setDaemon(true);
-		serverThread.start();
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "listen");
-	}
-
-	public synchronized void stopListening() {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "stopListening");
-
-		// check if we are listening
-		if (serverThread == null) {
-			final IllegalStateException e =
-					new IllegalStateException("We are not listening!");
-			logger.throwing(this.getClass().getName(), "stopListening", e);
-			throw e;
-		}
-
-		// log this event
-		logger.log(Level.FINE, "Shutting down server");
-
-		// interrupt thread
-		serverThread.interrupt();
-
-		// unset listening thread
-		serverThread = null;
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "stopListening");
-	}
-
-	public synchronized boolean isListening() {
-		return serverThread != null;
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": serverApplicationName=\""
-				+ serverApplicationName + "\", serverPort=" + serverPort;
-	}
-
-	public String getServerApplicationName() {
-		return serverApplicationName;
-	}
-
-	public int getServerPort() {
-		return serverPort;
-	}
-}
+/*
+ * 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 de.uniba.wiai.lspi.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.util.logging.Level;
+import java.util.logging.Logger;
+
+import de.uniba.wiai.lspi.puppetor.ServerApplication;
+import de.uniba.wiai.lspi.puppetor.ServerEventType;
+
+/**
+ * Implementation of <code>ServerApplication</code>.
+ *
+ * @author kloesing
+ */
+public class ServerApplicationImpl implements ServerApplication {
+
+	/**
+	 * Internal thread class that is used to process an incoming request.
+	 */
+	private class HandlerThread extends Thread {
+
+		/**
+		 * Accepted socket on which the request came in.
+		 */
+		private Socket handleSocket = null;
+
+		/**
+		 * Creates a new thread to handle the request coming in on
+		 * <code>handleSocket</code>, but does not start handling it.
+		 *
+		 * @param handleSocket
+		 *            Accepted socket on which the request came in.
+		 */
+		public HandlerThread(final Socket handleSocket) {
+
+			// log entering
+			logger.entering(this.getClass().getName(), "HandlerThread",
+					handleSocket);
+
+			// remember parameter
+			this.handleSocket = handleSocket;
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "HandlerThread");
+		}
+
+		@Override
+		public void run() {
+
+			// log entering
+			logger.entering(this.getClass().getName(), "run");
+
+			try {
+
+				// wait for request (don't mind the content)
+				final BufferedReader in =
+						new BufferedReader(new InputStreamReader(handleSocket
+								.getInputStream()));
+				in.read();
+
+				// send event to event manager
+				eventManager.observeInternalEvent(System.currentTimeMillis(),
+						getServerApplicationName(),
+						ServerEventType.SERVER_RECEIVING_REQUEST_SENDING_REPLY,
+						"Receiving request.");
+
+				// write response
+				final PrintStream out =
+						new PrintStream(handleSocket.getOutputStream());
+				out.print("HTTP/1.0 200 OK\r\n");
+
+			} catch (final IOException e) {
+				logger.log(Level.SEVERE,
+						"I/O exception while handling incoming request!");
+				// we can't do more, because nobody takes notice of this thread.
+
+				// log exiting
+				logger.exiting(this.getClass().getName(), "run");
+				return;
+				// TODO do we need more?
+			}
+
+			// close socket
+			try {
+				handleSocket.close();
+			} catch (final IOException e) {
+				logger
+						.log(Level.WARNING,
+								"I/O exception while closing socket!");
+
+				// log exiting
+				logger.exiting(this.getClass().getName(), "run");
+				return;
+			}
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "run");
+		}
+	}
+
+	/**
+	 * Internal thread class that is used to listen for requests.
+	 */
+	private class ListenThread extends Thread {
+
+		/**
+		 * Flag to remember whether this thread listens for requests at the
+		 * moment (<code>true</code>), or has been stopped (<code>false</code>).
+		 */
+		private boolean connected;
+
+		/**
+		 * Creates a new thread to listen for requests, but does not start
+		 * listening, yet.
+		 */
+		ListenThread() {
+
+			// log entering
+			logger.entering(this.getClass().getName(), "ListenThread");
+
+			// start connected
+			connected = true;
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "ListenThread");
+		}
+
+		@Override
+		public void run() {
+
+			// log entering
+			logger.entering(this.getClass().getName(), "run");
+
+			try {
+
+				// create server socket
+				ServerSocket serverSocket = null;
+				try {
+					serverSocket = new ServerSocket(serverPort);
+				} catch (final IOException ioe) {
+					logger.log(Level.SEVERE,
+							"Can't open server socket on port " + serverPort
+									+ "!");
+
+					// log exiting
+					logger.exiting(this.getClass().getName(), "run");
+					return;
+				}
+
+				// as long as we are connected, accept incoming requests
+				logger.log(Level.FINE, "Listening on port " + serverPort
+						+ "...");
+				while (connected) {
+					Socket incomingConnection = null;
+					try {
+						incomingConnection = serverSocket.accept();
+					} catch (final Exception e) {
+						logger
+								.log(
+										Level.SEVERE,
+										"Exception while accepting socket requests! Stopping listening!",
+										e);
+						break;
+					}
+					new HandlerThread(incomingConnection).start();
+				}
+
+			} catch (final Exception e) {
+
+				// log that we have been interrupted
+				logger.log(Level.WARNING, "Server has been interrupted!", e);
+			}
+
+			// mark as disconnected
+			connected = false;
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "run");
+		}
+
+		/**
+		 * Stops listening on server socket.
+		 */
+		public void stopListening() {
+
+			// log entering
+			logger.entering(this.getClass().getName(), "stopListening");
+
+			// change connected state to false and interrupt thread
+			connected = false;
+			interrupt();
+
+			// log exiting
+			logger.exiting(this.getClass().getName(), "stopListening");
+		}
+	}
+
+	/**
+	 * Event manager that handles all events concerning this server application.
+	 */
+	private final EventManagerImpl eventManager;
+
+	/**
+	 * Logger for this server which is called "server." plus the name of this
+	 * server application.
+	 */
+	private final Logger logger;
+
+	/**
+	 * Name of this server application that is used as logger name of this node.
+	 */
+	private final String serverApplicationName;
+
+	/**
+	 * Port on which this server will listen for incoming requests.
+	 */
+	private final int serverPort;
+
+	/**
+	 * Thread that listens for requests in the background.
+	 */
+	private Thread serverThread;
+
+	/**
+	 * Creates a new HTTP server application within this JVM, but does not yet
+	 * listen for incoming requests.
+	 *
+	 * @param network
+	 *            Network to which this HTTP server belongs; at the moment this
+	 *            is only used to determine the event manager instance.
+	 * @param serverApplicationName
+	 *            Name of this server that is used as part of the logger name.
+	 * @param serverPort
+	 *            Port on which this server will listen for incoming requests.
+	 * @throws IllegalArgumentException
+	 *             If at least one of the parameters is <code>null</code> or
+	 *             has an invalid value.
+	 */
+	ServerApplicationImpl(final NetworkImpl network,
+			final String serverApplicationName, final int serverPort) {
+
+		// check if serverApplicationName can be used as logger name
+		if (serverApplicationName == null
+				|| serverApplicationName.length() == 0) {
+			throw new IllegalArgumentException(
+					"Invalid serverApplicationName: " + serverApplicationName);
+		}
+
+		// create logger
+		logger = Logger.getLogger("server." + serverApplicationName);
+
+		// log entering
+		logger.entering(this.getClass().getName(), "ServerApplicationImpl",
+				new Object[] { network, serverApplicationName, serverPort });
+
+		// check parameters
+		if (network == null || serverPort < 0 || serverPort > 65535) {
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "ServerApplicationImpl",
+					e);
+			throw e;
+		}
+
+		// remember parameters
+		this.serverApplicationName = serverApplicationName;
+		this.serverPort = serverPort;
+
+		// obtain reference on event manager
+		eventManager = network.getEventManagerImpl();
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "ServerApplicationImpl");
+	}
+
+	public synchronized void startListening() {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "listen");
+
+		// check if we are already listening
+		if (serverThread != null) {
+			final IllegalStateException e =
+					new IllegalStateException("We are already listening!");
+			logger.throwing(this.getClass().getName(), "listen", e);
+			throw e;
+		}
+
+		// create a thread that listens in the background
+		serverThread = new ListenThread();
+		serverThread.setName("Reply Thread");
+		serverThread.setDaemon(true);
+		serverThread.start();
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "listen");
+	}
+
+	public synchronized void stopListening() {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "stopListening");
+
+		// check if we are listening
+		if (serverThread == null) {
+			final IllegalStateException e =
+					new IllegalStateException("We are not listening!");
+			logger.throwing(this.getClass().getName(), "stopListening", e);
+			throw e;
+		}
+
+		// log this event
+		logger.log(Level.FINE, "Shutting down server");
+
+		// interrupt thread
+		serverThread.interrupt();
+
+		// unset listening thread
+		serverThread = null;
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "stopListening");
+	}
+
+	public synchronized boolean isListening() {
+		return serverThread != null;
+	}
+
+	@Override
+	public String toString() {
+		return this.getClass().getSimpleName() + ": serverApplicationName=\""
+				+ serverApplicationName + "\", serverPort=" + serverPort;
+	}
+
+	public String getServerApplicationName() {
+		return serverApplicationName;
+	}
+
+	public int getServerPort() {
+		return serverPort;
+	}
+}
diff --git a/tools/create_keystores.sh b/tools/create_keystores.sh
old mode 100755
new mode 100644
-- 
1.5.6.5