[or-cvs] [puppetor/gsoc2008] Change the package name to org.torproject.puppetor.

Sebastian Hahn sebastian at torproject.org
Fri Feb 20 05:13:11 UTC 2009


The old name was related to the University of Bamberg where Karsten used to
work.
---
 doc/DistributedPuppeTorReadme                      |    4 +-
 doc/howto.tex                                      |    2 +-
 doc/howtosecurermi.txt                             |    4 +-
 .../wiai/lspi/puppetor/ClientApplication.java      |  100 --
 .../uniba/wiai/lspi/puppetor/ClientEventType.java  |   64 --
 src/de/uniba/wiai/lspi/puppetor/DirectoryNode.java |   49 -
 src/de/uniba/wiai/lspi/puppetor/Event.java         |   54 -
 src/de/uniba/wiai/lspi/puppetor/EventListener.java |   25 -
 src/de/uniba/wiai/lspi/puppetor/EventManager.java  |  231 -----
 src/de/uniba/wiai/lspi/puppetor/EventType.java     |   26 -
 src/de/uniba/wiai/lspi/puppetor/HiddenService.java |   61 --
 .../wiai/lspi/puppetor/HiddenServiceEventType.java |  215 ----
 src/de/uniba/wiai/lspi/puppetor/Network.java       |  657 ------------
 .../uniba/wiai/lspi/puppetor/NetworkFactory.java   |   82 --
 src/de/uniba/wiai/lspi/puppetor/NodeEventType.java |   60 --
 src/de/uniba/wiai/lspi/puppetor/NodeState.java     |   44 -
 src/de/uniba/wiai/lspi/puppetor/ProxyNode.java     |  269 -----
 .../wiai/lspi/puppetor/PuppeTorException.java      |   65 --
 src/de/uniba/wiai/lspi/puppetor/RouterNode.java    |   55 -
 .../wiai/lspi/puppetor/ServerApplication.java      |   58 --
 .../uniba/wiai/lspi/puppetor/ServerEventType.java  |   41 -
 .../examples/AccessingPublicWebServerOverTor.java  |  119 ---
 ...ccessingHiddenServiceOverPrivateTorNetwork.java |  132 ---
 ...AccessingHiddenServiceOverPublicTorNetwork.java |  149 ---
 ...AdvertisingHiddenServiceToPublicTorNetwork.java |  110 --
 .../lspi/puppetor/impl/ClientApplicationImpl.java  |  457 ---------
 .../wiai/lspi/puppetor/impl/DirectoryNodeImpl.java |  431 --------
 .../uniba/wiai/lspi/puppetor/impl/EventImpl.java   |  122 ---
 .../wiai/lspi/puppetor/impl/EventManagerImpl.java  |  729 -------------
 .../wiai/lspi/puppetor/impl/HiddenServiceImpl.java |  166 ---
 .../uniba/wiai/lspi/puppetor/impl/NetworkImpl.java | 1082 -------------------
 .../wiai/lspi/puppetor/impl/ProxyNodeImpl.java     |  734 -------------
 .../wiai/lspi/puppetor/impl/RouterNodeImpl.java    |  402 --------
 .../lspi/puppetor/impl/ServerApplicationImpl.java  |  351 -------
 .../lspi/puppetor/rmi/AbstractMasterFactory.java   |   78 --
 .../lspi/puppetor/rmi/AbstractSlaveFactory.java    |   88 --
 .../uniba/wiai/lspi/puppetor/rmi/LocalMaster.java  |   36 -
 src/de/uniba/wiai/lspi/puppetor/rmi/Master.java    |   15 -
 .../wiai/lspi/puppetor/rmi/MasterConnector.java    |   31 -
 src/de/uniba/wiai/lspi/puppetor/rmi/Network.java   |   79 --
 .../wiai/lspi/puppetor/rmi/NetworkDescription.java |   16 -
 .../uniba/wiai/lspi/puppetor/rmi/RemoteMaster.java |   47 -
 src/de/uniba/wiai/lspi/puppetor/rmi/Slave.java     |   51 -
 src/de/uniba/wiai/lspi/puppetor/rmi/Task.java      |   38 -
 .../rmi/TaskExecutionNotSuccessfulException.java   |   23 -
 .../uniba/wiai/lspi/puppetor/rmi/TaskResult.java   |   39 -
 src/de/uniba/wiai/lspi/puppetor/rmi/Test.java      |   37 -
 .../uniba/wiai/lspi/puppetor/rmi/TestExecutor.java |  114 --
 .../uniba/wiai/lspi/puppetor/rmi/TestResult.java   |   15 -
 .../uniba/wiai/lspi/puppetor/rmi/TorInstance.java  |   57 -
 .../rmi/execute/PuppeTorMasterProgram.java         |  193 ----
 .../puppetor/rmi/execute/PuppeTorSlaveProgram.java |  215 ----
 .../lspi/puppetor/rmi/impl/AbstractTaskImpl.java   |   61 --
 .../lspi/puppetor/rmi/impl/AbstractTestImpl.java   |   52 -
 .../wiai/lspi/puppetor/rmi/impl/MasterImpl.java    |  104 --
 .../lspi/puppetor/rmi/impl/MasterImplFactory.java  |   55 -
 .../puppetor/rmi/impl/NetworkDescriptionImpl.java  |   43 -
 .../wiai/lspi/puppetor/rmi/impl/NetworkImpl.java   |  125 ---
 .../wiai/lspi/puppetor/rmi/impl/SlaveImpl.java     |  131 ---
 .../lspi/puppetor/rmi/impl/SlaveImplFactory.java   |   31 -
 .../lspi/puppetor/rmi/impl/TaskResultImpl.java     |   51 -
 .../lspi/puppetor/rmi/impl/TestExecutorImpl.java   |  554 ----------
 .../lspi/puppetor/rmi/impl/TorInstanceImpl.java    |  174 ----
 .../lspi/puppetor/rmi/tasks/AccessGoogleTask.java  |   73 --
 .../puppetor/rmi/tasks/AddHiddenServiceTask.java   |  105 --
 .../lspi/puppetor/rmi/tasks/BuildCircuitsTask.java |   40 -
 .../puppetor/rmi/tasks/ConfigureAsPrivateTask.java |   49 -
 .../rmi/tasks/CreateDirectoryAuthorityTask.java    |   47 -
 .../lspi/puppetor/rmi/tasks/CreateNetworkTask.java |   30 -
 .../lspi/puppetor/rmi/tasks/CreateProxyTask.java   |   47 -
 .../lspi/puppetor/rmi/tasks/CreateRouterTask.java  |   47 -
 .../lspi/puppetor/rmi/tasks/ShutdownNodesTask.java |   37 -
 .../lspi/puppetor/rmi/tasks/StartNodesTask.java    |   41 -
 .../lspi/puppetor/rmi/tasks/TerminateTask.java     |   29 -
 .../tests/AccessGoogleOverPublicTorNetwork.java    |   39 -
 ...singHiddenServiceOverPrivateTorNetworkTest.java |   40 -
 .../lspi/puppetor/rmi/tests/TestRegistration.java  |   33 -
 src/org/torproject/puppetor/ClientApplication.java |  100 ++
 src/org/torproject/puppetor/ClientEventType.java   |   64 ++
 src/org/torproject/puppetor/DirectoryNode.java     |   49 +
 src/org/torproject/puppetor/Event.java             |   54 +
 src/org/torproject/puppetor/EventListener.java     |   25 +
 src/org/torproject/puppetor/EventManager.java      |  231 +++++
 src/org/torproject/puppetor/EventType.java         |   26 +
 src/org/torproject/puppetor/HiddenService.java     |   61 ++
 .../puppetor/HiddenServiceEventType.java           |  215 ++++
 src/org/torproject/puppetor/Network.java           |  657 ++++++++++++
 src/org/torproject/puppetor/NetworkFactory.java    |   83 ++
 src/org/torproject/puppetor/NodeEventType.java     |   60 ++
 src/org/torproject/puppetor/NodeState.java         |   44 +
 src/org/torproject/puppetor/ProxyNode.java         |  269 +++++
 src/org/torproject/puppetor/PuppeTorException.java |   65 ++
 src/org/torproject/puppetor/RouterNode.java        |   55 +
 src/org/torproject/puppetor/ServerApplication.java |   58 ++
 src/org/torproject/puppetor/ServerEventType.java   |   41 +
 .../examples/AccessingPublicWebServerOverTor.java  |  119 +++
 ...ccessingHiddenServiceOverPrivateTorNetwork.java |  132 +++
 ...AccessingHiddenServiceOverPublicTorNetwork.java |  149 +++
 ...AdvertisingHiddenServiceToPublicTorNetwork.java |  110 ++
 .../puppetor/impl/ClientApplicationImpl.java       |  458 +++++++++
 .../puppetor/impl/DirectoryNodeImpl.java           |  432 ++++++++
 src/org/torproject/puppetor/impl/EventImpl.java    |  123 +++
 .../torproject/puppetor/impl/EventManagerImpl.java |  730 +++++++++++++
 .../puppetor/impl/HiddenServiceImpl.java           |  167 +++
 src/org/torproject/puppetor/impl/NetworkImpl.java  | 1083 ++++++++++++++++++++
 .../torproject/puppetor/impl/ProxyNodeImpl.java    |  735 +++++++++++++
 .../torproject/puppetor/impl/RouterNodeImpl.java   |  403 ++++++++
 .../puppetor/impl/ServerApplicationImpl.java       |  352 +++++++
 .../puppetor/rmi/AbstractMasterFactory.java        |   79 ++
 .../puppetor/rmi/AbstractSlaveFactory.java         |   89 ++
 src/org/torproject/puppetor/rmi/LocalMaster.java   |   36 +
 src/org/torproject/puppetor/rmi/Master.java        |   15 +
 .../torproject/puppetor/rmi/MasterConnector.java   |   31 +
 src/org/torproject/puppetor/rmi/Network.java       |   79 ++
 .../puppetor/rmi/NetworkDescription.java           |   16 +
 src/org/torproject/puppetor/rmi/RemoteMaster.java  |   47 +
 src/org/torproject/puppetor/rmi/Slave.java         |   51 +
 src/org/torproject/puppetor/rmi/Task.java          |   38 +
 .../rmi/TaskExecutionNotSuccessfulException.java   |   23 +
 src/org/torproject/puppetor/rmi/TaskResult.java    |   39 +
 src/org/torproject/puppetor/rmi/Test.java          |   37 +
 src/org/torproject/puppetor/rmi/TestExecutor.java  |  114 ++
 src/org/torproject/puppetor/rmi/TestResult.java    |   15 +
 src/org/torproject/puppetor/rmi/TorInstance.java   |   57 +
 .../rmi/execute/PuppeTorMasterProgram.java         |  194 ++++
 .../puppetor/rmi/execute/PuppeTorSlaveProgram.java |  216 ++++
 .../puppetor/rmi/impl/AbstractTaskImpl.java        |   62 ++
 .../puppetor/rmi/impl/AbstractTestImpl.java        |   52 +
 .../torproject/puppetor/rmi/impl/MasterImpl.java   |  105 ++
 .../puppetor/rmi/impl/MasterImplFactory.java       |   56 +
 .../puppetor/rmi/impl/NetworkDescriptionImpl.java  |   44 +
 .../torproject/puppetor/rmi/impl/NetworkImpl.java  |  126 +++
 .../torproject/puppetor/rmi/impl/SlaveImpl.java    |  132 +++
 .../puppetor/rmi/impl/SlaveImplFactory.java        |   32 +
 .../puppetor/rmi/impl/TaskResultImpl.java          |   51 +
 .../puppetor/rmi/impl/TestExecutorImpl.java        |  555 ++++++++++
 .../puppetor/rmi/impl/TorInstanceImpl.java         |  175 ++++
 .../puppetor/rmi/tasks/AccessGoogleTask.java       |   74 ++
 .../puppetor/rmi/tasks/AddHiddenServiceTask.java   |  106 ++
 .../puppetor/rmi/tasks/BuildCircuitsTask.java      |   41 +
 .../puppetor/rmi/tasks/ConfigureAsPrivateTask.java |   50 +
 .../rmi/tasks/CreateDirectoryAuthorityTask.java    |   48 +
 .../puppetor/rmi/tasks/CreateNetworkTask.java      |   31 +
 .../puppetor/rmi/tasks/CreateProxyTask.java        |   48 +
 .../puppetor/rmi/tasks/CreateRouterTask.java       |   48 +
 .../puppetor/rmi/tasks/ShutdownNodesTask.java      |   38 +
 .../puppetor/rmi/tasks/StartNodesTask.java         |   42 +
 .../puppetor/rmi/tasks/TerminateTask.java          |   30 +
 .../tests/AccessGoogleOverPublicTorNetwork.java    |   40 +
 ...singHiddenServiceOverPrivateTorNetworkTest.java |   41 +
 .../puppetor/rmi/tests/TestRegistration.java       |   33 +
 151 files changed, 10391 insertions(+), 10355 deletions(-)
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/ClientApplication.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/ClientEventType.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/DirectoryNode.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/Event.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/EventListener.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/EventManager.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/EventType.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/HiddenService.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/HiddenServiceEventType.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/Network.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/NetworkFactory.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/NodeEventType.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/NodeState.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/ProxyNode.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/PuppeTorException.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/RouterNode.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/ServerApplication.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/ServerEventType.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/examples/AccessingPublicWebServerOverTor.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/impl/ClientApplicationImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/impl/DirectoryNodeImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/impl/EventImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/impl/EventManagerImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/impl/HiddenServiceImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/impl/NetworkImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/impl/ProxyNodeImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/impl/RouterNodeImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/impl/ServerApplicationImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/AbstractMasterFactory.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/AbstractSlaveFactory.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/LocalMaster.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/Master.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/MasterConnector.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/Network.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/NetworkDescription.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/RemoteMaster.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/Slave.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/Task.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/TaskExecutionNotSuccessfulException.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/TaskResult.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/Test.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/TestExecutor.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/TestResult.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/TorInstance.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/execute/PuppeTorMasterProgram.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/execute/PuppeTorSlaveProgram.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/impl/AbstractTaskImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/impl/AbstractTestImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/impl/MasterImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/impl/MasterImplFactory.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/impl/NetworkDescriptionImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/impl/NetworkImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/impl/SlaveImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/impl/SlaveImplFactory.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/impl/TaskResultImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/impl/TestExecutorImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/impl/TorInstanceImpl.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tasks/AccessGoogleTask.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tasks/AddHiddenServiceTask.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tasks/BuildCircuitsTask.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tasks/ConfigureAsPrivateTask.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateDirectoryAuthorityTask.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateNetworkTask.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateProxyTask.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateRouterTask.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tasks/ShutdownNodesTask.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tasks/StartNodesTask.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tasks/TerminateTask.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tests/AccessGoogleOverPublicTorNetwork.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tests/StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest.java
 delete mode 100644 src/de/uniba/wiai/lspi/puppetor/rmi/tests/TestRegistration.java
 create mode 100644 src/org/torproject/puppetor/ClientApplication.java
 create mode 100644 src/org/torproject/puppetor/ClientEventType.java
 create mode 100644 src/org/torproject/puppetor/DirectoryNode.java
 create mode 100644 src/org/torproject/puppetor/Event.java
 create mode 100644 src/org/torproject/puppetor/EventListener.java
 create mode 100644 src/org/torproject/puppetor/EventManager.java
 create mode 100644 src/org/torproject/puppetor/EventType.java
 create mode 100644 src/org/torproject/puppetor/HiddenService.java
 create mode 100644 src/org/torproject/puppetor/HiddenServiceEventType.java
 create mode 100644 src/org/torproject/puppetor/Network.java
 create mode 100644 src/org/torproject/puppetor/NetworkFactory.java
 create mode 100644 src/org/torproject/puppetor/NodeEventType.java
 create mode 100644 src/org/torproject/puppetor/NodeState.java
 create mode 100644 src/org/torproject/puppetor/ProxyNode.java
 create mode 100644 src/org/torproject/puppetor/PuppeTorException.java
 create mode 100644 src/org/torproject/puppetor/RouterNode.java
 create mode 100644 src/org/torproject/puppetor/ServerApplication.java
 create mode 100644 src/org/torproject/puppetor/ServerEventType.java
 create mode 100644 src/org/torproject/puppetor/examples/AccessingPublicWebServerOverTor.java
 create mode 100644 src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
 create mode 100644 src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
 create mode 100644 src/org/torproject/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
 create mode 100644 src/org/torproject/puppetor/impl/ClientApplicationImpl.java
 create mode 100644 src/org/torproject/puppetor/impl/DirectoryNodeImpl.java
 create mode 100644 src/org/torproject/puppetor/impl/EventImpl.java
 create mode 100644 src/org/torproject/puppetor/impl/EventManagerImpl.java
 create mode 100644 src/org/torproject/puppetor/impl/HiddenServiceImpl.java
 create mode 100644 src/org/torproject/puppetor/impl/NetworkImpl.java
 create mode 100644 src/org/torproject/puppetor/impl/ProxyNodeImpl.java
 create mode 100644 src/org/torproject/puppetor/impl/RouterNodeImpl.java
 create mode 100644 src/org/torproject/puppetor/impl/ServerApplicationImpl.java
 create mode 100644 src/org/torproject/puppetor/rmi/AbstractMasterFactory.java
 create mode 100644 src/org/torproject/puppetor/rmi/AbstractSlaveFactory.java
 create mode 100644 src/org/torproject/puppetor/rmi/LocalMaster.java
 create mode 100644 src/org/torproject/puppetor/rmi/Master.java
 create mode 100644 src/org/torproject/puppetor/rmi/MasterConnector.java
 create mode 100644 src/org/torproject/puppetor/rmi/Network.java
 create mode 100644 src/org/torproject/puppetor/rmi/NetworkDescription.java
 create mode 100644 src/org/torproject/puppetor/rmi/RemoteMaster.java
 create mode 100644 src/org/torproject/puppetor/rmi/Slave.java
 create mode 100644 src/org/torproject/puppetor/rmi/Task.java
 create mode 100644 src/org/torproject/puppetor/rmi/TaskExecutionNotSuccessfulException.java
 create mode 100644 src/org/torproject/puppetor/rmi/TaskResult.java
 create mode 100644 src/org/torproject/puppetor/rmi/Test.java
 create mode 100644 src/org/torproject/puppetor/rmi/TestExecutor.java
 create mode 100644 src/org/torproject/puppetor/rmi/TestResult.java
 create mode 100644 src/org/torproject/puppetor/rmi/TorInstance.java
 create mode 100644 src/org/torproject/puppetor/rmi/execute/PuppeTorMasterProgram.java
 create mode 100644 src/org/torproject/puppetor/rmi/execute/PuppeTorSlaveProgram.java
 create mode 100644 src/org/torproject/puppetor/rmi/impl/AbstractTaskImpl.java
 create mode 100644 src/org/torproject/puppetor/rmi/impl/AbstractTestImpl.java
 create mode 100644 src/org/torproject/puppetor/rmi/impl/MasterImpl.java
 create mode 100644 src/org/torproject/puppetor/rmi/impl/MasterImplFactory.java
 create mode 100644 src/org/torproject/puppetor/rmi/impl/NetworkDescriptionImpl.java
 create mode 100644 src/org/torproject/puppetor/rmi/impl/NetworkImpl.java
 create mode 100644 src/org/torproject/puppetor/rmi/impl/SlaveImpl.java
 create mode 100644 src/org/torproject/puppetor/rmi/impl/SlaveImplFactory.java
 create mode 100644 src/org/torproject/puppetor/rmi/impl/TaskResultImpl.java
 create mode 100644 src/org/torproject/puppetor/rmi/impl/TestExecutorImpl.java
 create mode 100644 src/org/torproject/puppetor/rmi/impl/TorInstanceImpl.java
 create mode 100644 src/org/torproject/puppetor/rmi/tasks/AccessGoogleTask.java
 create mode 100644 src/org/torproject/puppetor/rmi/tasks/AddHiddenServiceTask.java
 create mode 100644 src/org/torproject/puppetor/rmi/tasks/BuildCircuitsTask.java
 create mode 100644 src/org/torproject/puppetor/rmi/tasks/ConfigureAsPrivateTask.java
 create mode 100644 src/org/torproject/puppetor/rmi/tasks/CreateDirectoryAuthorityTask.java
 create mode 100644 src/org/torproject/puppetor/rmi/tasks/CreateNetworkTask.java
 create mode 100644 src/org/torproject/puppetor/rmi/tasks/CreateProxyTask.java
 create mode 100644 src/org/torproject/puppetor/rmi/tasks/CreateRouterTask.java
 create mode 100644 src/org/torproject/puppetor/rmi/tasks/ShutdownNodesTask.java
 create mode 100644 src/org/torproject/puppetor/rmi/tasks/StartNodesTask.java
 create mode 100644 src/org/torproject/puppetor/rmi/tasks/TerminateTask.java
 create mode 100644 src/org/torproject/puppetor/rmi/tests/AccessGoogleOverPublicTorNetwork.java
 create mode 100644 src/org/torproject/puppetor/rmi/tests/StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest.java
 create mode 100644 src/org/torproject/puppetor/rmi/tests/TestRegistration.java

diff --git a/doc/DistributedPuppeTorReadme b/doc/DistributedPuppeTorReadme
index 62588da..98c2f2a 100644
--- a/doc/DistributedPuppeTorReadme
+++ b/doc/DistributedPuppeTorReadme
@@ -48,7 +48,7 @@ computer that runs the master can also run a slave without a problem).
 
 To start testing, go ahead and start all the Slaves. Go to the puppetor
 directory, and simply execute
-	java -cp bin/ de.uniba.wiai.lspi.puppetor.rmi.execute.PuppeTorSlaveProgram
+	java -cp bin/ org.torproject.puppetor.rmi.execute.PuppeTorSlaveProgram
 This will run the PuppeTorSlaveProgram in a loop that tries connecting to the
 specified master once a minute. After it has made a connection, it will go back
 to this mode of continuously trying to reach the master, so you don't have to
@@ -58,7 +58,7 @@ large. Make sure you clean it occasionally.
 
 After all the slaves are started, fire up the Master by going into the puppetor
 directory, and executing
-	java -cp bin/ de.uniba.wiai.lspi.puppetor.rmi.execute.PuppeTorMasterProgram
+	java -cp bin/ org.torproject.puppetor.rmi.execute.PuppeTorMasterProgram
 
 You will see some output at the master to indicate connected slaves and the
 success of the execution of tests. Note that there are no good logging
diff --git a/doc/howto.tex b/doc/howto.tex
index dbe4d4d..8ddd951 100644
--- a/doc/howto.tex
+++ b/doc/howto.tex
@@ -109,7 +109,7 @@ base directory of this framework:
 
 \begin{verbatim}
 java -cp bin:lib/torctl.jar
-    de.uniba.wiai.lspi.puppetor.examples.
+    org.torproject.puppetor.examples.
     AccessingPublicWebServerOverTor
 \end{verbatim}
 
diff --git a/doc/howtosecurermi.txt b/doc/howtosecurermi.txt
index ccb8164..dffb918 100644
--- a/doc/howtosecurermi.txt
+++ b/doc/howtosecurermi.txt
@@ -30,12 +30,12 @@ Setting up the application:
   everything into bin/. Then you can start the master like this:
   
     java -cp bin/:lib/torctl.jar \
-    de.uniba.wiai.lspi.puppetor.rmi.execute.PuppeTorMasterProgram
+    org.torproject.puppetor.rmi.execute.PuppeTorMasterProgram
 
   and the slave like this:
 
     java -cp bin/:lib/torctl.jar \
-    de.uniba.wiai.lspi.puppetor.rmi.execute.PuppeTorSlaveProgram
+    org.torproject.puppetor.rmi.execute.PuppeTorSlaveProgram
     
   Optionally, add the option "-Djavax.net.debug=all" to receive debug output in
   the console window.
diff --git a/src/de/uniba/wiai/lspi/puppetor/ClientApplication.java b/src/de/uniba/wiai/lspi/puppetor/ClientApplication.java
deleted file mode 100644
index c6de5b6..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/ClientApplication.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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/ClientEventType.java b/src/de/uniba/wiai/lspi/puppetor/ClientEventType.java
deleted file mode 100644
index d6df5f3..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/ClientEventType.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor;
-
-/**
- * Event types that can be fired by a client application running as thread in
- * the background.
- */
- at SuppressWarnings("serial")
-public class ClientEventType implements EventType {
-
-	/**
-	 * String identifying the type of the event type.
-	 */
-	String typeString;
-
-	/**
-	 * Creates a new event type with the given type string.
-	 *
-	 * @param typeString
-	 *            String identifying the type of the event type.
-	 */
-	public ClientEventType(final String typeString) {
-		this.typeString = typeString;
-	}
-
-	public String getTypeName() {
-		return typeString;
-	}
-
-	/**
-	 * The client application is sending a request; this event is fired
-	 * internally and not parsed from a log statement from Tor.
-	 */
-	public static final ClientEventType CLIENT_SENDING_REQUEST =
-			new ClientEventType("CLIENT_SENDING_REQUEST");
-
-	/**
-	 * The client application has received a reply to a previously sent request;
-	 * this event is fired internally and not parsed from a log statement from
-	 * Tor.
-	 */
-	public static final ClientEventType CLIENT_REPLY_RECEIVED =
-			new ClientEventType("CLIENT_REPLY_RECEIVED");
-
-	/**
-	 * The client application has given up waiting for the reply to a previously
-	 * sent request; this event is fired internally and not parsed from a log
-	 * statement from Tor.
-	 */
-	public static final ClientEventType CLIENT_GAVE_UP_REQUEST =
-			new ClientEventType("CLIENT_GAVE_UP_REQUEST");
-
-	/**
-	 * The client application has completed a series of requests, whether they
-	 * were successful or not; this event is fired internally and not parsed
-	 * from a log statement from Tor.
-	 */
-	public static final ClientEventType CLIENT_REQUESTS_PERFORMED =
-			new ClientEventType("CLIENT_REQUESTS_PERFORMED");
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/DirectoryNode.java b/src/de/uniba/wiai/lspi/puppetor/DirectoryNode.java
deleted file mode 100644
index 37f363a..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/DirectoryNode.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
deleted file mode 100644
index 3cfa8c8..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/Event.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor;
-
-import java.io.Serializable;
-
-/**
- * An <code>Event</code> is created for every state change of an asynchronous
- * system component, e.g. a Tor process or a client/server application running
- * as thread in the background. In contrast to <code>NodeState</code> an
- * <code>Event</code> cannot be a pre- or postconditions for a method
- * invocation. There is no prescribed order in which events are fired by a
- * certain process or application. Some events can be fired only once, others
- * possibly multiple times. All management operations for events are contained
- * in the <code>EventManager</code>.
- *
- * @author kloesing
- */
-public interface Event extends Serializable {
-
-	/**
-	 * Returns the name of the source of this event, which can be a Tor process
-	 * or a client/server application running as thread in the background.
-	 *
-	 * @return The event source.
-	 */
-	public abstract String getSource();
-
-	/**
-	 * Returns the event type.
-	 *
-	 * @return The event type.
-	 */
-	public abstract EventType getType();
-
-	/**
-	 * Returns the original message that lead to firing this event.
-	 *
-	 * @return The original message.
-	 */
-	public abstract String getMessage();
-
-	/**
-	 * Returns the occurrence time of this event, which is either the parsed
-	 * time from a Tor log statement, or the current system time when a
-	 * client/server application fired this event.
-	 *
-	 * @return The occurrence time.
-	 */
-	public abstract long getOccurrenceTime();
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/EventListener.java b/src/de/uniba/wiai/lspi/puppetor/EventListener.java
deleted file mode 100644
index 3473d20..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/EventListener.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor;
-
-/**
- * This interface must be implemented by any object in a test application that
- * shall be registered as event listener.
- *
- * @author kloesing
- */
-public interface EventListener {
-
-	/**
-	 * Is invoked when an asynchronous event is fired by the source (or one of
-	 * the sources) for which this listener was registered.
-	 *
-	 * @param event
-	 *            The event that was fired.
-	 */
-	public void handleEvent(Event event);
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/EventManager.java b/src/de/uniba/wiai/lspi/puppetor/EventManager.java
deleted file mode 100644
index 761d1a9..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/EventManager.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
deleted file mode 100644
index 5d57da9..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/EventType.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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/HiddenService.java b/src/de/uniba/wiai/lspi/puppetor/HiddenService.java
deleted file mode 100644
index 5978b41..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/HiddenService.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor;
-
-/**
- * A <code>HiddenService</code> instance contains all configurations of a
- * hidden service that is registered at a node.
- *
- * @author kloesing
- */
-public interface HiddenService {
-
-	/**
-	 * Determines the onion address for a previously added hidden service with
-	 * name <code>serviceName</code>. Requires that the node has been
-	 * started, i.e. is in state <code>NodeState.RUNNING</code>, and is
-	 * configured to provide this hidden service.
-	 *
-	 * @return The onion address string consisting of 16 base32 chars plus
-	 *         ".onion" for hidden service versions 0 and 1 or 16 base32 chars
-	 *         plus "." plus 24 base32 chars plus ".onion" for hidden service
-	 *         version 2.
-	 * @throws IllegalArgumentException
-	 *             Thrown if <code>null</code> or a zero-length string is
-	 *             passed as parameter.
-	 * @throws IllegalStateException
-	 *             Thrown if the node at which this hidden service is configured
-	 *             is not in state <code>NodeState.RUNNING</code>.
-	 * @throws PuppeTorException
-	 *             Thrown if the onion address of this hidden service could not
-	 *             be read, which is also the case when the node's configuration
-	 *             has not been written and the node has not been HUP'ed after
-	 *             configuring the hidden service.
-	 */
-	public abstract String determineOnionAddress() throws PuppeTorException;
-
-	/**
-	 * Returns the name of the hidden service.
-	 *
-	 * @return The name of the hidden service.
-	 */
-	public String getServiceName();
-
-	/**
-	 * Returns the port on which the service listens for requests.
-	 *
-	 * @return The port on which the service listens for requests.
-	 */
-	public int getServicePort();
-
-	/**
-	 * Returns the virtual port that this hidden service runs on as it is
-	 * announced to clients.
-	 *
-	 * @return The virtual port of this hidden service.
-	 */
-	public int getVirtualPort();
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/HiddenServiceEventType.java b/src/de/uniba/wiai/lspi/puppetor/HiddenServiceEventType.java
deleted file mode 100644
index 3ef03fe..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/HiddenServiceEventType.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor;
-
-/**
- * Event types that can be fired by all Tor processes performing hidden-service
- * operations.
- */
- at SuppressWarnings("serial")
-public class HiddenServiceEventType implements EventType {
-
-	/**
-	 * String identifying the type of the event type.
-	 */
-	String typeString;
-
-	/**
-	 * Creates a new event type with the given type string.
-	 *
-	 * @param typeString
-	 *            String identifying the type of the event type.
-	 */
-	public HiddenServiceEventType(final String typeString) {
-		this.typeString = typeString;
-	}
-
-	public String getTypeName() {
-		return typeString;
-	}
-
-	/**
-	 * Alice has received an onion request; this event is parsed from a log
-	 * statement in connection_ap_handshake_rewrite_and_attach().
-	 */
-	public static final HiddenServiceEventType ALICE_ONION_REQUEST_RECEIVED =
-			new HiddenServiceEventType("ALICE_ONION_REQUEST_RECEIVED");
-
-	/**
-	 * Alice sends a fetch request for a hidden service descriptor to a
-	 * directory server; this event is parsed from a log statement in
-	 * rend_client_refetch_renddesc().
-	 */
-	public static final HiddenServiceEventType ALICE_SENDING_FETCH_DESC =
-			new HiddenServiceEventType("ALICE_SENDING_FETCH_DESC");
-
-	/**
-	 * Alice receives a reply to a previous fetch request for a hidden service
-	 * descriptors from a directory server; this event is parsed from a log
-	 * statement in connection_dir_client_reached_eof().
-	 */
-	public static final HiddenServiceEventType ALICE_DESC_FETCHED_RECEIVED =
-			new HiddenServiceEventType("ALICE_DESC_FETCHED_RECEIVED");
-
-	/**
-	 * Alice has built a circuit to a rendezvous point and sends an
-	 * ESTABLISH_RENDEZVOUS cell; this event is parsed from a log statement in
-	 * rend_client_send_establish_rendezvous().
-	 */
-	public static final HiddenServiceEventType ALICE_BUILT_REND_CIRC_SENDING_ESTABLISH_RENDEZVOUS =
-			new HiddenServiceEventType(
-					"ALICE_BUILT_REND_CIRC_SENDING_ESTABLISH_RENDEZVOUS");
-
-	/**
-	 * Alice receives a RENDEZVOUS_ESTABLISHED cell from a rendezvous point;
-	 * this event is parsed from a log statement in
-	 * rend_client_rendezvous_acked().
-	 */
-	public static final HiddenServiceEventType ALICE_RENDEZVOUS_ESTABLISHED_RECEIVED =
-			new HiddenServiceEventType("ALICE_RENDEZVOUS_ESTABLISHED_RECEIVED");
-
-	/**
-	 * Alice has built a circuit to an introduction point (which does not
-	 * automatically lead to sending an INTRODUCE1 cell, because the rendezvous
-	 * circuit might not be ready); this event is parsed from a log statement in
-	 * rend_client_introcirc_has_opened().
-	 */
-	public static final HiddenServiceEventType ALICE_BUILT_INTRO_CIRC =
-			new HiddenServiceEventType("ALICE_BUILT_INTRO_CIRC");
-
-	/**
-	 * Alice sends an INTRODUCE1 cell to an introduction point; this event is
-	 * parsed from a log statement in rend_client_send_introduction().
-	 */
-	public static final HiddenServiceEventType ALICE_SENDING_INTRODUCE1 =
-			new HiddenServiceEventType("ALICE_SENDING_INTRODUCE1");
-
-	/**
-	 * Alice has received an INTRODUCE_ACK cell as an acknowledgement to a
-	 * previously sent INTRODUCE1 cell; this event is parsed from a log
-	 * statement in rend_client_introduction_acked().
-	 */
-	public static final HiddenServiceEventType ALICE_INTRODUCE_ACK_RECEIVED =
-			new HiddenServiceEventType("ALICE_INTRODUCE_ACK_RECEIVED");
-
-	/**
-	 * Alice has received a RENDEZVOUS2 cell and can now open an application
-	 * connection to the client; this event is parsed from a log statement in
-	 * rend_client_receive_rendezvous().
-	 */
-	public static final HiddenServiceEventType ALICE_RENDEZVOUS2_RECEIVED_APP_CONN_OPENED =
-			new HiddenServiceEventType(
-					"ALICE_RENDEZVOUS2_RECEIVED_APP_CONN_OPENED");
-
-	/**
-	 * Bob has built a circuit to an introduction point and sends an
-	 * ESTABLISH_INTRO cell; this event is parsed from a log statement in
-	 * rend_service_intro_has_opened().
-	 */
-	public static final HiddenServiceEventType BOB_BUILT_INTRO_CIRC_SENDING_ESTABLISH_INTRO =
-			new HiddenServiceEventType(
-					"BOB_BUILT_INTRO_CIRC_SENDING_ESTABLISH_INTRO");
-
-	/**
-	 * Bob has received an INTRO_ESTABLISHED cell, i.e. a node has confirmed to
-	 * work as introduction point; this event is parsed from a log statement in
-	 * rend_service_intro_established().
-	 */
-	public static final HiddenServiceEventType BOB_INTRO_ESTABLISHED_RECEIVED =
-			new HiddenServiceEventType("BOB_INTRO_ESTABLISHED_RECEIVED");
-
-	/**
-	 * Bob posts a hidden service descriptor to the directory servers; this
-	 * event is parsed from a log statement in upload_service_descriptor().
-	 */
-	public static final HiddenServiceEventType BOB_SENDING_PUBLISH_DESC =
-			new HiddenServiceEventType("BOB_SENDING_PUBLISH_DESC");
-
-	/**
-	 * Bob received a response from a directory server to a previous publish
-	 * request; this event is parsed from a log statement in
-	 * connection_dir_client_reached_eof().
-	 */
-	public static final HiddenServiceEventType BOB_DESC_PUBLISHED_RECEIVED =
-			new HiddenServiceEventType("BOB_DESC_PUBLISHED_RECEIVED");
-
-	/**
-	 * Bob has received an INTRODUCE2 cell, i.e. a node wants to establish a
-	 * connection, and will now try to establish a circuit to the client's
-	 * rendezvous point; this event is parsed from a log statement in
-	 * rend_service_introduce().
-	 */
-	public static final HiddenServiceEventType BOB_INTRODUCE2_RECEIVED =
-			new HiddenServiceEventType("BOB_INTRODUCE2_RECEIVED");
-
-	/**
-	 * Bob has built a circuit to a rendezvous point and sends a RENDEZVOUS1
-	 * cell; this event is parsed from a log statement in
-	 * rend_service_rendezvous_has_opened().
-	 */
-	public static final HiddenServiceEventType BOB_BUILT_REND_CIRC_SENDING_RENDEZVOUS1 =
-			new HiddenServiceEventType(
-					"BOB_BUILT_REND_CIRC_SENDING_RENDEZVOUS1");
-
-	/**
-	 * Bob opens a connection to the actual hidden server; this event is parsed
-	 * from a log statement in connection_exit_begin_conn().
-	 */
-	public static final HiddenServiceEventType BOB_APP_CONN_OPENED =
-			new HiddenServiceEventType("BOB_APP_CONN_OPENED");
-
-	/**
-	 * The directory server has received a descriptor post request; this event
-	 * is parsed from a log statement in directory_handle_command_post().
-	 */
-	public static final HiddenServiceEventType DIR_PUBLISH_DESC_RECEIVED =
-			new HiddenServiceEventType("DIR_PUBLISH_DESC_RECEIVED");
-
-	/**
-	 * The directory server has received a descriptor fetch request; this event
-	 * is parsed from a log statement in directory_handle_command_get().
-	 */
-	public static final HiddenServiceEventType DIR_FETCH_DESC_RECEIVED =
-			new HiddenServiceEventType("DIR_FETCH_DESC_RECEIVED");
-
-	/**
-	 * The node received an ESTABLISH_INTRO cell, i.e. was requested to work as
-	 * introduction point, and replies with an INTRO_ESTABLISHED cell; this
-	 * event is parsed from a log statement in rend_mid_establish_intro().
-	 */
-	public static final HiddenServiceEventType IPO_RECEIVED_ESTABLISH_INTRO_SENDING_INTRO_ESTABLISHED =
-			new HiddenServiceEventType(
-					"IPO_RECEIVED_ESTABLISH_INTRO_SENDING_INTRO_ESTABLISHED");
-
-	/**
-	 * The introduction point received an INTRODUCE1 cell and reacts by sending
-	 * an INTRODUCE2 cell to Bob and an INTRODUCE_ACK cell to Alice; this event
-	 * is parsed from a log statement in rend_mid_introduce().
-	 */
-	public static final HiddenServiceEventType IPO_RECEIVED_INTRODUCE1_SENDING_INTRODUCE2_AND_INTRODUCE_ACK =
-			new HiddenServiceEventType(
-					"IPO_RECEIVED_INTRODUCE1_SENDING_INTRODUCE2_AND_INTRODUCE_ACK");
-
-	/**
-	 * The node received an ESTABLISH_RENDEZVOUS cell, i.e. was requested to
-	 * work as rendezvous point, and replies with a RENDEZVOUS_ESTABLISHED cell;
-	 * this event is parsed from a log statement in
-	 * rend_mid_establish_rendezvous().
-	 */
-	public static final HiddenServiceEventType RPO_RECEIVED_ESTABLISH_RENDEZVOUS_SENDING_RENDEZVOUS_ESTABLISHED =
-			new HiddenServiceEventType(
-					"RPO_RECEIVED_ESTABLISH_RENDEZVOUS_SENDING_RENDEZVOUS_ESTABLISHED");
-
-	/**
-	 * The rendezvous point received a RENDEZVOUS1 cell and reacts by sending a
-	 * RENDEZVOUS2 cell to Alice; this event is parsed from a log statement in
-	 * rend_mid_rendezvous().
-	 */
-	public static final HiddenServiceEventType RPO_RECEIVING_RENDEZVOUS1_SENDING_RENDEZVOUS2 =
-			new HiddenServiceEventType(
-					"RPO_RECEIVING_RENDEZVOUS1_SENDING_RENDEZVOUS2");
-
-}
\ No newline at end of file
diff --git a/src/de/uniba/wiai/lspi/puppetor/Network.java b/src/de/uniba/wiai/lspi/puppetor/Network.java
deleted file mode 100644
index 5ebd3c9..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/Network.java
+++ /dev/null
@@ -1,657 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
deleted file mode 100644
index f7b9837..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/NetworkFactory.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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/NodeEventType.java b/src/de/uniba/wiai/lspi/puppetor/NodeEventType.java
deleted file mode 100644
index e598a3c..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/NodeEventType.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor;
-
-/**
- * Event types that can be fired by all Tor processes.
- */
- at SuppressWarnings("serial")
-public class NodeEventType implements EventType {
-
-	/**
-	 * String identifying the type of the event type.
-	 */
-	String typeString;
-
-	/**
-	 * Creates a new event type with the given type string.
-	 *
-	 * @param typeString
-	 *            String identifying the type of the event type.
-	 */
-	public NodeEventType(final String typeString) {
-		this.typeString = typeString;
-	}
-
-	public String getTypeName() {
-		return typeString;
-	}
-
-	/**
-	 * The node was started and we managed to connect to its control port; this
-	 * event is fired internally and not parsed from a log statement from Tor.
-	 */
-	public static final NodeEventType NODE_STARTED =
-			new NodeEventType("NODE_STARTED");
-
-	/**
-	 * The node has opened its control port; this event is parsed from a log
-	 * statement in connection_create_listener().
-	 */
-	public static final NodeEventType NODE_CONTROL_PORT_OPENED =
-			new NodeEventType("NODE_CONTROL_PORT_OPENED");
-
-	/**
-	 * The node which has successfully opened a circuit; this event is parsed
-	 * from a log statement in circuit_send_next_onion_skin().
-	 */
-	public static final NodeEventType NODE_CIRCUIT_OPENED =
-			new NodeEventType("NODE_CIRCUIT_OPENED");
-
-	/**
-	 * The node was stopped; this event is fired internally and not parsed from
-	 * a log statement from Tor.
-	 */
-	public static final NodeEventType NODE_STOPPED =
-			new NodeEventType("NODE_STOPPED");
-}
\ No newline at end of file
diff --git a/src/de/uniba/wiai/lspi/puppetor/NodeState.java b/src/de/uniba/wiai/lspi/puppetor/NodeState.java
deleted file mode 100644
index 1891031..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/NodeState.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
deleted file mode 100644
index 3d46567..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/ProxyNode.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
deleted file mode 100644
index 648141e..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/PuppeTorException.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
- */
- at SuppressWarnings("serial")
-public class PuppeTorException extends Exception {
-
-	/**
-	 * Creates a <code>PuppeTorException</code> without detail message or
-	 * cause.
-	 */
-	public PuppeTorException() {
-		super();
-	}
-
-	/**
-	 * Creates a <code>PuppeTorException</code> with the given detail
-	 * <code>message</code> and <code>cause</code>.
-	 *
-	 * @param message
-	 *            The detail message of this exception.
-	 * @param cause
-	 *            The cause for this exception.
-	 */
-	public PuppeTorException(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
deleted file mode 100644
index 9b6bb5a..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/RouterNode.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
deleted file mode 100644
index 11cfbb5..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/ServerApplication.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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/ServerEventType.java b/src/de/uniba/wiai/lspi/puppetor/ServerEventType.java
deleted file mode 100644
index e2dc296..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/ServerEventType.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor;
-
-/**
- * Event types that can be fired by a server application running as thread in
- * the background.
- */
- at SuppressWarnings("serial")
-public class ServerEventType implements EventType {
-
-	/**
-	 * String identifying the type of the event type.
-	 */
-	String typeString;
-
-	/**
-	 * Creates a new event type with the given type string.
-	 *
-	 * @param typeString
-	 *            String identifying the type of the event type.
-	 */
-	public ServerEventType(final String typeString) {
-		this.typeString = typeString;
-	}
-
-	public String getTypeName() {
-		return typeString;
-	}
-
-	/**
-	 * The server application has received a request and sent a reply to it;
-	 * this event is fired internally and not parsed from a log statement from
-	 * Tor.
-	 */
-	public static final ServerEventType SERVER_RECEIVING_REQUEST_SENDING_REPLY =
-			new ServerEventType("SERVER_RECEIVING_REQUEST_SENDING_REPLY");
-}
\ No newline at end of file
diff --git a/src/de/uniba/wiai/lspi/puppetor/examples/AccessingPublicWebServerOverTor.java b/src/de/uniba/wiai/lspi/puppetor/examples/AccessingPublicWebServerOverTor.java
deleted file mode 100644
index 15d8ad7..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/examples/AccessingPublicWebServerOverTor.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
deleted file mode 100644
index ba830d5..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
deleted file mode 100644
index 9ef6774..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
deleted file mode 100644
index 1521487..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
deleted file mode 100644
index 9dfe014..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/impl/ClientApplicationImpl.java
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
deleted file mode 100644
index 2dd82ea..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/impl/DirectoryNodeImpl.java
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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/EventImpl.java b/src/de/uniba/wiai/lspi/puppetor/impl/EventImpl.java
deleted file mode 100644
index 52384fd..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/impl/EventImpl.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.impl;
-
-import java.util.Date;
-
-import de.uniba.wiai.lspi.puppetor.Event;
-import de.uniba.wiai.lspi.puppetor.EventType;
-
-/**
- * Implementation of <code>Event</code>.
- *
- * @author kloesing
- */
- at SuppressWarnings("serial")
-public class EventImpl implements Event {
-
-	/**
-	 * The source of this event.
-	 */
-	private final String source;
-
-	/**
-	 * The type of this event.
-	 */
-	private EventType type;
-
-	/**
-	 * Either the log message that led to firing this event, or an internal
-	 * message.
-	 */
-	private final String message;
-
-	/**
-	 * The occurrence time of the event or of the corresponding log statement.
-	 */
-	private long occurrenceTime;
-
-	/**
-	 * Creates a new <code>EventImpl</code>.
-	 *
-	 * @param occurrenceTime
-	 *            The occurrence time of the event or of the corresponding log
-	 *            statement.
-	 * @param source
-	 *            The source of this event.
-	 * @param type
-	 *            The type of this event.
-	 * @param message
-	 *            Either the log message that led to firing this event, or an
-	 *            internal message.
-	 */
-	EventImpl(final long occurrenceTime, final String source,
-			final EventType type, final String message) {
-		this.occurrenceTime = occurrenceTime;
-		this.source = source;
-		this.type = type;
-		this.message = message;
-	}
-
-	/**
-	 * Creates a new <code>EventImpl</code>.
-	 *
-	 * @param source
-	 *            The source of this event.
-	 * @param message
-	 *            Either the log message that led to firing this event, or an
-	 *            internal message.
-	 */
-	EventImpl(final String source, final String message) {
-		this.source = source;
-		this.message = message;
-	}
-
-	public String getSource() {
-		return source;
-	}
-
-	public EventType getType() {
-		return type;
-	}
-
-	/**
-	 * Sets the event type to the given type.
-	 *
-	 * @param type
-	 *            The type of this event.
-	 */
-	void setType(final EventType type) {
-		this.type = type;
-	}
-
-	public String getMessage() {
-		return message;
-	}
-
-	public long getOccurrenceTime() {
-		return occurrenceTime;
-	}
-
-	@Override
-	public String toString() {
-		return this.getClass().getSimpleName() + ": occurenceTime="
-				+ new Date(occurrenceTime) + ", source=\"" + source
-				+ "\", type=" + type.getTypeName() + ", message=\"" + message
-				+ "\"";
-	}
-
-	/**
-	 * Sets the occurrence time to the given time.
-	 *
-	 * @param occurrenceTime
-	 *            The occurrence time of the event or of the corresponding log
-	 *            statement.
-	 */
-	void setOccurenceTime(final long occurrenceTime) {
-		this.occurrenceTime = occurrenceTime;
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/impl/EventManagerImpl.java b/src/de/uniba/wiai/lspi/puppetor/impl/EventManagerImpl.java
deleted file mode 100644
index 788d61f..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/impl/EventManagerImpl.java
+++ /dev/null
@@ -1,729 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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/HiddenServiceImpl.java b/src/de/uniba/wiai/lspi/puppetor/impl/HiddenServiceImpl.java
deleted file mode 100644
index 61c0763..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/impl/HiddenServiceImpl.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.impl;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import de.uniba.wiai.lspi.puppetor.HiddenService;
-import de.uniba.wiai.lspi.puppetor.PuppeTorException;
-
-/**
- * Implementation of <code>HiddenService</code>.
- *
- * @author kloesing
- */
-public class HiddenServiceImpl implements HiddenService {
-
-	/**
-	 * Logger for this node which is called "hidserv." plus the name of this
-	 * hidden service.
-	 */
-	protected Logger logger;
-
-	/**
-	 * The node at which this hidden service is configured.
-	 */
-	private final ProxyNodeImpl node;
-
-	/**
-	 * Name of the hidden service that will be used as name for the hidden
-	 * service directory.
-	 */
-	private final String serviceName;
-
-	/**
-	 * The TCP port on which the service will be available for requests.
-	 */
-	private final int servicePort;
-
-	/**
-	 * The virtual TCP port that this hidden service runs on as it is announced
-	 * to clients.
-	 */
-	private final int virtualPort;
-
-	/**
-	 * Adds the entries for a hidden service to the configuration of this node.
-	 *
-	 * Creates a new <code>HiddenServiceImpl</code>.
-	 *
-	 * @param node
-	 *            The node at which this hidden service is configured.
-	 * @param serviceName
-	 *            Name of the hidden service that will be used as name for the
-	 *            hidden service directory. May neither be <code>null</code>
-	 *            or a zero-length string.
-	 * @param servicePort
-	 *            The TCP port on which the service will be available for
-	 *            requests. This can, but need not be different from the virtual
-	 *            port that is announced to clients. May not be negative or
-	 *            greater than 65535.
-	 * @param virtualPort
-	 *            The virtual TCP port that this hidden service runs on as it is
-	 *            announced to clients. May not be negative or greater than
-	 *            65535.
-	 * @throws IllegalArgumentException
-	 *             Thrown if an invalid value is given for either of the
-	 *             parameters.
-	 */
-	HiddenServiceImpl(final ProxyNodeImpl node, final String serviceName,
-			final int servicePort, final int virtualPort) {
-
-		// check if networkName can be used as logger name
-		if (serviceName == null || serviceName.length() == 0) {
-			throw new IllegalArgumentException("Invalid serviceName: "
-					+ serviceName);
-		}
-
-		// create logger
-		logger = Logger.getLogger("hidserv." + serviceName);
-
-		// log entering
-		logger.entering(this.getClass().getName(), "HiddenServiceImpl",
-				new Object[] { node, serviceName, servicePort, virtualPort });
-
-		// check parameters
-		if (servicePort < 0 || servicePort > 65535 || virtualPort < 0
-				|| virtualPort > 65535) {
-			logger.log(Level.SEVERE,
-					"Illegal argument when adding hidden service!");
-			final IllegalArgumentException e = new IllegalArgumentException();
-			logger.throwing(this.getClass().getName(), "addHiddenService", e);
-			throw e;
-		}
-
-		// store parameter values
-		this.node = node;
-		this.serviceName = serviceName;
-		this.servicePort = servicePort;
-		this.virtualPort = virtualPort;
-
-		// log exiting
-		logger.exiting(this.getClass().getName(), "HiddenServiceImpl");
-	}
-
-	public String determineOnionAddress() throws PuppeTorException {
-
-		// log entering
-		logger.entering(this.getClass().getName(), "getOnionAddress");
-
-		// check if hidden service directory exists
-		final File hiddenServiceFile =
-				new File(node.getWorkingDir().getAbsolutePath()
-						+ File.separator + serviceName + File.separator
-						+ "hostname");
-		if (!hiddenServiceFile.exists()) {
-			logger.log(Level.SEVERE,
-					"Hidden service directory or hostname file does not exist: "
-							+ hiddenServiceFile.getAbsolutePath());
-
-			final PuppeTorException e =
-					new PuppeTorException(
-							"Hidden service directory or hostname file does not exist: "
-									+ hiddenServiceFile.getAbsolutePath());
-			logger.throwing(this.getClass().getName(), "getOnionAddress", e);
-			throw e;
-		}
-
-		// read hostname from file
-		String address = null;
-		try {
-			final BufferedReader br =
-					new BufferedReader(new FileReader(hiddenServiceFile));
-			address = br.readLine();
-			br.close();
-		} catch (final IOException e) {
-			final PuppeTorException ex =
-					new PuppeTorException("Could not read hostname file!", e);
-			logger.throwing(this.getClass().getName(), "getOnionAddress", ex);
-			throw ex;
-		}
-
-		// log exiting and return address
-		logger.exiting(this.getClass().getName(), "getOnionAddress", address);
-		return address;
-	}
-
-	public String getServiceName() {
-		return serviceName;
-	}
-
-	public int getServicePort() {
-		return servicePort;
-	}
-
-	public int getVirtualPort() {
-		return virtualPort;
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/impl/NetworkImpl.java b/src/de/uniba/wiai/lspi/puppetor/impl/NetworkImpl.java
deleted file mode 100644
index 5321abd..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/impl/NetworkImpl.java
+++ /dev/null
@@ -1,1082 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
deleted file mode 100644
index b4417fa..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/impl/ProxyNodeImpl.java
+++ /dev/null
@@ -1,734 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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
deleted file mode 100644
index 166f8c6..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/impl/RouterNodeImpl.java
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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 at reply.org");
-		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
deleted file mode 100644
index 5ac0ea6..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/impl/ServerApplicationImpl.java
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-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/src/de/uniba/wiai/lspi/puppetor/rmi/AbstractMasterFactory.java b/src/de/uniba/wiai/lspi/puppetor/rmi/AbstractMasterFactory.java
deleted file mode 100644
index 220ab7e..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/AbstractMasterFactory.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.rmi.RemoteException;
-
-import de.uniba.wiai.lspi.puppetor.rmi.tests.TestRegistration;
-
-/**
- * Factory to create all objects needed by the master side of a PuppeTor
- * network.
- *
- * @author Sebastian Hahn
- */
-public abstract class AbstractMasterFactory {
-
-	/**
-	 * Hold the concrete RemotePuppeTorFactory that will be used to create the
-	 * implementation classes.
-	 */
-	private static AbstractMasterFactory factory;
-
-	/**
-	 * @return a new concrete
-	 *         <code>AbstractRemotePuppeTorFactory<code>subclass as
-	 *     specified by the initialization
-	 */
-	final public synchronized static AbstractMasterFactory getInstance() {
-		return factory;
-	}
-
-	/**
-	 * @param factory
-	 *            save this as the factory if this hasn't been called before.
-	 */
-	final public synchronized static void initialize(
-			final AbstractMasterFactory factory) {
-		if (AbstractMasterFactory.factory != null) {
-			throw new RuntimeException("Don't call initialize twice!");
-		}
-		AbstractMasterFactory.factory = factory;
-	}
-
-	/**
-	 * Override this to create a subclass of <code>RemotePuppeTor</code>
-	 *
-	 * @param slave
-	 *            Create the server represantation for this connected slave
-	 * @return The new <code>RemotePuppeTor</code> instance
-	 */
-	public abstract Master createMaster(Slave slave) throws RemoteException;
-
-	/**
-	 * Override this to create a subclass of <code>TestExecutor</code>.
-	 *
-	 * @return The <code>TestExecutor</code> instance.
-	 */
-	public abstract TestExecutor getTestExecutorInstance();
-
-	/**
-	 * Override this to create a subclass of <code>TestRegistration</code>.
-	 *
-	 * @return The <code>TestRegistration</code> instance.
-	 */
-	public abstract TestRegistration getTestRegistrationInstance();
-	
-	/**
-	 * XXX just for now so I don't forget.-SH
-	 */
-	public abstract NetworkDescription createNetworkDescription();
-
-	
-
-//	public abstract Network createNetwork(String name);
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/AbstractSlaveFactory.java b/src/de/uniba/wiai/lspi/puppetor/rmi/AbstractSlaveFactory.java
deleted file mode 100644
index 45e7f0e..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/AbstractSlaveFactory.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.util.Set;
-
-import de.uniba.wiai.lspi.puppetor.rmi.Slave.OS;
-
-/**
- * Create a new representation of the server. Initialize this once with a
- * concrete subclass of itself to create the actual <code>RemotePuppeTor</code>
- * instance.
- *
- * @author Sebastian Hahn
- */
-public abstract class AbstractSlaveFactory {
-
-	/**
-	 * Hold the concrete RemotePuppeTorFactory that will be used to create
-	 * <code>Slave</code> instances. Uses
-	 * <code>AbstractSlaveFactory.class</code> for locking.
-	 */
-	private static AbstractSlaveFactory factory;
-
-	/**
-	 * Hold the remote master object. We only have one per slave. XXX Maybe this
-	 * entire thing is more like a SlaveManager, not a factory. Oh well. Rename
-	 * later, I guess -SH
-	 */
-	private static RemoteMaster master;
-
-	/**
-	 * @return a new concrete <code>AbstractSlaveFactory<code>subclass as
-	 *     specified by the initialization
-	 */
-	final public synchronized static AbstractSlaveFactory getInstance() {
-		return factory;
-	}
-
-	/**
-	 * @param factory
-	 *            save this as the factory if this hasn't been called before.
-	 *            XXX - We could throw an exception here, too. I will think
-	 *            about that.-SH
-	 */
-	final public synchronized static void initialize(
-			final AbstractSlaveFactory factory) {
-		if (AbstractSlaveFactory.factory == null) {
-			AbstractSlaveFactory.factory = factory;
-		}
-	}
-
-	/**
-	 * Register a new master. Note that calling this twice for different master
-	 * doesn't indicate an error, but trying to to set the same master again is
-	 * a bug. Setting this to null means the connection to the master was
-	 * closed.
-	 *
-	 * @param master
-	 *            The master that should be used from now on.
-	 */
-	final public synchronized static void setMaster(final RemoteMaster master) {
-		AbstractSlaveFactory.master = master;
-	}
-
-	/**
-	 * @return the RemoteMaster currently registered.
-	 */
-	final public synchronized static RemoteMaster getMaster() {
-		if (AbstractSlaveFactory.master == null) {
-			throw new IllegalStateException("No master set yet!");
-		}
-		return AbstractSlaveFactory.master;
-	}
-
-	/**
-	 * Override this to create a subclass of <code>Slave</code>
-	 *
-	 * @return The new <code>Slave</code> instance
-	 */
-	public abstract Slave createSlave(final String slaveName, final OS os,
-			final String ip, final Set<Integer> ports,
-			final Set<Integer> availableRevisions) throws NullPointerException;
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/LocalMaster.java b/src/de/uniba/wiai/lspi/puppetor/rmi/LocalMaster.java
deleted file mode 100644
index 54afc42..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/LocalMaster.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-/**
- * Represent a connection between the locally running PuppeTor network Master
- * and a slave. This is the master part of the <code>Master</code> interface.
- *
- * @author Sebastian Hahn
- */
-public interface LocalMaster {
-	/**
-	 * @return the slave instance this connection refers to.
-	 */
-	public Slave getSlave();
-
-	/**
-	 * Add a new AbstractTaskImpl for the connected slave.
-	 *
-	 * @param abstractTaskImpl
-	 */
-	public void addTask(Task task);
-
-	/**
-	 * @param taskId
-	 *            The task id that we want to get
-	 * @return Get the TaskResult for the Task with the specified taskId.
-	 * @throws InterruptedException
-	 *             if interrupted while waiting for the next TestResult
-	 */
-	public TaskResult getTaskResult(int taskId) throws InterruptedException;
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/Master.java b/src/de/uniba/wiai/lspi/puppetor/rmi/Master.java
deleted file mode 100644
index 2e722da..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/Master.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-/**
- * Any subclass implementing this must also implement <code>RemoteMaster</code>
- * and <code>LocalMaster</code>. XXX This isn't a good way to do it. Think
- * about it more later-SH & Karsten
- *
- * @author Sebastian Hahn
- */
-public interface Master {}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/MasterConnector.java b/src/de/uniba/wiai/lspi/puppetor/rmi/MasterConnector.java
deleted file mode 100644
index 446da5d..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/MasterConnector.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.rmi.Remote;
-import java.rmi.RemoteException;
-
-/**
- * Handle the initial handshake with the slaves and pass them back an object
- * that the slave uses to refer to the master. The master will create exactly
- * one <code>MasterConnector</code>, slaves don't ever need one.
- *
- * @author Sebastian Hahn
- */
-public interface MasterConnector extends Remote {
-
-	/**
-	 * Called once by every connecting client so that the master knows about it
-	 * and has a chance to pass back a representation of itself.
-	 *
-	 * @param slave
-	 *            A slave representation as passed from the connecting slave
-	 * @return The representation of the master
-	 * @throws RemoteException
-	 */
-	public RemoteMaster registerClient(final Slave slave)
-			throws RemoteException;
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/Network.java b/src/de/uniba/wiai/lspi/puppetor/rmi/Network.java
deleted file mode 100644
index 83605d3..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/Network.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.io.Serializable;
-import java.util.List;
-import java.util.Set;
-
-/**
- * This class describes all Tor instances on the slaves that are used to form a
- * private Tor network, or the Tor instances that are added to the public
- * network and are used in a set of tests.
- * 
- * @author Sebastian Hahn
- */
-public interface Network extends Serializable {
-
-	/**
-	 * @param torInstance
-	 *  	add this <code>TorInstance</code> to the network.
-	 */
-	public void addTorInstance(TorInstance torInstance);
-	
-	/**
-	 * @return this network's name, mainly to identify it in logs.
-	 */
-	public String getName();
-
-	/**
-	 * @param test
-	 *            add this <code>test</code> to the network.
-	 */
-	public void addTest(Test test);
-
-	/**
-	 * @return a link for each slave that is part of this network.
-	 */
-	public Set<LocalMaster> getMasters();
-
-	/**
-	 * @return all Tor instances that are authorities
-	 */
-	public Set<TorInstance> getDirectoryAuthorities();
-
-	/**
-	 * @return all Tor instances that are routers, but not authorities
-	 */
-	public Set<TorInstance> getRoutersOnly();
-
-	/**
-	 * @return all Tor instances that are routers (including authorities)
-	 */
-	public Set<TorInstance> getRouters();
-
-	/**
-	 * @return all Tor instances that are proxis (but not authorities or
-	 *         routers)
-	 */
-	public Set<TorInstance> getProxiesOnly();
-
-	/**
-	 * @return all Tor instances that are proxies (including authorities and
-	 *         routers)
-	 */
-	public Set<TorInstance> getProxies();
-	
-	/**
-	 * @return all Tests that can run on this network -SH
-	 */
-	public List<Test> getTests();
-
-	/**
-	 * Is this a private network or part of the public one?
-	 */
-	public boolean isPrivateNetwork();
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/NetworkDescription.java b/src/de/uniba/wiai/lspi/puppetor/rmi/NetworkDescription.java
deleted file mode 100644
index a3f9949..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/NetworkDescription.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.io.Serializable;
-
-public interface NetworkDescription extends Serializable {
-	public int necessaryProxies();
-
-	public int necessaryRouters();
-
-	public int necessaryDirectoryAuthorities();
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/RemoteMaster.java b/src/de/uniba/wiai/lspi/puppetor/rmi/RemoteMaster.java
deleted file mode 100644
index 4384485..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/RemoteMaster.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.rmi.Remote;
-import java.rmi.RemoteException;
-
-/**
- * <code>RemotePuppeTor</code> is the main interface that drives the
- * interaction between the interconnected master and its slave instances. It is
- * published by the master's instance, and represents the view of the master a
- * slave has. It provides ways for the slave to register a <code>Slave</code>
- * to provide information about its capabilities, as well as a way for the slave
- * to poll the master for new work and then report back results. XXX We will
- * need a way to allow the master to access this, as well as a way for clients
- * with open ports to register for callback support instead of polling for new
- * tasks =SH
- *
- * @author Sebastian Hahn
- */
-public interface RemoteMaster extends Remote {
-
-	/**
-	 * Slaves that must poll because they are not reachable can use this method
-	 * to ask the master for new work.
-	 *
-	 * @throws RemoteException
-	 *             RMI...
-	 * @throws InterruptedException
-	 *             when interrupted while waiting for the next job to arrive.
-	 */
-	public Task getNewTask() throws RemoteException, InterruptedException;
-
-	/**
-	 * Report back the result of the task to the master. If possible, add an
-	 * error message. XXX Maybe down the road an error object is
-	 * needed/wanted-SH
-	 *
-	 * @throws RemoteException
-	 *             RMI
-	 */
-	public void reportTaskResult(TaskResult task) throws RemoteException,
-			InterruptedException;
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/Slave.java b/src/de/uniba/wiai/lspi/puppetor/rmi/Slave.java
deleted file mode 100644
index f6fb417..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/Slave.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.io.Serializable;
-import java.net.InetAddress;
-import java.util.Set;
-
-/**
- * A <code>Slave</code> represents a slave instance ready for tests. It
- * carries information about the testing capabilities of a given slave. A slave
- * may not provide information except for its name, which is always mandatory.
- * The slave will not be used for non-client traffic, though.
- *
- * @author Sebastian Hahn
- */
-public interface Slave extends Serializable {
-
-	public enum OS {
-		UNDEFINED, LINUX_I86_32, LINUX_I86_64, MAC_OSX_PPC, MAC_OSX_I86,
-		WINDOWS_XP_32, WINDOWS_XP_64, WINDOWS_VISTA_32, WINDOWS_VISTA_64
-	}
-
-	/**
-	 * @return the slave's name.
-	 */
-	public String getName();
-
-	/**
-	 * @return the slave's operating system as defined in <code>Slave.OS</code>.
-	 */
-	public OS getOS();
-
-	/**
-	 * @return the slave's IP address. XXX Test this with IPv6-SH
-	 */
-	public InetAddress getIP();
-
-	/**
-	 * @return the ports this slave can open
-	 */
-	public Set<Integer> openablePorts();
-
-	/**
-	 * @return Tor revisions available on the slave
-	 */
-	public Set<Integer> availableRevisions();
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/Task.java b/src/de/uniba/wiai/lspi/puppetor/rmi/Task.java
deleted file mode 100644
index e57a438..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/Task.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.io.Serializable;
-import java.rmi.RemoteException;
-import java.util.Collection;
-
-public interface Task extends Serializable {
-
-	/**
-	 * Execute the task. Must be called by the slave.
-	 *
-	 * @throws InterruptedException
-	 *             if interrupted while the task is running.
-	 * @throws RemoteException
-	 *             RMI
-	 */
-	public void execute() throws InterruptedException, RemoteException;
-
-	/**
-	 * Returns this tasks id. Task ids must be globally unique and larger than 0
-	 * or 0 to indicate a task that was sent out by the slave, not the master.
-	 */
-	public int getId();
-
-	/**
-	 * This method will be called by the <code>TestExecutor</code> to allow
-	 * tasks to collect additional information if they need it.
-	 * @param additionalInformation
-	 * 		offer this information.
-	 */
-	public void addAdditionalInformation(
-			Collection<String> additionalInformation);
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/TaskExecutionNotSuccessfulException.java b/src/de/uniba/wiai/lspi/puppetor/rmi/TaskExecutionNotSuccessfulException.java
deleted file mode 100644
index d287fdb..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/TaskExecutionNotSuccessfulException.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.util.Set;
-
-public class TaskExecutionNotSuccessfulException extends Exception {
-	private static final long serialVersionUID = 1L;
-	final private Set<TaskResult> taskResults;
-
-	public TaskExecutionNotSuccessfulException(final Set<TaskResult> taskResults) {
-		this.taskResults = taskResults;
-	}
-
-	public Set<TaskResult> getTaskResults() {
-		return taskResults;
-	}
-
-	// XXX Add some logging capabilities -SH
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/TaskResult.java b/src/de/uniba/wiai/lspi/puppetor/rmi/TaskResult.java
deleted file mode 100644
index e707e65..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/TaskResult.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.io.Serializable;
-
-/**
- * After the completion of a <code>Task</code>, a <code>TaskResult</code> is
- * created to report the result of the task as well as logging information to
- * the Master.  
- * 
- * @author Sebastian Hahn
- */
-public interface TaskResult extends Serializable {
-
-	/**
-	 * @return
-	 * 		the task that we have a result for
-	 */
-	public Task getTask();
-	
-	/**
-	 * @return
-	 * 		<code>true</code> if the task execution was successful,
-	 * 		false otherwise.
-	 */
-	public boolean wasSuccessful();
-	
-	/**
-	 * A <code>Task</code> may optionally pass back a result that can be used
-	 * by the Master to manage further Task execution. 
-	 * @return
-	 * 		the result of this task or null if no result is set.
-	 */
-	public String getTaskResult();
-}
\ No newline at end of file
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/Test.java b/src/de/uniba/wiai/lspi/puppetor/rmi/Test.java
deleted file mode 100644
index f67842f..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/Test.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.io.Serializable;
-
-/**
- * <code>Test</code> describes a test and the kind of slave it can run on, as
- * well as other network settings that are required for the test to work. A test
- * should be able to tell whether it was successfully run or not when passed a
- * TestResult.
- *
- * @author Sebastian Hahn
- */
-public interface Test extends Serializable {
-	/**
-	 * @return
-	 * 		a description of what this test is useful for.
-	 */
-	public String getDescription();
-
-	/**
-	 * @return
-	 * 		a description of the network that this test requires to run, so the
-	 * 		</code>TestExecutor</code> can pick or set up one. 
-	 */
-	public NetworkDescription getNetworkDescription();
-	
-	/**
-	 * Call this when the network has been set up and you want to run the test
-	 * with the current network.
-	 */
-	public void doTest();
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/TestExecutor.java b/src/de/uniba/wiai/lspi/puppetor/rmi/TestExecutor.java
deleted file mode 100644
index b17dc0d..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/TestExecutor.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A master uses the <code>TestExecutor</code> instance to load all registered
- * tests and check whether the currently running PuppeTor network can satisfy
- * the precondition necessary to run the test. If yes, it sets up the testing
- * environment and passes <code>PuppeTorJob</code>s to the slaves as the test
- * instructs it to do. There must be only a single instance of
- * <code>TestExecutor</code> at any time.
- *
- * @author Sebastian Hahn
- */
-public interface TestExecutor {
-
-	/**
-	 * Register a test with the executor so it will consider it for execution.
-	 * Accept all submitted tests even though some may not be executable at the
-	 * moment of submission.
-	 */
-	public void registerTest(Test test);
-
-	/**
-	 * Start executing tests. Callers should make sure that the PuppeTor network
-	 * that is supposed to be used is ready.
-	 */
-	public void startTesting();
-
-	/**
-	 * Return an immutable Map of the nodes that have been run successfully
-	 * along with the results of the tests.
-	 */
-	public Map<Test, TestResult> getSuccessfulTests();
-
-	/**
-	 * Return an immutable Map of the nodes that have been run but didn't
-	 * complete successfully along with the results of the tests.
-	 */
-	public Map<Test, TestResult> getFailedTests();
-
-	/**
-	 * Return all tests that could not be executed because the PuppeTorNetwork
-	 * could not match a test's preconditions.
-	 */
-	public Set<Test> getCannotRunTests();
-
-	/**
-	 * Register a newly connected PuppeTor slave via its
-	 * <code>LocalMaster</code> connection object.
-	 */
-	public void registerLocalMaster(LocalMaster master);
-
-	/**
-	 * Unregister a PuppeTor slave. Called when a slave disconnects.
-	 */
-	public void unregisterLocalMaster(LocalMaster master);
-
-	/**
-	 * @return
-	 * 		the currently running network.
-	 */
-	public Network getCurrentNetwork();
-
-	/**
-	 * Execute the passed <code>Task</code> once per passed
-	 * <code>LocalMaster</code>. If a <code>LocalMaster</code> is passed
-	 * more than once, the task will be executed multiple times.
-	 *
-	 * @param masters
-	 *            execute the task for those masters.
-	 * @param taskClass
-	 *            create a task of this class and execute it.
-	 * @param taskResults
-	 * 			place the results of the task in this <code>Set</code> unless
-	 * 			<code>null</code> is passed.
-	 * @param additionalInformation TODO
-	 * @throws TaskExecutionNotSuccessfulException
-	 *             Indicates a failure during task execution
-	 */
-	public void executeTaskForMasters(final Collection<LocalMaster> masters,
-			final Class<? extends Task> taskClass, final Set<TaskResult> taskResults,
-			Collection<String> additionalInformation)
-			throws TaskExecutionNotSuccessfulException;
-
-	/**
-	 * Execute the passed <code>Task</code> once per passed
-	 * <code>TorInstance</code>. If a <code>TorInstance</code> is passed
-	 * more than once, the task will be executed multiple times.
-	 * @param taskClass
-	 *          create a task of this class and execute it.
-	 * @param taskResults
-	 * 			place the results of the task in this <code>Set</code> unless
-	 * 			<code>null</code> is passed.
-	 * @param additionalInformation
-	 * 			call the task's addAdditionalInformation method with this
-	 * 			parameter.
-	 * @param torInstances
-	 *          execute the task for those masters.
-	 * @throws TaskExecutionNotSuccessfulException
-	 *          Indicates a failure during task execution
-	 */
-	public void executeTaskForTorInstances(Collection<TorInstance> instances,
-			Class<? extends Task> taskClass, Set<TaskResult> taskResults,
-			Collection<String> additionalInformation)
-			throws TaskExecutionNotSuccessfulException;
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/TestResult.java b/src/de/uniba/wiai/lspi/puppetor/rmi/TestResult.java
deleted file mode 100644
index 85e1b9f..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/TestResult.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.io.Serializable;
-
-/**
- * XXX unused for now. this will hold the result of a /<code>Test</code> -SH
- */
-public interface TestResult extends Serializable {
-	public Test getJob();
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/TorInstance.java b/src/de/uniba/wiai/lspi/puppetor/rmi/TorInstance.java
deleted file mode 100644
index 936f6ab..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/TorInstance.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi;
-
-import java.util.List;
-
-/**
- * Describe each tor instance at the client with a <code>TorInstance</code>.
- *
- * @author Sebastian Hahn
- */
-public interface TorInstance {
-	/**
-	 * Additional configuration options that a test wants to be set for a tor
-	 * instance. <code>TorInstance.shouldReplaceConfiguration()</code>
-	 * determines whether this configuration should be added to the default tor
-	 * configuration or replace it.
-	 *
-	 * @return An immutable list of extra configuration settings.
-	 */
-	public List<String> getConfiguration();
-
-	/**
-	 * @return the name by which this Tor instance will be identified at the
-	 *         slaves. See de.uniba.wiai.lspi.puppetor.Network.getNode().
-	 */
-	public String getName();
-
-	/**
-	 * Whether the caller should use the result of
-	 * <code>TorInstance.getExtraConfiguration()</code> to append tor's
-	 * default configuration or replace it.
-	 *
-	 * @return true if configuration should be replaced, false if it should be
-	 *         appended.
-	 */
-	public boolean shouldReplaceConfiguration();
-
-	public boolean isDirectoryAuthority();
-
-	public boolean isBridgeAuthority();
-
-	public boolean isRouter();
-
-	public boolean isExit();
-	
-	public boolean isOnPrivateNetwork();
-
-	public int getRevision();
-
-	public boolean isBridge();
-
-	public LocalMaster getMaster();
-}
\ No newline at end of file
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/execute/PuppeTorMasterProgram.java b/src/de/uniba/wiai/lspi/puppetor/rmi/execute/PuppeTorMasterProgram.java
deleted file mode 100644
index 16b0aae..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/execute/PuppeTorMasterProgram.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.execute;
-
-import java.rmi.AccessException;
-import java.rmi.NoSuchObjectException;
-import java.rmi.NotBoundException;
-import java.rmi.RemoteException;
-import java.rmi.registry.LocateRegistry;
-import java.rmi.registry.Registry;
-import java.rmi.server.UnicastRemoteObject;
-
-import javax.rmi.ssl.SslRMIClientSocketFactory;
-import javax.rmi.ssl.SslRMIServerSocketFactory;
-
-import de.uniba.wiai.lspi.puppetor.rmi.AbstractMasterFactory;
-import de.uniba.wiai.lspi.puppetor.rmi.LocalMaster;
-import de.uniba.wiai.lspi.puppetor.rmi.MasterConnector;
-import de.uniba.wiai.lspi.puppetor.rmi.Master;
-import de.uniba.wiai.lspi.puppetor.rmi.Slave;
-import de.uniba.wiai.lspi.puppetor.rmi.RemoteMaster;
-import de.uniba.wiai.lspi.puppetor.rmi.TestExecutor;
-import de.uniba.wiai.lspi.puppetor.rmi.impl.MasterImplFactory;
-import de.uniba.wiai.lspi.puppetor.rmi.tests.TestRegistration;
-
-/**
- * The <code>PuppeTorMasterProgram</code> contains the main method for the
- * master instance of a distributed PuppeTor testing infrastructure. Currently,
- * configuration of the the master's network settings are implemented as
- * instance variables of <code>PuppeTorMasterProgram</code>. XXX eventually,
- * using configuration files would be better -SH
- *
- * @author Sebastian Hahn
- */
-public class PuppeTorMasterProgram {
-
-	/**
-	 * The RMI registry.
-	 */
-	private Registry registry;
-
-	/**
-	 * The port the server is supposed to listen on. This port must not be
-	 * firewalled and must be forwarded to the machine if necessary.
-	 */
-	final private static int serverport = 2050;
-
-	/**
-	 * The address this server should bind on. Use 127.0.0.1 for local testing.
-	 * XXX Someone should check if this works with IPv6 -SH
-	 */
-	final private static String serveraddress = "127.0.0.1";
-
-	/**
-	 * Master application entry point
-	 *
-	 * @param args
-	 *            Command-line arguments - ignored for now.
-	 * @throws NotBoundException
-	 * @throws RemoteException
-	 * @throws AccessException
-	 */
-	public static void main(final String[] args) throws AccessException,
-			RemoteException, NotBoundException {
-		final PuppeTorMasterProgram server = new PuppeTorMasterProgram();
-
-		final TestExecutor exec =
-				AbstractMasterFactory.getInstance().getTestExecutorInstance();
-		exec.startTesting();
-		server.unregisterRMIRegistry();
-		// Suggest a garbage collection, may increase shutdown.
-		System.gc();
-	}
-
-	/**
-	 * Private constructor to set up RMI-related Java-internal variables and
-	 * create a <code>RemotePuppeTorImpl</code>.
-	 *
-	 * @throws NotBoundException
-	 * @throws RemoteException
-	 * @throws AccessException
-	 */
-	private PuppeTorMasterProgram() throws AccessException, RemoteException,
-			NotBoundException {
-		// Set the location of the keystore where your private certificate is.
-		System.setProperty("javax.net.ssl.keyStore", "res/keystore");
-		// Set the password for your keystore.
-		System.setProperty("javax.net.ssl.keyStorePassword", "pleasechange");
-
-		/*
-		 * Set the location of the truststore which contains the exported
-		 * certificates of your slaves.
-		 */
-		System.setProperty("javax.net.ssl.trustStore", "res/truststore");
-		// Use a timeout of 45 seconds to detect disconnected clients.
-		System.setProperty("java.rmi.dgc.leaseValue", "45000");
-
-		// Tell the RMI system the location of the master
-		System.setProperty("java.rmi.server.hostname", serveraddress);
-
-		// Set up the factories we want to use
-		AbstractMasterFactory.initialize(new MasterImplFactory());
-
-		registerTests();
-		final InnerMasterConnector master = InnerMasterConnector.getInstance();
-
-		try {
-			registry =
-					LocateRegistry.createRegistry(serverport,
-							new SslRMIClientSocketFactory(),
-							new SslRMIServerSocketFactory(null, null, true));
-			registry.bind("Master", master);
-		} catch (final Throwable th) {
-			th.printStackTrace();
-			throw new RuntimeException(
-					"Could not create the registry or bind"
-							+ "an object in it. Please check your system's configuration. "
-							+ th);
-		}
-
-		System.out.println("waiting for slaves.");
-		try {
-			Thread.sleep(1000 * 60 * 2);
-		} catch (final InterruptedException e) {
-			e.printStackTrace();
-			throw new RuntimeException("This is the main thread. We don't "
-					+ "want to be interrupted.");
-		}
-		registry.unbind("Master");
-		// Only unbinding is not enough, the object will still be exported.
-		UnicastRemoteObject.unexportObject(master, true);
-	}
-
-	/**
-	 * Register all the tests you want to execute here.
-	 */
-	private void registerTests() {
-		final AbstractMasterFactory fact = AbstractMasterFactory.getInstance();
-		final TestExecutor exec = fact.getTestExecutorInstance();
-		final TestRegistration registration = fact.getTestRegistrationInstance();
-		registration.registerTests(exec);
-	}
-
-	private void unregisterRMIRegistry() throws NoSuchObjectException {
-		UnicastRemoteObject.unexportObject(registry, true);
-		registry = null;
-	}
-
-	static class InnerMasterConnector extends UnicastRemoteObject implements
-			MasterConnector {
-		/**
-		 * Required for serialization. Needs to change for new released
-		 * versions.
-		 */
-		private static final long serialVersionUID = 1L;
-
-		private static InnerMasterConnector instance;
-
-		static {
-			try {
-				instance = new InnerMasterConnector();
-			} catch (final RemoteException e) {
-				e.printStackTrace();
-				throw new ExceptionInInitializerError(e);
-			}
-		}
-
-		/**
-		 * private constructor, Singleton
-		 *
-		 * @throws RemoteException
-		 *             RMI
-		 */
-		private InnerMasterConnector() throws RemoteException {}
-
-		public static InnerMasterConnector getInstance() {
-			return instance;
-		}
-
-		public RemoteMaster registerClient(final Slave slave)
-				throws RemoteException {
-			final AbstractMasterFactory fact =
-					AbstractMasterFactory.getInstance();
-			final Master master = fact.createMaster(slave);
-			AbstractMasterFactory.getInstance().getTestExecutorInstance()
-					.registerLocalMaster((LocalMaster) master);
-			return (RemoteMaster) master;
-		}
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/execute/PuppeTorSlaveProgram.java b/src/de/uniba/wiai/lspi/puppetor/rmi/execute/PuppeTorSlaveProgram.java
deleted file mode 100644
index 7baac20..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/execute/PuppeTorSlaveProgram.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.execute;
-
-import java.rmi.NoSuchObjectException;
-import java.rmi.NotBoundException;
-import java.rmi.RemoteException;
-import java.rmi.registry.LocateRegistry;
-import java.rmi.registry.Registry;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.concurrent.LinkedBlockingQueue;
-
-import javax.rmi.ssl.SslRMIClientSocketFactory;
-
-import de.uniba.wiai.lspi.puppetor.rmi.AbstractSlaveFactory;
-import de.uniba.wiai.lspi.puppetor.rmi.MasterConnector;
-import de.uniba.wiai.lspi.puppetor.rmi.Slave;
-import de.uniba.wiai.lspi.puppetor.rmi.Task;
-import de.uniba.wiai.lspi.puppetor.rmi.impl.SlaveImplFactory;
-import de.uniba.wiai.lspi.puppetor.rmi.tasks.TerminateTask;
-
-/**
- * The <code>PuppeTorSlaveProgram</code> contains the main method for the
- * slave instances of a distributed PuppeTor testing infrastructure. Currently,
- * configuration of the the master's network settings and some slave-specific
- * options are implemented as instance variables of
- * <code>PuppeTorSlaveProgram</code>. XXX eventually, using configuration
- * files would be better -SH
- *
- * @author Sebastian Hahn
- */
-public class PuppeTorSlaveProgram {
-
-	/**
-	 * The port the master server is supposed to listen on. This port must not
-	 * be firewalled and must be forwarded to the machine if necessary. Use the
-	 * same value as you used for the master's configuration.
-	 */
-	final private static int serverport = 2050;
-
-	/**
-	 * The address master server listens on. Use the same value as you used for
-	 * the master's configuration. XXX Someone should check if this works with
-	 * IPv6 -SH
-	 */
-	final private static String serveraddress = "127.0.0.1";//78.47.18.109";
-
-	/**
-	 * The address this slave listens on. Use 127.0.0.1 for slaves that do not
-	 * open any ports for the outside world and thus can act as Tor clients
-	 * only.
-	 */
-	final private static String myaddress = "127.0.0.1";
-	
-	/**
-	 * Each slave is identified by its unique <code>slaveName</code>. Be
-	 * careful to prevent naming collisions between slaves.
-	 */
-	final private static String slaveName = "slave1";
-
-	/**
-	 * A representation of this slave node to be sent to the master.
-	 */
-	final private Slave slave;
-
-	final protected static BlockingQueue<Task> tasks =
-			new LinkedBlockingQueue<Task>();
-
-	/**
-	 * Slave application entry point
-	 *
-	 * @param args
-	 *            Command-line arguments
-	 * @throws NoSuchObjectException
-	 */
-	public static void main(final String[] args) {
-		final PuppeTorSlaveProgram slave = new PuppeTorSlaveProgram();
-		while(true) {
-			while (slave.connectToMaster() == false) {
-				try {
-					Thread.sleep(60000);
-				} catch (final InterruptedException e) {
-					throw new RuntimeException("Bug: Nothing should " +
-							"interrupt us here!");
-				}
-			}
-	
-			final Thread taskGetter = new Thread(new Runnable() {
-				public void run() {
-					while (true) {
-						try {
-							try {
-								// XXX I dislike the following lines, but
-								// interruption doesn't work while we wait for the
-								// master. Maybe there is a better solution that
-								// doesn't eat the poison pill twice. -SH
-								Task task = AbstractSlaveFactory.getMaster()
-												.getNewTask();
-								tasks.put(task);
-								if (task instanceof TerminateTask) {
-									break;
-								}
-							} catch (final RemoteException e) {
-								// Empty the task queue - we cannot send any results
-								// back, anyways.
-								tasks.clear();
-								// A taskid of 0 indicates a local Task
-								tasks.put(new TerminateTask(0,
-										"local termination"));
-								Thread.currentThread().interrupt();
-							}
-						} catch (final InterruptedException e) {
-							Thread.currentThread().interrupt();
-						}
-						if (Thread.interrupted()) {
-							break;
-						}
-	
-					}
-				}
-			});
-	
-			taskGetter.start();
-			// XXX We use just a single Thread here. That isn't the smartest move.
-			// -SH
-			try {
-				while (true) {
-					final Task newTask = tasks.take();
-					newTask.execute();
-					if (Thread.interrupted()) {
-						break;
-					}
-				}
-			} catch (final InterruptedException ignored) {} catch (final RemoteException ignored) {}
-			taskGetter.interrupt();
-			AbstractSlaveFactory.setMaster(null);
-			// We don't shut down, but want the Master to see we disconnected,
-			// so hopefully a gc will happen soon.
-			System.gc();
-		}
-
-	}
-
-	/**
-	 * Private constructor to set up RMI-related Java-internal variables.
-	 */
-	private PuppeTorSlaveProgram() {
-		/*
-		 * Set the location of the keystore where your private certificate is.
-		 */
-		System.setProperty("javax.net.ssl.keyStore", "res/keystore");
-		/*
-		 * Set the password for your keystore.
-		 */
-		System.setProperty("javax.net.ssl.keyStorePassword", "pleasechange");
-		/*
-		 * Set the location of the truststore which contains the exported
-		 * certificates of your slaves
-		 */
-		System.setProperty("javax.net.ssl.trustStore", "res/truststore");
-		// Set up the factories we want to use
-		AbstractSlaveFactory.initialize(new SlaveImplFactory());
-		final AbstractSlaveFactory fac = AbstractSlaveFactory.getInstance();
-
-		/*
-		 * Create a new slave with the properties that are relevant for the
-		 * computer and networking environment it runs on.
-		 * XXX unused for now -SH
-		 */
-		final Set<Integer> ports = new HashSet<Integer>();
-		/*for (int i = 10025; i < 20000; i++) {
-			ports.add(i);
-		}*/
-		final Set<Integer> availableRevisions =
-				new CopyOnWriteArraySet<Integer>();
-		slave = fac.createSlave(slaveName, Slave.OS.UNDEFINED, myaddress,
-						ports, availableRevisions);
-
-	}
-
-	/**
-	 * Attempt to make a connection to the master through RMI. If the master is
-	 * down, that is not a fatal condition, we want to keep trying until we can
-	 * reach it. Don't call this except from main().
-	 *
-	 * @return true if the connection was successful, false otherwise.
-	 */
-	private boolean connectToMaster() {
-		try {
-			final Registry registry =
-					LocateRegistry.getRegistry(serveraddress, serverport,
-							new SslRMIClientSocketFactory());
-			AbstractSlaveFactory.setMaster(((MasterConnector) registry
-					.lookup("Master")).registerClient(slave));
-		} catch (final NotBoundException e) {
-			e.printStackTrace();
-			System.out.println("We could connect, but the master has not "
-					+ "exported the Master Object yet." + e);
-			return false;
-		} catch (final RemoteException e) {
-			return false;
-		} catch (final IllegalArgumentException e) {
-			throw new RuntimeException(
-					"A slave with this name is already connected "
-							+ "to the server! " + e);
-		}
-		return true;
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/AbstractTaskImpl.java b/src/de/uniba/wiai/lspi/puppetor/rmi/impl/AbstractTaskImpl.java
deleted file mode 100644
index 008ee49..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/AbstractTaskImpl.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.impl;
-
-import java.rmi.RemoteException;
-import java.util.Collection;
-
-import de.uniba.wiai.lspi.puppetor.rmi.AbstractSlaveFactory;
-import de.uniba.wiai.lspi.puppetor.rmi.Task;
-import de.uniba.wiai.lspi.puppetor.rmi.TaskResult;
-
-public abstract class AbstractTaskImpl implements Task {
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-	
-	/**
-	 * A human-readable name for the task (to be included in log-messages, etc).
-	 */
-	final protected String name;
-
-	/**
-	 * The globally unique id for this task.
-	 */
-	final protected int id;
-
-	public AbstractTaskImpl(final int id, final String name) {
-		this.name = name;
-		this.id = id;
-	}
-
-	public int getId() {
-		return id;
-	}
-
-	public abstract void execute() throws InterruptedException, RemoteException;
-
-	protected void reportResult(final boolean success)
-			throws InterruptedException, RemoteException {
-		reportResult(new TaskResultImpl(success, this, null));
-	}
-	
-	protected void reportResult(final boolean success, final String result)
-			throws InterruptedException, RemoteException {
-		reportResult(new TaskResultImpl(success, this, result));
-	}
-
-	// XXX protect against double-reporting of results -SH
-	protected void reportResult( final TaskResult taskResult)
-			throws InterruptedException, RemoteException {
-		AbstractSlaveFactory.getMaster().reportTaskResult(taskResult);
-	}
-	
-	public void addAdditionalInformation(
-			Collection<String> additionalInformation) {}
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/AbstractTestImpl.java b/src/de/uniba/wiai/lspi/puppetor/rmi/impl/AbstractTestImpl.java
deleted file mode 100644
index eea5b36..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/AbstractTestImpl.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.impl;
-
-import de.uniba.wiai.lspi.puppetor.rmi.AbstractMasterFactory;
-import de.uniba.wiai.lspi.puppetor.rmi.NetworkDescription;
-import de.uniba.wiai.lspi.puppetor.rmi.Test;
-import de.uniba.wiai.lspi.puppetor.rmi.TestExecutor;
-
-/**
- * Extend <code>AbstractTestImpl</code> for your own tests. Make sure you also add them
- * to the TestRegistration.registerTest method.
- * 
- * @author Sebastian Hahn
- */
-abstract public class AbstractTestImpl implements Test {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-	
-	/**
-	 * A test description for log messages.
-	 */
-	final private String description;
-
-	/**
-	 * The network that must exist for the test to be executed.
-	 */
-	final private NetworkDescription networkDescription;
-
-	final protected TestExecutor exec;
-	
-	public AbstractTestImpl(final String description, final NetworkDescription network) {
-		this.description = description;
-		networkDescription = network;
-		exec = AbstractMasterFactory.getInstance().
-				getTestExecutorInstance();
-	}
-
-	public final NetworkDescription getNetworkDescription() {
-		return networkDescription;
-	}
-
-	public String getDescription() {
-		return description;
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/MasterImpl.java b/src/de/uniba/wiai/lspi/puppetor/rmi/impl/MasterImpl.java
deleted file mode 100644
index 4de305c..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/MasterImpl.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.impl;
-
-import java.rmi.RemoteException;
-import java.rmi.server.UnicastRemoteObject;
-import java.rmi.server.Unreferenced;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-import de.uniba.wiai.lspi.puppetor.rmi.AbstractMasterFactory;
-import de.uniba.wiai.lspi.puppetor.rmi.LocalMaster;
-import de.uniba.wiai.lspi.puppetor.rmi.Master;
-import de.uniba.wiai.lspi.puppetor.rmi.Slave;
-import de.uniba.wiai.lspi.puppetor.rmi.RemoteMaster;
-import de.uniba.wiai.lspi.puppetor.rmi.Task;
-import de.uniba.wiai.lspi.puppetor.rmi.TaskResult;
-
-/**
- * Represent the master at the slave, and give the master a way to interact with
- * the respective slave.
- *
- * @author Sebastian Hahn
- */
-public class MasterImpl extends UnicastRemoteObject implements LocalMaster,
-		RemoteMaster, Master, Unreferenced {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * Store the reference to the connected slave
-	 */
-	private final Slave slave;
-
-	private final BlockingQueue<Task> tasks;
-
-	private final BlockingQueue<TaskResult> completedTasks;
-
-	private final Object completedTasksLock = new Object();
-
-	public Slave getSlave() {
-		return slave;
-	}
-
-	/**
-	 * Check the slave object for validity and save it for further reference
-	 *
-	 * @throws RemoteException
-	 *             RMI
-	 */
-	public MasterImpl(final Slave slave) throws RemoteException {
-		if (slave.getName() == null) {
-			throw new NullPointerException("The slave's name must not be null!");
-		}
-		this.slave = slave;
-		tasks = new LinkedBlockingQueue<Task>();
-		completedTasks = new LinkedBlockingQueue<TaskResult>();
-	}
-
-	public Task getNewTask() throws RemoteException, InterruptedException {
-		return tasks.take();
-	}
-
-	public void reportTaskResult(final TaskResult task) throws RemoteException,
-			InterruptedException {
-		synchronized (completedTasksLock) {
-			completedTasks.put(task);
-			completedTasksLock.notifyAll();
-		}
-	}
-
-	public TaskResult getTaskResult(final int taskId)
-			throws InterruptedException {
-
-		synchronized (completedTasksLock) {
-			TaskResult res;
-			while ((res = completedTasks.peek()) == null
-					|| res.getTask().getId() != taskId) {
-				completedTasksLock.wait();
-			}
-			return completedTasks.take();
-		}
-	}
-
-	/**
-	 * Remove the slave from the Map of slaves and destroy its workqueues.
-	 * Inform the TestExecutor that all still-running tests failed because the
-	 * slave went away.
-	 */
-	public void unreferenced() {
-		AbstractMasterFactory.getInstance().getTestExecutorInstance()
-				.unregisterLocalMaster(this);
-	}
-
-	public void addTask(final Task task) {
-		tasks.add(task);
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/MasterImplFactory.java b/src/de/uniba/wiai/lspi/puppetor/rmi/impl/MasterImplFactory.java
deleted file mode 100644
index 2e4e67b..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/MasterImplFactory.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.impl;
-
-import java.rmi.RemoteException;
-import de.uniba.wiai.lspi.puppetor.rmi.AbstractMasterFactory;
-import de.uniba.wiai.lspi.puppetor.rmi.Master;
-import de.uniba.wiai.lspi.puppetor.rmi.NetworkDescription;
-import de.uniba.wiai.lspi.puppetor.rmi.Slave;
-import de.uniba.wiai.lspi.puppetor.rmi.TestExecutor;
-import de.uniba.wiai.lspi.puppetor.rmi.tests.TestRegistration;
-
-/**
- * Create <code>RemotePuppeTorImpl</code> instances
- *
- * @author Sebastian Hahn
- */
-public class MasterImplFactory extends AbstractMasterFactory {
-
-	@Override
-	public Master createMaster(final Slave slave) throws RemoteException {
-		return new MasterImpl(slave);
-	}
-
-	@Override
-	public TestExecutor getTestExecutorInstance() {
-		return TestExecutorImpl.getInstance();
-	}
-
-	@Override
-	public TestRegistration getTestRegistrationInstance() {
-		return TestRegistration.getInstance();
-	}
-
-/*	@Override
-	public Test createPublicNetworkTestForClientsOnly(final String description) {
-		return null;
-		return new AbstractTestImpl(description, new NetworkDescriptionImpl(1, 0, 0));
-	}*/
-
-	/*@Override
-	public Network createNetwork(final String name) {
-		return new NetworkImpl(name);
-	}*/
-
-	@Override
-	public NetworkDescription createNetworkDescription() {
-		// XXX implement -SH
-		return null;
-	}
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/NetworkDescriptionImpl.java b/src/de/uniba/wiai/lspi/puppetor/rmi/impl/NetworkDescriptionImpl.java
deleted file mode 100644
index be3926d..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/NetworkDescriptionImpl.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.impl;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
-import de.uniba.wiai.lspi.puppetor.rmi.NetworkDescription;
-
-public class NetworkDescriptionImpl implements NetworkDescription {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	private final AtomicInteger numProxies;
-	private final AtomicInteger numRouters;
-	private final AtomicInteger numDirectoryAuthorities;
-
-	public int necessaryProxies() {
-		return numProxies.get();
-	}
-
-	public int necessaryRouters() {
-		return numRouters.get();
-	}
-
-	public int necessaryDirectoryAuthorities() {
-		return numDirectoryAuthorities.get();
-	}
-
-	public NetworkDescriptionImpl(final int numProxies, final int numRouters,
-			final int numDirectoryAuthorities) {
-		this.numProxies = new AtomicInteger(numProxies);
-		this.numRouters = new AtomicInteger(numRouters);
-		this.numDirectoryAuthorities =
-				new AtomicInteger(numDirectoryAuthorities);
-	}
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/NetworkImpl.java b/src/de/uniba/wiai/lspi/puppetor/rmi/impl/NetworkImpl.java
deleted file mode 100644
index 5340a5b..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/NetworkImpl.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.impl;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-import de.uniba.wiai.lspi.puppetor.rmi.LocalMaster;
-import de.uniba.wiai.lspi.puppetor.rmi.Network;
-import de.uniba.wiai.lspi.puppetor.rmi.Test;
-import de.uniba.wiai.lspi.puppetor.rmi.TorInstance;
-
-public class NetworkImpl implements Network {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The name of the network, so we can identify it in logs etc.
-	 */
-	final private String name;
-
-	/**
-	 * All the tests that will run with this network. The individual Tor
-	 * instances will have the tests they need to execute broken down further.
-	 */
-	private final List<Test> tests;
-
-	/**
-	 * All the Tor instances running at the slaves that are linked to this
-	 * network.
-	 */
-	private final Set<TorInstance> torInstances;
-
-	private final boolean privateNetwork;
-	
-	public NetworkImpl(final String name, boolean privateNetwork) {
-		this.name = name;
-		tests = new CopyOnWriteArrayList<Test>();
-		torInstances = new CopyOnWriteArraySet<TorInstance>();
-		this.privateNetwork = privateNetwork;
-	}
-
-	public void addTorInstance(final TorInstance torInstance) {
-		torInstances.add(torInstance);
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public void addTest(final Test test) {
-		tests.add(test);
-	}
-
-	public Set<LocalMaster> getMasters() {
-		final Set<LocalMaster> masters = new HashSet<LocalMaster>();
-		for (final TorInstance torInstance : torInstances) {
-			masters.add(torInstance.getMaster());
-		}
-		return Collections.unmodifiableSet(masters);
-	}
-
-	public Set<TorInstance> getDirectoryAuthorities() {
-		final Set<TorInstance> authorities = new HashSet<TorInstance>();
-		for (final TorInstance torInstance : torInstances) {
-			if (torInstance.isDirectoryAuthority()) {
-				authorities.add(torInstance);
-			}
-		}
-		return Collections.unmodifiableSet(authorities);
-	}
-
-	public Set<TorInstance> getProxies() {
-		return Collections.unmodifiableSet(torInstances);
-	}
-
-	public Set<TorInstance> getProxiesOnly() {
-		final Set<TorInstance> proxies = new HashSet<TorInstance>();
-		for (final TorInstance torInstance : torInstances) {
-			if (!torInstance.isRouter()) {
-				proxies.add(torInstance);
-			}
-		}
-		return Collections.unmodifiableSet(proxies);
-	}
-
-	public Set<TorInstance> getRouters() {
-		final Set<TorInstance> routers = new HashSet<TorInstance>();
-		for (final TorInstance torInstance : torInstances) {
-			if (!torInstance.isRouter()) {
-				routers.add(torInstance);
-			}
-		}
-		return Collections.unmodifiableSet(routers);
-	}
-
-	public Set<TorInstance> getRoutersOnly() {
-		final Set<TorInstance> routers = new HashSet<TorInstance>();
-		for (final TorInstance torInstance : torInstances) {
-			if (torInstance.isRouter() && !torInstance.isDirectoryAuthority()) {
-				routers.add(torInstance);
-			}
-		}
-		return Collections.unmodifiableSet(routers);
-	}
-	
-	public List<Test> getTests() {
-		return Collections.unmodifiableList(tests);
-	}
-
-	public boolean isPrivateNetwork() {
-		return privateNetwork;
-	}
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/SlaveImpl.java b/src/de/uniba/wiai/lspi/puppetor/rmi/impl/SlaveImpl.java
deleted file mode 100644
index 2e0829c..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/SlaveImpl.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.impl;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.Collections;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-import de.uniba.wiai.lspi.puppetor.rmi.Slave;
-
-/**
- * Describe a client including its capabilities and network location to the
- * master. Also allow the master to query that information. A slave is not
- * required to give more than its name, but it can only be used for very limited
- * tests.
- *
- * @author Sebastian Hahn
- */
-public final class SlaveImpl implements Slave {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The slave's name
-	 */
-	protected final String name;
-
-	/**
-	 * The slave's ip address
-	 */
-	protected final InetAddress ip;
-
-	/**
-	 * The operating system of the slave. See <code>Slave.OS</code>.
-	 */
-	protected final OS os;
-
-	/**
-	 * The ports this slave can open. Currently, we choose to use 256 as a
-	 * maximum that "should be" enough so we don't transfer so much data when
-	 * serializing. See the constructor. Make sure this is immutable.
-	 */
-	protected final Set<Integer> ports;
-
-	/**
-	 * The different revisions of Tor clients this slave can execute.
-	 */
-	protected final Set<Integer> availableRevisions;
-
-	/**
-	 * Create a new <code>Slave</code> with the metrics we currently use,
-	 * meaning it's operating system, it's IP, and the ports it can open. We may
-	 * in the future include other things like Tor executable version numbers
-	 * and the ability to run certain extra scripts.
-	 *
-	 * @param slaveName
-	 *            the slave's name. Each slave must have it's own unique name in
-	 *            a given PuppeTor network.
-	 * @param os
-	 *            the slave's operating system.
-	 * @param ip
-	 *            A String representation of the slave's IP (v4 or v6).
-	 * @param ports
-	 *            All the ports this slave can open. Currently, we pick 256 out
-	 *            of those at random.
-	 * @throws NullPointerException
-	 *             if the slavename is null
-	 */
-	public SlaveImpl(final String slaveName, final OS os, final String ip,
-			final Set<Integer> ports, final Set<Integer> availableRevisions)
-			throws NullPointerException {
-		if (slaveName == null) {
-			throw new NullPointerException("slaveName must not be null");
-		}
-		name = slaveName;
-		this.os = os;
-		try {
-			this.ip = InetAddress.getByName(ip);
-		} catch (final UnknownHostException e) {
-			e.printStackTrace();
-			throw new RuntimeException("You specified an invalid IP address!");
-		}
-
-		if (ports.size() > 256) {
-			final Set<Integer> tmp = new CopyOnWriteArraySet<Integer>();
-			int i = 0;
-			for (final Integer j : tmp) {
-				if (i++ == 256) {
-					break;
-				}
-				tmp.add(j);
-			}
-			this.ports = Collections.unmodifiableSet(tmp);
-		} else {
-			this.ports =
-					Collections
-							.unmodifiableSet(new CopyOnWriteArraySet<Integer>(
-									ports));
-		}
-		this.availableRevisions =
-				Collections.unmodifiableSet(availableRevisions);
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public OS getOS() {
-		return os;
-	}
-
-	public InetAddress getIP() {
-		return ip;
-	}
-
-	public Set<Integer> openablePorts() {
-		return ports;
-	}
-
-	public Set<Integer> availableRevisions() {
-		return availableRevisions;
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/SlaveImplFactory.java b/src/de/uniba/wiai/lspi/puppetor/rmi/impl/SlaveImplFactory.java
deleted file mode 100644
index 1fcb461..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/SlaveImplFactory.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.impl;
-
-import java.util.Set;
-
-import de.uniba.wiai.lspi.puppetor.rmi.AbstractSlaveFactory;
-import de.uniba.wiai.lspi.puppetor.rmi.Slave;
-import de.uniba.wiai.lspi.puppetor.rmi.Slave.OS;
-
-/**
- * Create <code>RemotePuppeTorImpl</code> instances
- *
- * @author Sebastian Hahn
- */
-public class SlaveImplFactory extends AbstractSlaveFactory {
-
-	/**
-	 * @return a new <code>RemotePuppeTorImpl</code> instance.
-	 */
-	@Override
-	public Slave createSlave(final String slaveName, final OS os,
-			final String ip, final Set<Integer> ports,
-			final Set<Integer> availableRevisions) throws NullPointerException {
-		return new SlaveImpl(slaveName, os, ip, ports, availableRevisions);
-	}
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/TaskResultImpl.java b/src/de/uniba/wiai/lspi/puppetor/rmi/impl/TaskResultImpl.java
deleted file mode 100644
index fc3124f..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/TaskResultImpl.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.impl;
-
-import de.uniba.wiai.lspi.puppetor.rmi.Task;
-import de.uniba.wiai.lspi.puppetor.rmi.TaskResult;
-
-public class TaskResultImpl implements TaskResult {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The task this result is used for.
-	 */
-	final private Task task;
-
-	/**
-	 * Store the result of the <code>task</code>, or <code>null</code> if no
-	 * result is necessary.
-	 */
-	final private String result;
-	
-	/**
-	 * Whether the task execution was successful.
-	 */
-	final private boolean wasSuccessful;
-
-	public TaskResultImpl(final boolean success, final Task task, final String result) {
-		wasSuccessful = success;
-		this.task = task;
-		this.result = result;
-	}
-
-	public Task getTask() {
-		return task;
-	}
-
-	public boolean wasSuccessful() {
-		return wasSuccessful;
-	}
-
-	public String getTaskResult() {
-		return result;
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/TestExecutorImpl.java b/src/de/uniba/wiai/lspi/puppetor/rmi/impl/TestExecutorImpl.java
deleted file mode 100644
index 605dda0..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/TestExecutorImpl.java
+++ /dev/null
@@ -1,554 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.impl;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.rmi.NoSuchObjectException;
-import java.rmi.server.UnicastRemoteObject;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Vector;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import de.uniba.wiai.lspi.puppetor.rmi.Network;
-import de.uniba.wiai.lspi.puppetor.rmi.LocalMaster;
-import de.uniba.wiai.lspi.puppetor.rmi.NetworkDescription;
-import de.uniba.wiai.lspi.puppetor.rmi.RemoteMaster;
-import de.uniba.wiai.lspi.puppetor.rmi.Task;
-import de.uniba.wiai.lspi.puppetor.rmi.TaskExecutionNotSuccessfulException;
-import de.uniba.wiai.lspi.puppetor.rmi.TaskResult;
-import de.uniba.wiai.lspi.puppetor.rmi.Test;
-import de.uniba.wiai.lspi.puppetor.rmi.TestResult;
-import de.uniba.wiai.lspi.puppetor.rmi.TestExecutor;
-import de.uniba.wiai.lspi.puppetor.rmi.TorInstance;
-import de.uniba.wiai.lspi.puppetor.rmi.tasks.BuildCircuitsTask;
-import de.uniba.wiai.lspi.puppetor.rmi.tasks.ConfigureAsPrivateTask;
-import de.uniba.wiai.lspi.puppetor.rmi.tasks.CreateDirectoryAuthorityTask;
-import de.uniba.wiai.lspi.puppetor.rmi.tasks.CreateNetworkTask;
-import de.uniba.wiai.lspi.puppetor.rmi.tasks.CreateProxyTask;
-import de.uniba.wiai.lspi.puppetor.rmi.tasks.CreateRouterTask;
-import de.uniba.wiai.lspi.puppetor.rmi.tasks.ShutdownNodesTask;
-import de.uniba.wiai.lspi.puppetor.rmi.tasks.StartNodesTask;
-import de.uniba.wiai.lspi.puppetor.rmi.tasks.TerminateTask;
-
-/**
- * @author Sebastian Hahn
- */
-public final class TestExecutorImpl implements TestExecutor {
-
-	/**
-	 * The TestExecutor instance that we'll use
-	 */
-	private static final TestExecutor instance = new TestExecutorImpl();
-
-	/**
-	 * Store all local representations of connections to slaves. Use the
-	 * slave's name as key.
-	 */
-	private static final ConcurrentMap<String, LocalMaster> masters =
-			new ConcurrentHashMap<String, LocalMaster>();
-
-	/**
-	 * Store all registered tests until they are started.
-	 */
-	private static final List<Test> newTests = new Vector<Test>();
-
-	/**
-	 * Store all currently running tests.
-	 */
-	// private static final List<Test> runningTests = new Vector<Test>();
-	/**
-	 * Store all tests that cannot run because the slave's cannot build such a
-	 * network.
-	 */
-	private static final Set<Test> cannotRunTests =
-			new CopyOnWriteArraySet<Test>();
-
-	/**
-	 * Store the networks that are created out of the NetworkDescriptions.
-	 */
-	private static final Set<Network> networks =
-			new CopyOnWriteArraySet<Network>();
-	
-	/**
-	 * Store the currently active network when executing a test. Null indicates
-	 * that either testing hasn't started yet, or is already finished.
-	 */
-	private static Network currentNetwork = null;
-
-	/**
-	 * Store all failed tests.
-	 */
-	private static final Map<Test, TestResult> failedTests =
-			new ConcurrentHashMap<Test, TestResult>();
-
-	/**
-	 * Store all successfully completed tests.
-	 */
-	private static final Map<Test, TestResult> successfulTests =
-			new ConcurrentHashMap<Test, TestResult>();
-
-	/**
-	 * Whether testing has started. This cannot be reset.
-	 */
-	private static AtomicBoolean startedTesting = new AtomicBoolean(false);
-
-	/**
-	 * Store the id for the next task. Every task gets its own unique id > 0. A
-	 * task id equal to 0 indicates that the task was created by the slave to
-	 * stop execution.
-	 */
-	private static AtomicInteger nextTaskId = new AtomicInteger(1);
-
-	/**
-	 * Singleton
-	 */
-	private TestExecutorImpl() {}
-
-	public static TestExecutor getInstance() {
-		return instance;
-	}
-
-	public void registerLocalMaster(final LocalMaster master) {
-		if (startedTesting.get() == true) {
-			throw new RuntimeException("Bug: Testing has started already.");
-		}
-		System.out.println(master.getSlave().getName() + " registered");
-		if (null != masters.putIfAbsent(master.getSlave().getName(), master)) {
-			throw new IllegalArgumentException(master.getSlave().getName()
-					+ " has already registered with this Server");
-		}
-	}
-
-	public void unregisterLocalMaster(final LocalMaster master) {
-		// XXX Don't forget to mark all tests as failed that depended on this
-		// slave if we have already started with testing-SH
-		try {
-			UnicastRemoteObject.unexportObject((RemoteMaster) master, true);
-		} catch (final NoSuchObjectException e) {
-			throw new RuntimeException(
-					"This is a bug. The master should have been exported.");
-		}
-		if (masters.remove(master.getSlave().getName()) == null) {
-			throw new RuntimeException(
-					"This is a bug. unregisterLocalMaster must never be " +
-					"called twice for the same master.");
-		}
-	}
-
-	/**
-	 * Evaluate which tests can be run with the currently connected slaves.
-	 * Cancel all currently running tests and report them as failed if they
-	 * cannot be restarted with the new (reduced) set of connected slaves,
-	 * restart those tests otherwise. XXX This should be merged with
-	 * makeNetworks -SH
-	 */
-	protected void evaluateNetwork() {
-		if (startedTesting.get() == false) {
-			throw new RuntimeException("Bug: Testing has not been started.");
-			// XXX We need to actually check the network.-SH
-		}
-	}
-
-	public void startTesting() {
-		if (startedTesting.get() == true) {
-			throw new RuntimeException("Bug: Testing has started already.");
-		}
-		startedTesting.set(true);
-		// check which tests we can actually execute
-		evaluateNetwork();
-		for (final Test test : newTests) {
-			makeNetworks(test);
-		}
-		// XXX cancelled networks will still appear here...=SH
-		for (final Network nw : networks) {
-			synchronized(TestExecutorImpl.class) {
-				currentNetwork = nw;
-			}
-			try {
-				executeTaskForMasters(nw.getMasters(),
-						CreateNetworkTask.class, null, null);
-				// The network has been created at the slaves. Set up
-				// authorities, routers and proxies.
-
-				Set<TaskResult> authorityResults =
-						new CopyOnWriteArraySet<TaskResult>();
-				executeTaskForTorInstances(nw.getDirectoryAuthorities(),
-						CreateDirectoryAuthorityTask.class, authorityResults,
-						null);
-				executeTaskForTorInstances(nw.getRoutersOnly(),
-						CreateRouterTask.class, null, null);
-				executeTaskForTorInstances(nw.getProxiesOnly(),
-						CreateProxyTask.class, null, null);
-				if(nw.isPrivateNetwork()) {
-					List<String> authorities = new LinkedList<String>();
-					for(TaskResult result : authorityResults) {
-						authorities.add(result.getTaskResult());
-					}
-					executeTaskForMasters(nw.getMasters(),
-							ConfigureAsPrivateTask.class, null, authorities);
-				}
-				// Start the nodes ...
-				executeTaskForMasters(nw.getMasters(), StartNodesTask.class,
-						null, null);
-				// ... and build circuits. This may take some time.
-				executeTaskForMasters(nw.getMasters(), BuildCircuitsTask.class,
-						null, null);
-
-				// Start using the individual tests that were set up for this
-				// network.
-				for (final Test test : nw.getTests()) {
-					test.doTest();
-				}
-
-				// We don't need the network anymore, make sure it's shut down
-				// properly.
-				executeTaskForMasters(nw.getMasters(), ShutdownNodesTask.class,
-						null, null);
-
-			} catch (final TaskExecutionNotSuccessfulException e) {
-				e.printStackTrace();
-				// XXX Log this properly. -SH
-			}
-
-		}
-		// The terminate Task interrupts the slave's main process to
-		// indicate that it should disconnect.
-		synchronized(TestExecutorImpl.class) {
-			currentNetwork = null;
-		}
-		try {
-			executeTaskForMasters(masters.values(), TerminateTask.class, null,
-					null);
-		} catch (TaskExecutionNotSuccessfulException e) {
-			e.printStackTrace();
-			// XXX Log this properly. -SH
-		}
-	}
-
-	//XXX This method and the next look very similar. See what we can do. -SH
-	public void executeTaskForMasters(final Collection<LocalMaster> masters,
-			final Class<? extends Task> taskClass,
-			final Set<TaskResult> taskResults,
-			Collection<String> additionalInformation)
-			throws TaskExecutionNotSuccessfulException {
-
-		if (masters.isEmpty()) {
-			return;
-		}
-		final Set<MasterTask> mTasks = new HashSet<MasterTask>();
-		Constructor<? extends Task> ctor = null;
-		try {
-			ctor = taskClass.getConstructor(int.class, String.class);
-			String networkName = "";
-			synchronized(TestExecutorImpl.class) {
-				if(currentNetwork == null) {
-					networkName = "";
-				} else {
-					networkName = currentNetwork.getName();
-				}
-			}
-			for (final LocalMaster master : masters) {
-				Task task = ctor.newInstance(
-						nextTaskId.getAndIncrement(), networkName);
-				task.addAdditionalInformation(additionalInformation);
-				mTasks.add(new MasterTask(master, task));				
-			}
-
-			final TaskManager man = new TaskManager(mTasks);
-			final FutureTask<Boolean> futureTask =
-				new FutureTask<Boolean>(man);
-			new Thread(futureTask).start();
-			try {
-				if (futureTask.get() == false) {
-					throw new TaskExecutionNotSuccessfulException(man
-							.getTaskResults());
-				}
-			} catch (final InterruptedException e) {
-				throw new TaskExecutionNotSuccessfulException(null);
-			} catch (final ExecutionException e) {
-				// XXX Log this with the cause. Should not happen. -SH
-				e.printStackTrace();
-				throw new TaskExecutionNotSuccessfulException(null);
-			}
-			if(taskResults != null) {
-				taskResults.addAll(man.getTaskResults());
-			}
-		} catch (final SecurityException e) {
-			throw new RuntimeException(
-					"BUG: We're not using a security manager, so where " +
-					"does this exception come from?");
-		} catch (final NoSuchMethodException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Expected constructor undefined!");
-		} catch (final IllegalArgumentException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Expected constructor undefined!");
-		} catch (final InstantiationException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: InstantiationException.");
-		} catch (final IllegalAccessException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Expected constructor undefined!");
-		} catch (final InvocationTargetException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Constructor threw an exception.");
-		}
-	}
-	
-	public Network getCurrentNetwork() {
-		synchronized(TestExecutorImpl.class) {
-			if(currentNetwork == null) {
-				throw new RuntimeException("There is no network currently!");
-			}
-			return currentNetwork;
-		}
-	}
-
-	public void executeTaskForTorInstances(
-			final Collection<TorInstance> torInstances,
-			final Class<? extends Task> taskClass,
-			final Set<TaskResult> taskResults,
-			Collection<String> additionalInformation)
-			throws TaskExecutionNotSuccessfulException {
-
-		if (torInstances.isEmpty()) {
-			return;
-		}
-		final Set<MasterTask> mTasks = new HashSet<MasterTask>();
-		Constructor<? extends Task> ctor = null;
-		try {
-			ctor = taskClass.getConstructor(int.class, String.class,
-							String.class);
-			String networkName = "";
-			synchronized(TestExecutorImpl.class) {
-				if(currentNetwork == null) {
-					networkName = "";
-				} else {
-					networkName = currentNetwork.getName();
-				}
-			}
-			for (final TorInstance torInstance : torInstances) {
-				Task task = ctor.newInstance(nextTaskId.getAndIncrement(),
-						networkName, torInstance.getName());
-				task.addAdditionalInformation(additionalInformation);
-				mTasks.add(new MasterTask(torInstance.getMaster(), task));
-			}
-
-			final TaskManager man = new TaskManager(mTasks);
-			final FutureTask<Boolean> futureTask =
-				new FutureTask<Boolean>(man);
-			new Thread(futureTask).start();
-			try {
-				if (futureTask.get() == false) {
-					throw new TaskExecutionNotSuccessfulException(man
-							.getTaskResults());
-				}
-			} catch (final InterruptedException e) {
-				throw new TaskExecutionNotSuccessfulException(null);
-			} catch (final ExecutionException e) {
-				// XXX Log this with the cause. Should not happen. -SH
-				e.printStackTrace();
-				throw new TaskExecutionNotSuccessfulException(null);
-			}
-			if(taskResults != null) {
-				taskResults.addAll(man.getTaskResults());
-			}
-		} catch (final SecurityException e) {
-			throw new RuntimeException(
-					"BUG: We're not using a security manager, " +
-					"so where does this exception come from?");
-		} catch (final NoSuchMethodException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Expected constructor undefined!");
-		} catch (final IllegalArgumentException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Expected constructor undefined!");
-		} catch (final InstantiationException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: InstantiationException.");
-		} catch (final IllegalAccessException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Expected constructor undefined!");
-		} catch (final InvocationTargetException e) {
-			e.printStackTrace();
-			throw new RuntimeException("BUG: Constructor threw an exception.");
-		}
-	}
-
-	protected void makeNetworks(final Test test) {
-		final NetworkDescription nd = test.getNetworkDescription();
-		// XXX we want to do something much smarter here.-SH
-
-		// XXX obviously, the following code adds the specified amount of
-		// authorities, routers, and proxies for each slave, regardless of
-		// capabilities. That isn't good. -SH
-		for (final LocalMaster master : masters.values()) {
-			int necessaryDirectoryAuthorities =
-				nd.necessaryDirectoryAuthorities();
-			boolean onPrivateNetwork = (necessaryDirectoryAuthorities > 0);
-			// XXX the name choosing doesn't make sense. -SH
-			final Network nw =
-					new NetworkImpl(test.getDescription() + " slavename:"
-							+ master.getSlave().getName(), onPrivateNetwork);
-			while (necessaryDirectoryAuthorities >= 1) {
-				TorInstanceImpl authority = new TorInstanceImpl(master, 
-						"Authority" + necessaryDirectoryAuthorities);
-				authority.setDirectoryAuthority(true);
-				authority.setOnPrivateNetwork(onPrivateNetwork);
-				nw.addTorInstance(authority);
-				necessaryDirectoryAuthorities--;
-			}
-			int necessaryRouters = nd.necessaryRouters();
-			while (necessaryRouters >= 1) {
-				TorInstanceImpl router = new TorInstanceImpl(master, "Router" + 
-						necessaryRouters);
-				router.setRouter(true);
-				router.setOnPrivateNetwork(onPrivateNetwork);
-				nw.addTorInstance(router);
-				necessaryRouters--;
-			}
-			int necessaryProxies = nd.necessaryProxies();
-			while (necessaryProxies >= 1) {
-				TorInstanceImpl proxy = new TorInstanceImpl(master, "Proxy" +
-						necessaryProxies); 
-				proxy.setOnPrivateNetwork(onPrivateNetwork);
-				nw.addTorInstance(proxy);
-				necessaryProxies--;
-			}
-			nw.addTest(test);
-			networks.add(nw);
-		}
-	}
-
-	public Map<Test, TestResult> getFailedTests() {
-		return failedTests;
-	}
-
-	public Set<Test> getCannotRunTests() {
-		return cannotRunTests;
-	}
-
-	public Map<Test, TestResult> getSuccessfulTests() {
-		return successfulTests;
-	}
-
-	public void registerTest(final Test test) {
-		if (startedTesting.get() == true) {
-			throw new RuntimeException("Bug: Testing has started already.");
-		}
-		if (test == null) {
-			throw new NullPointerException("tests must not be null");
-		}
-		newTests.add(test);
-	}
-
-	private static class TaskManager implements Callable<Boolean> {
-
-		final private Set<TaskResult> taskResults =
-				new CopyOnWriteArraySet<TaskResult>();
-
-		final private Object taskResultsLock = new Object();
-
-		final private Set<MasterTask> tasks;
-
-		TaskManager(final Set<MasterTask> tasks) {
-			if (tasks.isEmpty()) {
-				throw new IllegalArgumentException("No tasks passed!");
-			}
-			this.tasks =
-					Collections.synchronizedSet(
-							new HashSet<MasterTask>(tasks));
-		}
-
-		public Boolean call() {
-			for (final MasterTask task : tasks) {
-				task.getMaster().addTask(task.getTask());
-				// XXX Asking for trouble here. task could throw an exception!
-				// -SH
-				new Thread(new TaskCollector(task)).start();
-			}
-			try {
-				synchronized (taskResultsLock) {
-					// XXX See above. This needs more thinking -SH
-					while (tasks.size() != taskResults.size()) {
-						taskResultsLock.wait();
-					}
-					// XXX We want some logic here.-SH
-					for (final TaskResult taskResult : taskResults) {
-						if (!taskResult.wasSuccessful()) {
-							return false;
-						}
-					}
-					return true;
-				}
-			} catch (final InterruptedException e) {
-				// XXX figure out a better solution here -SH
-				e.printStackTrace();
-			}
-			return false;
-		}
-
-		public Set<TaskResult> getTaskResults() {
-			return taskResults;
-		}
-
-		private class TaskCollector implements Runnable {
-			private final MasterTask task;
-
-			public void run() {
-				try {
-					final TaskResult taskResult =
-							task.getMaster().getTaskResult(
-									task.getTask().getId());
-					synchronized (taskResultsLock) {
-						taskResults.add(taskResult);
-						taskResultsLock.notifyAll();
-					}
-				} catch (final InterruptedException e) {
-					e.printStackTrace();
-					Thread.currentThread().interrupt();
-				}
-
-			}
-
-			protected TaskCollector(final MasterTask task) {
-				this.task = task;
-			}
-		}
-	}
-
-	private static class MasterTask {
-		final private LocalMaster master;
-		final private Task task;
-
-		MasterTask(final LocalMaster master, final Task task) {
-			this.master = master;
-			this.task = task;
-		}
-
-		public LocalMaster getMaster() {
-			return master;
-		}
-
-		public Task getTask() {
-			return task;
-		}
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/TorInstanceImpl.java b/src/de/uniba/wiai/lspi/puppetor/rmi/impl/TorInstanceImpl.java
deleted file mode 100644
index 4d1ff5b..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/impl/TorInstanceImpl.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.impl;
-
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import de.uniba.wiai.lspi.puppetor.rmi.LocalMaster;
-import de.uniba.wiai.lspi.puppetor.rmi.TorInstance;
-
-/**
- * Implements a Tor node. Threadsafe.
- *
- * @author Sebastian Hahn
- */
-public class TorInstanceImpl implements TorInstance {
-
-	/**
-	 * Saves configuration options in an "option value" format.
-	 */
-	private final List<String> configuration = new LinkedList<String>();
-
-	/**
-	 * The name of this node.
-	 */
-	private final String name;
-
-	/**
-	 * The svn revision of the Tor build. 0 means no specified revision.
-	 */
-	private final AtomicInteger revision = new AtomicInteger(0);
-
-	/**
-	 * Whether we are a bridge authority.
-	 */
-	private final AtomicBoolean bridgeAuthority = new AtomicBoolean(false);
-
-	/**
-	 * Whether we are a directory authority.
-	 */
-	final private AtomicBoolean directoryAuthority = new AtomicBoolean(false);
-
-	/**
-	 * Whether we are an exit.
-	 */
-	private final AtomicBoolean exit = new AtomicBoolean(false);
-
-	/**
-	 * Whether we are a relay.
-	 */
-	private final AtomicBoolean router = new AtomicBoolean(false);
-
-	/**
-	 * Whether we are a bridge.
-	 */
-	private final AtomicBoolean bridge = new AtomicBoolean(false);
-
-	/**
-	 * Whether we are on a private network.
-	 */
-	private AtomicBoolean privateNetwork = new AtomicBoolean(false);
-
-	/**
-	 * Whether the configuration of the tor instance should be replaced by the
-	 * config returned from <code>this.getConfiguration()</code>.
-	 */
-	private AtomicBoolean replaceConfiguration = new AtomicBoolean(false);
-
-	/**
-	 * Provide a link to the slave where this torInstance will run.
-	 */
-	private final LocalMaster master;
-
-	/**
-	 * @param master
-	 * 		the <code>LocalMaster</code> that represents the slave on which
-	 * 		this Tor instance is running.
-	 * @param name
-	 * 		A name to identify the node.
-	 * 		XXX think about duplicates, we need a unique name to identify the
-	 * 		node, but is it smart to use that name as the Tor proxy's name as
-	 * 		well? -SH
-	 */
-	TorInstanceImpl(final LocalMaster master, final String name) {
-		this.master = master;
-		this.name = name;
-	}
-
-	public synchronized List<String> getConfiguration() {
-		return Collections.unmodifiableList(configuration);
-	}
-	
-	public synchronized void addConfiguration(List<String> newConfig) {
-		configuration.addAll(newConfig);
-	}
-
-	public int getRevision() {
-		return revision.get();
-	}
-	
-	public void setRevision(final int revision) {
-		this.revision.set(revision);
-	}
-
-	public boolean isBridgeAuthority() {
-		return bridgeAuthority.get();
-	}
-	
-	public void setBridgeAuthority(boolean isBridgeAuthority) {
-		bridgeAuthority.set(isBridgeAuthority);
-	}
-
-	public boolean isDirectoryAuthority() {
-		return directoryAuthority.get();
-	}
-	
-	public void setDirectoryAuthority(boolean isDirectoryAuthority) {
-		if(isDirectoryAuthority) {
-			setRouter(true);
-		}
-		directoryAuthority.set(isDirectoryAuthority);
-	}
-
-	public boolean isExit() {
-		return exit.get();
-	}
-
-	public boolean isRouter() {
-		return router.get();
-	}
-	
-	public void setRouter(boolean isRouter) {
-		if(!isRouter) {
-			setDirectoryAuthority(false);
-		}
-		router.set(isRouter);
-	}
-
-	public boolean isOnPrivateNetwork() {
-		return privateNetwork.get();
-	}
-	
-	public void setOnPrivateNetwork(boolean onPrivateNetwork) {
-		privateNetwork.set(onPrivateNetwork);
-	}
-
-	public boolean shouldReplaceConfiguration() {
-		return replaceConfiguration.get();
-	}
-
-	public void setShouldReplaceConfiguration(
-			boolean shouldReplaceConfiguration) {
-		replaceConfiguration.set(shouldReplaceConfiguration);
-	}
-
-	public boolean isBridge() {
-		return bridge.get();
-	}
-
-	public LocalMaster getMaster() {
-		return master;
-	}
-
-	public synchronized String getName() {
-		return name;
-	}
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/AccessGoogleTask.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/AccessGoogleTask.java
deleted file mode 100644
index 52bf7eb..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/AccessGoogleTask.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tasks;
-
-import java.rmi.RemoteException;
-
-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.rmi.impl.AbstractTaskImpl;
-
-public class AccessGoogleTask extends AbstractTaskImpl {
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The name of this tor node.
-	 */
-	private final String nodeName;
-	
-	public AccessGoogleTask(final int id, final String networkName,
-			final String nodeName) {
-		super(id, networkName);
-		this.nodeName = nodeName;
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		
-		final ClientApplication client =
-			network.createClient("client", "www.google.com", 80, network.getNode(nodeName)
-					.getSocksPort());
-
-		// create event listener to listen for client application events
-		final EventListener clientEventListener = new EventListener() {
-	
-			// remember time when request was sent
-			//private long before;
-	
-			public void handleEvent(Event event) {
-				if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
-					//before = System.currentTimeMillis();
-				} else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
-					// XXX Log how long requests took, see whether we need this all -SH
-					;
-				}
-			}
-		};
-	
-		// obtain reference to event manager to be able to respond to events
-		final EventManager manager = network.getEventManager();
-	
-		manager.addEventListener(client.getClientApplicationName(),
-				clientEventListener);
-
-		// perform at most three request with a timeout of 20 seconds each
-		client.startRequests(3, 20000, true);
-	
-		// block this thread as long as client requests are running, but don't wait forever.
-		reportResult(manager.waitForAnyOccurence(client.getClientApplicationName(),
-				ClientEventType.CLIENT_REQUESTS_PERFORMED, 75000));
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/AddHiddenServiceTask.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/AddHiddenServiceTask.java
deleted file mode 100644
index fc30b5d..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/AddHiddenServiceTask.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tasks;
-
-import java.rmi.RemoteException;
-import java.util.Set;
-
-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.rmi.impl.AbstractTaskImpl;
-
-public class AddHiddenServiceTask extends AbstractTaskImpl {
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The name of this tor node.
-	 */
-	private final String nodeName;
-	
-	public AddHiddenServiceTask(final int id, final String networkName,
-			final String nodeName) {
-		super(id, networkName);
-		this.nodeName = nodeName;
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		ProxyNode node = network.getNode(nodeName);
-		try {
-			// add hidden service
-			final HiddenService hidServ = node.addHiddenService("hidServ");
-			node.writeConfiguration();
-			node.hup();
-			final EventManager manager = network.getEventManager();
-	
-			// wait for 6 minutes that the proxy has published its first RSD
-			if (!manager.waitForAnyOccurence(node.getNodeName(),
-					HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
-					6L * 60L * 1000L)) {
-				// failed to publish an RSD
-				reportResult(false);
-				return;
-			}
-	
-			// create server application
-			final ServerApplication server =
-					network.createServer("server", hidServ.getServicePort());
-	
-			// create client application, pick one of the nodes.
-			// XXX fix this so we can choose which node we want. -SH
-			Set<String> nodes = network.getAllNodes().keySet();
-			String[] localNodeNames = new String[10];
-			localNodeNames = nodes.toArray(localNodeNames);
-			final ClientApplication client =
-					network.createClient("client",
-							hidServ.determineOnionAddress(), hidServ
-									.getVirtualPort(), network.getNode(localNodeNames[0]).getSocksPort());
-	
-			// register event listener
-			final EventListener clientAndServerEventListener = new EventListener() {
-				public void handleEvent(Event event) {
-					// XXX log -SH
-				}
-			};
-			manager.addEventListener(client.getClientApplicationName(),
-					clientAndServerEventListener);
-			manager.addEventListener(server.getServerApplicationName(),
-					clientAndServerEventListener);
-	
-			// start server
-			server.startListening();
-	
-			// perform at most five request with a timeout of 45 seconds each
-			client.startRequests(5, 45000, true);
-	
-			// wait for request to be performed
-			manager.waitForAnyOccurence(client.getClientApplicationName(),
-					ClientEventType.CLIENT_REQUESTS_PERFORMED);
-			reportResult(true);
-			} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-
-
-	}
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/BuildCircuitsTask.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/BuildCircuitsTask.java
deleted file mode 100644
index 5c002c7..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/BuildCircuitsTask.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tasks;
-
-import java.rmi.RemoteException;
-
-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.rmi.impl.AbstractTaskImpl;
-
-public class BuildCircuitsTask extends AbstractTaskImpl {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	public BuildCircuitsTask(final int id, final String networkName) {
-		super(id, networkName);
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		try {
-			if (!network.hupUntilUp(50, 10000)) {
-				reportResult(false);
-			}
-		} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-		reportResult(true);
-	}
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/ConfigureAsPrivateTask.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/ConfigureAsPrivateTask.java
deleted file mode 100644
index 464df65..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/ConfigureAsPrivateTask.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tasks;
-
-import java.rmi.RemoteException;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-
-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.rmi.impl.AbstractTaskImpl;
-
-public class ConfigureAsPrivateTask extends AbstractTaskImpl {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-	
-	final List<String> authorityInformation = new LinkedList<String>();
-
-	public ConfigureAsPrivateTask(final int id, final String networkName) {
-		super(id, networkName);
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		network.configureAsPartOfPrivateNetwork(authorityInformation);
-		try {
-			network.writeConfigurations();
-			reportResult(true);
-		} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-	}
-	
-	@Override
-	public void addAdditionalInformation(
-			Collection<String> additionalInformation) {
-		authorityInformation.addAll(additionalInformation);
-	}
-}
\ No newline at end of file
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateDirectoryAuthorityTask.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateDirectoryAuthorityTask.java
deleted file mode 100644
index 7183d7d..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateDirectoryAuthorityTask.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tasks;
-
-import java.rmi.RemoteException;
-
-import de.uniba.wiai.lspi.puppetor.DirectoryNode;
-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.rmi.impl.AbstractTaskImpl;
-
-public class CreateDirectoryAuthorityTask extends AbstractTaskImpl {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The name of this Tor authority.
-	 */
-	private final String authorityName;
-
-	public CreateDirectoryAuthorityTask(final int id, final String networkName,
-			final String authorityName) {
-		super(id, networkName);
-		this.authorityName = authorityName;
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		DirectoryNode node = network.createDirectory(authorityName);
-		// write configuration of proxy node
-		try {
-			network.writeConfigurations();
-			reportResult(true, node.getDirServerString());
-		} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateNetworkTask.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateNetworkTask.java
deleted file mode 100644
index 5c9f919..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateNetworkTask.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tasks;
-
-import java.rmi.RemoteException;
-
-import de.uniba.wiai.lspi.puppetor.NetworkFactory;
-import de.uniba.wiai.lspi.puppetor.rmi.impl.AbstractTaskImpl;
-
-public class CreateNetworkTask extends AbstractTaskImpl {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	public CreateNetworkTask(final int id, final String name) {
-		super(id, name);
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		NetworkFactory.createNetwork(name);
-		reportResult(true);
-	}
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateProxyTask.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateProxyTask.java
deleted file mode 100644
index 90fd6a1..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateProxyTask.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tasks;
-
-import java.rmi.RemoteException;
-
-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.rmi.impl.AbstractTaskImpl;
-
-public class CreateProxyTask extends AbstractTaskImpl {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The name of this tor proxy.
-	 */
-	private final String proxyName;
-
-	public CreateProxyTask(final int id, final String networkName,
-			final String proxyName) {
-		super(id, networkName);
-		this.proxyName = proxyName;
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		network.createProxy(proxyName);
-
-		// write configuration of proxy node
-		try {
-			network.writeConfigurations();
-			reportResult(true);
-		} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateRouterTask.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateRouterTask.java
deleted file mode 100644
index 5e1c170..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/CreateRouterTask.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tasks;
-
-import java.rmi.RemoteException;
-
-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.rmi.impl.AbstractTaskImpl;
-
-public class CreateRouterTask extends AbstractTaskImpl {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * The name of this router.
-	 */
-	private final String routerName;
-
-	public CreateRouterTask(final int id, final String networkName,
-			final String routerName) {
-		super(id, networkName);
-		this.routerName = routerName;
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		network.createRouter(routerName);
-		// write configuration of router node
-		try {
-			network.writeConfigurations();
-			reportResult(true);
-		} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/ShutdownNodesTask.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/ShutdownNodesTask.java
deleted file mode 100644
index 9308340..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/ShutdownNodesTask.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tasks;
-
-import java.rmi.RemoteException;
-
-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.rmi.impl.AbstractTaskImpl;
-
-public class ShutdownNodesTask extends AbstractTaskImpl {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	public ShutdownNodesTask(final int id, final String name) {
-		super(id, name);
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		try {
-			network.shutdownNodes();
-			reportResult(true);
-		} catch (final PuppeTorException e) {
-			reportResult(false);
-		}
-	}
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/StartNodesTask.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/StartNodesTask.java
deleted file mode 100644
index 39716a0..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/StartNodesTask.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tasks;
-
-import java.rmi.RemoteException;
-
-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.rmi.impl.AbstractTaskImpl;
-
-public class StartNodesTask extends AbstractTaskImpl {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	public StartNodesTask(final int id, final String networkName) {
-		super(id, networkName);
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		final Network network = NetworkFactory.getNetworkByName(name);
-		try {
-			if (!network.startNodes(5000)) {
-				// failed to start the proxy
-				reportResult(false);
-			}
-		} catch (final PuppeTorException e) {
-			e.printStackTrace();
-			reportResult(false);
-		}
-		reportResult(true);
-	}
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/TerminateTask.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/TerminateTask.java
deleted file mode 100644
index 8fc5b95..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tasks/TerminateTask.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tasks;
-
-import java.rmi.RemoteException;
-
-import de.uniba.wiai.lspi.puppetor.rmi.impl.AbstractTaskImpl;
-
-public class TerminateTask extends AbstractTaskImpl {
-
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-
-	public TerminateTask(final int id, final String name) {
-		super(id, name);
-	}
-
-	@Override
-	public void execute() throws InterruptedException, RemoteException {
-		Thread.currentThread().interrupt();
-		reportResult(true);
-	}
-
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tests/AccessGoogleOverPublicTorNetwork.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tests/AccessGoogleOverPublicTorNetwork.java
deleted file mode 100644
index 2af3c74..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tests/AccessGoogleOverPublicTorNetwork.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tests;
-
-import java.util.Set;
-
-import de.uniba.wiai.lspi.puppetor.rmi.Network;
-import de.uniba.wiai.lspi.puppetor.rmi.TaskExecutionNotSuccessfulException;
-import de.uniba.wiai.lspi.puppetor.rmi.TorInstance;
-import de.uniba.wiai.lspi.puppetor.rmi.impl.AbstractTestImpl;
-import de.uniba.wiai.lspi.puppetor.rmi.impl.NetworkDescriptionImpl;
-import de.uniba.wiai.lspi.puppetor.rmi.tasks.AccessGoogleTask;
-
-public class AccessGoogleOverPublicTorNetwork extends AbstractTestImpl {
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-	
-	public AccessGoogleOverPublicTorNetwork() {
-		super("Use the public Tor network to connect to google.com and wait" +
-				" for a response", new NetworkDescriptionImpl(1, 0, 0));
-	}
-
-	public void doTest() {
-		Network nw = exec.getCurrentNetwork();
-		Set<TorInstance> proxies = nw.getProxies();
-		try {
-			exec.executeTaskForTorInstances(proxies, AccessGoogleTask.class, null, null);
-			System.out.println("Accessing google succeeded");
-		} catch (TaskExecutionNotSuccessfulException e) {
-			System.out.println("Accessing google failed");
-			// Log this properly, especially where we failed
-		}
-	}
-}
\ No newline at end of file
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tests/StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tests/StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest.java
deleted file mode 100644
index d8f5738..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tests/StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tests;
-
-import java.util.Set;
-
-import de.uniba.wiai.lspi.puppetor.rmi.Network;
-import de.uniba.wiai.lspi.puppetor.rmi.TaskExecutionNotSuccessfulException;
-import de.uniba.wiai.lspi.puppetor.rmi.TorInstance;
-import de.uniba.wiai.lspi.puppetor.rmi.impl.AbstractTestImpl;
-import de.uniba.wiai.lspi.puppetor.rmi.impl.NetworkDescriptionImpl;
-import de.uniba.wiai.lspi.puppetor.rmi.tasks.AddHiddenServiceTask;
-
-public class StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest extends
-		AbstractTestImpl {
-	/**
-	 * Required for serialization. Needs to change for new released versions.
-	 */
-	private static final long serialVersionUID = 1L;
-	
-	public StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest() {
-		super("Create a private Tor network, set up a hidden service, " +
-				"and access it", new NetworkDescriptionImpl(1, 3, 2));
-	}
-	
-	public void doTest() {
-		Network nw = exec.getCurrentNetwork();
-		Set<TorInstance> proxies = nw.getProxiesOnly();
-		try {
-			exec.executeTaskForTorInstances(proxies, AddHiddenServiceTask.class, null, null);
-			System.out.println("Accessing the hidden service succeeded.");
-		} catch (TaskExecutionNotSuccessfulException e) {
-			System.out.println("Accessing the hidden service failed.");
-			// Log this properly, especially where we failed
-		}
-	}
-}
diff --git a/src/de/uniba/wiai/lspi/puppetor/rmi/tests/TestRegistration.java b/src/de/uniba/wiai/lspi/puppetor/rmi/tests/TestRegistration.java
deleted file mode 100644
index 3e55a32..0000000
--- a/src/de/uniba/wiai/lspi/puppetor/rmi/tests/TestRegistration.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2007 Karsten Loesing
- * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
- * See LICENSE for licensing information
- */
-package de.uniba.wiai.lspi.puppetor.rmi.tests;
-
-import de.uniba.wiai.lspi.puppetor.rmi.TestExecutor;
-
-public class TestRegistration {
-
-	private static TestRegistration instance = new TestRegistration();
-	
-	public void registerTests(TestExecutor testExecutor) {
-		testExecutor.registerTest(new AccessGoogleOverPublicTorNetwork());
-		testExecutor.registerTest(new
-				StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest());
-	}
-	
-	/**
-	 * Singleton
-	 */
-	private TestRegistration() {}
-	
-	/**
-	 * @return
-	 * 		the <code>TestRegistration</code> instance.
-	 */
-	public static TestRegistration getInstance() {
-		return instance;
-	}
-
-}
diff --git a/src/org/torproject/puppetor/ClientApplication.java b/src/org/torproject/puppetor/ClientApplication.java
new file mode 100644
index 0000000..a1b27a0
--- /dev/null
+++ b/src/org/torproject/puppetor/ClientApplication.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.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/org/torproject/puppetor/ClientEventType.java b/src/org/torproject/puppetor/ClientEventType.java
new file mode 100644
index 0000000..0b5c81e
--- /dev/null
+++ b/src/org/torproject/puppetor/ClientEventType.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor;
+
+/**
+ * Event types that can be fired by a client application running as thread in
+ * the background.
+ */
+ at SuppressWarnings("serial")
+public class ClientEventType implements EventType {
+
+	/**
+	 * String identifying the type of the event type.
+	 */
+	String typeString;
+
+	/**
+	 * Creates a new event type with the given type string.
+	 *
+	 * @param typeString
+	 *            String identifying the type of the event type.
+	 */
+	public ClientEventType(final String typeString) {
+		this.typeString = typeString;
+	}
+
+	public String getTypeName() {
+		return typeString;
+	}
+
+	/**
+	 * The client application is sending a request; this event is fired
+	 * internally and not parsed from a log statement from Tor.
+	 */
+	public static final ClientEventType CLIENT_SENDING_REQUEST =
+			new ClientEventType("CLIENT_SENDING_REQUEST");
+
+	/**
+	 * The client application has received a reply to a previously sent request;
+	 * this event is fired internally and not parsed from a log statement from
+	 * Tor.
+	 */
+	public static final ClientEventType CLIENT_REPLY_RECEIVED =
+			new ClientEventType("CLIENT_REPLY_RECEIVED");
+
+	/**
+	 * The client application has given up waiting for the reply to a previously
+	 * sent request; this event is fired internally and not parsed from a log
+	 * statement from Tor.
+	 */
+	public static final ClientEventType CLIENT_GAVE_UP_REQUEST =
+			new ClientEventType("CLIENT_GAVE_UP_REQUEST");
+
+	/**
+	 * The client application has completed a series of requests, whether they
+	 * were successful or not; this event is fired internally and not parsed
+	 * from a log statement from Tor.
+	 */
+	public static final ClientEventType CLIENT_REQUESTS_PERFORMED =
+			new ClientEventType("CLIENT_REQUESTS_PERFORMED");
+}
diff --git a/src/org/torproject/puppetor/DirectoryNode.java b/src/org/torproject/puppetor/DirectoryNode.java
new file mode 100644
index 0000000..41fa6a2
--- /dev/null
+++ b/src/org/torproject/puppetor/DirectoryNode.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.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/org/torproject/puppetor/Event.java b/src/org/torproject/puppetor/Event.java
new file mode 100644
index 0000000..03626b1
--- /dev/null
+++ b/src/org/torproject/puppetor/Event.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor;
+
+import java.io.Serializable;
+
+/**
+ * An <code>Event</code> is created for every state change of an asynchronous
+ * system component, e.g. a Tor process or a client/server application running
+ * as thread in the background. In contrast to <code>NodeState</code> an
+ * <code>Event</code> cannot be a pre- or postconditions for a method
+ * invocation. There is no prescribed order in which events are fired by a
+ * certain process or application. Some events can be fired only once, others
+ * possibly multiple times. All management operations for events are contained
+ * in the <code>EventManager</code>.
+ *
+ * @author kloesing
+ */
+public interface Event extends Serializable {
+
+	/**
+	 * Returns the name of the source of this event, which can be a Tor process
+	 * or a client/server application running as thread in the background.
+	 *
+	 * @return The event source.
+	 */
+	public abstract String getSource();
+
+	/**
+	 * Returns the event type.
+	 *
+	 * @return The event type.
+	 */
+	public abstract EventType getType();
+
+	/**
+	 * Returns the original message that lead to firing this event.
+	 *
+	 * @return The original message.
+	 */
+	public abstract String getMessage();
+
+	/**
+	 * Returns the occurrence time of this event, which is either the parsed
+	 * time from a Tor log statement, or the current system time when a
+	 * client/server application fired this event.
+	 *
+	 * @return The occurrence time.
+	 */
+	public abstract long getOccurrenceTime();
+}
diff --git a/src/org/torproject/puppetor/EventListener.java b/src/org/torproject/puppetor/EventListener.java
new file mode 100644
index 0000000..099cfec
--- /dev/null
+++ b/src/org/torproject/puppetor/EventListener.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor;
+
+/**
+ * This interface must be implemented by any object in a test application that
+ * shall be registered as event listener.
+ *
+ * @author kloesing
+ */
+public interface EventListener {
+
+	/**
+	 * Is invoked when an asynchronous event is fired by the source (or one of
+	 * the sources) for which this listener was registered.
+	 *
+	 * @param event
+	 *            The event that was fired.
+	 */
+	public void handleEvent(Event event);
+
+}
diff --git a/src/org/torproject/puppetor/EventManager.java b/src/org/torproject/puppetor/EventManager.java
new file mode 100644
index 0000000..eafb9c1
--- /dev/null
+++ b/src/org/torproject/puppetor/EventManager.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.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/org/torproject/puppetor/EventType.java b/src/org/torproject/puppetor/EventType.java
new file mode 100644
index 0000000..4923984
--- /dev/null
+++ b/src/org/torproject/puppetor/EventType.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor;
+
+import java.io.Serializable;
+
+/**
+ * The super interface of possible event types that are fired on a state change
+ * of an asynchronous system component, e.g. a Tor process or a client/server
+ * application running as thread in the background.
+ *
+ * @author kloesing
+ */
+public interface EventType extends Serializable {
+
+	/**
+	 * Returns a string representation of the event type name for display
+	 * purposes.
+	 *
+	 * @return String representation of the event type name.
+	 */
+	public abstract String getTypeName();
+}
diff --git a/src/org/torproject/puppetor/HiddenService.java b/src/org/torproject/puppetor/HiddenService.java
new file mode 100644
index 0000000..26975fb
--- /dev/null
+++ b/src/org/torproject/puppetor/HiddenService.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor;
+
+/**
+ * A <code>HiddenService</code> instance contains all configurations of a
+ * hidden service that is registered at a node.
+ *
+ * @author kloesing
+ */
+public interface HiddenService {
+
+	/**
+	 * Determines the onion address for a previously added hidden service with
+	 * name <code>serviceName</code>. Requires that the node has been
+	 * started, i.e. is in state <code>NodeState.RUNNING</code>, and is
+	 * configured to provide this hidden service.
+	 *
+	 * @return The onion address string consisting of 16 base32 chars plus
+	 *         ".onion" for hidden service versions 0 and 1 or 16 base32 chars
+	 *         plus "." plus 24 base32 chars plus ".onion" for hidden service
+	 *         version 2.
+	 * @throws IllegalArgumentException
+	 *             Thrown if <code>null</code> or a zero-length string is
+	 *             passed as parameter.
+	 * @throws IllegalStateException
+	 *             Thrown if the node at which this hidden service is configured
+	 *             is not in state <code>NodeState.RUNNING</code>.
+	 * @throws PuppeTorException
+	 *             Thrown if the onion address of this hidden service could not
+	 *             be read, which is also the case when the node's configuration
+	 *             has not been written and the node has not been HUP'ed after
+	 *             configuring the hidden service.
+	 */
+	public abstract String determineOnionAddress() throws PuppeTorException;
+
+	/**
+	 * Returns the name of the hidden service.
+	 *
+	 * @return The name of the hidden service.
+	 */
+	public String getServiceName();
+
+	/**
+	 * Returns the port on which the service listens for requests.
+	 *
+	 * @return The port on which the service listens for requests.
+	 */
+	public int getServicePort();
+
+	/**
+	 * Returns the virtual port that this hidden service runs on as it is
+	 * announced to clients.
+	 *
+	 * @return The virtual port of this hidden service.
+	 */
+	public int getVirtualPort();
+}
diff --git a/src/org/torproject/puppetor/HiddenServiceEventType.java b/src/org/torproject/puppetor/HiddenServiceEventType.java
new file mode 100644
index 0000000..5d29f1f
--- /dev/null
+++ b/src/org/torproject/puppetor/HiddenServiceEventType.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor;
+
+/**
+ * Event types that can be fired by all Tor processes performing hidden-service
+ * operations.
+ */
+ at SuppressWarnings("serial")
+public class HiddenServiceEventType implements EventType {
+
+	/**
+	 * String identifying the type of the event type.
+	 */
+	String typeString;
+
+	/**
+	 * Creates a new event type with the given type string.
+	 *
+	 * @param typeString
+	 *            String identifying the type of the event type.
+	 */
+	public HiddenServiceEventType(final String typeString) {
+		this.typeString = typeString;
+	}
+
+	public String getTypeName() {
+		return typeString;
+	}
+
+	/**
+	 * Alice has received an onion request; this event is parsed from a log
+	 * statement in connection_ap_handshake_rewrite_and_attach().
+	 */
+	public static final HiddenServiceEventType ALICE_ONION_REQUEST_RECEIVED =
+			new HiddenServiceEventType("ALICE_ONION_REQUEST_RECEIVED");
+
+	/**
+	 * Alice sends a fetch request for a hidden service descriptor to a
+	 * directory server; this event is parsed from a log statement in
+	 * rend_client_refetch_renddesc().
+	 */
+	public static final HiddenServiceEventType ALICE_SENDING_FETCH_DESC =
+			new HiddenServiceEventType("ALICE_SENDING_FETCH_DESC");
+
+	/**
+	 * Alice receives a reply to a previous fetch request for a hidden service
+	 * descriptors from a directory server; this event is parsed from a log
+	 * statement in connection_dir_client_reached_eof().
+	 */
+	public static final HiddenServiceEventType ALICE_DESC_FETCHED_RECEIVED =
+			new HiddenServiceEventType("ALICE_DESC_FETCHED_RECEIVED");
+
+	/**
+	 * Alice has built a circuit to a rendezvous point and sends an
+	 * ESTABLISH_RENDEZVOUS cell; this event is parsed from a log statement in
+	 * rend_client_send_establish_rendezvous().
+	 */
+	public static final HiddenServiceEventType ALICE_BUILT_REND_CIRC_SENDING_ESTABLISH_RENDEZVOUS =
+			new HiddenServiceEventType(
+					"ALICE_BUILT_REND_CIRC_SENDING_ESTABLISH_RENDEZVOUS");
+
+	/**
+	 * Alice receives a RENDEZVOUS_ESTABLISHED cell from a rendezvous point;
+	 * this event is parsed from a log statement in
+	 * rend_client_rendezvous_acked().
+	 */
+	public static final HiddenServiceEventType ALICE_RENDEZVOUS_ESTABLISHED_RECEIVED =
+			new HiddenServiceEventType("ALICE_RENDEZVOUS_ESTABLISHED_RECEIVED");
+
+	/**
+	 * Alice has built a circuit to an introduction point (which does not
+	 * automatically lead to sending an INTRODUCE1 cell, because the rendezvous
+	 * circuit might not be ready); this event is parsed from a log statement in
+	 * rend_client_introcirc_has_opened().
+	 */
+	public static final HiddenServiceEventType ALICE_BUILT_INTRO_CIRC =
+			new HiddenServiceEventType("ALICE_BUILT_INTRO_CIRC");
+
+	/**
+	 * Alice sends an INTRODUCE1 cell to an introduction point; this event is
+	 * parsed from a log statement in rend_client_send_introduction().
+	 */
+	public static final HiddenServiceEventType ALICE_SENDING_INTRODUCE1 =
+			new HiddenServiceEventType("ALICE_SENDING_INTRODUCE1");
+
+	/**
+	 * Alice has received an INTRODUCE_ACK cell as an acknowledgement to a
+	 * previously sent INTRODUCE1 cell; this event is parsed from a log
+	 * statement in rend_client_introduction_acked().
+	 */
+	public static final HiddenServiceEventType ALICE_INTRODUCE_ACK_RECEIVED =
+			new HiddenServiceEventType("ALICE_INTRODUCE_ACK_RECEIVED");
+
+	/**
+	 * Alice has received a RENDEZVOUS2 cell and can now open an application
+	 * connection to the client; this event is parsed from a log statement in
+	 * rend_client_receive_rendezvous().
+	 */
+	public static final HiddenServiceEventType ALICE_RENDEZVOUS2_RECEIVED_APP_CONN_OPENED =
+			new HiddenServiceEventType(
+					"ALICE_RENDEZVOUS2_RECEIVED_APP_CONN_OPENED");
+
+	/**
+	 * Bob has built a circuit to an introduction point and sends an
+	 * ESTABLISH_INTRO cell; this event is parsed from a log statement in
+	 * rend_service_intro_has_opened().
+	 */
+	public static final HiddenServiceEventType BOB_BUILT_INTRO_CIRC_SENDING_ESTABLISH_INTRO =
+			new HiddenServiceEventType(
+					"BOB_BUILT_INTRO_CIRC_SENDING_ESTABLISH_INTRO");
+
+	/**
+	 * Bob has received an INTRO_ESTABLISHED cell, i.e. a node has confirmed to
+	 * work as introduction point; this event is parsed from a log statement in
+	 * rend_service_intro_established().
+	 */
+	public static final HiddenServiceEventType BOB_INTRO_ESTABLISHED_RECEIVED =
+			new HiddenServiceEventType("BOB_INTRO_ESTABLISHED_RECEIVED");
+
+	/**
+	 * Bob posts a hidden service descriptor to the directory servers; this
+	 * event is parsed from a log statement in upload_service_descriptor().
+	 */
+	public static final HiddenServiceEventType BOB_SENDING_PUBLISH_DESC =
+			new HiddenServiceEventType("BOB_SENDING_PUBLISH_DESC");
+
+	/**
+	 * Bob received a response from a directory server to a previous publish
+	 * request; this event is parsed from a log statement in
+	 * connection_dir_client_reached_eof().
+	 */
+	public static final HiddenServiceEventType BOB_DESC_PUBLISHED_RECEIVED =
+			new HiddenServiceEventType("BOB_DESC_PUBLISHED_RECEIVED");
+
+	/**
+	 * Bob has received an INTRODUCE2 cell, i.e. a node wants to establish a
+	 * connection, and will now try to establish a circuit to the client's
+	 * rendezvous point; this event is parsed from a log statement in
+	 * rend_service_introduce().
+	 */
+	public static final HiddenServiceEventType BOB_INTRODUCE2_RECEIVED =
+			new HiddenServiceEventType("BOB_INTRODUCE2_RECEIVED");
+
+	/**
+	 * Bob has built a circuit to a rendezvous point and sends a RENDEZVOUS1
+	 * cell; this event is parsed from a log statement in
+	 * rend_service_rendezvous_has_opened().
+	 */
+	public static final HiddenServiceEventType BOB_BUILT_REND_CIRC_SENDING_RENDEZVOUS1 =
+			new HiddenServiceEventType(
+					"BOB_BUILT_REND_CIRC_SENDING_RENDEZVOUS1");
+
+	/**
+	 * Bob opens a connection to the actual hidden server; this event is parsed
+	 * from a log statement in connection_exit_begin_conn().
+	 */
+	public static final HiddenServiceEventType BOB_APP_CONN_OPENED =
+			new HiddenServiceEventType("BOB_APP_CONN_OPENED");
+
+	/**
+	 * The directory server has received a descriptor post request; this event
+	 * is parsed from a log statement in directory_handle_command_post().
+	 */
+	public static final HiddenServiceEventType DIR_PUBLISH_DESC_RECEIVED =
+			new HiddenServiceEventType("DIR_PUBLISH_DESC_RECEIVED");
+
+	/**
+	 * The directory server has received a descriptor fetch request; this event
+	 * is parsed from a log statement in directory_handle_command_get().
+	 */
+	public static final HiddenServiceEventType DIR_FETCH_DESC_RECEIVED =
+			new HiddenServiceEventType("DIR_FETCH_DESC_RECEIVED");
+
+	/**
+	 * The node received an ESTABLISH_INTRO cell, i.e. was requested to work as
+	 * introduction point, and replies with an INTRO_ESTABLISHED cell; this
+	 * event is parsed from a log statement in rend_mid_establish_intro().
+	 */
+	public static final HiddenServiceEventType IPO_RECEIVED_ESTABLISH_INTRO_SENDING_INTRO_ESTABLISHED =
+			new HiddenServiceEventType(
+					"IPO_RECEIVED_ESTABLISH_INTRO_SENDING_INTRO_ESTABLISHED");
+
+	/**
+	 * The introduction point received an INTRODUCE1 cell and reacts by sending
+	 * an INTRODUCE2 cell to Bob and an INTRODUCE_ACK cell to Alice; this event
+	 * is parsed from a log statement in rend_mid_introduce().
+	 */
+	public static final HiddenServiceEventType IPO_RECEIVED_INTRODUCE1_SENDING_INTRODUCE2_AND_INTRODUCE_ACK =
+			new HiddenServiceEventType(
+					"IPO_RECEIVED_INTRODUCE1_SENDING_INTRODUCE2_AND_INTRODUCE_ACK");
+
+	/**
+	 * The node received an ESTABLISH_RENDEZVOUS cell, i.e. was requested to
+	 * work as rendezvous point, and replies with a RENDEZVOUS_ESTABLISHED cell;
+	 * this event is parsed from a log statement in
+	 * rend_mid_establish_rendezvous().
+	 */
+	public static final HiddenServiceEventType RPO_RECEIVED_ESTABLISH_RENDEZVOUS_SENDING_RENDEZVOUS_ESTABLISHED =
+			new HiddenServiceEventType(
+					"RPO_RECEIVED_ESTABLISH_RENDEZVOUS_SENDING_RENDEZVOUS_ESTABLISHED");
+
+	/**
+	 * The rendezvous point received a RENDEZVOUS1 cell and reacts by sending a
+	 * RENDEZVOUS2 cell to Alice; this event is parsed from a log statement in
+	 * rend_mid_rendezvous().
+	 */
+	public static final HiddenServiceEventType RPO_RECEIVING_RENDEZVOUS1_SENDING_RENDEZVOUS2 =
+			new HiddenServiceEventType(
+					"RPO_RECEIVING_RENDEZVOUS1_SENDING_RENDEZVOUS2");
+
+}
\ No newline at end of file
diff --git a/src/org/torproject/puppetor/Network.java b/src/org/torproject/puppetor/Network.java
new file mode 100644
index 0000000..743c67c
--- /dev/null
+++ b/src/org/torproject/puppetor/Network.java
@@ -0,0 +1,657 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.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/org/torproject/puppetor/NetworkFactory.java b/src/org/torproject/puppetor/NetworkFactory.java
new file mode 100644
index 0000000..2b93ed3
--- /dev/null
+++ b/src/org/torproject/puppetor/NetworkFactory.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.torproject.puppetor.impl.NetworkImpl;
+
+
+/**
+ * The <code>NetworkFactory</code> is a concrete factory that can create
+ * <code>Network</code> instances.
+ *
+ * TODO At the moment, this class uses the concrete class NetworkImpl to
+ * implement its only factory method. If we want to make this a real abstract
+ * factory, we need to replace the concrete constructor by reading the class
+ * name of the class implementing Network from a property file and invoking its
+ * constructor using reflection. Currently, this is the only place where we
+ * reference a class from the impl package.
+ *
+ * @author kloesing
+ */
+public abstract class NetworkFactory {
+
+	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/org/torproject/puppetor/NodeEventType.java b/src/org/torproject/puppetor/NodeEventType.java
new file mode 100644
index 0000000..7b65bd4
--- /dev/null
+++ b/src/org/torproject/puppetor/NodeEventType.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor;
+
+/**
+ * Event types that can be fired by all Tor processes.
+ */
+ at SuppressWarnings("serial")
+public class NodeEventType implements EventType {
+
+	/**
+	 * String identifying the type of the event type.
+	 */
+	String typeString;
+
+	/**
+	 * Creates a new event type with the given type string.
+	 *
+	 * @param typeString
+	 *            String identifying the type of the event type.
+	 */
+	public NodeEventType(final String typeString) {
+		this.typeString = typeString;
+	}
+
+	public String getTypeName() {
+		return typeString;
+	}
+
+	/**
+	 * The node was started and we managed to connect to its control port; this
+	 * event is fired internally and not parsed from a log statement from Tor.
+	 */
+	public static final NodeEventType NODE_STARTED =
+			new NodeEventType("NODE_STARTED");
+
+	/**
+	 * The node has opened its control port; this event is parsed from a log
+	 * statement in connection_create_listener().
+	 */
+	public static final NodeEventType NODE_CONTROL_PORT_OPENED =
+			new NodeEventType("NODE_CONTROL_PORT_OPENED");
+
+	/**
+	 * The node which has successfully opened a circuit; this event is parsed
+	 * from a log statement in circuit_send_next_onion_skin().
+	 */
+	public static final NodeEventType NODE_CIRCUIT_OPENED =
+			new NodeEventType("NODE_CIRCUIT_OPENED");
+
+	/**
+	 * The node was stopped; this event is fired internally and not parsed from
+	 * a log statement from Tor.
+	 */
+	public static final NodeEventType NODE_STOPPED =
+			new NodeEventType("NODE_STOPPED");
+}
\ No newline at end of file
diff --git a/src/org/torproject/puppetor/NodeState.java b/src/org/torproject/puppetor/NodeState.java
new file mode 100644
index 0000000..720b44f
--- /dev/null
+++ b/src/org/torproject/puppetor/NodeState.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor;
+
+/**
+ * The <code>NodeState</code> constitutes the state of a single Tor node. In
+ * contrast to <code>EventType</code> the node states depend only on the
+ * methods that have been invoked on these objects, and not on asynchronous
+ * state changes. Most operations of <code>ProxyNode</code> and its subclasses
+ * require a certain <code>NodeState</code> as precondition and may ensure
+ * another <code>NodeState</code> as postcondition. There is a prescribed
+ * order of states.
+ *
+ * @author kloesing
+ */
+public enum NodeState {
+
+	/**
+	 * The configuration of this node has not been written to disk. This is the
+	 * initial state of a <code>ProxyNode</code> or one of its subclasses.
+	 */
+	CONFIGURING,
+
+	/**
+	 * The configuration of this node has been written to disk, but the Tor
+	 * process has not been started, yet. This state could be useful to review
+	 * the configuration that has been written to disk.
+	 */
+	CONFIGURATION_WRITTEN,
+
+	/**
+	 * The node has been started and is running.
+	 */
+	RUNNING,
+
+	/**
+	 * The node had been started and shut down. It cannot be started at a later
+	 * time anymore.
+	 */
+	SHUT_DOWN
+}
diff --git a/src/org/torproject/puppetor/ProxyNode.java b/src/org/torproject/puppetor/ProxyNode.java
new file mode 100644
index 0000000..78ac209
--- /dev/null
+++ b/src/org/torproject/puppetor/ProxyNode.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.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/org/torproject/puppetor/PuppeTorException.java b/src/org/torproject/puppetor/PuppeTorException.java
new file mode 100644
index 0000000..ff4ef72
--- /dev/null
+++ b/src/org/torproject/puppetor/PuppeTorException.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor;
+
+/**
+ * The <code>PuppeTorException</code> comprises all kinds of checked
+ * exceptions that occur when interacting with the JVM-external Tor processes or
+ * with the local file system. Any occurence of this exception denotes either a
+ * configuration problem that can only be solved outside of the JVM, or an
+ * unexpected problem. In contrast to this, all kinds of programming errors of
+ * an application using this API (invoking a method with wrong parameter values,
+ * in wrong state, etc.) will instead cause appropriate runtime exceptions from
+ * the Java API.
+ *
+ * @author kloesing
+ */
+ at SuppressWarnings("serial")
+public class PuppeTorException extends Exception {
+
+	/**
+	 * Creates a <code>PuppeTorException</code> without detail message or
+	 * cause.
+	 */
+	public PuppeTorException() {
+		super();
+	}
+
+	/**
+	 * Creates a <code>PuppeTorException</code> with the given detail
+	 * <code>message</code> and <code>cause</code>.
+	 *
+	 * @param message
+	 *            The detail message of this exception.
+	 * @param cause
+	 *            The cause for this exception.
+	 */
+	public PuppeTorException(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/org/torproject/puppetor/RouterNode.java b/src/org/torproject/puppetor/RouterNode.java
new file mode 100644
index 0000000..ea42a87
--- /dev/null
+++ b/src/org/torproject/puppetor/RouterNode.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.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/org/torproject/puppetor/ServerApplication.java b/src/org/torproject/puppetor/ServerApplication.java
new file mode 100644
index 0000000..2859d97
--- /dev/null
+++ b/src/org/torproject/puppetor/ServerApplication.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.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/org/torproject/puppetor/ServerEventType.java b/src/org/torproject/puppetor/ServerEventType.java
new file mode 100644
index 0000000..e98fd95
--- /dev/null
+++ b/src/org/torproject/puppetor/ServerEventType.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor;
+
+/**
+ * Event types that can be fired by a server application running as thread in
+ * the background.
+ */
+ at SuppressWarnings("serial")
+public class ServerEventType implements EventType {
+
+	/**
+	 * String identifying the type of the event type.
+	 */
+	String typeString;
+
+	/**
+	 * Creates a new event type with the given type string.
+	 *
+	 * @param typeString
+	 *            String identifying the type of the event type.
+	 */
+	public ServerEventType(final String typeString) {
+		this.typeString = typeString;
+	}
+
+	public String getTypeName() {
+		return typeString;
+	}
+
+	/**
+	 * The server application has received a request and sent a reply to it;
+	 * this event is fired internally and not parsed from a log statement from
+	 * Tor.
+	 */
+	public static final ServerEventType SERVER_RECEIVING_REQUEST_SENDING_REPLY =
+			new ServerEventType("SERVER_RECEIVING_REQUEST_SENDING_REPLY");
+}
\ No newline at end of file
diff --git a/src/org/torproject/puppetor/examples/AccessingPublicWebServerOverTor.java b/src/org/torproject/puppetor/examples/AccessingPublicWebServerOverTor.java
new file mode 100644
index 0000000..0d0d633
--- /dev/null
+++ b/src/org/torproject/puppetor/examples/AccessingPublicWebServerOverTor.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.examples;
+
+import org.torproject.puppetor.ClientApplication;
+import org.torproject.puppetor.ClientEventType;
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.ProxyNode;
+import org.torproject.puppetor.PuppeTorException;
+
+/**
+ * Example for accessing a public web server (here: <code>www.google.com</code>)
+ * over Tor to measure the access time.
+ *
+ * @author kloesing
+ */
+public class AccessingPublicWebServerOverTor {
+
+	/**
+	 * Sets up and runs the test.
+	 *
+	 * @param args
+	 *            Command-line arguments (ignored).
+	 * @throws PuppeTorException
+	 *             Thrown if there is a problem with the JVM-external Tor
+	 *             processes that we cannot handle.
+	 */
+	public static void main(final String[] args) throws PuppeTorException {
+
+		// though we only need a single proxy, we always need to create a
+		// network to initialize a test case.
+		final Network network = NetworkFactory.createNetwork("example1");
+
+		// create a single proxy node with name "proxy"
+		final ProxyNode proxy = network.createProxy("proxy");
+
+		// write configuration of proxy node
+		network.writeConfigurations();
+
+		// start proxy node and wait until it has opened a circuit with a
+		// timeout of 5 seconds
+		if (!network.startNodes(5000)) {
+
+			// failed to start the proxy
+			System.out.println("Failed to start the node!");
+			return;
+		}
+		System.out.println("Successfully started the node!");
+
+		// hup until proxy has built circuits (5 retries, 10 seconds timeout
+		// each)
+		if (!network.hupUntilUp(5, 10000)) {
+
+			// failed to build circuits
+			System.out.println("Failed to build circuits!");
+			System.exit(0);
+		}
+		System.out.println("Successfully built circuits!");
+
+		// create client application
+		final ClientApplication client =
+				network.createClient("client", "www.google.com", 80, proxy
+						.getSocksPort());
+
+		// create event listener to listen for client application events
+		final EventListener clientEventListener = new EventListener() {
+
+			// remember time when request was sent
+			private long before;
+
+			public void handleEvent(Event event) {
+				if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
+					before = System.currentTimeMillis();
+				} else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
+					System.out.println("Request took "
+							+ (System.currentTimeMillis() - before)
+							+ " milliseconds");
+				}
+			}
+		};
+
+		// obtain reference to event manager to be able to respond to events
+		final EventManager manager = network.getEventManager();
+
+		// register event handler for client application events
+		manager.addEventListener(client.getClientApplicationName(),
+				clientEventListener);
+
+		// perform at most three request with a timeout of 20 seconds each
+		client.startRequests(3, 20000, true);
+
+		// block this thread as long as client requests are running
+		manager.waitForAnyOccurence(client.getClientApplicationName(),
+				ClientEventType.CLIENT_REQUESTS_PERFORMED);
+
+		// wait a second before shutting down the proxy
+		try {
+			Thread.sleep(1000);
+		} catch (final InterruptedException e) {}
+
+		// shut down proxy
+		network.shutdownNodes();
+
+		// wait another second before exiting the application
+		try {
+			Thread.sleep(1000);
+		} catch (final InterruptedException e) {}
+
+		// Shut down the JVM
+		System.out.println("Goodbye.");
+	}
+}
diff --git a/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java b/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
new file mode 100644
index 0000000..d2d7016
--- /dev/null
+++ b/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.examples;
+
+import org.torproject.puppetor.ClientApplication;
+import org.torproject.puppetor.ClientEventType;
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.HiddenService;
+import org.torproject.puppetor.HiddenServiceEventType;
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.RouterNode;
+import org.torproject.puppetor.ServerApplication;
+
+/**
+ * Example for advertising and accessing a hidden service over a private Tor
+ * network.
+ *
+ * @author kloesing
+ */
+public class AdvertisingAndAccessingHiddenServiceOverPrivateTorNetwork {
+
+	/**
+	 * Sets up and runs the test.
+	 *
+	 * @param args
+	 *            Command-line arguments (ignored).
+	 * @throws PuppeTorException
+	 *             Thrown if there is a problem with the JVM-external Tor
+	 *             processes that we cannot handle.
+	 */
+	public static void main(final String[] args) throws PuppeTorException {
+
+		// create a network to initialize the test case
+		final Network network = NetworkFactory.createNetwork("example4");
+
+		// create three router nodes
+		final RouterNode router1 = network.createRouter("router1");
+		network.createRouter("router2");
+		final RouterNode router3 = network.createRouter("router3");
+
+		// create only one directory node
+		network.createDirectory("dir1");
+
+		// add hidden service
+		final HiddenService hidServ1 = router1.addHiddenService("hidServ");
+
+		// configure nodes of this network to be part of a private network
+		network.configureAsPrivateNetwork();
+
+		// write node configurations
+		network.writeConfigurations();
+
+		// start nodes and wait until they have opened a circuit with a timeout
+		// of 5 seconds
+		if (!network.startNodes(5000)) {
+
+			// failed to start the nodes
+			System.out.println("Failed to start nodes!");
+			System.exit(0);
+		}
+		System.out.println("Successfully started nodes!");
+
+		// hup until nodes have built circuits (60 retries, 10 seconds timeout
+		// each)
+		if (!network.hupUntilUp(60, 10000)) {
+
+			// failed to build circuits
+			System.out.println("Failed to build circuits!");
+			System.exit(0);
+		}
+		System.out.println("Successfully built circuits!");
+
+		// obtain reference to event manager to be able to respond to events
+		final EventManager manager = network.getEventManager();
+
+		// wait for 1 hour that the proxy has published its first RSD
+		if (!manager.waitForAnyOccurence(router1.getNodeName(),
+				HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
+				1L * 60L * 60L * 1000L)) {
+			// failed to publish an RSD
+			System.out.println("Failed to publish an RSD!");
+			System.exit(0);
+		}
+		System.out.println("Successfully published an RSD!");
+
+		// create server application
+		final ServerApplication server =
+				network.createServer("server", hidServ1.getServicePort());
+
+		// create client application
+		final ClientApplication client =
+				network.createClient("client",
+						hidServ1.determineOnionAddress(), hidServ1
+								.getVirtualPort(), router3.getSocksPort());
+
+		// register event listener
+		final EventListener clientAndServerEventListener = new EventListener() {
+			public void handleEvent(Event event) {
+				System.out.println("Handling event: " + event.getMessage());
+			}
+		};
+		manager.addEventListener(client.getClientApplicationName(),
+				clientAndServerEventListener);
+		manager.addEventListener(server.getServerApplicationName(),
+				clientAndServerEventListener);
+
+		// start server
+		server.startListening();
+		System.out.println("Started server");
+
+		// perform at most five request with a timeout of 45 seconds each
+		client.startRequests(5, 45000, true);
+
+		// wait for request to be performed
+		manager.waitForAnyOccurence(client.getClientApplicationName(),
+				ClientEventType.CLIENT_REQUESTS_PERFORMED);
+
+		// shut down nodes
+		network.shutdownNodes();
+
+		// Shut down the JVM
+		System.out.println("Goodbye.");
+		System.exit(0);
+	}
+}
diff --git a/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java b/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
new file mode 100644
index 0000000..87675c3
--- /dev/null
+++ b/src/org/torproject/puppetor/examples/AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.examples;
+
+import org.torproject.puppetor.ClientApplication;
+import org.torproject.puppetor.ClientEventType;
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.HiddenService;
+import org.torproject.puppetor.HiddenServiceEventType;
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.ProxyNode;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.ServerApplication;
+import org.torproject.puppetor.ServerEventType;
+
+/**
+ * Example for advertising and accessing a hidden service over the public Tor
+ * network.
+ *
+ * @author kloesing
+ */
+public class AdvertisingAndAccessingHiddenServiceOverPublicTorNetwork {
+
+	/**
+	 * Sets up and runs the test.
+	 *
+	 * @param args
+	 *            Command-line arguments (ignored).
+	 * @throws PuppeTorException
+	 *             Thrown if there is a problem with the JVM-external Tor
+	 *             processes that we cannot handle.
+	 */
+	public static void main(final String[] args) throws PuppeTorException {
+
+		// create a network to initialize the test case
+		final Network network = NetworkFactory.createNetwork("example3");
+
+		// create two proxy nodes
+		final ProxyNode proxy1 = network.createProxy("proxy1");
+		final ProxyNode proxy2 = network.createProxy("proxy2");
+
+		// add hidden service to the configuration of proxy1
+		final HiddenService hidServ1 = proxy1.addHiddenService("hidServ");
+
+		// write configuration of proxy nodes
+		network.writeConfigurations();
+
+		// start nodes and wait until they have opened a circuit with a timeout
+		// of 5 seconds
+		if (!network.startNodes(5000)) {
+
+			// failed to start the proxy
+			System.out.println("Failed to start nodes!");
+			System.exit(0);
+		}
+		System.out.println("Successfully started nodes!");
+
+		// hup until nodes have built circuits (5 retries, 10 seconds timeout
+		// each)
+		if (!network.hupUntilUp(5, 10000)) {
+
+			// failed to build circuits
+			System.out.println("Failed to build circuits!");
+			System.exit(0);
+		}
+		System.out.println("Successfully built circuits!");
+
+		// obtain reference to event manager to be able to respond to events
+		final EventManager manager = network.getEventManager();
+
+		// wait for 3 minutes that the proxy has published its first RSD
+		if (!manager.waitForAnyOccurence(proxy1.getNodeName(),
+				HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
+				3L * 60L * 1000L)) {
+
+			// failed to publish an RSD
+			System.out.println("Failed to publish an RSD!");
+			System.exit(0);
+		}
+		System.out.println("Successfully published an RSD!");
+
+		// create server application
+		final ServerApplication server =
+				network.createServer("server", hidServ1.getServicePort());
+
+		// create client application
+		final ClientApplication client =
+				network.createClient("client",
+						hidServ1.determineOnionAddress(), hidServ1
+								.getVirtualPort(), proxy2.getSocksPort());
+
+		// create event listener to listen for client and server application
+		// events
+		final EventListener clientAndServerEventListener = new EventListener() {
+
+			private long requestReceivedAtServer;
+
+			// remember time when request was sent and when it was received
+			private long requestSentFromClient;
+
+			public void handleEvent(Event event) {
+				if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
+					requestSentFromClient = event.getOccurrenceTime();
+				} else if (event.getType() == ServerEventType.SERVER_RECEIVING_REQUEST_SENDING_REPLY) {
+					requestReceivedAtServer = event.getOccurrenceTime();
+					System.out.println("Request took "
+							+ (requestReceivedAtServer - requestSentFromClient)
+							+ " milliseconds from client to server!");
+				} else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
+					System.out
+							.println("Request took "
+									+ (event.getOccurrenceTime() - requestSentFromClient)
+									+ " milliseconds for the round-trip and "
+									+ (event.getOccurrenceTime() - requestReceivedAtServer)
+									+ " milliseconds from server to client!");
+				}
+			}
+		};
+
+		// register event handler for client and server application events
+		manager.addEventListener(client.getClientApplicationName(),
+				clientAndServerEventListener);
+		manager.addEventListener(server.getServerApplicationName(),
+				clientAndServerEventListener);
+
+		// start server
+		server.startListening();
+
+		// perform at most five request with a timeout of 45 seconds each
+		client.startRequests(5, 45000, true);
+
+		// block this thread as long as client requests are running
+		manager.waitForAnyOccurence(client.getClientApplicationName(),
+				ClientEventType.CLIENT_REQUESTS_PERFORMED);
+
+		// shut down proxy
+		network.shutdownNodes();
+
+		// Shut down the JVM
+		System.out.println("Goodbye.");
+		System.exit(0);
+	}
+}
diff --git a/src/org/torproject/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java b/src/org/torproject/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
new file mode 100644
index 0000000..2f9773a
--- /dev/null
+++ b/src/org/torproject/puppetor/examples/AdvertisingHiddenServiceToPublicTorNetwork.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.examples;
+
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.HiddenServiceEventType;
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.NodeEventType;
+import org.torproject.puppetor.ProxyNode;
+import org.torproject.puppetor.PuppeTorException;
+
+/**
+ * Example for advertising a hidden service to the public Tor network and
+ * observing the publication of rendezvous service descriptors.
+ *
+ * @author kloesing
+ */
+public class AdvertisingHiddenServiceToPublicTorNetwork {
+
+	/**
+	 * Sets up and runs the test.
+	 *
+	 * @param args
+	 *            Command-line arguments (ignored).
+	 * @throws PuppeTorException
+	 *             Thrown if there is a problem with the JVM-external Tor
+	 *             processes that we cannot handle.
+	 */
+	public static void main(final String[] args) throws PuppeTorException {
+		// create a network to initialize the test case
+		final Network network = NetworkFactory.createNetwork("example2");
+
+		// create a single proxy node
+		final ProxyNode proxy = network.createProxy("proxy");
+
+		// add hidden service to the configuration
+		proxy.addHiddenService("hidServ");
+
+		// write configuration of proxy node
+		network.writeConfigurations();
+
+		// create event listener to listen for events from our proxy
+		final EventListener proxyEventListener = new EventListener() {
+
+			// remember time when request was sent
+			private long circuitOpened = -1;
+
+			public void handleEvent(Event event) {
+				if (event.getType() == NodeEventType.NODE_CIRCUIT_OPENED) {
+					if (circuitOpened == -1) {
+						circuitOpened = System.currentTimeMillis();
+					}
+				} else if (event.getType() == HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED) {
+					System.out.println("RSD published "
+							+ (System.currentTimeMillis() - circuitOpened)
+							+ " milliseconds after first circuit was opened");
+				}
+			}
+		};
+
+		// obtain reference to event manager to be able to respond to events
+		final EventManager manager = network.getEventManager();
+
+		// register event handler for proxy events
+		manager.addEventListener(proxy.getNodeName(), proxyEventListener);
+
+		// start proxy node and wait until it has opened a circuit with a
+		// timeout of 5 seconds
+		if (!network.startNodes(5000)) {
+
+			// failed to start the proxy
+			System.out.println("Failed to start the node!");
+			System.exit(0);
+		}
+		System.out.println("Successfully started the node!");
+
+		// hup until proxy has built circuits (5 retries, 10 seconds timeout
+		// each)
+		if (!network.hupUntilUp(5, 10000)) {
+
+			// failed to build circuits
+			System.out.println("Failed to build circuits!");
+			System.exit(0);
+		}
+		System.out.println("Successfully built circuits!");
+
+		// let it run for 2 minutes and observe when RSDs are published...
+		System.out
+				.println("Waiting for 2 minutes and observing RSD publications...");
+
+		try {
+			Thread.sleep(2L * 60L * 1000L);
+		} catch (final InterruptedException e) {
+			// do nothing
+		}
+
+		// shut down proxy
+		network.shutdownNodes();
+
+		// Shut down the JVM
+		System.out.println("Goodbye.");
+		System.exit(0);
+	}
+}
diff --git a/src/org/torproject/puppetor/impl/ClientApplicationImpl.java b/src/org/torproject/puppetor/impl/ClientApplicationImpl.java
new file mode 100644
index 0000000..ef4cbb4
--- /dev/null
+++ b/src/org/torproject/puppetor/impl/ClientApplicationImpl.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.impl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.net.Proxy.Type;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.torproject.puppetor.ClientApplication;
+import org.torproject.puppetor.ClientEventType;
+
+
+/**
+ * Implementation of <code>ClientApplication</code>.
+ *
+ * @author kloesing
+ */
+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/org/torproject/puppetor/impl/DirectoryNodeImpl.java b/src/org/torproject/puppetor/impl/DirectoryNodeImpl.java
new file mode 100644
index 0000000..b8cf15c
--- /dev/null
+++ b/src/org/torproject/puppetor/impl/DirectoryNodeImpl.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.impl;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.logging.Level;
+
+import org.torproject.puppetor.DirectoryNode;
+import org.torproject.puppetor.PuppeTorException;
+
+
+/**
+ * Implementation of <code>DirectoryNode</code>.
+ *
+ * @author kloesing
+ */
+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/org/torproject/puppetor/impl/EventImpl.java b/src/org/torproject/puppetor/impl/EventImpl.java
new file mode 100644
index 0000000..4c277c5
--- /dev/null
+++ b/src/org/torproject/puppetor/impl/EventImpl.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.impl;
+
+import java.util.Date;
+
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventType;
+
+
+/**
+ * Implementation of <code>Event</code>.
+ *
+ * @author kloesing
+ */
+ at SuppressWarnings("serial")
+public class EventImpl implements Event {
+
+	/**
+	 * The source of this event.
+	 */
+	private final String source;
+
+	/**
+	 * The type of this event.
+	 */
+	private EventType type;
+
+	/**
+	 * Either the log message that led to firing this event, or an internal
+	 * message.
+	 */
+	private final String message;
+
+	/**
+	 * The occurrence time of the event or of the corresponding log statement.
+	 */
+	private long occurrenceTime;
+
+	/**
+	 * Creates a new <code>EventImpl</code>.
+	 *
+	 * @param occurrenceTime
+	 *            The occurrence time of the event or of the corresponding log
+	 *            statement.
+	 * @param source
+	 *            The source of this event.
+	 * @param type
+	 *            The type of this event.
+	 * @param message
+	 *            Either the log message that led to firing this event, or an
+	 *            internal message.
+	 */
+	EventImpl(final long occurrenceTime, final String source,
+			final EventType type, final String message) {
+		this.occurrenceTime = occurrenceTime;
+		this.source = source;
+		this.type = type;
+		this.message = message;
+	}
+
+	/**
+	 * Creates a new <code>EventImpl</code>.
+	 *
+	 * @param source
+	 *            The source of this event.
+	 * @param message
+	 *            Either the log message that led to firing this event, or an
+	 *            internal message.
+	 */
+	EventImpl(final String source, final String message) {
+		this.source = source;
+		this.message = message;
+	}
+
+	public String getSource() {
+		return source;
+	}
+
+	public EventType getType() {
+		return type;
+	}
+
+	/**
+	 * Sets the event type to the given type.
+	 *
+	 * @param type
+	 *            The type of this event.
+	 */
+	void setType(final EventType type) {
+		this.type = type;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public long getOccurrenceTime() {
+		return occurrenceTime;
+	}
+
+	@Override
+	public String toString() {
+		return this.getClass().getSimpleName() + ": occurenceTime="
+				+ new Date(occurrenceTime) + ", source=\"" + source
+				+ "\", type=" + type.getTypeName() + ", message=\"" + message
+				+ "\"";
+	}
+
+	/**
+	 * Sets the occurrence time to the given time.
+	 *
+	 * @param occurrenceTime
+	 *            The occurrence time of the event or of the corresponding log
+	 *            statement.
+	 */
+	void setOccurenceTime(final long occurrenceTime) {
+		this.occurrenceTime = occurrenceTime;
+	}
+}
diff --git a/src/org/torproject/puppetor/impl/EventManagerImpl.java b/src/org/torproject/puppetor/impl/EventManagerImpl.java
new file mode 100644
index 0000000..163e4d8
--- /dev/null
+++ b/src/org/torproject/puppetor/impl/EventManagerImpl.java
@@ -0,0 +1,730 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.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 org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.EventType;
+import org.torproject.puppetor.HiddenServiceEventType;
+import org.torproject.puppetor.NodeEventType;
+
+
+/**
+ * Implementation of <code>EventManager</code>.
+ *
+ * @author kloesing
+ */
+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/org/torproject/puppetor/impl/HiddenServiceImpl.java b/src/org/torproject/puppetor/impl/HiddenServiceImpl.java
new file mode 100644
index 0000000..35fe787
--- /dev/null
+++ b/src/org/torproject/puppetor/impl/HiddenServiceImpl.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.impl;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.torproject.puppetor.HiddenService;
+import org.torproject.puppetor.PuppeTorException;
+
+
+/**
+ * Implementation of <code>HiddenService</code>.
+ *
+ * @author kloesing
+ */
+public class HiddenServiceImpl implements HiddenService {
+
+	/**
+	 * Logger for this node which is called "hidserv." plus the name of this
+	 * hidden service.
+	 */
+	protected Logger logger;
+
+	/**
+	 * The node at which this hidden service is configured.
+	 */
+	private final ProxyNodeImpl node;
+
+	/**
+	 * Name of the hidden service that will be used as name for the hidden
+	 * service directory.
+	 */
+	private final String serviceName;
+
+	/**
+	 * The TCP port on which the service will be available for requests.
+	 */
+	private final int servicePort;
+
+	/**
+	 * The virtual TCP port that this hidden service runs on as it is announced
+	 * to clients.
+	 */
+	private final int virtualPort;
+
+	/**
+	 * Adds the entries for a hidden service to the configuration of this node.
+	 *
+	 * Creates a new <code>HiddenServiceImpl</code>.
+	 *
+	 * @param node
+	 *            The node at which this hidden service is configured.
+	 * @param serviceName
+	 *            Name of the hidden service that will be used as name for the
+	 *            hidden service directory. May neither be <code>null</code>
+	 *            or a zero-length string.
+	 * @param servicePort
+	 *            The TCP port on which the service will be available for
+	 *            requests. This can, but need not be different from the virtual
+	 *            port that is announced to clients. May not be negative or
+	 *            greater than 65535.
+	 * @param virtualPort
+	 *            The virtual TCP port that this hidden service runs on as it is
+	 *            announced to clients. May not be negative or greater than
+	 *            65535.
+	 * @throws IllegalArgumentException
+	 *             Thrown if an invalid value is given for either of the
+	 *             parameters.
+	 */
+	HiddenServiceImpl(final ProxyNodeImpl node, final String serviceName,
+			final int servicePort, final int virtualPort) {
+
+		// check if networkName can be used as logger name
+		if (serviceName == null || serviceName.length() == 0) {
+			throw new IllegalArgumentException("Invalid serviceName: "
+					+ serviceName);
+		}
+
+		// create logger
+		logger = Logger.getLogger("hidserv." + serviceName);
+
+		// log entering
+		logger.entering(this.getClass().getName(), "HiddenServiceImpl",
+				new Object[] { node, serviceName, servicePort, virtualPort });
+
+		// check parameters
+		if (servicePort < 0 || servicePort > 65535 || virtualPort < 0
+				|| virtualPort > 65535) {
+			logger.log(Level.SEVERE,
+					"Illegal argument when adding hidden service!");
+			final IllegalArgumentException e = new IllegalArgumentException();
+			logger.throwing(this.getClass().getName(), "addHiddenService", e);
+			throw e;
+		}
+
+		// store parameter values
+		this.node = node;
+		this.serviceName = serviceName;
+		this.servicePort = servicePort;
+		this.virtualPort = virtualPort;
+
+		// log exiting
+		logger.exiting(this.getClass().getName(), "HiddenServiceImpl");
+	}
+
+	public String determineOnionAddress() throws PuppeTorException {
+
+		// log entering
+		logger.entering(this.getClass().getName(), "getOnionAddress");
+
+		// check if hidden service directory exists
+		final File hiddenServiceFile =
+				new File(node.getWorkingDir().getAbsolutePath()
+						+ File.separator + serviceName + File.separator
+						+ "hostname");
+		if (!hiddenServiceFile.exists()) {
+			logger.log(Level.SEVERE,
+					"Hidden service directory or hostname file does not exist: "
+							+ hiddenServiceFile.getAbsolutePath());
+
+			final PuppeTorException e =
+					new PuppeTorException(
+							"Hidden service directory or hostname file does not exist: "
+									+ hiddenServiceFile.getAbsolutePath());
+			logger.throwing(this.getClass().getName(), "getOnionAddress", e);
+			throw e;
+		}
+
+		// read hostname from file
+		String address = null;
+		try {
+			final BufferedReader br =
+					new BufferedReader(new FileReader(hiddenServiceFile));
+			address = br.readLine();
+			br.close();
+		} catch (final IOException e) {
+			final PuppeTorException ex =
+					new PuppeTorException("Could not read hostname file!", e);
+			logger.throwing(this.getClass().getName(), "getOnionAddress", ex);
+			throw ex;
+		}
+
+		// log exiting and return address
+		logger.exiting(this.getClass().getName(), "getOnionAddress", address);
+		return address;
+	}
+
+	public String getServiceName() {
+		return serviceName;
+	}
+
+	public int getServicePort() {
+		return servicePort;
+	}
+
+	public int getVirtualPort() {
+		return virtualPort;
+	}
+}
diff --git a/src/org/torproject/puppetor/impl/NetworkImpl.java b/src/org/torproject/puppetor/impl/NetworkImpl.java
new file mode 100644
index 0000000..8bda03d
--- /dev/null
+++ b/src/org/torproject/puppetor/impl/NetworkImpl.java
@@ -0,0 +1,1083 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.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 org.torproject.puppetor.ClientApplication;
+import org.torproject.puppetor.DirectoryNode;
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NodeEventType;
+import org.torproject.puppetor.NodeState;
+import org.torproject.puppetor.ProxyNode;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.RouterNode;
+import org.torproject.puppetor.ServerApplication;
+
+
+/**
+ * Implementation of <code>Network</code>.
+ *
+ * @author kloesing
+ */
+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/org/torproject/puppetor/impl/ProxyNodeImpl.java b/src/org/torproject/puppetor/impl/ProxyNodeImpl.java
new file mode 100644
index 0000000..5b65479
--- /dev/null
+++ b/src/org/torproject/puppetor/impl/ProxyNodeImpl.java
@@ -0,0 +1,735 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.impl;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.Socket;
+import java.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 org.torproject.puppetor.HiddenService;
+import org.torproject.puppetor.NodeEventType;
+import org.torproject.puppetor.NodeState;
+import org.torproject.puppetor.ProxyNode;
+import org.torproject.puppetor.PuppeTorException;
+
+import net.freehaven.tor.control.TorControlConnection;
+
+/**
+ * Implementation of <code>ProxyNode</code>.
+ *
+ * @author kloesing
+ */
+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/org/torproject/puppetor/impl/RouterNodeImpl.java b/src/org/torproject/puppetor/impl/RouterNodeImpl.java
new file mode 100644
index 0000000..d291452
--- /dev/null
+++ b/src/org/torproject/puppetor/impl/RouterNodeImpl.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.impl;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.regex.Pattern;
+
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.RouterNode;
+
+
+/**
+ * Implementation of <code>RouterNode</code>.
+ *
+ * @author kloesing
+ */
+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 at reply.org");
+		templateConfiguration.add("HidServDirectoryV2 1");
+
+		// TODO This is now contained in proposal 135.
+		// templateConfiguration.add("ExitPolicyRejectPrivate 0");
+		// templateConfiguration.add("AssumeReachable 1");
+		// templateConfiguration.add("ServerDNSAllowBrokenResolvConf 1");
+	}
+}
diff --git a/src/org/torproject/puppetor/impl/ServerApplicationImpl.java b/src/org/torproject/puppetor/impl/ServerApplicationImpl.java
new file mode 100644
index 0000000..95e1536
--- /dev/null
+++ b/src/org/torproject/puppetor/impl/ServerApplicationImpl.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.impl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.torproject.puppetor.ServerApplication;
+import org.torproject.puppetor.ServerEventType;
+
+
+/**
+ * Implementation of <code>ServerApplication</code>.
+ *
+ * @author kloesing
+ */
+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/src/org/torproject/puppetor/rmi/AbstractMasterFactory.java b/src/org/torproject/puppetor/rmi/AbstractMasterFactory.java
new file mode 100644
index 0000000..85508e3
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/AbstractMasterFactory.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.rmi.tests.TestRegistration;
+
+
+/**
+ * Factory to create all objects needed by the master side of a PuppeTor
+ * network.
+ *
+ * @author Sebastian Hahn
+ */
+public abstract class AbstractMasterFactory {
+
+	/**
+	 * Hold the concrete RemotePuppeTorFactory that will be used to create the
+	 * implementation classes.
+	 */
+	private static AbstractMasterFactory factory;
+
+	/**
+	 * @return a new concrete
+	 *         <code>AbstractRemotePuppeTorFactory<code>subclass as
+	 *     specified by the initialization
+	 */
+	final public synchronized static AbstractMasterFactory getInstance() {
+		return factory;
+	}
+
+	/**
+	 * @param factory
+	 *            save this as the factory if this hasn't been called before.
+	 */
+	final public synchronized static void initialize(
+			final AbstractMasterFactory factory) {
+		if (AbstractMasterFactory.factory != null) {
+			throw new RuntimeException("Don't call initialize twice!");
+		}
+		AbstractMasterFactory.factory = factory;
+	}
+
+	/**
+	 * Override this to create a subclass of <code>RemotePuppeTor</code>
+	 *
+	 * @param slave
+	 *            Create the server represantation for this connected slave
+	 * @return The new <code>RemotePuppeTor</code> instance
+	 */
+	public abstract Master createMaster(Slave slave) throws RemoteException;
+
+	/**
+	 * Override this to create a subclass of <code>TestExecutor</code>.
+	 *
+	 * @return The <code>TestExecutor</code> instance.
+	 */
+	public abstract TestExecutor getTestExecutorInstance();
+
+	/**
+	 * Override this to create a subclass of <code>TestRegistration</code>.
+	 *
+	 * @return The <code>TestRegistration</code> instance.
+	 */
+	public abstract TestRegistration getTestRegistrationInstance();
+	
+	/**
+	 * XXX just for now so I don't forget.-SH
+	 */
+	public abstract NetworkDescription createNetworkDescription();
+
+	
+
+//	public abstract Network createNetwork(String name);
+}
diff --git a/src/org/torproject/puppetor/rmi/AbstractSlaveFactory.java b/src/org/torproject/puppetor/rmi/AbstractSlaveFactory.java
new file mode 100644
index 0000000..ac5b2d7
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/AbstractSlaveFactory.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.util.Set;
+
+import org.torproject.puppetor.rmi.Slave.OS;
+
+
+/**
+ * Create a new representation of the server. Initialize this once with a
+ * concrete subclass of itself to create the actual <code>RemotePuppeTor</code>
+ * instance.
+ *
+ * @author Sebastian Hahn
+ */
+public abstract class AbstractSlaveFactory {
+
+	/**
+	 * Hold the concrete RemotePuppeTorFactory that will be used to create
+	 * <code>Slave</code> instances. Uses
+	 * <code>AbstractSlaveFactory.class</code> for locking.
+	 */
+	private static AbstractSlaveFactory factory;
+
+	/**
+	 * Hold the remote master object. We only have one per slave. XXX Maybe this
+	 * entire thing is more like a SlaveManager, not a factory. Oh well. Rename
+	 * later, I guess -SH
+	 */
+	private static RemoteMaster master;
+
+	/**
+	 * @return a new concrete <code>AbstractSlaveFactory<code>subclass as
+	 *     specified by the initialization
+	 */
+	final public synchronized static AbstractSlaveFactory getInstance() {
+		return factory;
+	}
+
+	/**
+	 * @param factory
+	 *            save this as the factory if this hasn't been called before.
+	 *            XXX - We could throw an exception here, too. I will think
+	 *            about that.-SH
+	 */
+	final public synchronized static void initialize(
+			final AbstractSlaveFactory factory) {
+		if (AbstractSlaveFactory.factory == null) {
+			AbstractSlaveFactory.factory = factory;
+		}
+	}
+
+	/**
+	 * Register a new master. Note that calling this twice for different master
+	 * doesn't indicate an error, but trying to to set the same master again is
+	 * a bug. Setting this to null means the connection to the master was
+	 * closed.
+	 *
+	 * @param master
+	 *            The master that should be used from now on.
+	 */
+	final public synchronized static void setMaster(final RemoteMaster master) {
+		AbstractSlaveFactory.master = master;
+	}
+
+	/**
+	 * @return the RemoteMaster currently registered.
+	 */
+	final public synchronized static RemoteMaster getMaster() {
+		if (AbstractSlaveFactory.master == null) {
+			throw new IllegalStateException("No master set yet!");
+		}
+		return AbstractSlaveFactory.master;
+	}
+
+	/**
+	 * Override this to create a subclass of <code>Slave</code>
+	 *
+	 * @return The new <code>Slave</code> instance
+	 */
+	public abstract Slave createSlave(final String slaveName, final OS os,
+			final String ip, final Set<Integer> ports,
+			final Set<Integer> availableRevisions) throws NullPointerException;
+
+}
diff --git a/src/org/torproject/puppetor/rmi/LocalMaster.java b/src/org/torproject/puppetor/rmi/LocalMaster.java
new file mode 100644
index 0000000..50dbacb
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/LocalMaster.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+/**
+ * Represent a connection between the locally running PuppeTor network Master
+ * and a slave. This is the master part of the <code>Master</code> interface.
+ *
+ * @author Sebastian Hahn
+ */
+public interface LocalMaster {
+	/**
+	 * @return the slave instance this connection refers to.
+	 */
+	public Slave getSlave();
+
+	/**
+	 * Add a new AbstractTaskImpl for the connected slave.
+	 *
+	 * @param abstractTaskImpl
+	 */
+	public void addTask(Task task);
+
+	/**
+	 * @param taskId
+	 *            The task id that we want to get
+	 * @return Get the TaskResult for the Task with the specified taskId.
+	 * @throws InterruptedException
+	 *             if interrupted while waiting for the next TestResult
+	 */
+	public TaskResult getTaskResult(int taskId) throws InterruptedException;
+
+}
diff --git a/src/org/torproject/puppetor/rmi/Master.java b/src/org/torproject/puppetor/rmi/Master.java
new file mode 100644
index 0000000..20673c8
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/Master.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+/**
+ * Any subclass implementing this must also implement <code>RemoteMaster</code>
+ * and <code>LocalMaster</code>. XXX This isn't a good way to do it. Think
+ * about it more later-SH & Karsten
+ *
+ * @author Sebastian Hahn
+ */
+public interface Master {}
diff --git a/src/org/torproject/puppetor/rmi/MasterConnector.java b/src/org/torproject/puppetor/rmi/MasterConnector.java
new file mode 100644
index 0000000..44ea315
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/MasterConnector.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Handle the initial handshake with the slaves and pass them back an object
+ * that the slave uses to refer to the master. The master will create exactly
+ * one <code>MasterConnector</code>, slaves don't ever need one.
+ *
+ * @author Sebastian Hahn
+ */
+public interface MasterConnector extends Remote {
+
+	/**
+	 * Called once by every connecting client so that the master knows about it
+	 * and has a chance to pass back a representation of itself.
+	 *
+	 * @param slave
+	 *            A slave representation as passed from the connecting slave
+	 * @return The representation of the master
+	 * @throws RemoteException
+	 */
+	public RemoteMaster registerClient(final Slave slave)
+			throws RemoteException;
+}
diff --git a/src/org/torproject/puppetor/rmi/Network.java b/src/org/torproject/puppetor/rmi/Network.java
new file mode 100644
index 0000000..4f4f6c2
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/Network.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class describes all Tor instances on the slaves that are used to form a
+ * private Tor network, or the Tor instances that are added to the public
+ * network and are used in a set of tests.
+ * 
+ * @author Sebastian Hahn
+ */
+public interface Network extends Serializable {
+
+	/**
+	 * @param torInstance
+	 *  	add this <code>TorInstance</code> to the network.
+	 */
+	public void addTorInstance(TorInstance torInstance);
+	
+	/**
+	 * @return this network's name, mainly to identify it in logs.
+	 */
+	public String getName();
+
+	/**
+	 * @param test
+	 *            add this <code>test</code> to the network.
+	 */
+	public void addTest(Test test);
+
+	/**
+	 * @return a link for each slave that is part of this network.
+	 */
+	public Set<LocalMaster> getMasters();
+
+	/**
+	 * @return all Tor instances that are authorities
+	 */
+	public Set<TorInstance> getDirectoryAuthorities();
+
+	/**
+	 * @return all Tor instances that are routers, but not authorities
+	 */
+	public Set<TorInstance> getRoutersOnly();
+
+	/**
+	 * @return all Tor instances that are routers (including authorities)
+	 */
+	public Set<TorInstance> getRouters();
+
+	/**
+	 * @return all Tor instances that are proxis (but not authorities or
+	 *         routers)
+	 */
+	public Set<TorInstance> getProxiesOnly();
+
+	/**
+	 * @return all Tor instances that are proxies (including authorities and
+	 *         routers)
+	 */
+	public Set<TorInstance> getProxies();
+	
+	/**
+	 * @return all Tests that can run on this network -SH
+	 */
+	public List<Test> getTests();
+
+	/**
+	 * Is this a private network or part of the public one?
+	 */
+	public boolean isPrivateNetwork();
+}
diff --git a/src/org/torproject/puppetor/rmi/NetworkDescription.java b/src/org/torproject/puppetor/rmi/NetworkDescription.java
new file mode 100644
index 0000000..1636566
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/NetworkDescription.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.io.Serializable;
+
+public interface NetworkDescription extends Serializable {
+	public int necessaryProxies();
+
+	public int necessaryRouters();
+
+	public int necessaryDirectoryAuthorities();
+}
diff --git a/src/org/torproject/puppetor/rmi/RemoteMaster.java b/src/org/torproject/puppetor/rmi/RemoteMaster.java
new file mode 100644
index 0000000..07a1b87
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/RemoteMaster.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * <code>RemotePuppeTor</code> is the main interface that drives the
+ * interaction between the interconnected master and its slave instances. It is
+ * published by the master's instance, and represents the view of the master a
+ * slave has. It provides ways for the slave to register a <code>Slave</code>
+ * to provide information about its capabilities, as well as a way for the slave
+ * to poll the master for new work and then report back results. XXX We will
+ * need a way to allow the master to access this, as well as a way for clients
+ * with open ports to register for callback support instead of polling for new
+ * tasks =SH
+ *
+ * @author Sebastian Hahn
+ */
+public interface RemoteMaster extends Remote {
+
+	/**
+	 * Slaves that must poll because they are not reachable can use this method
+	 * to ask the master for new work.
+	 *
+	 * @throws RemoteException
+	 *             RMI...
+	 * @throws InterruptedException
+	 *             when interrupted while waiting for the next job to arrive.
+	 */
+	public Task getNewTask() throws RemoteException, InterruptedException;
+
+	/**
+	 * Report back the result of the task to the master. If possible, add an
+	 * error message. XXX Maybe down the road an error object is
+	 * needed/wanted-SH
+	 *
+	 * @throws RemoteException
+	 *             RMI
+	 */
+	public void reportTaskResult(TaskResult task) throws RemoteException,
+			InterruptedException;
+}
diff --git a/src/org/torproject/puppetor/rmi/Slave.java b/src/org/torproject/puppetor/rmi/Slave.java
new file mode 100644
index 0000000..2f037a1
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/Slave.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.io.Serializable;
+import java.net.InetAddress;
+import java.util.Set;
+
+/**
+ * A <code>Slave</code> represents a slave instance ready for tests. It
+ * carries information about the testing capabilities of a given slave. A slave
+ * may not provide information except for its name, which is always mandatory.
+ * The slave will not be used for non-client traffic, though.
+ *
+ * @author Sebastian Hahn
+ */
+public interface Slave extends Serializable {
+
+	public enum OS {
+		UNDEFINED, LINUX_I86_32, LINUX_I86_64, MAC_OSX_PPC, MAC_OSX_I86,
+		WINDOWS_XP_32, WINDOWS_XP_64, WINDOWS_VISTA_32, WINDOWS_VISTA_64
+	}
+
+	/**
+	 * @return the slave's name.
+	 */
+	public String getName();
+
+	/**
+	 * @return the slave's operating system as defined in <code>Slave.OS</code>.
+	 */
+	public OS getOS();
+
+	/**
+	 * @return the slave's IP address. XXX Test this with IPv6-SH
+	 */
+	public InetAddress getIP();
+
+	/**
+	 * @return the ports this slave can open
+	 */
+	public Set<Integer> openablePorts();
+
+	/**
+	 * @return Tor revisions available on the slave
+	 */
+	public Set<Integer> availableRevisions();
+}
diff --git a/src/org/torproject/puppetor/rmi/Task.java b/src/org/torproject/puppetor/rmi/Task.java
new file mode 100644
index 0000000..bb466e8
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/Task.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.io.Serializable;
+import java.rmi.RemoteException;
+import java.util.Collection;
+
+public interface Task extends Serializable {
+
+	/**
+	 * Execute the task. Must be called by the slave.
+	 *
+	 * @throws InterruptedException
+	 *             if interrupted while the task is running.
+	 * @throws RemoteException
+	 *             RMI
+	 */
+	public void execute() throws InterruptedException, RemoteException;
+
+	/**
+	 * Returns this tasks id. Task ids must be globally unique and larger than 0
+	 * or 0 to indicate a task that was sent out by the slave, not the master.
+	 */
+	public int getId();
+
+	/**
+	 * This method will be called by the <code>TestExecutor</code> to allow
+	 * tasks to collect additional information if they need it.
+	 * @param additionalInformation
+	 * 		offer this information.
+	 */
+	public void addAdditionalInformation(
+			Collection<String> additionalInformation);
+}
diff --git a/src/org/torproject/puppetor/rmi/TaskExecutionNotSuccessfulException.java b/src/org/torproject/puppetor/rmi/TaskExecutionNotSuccessfulException.java
new file mode 100644
index 0000000..a37bb9a
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/TaskExecutionNotSuccessfulException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.util.Set;
+
+public class TaskExecutionNotSuccessfulException extends Exception {
+	private static final long serialVersionUID = 1L;
+	final private Set<TaskResult> taskResults;
+
+	public TaskExecutionNotSuccessfulException(final Set<TaskResult> taskResults) {
+		this.taskResults = taskResults;
+	}
+
+	public Set<TaskResult> getTaskResults() {
+		return taskResults;
+	}
+
+	// XXX Add some logging capabilities -SH
+}
diff --git a/src/org/torproject/puppetor/rmi/TaskResult.java b/src/org/torproject/puppetor/rmi/TaskResult.java
new file mode 100644
index 0000000..7cd9f68
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/TaskResult.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.io.Serializable;
+
+/**
+ * After the completion of a <code>Task</code>, a <code>TaskResult</code> is
+ * created to report the result of the task as well as logging information to
+ * the Master.  
+ * 
+ * @author Sebastian Hahn
+ */
+public interface TaskResult extends Serializable {
+
+	/**
+	 * @return
+	 * 		the task that we have a result for
+	 */
+	public Task getTask();
+	
+	/**
+	 * @return
+	 * 		<code>true</code> if the task execution was successful,
+	 * 		false otherwise.
+	 */
+	public boolean wasSuccessful();
+	
+	/**
+	 * A <code>Task</code> may optionally pass back a result that can be used
+	 * by the Master to manage further Task execution. 
+	 * @return
+	 * 		the result of this task or null if no result is set.
+	 */
+	public String getTaskResult();
+}
\ No newline at end of file
diff --git a/src/org/torproject/puppetor/rmi/Test.java b/src/org/torproject/puppetor/rmi/Test.java
new file mode 100644
index 0000000..c6a400f
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/Test.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.io.Serializable;
+
+/**
+ * <code>Test</code> describes a test and the kind of slave it can run on, as
+ * well as other network settings that are required for the test to work. A test
+ * should be able to tell whether it was successfully run or not when passed a
+ * TestResult.
+ *
+ * @author Sebastian Hahn
+ */
+public interface Test extends Serializable {
+	/**
+	 * @return
+	 * 		a description of what this test is useful for.
+	 */
+	public String getDescription();
+
+	/**
+	 * @return
+	 * 		a description of the network that this test requires to run, so the
+	 * 		</code>TestExecutor</code> can pick or set up one. 
+	 */
+	public NetworkDescription getNetworkDescription();
+	
+	/**
+	 * Call this when the network has been set up and you want to run the test
+	 * with the current network.
+	 */
+	public void doTest();
+}
diff --git a/src/org/torproject/puppetor/rmi/TestExecutor.java b/src/org/torproject/puppetor/rmi/TestExecutor.java
new file mode 100644
index 0000000..f5ed5c6
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/TestExecutor.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A master uses the <code>TestExecutor</code> instance to load all registered
+ * tests and check whether the currently running PuppeTor network can satisfy
+ * the precondition necessary to run the test. If yes, it sets up the testing
+ * environment and passes <code>PuppeTorJob</code>s to the slaves as the test
+ * instructs it to do. There must be only a single instance of
+ * <code>TestExecutor</code> at any time.
+ *
+ * @author Sebastian Hahn
+ */
+public interface TestExecutor {
+
+	/**
+	 * Register a test with the executor so it will consider it for execution.
+	 * Accept all submitted tests even though some may not be executable at the
+	 * moment of submission.
+	 */
+	public void registerTest(Test test);
+
+	/**
+	 * Start executing tests. Callers should make sure that the PuppeTor network
+	 * that is supposed to be used is ready.
+	 */
+	public void startTesting();
+
+	/**
+	 * Return an immutable Map of the nodes that have been run successfully
+	 * along with the results of the tests.
+	 */
+	public Map<Test, TestResult> getSuccessfulTests();
+
+	/**
+	 * Return an immutable Map of the nodes that have been run but didn't
+	 * complete successfully along with the results of the tests.
+	 */
+	public Map<Test, TestResult> getFailedTests();
+
+	/**
+	 * Return all tests that could not be executed because the PuppeTorNetwork
+	 * could not match a test's preconditions.
+	 */
+	public Set<Test> getCannotRunTests();
+
+	/**
+	 * Register a newly connected PuppeTor slave via its
+	 * <code>LocalMaster</code> connection object.
+	 */
+	public void registerLocalMaster(LocalMaster master);
+
+	/**
+	 * Unregister a PuppeTor slave. Called when a slave disconnects.
+	 */
+	public void unregisterLocalMaster(LocalMaster master);
+
+	/**
+	 * @return
+	 * 		the currently running network.
+	 */
+	public Network getCurrentNetwork();
+
+	/**
+	 * Execute the passed <code>Task</code> once per passed
+	 * <code>LocalMaster</code>. If a <code>LocalMaster</code> is passed
+	 * more than once, the task will be executed multiple times.
+	 *
+	 * @param masters
+	 *            execute the task for those masters.
+	 * @param taskClass
+	 *            create a task of this class and execute it.
+	 * @param taskResults
+	 * 			place the results of the task in this <code>Set</code> unless
+	 * 			<code>null</code> is passed.
+	 * @param additionalInformation TODO
+	 * @throws TaskExecutionNotSuccessfulException
+	 *             Indicates a failure during task execution
+	 */
+	public void executeTaskForMasters(final Collection<LocalMaster> masters,
+			final Class<? extends Task> taskClass, final Set<TaskResult> taskResults,
+			Collection<String> additionalInformation)
+			throws TaskExecutionNotSuccessfulException;
+
+	/**
+	 * Execute the passed <code>Task</code> once per passed
+	 * <code>TorInstance</code>. If a <code>TorInstance</code> is passed
+	 * more than once, the task will be executed multiple times.
+	 * @param taskClass
+	 *          create a task of this class and execute it.
+	 * @param taskResults
+	 * 			place the results of the task in this <code>Set</code> unless
+	 * 			<code>null</code> is passed.
+	 * @param additionalInformation
+	 * 			call the task's addAdditionalInformation method with this
+	 * 			parameter.
+	 * @param torInstances
+	 *          execute the task for those masters.
+	 * @throws TaskExecutionNotSuccessfulException
+	 *          Indicates a failure during task execution
+	 */
+	public void executeTaskForTorInstances(Collection<TorInstance> instances,
+			Class<? extends Task> taskClass, Set<TaskResult> taskResults,
+			Collection<String> additionalInformation)
+			throws TaskExecutionNotSuccessfulException;
+}
diff --git a/src/org/torproject/puppetor/rmi/TestResult.java b/src/org/torproject/puppetor/rmi/TestResult.java
new file mode 100644
index 0000000..32788c1
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/TestResult.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.io.Serializable;
+
+/**
+ * XXX unused for now. this will hold the result of a /<code>Test</code> -SH
+ */
+public interface TestResult extends Serializable {
+	public Test getJob();
+}
diff --git a/src/org/torproject/puppetor/rmi/TorInstance.java b/src/org/torproject/puppetor/rmi/TorInstance.java
new file mode 100644
index 0000000..fd9295e
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/TorInstance.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi;
+
+import java.util.List;
+
+/**
+ * Describe each tor instance at the client with a <code>TorInstance</code>.
+ *
+ * @author Sebastian Hahn
+ */
+public interface TorInstance {
+	/**
+	 * Additional configuration options that a test wants to be set for a tor
+	 * instance. <code>TorInstance.shouldReplaceConfiguration()</code>
+	 * determines whether this configuration should be added to the default tor
+	 * configuration or replace it.
+	 *
+	 * @return An immutable list of extra configuration settings.
+	 */
+	public List<String> getConfiguration();
+
+	/**
+	 * @return the name by which this Tor instance will be identified at the
+	 *         slaves. See org.torproject.puppetor.Network.getNode().
+	 */
+	public String getName();
+
+	/**
+	 * Whether the caller should use the result of
+	 * <code>TorInstance.getExtraConfiguration()</code> to append tor's
+	 * default configuration or replace it.
+	 *
+	 * @return true if configuration should be replaced, false if it should be
+	 *         appended.
+	 */
+	public boolean shouldReplaceConfiguration();
+
+	public boolean isDirectoryAuthority();
+
+	public boolean isBridgeAuthority();
+
+	public boolean isRouter();
+
+	public boolean isExit();
+	
+	public boolean isOnPrivateNetwork();
+
+	public int getRevision();
+
+	public boolean isBridge();
+
+	public LocalMaster getMaster();
+}
\ No newline at end of file
diff --git a/src/org/torproject/puppetor/rmi/execute/PuppeTorMasterProgram.java b/src/org/torproject/puppetor/rmi/execute/PuppeTorMasterProgram.java
new file mode 100644
index 0000000..e05593a
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/execute/PuppeTorMasterProgram.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.execute;
+
+import java.rmi.AccessException;
+import java.rmi.NoSuchObjectException;
+import java.rmi.NotBoundException;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.UnicastRemoteObject;
+
+import javax.rmi.ssl.SslRMIClientSocketFactory;
+import javax.rmi.ssl.SslRMIServerSocketFactory;
+
+import org.torproject.puppetor.rmi.AbstractMasterFactory;
+import org.torproject.puppetor.rmi.LocalMaster;
+import org.torproject.puppetor.rmi.Master;
+import org.torproject.puppetor.rmi.MasterConnector;
+import org.torproject.puppetor.rmi.RemoteMaster;
+import org.torproject.puppetor.rmi.Slave;
+import org.torproject.puppetor.rmi.TestExecutor;
+import org.torproject.puppetor.rmi.impl.MasterImplFactory;
+import org.torproject.puppetor.rmi.tests.TestRegistration;
+
+
+/**
+ * The <code>PuppeTorMasterProgram</code> contains the main method for the
+ * master instance of a distributed PuppeTor testing infrastructure. Currently,
+ * configuration of the the master's network settings are implemented as
+ * instance variables of <code>PuppeTorMasterProgram</code>. XXX eventually,
+ * using configuration files would be better -SH
+ *
+ * @author Sebastian Hahn
+ */
+public class PuppeTorMasterProgram {
+
+	/**
+	 * The RMI registry.
+	 */
+	private Registry registry;
+
+	/**
+	 * The port the server is supposed to listen on. This port must not be
+	 * firewalled and must be forwarded to the machine if necessary.
+	 */
+	final private static int serverport = 2050;
+
+	/**
+	 * The address this server should bind on. Use 127.0.0.1 for local testing.
+	 * XXX Someone should check if this works with IPv6 -SH
+	 */
+	final private static String serveraddress = "127.0.0.1";
+
+	/**
+	 * Master application entry point
+	 *
+	 * @param args
+	 *            Command-line arguments - ignored for now.
+	 * @throws NotBoundException
+	 * @throws RemoteException
+	 * @throws AccessException
+	 */
+	public static void main(final String[] args) throws AccessException,
+			RemoteException, NotBoundException {
+		final PuppeTorMasterProgram server = new PuppeTorMasterProgram();
+
+		final TestExecutor exec =
+				AbstractMasterFactory.getInstance().getTestExecutorInstance();
+		exec.startTesting();
+		server.unregisterRMIRegistry();
+		// Suggest a garbage collection, may increase shutdown.
+		System.gc();
+	}
+
+	/**
+	 * Private constructor to set up RMI-related Java-internal variables and
+	 * create a <code>RemotePuppeTorImpl</code>.
+	 *
+	 * @throws NotBoundException
+	 * @throws RemoteException
+	 * @throws AccessException
+	 */
+	private PuppeTorMasterProgram() throws AccessException, RemoteException,
+			NotBoundException {
+		// Set the location of the keystore where your private certificate is.
+		System.setProperty("javax.net.ssl.keyStore", "res/keystore");
+		// Set the password for your keystore.
+		System.setProperty("javax.net.ssl.keyStorePassword", "pleasechange");
+
+		/*
+		 * Set the location of the truststore which contains the exported
+		 * certificates of your slaves.
+		 */
+		System.setProperty("javax.net.ssl.trustStore", "res/truststore");
+		// Use a timeout of 45 seconds to detect disconnected clients.
+		System.setProperty("java.rmi.dgc.leaseValue", "45000");
+
+		// Tell the RMI system the location of the master
+		System.setProperty("java.rmi.server.hostname", serveraddress);
+
+		// Set up the factories we want to use
+		AbstractMasterFactory.initialize(new MasterImplFactory());
+
+		registerTests();
+		final InnerMasterConnector master = InnerMasterConnector.getInstance();
+
+		try {
+			registry =
+					LocateRegistry.createRegistry(serverport,
+							new SslRMIClientSocketFactory(),
+							new SslRMIServerSocketFactory(null, null, true));
+			registry.bind("Master", master);
+		} catch (final Throwable th) {
+			th.printStackTrace();
+			throw new RuntimeException(
+					"Could not create the registry or bind"
+							+ "an object in it. Please check your system's configuration. "
+							+ th);
+		}
+
+		System.out.println("waiting for slaves.");
+		try {
+			Thread.sleep(1000 * 60 * 2);
+		} catch (final InterruptedException e) {
+			e.printStackTrace();
+			throw new RuntimeException("This is the main thread. We don't "
+					+ "want to be interrupted.");
+		}
+		registry.unbind("Master");
+		// Only unbinding is not enough, the object will still be exported.
+		UnicastRemoteObject.unexportObject(master, true);
+	}
+
+	/**
+	 * Register all the tests you want to execute here.
+	 */
+	private void registerTests() {
+		final AbstractMasterFactory fact = AbstractMasterFactory.getInstance();
+		final TestExecutor exec = fact.getTestExecutorInstance();
+		final TestRegistration registration = fact.getTestRegistrationInstance();
+		registration.registerTests(exec);
+	}
+
+	private void unregisterRMIRegistry() throws NoSuchObjectException {
+		UnicastRemoteObject.unexportObject(registry, true);
+		registry = null;
+	}
+
+	static class InnerMasterConnector extends UnicastRemoteObject implements
+			MasterConnector {
+		/**
+		 * Required for serialization. Needs to change for new released
+		 * versions.
+		 */
+		private static final long serialVersionUID = 1L;
+
+		private static InnerMasterConnector instance;
+
+		static {
+			try {
+				instance = new InnerMasterConnector();
+			} catch (final RemoteException e) {
+				e.printStackTrace();
+				throw new ExceptionInInitializerError(e);
+			}
+		}
+
+		/**
+		 * private constructor, Singleton
+		 *
+		 * @throws RemoteException
+		 *             RMI
+		 */
+		private InnerMasterConnector() throws RemoteException {}
+
+		public static InnerMasterConnector getInstance() {
+			return instance;
+		}
+
+		public RemoteMaster registerClient(final Slave slave)
+				throws RemoteException {
+			final AbstractMasterFactory fact =
+					AbstractMasterFactory.getInstance();
+			final Master master = fact.createMaster(slave);
+			AbstractMasterFactory.getInstance().getTestExecutorInstance()
+					.registerLocalMaster((LocalMaster) master);
+			return (RemoteMaster) master;
+		}
+	}
+}
diff --git a/src/org/torproject/puppetor/rmi/execute/PuppeTorSlaveProgram.java b/src/org/torproject/puppetor/rmi/execute/PuppeTorSlaveProgram.java
new file mode 100644
index 0000000..5605ed3
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/execute/PuppeTorSlaveProgram.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.execute;
+
+import java.rmi.NoSuchObjectException;
+import java.rmi.NotBoundException;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import javax.rmi.ssl.SslRMIClientSocketFactory;
+
+import org.torproject.puppetor.rmi.AbstractSlaveFactory;
+import org.torproject.puppetor.rmi.MasterConnector;
+import org.torproject.puppetor.rmi.Slave;
+import org.torproject.puppetor.rmi.Task;
+import org.torproject.puppetor.rmi.impl.SlaveImplFactory;
+import org.torproject.puppetor.rmi.tasks.TerminateTask;
+
+
+/**
+ * The <code>PuppeTorSlaveProgram</code> contains the main method for the
+ * slave instances of a distributed PuppeTor testing infrastructure. Currently,
+ * configuration of the the master's network settings and some slave-specific
+ * options are implemented as instance variables of
+ * <code>PuppeTorSlaveProgram</code>. XXX eventually, using configuration
+ * files would be better -SH
+ *
+ * @author Sebastian Hahn
+ */
+public class PuppeTorSlaveProgram {
+
+	/**
+	 * The port the master server is supposed to listen on. This port must not
+	 * be firewalled and must be forwarded to the machine if necessary. Use the
+	 * same value as you used for the master's configuration.
+	 */
+	final private static int serverport = 2050;
+
+	/**
+	 * The address master server listens on. Use the same value as you used for
+	 * the master's configuration. XXX Someone should check if this works with
+	 * IPv6 -SH
+	 */
+	final private static String serveraddress = "127.0.0.1";//78.47.18.109";
+
+	/**
+	 * The address this slave listens on. Use 127.0.0.1 for slaves that do not
+	 * open any ports for the outside world and thus can act as Tor clients
+	 * only.
+	 */
+	final private static String myaddress = "127.0.0.1";
+	
+	/**
+	 * Each slave is identified by its unique <code>slaveName</code>. Be
+	 * careful to prevent naming collisions between slaves.
+	 */
+	final private static String slaveName = "slave1";
+
+	/**
+	 * A representation of this slave node to be sent to the master.
+	 */
+	final private Slave slave;
+
+	final protected static BlockingQueue<Task> tasks =
+			new LinkedBlockingQueue<Task>();
+
+	/**
+	 * Slave application entry point
+	 *
+	 * @param args
+	 *            Command-line arguments
+	 * @throws NoSuchObjectException
+	 */
+	public static void main(final String[] args) {
+		final PuppeTorSlaveProgram slave = new PuppeTorSlaveProgram();
+		while(true) {
+			while (slave.connectToMaster() == false) {
+				try {
+					Thread.sleep(60000);
+				} catch (final InterruptedException e) {
+					throw new RuntimeException("Bug: Nothing should " +
+							"interrupt us here!");
+				}
+			}
+	
+			final Thread taskGetter = new Thread(new Runnable() {
+				public void run() {
+					while (true) {
+						try {
+							try {
+								// XXX I dislike the following lines, but
+								// interruption doesn't work while we wait for the
+								// master. Maybe there is a better solution that
+								// doesn't eat the poison pill twice. -SH
+								Task task = AbstractSlaveFactory.getMaster()
+												.getNewTask();
+								tasks.put(task);
+								if (task instanceof TerminateTask) {
+									break;
+								}
+							} catch (final RemoteException e) {
+								// Empty the task queue - we cannot send any results
+								// back, anyways.
+								tasks.clear();
+								// A taskid of 0 indicates a local Task
+								tasks.put(new TerminateTask(0,
+										"local termination"));
+								Thread.currentThread().interrupt();
+							}
+						} catch (final InterruptedException e) {
+							Thread.currentThread().interrupt();
+						}
+						if (Thread.interrupted()) {
+							break;
+						}
+	
+					}
+				}
+			});
+	
+			taskGetter.start();
+			// XXX We use just a single Thread here. That isn't the smartest move.
+			// -SH
+			try {
+				while (true) {
+					final Task newTask = tasks.take();
+					newTask.execute();
+					if (Thread.interrupted()) {
+						break;
+					}
+				}
+			} catch (final InterruptedException ignored) {} catch (final RemoteException ignored) {}
+			taskGetter.interrupt();
+			AbstractSlaveFactory.setMaster(null);
+			// We don't shut down, but want the Master to see we disconnected,
+			// so hopefully a gc will happen soon.
+			System.gc();
+		}
+
+	}
+
+	/**
+	 * Private constructor to set up RMI-related Java-internal variables.
+	 */
+	private PuppeTorSlaveProgram() {
+		/*
+		 * Set the location of the keystore where your private certificate is.
+		 */
+		System.setProperty("javax.net.ssl.keyStore", "res/keystore");
+		/*
+		 * Set the password for your keystore.
+		 */
+		System.setProperty("javax.net.ssl.keyStorePassword", "pleasechange");
+		/*
+		 * Set the location of the truststore which contains the exported
+		 * certificates of your slaves
+		 */
+		System.setProperty("javax.net.ssl.trustStore", "res/truststore");
+		// Set up the factories we want to use
+		AbstractSlaveFactory.initialize(new SlaveImplFactory());
+		final AbstractSlaveFactory fac = AbstractSlaveFactory.getInstance();
+
+		/*
+		 * Create a new slave with the properties that are relevant for the
+		 * computer and networking environment it runs on.
+		 * XXX unused for now -SH
+		 */
+		final Set<Integer> ports = new HashSet<Integer>();
+		/*for (int i = 10025; i < 20000; i++) {
+			ports.add(i);
+		}*/
+		final Set<Integer> availableRevisions =
+				new CopyOnWriteArraySet<Integer>();
+		slave = fac.createSlave(slaveName, Slave.OS.UNDEFINED, myaddress,
+						ports, availableRevisions);
+
+	}
+
+	/**
+	 * Attempt to make a connection to the master through RMI. If the master is
+	 * down, that is not a fatal condition, we want to keep trying until we can
+	 * reach it. Don't call this except from main().
+	 *
+	 * @return true if the connection was successful, false otherwise.
+	 */
+	private boolean connectToMaster() {
+		try {
+			final Registry registry =
+					LocateRegistry.getRegistry(serveraddress, serverport,
+							new SslRMIClientSocketFactory());
+			AbstractSlaveFactory.setMaster(((MasterConnector) registry
+					.lookup("Master")).registerClient(slave));
+		} catch (final NotBoundException e) {
+			e.printStackTrace();
+			System.out.println("We could connect, but the master has not "
+					+ "exported the Master Object yet." + e);
+			return false;
+		} catch (final RemoteException e) {
+			return false;
+		} catch (final IllegalArgumentException e) {
+			throw new RuntimeException(
+					"A slave with this name is already connected "
+							+ "to the server! " + e);
+		}
+		return true;
+	}
+}
diff --git a/src/org/torproject/puppetor/rmi/impl/AbstractTaskImpl.java b/src/org/torproject/puppetor/rmi/impl/AbstractTaskImpl.java
new file mode 100644
index 0000000..e63eb14
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/impl/AbstractTaskImpl.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.impl;
+
+import java.rmi.RemoteException;
+import java.util.Collection;
+
+import org.torproject.puppetor.rmi.AbstractSlaveFactory;
+import org.torproject.puppetor.rmi.Task;
+import org.torproject.puppetor.rmi.TaskResult;
+
+
+public abstract class AbstractTaskImpl implements Task {
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+	
+	/**
+	 * A human-readable name for the task (to be included in log-messages, etc).
+	 */
+	final protected String name;
+
+	/**
+	 * The globally unique id for this task.
+	 */
+	final protected int id;
+
+	public AbstractTaskImpl(final int id, final String name) {
+		this.name = name;
+		this.id = id;
+	}
+
+	public int getId() {
+		return id;
+	}
+
+	public abstract void execute() throws InterruptedException, RemoteException;
+
+	protected void reportResult(final boolean success)
+			throws InterruptedException, RemoteException {
+		reportResult(new TaskResultImpl(success, this, null));
+	}
+	
+	protected void reportResult(final boolean success, final String result)
+			throws InterruptedException, RemoteException {
+		reportResult(new TaskResultImpl(success, this, result));
+	}
+
+	// XXX protect against double-reporting of results -SH
+	protected void reportResult( final TaskResult taskResult)
+			throws InterruptedException, RemoteException {
+		AbstractSlaveFactory.getMaster().reportTaskResult(taskResult);
+	}
+	
+	public void addAdditionalInformation(
+			Collection<String> additionalInformation) {}
+
+}
diff --git a/src/org/torproject/puppetor/rmi/impl/AbstractTestImpl.java b/src/org/torproject/puppetor/rmi/impl/AbstractTestImpl.java
new file mode 100644
index 0000000..018fdc0
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/impl/AbstractTestImpl.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.impl;
+
+import org.torproject.puppetor.rmi.AbstractMasterFactory;
+import org.torproject.puppetor.rmi.NetworkDescription;
+import org.torproject.puppetor.rmi.Test;
+import org.torproject.puppetor.rmi.TestExecutor;
+
+/**
+ * Extend <code>AbstractTestImpl</code> for your own tests. Make sure you also add them
+ * to the TestRegistration.registerTest method.
+ * 
+ * @author Sebastian Hahn
+ */
+abstract public class AbstractTestImpl implements Test {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+	
+	/**
+	 * A test description for log messages.
+	 */
+	final private String description;
+
+	/**
+	 * The network that must exist for the test to be executed.
+	 */
+	final private NetworkDescription networkDescription;
+
+	final protected TestExecutor exec;
+	
+	public AbstractTestImpl(final String description, final NetworkDescription network) {
+		this.description = description;
+		networkDescription = network;
+		exec = AbstractMasterFactory.getInstance().
+				getTestExecutorInstance();
+	}
+
+	public final NetworkDescription getNetworkDescription() {
+		return networkDescription;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+}
diff --git a/src/org/torproject/puppetor/rmi/impl/MasterImpl.java b/src/org/torproject/puppetor/rmi/impl/MasterImpl.java
new file mode 100644
index 0000000..5550486
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/impl/MasterImpl.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.impl;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.rmi.server.Unreferenced;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.torproject.puppetor.rmi.AbstractMasterFactory;
+import org.torproject.puppetor.rmi.LocalMaster;
+import org.torproject.puppetor.rmi.Master;
+import org.torproject.puppetor.rmi.RemoteMaster;
+import org.torproject.puppetor.rmi.Slave;
+import org.torproject.puppetor.rmi.Task;
+import org.torproject.puppetor.rmi.TaskResult;
+
+
+/**
+ * Represent the master at the slave, and give the master a way to interact with
+ * the respective slave.
+ *
+ * @author Sebastian Hahn
+ */
+public class MasterImpl extends UnicastRemoteObject implements LocalMaster,
+		RemoteMaster, Master, Unreferenced {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Store the reference to the connected slave
+	 */
+	private final Slave slave;
+
+	private final BlockingQueue<Task> tasks;
+
+	private final BlockingQueue<TaskResult> completedTasks;
+
+	private final Object completedTasksLock = new Object();
+
+	public Slave getSlave() {
+		return slave;
+	}
+
+	/**
+	 * Check the slave object for validity and save it for further reference
+	 *
+	 * @throws RemoteException
+	 *             RMI
+	 */
+	public MasterImpl(final Slave slave) throws RemoteException {
+		if (slave.getName() == null) {
+			throw new NullPointerException("The slave's name must not be null!");
+		}
+		this.slave = slave;
+		tasks = new LinkedBlockingQueue<Task>();
+		completedTasks = new LinkedBlockingQueue<TaskResult>();
+	}
+
+	public Task getNewTask() throws RemoteException, InterruptedException {
+		return tasks.take();
+	}
+
+	public void reportTaskResult(final TaskResult task) throws RemoteException,
+			InterruptedException {
+		synchronized (completedTasksLock) {
+			completedTasks.put(task);
+			completedTasksLock.notifyAll();
+		}
+	}
+
+	public TaskResult getTaskResult(final int taskId)
+			throws InterruptedException {
+
+		synchronized (completedTasksLock) {
+			TaskResult res;
+			while ((res = completedTasks.peek()) == null
+					|| res.getTask().getId() != taskId) {
+				completedTasksLock.wait();
+			}
+			return completedTasks.take();
+		}
+	}
+
+	/**
+	 * Remove the slave from the Map of slaves and destroy its workqueues.
+	 * Inform the TestExecutor that all still-running tests failed because the
+	 * slave went away.
+	 */
+	public void unreferenced() {
+		AbstractMasterFactory.getInstance().getTestExecutorInstance()
+				.unregisterLocalMaster(this);
+	}
+
+	public void addTask(final Task task) {
+		tasks.add(task);
+	}
+}
diff --git a/src/org/torproject/puppetor/rmi/impl/MasterImplFactory.java b/src/org/torproject/puppetor/rmi/impl/MasterImplFactory.java
new file mode 100644
index 0000000..f260982
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/impl/MasterImplFactory.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.impl;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.rmi.AbstractMasterFactory;
+import org.torproject.puppetor.rmi.Master;
+import org.torproject.puppetor.rmi.NetworkDescription;
+import org.torproject.puppetor.rmi.Slave;
+import org.torproject.puppetor.rmi.TestExecutor;
+import org.torproject.puppetor.rmi.tests.TestRegistration;
+
+/**
+ * Create <code>RemotePuppeTorImpl</code> instances
+ *
+ * @author Sebastian Hahn
+ */
+public class MasterImplFactory extends AbstractMasterFactory {
+
+	@Override
+	public Master createMaster(final Slave slave) throws RemoteException {
+		return new MasterImpl(slave);
+	}
+
+	@Override
+	public TestExecutor getTestExecutorInstance() {
+		return TestExecutorImpl.getInstance();
+	}
+
+	@Override
+	public TestRegistration getTestRegistrationInstance() {
+		return TestRegistration.getInstance();
+	}
+
+/*	@Override
+	public Test createPublicNetworkTestForClientsOnly(final String description) {
+		return null;
+		return new AbstractTestImpl(description, new NetworkDescriptionImpl(1, 0, 0));
+	}*/
+
+	/*@Override
+	public Network createNetwork(final String name) {
+		return new NetworkImpl(name);
+	}*/
+
+	@Override
+	public NetworkDescription createNetworkDescription() {
+		// XXX implement -SH
+		return null;
+	}
+
+}
diff --git a/src/org/torproject/puppetor/rmi/impl/NetworkDescriptionImpl.java b/src/org/torproject/puppetor/rmi/impl/NetworkDescriptionImpl.java
new file mode 100644
index 0000000..e13614e
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/impl/NetworkDescriptionImpl.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.impl;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.torproject.puppetor.rmi.NetworkDescription;
+
+
+public class NetworkDescriptionImpl implements NetworkDescription {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	private final AtomicInteger numProxies;
+	private final AtomicInteger numRouters;
+	private final AtomicInteger numDirectoryAuthorities;
+
+	public int necessaryProxies() {
+		return numProxies.get();
+	}
+
+	public int necessaryRouters() {
+		return numRouters.get();
+	}
+
+	public int necessaryDirectoryAuthorities() {
+		return numDirectoryAuthorities.get();
+	}
+
+	public NetworkDescriptionImpl(final int numProxies, final int numRouters,
+			final int numDirectoryAuthorities) {
+		this.numProxies = new AtomicInteger(numProxies);
+		this.numRouters = new AtomicInteger(numRouters);
+		this.numDirectoryAuthorities =
+				new AtomicInteger(numDirectoryAuthorities);
+	}
+
+}
diff --git a/src/org/torproject/puppetor/rmi/impl/NetworkImpl.java b/src/org/torproject/puppetor/rmi/impl/NetworkImpl.java
new file mode 100644
index 0000000..aa8fd01
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/impl/NetworkImpl.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.impl;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.torproject.puppetor.rmi.LocalMaster;
+import org.torproject.puppetor.rmi.Network;
+import org.torproject.puppetor.rmi.Test;
+import org.torproject.puppetor.rmi.TorInstance;
+
+
+public class NetworkImpl implements Network {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * The name of the network, so we can identify it in logs etc.
+	 */
+	final private String name;
+
+	/**
+	 * All the tests that will run with this network. The individual Tor
+	 * instances will have the tests they need to execute broken down further.
+	 */
+	private final List<Test> tests;
+
+	/**
+	 * All the Tor instances running at the slaves that are linked to this
+	 * network.
+	 */
+	private final Set<TorInstance> torInstances;
+
+	private final boolean privateNetwork;
+	
+	public NetworkImpl(final String name, boolean privateNetwork) {
+		this.name = name;
+		tests = new CopyOnWriteArrayList<Test>();
+		torInstances = new CopyOnWriteArraySet<TorInstance>();
+		this.privateNetwork = privateNetwork;
+	}
+
+	public void addTorInstance(final TorInstance torInstance) {
+		torInstances.add(torInstance);
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void addTest(final Test test) {
+		tests.add(test);
+	}
+
+	public Set<LocalMaster> getMasters() {
+		final Set<LocalMaster> masters = new HashSet<LocalMaster>();
+		for (final TorInstance torInstance : torInstances) {
+			masters.add(torInstance.getMaster());
+		}
+		return Collections.unmodifiableSet(masters);
+	}
+
+	public Set<TorInstance> getDirectoryAuthorities() {
+		final Set<TorInstance> authorities = new HashSet<TorInstance>();
+		for (final TorInstance torInstance : torInstances) {
+			if (torInstance.isDirectoryAuthority()) {
+				authorities.add(torInstance);
+			}
+		}
+		return Collections.unmodifiableSet(authorities);
+	}
+
+	public Set<TorInstance> getProxies() {
+		return Collections.unmodifiableSet(torInstances);
+	}
+
+	public Set<TorInstance> getProxiesOnly() {
+		final Set<TorInstance> proxies = new HashSet<TorInstance>();
+		for (final TorInstance torInstance : torInstances) {
+			if (!torInstance.isRouter()) {
+				proxies.add(torInstance);
+			}
+		}
+		return Collections.unmodifiableSet(proxies);
+	}
+
+	public Set<TorInstance> getRouters() {
+		final Set<TorInstance> routers = new HashSet<TorInstance>();
+		for (final TorInstance torInstance : torInstances) {
+			if (!torInstance.isRouter()) {
+				routers.add(torInstance);
+			}
+		}
+		return Collections.unmodifiableSet(routers);
+	}
+
+	public Set<TorInstance> getRoutersOnly() {
+		final Set<TorInstance> routers = new HashSet<TorInstance>();
+		for (final TorInstance torInstance : torInstances) {
+			if (torInstance.isRouter() && !torInstance.isDirectoryAuthority()) {
+				routers.add(torInstance);
+			}
+		}
+		return Collections.unmodifiableSet(routers);
+	}
+	
+	public List<Test> getTests() {
+		return Collections.unmodifiableList(tests);
+	}
+
+	public boolean isPrivateNetwork() {
+		return privateNetwork;
+	}
+
+}
diff --git a/src/org/torproject/puppetor/rmi/impl/SlaveImpl.java b/src/org/torproject/puppetor/rmi/impl/SlaveImpl.java
new file mode 100644
index 0000000..ca532b7
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/impl/SlaveImpl.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.impl;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.torproject.puppetor.rmi.Slave;
+
+
+/**
+ * Describe a client including its capabilities and network location to the
+ * master. Also allow the master to query that information. A slave is not
+ * required to give more than its name, but it can only be used for very limited
+ * tests.
+ *
+ * @author Sebastian Hahn
+ */
+public final class SlaveImpl implements Slave {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * The slave's name
+	 */
+	protected final String name;
+
+	/**
+	 * The slave's ip address
+	 */
+	protected final InetAddress ip;
+
+	/**
+	 * The operating system of the slave. See <code>Slave.OS</code>.
+	 */
+	protected final OS os;
+
+	/**
+	 * The ports this slave can open. Currently, we choose to use 256 as a
+	 * maximum that "should be" enough so we don't transfer so much data when
+	 * serializing. See the constructor. Make sure this is immutable.
+	 */
+	protected final Set<Integer> ports;
+
+	/**
+	 * The different revisions of Tor clients this slave can execute.
+	 */
+	protected final Set<Integer> availableRevisions;
+
+	/**
+	 * Create a new <code>Slave</code> with the metrics we currently use,
+	 * meaning it's operating system, it's IP, and the ports it can open. We may
+	 * in the future include other things like Tor executable version numbers
+	 * and the ability to run certain extra scripts.
+	 *
+	 * @param slaveName
+	 *            the slave's name. Each slave must have it's own unique name in
+	 *            a given PuppeTor network.
+	 * @param os
+	 *            the slave's operating system.
+	 * @param ip
+	 *            A String representation of the slave's IP (v4 or v6).
+	 * @param ports
+	 *            All the ports this slave can open. Currently, we pick 256 out
+	 *            of those at random.
+	 * @throws NullPointerException
+	 *             if the slavename is null
+	 */
+	public SlaveImpl(final String slaveName, final OS os, final String ip,
+			final Set<Integer> ports, final Set<Integer> availableRevisions)
+			throws NullPointerException {
+		if (slaveName == null) {
+			throw new NullPointerException("slaveName must not be null");
+		}
+		name = slaveName;
+		this.os = os;
+		try {
+			this.ip = InetAddress.getByName(ip);
+		} catch (final UnknownHostException e) {
+			e.printStackTrace();
+			throw new RuntimeException("You specified an invalid IP address!");
+		}
+
+		if (ports.size() > 256) {
+			final Set<Integer> tmp = new CopyOnWriteArraySet<Integer>();
+			int i = 0;
+			for (final Integer j : tmp) {
+				if (i++ == 256) {
+					break;
+				}
+				tmp.add(j);
+			}
+			this.ports = Collections.unmodifiableSet(tmp);
+		} else {
+			this.ports =
+					Collections
+							.unmodifiableSet(new CopyOnWriteArraySet<Integer>(
+									ports));
+		}
+		this.availableRevisions =
+				Collections.unmodifiableSet(availableRevisions);
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public OS getOS() {
+		return os;
+	}
+
+	public InetAddress getIP() {
+		return ip;
+	}
+
+	public Set<Integer> openablePorts() {
+		return ports;
+	}
+
+	public Set<Integer> availableRevisions() {
+		return availableRevisions;
+	}
+}
diff --git a/src/org/torproject/puppetor/rmi/impl/SlaveImplFactory.java b/src/org/torproject/puppetor/rmi/impl/SlaveImplFactory.java
new file mode 100644
index 0000000..0448d69
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/impl/SlaveImplFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.impl;
+
+import java.util.Set;
+
+import org.torproject.puppetor.rmi.AbstractSlaveFactory;
+import org.torproject.puppetor.rmi.Slave;
+import org.torproject.puppetor.rmi.Slave.OS;
+
+
+/**
+ * Create <code>RemotePuppeTorImpl</code> instances
+ *
+ * @author Sebastian Hahn
+ */
+public class SlaveImplFactory extends AbstractSlaveFactory {
+
+	/**
+	 * @return a new <code>RemotePuppeTorImpl</code> instance.
+	 */
+	@Override
+	public Slave createSlave(final String slaveName, final OS os,
+			final String ip, final Set<Integer> ports,
+			final Set<Integer> availableRevisions) throws NullPointerException {
+		return new SlaveImpl(slaveName, os, ip, ports, availableRevisions);
+	}
+
+}
diff --git a/src/org/torproject/puppetor/rmi/impl/TaskResultImpl.java b/src/org/torproject/puppetor/rmi/impl/TaskResultImpl.java
new file mode 100644
index 0000000..2c66f9f
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/impl/TaskResultImpl.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.impl;
+
+import org.torproject.puppetor.rmi.Task;
+import org.torproject.puppetor.rmi.TaskResult;
+
+public class TaskResultImpl implements TaskResult {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * The task this result is used for.
+	 */
+	final private Task task;
+
+	/**
+	 * Store the result of the <code>task</code>, or <code>null</code> if no
+	 * result is necessary.
+	 */
+	final private String result;
+	
+	/**
+	 * Whether the task execution was successful.
+	 */
+	final private boolean wasSuccessful;
+
+	public TaskResultImpl(final boolean success, final Task task, final String result) {
+		wasSuccessful = success;
+		this.task = task;
+		this.result = result;
+	}
+
+	public Task getTask() {
+		return task;
+	}
+
+	public boolean wasSuccessful() {
+		return wasSuccessful;
+	}
+
+	public String getTaskResult() {
+		return result;
+	}
+}
diff --git a/src/org/torproject/puppetor/rmi/impl/TestExecutorImpl.java b/src/org/torproject/puppetor/rmi/impl/TestExecutorImpl.java
new file mode 100644
index 0000000..ddf7065
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/impl/TestExecutorImpl.java
@@ -0,0 +1,555 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.impl;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.rmi.NoSuchObjectException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.torproject.puppetor.rmi.LocalMaster;
+import org.torproject.puppetor.rmi.Network;
+import org.torproject.puppetor.rmi.NetworkDescription;
+import org.torproject.puppetor.rmi.RemoteMaster;
+import org.torproject.puppetor.rmi.Task;
+import org.torproject.puppetor.rmi.TaskExecutionNotSuccessfulException;
+import org.torproject.puppetor.rmi.TaskResult;
+import org.torproject.puppetor.rmi.Test;
+import org.torproject.puppetor.rmi.TestExecutor;
+import org.torproject.puppetor.rmi.TestResult;
+import org.torproject.puppetor.rmi.TorInstance;
+import org.torproject.puppetor.rmi.tasks.BuildCircuitsTask;
+import org.torproject.puppetor.rmi.tasks.ConfigureAsPrivateTask;
+import org.torproject.puppetor.rmi.tasks.CreateDirectoryAuthorityTask;
+import org.torproject.puppetor.rmi.tasks.CreateNetworkTask;
+import org.torproject.puppetor.rmi.tasks.CreateProxyTask;
+import org.torproject.puppetor.rmi.tasks.CreateRouterTask;
+import org.torproject.puppetor.rmi.tasks.ShutdownNodesTask;
+import org.torproject.puppetor.rmi.tasks.StartNodesTask;
+import org.torproject.puppetor.rmi.tasks.TerminateTask;
+
+
+/**
+ * @author Sebastian Hahn
+ */
+public final class TestExecutorImpl implements TestExecutor {
+
+	/**
+	 * The TestExecutor instance that we'll use
+	 */
+	private static final TestExecutor instance = new TestExecutorImpl();
+
+	/**
+	 * Store all local representations of connections to slaves. Use the
+	 * slave's name as key.
+	 */
+	private static final ConcurrentMap<String, LocalMaster> masters =
+			new ConcurrentHashMap<String, LocalMaster>();
+
+	/**
+	 * Store all registered tests until they are started.
+	 */
+	private static final List<Test> newTests = new Vector<Test>();
+
+	/**
+	 * Store all currently running tests.
+	 */
+	// private static final List<Test> runningTests = new Vector<Test>();
+	/**
+	 * Store all tests that cannot run because the slave's cannot build such a
+	 * network.
+	 */
+	private static final Set<Test> cannotRunTests =
+			new CopyOnWriteArraySet<Test>();
+
+	/**
+	 * Store the networks that are created out of the NetworkDescriptions.
+	 */
+	private static final Set<Network> networks =
+			new CopyOnWriteArraySet<Network>();
+	
+	/**
+	 * Store the currently active network when executing a test. Null indicates
+	 * that either testing hasn't started yet, or is already finished.
+	 */
+	private static Network currentNetwork = null;
+
+	/**
+	 * Store all failed tests.
+	 */
+	private static final Map<Test, TestResult> failedTests =
+			new ConcurrentHashMap<Test, TestResult>();
+
+	/**
+	 * Store all successfully completed tests.
+	 */
+	private static final Map<Test, TestResult> successfulTests =
+			new ConcurrentHashMap<Test, TestResult>();
+
+	/**
+	 * Whether testing has started. This cannot be reset.
+	 */
+	private static AtomicBoolean startedTesting = new AtomicBoolean(false);
+
+	/**
+	 * Store the id for the next task. Every task gets its own unique id > 0. A
+	 * task id equal to 0 indicates that the task was created by the slave to
+	 * stop execution.
+	 */
+	private static AtomicInteger nextTaskId = new AtomicInteger(1);
+
+	/**
+	 * Singleton
+	 */
+	private TestExecutorImpl() {}
+
+	public static TestExecutor getInstance() {
+		return instance;
+	}
+
+	public void registerLocalMaster(final LocalMaster master) {
+		if (startedTesting.get() == true) {
+			throw new RuntimeException("Bug: Testing has started already.");
+		}
+		System.out.println(master.getSlave().getName() + " registered");
+		if (null != masters.putIfAbsent(master.getSlave().getName(), master)) {
+			throw new IllegalArgumentException(master.getSlave().getName()
+					+ " has already registered with this Server");
+		}
+	}
+
+	public void unregisterLocalMaster(final LocalMaster master) {
+		// XXX Don't forget to mark all tests as failed that depended on this
+		// slave if we have already started with testing-SH
+		try {
+			UnicastRemoteObject.unexportObject((RemoteMaster) master, true);
+		} catch (final NoSuchObjectException e) {
+			throw new RuntimeException(
+					"This is a bug. The master should have been exported.");
+		}
+		if (masters.remove(master.getSlave().getName()) == null) {
+			throw new RuntimeException(
+					"This is a bug. unregisterLocalMaster must never be " +
+					"called twice for the same master.");
+		}
+	}
+
+	/**
+	 * Evaluate which tests can be run with the currently connected slaves.
+	 * Cancel all currently running tests and report them as failed if they
+	 * cannot be restarted with the new (reduced) set of connected slaves,
+	 * restart those tests otherwise. XXX This should be merged with
+	 * makeNetworks -SH
+	 */
+	protected void evaluateNetwork() {
+		if (startedTesting.get() == false) {
+			throw new RuntimeException("Bug: Testing has not been started.");
+			// XXX We need to actually check the network.-SH
+		}
+	}
+
+	public void startTesting() {
+		if (startedTesting.get() == true) {
+			throw new RuntimeException("Bug: Testing has started already.");
+		}
+		startedTesting.set(true);
+		// check which tests we can actually execute
+		evaluateNetwork();
+		for (final Test test : newTests) {
+			makeNetworks(test);
+		}
+		// XXX cancelled networks will still appear here...=SH
+		for (final Network nw : networks) {
+			synchronized(TestExecutorImpl.class) {
+				currentNetwork = nw;
+			}
+			try {
+				executeTaskForMasters(nw.getMasters(),
+						CreateNetworkTask.class, null, null);
+				// The network has been created at the slaves. Set up
+				// authorities, routers and proxies.
+
+				Set<TaskResult> authorityResults =
+						new CopyOnWriteArraySet<TaskResult>();
+				executeTaskForTorInstances(nw.getDirectoryAuthorities(),
+						CreateDirectoryAuthorityTask.class, authorityResults,
+						null);
+				executeTaskForTorInstances(nw.getRoutersOnly(),
+						CreateRouterTask.class, null, null);
+				executeTaskForTorInstances(nw.getProxiesOnly(),
+						CreateProxyTask.class, null, null);
+				if(nw.isPrivateNetwork()) {
+					List<String> authorities = new LinkedList<String>();
+					for(TaskResult result : authorityResults) {
+						authorities.add(result.getTaskResult());
+					}
+					executeTaskForMasters(nw.getMasters(),
+							ConfigureAsPrivateTask.class, null, authorities);
+				}
+				// Start the nodes ...
+				executeTaskForMasters(nw.getMasters(), StartNodesTask.class,
+						null, null);
+				// ... and build circuits. This may take some time.
+				executeTaskForMasters(nw.getMasters(), BuildCircuitsTask.class,
+						null, null);
+
+				// Start using the individual tests that were set up for this
+				// network.
+				for (final Test test : nw.getTests()) {
+					test.doTest();
+				}
+
+				// We don't need the network anymore, make sure it's shut down
+				// properly.
+				executeTaskForMasters(nw.getMasters(), ShutdownNodesTask.class,
+						null, null);
+
+			} catch (final TaskExecutionNotSuccessfulException e) {
+				e.printStackTrace();
+				// XXX Log this properly. -SH
+			}
+
+		}
+		// The terminate Task interrupts the slave's main process to
+		// indicate that it should disconnect.
+		synchronized(TestExecutorImpl.class) {
+			currentNetwork = null;
+		}
+		try {
+			executeTaskForMasters(masters.values(), TerminateTask.class, null,
+					null);
+		} catch (TaskExecutionNotSuccessfulException e) {
+			e.printStackTrace();
+			// XXX Log this properly. -SH
+		}
+	}
+
+	//XXX This method and the next look very similar. See what we can do. -SH
+	public void executeTaskForMasters(final Collection<LocalMaster> masters,
+			final Class<? extends Task> taskClass,
+			final Set<TaskResult> taskResults,
+			Collection<String> additionalInformation)
+			throws TaskExecutionNotSuccessfulException {
+
+		if (masters.isEmpty()) {
+			return;
+		}
+		final Set<MasterTask> mTasks = new HashSet<MasterTask>();
+		Constructor<? extends Task> ctor = null;
+		try {
+			ctor = taskClass.getConstructor(int.class, String.class);
+			String networkName = "";
+			synchronized(TestExecutorImpl.class) {
+				if(currentNetwork == null) {
+					networkName = "";
+				} else {
+					networkName = currentNetwork.getName();
+				}
+			}
+			for (final LocalMaster master : masters) {
+				Task task = ctor.newInstance(
+						nextTaskId.getAndIncrement(), networkName);
+				task.addAdditionalInformation(additionalInformation);
+				mTasks.add(new MasterTask(master, task));				
+			}
+
+			final TaskManager man = new TaskManager(mTasks);
+			final FutureTask<Boolean> futureTask =
+				new FutureTask<Boolean>(man);
+			new Thread(futureTask).start();
+			try {
+				if (futureTask.get() == false) {
+					throw new TaskExecutionNotSuccessfulException(man
+							.getTaskResults());
+				}
+			} catch (final InterruptedException e) {
+				throw new TaskExecutionNotSuccessfulException(null);
+			} catch (final ExecutionException e) {
+				// XXX Log this with the cause. Should not happen. -SH
+				e.printStackTrace();
+				throw new TaskExecutionNotSuccessfulException(null);
+			}
+			if(taskResults != null) {
+				taskResults.addAll(man.getTaskResults());
+			}
+		} catch (final SecurityException e) {
+			throw new RuntimeException(
+					"BUG: We're not using a security manager, so where " +
+					"does this exception come from?");
+		} catch (final NoSuchMethodException e) {
+			e.printStackTrace();
+			throw new RuntimeException("BUG: Expected constructor undefined!");
+		} catch (final IllegalArgumentException e) {
+			e.printStackTrace();
+			throw new RuntimeException("BUG: Expected constructor undefined!");
+		} catch (final InstantiationException e) {
+			e.printStackTrace();
+			throw new RuntimeException("BUG: InstantiationException.");
+		} catch (final IllegalAccessException e) {
+			e.printStackTrace();
+			throw new RuntimeException("BUG: Expected constructor undefined!");
+		} catch (final InvocationTargetException e) {
+			e.printStackTrace();
+			throw new RuntimeException("BUG: Constructor threw an exception.");
+		}
+	}
+	
+	public Network getCurrentNetwork() {
+		synchronized(TestExecutorImpl.class) {
+			if(currentNetwork == null) {
+				throw new RuntimeException("There is no network currently!");
+			}
+			return currentNetwork;
+		}
+	}
+
+	public void executeTaskForTorInstances(
+			final Collection<TorInstance> torInstances,
+			final Class<? extends Task> taskClass,
+			final Set<TaskResult> taskResults,
+			Collection<String> additionalInformation)
+			throws TaskExecutionNotSuccessfulException {
+
+		if (torInstances.isEmpty()) {
+			return;
+		}
+		final Set<MasterTask> mTasks = new HashSet<MasterTask>();
+		Constructor<? extends Task> ctor = null;
+		try {
+			ctor = taskClass.getConstructor(int.class, String.class,
+							String.class);
+			String networkName = "";
+			synchronized(TestExecutorImpl.class) {
+				if(currentNetwork == null) {
+					networkName = "";
+				} else {
+					networkName = currentNetwork.getName();
+				}
+			}
+			for (final TorInstance torInstance : torInstances) {
+				Task task = ctor.newInstance(nextTaskId.getAndIncrement(),
+						networkName, torInstance.getName());
+				task.addAdditionalInformation(additionalInformation);
+				mTasks.add(new MasterTask(torInstance.getMaster(), task));
+			}
+
+			final TaskManager man = new TaskManager(mTasks);
+			final FutureTask<Boolean> futureTask =
+				new FutureTask<Boolean>(man);
+			new Thread(futureTask).start();
+			try {
+				if (futureTask.get() == false) {
+					throw new TaskExecutionNotSuccessfulException(man
+							.getTaskResults());
+				}
+			} catch (final InterruptedException e) {
+				throw new TaskExecutionNotSuccessfulException(null);
+			} catch (final ExecutionException e) {
+				// XXX Log this with the cause. Should not happen. -SH
+				e.printStackTrace();
+				throw new TaskExecutionNotSuccessfulException(null);
+			}
+			if(taskResults != null) {
+				taskResults.addAll(man.getTaskResults());
+			}
+		} catch (final SecurityException e) {
+			throw new RuntimeException(
+					"BUG: We're not using a security manager, " +
+					"so where does this exception come from?");
+		} catch (final NoSuchMethodException e) {
+			e.printStackTrace();
+			throw new RuntimeException("BUG: Expected constructor undefined!");
+		} catch (final IllegalArgumentException e) {
+			e.printStackTrace();
+			throw new RuntimeException("BUG: Expected constructor undefined!");
+		} catch (final InstantiationException e) {
+			e.printStackTrace();
+			throw new RuntimeException("BUG: InstantiationException.");
+		} catch (final IllegalAccessException e) {
+			e.printStackTrace();
+			throw new RuntimeException("BUG: Expected constructor undefined!");
+		} catch (final InvocationTargetException e) {
+			e.printStackTrace();
+			throw new RuntimeException("BUG: Constructor threw an exception.");
+		}
+	}
+
+	protected void makeNetworks(final Test test) {
+		final NetworkDescription nd = test.getNetworkDescription();
+		// XXX we want to do something much smarter here.-SH
+
+		// XXX obviously, the following code adds the specified amount of
+		// authorities, routers, and proxies for each slave, regardless of
+		// capabilities. That isn't good. -SH
+		for (final LocalMaster master : masters.values()) {
+			int necessaryDirectoryAuthorities =
+				nd.necessaryDirectoryAuthorities();
+			boolean onPrivateNetwork = (necessaryDirectoryAuthorities > 0);
+			// XXX the name choosing doesn't make sense. -SH
+			final Network nw =
+					new NetworkImpl(test.getDescription() + " slavename:"
+							+ master.getSlave().getName(), onPrivateNetwork);
+			while (necessaryDirectoryAuthorities >= 1) {
+				TorInstanceImpl authority = new TorInstanceImpl(master, 
+						"Authority" + necessaryDirectoryAuthorities);
+				authority.setDirectoryAuthority(true);
+				authority.setOnPrivateNetwork(onPrivateNetwork);
+				nw.addTorInstance(authority);
+				necessaryDirectoryAuthorities--;
+			}
+			int necessaryRouters = nd.necessaryRouters();
+			while (necessaryRouters >= 1) {
+				TorInstanceImpl router = new TorInstanceImpl(master, "Router" + 
+						necessaryRouters);
+				router.setRouter(true);
+				router.setOnPrivateNetwork(onPrivateNetwork);
+				nw.addTorInstance(router);
+				necessaryRouters--;
+			}
+			int necessaryProxies = nd.necessaryProxies();
+			while (necessaryProxies >= 1) {
+				TorInstanceImpl proxy = new TorInstanceImpl(master, "Proxy" +
+						necessaryProxies); 
+				proxy.setOnPrivateNetwork(onPrivateNetwork);
+				nw.addTorInstance(proxy);
+				necessaryProxies--;
+			}
+			nw.addTest(test);
+			networks.add(nw);
+		}
+	}
+
+	public Map<Test, TestResult> getFailedTests() {
+		return failedTests;
+	}
+
+	public Set<Test> getCannotRunTests() {
+		return cannotRunTests;
+	}
+
+	public Map<Test, TestResult> getSuccessfulTests() {
+		return successfulTests;
+	}
+
+	public void registerTest(final Test test) {
+		if (startedTesting.get() == true) {
+			throw new RuntimeException("Bug: Testing has started already.");
+		}
+		if (test == null) {
+			throw new NullPointerException("tests must not be null");
+		}
+		newTests.add(test);
+	}
+
+	private static class TaskManager implements Callable<Boolean> {
+
+		final private Set<TaskResult> taskResults =
+				new CopyOnWriteArraySet<TaskResult>();
+
+		final private Object taskResultsLock = new Object();
+
+		final private Set<MasterTask> tasks;
+
+		TaskManager(final Set<MasterTask> tasks) {
+			if (tasks.isEmpty()) {
+				throw new IllegalArgumentException("No tasks passed!");
+			}
+			this.tasks =
+					Collections.synchronizedSet(
+							new HashSet<MasterTask>(tasks));
+		}
+
+		public Boolean call() {
+			for (final MasterTask task : tasks) {
+				task.getMaster().addTask(task.getTask());
+				// XXX Asking for trouble here. task could throw an exception!
+				// -SH
+				new Thread(new TaskCollector(task)).start();
+			}
+			try {
+				synchronized (taskResultsLock) {
+					// XXX See above. This needs more thinking -SH
+					while (tasks.size() != taskResults.size()) {
+						taskResultsLock.wait();
+					}
+					// XXX We want some logic here.-SH
+					for (final TaskResult taskResult : taskResults) {
+						if (!taskResult.wasSuccessful()) {
+							return false;
+						}
+					}
+					return true;
+				}
+			} catch (final InterruptedException e) {
+				// XXX figure out a better solution here -SH
+				e.printStackTrace();
+			}
+			return false;
+		}
+
+		public Set<TaskResult> getTaskResults() {
+			return taskResults;
+		}
+
+		private class TaskCollector implements Runnable {
+			private final MasterTask task;
+
+			public void run() {
+				try {
+					final TaskResult taskResult =
+							task.getMaster().getTaskResult(
+									task.getTask().getId());
+					synchronized (taskResultsLock) {
+						taskResults.add(taskResult);
+						taskResultsLock.notifyAll();
+					}
+				} catch (final InterruptedException e) {
+					e.printStackTrace();
+					Thread.currentThread().interrupt();
+				}
+
+			}
+
+			protected TaskCollector(final MasterTask task) {
+				this.task = task;
+			}
+		}
+	}
+
+	private static class MasterTask {
+		final private LocalMaster master;
+		final private Task task;
+
+		MasterTask(final LocalMaster master, final Task task) {
+			this.master = master;
+			this.task = task;
+		}
+
+		public LocalMaster getMaster() {
+			return master;
+		}
+
+		public Task getTask() {
+			return task;
+		}
+	}
+}
diff --git a/src/org/torproject/puppetor/rmi/impl/TorInstanceImpl.java b/src/org/torproject/puppetor/rmi/impl/TorInstanceImpl.java
new file mode 100644
index 0000000..eba41fa
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/impl/TorInstanceImpl.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.impl;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.torproject.puppetor.rmi.LocalMaster;
+import org.torproject.puppetor.rmi.TorInstance;
+
+
+/**
+ * Implements a Tor node. Threadsafe.
+ *
+ * @author Sebastian Hahn
+ */
+public class TorInstanceImpl implements TorInstance {
+
+	/**
+	 * Saves configuration options in an "option value" format.
+	 */
+	private final List<String> configuration = new LinkedList<String>();
+
+	/**
+	 * The name of this node.
+	 */
+	private final String name;
+
+	/**
+	 * The svn revision of the Tor build. 0 means no specified revision.
+	 */
+	private final AtomicInteger revision = new AtomicInteger(0);
+
+	/**
+	 * Whether we are a bridge authority.
+	 */
+	private final AtomicBoolean bridgeAuthority = new AtomicBoolean(false);
+
+	/**
+	 * Whether we are a directory authority.
+	 */
+	final private AtomicBoolean directoryAuthority = new AtomicBoolean(false);
+
+	/**
+	 * Whether we are an exit.
+	 */
+	private final AtomicBoolean exit = new AtomicBoolean(false);
+
+	/**
+	 * Whether we are a relay.
+	 */
+	private final AtomicBoolean router = new AtomicBoolean(false);
+
+	/**
+	 * Whether we are a bridge.
+	 */
+	private final AtomicBoolean bridge = new AtomicBoolean(false);
+
+	/**
+	 * Whether we are on a private network.
+	 */
+	private AtomicBoolean privateNetwork = new AtomicBoolean(false);
+
+	/**
+	 * Whether the configuration of the tor instance should be replaced by the
+	 * config returned from <code>this.getConfiguration()</code>.
+	 */
+	private AtomicBoolean replaceConfiguration = new AtomicBoolean(false);
+
+	/**
+	 * Provide a link to the slave where this torInstance will run.
+	 */
+	private final LocalMaster master;
+
+	/**
+	 * @param master
+	 * 		the <code>LocalMaster</code> that represents the slave on which
+	 * 		this Tor instance is running.
+	 * @param name
+	 * 		A name to identify the node.
+	 * 		XXX think about duplicates, we need a unique name to identify the
+	 * 		node, but is it smart to use that name as the Tor proxy's name as
+	 * 		well? -SH
+	 */
+	TorInstanceImpl(final LocalMaster master, final String name) {
+		this.master = master;
+		this.name = name;
+	}
+
+	public synchronized List<String> getConfiguration() {
+		return Collections.unmodifiableList(configuration);
+	}
+	
+	public synchronized void addConfiguration(List<String> newConfig) {
+		configuration.addAll(newConfig);
+	}
+
+	public int getRevision() {
+		return revision.get();
+	}
+	
+	public void setRevision(final int revision) {
+		this.revision.set(revision);
+	}
+
+	public boolean isBridgeAuthority() {
+		return bridgeAuthority.get();
+	}
+	
+	public void setBridgeAuthority(boolean isBridgeAuthority) {
+		bridgeAuthority.set(isBridgeAuthority);
+	}
+
+	public boolean isDirectoryAuthority() {
+		return directoryAuthority.get();
+	}
+	
+	public void setDirectoryAuthority(boolean isDirectoryAuthority) {
+		if(isDirectoryAuthority) {
+			setRouter(true);
+		}
+		directoryAuthority.set(isDirectoryAuthority);
+	}
+
+	public boolean isExit() {
+		return exit.get();
+	}
+
+	public boolean isRouter() {
+		return router.get();
+	}
+	
+	public void setRouter(boolean isRouter) {
+		if(!isRouter) {
+			setDirectoryAuthority(false);
+		}
+		router.set(isRouter);
+	}
+
+	public boolean isOnPrivateNetwork() {
+		return privateNetwork.get();
+	}
+	
+	public void setOnPrivateNetwork(boolean onPrivateNetwork) {
+		privateNetwork.set(onPrivateNetwork);
+	}
+
+	public boolean shouldReplaceConfiguration() {
+		return replaceConfiguration.get();
+	}
+
+	public void setShouldReplaceConfiguration(
+			boolean shouldReplaceConfiguration) {
+		replaceConfiguration.set(shouldReplaceConfiguration);
+	}
+
+	public boolean isBridge() {
+		return bridge.get();
+	}
+
+	public LocalMaster getMaster() {
+		return master;
+	}
+
+	public synchronized String getName() {
+		return name;
+	}
+
+}
diff --git a/src/org/torproject/puppetor/rmi/tasks/AccessGoogleTask.java b/src/org/torproject/puppetor/rmi/tasks/AccessGoogleTask.java
new file mode 100644
index 0000000..e2dae7d
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tasks/AccessGoogleTask.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tasks;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.ClientApplication;
+import org.torproject.puppetor.ClientEventType;
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
+
+
+public class AccessGoogleTask extends AbstractTaskImpl {
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * The name of this tor node.
+	 */
+	private final String nodeName;
+	
+	public AccessGoogleTask(final int id, final String networkName,
+			final String nodeName) {
+		super(id, networkName);
+		this.nodeName = nodeName;
+	}
+
+	@Override
+	public void execute() throws InterruptedException, RemoteException {
+		final Network network = NetworkFactory.getNetworkByName(name);
+		
+		final ClientApplication client =
+			network.createClient("client", "www.google.com", 80, network.getNode(nodeName)
+					.getSocksPort());
+
+		// create event listener to listen for client application events
+		final EventListener clientEventListener = new EventListener() {
+	
+			// remember time when request was sent
+			//private long before;
+	
+			public void handleEvent(Event event) {
+				if (event.getType() == ClientEventType.CLIENT_SENDING_REQUEST) {
+					//before = System.currentTimeMillis();
+				} else if (event.getType() == ClientEventType.CLIENT_REPLY_RECEIVED) {
+					// XXX Log how long requests took, see whether we need this all -SH
+					;
+				}
+			}
+		};
+	
+		// obtain reference to event manager to be able to respond to events
+		final EventManager manager = network.getEventManager();
+	
+		manager.addEventListener(client.getClientApplicationName(),
+				clientEventListener);
+
+		// perform at most three request with a timeout of 20 seconds each
+		client.startRequests(3, 20000, true);
+	
+		// block this thread as long as client requests are running, but don't wait forever.
+		reportResult(manager.waitForAnyOccurence(client.getClientApplicationName(),
+				ClientEventType.CLIENT_REQUESTS_PERFORMED, 75000));
+	}
+}
diff --git a/src/org/torproject/puppetor/rmi/tasks/AddHiddenServiceTask.java b/src/org/torproject/puppetor/rmi/tasks/AddHiddenServiceTask.java
new file mode 100644
index 0000000..cf206bb
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tasks/AddHiddenServiceTask.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tasks;
+
+import java.rmi.RemoteException;
+import java.util.Set;
+
+import org.torproject.puppetor.ClientApplication;
+import org.torproject.puppetor.ClientEventType;
+import org.torproject.puppetor.Event;
+import org.torproject.puppetor.EventListener;
+import org.torproject.puppetor.EventManager;
+import org.torproject.puppetor.HiddenService;
+import org.torproject.puppetor.HiddenServiceEventType;
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.ProxyNode;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.ServerApplication;
+import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
+
+
+public class AddHiddenServiceTask extends AbstractTaskImpl {
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * The name of this tor node.
+	 */
+	private final String nodeName;
+	
+	public AddHiddenServiceTask(final int id, final String networkName,
+			final String nodeName) {
+		super(id, networkName);
+		this.nodeName = nodeName;
+	}
+
+	@Override
+	public void execute() throws InterruptedException, RemoteException {
+		final Network network = NetworkFactory.getNetworkByName(name);
+		ProxyNode node = network.getNode(nodeName);
+		try {
+			// add hidden service
+			final HiddenService hidServ = node.addHiddenService("hidServ");
+			node.writeConfiguration();
+			node.hup();
+			final EventManager manager = network.getEventManager();
+	
+			// wait for 6 minutes that the proxy has published its first RSD
+			if (!manager.waitForAnyOccurence(node.getNodeName(),
+					HiddenServiceEventType.BOB_DESC_PUBLISHED_RECEIVED,
+					6L * 60L * 1000L)) {
+				// failed to publish an RSD
+				reportResult(false);
+				return;
+			}
+	
+			// create server application
+			final ServerApplication server =
+					network.createServer("server", hidServ.getServicePort());
+	
+			// create client application, pick one of the nodes.
+			// XXX fix this so we can choose which node we want. -SH
+			Set<String> nodes = network.getAllNodes().keySet();
+			String[] localNodeNames = new String[10];
+			localNodeNames = nodes.toArray(localNodeNames);
+			final ClientApplication client =
+					network.createClient("client",
+							hidServ.determineOnionAddress(), hidServ
+									.getVirtualPort(), network.getNode(localNodeNames[0]).getSocksPort());
+	
+			// register event listener
+			final EventListener clientAndServerEventListener = new EventListener() {
+				public void handleEvent(Event event) {
+					// XXX log -SH
+				}
+			};
+			manager.addEventListener(client.getClientApplicationName(),
+					clientAndServerEventListener);
+			manager.addEventListener(server.getServerApplicationName(),
+					clientAndServerEventListener);
+	
+			// start server
+			server.startListening();
+	
+			// perform at most five request with a timeout of 45 seconds each
+			client.startRequests(5, 45000, true);
+	
+			// wait for request to be performed
+			manager.waitForAnyOccurence(client.getClientApplicationName(),
+					ClientEventType.CLIENT_REQUESTS_PERFORMED);
+			reportResult(true);
+			} catch (final PuppeTorException e) {
+			e.printStackTrace();
+			reportResult(false);
+		}
+
+
+	}
+
+}
diff --git a/src/org/torproject/puppetor/rmi/tasks/BuildCircuitsTask.java b/src/org/torproject/puppetor/rmi/tasks/BuildCircuitsTask.java
new file mode 100644
index 0000000..85d0b86
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tasks/BuildCircuitsTask.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tasks;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
+
+
+public class BuildCircuitsTask extends AbstractTaskImpl {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	public BuildCircuitsTask(final int id, final String networkName) {
+		super(id, networkName);
+	}
+
+	@Override
+	public void execute() throws InterruptedException, RemoteException {
+		final Network network = NetworkFactory.getNetworkByName(name);
+		try {
+			if (!network.hupUntilUp(50, 10000)) {
+				reportResult(false);
+			}
+		} catch (final PuppeTorException e) {
+			e.printStackTrace();
+			reportResult(false);
+		}
+		reportResult(true);
+	}
+
+}
diff --git a/src/org/torproject/puppetor/rmi/tasks/ConfigureAsPrivateTask.java b/src/org/torproject/puppetor/rmi/tasks/ConfigureAsPrivateTask.java
new file mode 100644
index 0000000..2c6aa20
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tasks/ConfigureAsPrivateTask.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tasks;
+
+import java.rmi.RemoteException;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
+
+
+public class ConfigureAsPrivateTask extends AbstractTaskImpl {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+	
+	final List<String> authorityInformation = new LinkedList<String>();
+
+	public ConfigureAsPrivateTask(final int id, final String networkName) {
+		super(id, networkName);
+	}
+
+	@Override
+	public void execute() throws InterruptedException, RemoteException {
+		final Network network = NetworkFactory.getNetworkByName(name);
+		network.configureAsPartOfPrivateNetwork(authorityInformation);
+		try {
+			network.writeConfigurations();
+			reportResult(true);
+		} catch (final PuppeTorException e) {
+			e.printStackTrace();
+			reportResult(false);
+		}
+	}
+	
+	@Override
+	public void addAdditionalInformation(
+			Collection<String> additionalInformation) {
+		authorityInformation.addAll(additionalInformation);
+	}
+}
\ No newline at end of file
diff --git a/src/org/torproject/puppetor/rmi/tasks/CreateDirectoryAuthorityTask.java b/src/org/torproject/puppetor/rmi/tasks/CreateDirectoryAuthorityTask.java
new file mode 100644
index 0000000..e3bd07c
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tasks/CreateDirectoryAuthorityTask.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tasks;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.DirectoryNode;
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
+
+
+public class CreateDirectoryAuthorityTask extends AbstractTaskImpl {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * The name of this Tor authority.
+	 */
+	private final String authorityName;
+
+	public CreateDirectoryAuthorityTask(final int id, final String networkName,
+			final String authorityName) {
+		super(id, networkName);
+		this.authorityName = authorityName;
+	}
+
+	@Override
+	public void execute() throws InterruptedException, RemoteException {
+		final Network network = NetworkFactory.getNetworkByName(name);
+		DirectoryNode node = network.createDirectory(authorityName);
+		// write configuration of proxy node
+		try {
+			network.writeConfigurations();
+			reportResult(true, node.getDirServerString());
+		} catch (final PuppeTorException e) {
+			e.printStackTrace();
+			reportResult(false);
+		}
+	}
+}
diff --git a/src/org/torproject/puppetor/rmi/tasks/CreateNetworkTask.java b/src/org/torproject/puppetor/rmi/tasks/CreateNetworkTask.java
new file mode 100644
index 0000000..31fea77
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tasks/CreateNetworkTask.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tasks;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
+
+
+public class CreateNetworkTask extends AbstractTaskImpl {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	public CreateNetworkTask(final int id, final String name) {
+		super(id, name);
+	}
+
+	@Override
+	public void execute() throws InterruptedException, RemoteException {
+		NetworkFactory.createNetwork(name);
+		reportResult(true);
+	}
+
+}
diff --git a/src/org/torproject/puppetor/rmi/tasks/CreateProxyTask.java b/src/org/torproject/puppetor/rmi/tasks/CreateProxyTask.java
new file mode 100644
index 0000000..5f4f5c0
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tasks/CreateProxyTask.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tasks;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
+
+
+public class CreateProxyTask extends AbstractTaskImpl {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * The name of this tor proxy.
+	 */
+	private final String proxyName;
+
+	public CreateProxyTask(final int id, final String networkName,
+			final String proxyName) {
+		super(id, networkName);
+		this.proxyName = proxyName;
+	}
+
+	@Override
+	public void execute() throws InterruptedException, RemoteException {
+		final Network network = NetworkFactory.getNetworkByName(name);
+		network.createProxy(proxyName);
+
+		// write configuration of proxy node
+		try {
+			network.writeConfigurations();
+			reportResult(true);
+		} catch (final PuppeTorException e) {
+			e.printStackTrace();
+			reportResult(false);
+		}
+	}
+}
diff --git a/src/org/torproject/puppetor/rmi/tasks/CreateRouterTask.java b/src/org/torproject/puppetor/rmi/tasks/CreateRouterTask.java
new file mode 100644
index 0000000..09b74cf
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tasks/CreateRouterTask.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tasks;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
+
+
+public class CreateRouterTask extends AbstractTaskImpl {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * The name of this router.
+	 */
+	private final String routerName;
+
+	public CreateRouterTask(final int id, final String networkName,
+			final String routerName) {
+		super(id, networkName);
+		this.routerName = routerName;
+	}
+
+	@Override
+	public void execute() throws InterruptedException, RemoteException {
+		final Network network = NetworkFactory.getNetworkByName(name);
+		network.createRouter(routerName);
+		// write configuration of router node
+		try {
+			network.writeConfigurations();
+			reportResult(true);
+		} catch (final PuppeTorException e) {
+			e.printStackTrace();
+			reportResult(false);
+		}
+
+	}
+}
diff --git a/src/org/torproject/puppetor/rmi/tasks/ShutdownNodesTask.java b/src/org/torproject/puppetor/rmi/tasks/ShutdownNodesTask.java
new file mode 100644
index 0000000..7837d5b
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tasks/ShutdownNodesTask.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tasks;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
+
+
+public class ShutdownNodesTask extends AbstractTaskImpl {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	public ShutdownNodesTask(final int id, final String name) {
+		super(id, name);
+	}
+
+	@Override
+	public void execute() throws InterruptedException, RemoteException {
+		final Network network = NetworkFactory.getNetworkByName(name);
+		try {
+			network.shutdownNodes();
+			reportResult(true);
+		} catch (final PuppeTorException e) {
+			reportResult(false);
+		}
+	}
+
+}
diff --git a/src/org/torproject/puppetor/rmi/tasks/StartNodesTask.java b/src/org/torproject/puppetor/rmi/tasks/StartNodesTask.java
new file mode 100644
index 0000000..1049cd5
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tasks/StartNodesTask.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tasks;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.Network;
+import org.torproject.puppetor.NetworkFactory;
+import org.torproject.puppetor.PuppeTorException;
+import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
+
+
+public class StartNodesTask extends AbstractTaskImpl {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	public StartNodesTask(final int id, final String networkName) {
+		super(id, networkName);
+	}
+
+	@Override
+	public void execute() throws InterruptedException, RemoteException {
+		final Network network = NetworkFactory.getNetworkByName(name);
+		try {
+			if (!network.startNodes(5000)) {
+				// failed to start the proxy
+				reportResult(false);
+			}
+		} catch (final PuppeTorException e) {
+			e.printStackTrace();
+			reportResult(false);
+		}
+		reportResult(true);
+	}
+
+}
diff --git a/src/org/torproject/puppetor/rmi/tasks/TerminateTask.java b/src/org/torproject/puppetor/rmi/tasks/TerminateTask.java
new file mode 100644
index 0000000..c2e1ac7
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tasks/TerminateTask.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tasks;
+
+import java.rmi.RemoteException;
+
+import org.torproject.puppetor.rmi.impl.AbstractTaskImpl;
+
+
+public class TerminateTask extends AbstractTaskImpl {
+
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+
+	public TerminateTask(final int id, final String name) {
+		super(id, name);
+	}
+
+	@Override
+	public void execute() throws InterruptedException, RemoteException {
+		Thread.currentThread().interrupt();
+		reportResult(true);
+	}
+
+}
diff --git a/src/org/torproject/puppetor/rmi/tests/AccessGoogleOverPublicTorNetwork.java b/src/org/torproject/puppetor/rmi/tests/AccessGoogleOverPublicTorNetwork.java
new file mode 100644
index 0000000..4eb6b8a
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tests/AccessGoogleOverPublicTorNetwork.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tests;
+
+import java.util.Set;
+
+import org.torproject.puppetor.rmi.Network;
+import org.torproject.puppetor.rmi.TaskExecutionNotSuccessfulException;
+import org.torproject.puppetor.rmi.TorInstance;
+import org.torproject.puppetor.rmi.impl.AbstractTestImpl;
+import org.torproject.puppetor.rmi.impl.NetworkDescriptionImpl;
+import org.torproject.puppetor.rmi.tasks.AccessGoogleTask;
+
+
+public class AccessGoogleOverPublicTorNetwork extends AbstractTestImpl {
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+	
+	public AccessGoogleOverPublicTorNetwork() {
+		super("Use the public Tor network to connect to google.com and wait" +
+				" for a response", new NetworkDescriptionImpl(1, 0, 0));
+	}
+
+	public void doTest() {
+		Network nw = exec.getCurrentNetwork();
+		Set<TorInstance> proxies = nw.getProxies();
+		try {
+			exec.executeTaskForTorInstances(proxies, AccessGoogleTask.class, null, null);
+			System.out.println("Accessing google succeeded");
+		} catch (TaskExecutionNotSuccessfulException e) {
+			System.out.println("Accessing google failed");
+			// Log this properly, especially where we failed
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/org/torproject/puppetor/rmi/tests/StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest.java b/src/org/torproject/puppetor/rmi/tests/StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest.java
new file mode 100644
index 0000000..b11a7c3
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tests/StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tests;
+
+import java.util.Set;
+
+import org.torproject.puppetor.rmi.Network;
+import org.torproject.puppetor.rmi.TaskExecutionNotSuccessfulException;
+import org.torproject.puppetor.rmi.TorInstance;
+import org.torproject.puppetor.rmi.impl.AbstractTestImpl;
+import org.torproject.puppetor.rmi.impl.NetworkDescriptionImpl;
+import org.torproject.puppetor.rmi.tasks.AddHiddenServiceTask;
+
+
+public class StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest extends
+		AbstractTestImpl {
+	/**
+	 * Required for serialization. Needs to change for new released versions.
+	 */
+	private static final long serialVersionUID = 1L;
+	
+	public StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest() {
+		super("Create a private Tor network, set up a hidden service, " +
+				"and access it", new NetworkDescriptionImpl(1, 3, 2));
+	}
+	
+	public void doTest() {
+		Network nw = exec.getCurrentNetwork();
+		Set<TorInstance> proxies = nw.getProxiesOnly();
+		try {
+			exec.executeTaskForTorInstances(proxies, AddHiddenServiceTask.class, null, null);
+			System.out.println("Accessing the hidden service succeeded.");
+		} catch (TaskExecutionNotSuccessfulException e) {
+			System.out.println("Accessing the hidden service failed.");
+			// Log this properly, especially where we failed
+		}
+	}
+}
diff --git a/src/org/torproject/puppetor/rmi/tests/TestRegistration.java b/src/org/torproject/puppetor/rmi/tests/TestRegistration.java
new file mode 100644
index 0000000..0260fdd
--- /dev/null
+++ b/src/org/torproject/puppetor/rmi/tests/TestRegistration.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2007 Karsten Loesing
+ * Copyright 2008-2009 Karsten Loesing and Sebastian Hahn
+ * See LICENSE for licensing information
+ */
+package org.torproject.puppetor.rmi.tests;
+
+import org.torproject.puppetor.rmi.TestExecutor;
+
+public class TestRegistration {
+
+	private static TestRegistration instance = new TestRegistration();
+	
+	public void registerTests(TestExecutor testExecutor) {
+		testExecutor.registerTest(new AccessGoogleOverPublicTorNetwork());
+		testExecutor.registerTest(new
+				StartingAndAccessingHiddenServiceOverPrivateTorNetworkTest());
+	}
+	
+	/**
+	 * Singleton
+	 */
+	private TestRegistration() {}
+	
+	/**
+	 * @return
+	 * 		the <code>TestRegistration</code> instance.
+	 */
+	public static TestRegistration getInstance() {
+		return instance;
+	}
+
+}
-- 
1.5.6.5




More information about the tor-commits mailing list