Initial commit
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
package dslab;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import dslab.client.IMessageClient;
|
||||
import dslab.client.MessageClient;
|
||||
import dslab.mailbox.IMailboxServer;
|
||||
import dslab.mailbox.MailboxServer;
|
||||
import dslab.monitoring.IMonitoringServer;
|
||||
import dslab.monitoring.MonitoringServer;
|
||||
import dslab.nameserver.INameserver;
|
||||
import dslab.nameserver.Nameserver;
|
||||
import dslab.transfer.ITransferServer;
|
||||
import dslab.transfer.TransferServer;
|
||||
import dslab.util.Config;
|
||||
|
||||
/**
|
||||
* The component factory provides methods to create the core components of the application. You can edit the method body
|
||||
* if the component instantiation requires additional logic.
|
||||
*
|
||||
* Do not change the existing method signatures!
|
||||
*/
|
||||
public final class ComponentFactory {
|
||||
|
||||
private ComponentFactory() {
|
||||
// static utility class
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link IMonitoringServer} instance.
|
||||
*
|
||||
* @param componentId the component id
|
||||
* @param in the input stream used for accepting cli commands
|
||||
* @param out the output stream to print to
|
||||
* @return a new MonitoringServer instance
|
||||
*/
|
||||
public static IMonitoringServer createMonitoringServer(String componentId, InputStream in, PrintStream out)
|
||||
throws Exception {
|
||||
/*
|
||||
* TODO: Here you can modify the code (if necessary) to instantiate your components
|
||||
*/
|
||||
|
||||
Config config = new Config(componentId);
|
||||
return new MonitoringServer(componentId, config, in, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link IMailboxServer} instance.
|
||||
*
|
||||
* @param componentId the component id
|
||||
* @param in the input stream used for accepting cli commands
|
||||
* @param out the output stream to print to
|
||||
* @return a new MailboxServer instance
|
||||
*/
|
||||
public static IMailboxServer createMailboxServer(String componentId, InputStream in, PrintStream out)
|
||||
throws Exception {
|
||||
/*
|
||||
* TODO: Here you can modify the code (if necessary) to instantiate your components
|
||||
*/
|
||||
|
||||
Config config = new Config(componentId);
|
||||
return new MailboxServer(componentId, config, in, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ITransferServer} instance.
|
||||
*
|
||||
* @param componentId the component id
|
||||
* @param in the input stream used for accepting cli commands
|
||||
* @param out the output stream to print to
|
||||
* @return a new TransferServer instance
|
||||
*/
|
||||
public static ITransferServer createTransferServer(String componentId, InputStream in, PrintStream out)
|
||||
throws Exception {
|
||||
/*
|
||||
* TODO: Here you can modify the code (if necessary) to instantiate your components
|
||||
*/
|
||||
|
||||
Config config = new Config(componentId);
|
||||
return new TransferServer(componentId, config, in, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link INameserver} instance.
|
||||
*
|
||||
* @param componentId the component id
|
||||
* @param in the input stream used for accepting cli commands
|
||||
* @param out the output stream to print to
|
||||
* @return a new Nameserver instance
|
||||
*/
|
||||
public static INameserver createNameserver(String componentId, InputStream in, PrintStream out)
|
||||
throws Exception {
|
||||
/*
|
||||
* TODO: Here you can modify the code (if necessary) to instantiate your components
|
||||
*/
|
||||
|
||||
Config config = new Config(componentId);
|
||||
return new Nameserver(componentId, config, in, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link IMessageClient} instance.
|
||||
*
|
||||
* @param componentId the component id
|
||||
* @param in the input stream used for accepting cli commands
|
||||
* @param out the output stream to print to
|
||||
* @return a new MessageClient instance
|
||||
*/
|
||||
public static IMessageClient createMessageClient(String componentId, InputStream in, PrintStream out)
|
||||
throws Exception {
|
||||
/*
|
||||
* TODO: Here you can modify the code (if necessary) to instantiate your components
|
||||
*/
|
||||
|
||||
Config config = new Config(componentId);
|
||||
return new MessageClient(componentId, config, in, out);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package dslab.client;
|
||||
|
||||
/**
|
||||
* A Message Client application.
|
||||
*
|
||||
* Do not change the existing method signatures!
|
||||
*/
|
||||
public interface IMessageClient extends Runnable {
|
||||
|
||||
/**
|
||||
* Starts the message client.
|
||||
*/
|
||||
@Override
|
||||
void run();
|
||||
|
||||
/**
|
||||
* Outputs the contents of the user's inbox on the shell.
|
||||
*/
|
||||
void inbox();
|
||||
|
||||
/**
|
||||
* Deletes the mail with the given id. Prints 'ok' if the mail was deleted successfully, 'error {explanation}'
|
||||
* otherwise.
|
||||
*
|
||||
* @param id the mail id
|
||||
*/
|
||||
void delete(String id);
|
||||
|
||||
/**
|
||||
* Verifies the signature of the message by calculating its hash value using the shared secret. Prints 'ok' if the
|
||||
* message integrity was successfully verified, or 'error' otherwise.
|
||||
*
|
||||
* @param id the message id
|
||||
*/
|
||||
void verify(String id);
|
||||
|
||||
/**
|
||||
* Sends a message from the mail client's user to the given recipient(s)
|
||||
*
|
||||
* @param to comma separated list of recipients
|
||||
* @param subject the message subject
|
||||
* @param data the message data
|
||||
*/
|
||||
void msg(String to, String subject, String data);
|
||||
|
||||
/**
|
||||
* Shuts down the application.
|
||||
*/
|
||||
void shutdown();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package dslab.client;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.util.Config;
|
||||
|
||||
public class MessageClient implements IMessageClient, Runnable {
|
||||
|
||||
/**
|
||||
* Creates a new client instance.
|
||||
*
|
||||
* @param componentId the id of the component that corresponds to the Config resource
|
||||
* @param config the component config
|
||||
* @param in the input stream to read console input from
|
||||
* @param out the output stream to write console output to
|
||||
*/
|
||||
public MessageClient(String componentId, Config config, InputStream in, PrintStream out) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inbox() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(String id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void msg(String to, String subject, String data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
IMessageClient client = ComponentFactory.createMessageClient(args[0], System.in, System.out);
|
||||
client.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package dslab.mailbox;
|
||||
|
||||
/**
|
||||
* The mailbox server receives mails via DMTP from transfer servers, and makes them available to users via the DMAP
|
||||
* protocol.
|
||||
*
|
||||
* Do not change the existing method signatures!
|
||||
*/
|
||||
public interface IMailboxServer extends Runnable {
|
||||
|
||||
/**
|
||||
* Starts the server.
|
||||
*/
|
||||
@Override
|
||||
void run();
|
||||
|
||||
/**
|
||||
* CLI command to shut down the server. After this method, all resources should be closed, and the application
|
||||
* should terminate.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package dslab.mailbox;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.util.Config;
|
||||
|
||||
public class MailboxServer implements IMailboxServer, Runnable {
|
||||
|
||||
/**
|
||||
* Creates a new server instance.
|
||||
*
|
||||
* @param componentId the id of the component that corresponds to the Config resource
|
||||
* @param config the component config
|
||||
* @param in the input stream to read console input from
|
||||
* @param out the output stream to write console output to
|
||||
*/
|
||||
public MailboxServer(String componentId, Config config, InputStream in, PrintStream out) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
IMailboxServer server = ComponentFactory.createMailboxServer(args[0], System.in, System.out);
|
||||
server.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package dslab.monitoring;
|
||||
|
||||
/**
|
||||
* The monitoring service accepts incoming monitoring packets via UDP. It provides CLI commands to access the
|
||||
* information.
|
||||
*
|
||||
* Do not change the existing method signatures!
|
||||
*/
|
||||
public interface IMonitoringServer extends Runnable {
|
||||
|
||||
/**
|
||||
* Starts the server.
|
||||
*/
|
||||
@Override
|
||||
void run();
|
||||
|
||||
/**
|
||||
* CLI command to shut down the server. After this method, all resources should be closed, and the application
|
||||
* should terminate.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* CLI command to report usage statistics for transfer servers.
|
||||
*/
|
||||
void servers();
|
||||
|
||||
/**
|
||||
* CLI command to report usage statistics for individual senders.
|
||||
*/
|
||||
void addresses();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package dslab.monitoring;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.util.Config;
|
||||
|
||||
public class MonitoringServer implements IMonitoringServer {
|
||||
|
||||
/**
|
||||
* Creates a new server instance.
|
||||
*
|
||||
* @param componentId the id of the component that corresponds to the Config resource
|
||||
* @param config the component config
|
||||
* @param in the input stream to read console input from
|
||||
* @param out the output stream to write console output to
|
||||
*/
|
||||
public MonitoringServer(String componentId, Config config, InputStream in, PrintStream out) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addresses() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void servers() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
IMonitoringServer server = ComponentFactory.createMonitoringServer(args[0], System.in, System.out);
|
||||
server.run();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package dslab.nameserver;
|
||||
|
||||
public class AlreadyRegisteredException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public AlreadyRegisteredException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public AlreadyRegisteredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package dslab.nameserver;
|
||||
|
||||
/**
|
||||
* A Nameserver application.
|
||||
*
|
||||
* Do not change the existing method signatures!
|
||||
*/
|
||||
public interface INameserver extends Runnable {
|
||||
|
||||
/**
|
||||
* Starts the server.
|
||||
*/
|
||||
@Override
|
||||
void run();
|
||||
|
||||
/**
|
||||
* CLI command to report information about each known nameserver (zones) from the perspective of this nameserver.
|
||||
*/
|
||||
void nameservers();
|
||||
|
||||
/**
|
||||
* CLI command to report information about the handled mailbox servers, containing mail domain and address
|
||||
* (IP:port).
|
||||
*/
|
||||
void addresses();
|
||||
|
||||
/**
|
||||
* CLI command to shut down the server. After this method, all resources should be closed, and the application
|
||||
* should terminate.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package dslab.nameserver;
|
||||
|
||||
import java.rmi.Remote;
|
||||
import java.rmi.RemoteException;
|
||||
|
||||
/**
|
||||
* The remote object of a nameserver which can be called via RMI.
|
||||
*/
|
||||
public interface INameserverRemote extends Remote {
|
||||
|
||||
/**
|
||||
* Registers a mailbox server with the given address for the given domain. For example, when registering a
|
||||
* nameserver for the domain 'earth.planet', the new nameserver first calls the root nameserver with the argument
|
||||
* 'earth.planet'. The root nameserver locates the nameserver for 'planet' via its child-nameserver references, and
|
||||
* invokes this method with the remainder of the domain (i.e., 'earth'). Because 'earth' is then the leaf zone, the
|
||||
* current nameserver ('planet') stores the reference in its child-nameserver references.
|
||||
*
|
||||
* @param domain the domain
|
||||
* @param nameserver the nameserver's remote object
|
||||
* @throws RemoteException RMI exception (declaration required by RMI)
|
||||
* @throws AlreadyRegisteredException if the given domain is already registered
|
||||
* @throws InvalidDomainException if the domain is invalid (e.g., due to a syntax error, or a required intermediary
|
||||
* nameserver was not found)
|
||||
*/
|
||||
void registerNameserver(String domain, INameserverRemote nameserver) throws RemoteException,
|
||||
AlreadyRegisteredException, InvalidDomainException;
|
||||
|
||||
/**
|
||||
* Registers a mailbox server with the given address for the given domain.
|
||||
*
|
||||
* @param domain the mail domain, e.g. <code>vienna.earth.planet</code>
|
||||
* @param address the socket address of the mailbox server's DMTP socket, e.g., <code>127.0.0.1:16503</code>
|
||||
* @throws RemoteException RMI exception (declaration required by RMI)
|
||||
* @throws AlreadyRegisteredException if the given domain is already in use
|
||||
* @throws InvalidDomainException if the domain is invalid (e.g., due to a syntax error, or the responsible
|
||||
* nameserver was not found)
|
||||
*/
|
||||
void registerMailboxServer(String domain, String address) throws RemoteException, AlreadyRegisteredException,
|
||||
InvalidDomainException;
|
||||
|
||||
/**
|
||||
* Returns a reference to the remote object of the ns for the given zone. For example, if called with the argument
|
||||
* 'earth' on the remote object of zone 'planet', the call returns the reference to the nameserver of the zone
|
||||
* 'earth.planet'.
|
||||
*
|
||||
* @param zone the child zone, e.g. <code>earth</code>
|
||||
* @return the remote object reference of the given zone, or <code>null</code> if it does not exist
|
||||
* @throws RemoteException RMI exception (declaration required by RMI)
|
||||
*/
|
||||
INameserverRemote getNameserver(String zone) throws RemoteException;
|
||||
|
||||
/**
|
||||
* @param username
|
||||
* @return
|
||||
* @throws RemoteException
|
||||
*/
|
||||
String lookup(String username) throws RemoteException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package dslab.nameserver;
|
||||
|
||||
public class InvalidDomainException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InvalidDomainException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InvalidDomainException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package dslab.nameserver;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.util.Config;
|
||||
|
||||
public class Nameserver implements INameserver {
|
||||
|
||||
/**
|
||||
* Creates a new server instance.
|
||||
*
|
||||
* @param componentId the id of the component that corresponds to the Config resource
|
||||
* @param config the component config
|
||||
* @param in the input stream to read console input from
|
||||
* @param out the output stream to write console output to
|
||||
*/
|
||||
public Nameserver(String componentId, Config config, InputStream in, PrintStream out) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nameservers() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addresses() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
INameserver component = ComponentFactory.createNameserver(args[0], System.in, System.out);
|
||||
component.run();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package dslab.transfer;
|
||||
|
||||
/**
|
||||
* The transfer server is responsible for accepting mails sent by users, and forward them to mailbox servers via DMTP.
|
||||
* It also reports usage statistics to the monitoring server.
|
||||
*
|
||||
* Do not change the existing method signatures!
|
||||
*/
|
||||
public interface ITransferServer extends Runnable {
|
||||
|
||||
/**
|
||||
* Starts the server.
|
||||
*/
|
||||
@Override
|
||||
void run();
|
||||
|
||||
/**
|
||||
* CLI command to shut down the server. After this method, all resources should be closed, and the application
|
||||
* should terminate.
|
||||
*/
|
||||
void shutdown();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package dslab.transfer;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.util.Config;
|
||||
|
||||
public class TransferServer implements ITransferServer, Runnable {
|
||||
|
||||
/**
|
||||
* Creates a new server instance.
|
||||
*
|
||||
* @param componentId the id of the component that corresponds to the Config resource
|
||||
* @param config the component config
|
||||
* @param in the input stream to read console input from
|
||||
* @param out the output stream to write console output to
|
||||
*/
|
||||
public TransferServer(String componentId, Config config, InputStream in, PrintStream out) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ITransferServer server = ComponentFactory.createTransferServer(args[0], System.in, System.out);
|
||||
server.run();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package dslab.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Reads the configuration from a {@code .properties} file.
|
||||
*/
|
||||
public final class Config {
|
||||
|
||||
private final ResourceBundle bundle;
|
||||
private Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates an instance of Config which reads configuration data form {@code .properties} file with given name found
|
||||
* in classpath.
|
||||
*
|
||||
* @param name the name of the .properties file
|
||||
*/
|
||||
public Config(String name) {
|
||||
if (name.endsWith(".properties")) {
|
||||
this.bundle = ResourceBundle.getBundle(name.substring(0, name.length() - 11));
|
||||
} else {
|
||||
this.bundle = ResourceBundle.getBundle(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value as String for the given key.
|
||||
*
|
||||
* @param key the property's key
|
||||
* @return String value of the property
|
||||
* @see ResourceBundle#getString(String)
|
||||
*/
|
||||
public String getString(String key) {
|
||||
if (properties.containsKey(key)) {
|
||||
return properties.get(key).toString();
|
||||
}
|
||||
return this.bundle.getString(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value as {@code int} for the given key.
|
||||
*
|
||||
* @param key the property's key
|
||||
* @return int value of the property
|
||||
* @throws NumberFormatException if the String cannot be parsed to an Integer
|
||||
*/
|
||||
public int getInt(String key) {
|
||||
return Integer.parseInt(getString(key));
|
||||
}
|
||||
|
||||
public boolean containsKey(String key) {
|
||||
return properties.containsKey(key) || bundle.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value for the given key.
|
||||
*
|
||||
* @param key the property's key
|
||||
* @param value the value of the property
|
||||
*/
|
||||
public void setProperty(String key, Object value) {
|
||||
properties.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all keys of this configuration.
|
||||
*
|
||||
* @return the keys
|
||||
*/
|
||||
public Set<String> listKeys() {
|
||||
Set<String> keys = bundle.keySet();
|
||||
keys.addAll(properties.keySet());
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package dslab.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Reads encryption keys from the file system.
|
||||
*/
|
||||
public final class Keys {
|
||||
|
||||
private Keys() {
|
||||
// util class
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an RSA private key from the given file.
|
||||
* The key files must be in DER format so JAVA can read it.
|
||||
*
|
||||
* @param file the file containing the RSA key
|
||||
* @return a PrivateKey instance
|
||||
* @throws IOException if an exception occurred while reading the key file
|
||||
* @throws NullPointerException if encoded key is null
|
||||
* @throws IllegalStateException if an error occurred in the java security api
|
||||
*/
|
||||
public static PrivateKey readPrivateKey(File file) throws IOException, NullPointerException, IllegalStateException {
|
||||
try {
|
||||
byte[] fileContent = Files.readAllBytes(file.toPath());
|
||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(fileContent);
|
||||
|
||||
return createPrivateKey(spec);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IOException(String.format("Cannot read key file %s.", file.getCanonicalPath()));
|
||||
}
|
||||
catch (NullPointerException e) {
|
||||
throw new NullPointerException(String.format("Key seems to be empty from file %s", file.getCanonicalPath()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an RSA public key from the given file.
|
||||
* The key files must be in DER format so JAVA can read it.
|
||||
*
|
||||
* @param file the file containing the RSA key
|
||||
* @return a PublicKey instance
|
||||
* @throws IOException if an exception occurred while reading the key file
|
||||
* @throws NullPointerException if encoded key is null
|
||||
* @throws IllegalStateException if an error occurred in the java security api
|
||||
*/
|
||||
public static PublicKey readPublicKey(File file) throws IOException, NullPointerException, IllegalStateException {
|
||||
try {
|
||||
byte[] fileContent = Files.readAllBytes(file.toPath());
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(fileContent);
|
||||
|
||||
return createPublicKey(spec);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IOException(String.format("Cannot read key file %s.", file.getCanonicalPath()), e);
|
||||
}
|
||||
catch (NullPointerException e) {
|
||||
throw new NullPointerException(String.format("Key seems to be empty from file %s", file.getCanonicalPath()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the {@link SecretKeySpec} from the given location which is expected to contain a HMAC SHA-256 key.
|
||||
*
|
||||
* @param file the path to key located in the file system
|
||||
* @return the secret key
|
||||
* @throws IOException if an I/O error occurs or the security provider cannot handle the file
|
||||
*/
|
||||
public static SecretKeySpec readSecretKey(File file) throws IOException {
|
||||
try (FileInputStream in = new FileInputStream(file)) {
|
||||
byte[] keyBytes = new byte[1024];
|
||||
if (in.read(keyBytes) < 0) {
|
||||
throw new IOException(String.format("Cannot read key file %s.", file.getCanonicalPath()));
|
||||
}
|
||||
|
||||
byte[] input = hexStringToByteArray(new String(keyBytes));
|
||||
return new SecretKeySpec(input, "HmacSHA256");
|
||||
}
|
||||
}
|
||||
|
||||
private static PrivateKey createPrivateKey(KeySpec spec) {
|
||||
try {
|
||||
return getRsaKeyFactory().generatePrivate(spec);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new IllegalStateException("Error creating private key", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static PublicKey createPublicKey(KeySpec spec) {
|
||||
try {
|
||||
return getRsaKeyFactory().generatePublic(spec);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new IllegalStateException("Error creating private key", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyFactory getRsaKeyFactory() throws IllegalStateException {
|
||||
try {
|
||||
return KeyFactory.getInstance("RSA");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Error creating RSA key factory with default security provider", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] hexStringToByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# TODO: replace host and port with that of your server
|
||||
transfer.host=127.0.0.1
|
||||
transfer.port=port_range
|
||||
transfer.email=arthur@earth.planet
|
||||
|
||||
# TODO: replace host and port with that of your server
|
||||
mailbox.host=127.0.0.1
|
||||
mailbox.port=port_range+3
|
||||
mailbox.user=arthur
|
||||
mailbox.password=23456
|
||||
@@ -0,0 +1,10 @@
|
||||
# TODO: replace host and port with that of your server
|
||||
transfer.host=127.0.0.1
|
||||
transfer.port=port_range
|
||||
transfer.email=trillian@earth.planet
|
||||
|
||||
# TODO: replace host and port with that of your server
|
||||
mailbox.host=127.0.0.1
|
||||
mailbox.port=port_range+3
|
||||
mailbox.user=trillian
|
||||
mailbox.password=12345
|
||||
@@ -0,0 +1,10 @@
|
||||
# TODO: replace host and port with that of your server
|
||||
transfer.host=127.0.0.1
|
||||
transfer.port=port_range+1
|
||||
transfer.email=zaphod@univer.ze
|
||||
|
||||
# TODO: replace host and port with that of your server
|
||||
mailbox.host=127.0.0.1
|
||||
mailbox.port=port_range+5
|
||||
mailbox.user=zaphod
|
||||
mailbox.password=12345
|
||||
@@ -0,0 +1,4 @@
|
||||
# <domain>=<socket-address>
|
||||
# TODO: replace ports with the ports that your mailbox servers use
|
||||
earth.planet=127.0.0.1:port_range+2
|
||||
univer.ze=127.0.0.1:port_range+4
|
||||
@@ -0,0 +1,25 @@
|
||||
# TCP port used for the DMTP server socket
|
||||
# TODO: REPLACE with real value such as 16502 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
dmtp.tcp.port=port_range+2
|
||||
|
||||
# TCP port used for the DMAP server socket
|
||||
# TODO: REPLACE with real value such as 16503 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
dmap.tcp.port=port_range+3
|
||||
|
||||
# mail domain managed by the mailbox server
|
||||
domain=earth.planet
|
||||
|
||||
# location of the users for this mailbox server
|
||||
users.config=users-earth-planet.properties
|
||||
|
||||
# ============================================= Required for Assignment 2
|
||||
|
||||
# name of the root nameserver's remote object
|
||||
root_id=root-nameserver
|
||||
|
||||
# RMI registry host
|
||||
registry.host=localhost
|
||||
|
||||
# RMI registry port
|
||||
# TODO: REPLACE with real value such as 16509 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
registry.port=port_range+9
|
||||
@@ -0,0 +1,25 @@
|
||||
# TCP port used for the DMTP server socket
|
||||
# TODO: REPLACE with real value such as 16504 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
dmtp.tcp.port=port_range+4
|
||||
|
||||
# TCP port used for the DMAP server socket
|
||||
# TODO: REPLACE with real value such as 16505 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
dmap.tcp.port=port_range+5
|
||||
|
||||
# mail domain managed by the mailbox server
|
||||
domain=univer.ze
|
||||
|
||||
# location of the users for this mailbox server
|
||||
users.config=users-univer-ze.properties
|
||||
|
||||
# ============================================= Required for Assignment 2
|
||||
|
||||
# name of the root nameserver's remote object
|
||||
root_id=root-nameserver
|
||||
|
||||
# RMI registry host
|
||||
registry.host=localhost
|
||||
|
||||
# RMI registry port
|
||||
# TODO: REPLACE with real value such as 16509 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
registry.port=port_range+9
|
||||
@@ -0,0 +1,3 @@
|
||||
# UDP port used for accepting monitoring packets
|
||||
# TODO: REPLACE with real value such as 16508 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
udp.port=port_range+8
|
||||
@@ -0,0 +1,13 @@
|
||||
# binding name
|
||||
root_id=root-nameserver
|
||||
|
||||
# registry host
|
||||
registry.host=localhost
|
||||
|
||||
# registry port
|
||||
# TODO: REPLACE with real value such as 16509 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
registry.port=port_range+9
|
||||
|
||||
|
||||
# managed domain
|
||||
domain=earth.planet
|
||||
@@ -0,0 +1,13 @@
|
||||
# binding name
|
||||
root_id=root-nameserver
|
||||
|
||||
# registry host
|
||||
registry.host=localhost
|
||||
|
||||
# registry port
|
||||
# TODO: REPLACE with real value such as 16509 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
registry.port=port_range+9
|
||||
|
||||
|
||||
# managed domain
|
||||
domain=planet
|
||||
@@ -0,0 +1,9 @@
|
||||
# binding name
|
||||
root_id=root-nameserver
|
||||
|
||||
# registry host
|
||||
registry.host=localhost
|
||||
|
||||
# registry port
|
||||
# TODO: REPLACE with real value such as 16509 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
registry.port=port_range+9
|
||||
@@ -0,0 +1,13 @@
|
||||
# binding name
|
||||
root_id=root-nameserver
|
||||
|
||||
# registry host
|
||||
registry.host=localhost
|
||||
|
||||
# registry port
|
||||
# TODO: REPLACE with real value such as 16509 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
registry.port=port_range+9
|
||||
|
||||
|
||||
# managed domain
|
||||
domain=ze
|
||||
@@ -0,0 +1,20 @@
|
||||
# TCP port used for the DMTP server socket
|
||||
# TODO: REPLACE with real value such as 16500 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
tcp.port=port_range
|
||||
|
||||
# UDP socket address of the monitoring server
|
||||
monitoring.host=127.0.0.1
|
||||
# TODO: REPLACE with the real value of the monitoring server port
|
||||
monitoring.port=port_range+8
|
||||
|
||||
# ============================================= Required for Assignment 2
|
||||
|
||||
# name of the root nameserver's remote object
|
||||
root_id=root-nameserver
|
||||
|
||||
# RMI registry host
|
||||
registry.host=localhost
|
||||
|
||||
# RMI registry port
|
||||
# TODO: REPLACE with real value such as 16509 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
registry.port=port_range+9
|
||||
@@ -0,0 +1,20 @@
|
||||
# TCP port used for the DMTP server socket
|
||||
# TODO: REPLACE with real value such as 16501 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
tcp.port=port_range+1
|
||||
|
||||
# UDP socket address of the monitoring server
|
||||
monitoring.host=127.0.0.1
|
||||
# TODO: REPLACE with the real value of the monitoring server port
|
||||
monitoring.port=port_range+8
|
||||
|
||||
# ============================================= Required for Assignment 2
|
||||
|
||||
# name of the root nameserver's remote object
|
||||
root_id=root-nameserver
|
||||
|
||||
# RMI registry host
|
||||
registry.host=localhost
|
||||
|
||||
# RMI registry port
|
||||
# TODO: REPLACE with real value such as 16509 - considering the port range associated with your account - you have received after Lab 0 a port range (beginning_of_the_range, end_of_the_range)
|
||||
registry.port=port_range+9
|
||||
@@ -0,0 +1,3 @@
|
||||
# <username>=<password>
|
||||
trillian=12345
|
||||
arthur=23456
|
||||
@@ -0,0 +1,2 @@
|
||||
# <username>=<password>
|
||||
zaphod=12345
|
||||
@@ -0,0 +1,9 @@
|
||||
package dslab;
|
||||
|
||||
/**
|
||||
* CheckedConsumer.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CheckedConsumer<T, E extends Exception> {
|
||||
void accept(T socket) throws E;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package dslab;
|
||||
|
||||
public interface Constants {
|
||||
|
||||
/**
|
||||
* Default time (in milliseconds) to wait after starting a component to test.
|
||||
*/
|
||||
long COMPONENT_STARTUP_WAIT = 3000;
|
||||
|
||||
/**
|
||||
* Default time (in milliseconds) to wait after shutting down a component to test.
|
||||
*/
|
||||
long COMPONENT_TEARDOWN_WAIT = 3000;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package dslab;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.Assert;
|
||||
import org.junit.rules.ErrorCollector;
|
||||
|
||||
public class JunitSocketClient implements Closeable {
|
||||
|
||||
private ErrorCollector err;
|
||||
|
||||
private Socket socket;
|
||||
private PrintWriter writer;
|
||||
|
||||
private StreamListener listener;
|
||||
|
||||
/**
|
||||
* Creates a new Socket that connects to localhost on the given port and holds the I/O resources.
|
||||
*
|
||||
* @param port the port to connect to
|
||||
* @throws IOException if an I/O error occurred while connecting.
|
||||
*/
|
||||
public JunitSocketClient(int port) throws IOException {
|
||||
this(new Socket("127.0.0.1", port));
|
||||
}
|
||||
|
||||
public JunitSocketClient(Socket socket) throws IOException {
|
||||
this.socket = socket;
|
||||
this.writer = new PrintWriter(socket.getOutputStream());
|
||||
this.listener = new StreamListener(socket.getInputStream());
|
||||
|
||||
new Thread(listener).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Socket that connects to localhost on the given port and holds the I/O resources.
|
||||
*
|
||||
* @param port the port to connect to
|
||||
* @param err the error collector used to verify communication
|
||||
* @throws IOException if an I/O error occurred while connecting.
|
||||
*/
|
||||
|
||||
public JunitSocketClient(int port, ErrorCollector err) throws IOException {
|
||||
this(port);
|
||||
this.err = err;
|
||||
}
|
||||
|
||||
public Socket getSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
public PrintWriter getWriter() {
|
||||
return writer;
|
||||
}
|
||||
|
||||
public void send(String message) {
|
||||
writer.println(message);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
public String sendAndListen(String message) {
|
||||
send(message);
|
||||
return listen();
|
||||
}
|
||||
|
||||
public String listen() {
|
||||
return listen(1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public String listen(long time, TimeUnit timeUnit) {
|
||||
return listener.listen(time, timeUnit);
|
||||
}
|
||||
|
||||
public String read() throws IOException {
|
||||
return listener.poll(1, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a line from the input stream and verifies that it contains the given string.
|
||||
*
|
||||
* @param string the partial string to match
|
||||
* @throws IOException on read errors
|
||||
*/
|
||||
public void verify(String string) throws IOException {
|
||||
assertThat(read(), containsString(string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given string to the output stream, and then behaves like {@link #verify(String)}.
|
||||
*
|
||||
* @param request the request to send
|
||||
* @param response the expected response (partial string match)
|
||||
* @throws IOException on I/O errors
|
||||
*/
|
||||
public void sendAndVerify(String request, String response) throws IOException {
|
||||
assertThat(sendAndRead(request), containsString(response));
|
||||
}
|
||||
|
||||
public String sendAndRead(String message) throws IOException {
|
||||
send(message);
|
||||
return read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
closeQuietly(listener);
|
||||
closeQuietly(writer);
|
||||
closeQuietly(socket);
|
||||
}
|
||||
|
||||
private <T> void assertThat(T actual, Matcher<? super T> matcher) {
|
||||
if (err != null) {
|
||||
err.checkThat(actual, matcher);
|
||||
} else {
|
||||
Assert.assertThat(actual, matcher);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeQuietly(Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package dslab;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* This {@link OutputStream} has no destination (file/socket etc.) and all bytes written to it are ignored and lost.
|
||||
*/
|
||||
public final class NullOutputStream extends OutputStream {
|
||||
public static final OutputStream INSTANCE = new NullOutputStream();
|
||||
|
||||
private NullOutputStream() {
|
||||
}
|
||||
|
||||
public static OutputStream getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards the specified byte.
|
||||
*/
|
||||
@Override
|
||||
public void write(int b) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards the specified byte array.
|
||||
*/
|
||||
@Override
|
||||
public void write(byte[] b) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards the specified byte array.
|
||||
*/
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package dslab;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Used for mocking purposes, this class provides a simple control loop over a ServerSocket.
|
||||
*/
|
||||
public class SimpleTcpServer implements Runnable, Closeable {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(SimpleTcpServer.class);
|
||||
|
||||
private final int port;
|
||||
private CheckedConsumer<Socket, IOException> socketAcceptor;
|
||||
|
||||
private ServerSocket serverSocket;
|
||||
|
||||
public SimpleTcpServer(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public synchronized CheckedConsumer<Socket, IOException> getSocketAcceptor() {
|
||||
return socketAcceptor;
|
||||
}
|
||||
|
||||
public void setSocketAcceptor(CheckedConsumer<Socket, IOException> socketAcceptor) {
|
||||
this.socketAcceptor = socketAcceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
LOG.info("Starting mock server on " + port);
|
||||
serverSocket = new ServerSocket(port);
|
||||
|
||||
while (true) {
|
||||
Socket connection;
|
||||
try {
|
||||
LOG.info("Listening on client connection");
|
||||
connection = serverSocket.accept();
|
||||
} catch (SocketException e) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
LOG.info("Processing client connection " + connection);
|
||||
onAccept(connection);
|
||||
} catch (SocketException e) {
|
||||
continue;
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
} finally {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (serverSocket != null) {
|
||||
try {
|
||||
LOG.info("Closing server socket");
|
||||
serverSocket.close();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Error closing server socket", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onAccept(Socket socket) throws IOException {
|
||||
if (socketAcceptor != null) {
|
||||
socketAcceptor.accept(socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package dslab;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.BindException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
/**
|
||||
* Util class for checking sockets.
|
||||
*/
|
||||
public final class Sockets {
|
||||
|
||||
private Sockets() {
|
||||
// util class
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits a given time for a TCP server socket to be opened at the given host and port by repeatedly (every 10 ms)
|
||||
* trying to connect to the address.
|
||||
*
|
||||
* @param host the expected server socket host
|
||||
* @param port the expected server socket port
|
||||
* @param ms the time in milliseconds.
|
||||
* @throws SocketTimeoutException if the timeout period was reached
|
||||
*/
|
||||
public static void waitForSocket(String host, int port, long ms) throws SocketTimeoutException {
|
||||
long interval = 10;
|
||||
|
||||
while (ms > 0) {
|
||||
try (Socket ignored = new Socket(host, port)) {
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
ms -= interval;
|
||||
|
||||
try {
|
||||
Thread.sleep(interval);
|
||||
} catch (InterruptedException e1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new SocketTimeoutException("Gave up waiting for socket " + host + ":" + port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to open a DatagramSocket on the given port. If a BindException is thrown, it indicates that the socket was
|
||||
* open before, and the method will return true. If the socket was opened successfully, the method returns false and
|
||||
* immediately closes the socket.
|
||||
*
|
||||
* @param port the local port
|
||||
* @return true if the socket was already open
|
||||
* @throws SocketException if the socket could not be opened due to some other reason than a bind exception
|
||||
*/
|
||||
public static boolean isDatagramSocketOpen(int port) throws SocketException {
|
||||
try (DatagramSocket socket = new DatagramSocket(port)) {
|
||||
return false;
|
||||
} catch (BindException e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to open a ServerSocket on the given port. If a BindException is thrown, it indicates that the socket was
|
||||
* open before, and the method will return true. If the socket was opened successfully, the method returns false and
|
||||
* immediately closes the socket.
|
||||
*
|
||||
* @param port the local port
|
||||
* @return true if the socket was already open
|
||||
* @throws IOException if the socket could not be opened due to some other reason than a bind exception
|
||||
*/
|
||||
public static boolean isServerSocketOpen(int port) throws IOException {
|
||||
try (ServerSocket socket = new ServerSocket(port)) {
|
||||
return false;
|
||||
} catch (BindException e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package dslab;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.SocketException;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Runnable that reads from a Socket's InputStream line-by-line and writes it to a queue, until the socket is closed.
|
||||
*/
|
||||
public class StreamListener implements Runnable, Closeable {
|
||||
|
||||
public static final String NULL_CHAR = new String(new byte[]{0x0});
|
||||
|
||||
private InputStream in;
|
||||
private LinkedBlockingQueue<String> queue;
|
||||
|
||||
public StreamListener(InputStream in) {
|
||||
this.in = in;
|
||||
this.queue = new LinkedBlockingQueue<>();
|
||||
}
|
||||
|
||||
private static String removeNullBytes(String str) {
|
||||
return str.replace(NULL_CHAR, "");
|
||||
}
|
||||
|
||||
public String poll(long timeout, TimeUnit timeUnit) {
|
||||
try {
|
||||
return queue.poll(timeout, timeUnit);
|
||||
} catch (InterruptedException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String listen(long timeout, TimeUnit timeUnit) {
|
||||
StringBuilder str = new StringBuilder(128);
|
||||
|
||||
String line;
|
||||
while ((line = poll(timeout, timeUnit)) != null) {
|
||||
str.append(removeNullBytes(line)).append("\n");
|
||||
}
|
||||
|
||||
if (str.length() > 0) {
|
||||
// remove trailing whitespace
|
||||
int i = str.length() - 1;
|
||||
if ('\n' == str.charAt(i)) {
|
||||
str.deleteCharAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
|
||||
String line;
|
||||
try {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
queue.offer(line);
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
// socket closed
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
in.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package dslab;
|
||||
|
||||
import org.hamcrest.Factory;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.core.SubstringMatcher;
|
||||
|
||||
/**
|
||||
* String matcher that checks for regex matching.
|
||||
*/
|
||||
public class StringMatches extends SubstringMatcher {
|
||||
|
||||
public StringMatches(String substring) {
|
||||
super(substring);
|
||||
}
|
||||
|
||||
@Factory
|
||||
public static Matcher<String> matchesPattern(String pattern) {
|
||||
return new StringMatches(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean evalSubstringOf(String string) {
|
||||
return string.matches(substring);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String relationship() {
|
||||
return "matching";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package dslab;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.ErrorCollector;
|
||||
import org.junit.rules.Timeout;
|
||||
|
||||
/**
|
||||
* Contains a generic setup for a unit test.
|
||||
*/
|
||||
public class TestBase {
|
||||
|
||||
@Rule
|
||||
public ErrorCollector err = new ErrorCollector();
|
||||
|
||||
@Rule
|
||||
public Timeout timeout = new Timeout(30, TimeUnit.SECONDS); // fail tests that do not terminate after 30 seconds
|
||||
|
||||
protected TestInputStream in;
|
||||
protected TestOutputStream out;
|
||||
|
||||
@Before
|
||||
public void setUpBase() throws Exception {
|
||||
in = new TestInputStream();
|
||||
out = new TestOutputStream();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package dslab;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Simulates reading lines from an {@link InputStream}.
|
||||
*
|
||||
* Internally, the lines read from the underlying {@link InputStream} are buffered and can be retrieved on demand for
|
||||
* verification purposes.
|
||||
*/
|
||||
public class TestInputStream extends InputStream {
|
||||
private volatile BlockingQueue<String> lines = new LinkedBlockingQueue<>();
|
||||
private InputStream in;
|
||||
|
||||
@Override
|
||||
public synchronized int read() throws IOException {
|
||||
if (in == null) {
|
||||
if ((in = nextLine()) == null) {
|
||||
return -1;
|
||||
}
|
||||
} else if (in.available() <= 0) {
|
||||
try {
|
||||
return -1;
|
||||
} finally {
|
||||
in = null;
|
||||
}
|
||||
}
|
||||
return in != null ? in.read() : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the lines available for reading.
|
||||
*
|
||||
* @return the available lines
|
||||
* @throws IOException if the stream is closed
|
||||
*/
|
||||
public List<String> getLines() throws IOException {
|
||||
List<String> copy = new ArrayList<>();
|
||||
do {
|
||||
try {
|
||||
copy.add(lines.take());
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
} while (lines.size() > 0);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given line to the input queue.
|
||||
*
|
||||
* @param line the line to add
|
||||
*/
|
||||
public void addLine(String line) {
|
||||
if (lines != null) {
|
||||
lines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the next line available for reading from it.
|
||||
* <p/>
|
||||
* This method blocks until a line is available or the stream becomes closed.
|
||||
*
|
||||
* @return the {@link InputStream} holding the line
|
||||
* @throws IOException if the stream is closed
|
||||
*/
|
||||
private InputStream nextLine() throws IOException {
|
||||
try {
|
||||
String line = null;
|
||||
while (lines != null && line == null) {
|
||||
line = lines.poll(500L, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
if (line != null) {
|
||||
return new ByteArrayInputStream((line.endsWith("\n") ? line : line + '\n').getBytes());
|
||||
} else {
|
||||
return new ByteArrayInputStream("".getBytes());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (in != System.in) {
|
||||
super.close();
|
||||
}
|
||||
lines = null;
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package dslab;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Simulates writing lines to an {@link PrintStream}.
|
||||
* <p/>
|
||||
* Internally, the lines written to the underlying {@link PrintStream} are buffered and can be retrieved on demand for
|
||||
* verification purposes.
|
||||
*/
|
||||
public class TestOutputStream extends PrintStream {
|
||||
private final LinkedBlockingQueue<String> lines = new LinkedBlockingQueue<>();
|
||||
private volatile StringBuilder line = new StringBuilder();
|
||||
private PrintStream delegate;
|
||||
|
||||
/**
|
||||
* Creates a new {@code TestOutputStream} instance writing to an {@link NullOutputStream}.
|
||||
*/
|
||||
public TestOutputStream() {
|
||||
this(new PrintStream(NullOutputStream.getInstance()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code TestOutputStream} instance writing to the provided {@link PrintStream}.
|
||||
*
|
||||
* @param delegate the stream to write to
|
||||
*/
|
||||
public TestOutputStream(PrintStream delegate) {
|
||||
super(delegate);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (delegate != System.out) {
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) {
|
||||
delegate.write(b);
|
||||
if (b == '\r') {
|
||||
// Do nothing
|
||||
} else if (b == '\n') {
|
||||
addLine();
|
||||
} else {
|
||||
line.append((char) b);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(byte b[], int off, int len) {
|
||||
if (b == null) {
|
||||
throw new NullPointerException();
|
||||
} else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
} else if (len == 0) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
write(b[off + i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the lines written to the {@link PrintStream} so far.
|
||||
*
|
||||
* @return the written lines
|
||||
*/
|
||||
public List<String> getLines() {
|
||||
synchronized (lines) {
|
||||
if (line.length() > 0) {
|
||||
addLine();
|
||||
}
|
||||
return new ArrayList<>(lines);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for stream output until no output has been received for one second.
|
||||
*
|
||||
* @return the aggregated output (joined by a newline)
|
||||
* @throws InterruptedException if the polling was interrupted
|
||||
*/
|
||||
public String listen() throws InterruptedException {
|
||||
return listen(1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public String listen(long timeout, TimeUnit timeUnit) throws InterruptedException {
|
||||
StringBuilder str = new StringBuilder(128);
|
||||
|
||||
String line;
|
||||
while ((line = poll(timeout, timeUnit)) != null) {
|
||||
str.append(line).append("\n");
|
||||
}
|
||||
|
||||
if (str.length() > 0) {
|
||||
// remove trailing whitespace
|
||||
int i = str.length() - 1;
|
||||
if ('\n' == str.charAt(i)) {
|
||||
str.deleteCharAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public String poll(long time, TimeUnit timeUnit) throws InterruptedException {
|
||||
return lines.poll(time, timeUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the lines written to the {@link PrintStream} so far and clears the buffer.
|
||||
*
|
||||
* @return the written lines
|
||||
* @see #getLines()
|
||||
* @see #clear()
|
||||
*/
|
||||
public List<String> reset() {
|
||||
synchronized (lines) {
|
||||
List<String> lines = getLines();
|
||||
clear();
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the buffer holding the lines written to the {@link PrintStream} so far.
|
||||
*/
|
||||
private void clear() {
|
||||
synchronized (lines) {
|
||||
lines.clear();
|
||||
line = new StringBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the current line to the buffer.
|
||||
*/
|
||||
private void addLine() {
|
||||
synchronized (lines) {
|
||||
lines.add(line.toString());
|
||||
line = new StringBuilder();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package dslab.client;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.allOf;
|
||||
import static org.hamcrest.CoreMatchers.anyOf;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ErrorCollector;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.Constants;
|
||||
import dslab.JunitSocketClient;
|
||||
import dslab.Sockets;
|
||||
import dslab.TestInputStream;
|
||||
import dslab.TestOutputStream;
|
||||
import dslab.util.Config;
|
||||
|
||||
/**
|
||||
* Starts a mailbox server and a message client, and injects mails to the mailbox server that are read by the mail
|
||||
* client. Note that your mailbox server should run even if there is no naming service is available.
|
||||
*/
|
||||
public class MessageClientMailboxTest {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(MessageClientMailboxTest.class);
|
||||
|
||||
@Rule
|
||||
public ErrorCollector err = new ErrorCollector();
|
||||
|
||||
private Config mailboxConfig;
|
||||
private TestInputStream mailboxServerIn;
|
||||
private TestOutputStream mailboxServerOut;
|
||||
private Thread mailboxServerThread;
|
||||
|
||||
private TestInputStream messageClientIn;
|
||||
private TestOutputStream messageClientOut;
|
||||
private Thread messageClientThread;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
LOG.info("Creating mailbox server");
|
||||
mailboxConfig = new Config("mailbox-earth-planet");
|
||||
mailboxServerIn = new TestInputStream();
|
||||
mailboxServerOut = new TestOutputStream();
|
||||
Runnable mailboxServer = ComponentFactory.createMailboxServer("mailbox-earth-planet", mailboxServerIn, mailboxServerOut);
|
||||
|
||||
mailboxServerThread = new Thread(mailboxServer);
|
||||
mailboxServerThread.start();
|
||||
|
||||
LOG.info("Waiting for mailbox server sockets");
|
||||
Sockets.waitForSocket("localhost", mailboxConfig.getInt("dmtp.tcp.port"), Constants.COMPONENT_STARTUP_WAIT);
|
||||
Sockets.waitForSocket("localhost", mailboxConfig.getInt("dmap.tcp.port"), Constants.COMPONENT_STARTUP_WAIT);
|
||||
|
||||
LOG.info("Starting message client");
|
||||
messageClientIn = new TestInputStream();
|
||||
messageClientOut = new TestOutputStream();
|
||||
Runnable messageClient = ComponentFactory.createMessageClient("client-trillian", messageClientIn, messageClientOut);
|
||||
|
||||
messageClientThread = new Thread(messageClient);
|
||||
messageClientThread.start();
|
||||
Thread.sleep(Constants.COMPONENT_STARTUP_WAIT);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
messageClientIn.addLine("shutdown");
|
||||
messageClientThread.join(Constants.COMPONENT_TEARDOWN_WAIT);
|
||||
|
||||
mailboxServerIn.addLine("shutdown");
|
||||
mailboxServerThread.join(Constants.COMPONENT_TEARDOWN_WAIT);
|
||||
}
|
||||
|
||||
@Test(timeout = 20000)
|
||||
public void inbox_singleMail_showsAllInboxDataCorrectly() throws Exception {
|
||||
|
||||
try (JunitSocketClient client = new JunitSocketClient(mailboxConfig.getInt("dmtp.tcp.port"), err)) {
|
||||
err.checkThat("Expected mailbox server to implement DMTP2.0", client.read(), is("ok DMTP2.0"));
|
||||
client.sendAndVerify("begin", "ok");
|
||||
client.sendAndVerify("from arthur@earth.planet", "ok");
|
||||
client.sendAndVerify("to trillian@earth.planet", "ok");
|
||||
client.sendAndVerify("subject somesubject", "ok");
|
||||
client.sendAndVerify("data somedata", "ok");
|
||||
client.sendAndVerify("hash 98yUrgHu4BctmhAel19nUAhGRVdVh9qD7Ge3VJBiehk=", "ok"); // valid hash
|
||||
client.sendAndVerify("send", "ok");
|
||||
client.send("quit");
|
||||
}
|
||||
|
||||
Thread.sleep(2000); // wait a bit
|
||||
|
||||
messageClientIn.addLine("inbox");
|
||||
String output = messageClientOut.listen();
|
||||
|
||||
err.checkThat(output, allOf(
|
||||
containsString("trillian@earth.planet"),
|
||||
containsString("arthur@earth.planet"),
|
||||
containsString("somesubject"),
|
||||
containsString("somedata")
|
||||
));
|
||||
}
|
||||
|
||||
@Test(timeout = 40000)
|
||||
public void inbox_mulipleMails_showsAllInboxDataCorrectly() throws Exception {
|
||||
|
||||
// send a mail to trillian
|
||||
try (JunitSocketClient client = new JunitSocketClient(mailboxConfig.getInt("dmtp.tcp.port"), err)) {
|
||||
err.checkThat("Expected mailbox server to implement DMTP2.0", client.read(), is("ok DMTP2.0"));
|
||||
client.sendAndVerify("begin", "ok");
|
||||
client.sendAndVerify("from arthur@earth.planet", "ok");
|
||||
client.sendAndVerify("to trillian@earth.planet", "ok");
|
||||
client.sendAndVerify("subject somesubject", "ok");
|
||||
client.sendAndVerify("data somedata", "ok");
|
||||
client.sendAndVerify("hash 98yUrgHu4BctmhAel19nUAhGRVdVh9qD7Ge3VJBiehk=", "ok"); // valid hash
|
||||
client.sendAndVerify("send", "ok");
|
||||
client.send("quit");
|
||||
}
|
||||
|
||||
// send another mail to trillian
|
||||
try (JunitSocketClient client = new JunitSocketClient(mailboxConfig.getInt("dmtp.tcp.port"), err)) {
|
||||
err.checkThat(client.read(), is("ok DMTP2.0"));
|
||||
client.sendAndVerify("begin", "ok");
|
||||
client.sendAndVerify("from zaphod@univer.ze", "ok");
|
||||
client.sendAndVerify("to trillian@earth.planet", "ok");
|
||||
client.sendAndVerify("subject zaphodsubject", "ok");
|
||||
client.sendAndVerify("data zaphoddata", "ok");
|
||||
client.sendAndVerify("hash 4Bctm9nHuVU9qe3VJBhiAhGR987GyUekrgdVhhAel1D=", "ok"); // invalid hash
|
||||
client.sendAndVerify("send", "ok");
|
||||
client.send("quit");
|
||||
}
|
||||
|
||||
// send a mail to arthur
|
||||
try (JunitSocketClient client = new JunitSocketClient(mailboxConfig.getInt("dmtp.tcp.port"), err)) {
|
||||
err.checkThat(client.read(), is("ok DMTP2.0"));
|
||||
client.sendAndVerify("begin", "ok");
|
||||
client.sendAndVerify("from zaphod@univer.ze", "ok");
|
||||
client.sendAndVerify("to arthur@earth.planet", "ok");
|
||||
client.sendAndVerify("subject nottrilliansubject", "ok");
|
||||
client.sendAndVerify("data nottrilliandata", "ok");
|
||||
client.sendAndVerify("hash gdBctmhAeU9qel1DuV3VJBiUrhkA9Vhe4n987GyHhGR=", "ok"); // invalid hash
|
||||
client.sendAndVerify("send", "ok");
|
||||
client.send("quit");
|
||||
}
|
||||
|
||||
Thread.sleep(2000); // wait a bit
|
||||
|
||||
messageClientIn.addLine("inbox");
|
||||
String output = messageClientOut.listen(3, TimeUnit.SECONDS);
|
||||
|
||||
err.checkThat("inbox output did not contain all data from first mail", output, allOf(
|
||||
containsString("trillian@earth.planet"),
|
||||
containsString("arthur@earth.planet"),
|
||||
containsString("somesubject"),
|
||||
containsString("somedata")
|
||||
));
|
||||
err.checkThat("inbox output did not contain all data from second mail", output, allOf(
|
||||
containsString("zaphod@univer.ze"),
|
||||
containsString("zaphodsubject"),
|
||||
containsString("zaphoddata")
|
||||
));
|
||||
err.checkThat("inbox output contained data from a different user", output, not(anyOf(
|
||||
containsString("nottrilliansubject"),
|
||||
containsString("nottrilliandata")
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package dslab.client;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ErrorCollector;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.Constants;
|
||||
import dslab.JunitSocketClient;
|
||||
import dslab.SimpleTcpServer;
|
||||
import dslab.Sockets;
|
||||
import dslab.TestInputStream;
|
||||
import dslab.TestOutputStream;
|
||||
import dslab.util.Config;
|
||||
|
||||
/**
|
||||
* Tests that the message client connects to the configured DMAP server at startup and sends the startsecure command.
|
||||
*/
|
||||
public class MessageClientStartupTest {
|
||||
|
||||
@Rule
|
||||
public ErrorCollector err = new ErrorCollector();
|
||||
|
||||
private SimpleTcpServer dmapServer;
|
||||
private Thread serverThread;
|
||||
private String clientId = "client-trillian";
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
Config clientConfig = new Config(clientId);
|
||||
int port = clientConfig.getInt("mailbox.port");
|
||||
dmapServer = new SimpleTcpServer(port);
|
||||
|
||||
serverThread = new Thread(dmapServer);
|
||||
serverThread.start();
|
||||
|
||||
Sockets.waitForSocket("localhost", port, Constants.COMPONENT_STARTUP_WAIT);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
dmapServer.close();
|
||||
serverThread.join(Constants.COMPONENT_TEARDOWN_WAIT);
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void startClient_shouldConnectToMailboxServerAndSendStartsecure() throws Exception {
|
||||
final CountDownLatch connected = new CountDownLatch(1);
|
||||
|
||||
// setup mock server
|
||||
dmapServer.setSocketAcceptor(socket -> {
|
||||
try (JunitSocketClient client = new JunitSocketClient(socket)) {
|
||||
client.send("ok DMAP2.0");
|
||||
err.checkThat("expected first command from client to be startsecure", client.read(), is("startsecure"));
|
||||
|
||||
connected.countDown();
|
||||
// the server unexpectedly terminates the connection here. make sure your client can handle it!
|
||||
} finally {
|
||||
dmapServer.close();
|
||||
}
|
||||
});
|
||||
|
||||
// setup message client
|
||||
TestInputStream messageClientIn = new TestInputStream();
|
||||
TestOutputStream messageClientOut = new TestOutputStream();
|
||||
|
||||
Runnable messageClient = ComponentFactory.createMessageClient(clientId, messageClientIn, messageClientOut);
|
||||
Thread messClientThread = new Thread(messageClient);
|
||||
messClientThread.start();
|
||||
|
||||
// shutdown message client once the connection has been made
|
||||
connected.await();
|
||||
messageClientIn.addLine("shutdown");
|
||||
|
||||
try {
|
||||
messClientThread.join(Constants.COMPONENT_TEARDOWN_WAIT);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package dslab.mailbox;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.Constants;
|
||||
import dslab.JunitSocketClient;
|
||||
import dslab.Sockets;
|
||||
import dslab.TestBase;
|
||||
import dslab.util.Config;
|
||||
|
||||
public class MailboxServerProtocolTest extends TestBase {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(MailboxServerProtocolTest.class);
|
||||
|
||||
private String componentId = "mailbox-earth-planet";
|
||||
|
||||
private IMailboxServer component;
|
||||
private int dmapServerPort;
|
||||
private int dmtpServerPort;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
component = ComponentFactory.createMailboxServer(componentId, in, out);
|
||||
dmapServerPort = new Config(componentId).getInt("dmap.tcp.port");
|
||||
dmtpServerPort = new Config(componentId).getInt("dmtp.tcp.port");
|
||||
|
||||
new Thread(component).start();
|
||||
|
||||
LOG.info("Waiting for server sockets to appear");
|
||||
Sockets.waitForSocket("localhost", dmapServerPort, Constants.COMPONENT_STARTUP_WAIT);
|
||||
Sockets.waitForSocket("localhost", dmtpServerPort, Constants.COMPONENT_STARTUP_WAIT);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
in.addLine("shutdown"); // send "shutdown" command to command line
|
||||
Thread.sleep(Constants.COMPONENT_TEARDOWN_WAIT);
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void loginAndLogout_withValidLogin() throws Exception {
|
||||
try (JunitSocketClient client = new JunitSocketClient(dmapServerPort, err)) {
|
||||
client.verify("ok DMAP");
|
||||
client.sendAndVerify("login trillian 12345", "ok");
|
||||
client.sendAndVerify("logout", "ok");
|
||||
client.sendAndVerify("quit", "ok bye");
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void login_withInvalidLogin_returnsError() throws Exception {
|
||||
try (JunitSocketClient client = new JunitSocketClient(dmapServerPort, err)) {
|
||||
client.verify("ok DMAP");
|
||||
client.sendAndVerify("login trillian WRONGPW", "error");
|
||||
client.sendAndVerify("quit", "ok bye");
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void acceptDmtpMessage_listDmapMessage() throws Exception {
|
||||
|
||||
// accept a message via DMTP (to trillian)
|
||||
try (JunitSocketClient client = new JunitSocketClient(dmtpServerPort, err)) {
|
||||
client.verify("ok DMTP");
|
||||
client.sendAndVerify("begin", "ok");
|
||||
client.sendAndVerify("from arthur@earth.planet", "ok");
|
||||
client.sendAndVerify("to trillian@earth.planet", "ok 1");
|
||||
client.sendAndVerify("subject hello", "ok");
|
||||
client.sendAndVerify("data hello from junit", "ok");
|
||||
client.sendAndVerify("send", "ok");
|
||||
client.sendAndVerify("quit", "ok bye");
|
||||
}
|
||||
|
||||
// list the message via DMAP list
|
||||
try (JunitSocketClient client = new JunitSocketClient(dmapServerPort, err)) {
|
||||
client.verify("ok DMAP");
|
||||
client.sendAndVerify("login trillian 12345", "ok");
|
||||
|
||||
client.send("list");
|
||||
String listResult = client.listen();
|
||||
err.checkThat(listResult, containsString("arthur@earth.planet hello"));
|
||||
|
||||
client.sendAndVerify("logout", "ok");
|
||||
client.sendAndVerify("quit", "ok bye");
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void dmtpMessage_withUnknownRecipient_returnsError() throws Exception {
|
||||
|
||||
// accept a message via DMTP (to trillian)
|
||||
try (JunitSocketClient client = new JunitSocketClient(dmtpServerPort, err)) {
|
||||
client.verify("ok DMTP");
|
||||
client.sendAndVerify("begin", "ok");
|
||||
client.sendAndVerify("from arthur@earth.planet", "ok");
|
||||
client.sendAndVerify("to unknown@earth.planet", "error unknown");
|
||||
client.sendAndVerify("quit", "ok bye");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package dslab.mailbox;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Test;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.Constants;
|
||||
import dslab.Sockets;
|
||||
import dslab.TestBase;
|
||||
import dslab.monitoring.MonitoringServerTest;
|
||||
import dslab.util.Config;
|
||||
|
||||
/**
|
||||
* MailboxServerTest.
|
||||
*/
|
||||
public class MailboxServerTest extends TestBase {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(MonitoringServerTest.class);
|
||||
|
||||
@Test
|
||||
public void runAndShutdownTransferServer_createsAndStopsTcpSocketCorrectly() throws Exception {
|
||||
String componentId = "mailbox-earth-planet";
|
||||
Config config = new Config(componentId);
|
||||
|
||||
IMailboxServer component = ComponentFactory.createMailboxServer(componentId, in, out);
|
||||
int dmtpPort = config.getInt("dmtp.tcp.port");
|
||||
int dmapPort = config.getInt("dmap.tcp.port");
|
||||
|
||||
assertThat(component, is(notNullValue()));
|
||||
|
||||
Thread componentThread = new Thread(component);
|
||||
LOG.info("Starting thread with component " + component);
|
||||
componentThread.start();
|
||||
|
||||
try {
|
||||
LOG.info("Waiting for DMTP socket to open on port " + dmtpPort);
|
||||
Sockets.waitForSocket("localhost", dmtpPort, Constants.COMPONENT_STARTUP_WAIT);
|
||||
} catch (SocketTimeoutException e) {
|
||||
err.addError(new AssertionError("Expected a TCP server socket on port " + dmtpPort, e));
|
||||
}
|
||||
|
||||
try {
|
||||
LOG.info("Waiting for DMAP socket to open on port " + dmapPort);
|
||||
Sockets.waitForSocket("localhost", dmapPort, Constants.COMPONENT_STARTUP_WAIT);
|
||||
} catch (SocketTimeoutException e) {
|
||||
err.addError(new AssertionError("Expected a TCP server socket on port " + dmapPort, e));
|
||||
}
|
||||
|
||||
LOG.info("Shutting down component " + component);
|
||||
in.addLine("shutdown"); // send "shutdown" command to command line
|
||||
Thread.sleep(Constants.COMPONENT_TEARDOWN_WAIT);
|
||||
|
||||
try {
|
||||
LOG.info("Waiting for thread to stop for component " + component);
|
||||
componentThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
err.addError(new AssertionError("Monitoring server was not terminated correctly"));
|
||||
}
|
||||
|
||||
|
||||
err.checkThat("Expected tcp socket on port " + dmtpPort + " to be closed after shutdown",
|
||||
Sockets.isServerSocketOpen(dmtpPort), is(false));
|
||||
|
||||
err.checkThat("Expected tcp socket on port " + dmapPort + " to be closed after shutdown",
|
||||
Sockets.isServerSocketOpen(dmapPort), is(false));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package dslab.mailbox;
|
||||
|
||||
import static dslab.StringMatches.matchesPattern;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.Constants;
|
||||
import dslab.JunitSocketClient;
|
||||
import dslab.Sockets;
|
||||
import dslab.TestBase;
|
||||
import dslab.util.Config;
|
||||
|
||||
public class MailboxStartsecureTest extends TestBase {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(MailboxServerProtocolTest.class);
|
||||
|
||||
|
||||
private int dmapServerPort;
|
||||
private int dmtpServerPort;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
String componentId = "mailbox-earth-planet";
|
||||
|
||||
IMailboxServer component = ComponentFactory.createMailboxServer(componentId, in, out);
|
||||
dmapServerPort = new Config(componentId).getInt("dmap.tcp.port");
|
||||
dmtpServerPort = new Config(componentId).getInt("dmtp.tcp.port");
|
||||
|
||||
new Thread(component).start();
|
||||
|
||||
LOG.info("Waiting for server sockets to appear");
|
||||
Sockets.waitForSocket("localhost", dmapServerPort, Constants.COMPONENT_STARTUP_WAIT);
|
||||
Sockets.waitForSocket("localhost", dmtpServerPort, Constants.COMPONENT_STARTUP_WAIT);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
in.addLine("shutdown"); // send "shutdown" command to command line
|
||||
Thread.sleep(Constants.COMPONENT_TEARDOWN_WAIT);
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void sendStartsecure() throws Exception {
|
||||
|
||||
// a challenge, aes secret and iv param encrypted with the server's RSA key
|
||||
String testChallenge = "wTZqUdwD6RIWtgTrvoYecJgulKRQVActTzbaW7u4i0puTak8ymlUHmvVQGT6wCUVoByDaF3dEhRFku5uC4kap" +
|
||||
"9yd2FntrtIcuftaf36WSU/Qg2ue254TiEVmCLILd2eef8SxHh6U0hyWwXPdD+BHBplzrBeIIiTPqLteKKHl6veEzuEh+s/u66hcy" +
|
||||
"PG+3t18C4ZR1jo50VZhAa9Kfqeuj787llQTZMMv+2gEIRciKPu8pF5/57+hmOmcp+mAoBaK0XdjTZ1Win4bF1CP44sdHLgKy2Bfv" +
|
||||
"Gn69RN7ThWBEu8fXuBsxcflhLDus1OIlDv8YgoLVGiOCamtZf0TtqcErg==";
|
||||
|
||||
try (JunitSocketClient client = new JunitSocketClient(dmapServerPort, err)) {
|
||||
// protocol check
|
||||
client.verify("ok DMAP2.0");
|
||||
|
||||
// check that mailbox returns its component id
|
||||
client.sendAndVerify("startsecure", "ok mailbox-earth-planet");
|
||||
|
||||
// send the challenge + aes init
|
||||
client.send(testChallenge);
|
||||
|
||||
// response should be "ok <challenge>" (which is AES encrypted and base64 encoded)
|
||||
// specifically it should be g9UJxNFULO+H0otZoH5AVXoHv9TxJUEcbY/ScWoWMvcJYLz2lYBaZ16OtqEKtVk=
|
||||
err.checkThat("Expected server response to be Base64 encoded", client.listen(),
|
||||
matchesPattern("^(?:[a-zA-Z0-9+/]{4})*(?:[a-zA-Z0-9+/]{2}==|[a-zA-Z0-9+/]{3}=)?$"));
|
||||
|
||||
// send encrypted "ok" (with the aes cipher) in
|
||||
client.send("g9U=");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package dslab.monitoring;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.Constants;
|
||||
import dslab.TestBase;
|
||||
import dslab.util.Config;
|
||||
|
||||
/**
|
||||
* Tests whether the UDP-based monitoring protocol is implemented correctly on the server side.
|
||||
*/
|
||||
public class MonitoringServerProtocolTest extends TestBase {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(MonitoringServerProtocolTest.class);
|
||||
|
||||
private String componentId = "monitoring";
|
||||
|
||||
private IMonitoringServer component;
|
||||
private InetSocketAddress addr;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
component = ComponentFactory.createMonitoringServer(componentId, in, out);
|
||||
addr = new InetSocketAddress("127.0.0.1", new Config(componentId).getInt("udp.port"));
|
||||
|
||||
new Thread(component).start();
|
||||
Thread.sleep(Constants.COMPONENT_STARTUP_WAIT);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
in.addLine("shutdown");
|
||||
Thread.sleep(Constants.COMPONENT_TEARDOWN_WAIT);
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void addresses_returnsCorrectStatistics() throws Exception {
|
||||
LOG.info("Sending three monitoring packets to monitoring socket");
|
||||
try (DatagramSocket socket = new DatagramSocket()) {
|
||||
String str1 = "127.0.0.1:42 foo@example.com";
|
||||
String str2 = "127.0.0.1:43 foo@example.com";
|
||||
String str3 = "127.0.0.1:42 bar@example.com";
|
||||
|
||||
socket.send(new DatagramPacket(str1.getBytes(), str1.length(), addr));
|
||||
socket.send(new DatagramPacket(str2.getBytes(), str2.length(), addr));
|
||||
socket.send(new DatagramPacket(str3.getBytes(), str3.length(), addr));
|
||||
}
|
||||
|
||||
Thread.sleep(2500);
|
||||
in.addLine("addresses"); // send "addresses" command to command line
|
||||
Thread.sleep(2500);
|
||||
String output = String.join(",", out.getLines());
|
||||
assertThat(output, containsString("foo@example.com 2"));
|
||||
assertThat(output, containsString("bar@example.com 1"));
|
||||
}
|
||||
|
||||
/*
|
||||
* Assumes that run and shutdown works correctly.
|
||||
*/
|
||||
@Test(timeout = 15000)
|
||||
public void servers_returnsCorrectStatistics() throws Exception {
|
||||
LOG.info("Sending three monitoring packets to monitoring socket");
|
||||
try (DatagramSocket socket = new DatagramSocket()) {
|
||||
String str1 = "127.0.0.1:42 foo@example.com";
|
||||
String str2 = "127.0.0.1:43 foo@example.com";
|
||||
String str3 = "127.0.0.1:42 bar@example.com";
|
||||
|
||||
socket.send(new DatagramPacket(str1.getBytes(), str1.length(), addr));
|
||||
socket.send(new DatagramPacket(str2.getBytes(), str2.length(), addr));
|
||||
socket.send(new DatagramPacket(str3.getBytes(), str3.length(), addr));
|
||||
}
|
||||
|
||||
Thread.sleep(2500);
|
||||
in.addLine("servers"); // send "addresses" command to command line
|
||||
Thread.sleep(2500);
|
||||
String output = String.join(",", out.getLines());
|
||||
assertThat(output, containsString("127.0.0.1:42 2"));
|
||||
assertThat(output, containsString("127.0.0.1:43 1"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package dslab.monitoring;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Test;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.Constants;
|
||||
import dslab.Sockets;
|
||||
import dslab.TestBase;
|
||||
import dslab.util.Config;
|
||||
|
||||
/**
|
||||
* Tests the creation, running, and shutting down of the monitoring server.
|
||||
*/
|
||||
public class MonitoringServerTest extends TestBase {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(MonitoringServerTest.class);
|
||||
|
||||
@Test
|
||||
public void runAndShutdownMonitoringServer_createsAndStopsUdpSocketCorrectly() throws Exception {
|
||||
IMonitoringServer component = ComponentFactory.createMonitoringServer("monitoring", in, out);
|
||||
int port = new Config("monitoring").getInt("udp.port");
|
||||
|
||||
assertThat(component, is(notNullValue()));
|
||||
|
||||
Thread componentThread = new Thread(component);
|
||||
LOG.info("Starting thread with component " + component);
|
||||
componentThread.start();
|
||||
|
||||
Thread.sleep(Constants.COMPONENT_STARTUP_WAIT); // wait a bit for resources to be initialized
|
||||
|
||||
try {
|
||||
LOG.info("Trying to create socket on port " + port);
|
||||
err.checkThat("Expected an open UDP socket on port " + port, Sockets.isDatagramSocketOpen(port), is(true));
|
||||
} catch (Exception e) {
|
||||
// a different unexpected error occurred (unlikely)
|
||||
err.addError(e);
|
||||
}
|
||||
|
||||
LOG.info("Shutting down component " + component);
|
||||
in.addLine("shutdown"); // send "shutdown" command to command line
|
||||
Thread.sleep(Constants.COMPONENT_TEARDOWN_WAIT);
|
||||
|
||||
try {
|
||||
LOG.info("Waiting for thread to stop for component " + component);
|
||||
componentThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
err.addError(new AssertionError("Monitoring server was not terminated correctly"));
|
||||
}
|
||||
|
||||
err.checkThat("Expected datagram socket on port " + port + " to be closed after shutdown",
|
||||
Sockets.isDatagramSocketOpen(port), is(false));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package dslab.naming;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.allOf;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
|
||||
import java.rmi.NotBoundException;
|
||||
import java.rmi.Remote;
|
||||
import java.rmi.RemoteException;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
import java.rmi.registry.Registry;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ErrorCollector;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.Constants;
|
||||
import dslab.TestInputStream;
|
||||
import dslab.TestOutputStream;
|
||||
import dslab.nameserver.INameserverRemote;
|
||||
import dslab.util.Config;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class NameserverTest {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(NameserverTest.class);
|
||||
|
||||
@Rule
|
||||
public ErrorCollector err = new ErrorCollector();
|
||||
|
||||
private TestInputStream nsRootIn;
|
||||
private TestOutputStream nsRootOut;
|
||||
|
||||
private Config nsPlanetConfig;
|
||||
private TestInputStream nsPlanetIn;
|
||||
private TestOutputStream nsPlanetOut;
|
||||
|
||||
private static void sendShutdown(TestInputStream in) {
|
||||
try {
|
||||
LOG.info("Sending shutdown to component component");
|
||||
in.addLine("shutdown");
|
||||
Thread.sleep(Constants.COMPONENT_TEARDOWN_WAIT);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Interrupted while waiting on component teardown");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
this.nsRootIn = new TestInputStream();
|
||||
this.nsPlanetIn = new TestInputStream();
|
||||
this.nsRootOut = new TestOutputStream();
|
||||
this.nsPlanetOut = new TestOutputStream();
|
||||
|
||||
this.nsPlanetConfig = new Config("ns-planet");
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void ns01_startAndShutdownRootNameserver_createsAndUnexportsRegistryCorrectly() throws Exception {
|
||||
Runnable nsRoot = ComponentFactory.createNameserver("ns-root", nsRootIn, nsRootOut);
|
||||
|
||||
LOG.info("Starting ns-root thread");
|
||||
Thread nsRootThread = new Thread(nsRoot);
|
||||
nsRootThread.start();
|
||||
|
||||
Thread.sleep(Constants.COMPONENT_STARTUP_WAIT);
|
||||
try {
|
||||
Registry registry = LocateRegistry.getRegistry(nsPlanetConfig.getString("registry.host"), nsPlanetConfig.getInt("registry.port"));
|
||||
registry.list();
|
||||
} catch (RemoteException e) {
|
||||
err.addError(new AssertionError("Expected root nameserver to start a registry, but it couldn't be located or registry.list() failed", e));
|
||||
} finally {
|
||||
sendShutdown(nsRootIn);
|
||||
|
||||
try {
|
||||
Registry registry = LocateRegistry.getRegistry(nsPlanetConfig.getString("registry.host"), nsPlanetConfig.getInt("registry.port"));
|
||||
registry.list();
|
||||
err.addError(new AssertionError("Expected root nameserver to shut down the registry, but could call list"));
|
||||
} catch (RemoteException e) {
|
||||
// ok
|
||||
} catch (Exception e) {
|
||||
err.addError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void ns02_startRootNameserver_registersRemoteObjectCorrectly() throws Exception {
|
||||
Runnable nsRoot = ComponentFactory.createNameserver("ns-root", nsRootIn, nsRootOut);
|
||||
|
||||
LOG.info("Starting ns-root thread");
|
||||
Thread nsRootThread = new Thread(nsRoot);
|
||||
nsRootThread.start();
|
||||
|
||||
String id = nsPlanetConfig.getString("root_id");
|
||||
try {
|
||||
Thread.sleep(Constants.COMPONENT_STARTUP_WAIT);
|
||||
Registry registry = LocateRegistry.getRegistry(nsPlanetConfig.getString("registry.host"), nsPlanetConfig.getInt("registry.port"));
|
||||
|
||||
LOG.info("Looking up " + id + " in registry");
|
||||
Remote remote = registry.lookup(id);
|
||||
err.checkThat("Remote object bound to " + id + " should be a INameserverRemote", remote, instanceOf(INameserverRemote.class));
|
||||
|
||||
String[] list = registry.list();
|
||||
err.checkThat("Registry should only contain a single remote object (the root nameserver)", list.length, is(1));
|
||||
} catch (RemoteException | NotBoundException e) {
|
||||
err.addError(new AssertionError("Error while looking up remote object " + ""));
|
||||
} finally {
|
||||
sendShutdown(nsRootIn);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 30000)
|
||||
public void ns03_startingZoneNameserver_registersNameserverCorrectly() throws Exception {
|
||||
Runnable nsRoot = ComponentFactory.createNameserver("ns-root", nsRootIn, nsRootOut);
|
||||
|
||||
LOG.info("Starting ns-root thread");
|
||||
Thread nsRootThread = new Thread(nsRoot);
|
||||
nsRootThread.start();
|
||||
Thread.sleep(Constants.COMPONENT_STARTUP_WAIT);
|
||||
|
||||
LOG.info("Starting ns-planet thread");
|
||||
Runnable nsPlanet = ComponentFactory.createNameserver("ns-planet", nsPlanetIn, nsPlanetOut);
|
||||
Thread nsPlanetThread = new Thread(nsPlanet);
|
||||
nsPlanetThread.start();
|
||||
Thread.sleep(Constants.COMPONENT_STARTUP_WAIT);
|
||||
|
||||
try {
|
||||
// lookup registry and root remote
|
||||
Registry registry = LocateRegistry.getRegistry(nsPlanetConfig.getString("registry.host"), nsPlanetConfig.getInt("registry.port"));
|
||||
|
||||
err.checkThat("Adding a new nameserver should not bind an additional remote object to the registry", registry.list().length, is(1));
|
||||
|
||||
String id = nsPlanetConfig.getString("root_id");
|
||||
LOG.info("Looking up " + id + " in registry");
|
||||
INameserverRemote root = (INameserverRemote) registry.lookup(id); // from ns02 we assume that this has the correct type
|
||||
|
||||
// check the nameserver remote
|
||||
LOG.info("Getting nameserver 'planet' from server remote");
|
||||
Remote zone = root.getNameserver("planet");
|
||||
|
||||
err.checkThat(zone, not(nullValue()));
|
||||
|
||||
// read nameservers from ns-root's command line
|
||||
LOG.info("Sending 'nameservers' command");
|
||||
nsRootIn.addLine("nameservers");
|
||||
String output = nsRootOut.listen();
|
||||
LOG.info("Got output: " + output);
|
||||
err.checkThat("output of 'nameservers' command should contain the domain", output, containsString("planet"));
|
||||
} finally {
|
||||
// shutdown
|
||||
sendShutdown(nsPlanetIn);
|
||||
sendShutdown(nsRootIn);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 30000)
|
||||
public void ns04_registerAndLookupMailboxServer_registersAndReturnsAddressCorrectly() throws Exception {
|
||||
Runnable nsRoot = ComponentFactory.createNameserver("ns-root", nsRootIn, nsRootOut);
|
||||
|
||||
LOG.info("Starting ns-root thread");
|
||||
Thread nsRootThread = new Thread(nsRoot);
|
||||
nsRootThread.start();
|
||||
Thread.sleep(Constants.COMPONENT_STARTUP_WAIT);
|
||||
|
||||
LOG.info("Starting ns-planet thread");
|
||||
Runnable nsPlanet = ComponentFactory.createNameserver("ns-planet", nsPlanetIn, nsPlanetOut);
|
||||
Thread nsPlanetThread = new Thread(nsPlanet);
|
||||
nsPlanetThread.start();
|
||||
Thread.sleep(Constants.COMPONENT_STARTUP_WAIT);
|
||||
|
||||
try {
|
||||
// lookup registry and root remote
|
||||
Registry registry = LocateRegistry.getRegistry(nsPlanetConfig.getString("registry.host"), nsPlanetConfig.getInt("registry.port"));
|
||||
|
||||
String id = nsPlanetConfig.getString("root_id");
|
||||
LOG.info("Looking up " + id + " in registry");
|
||||
INameserverRemote root = (INameserverRemote) registry.lookup(id); // from ns02 we assume that this has the correct type
|
||||
|
||||
// registration
|
||||
LOG.info("Registering mailbox server for @mars.planet mail domain");
|
||||
root.registerMailboxServer("mars.planet", "192.168.0.1:14242");
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
INameserverRemote zone = root.getNameserver("planet");
|
||||
LOG.info("Looking up 'mars' at nameserver planet");
|
||||
String address = zone.lookup("mars");
|
||||
|
||||
err.checkThat("registered address should contain both address and port", address, allOf(containsString("192.168.0.1"), containsString("14242")));
|
||||
|
||||
} finally {
|
||||
// shutdown
|
||||
sendShutdown(nsPlanetIn);
|
||||
sendShutdown(nsRootIn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package dslab.transfer;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.Constants;
|
||||
import dslab.JunitSocketClient;
|
||||
import dslab.Sockets;
|
||||
import dslab.TestBase;
|
||||
import dslab.util.Config;
|
||||
|
||||
/**
|
||||
* TransferServerProtocolTest.
|
||||
*/
|
||||
public class TransferServerProtocolTest extends TestBase {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(TransferServerProtocolTest.class);
|
||||
|
||||
private String componentId = "transfer-1";
|
||||
|
||||
private ITransferServer component;
|
||||
private int serverPort;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
component = ComponentFactory.createTransferServer(componentId, in, out);
|
||||
serverPort = new Config(componentId).getInt("tcp.port");
|
||||
new Thread(component).start();
|
||||
|
||||
LOG.info("Waiting for server socket to appear");
|
||||
Sockets.waitForSocket("localhost", serverPort, Constants.COMPONENT_STARTUP_WAIT);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
in.addLine("shutdown"); // send "shutdown" command to command line
|
||||
Thread.sleep(Constants.COMPONENT_TEARDOWN_WAIT);
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void defaultDmtpInteractionTest() throws Exception {
|
||||
try (JunitSocketClient client = new JunitSocketClient(serverPort, err)) {
|
||||
client.verify("ok DMTP");
|
||||
client.sendAndVerify("begin", "ok");
|
||||
client.sendAndVerify("from trillian@earth.planet", "ok");
|
||||
client.sendAndVerify("to arthur@earth.planet", "ok 1");
|
||||
client.sendAndVerify("subject hello", "ok");
|
||||
client.sendAndVerify("data hello from junit", "ok");
|
||||
client.sendAndVerify("send", "ok");
|
||||
client.sendAndVerify("quit", "ok bye");
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void sendWithoutRecipient_returnsErrorOnSend() throws Exception {
|
||||
try (JunitSocketClient client = new JunitSocketClient(serverPort, err)) {
|
||||
client.verify("ok DMTP");
|
||||
client.sendAndVerify("begin", "ok");
|
||||
client.sendAndVerify("from trillian@earth.planet", "ok");
|
||||
client.sendAndVerify("subject hello", "ok");
|
||||
client.sendAndVerify("data hello from junit", "ok");
|
||||
client.sendAndVerify("send", "error");
|
||||
client.sendAndVerify("quit", "ok bye");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package dslab.transfer;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Test;
|
||||
|
||||
import dslab.ComponentFactory;
|
||||
import dslab.Constants;
|
||||
import dslab.Sockets;
|
||||
import dslab.TestBase;
|
||||
import dslab.monitoring.MonitoringServerTest;
|
||||
import dslab.util.Config;
|
||||
|
||||
/**
|
||||
* TransferServerTest.
|
||||
*/
|
||||
public class TransferServerTest extends TestBase {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(MonitoringServerTest.class);
|
||||
|
||||
@Test
|
||||
public void runAndShutdownTransferServer_createsAndStopsTcpSocketCorrectly() throws Exception {
|
||||
ITransferServer component = ComponentFactory.createTransferServer("transfer-1", in, out);
|
||||
int port = new Config("transfer-1").getInt("tcp.port");
|
||||
|
||||
assertThat(component, is(notNullValue()));
|
||||
|
||||
Thread componentThread = new Thread(component);
|
||||
LOG.info("Starting thread with component " + component);
|
||||
componentThread.start();
|
||||
|
||||
try {
|
||||
LOG.info("Waiting for socket to open on port " + port);
|
||||
Sockets.waitForSocket("localhost", port, Constants.COMPONENT_STARTUP_WAIT);
|
||||
} catch (SocketTimeoutException e) {
|
||||
err.addError(new AssertionError("Expected a TCP server socket on port " + port, e));
|
||||
}
|
||||
|
||||
LOG.info("Shutting down component " + component);
|
||||
in.addLine("shutdown"); // send "shutdown" command to command line
|
||||
Thread.sleep(Constants.COMPONENT_TEARDOWN_WAIT);
|
||||
|
||||
try {
|
||||
LOG.info("Waiting for thread to stop for component " + component);
|
||||
componentThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
err.addError(new AssertionError("Monitoring server was not terminated correctly"));
|
||||
}
|
||||
|
||||
err.checkThat("Expected tcp socket on port " + port + " to be closed after shutdown",
|
||||
Sockets.isServerSocketOpen(port), is(false));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user