diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5ff6309b7199129c1afe4f4ec1906e640bec48c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..13566b81b018ad684f3a35fee301741b2734c8f4 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000000000000000000000000000000000000..aa00ffab7828f4818589659c804ec2cfd99baed3 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="Encoding"> + <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" /> + <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" /> + </component> +</project> \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000000000000000000000000000000000..4258c62fd9edaffc241e79c34ec36a478505f4f1 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ExternalStorageConfigurationManager" enabled="true" /> + <component name="MavenProjectsManager"> + <option name="originalFiles"> + <list> + <option value="$PROJECT_DIR$/pom.xml" /> + </list> + </option> + </component> + <component name="ProjectRootManager" version="2" languageLevel="JDK_18" default="true" project-jdk-name="18" project-jdk-type="JavaSDK"> + <output url="file://$PROJECT_DIR$/out" /> + </component> +</project> \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..35eb1ddfbbc029bcab630581847471d7f238ec53 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="" vcs="Git" /> + </component> +</project> \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..0a62eb6da9ad9324c78dfac3a9ffaf50da07c899 --- /dev/null +++ b/pom.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.internetworking</groupId> + <artifactId>InternetworkingWS23</artifactId> + <version>1.0-SNAPSHOT</version> + + <properties> + <maven.compiler.source>18</maven.compiler.source> + <maven.compiler.target>18</maven.compiler.target> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-junit-jupiter</artifactId> + <version>4.6.1</version> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-inline</artifactId> + <version>4.6.1</version> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>5.9.0</version> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>5.9.0</version> + </dependency> + </dependencies> + +</project> \ No newline at end of file diff --git a/src/main/java/apps/CPClient.java b/src/main/java/apps/CPClient.java new file mode 100644 index 0000000000000000000000000000000000000000..6a1d55c5482bda8a72e082123648b9e86db9160c --- /dev/null +++ b/src/main/java/apps/CPClient.java @@ -0,0 +1,62 @@ +package apps; + +import cp.*; +import exceptions.*; +import phy.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; + +public class CPClient { + private static final String SERVER_NAME = "localhost"; + + + public static void main(String[] args) { + // Each client needs to start on a unique UDP port provided by the user + if (args.length != 1) { + System.out.println("Provide an address identifier (int) from range [5000:65534]"); + return; + } + var id = Integer.parseInt(args[0]); + if (id < 5000 || id > 65534) { + System.out.println("Invalid address identifier! Range [5000:65534]"); + return; + } + + // Set up the virtual link protocol + PhyProtocol phy = new PhyProtocol(id); + + // Set up command protocol + CPProtocol cp = null; + try { + cp = new CPProtocol(InetAddress.getByName(SERVER_NAME), CPServer.SERVER_PORT, phy); + } catch (Exception e) { + e.printStackTrace(); + } + + // Read data from user to send to server + BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in)); + boolean eof = false; + while (!eof) { + try { + String sentence = null; + System.out.println("Command: "); + sentence = inFromUser.readLine(); + // Currently only these two commands are supported by the specification + if(!(sentence.equals("status") || sentence.startsWith("print"))) + continue; + + cp.send(sentence.trim(), null); + System.out.println("Command sent to server ... wating for response"); + String answer = cp.receive().getData(); + System.out.println(answer); + } catch (IllegalCommandException e) { + System.out.println("Only these two commands are supported: status, print \"text\""); + } catch (IWProtocolException | IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/apps/CPServer.java b/src/main/java/apps/CPServer.java new file mode 100644 index 0000000000000000000000000000000000000000..071a8eddca62f9ce581a813fd05d20532db66703 --- /dev/null +++ b/src/main/java/apps/CPServer.java @@ -0,0 +1,37 @@ +package apps; + +import core.Msg; +import cp.CPProtocol; +import exceptions.IWProtocolException; +import phy.PhyProtocol; + +import java.io.IOException; + +public class CPServer { + protected static final int SERVER_PORT = 3027; + + public static void main(String[] args) { + // Set up the virtual link protocol + PhyProtocol phy = new PhyProtocol(SERVER_PORT); + + // Set up command protocol + CPProtocol cp = null; + try { + cp = new CPProtocol(phy); + } catch (Exception e) { + e.printStackTrace(); + } + + // Start server processing + boolean eof = false; + while (!eof) { + try { + Msg msg = cp.receive(); + String sentence = msg.getData().trim(); + System.out.println("Received message: " + sentence); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/core/Configuration.java b/src/main/java/core/Configuration.java new file mode 100644 index 0000000000000000000000000000000000000000..59ee4581d22bf200299ddd2e32063dbdbe6cbfb3 --- /dev/null +++ b/src/main/java/core/Configuration.java @@ -0,0 +1,21 @@ +package core; + +/* + * Abstract configuration class + * nextLowerProtocol attribute is currently unused (ignore) + */ +public class Configuration { + protected Protocol nextLowerProtocol; + + public Configuration(Protocol proto) { + this.nextLowerProtocol = proto; + } + + public Protocol getNextLowerProtocol() { + return nextLowerProtocol; + } + + private void setNextLowerProtocol(Protocol nextLowerProtocol) { + this.nextLowerProtocol = nextLowerProtocol; + } +} diff --git a/src/main/java/core/Msg.java b/src/main/java/core/Msg.java new file mode 100644 index 0000000000000000000000000000000000000000..bf3be7213d97e3569b414bdf325274513100320d --- /dev/null +++ b/src/main/java/core/Msg.java @@ -0,0 +1,42 @@ +package core; + +import exceptions.IWProtocolException; + +/* + * Msg base class (abstract) + */ +public abstract class Msg { + protected String data; + protected byte[] dataBytes; + protected Configuration config; + + public byte[] getDataBytes() { + return this.dataBytes; + } + + public int getLength() { + return this.dataBytes.length; + } + + public String getData() { + return this.data; + } + + public void setData(String data) { + this.data = data; + } + + public Configuration getConfiguration() { + return this.config; + } + public void setConfiguration(Configuration c) {this.config = c;} + + protected abstract void create(String sentence); + protected abstract Msg parse(String sentence) throws IWProtocolException; + + + public void printDataBytes() { + String msgString = new String(this.dataBytes); + System.out.println(msgString); + } +} diff --git a/src/main/java/core/Protocol.java b/src/main/java/core/Protocol.java new file mode 100644 index 0000000000000000000000000000000000000000..54be4ee55718cd3d5250a191fa54b45d8f715c13 --- /dev/null +++ b/src/main/java/core/Protocol.java @@ -0,0 +1,18 @@ +package core; + +import java.io.IOException; + +import exceptions.IWProtocolException; + +/* + * Protocol base class (abstract) + */ +public abstract class Protocol { + public enum proto_id { + PHY, APP, SLP, CP + } + + public abstract void send(String s, Configuration config) throws IOException, IWProtocolException; + public abstract Msg receive() throws IOException, IWProtocolException; + +} diff --git a/src/main/java/cp/CPCookieRequestMsg.java b/src/main/java/cp/CPCookieRequestMsg.java new file mode 100644 index 0000000000000000000000000000000000000000..79a9e5236cd48b9e691f115755f4900ec98a1652 --- /dev/null +++ b/src/main/java/cp/CPCookieRequestMsg.java @@ -0,0 +1,28 @@ +package cp; + +import core.Msg; +import exceptions.IllegalMsgException; + +class CPCookieRequestMsg extends CPMsg { + protected static final String CP_CREQ_HEADER = "creq"; + + /* + * Create cookie request message. + * The cp header is prepended in the super-class. + */ + @Override + protected void create(String data) { + // prepend reg header + data = CP_CREQ_HEADER; + // super class prepends slp header + super.create(data); + } + + @Override + protected Msg parse(String sentence) throws IllegalMsgException { + if (!sentence.startsWith(CP_CREQ_HEADER)) { + throw new IllegalMsgException(); + } + return this; + } +} \ No newline at end of file diff --git a/src/main/java/cp/CPCookieResponseMsg.java b/src/main/java/cp/CPCookieResponseMsg.java new file mode 100644 index 0000000000000000000000000000000000000000..faeed5f46f10f3e4dc0e762f48dfabf16df8383c --- /dev/null +++ b/src/main/java/cp/CPCookieResponseMsg.java @@ -0,0 +1,50 @@ +package cp; + +import core.Msg; +import exceptions.IllegalMsgException; + +class CPCookieResponseMsg extends CPMsg { + protected static final String CP_CRES_HEADER = "cres"; + private boolean success; + + protected CPCookieResponseMsg() { + + } + protected CPCookieResponseMsg(boolean s) { + this.success = s; + } + + protected boolean getSuccess() {return this.success;} + + /* + * Create cookie request message. + * The cp header is prepended in the super-class. + */ + @Override + protected void create(String data) { + if (this.success) { + // prepend cres header + data = CP_CRES_HEADER + " ACK " + data; + } else { + data = CP_CRES_HEADER + " NAK " + data; + } + // super class prepends slp header + super.create(data); + } + + protected Msg parse(String sentence) throws IllegalMsgException { + if (!sentence.startsWith(CP_CRES_HEADER)) { + throw new IllegalMsgException(); + } + String[] parts = sentence.split("\\s+", 3); + + if(parts[1].equals("ACK")) + this.success = true; + else + this.success = false; + + this.data = parts[2]; + return this; + } + +} diff --git a/src/main/java/cp/CPMsg.java b/src/main/java/cp/CPMsg.java new file mode 100644 index 0000000000000000000000000000000000000000..39f22905fc3ba2ef4c07a8290e4e5a0008012da2 --- /dev/null +++ b/src/main/java/cp/CPMsg.java @@ -0,0 +1,37 @@ +package cp; + +import core.Msg; +import exceptions.IWProtocolException; +import exceptions.IllegalMsgException; + +import java.util.zip.CRC32; +import java.util.zip.Checksum; + +class CPMsg extends Msg { + protected static final String CP_HEADER = "cp"; + @Override + protected void create(String sentence) { + this.data = sentence; + data = CP_HEADER + " " + sentence; + this.dataBytes = data.getBytes(); + } + + @Override + protected Msg parse(String sentence) throws IWProtocolException { + CPMsg parsedMsg; + if(!sentence.startsWith(CP_HEADER)) + throw new IllegalMsgException(); + + String[] parts = sentence.split("\\s+", 2); + if(parts[1].startsWith(CPCookieRequestMsg.CP_CREQ_HEADER)) { + parsedMsg = new CPCookieRequestMsg(); + } else if(parts[1].startsWith(CPCookieResponseMsg.CP_CRES_HEADER)) { + parsedMsg = new CPCookieResponseMsg(); + } else + throw new IllegalMsgException(); + + parsedMsg = (CPMsg) parsedMsg.parse(parts[1]); + return parsedMsg; + } + +} diff --git a/src/main/java/cp/CPProtocol.java b/src/main/java/cp/CPProtocol.java new file mode 100644 index 0000000000000000000000000000000000000000..66e0f5e9622c0aa169e36a899b2adcc53d71ae02 --- /dev/null +++ b/src/main/java/cp/CPProtocol.java @@ -0,0 +1,76 @@ +package cp; + +import core.*; +import exceptions.*; +import phy.*; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.Random; + +public class CPProtocol extends Protocol { + private static final int CP_TIMEOUT = 2000; + private static final int CP_HASHMAP_SIZE = 20; + private String cookie; + private int id; + private PhyConfiguration PhyConfig; + private PhyProtocol PhyProto; + boolean isClient; + HashMap<PhyConfiguration, Integer> cookieMap; + Random rnd; + + // Constructor for clients + public CPProtocol(InetAddress rname, int rp, PhyProtocol phyP) throws UnknownHostException { + this.PhyConfig = new PhyConfiguration(rname, rp, proto_id.CP); + this.PhyProto = phyP; + this.isClient = true; + } + // Constructor for servers + public CPProtocol(PhyProtocol phyP) { + this.PhyProto = phyP; + this.isClient = false; + this.cookieMap = new HashMap<>(); + this.rnd = new Random(); + } + @Override + public void send(String s, Configuration config) throws IOException, IWProtocolException { + if (cookie == null) { + // Request a new cookie from server + // Either updates the cookie attribute or returns with an exception + requestCookie(0); + } + } + + @Override + public Msg receive() throws IOException { + return null; + } + + + public void requestCookie(int count) throws IOException, IWProtocolException { + if(count >= 3) + throw new CookieRequestException(); + CPCookieRequestMsg reqMsg = new CPCookieRequestMsg(); + reqMsg.create(null); + + this.PhyProto.send(new String (reqMsg.getDataBytes()), this.PhyConfig); + Msg resMsg = new CPMsg(); + + try { + Msg in = this.PhyProto.receive(CP_TIMEOUT); + if (((PhyConfiguration)in.getConfiguration()).getPid() != proto_id.CP) + throw new IllegalMsgException(); + resMsg = ((CPMsg) resMsg).parse(in.getData()); + } catch (SocketTimeoutException e) { + requestCookie(count+1); + } + + if(resMsg instanceof CPCookieResponseMsg && !((CPCookieResponseMsg) resMsg).getSuccess()) { + throw new CookieRequestException(); + } + this.cookie = resMsg.getData(); + } +} diff --git a/src/main/java/exceptions/BadChecksumException.java b/src/main/java/exceptions/BadChecksumException.java new file mode 100644 index 0000000000000000000000000000000000000000..397c97b87fd7939aff2f329889f42c6a86df3811 --- /dev/null +++ b/src/main/java/exceptions/BadChecksumException.java @@ -0,0 +1,10 @@ +package exceptions; + +public class BadChecksumException extends IWProtocolException { + + /** + * + */ + private static final long serialVersionUID = -6481772554012497583L; + +} diff --git a/src/main/java/exceptions/CookieRequestException.java b/src/main/java/exceptions/CookieRequestException.java new file mode 100644 index 0000000000000000000000000000000000000000..a2c57679f515b8b1d4dcdbaf5e21c9ba6d7b9183 --- /dev/null +++ b/src/main/java/exceptions/CookieRequestException.java @@ -0,0 +1,4 @@ +package exceptions; + +public class CookieRequestException extends IWProtocolException { +} diff --git a/src/main/java/exceptions/IWProtocolException.java b/src/main/java/exceptions/IWProtocolException.java new file mode 100644 index 0000000000000000000000000000000000000000..a1e895c799f7cc2ff5c20b45c154f0d3d14cb29e --- /dev/null +++ b/src/main/java/exceptions/IWProtocolException.java @@ -0,0 +1,10 @@ +package exceptions; + +public abstract class IWProtocolException extends Exception { + + /** + * + */ + private static final long serialVersionUID = -7002466204521265834L; + +} diff --git a/src/main/java/exceptions/IllegalAddrException.java b/src/main/java/exceptions/IllegalAddrException.java new file mode 100644 index 0000000000000000000000000000000000000000..fa1a9b7d1a93be40322a3a00e410135bf1f0ac2d --- /dev/null +++ b/src/main/java/exceptions/IllegalAddrException.java @@ -0,0 +1,12 @@ +package exceptions; + +import exceptions.IWProtocolException; + +public class IllegalAddrException extends IWProtocolException { + + /** + * + */ + private static final long serialVersionUID = -2112808760774454967L; + +} diff --git a/src/main/java/exceptions/IllegalCommandException.java b/src/main/java/exceptions/IllegalCommandException.java new file mode 100644 index 0000000000000000000000000000000000000000..d1882d59fed81504104efe583c9dc130a353d4aa --- /dev/null +++ b/src/main/java/exceptions/IllegalCommandException.java @@ -0,0 +1,4 @@ +package exceptions; + +public class IllegalCommandException extends IWProtocolException { +} diff --git a/src/main/java/exceptions/IllegalMsgException.java b/src/main/java/exceptions/IllegalMsgException.java new file mode 100644 index 0000000000000000000000000000000000000000..9c5a1f5c913e874a5828a34416621f6fae0e3f85 --- /dev/null +++ b/src/main/java/exceptions/IllegalMsgException.java @@ -0,0 +1,10 @@ +package exceptions; + +public class IllegalMsgException extends IWProtocolException { + + /** + * + */ + private static final long serialVersionUID = 2003522098885060854L; + +} diff --git a/src/main/java/exceptions/NoNextStateException.java b/src/main/java/exceptions/NoNextStateException.java new file mode 100644 index 0000000000000000000000000000000000000000..ca10c0ccf42f4a11673cd3c4d3568146653fde98 --- /dev/null +++ b/src/main/java/exceptions/NoNextStateException.java @@ -0,0 +1,10 @@ +package exceptions; + +public class NoNextStateException extends IWProtocolException { + + /** + * + */ + private static final long serialVersionUID = 9032871496863489383L; + +} diff --git a/src/main/java/exceptions/RegistrationFailedException.java b/src/main/java/exceptions/RegistrationFailedException.java new file mode 100644 index 0000000000000000000000000000000000000000..960f98500a138f9396acf4d6fba002b34671a9e5 --- /dev/null +++ b/src/main/java/exceptions/RegistrationFailedException.java @@ -0,0 +1,10 @@ +package exceptions; + +public class RegistrationFailedException extends IWProtocolException { + + /** + * + */ + private static final long serialVersionUID = -3028142631363949563L; + +} diff --git a/src/main/java/phy/PhyConfiguration.java b/src/main/java/phy/PhyConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..e323121e59f39816f9893f823e7c876ecbb8fe0b --- /dev/null +++ b/src/main/java/phy/PhyConfiguration.java @@ -0,0 +1,31 @@ +package phy; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import core.Configuration; +import core.Protocol; + +public class PhyConfiguration extends Configuration{ + protected int remotePort; + protected InetAddress remoteIPAddress; + protected Protocol.proto_id pid; + protected boolean isClient; + + public PhyConfiguration(InetAddress rip, int rp, Protocol.proto_id pid) throws UnknownHostException { + super(null); + this.remotePort = rp; + this.remoteIPAddress = rip; + this.pid = pid; + this.isClient = true; + } + + public int getRemotePort() { + return this.remotePort; + } + + public InetAddress getRemoteIPAddress () { + return this.remoteIPAddress; + } + public Protocol.proto_id getPid() {return this.pid;} +} diff --git a/src/main/java/phy/PhyMsg.java b/src/main/java/phy/PhyMsg.java new file mode 100644 index 0000000000000000000000000000000000000000..22dbeb60a6502d7615ce077a305d064afa3a5680 --- /dev/null +++ b/src/main/java/phy/PhyMsg.java @@ -0,0 +1,82 @@ +package phy; + +import core.Msg; +import core.Protocol; +import exceptions.IllegalMsgException; + +/* + * The only message class for the phy layer + * This layer prepends a "phy" header when sending a message + * This layer removes the "phy" header when receiving a message + */ +public class PhyMsg extends Msg { + protected static final String PHY_HEADER = "phy"; + protected Protocol.proto_id pid; + + protected PhyMsg() {} + protected PhyMsg(PhyConfiguration config) { + super(); + this.config = config; + } + + protected Protocol.proto_id getPid() {return this.pid;} + + /* + * Prepend header for sending + */ + @Override + protected void create(String data) { + this.data = data; + int id; + PhyConfiguration conf = (PhyConfiguration) this.config; + id = switch (conf.getPid()) { + case PHY -> 1; + case APP -> 3; + case SLP -> 5; + case CP -> 7; + }; + data = PHY_HEADER + " " + id + " " +data; + this.dataBytes = data.getBytes(); + } + + /* + * Does the message start with the correct header + * -> if not illegal message + * Remove header and populate data attribute + */ + @Override + protected Msg parse(String sentence) throws IllegalMsgException { + this.dataBytes = sentence.getBytes(); + if (!sentence.startsWith(PHY_HEADER)) { + System.out.println("Illeagal data header: " + sentence); + throw new IllegalMsgException(); + } + String[] parts = sentence.split("\\s+", 3); + PhyMsg pdu; + // Parse the protocol id + int id; + try { + id = Integer.parseInt(parts[1]); + } catch (NumberFormatException e) { + throw new IllegalMsgException(); + } + //Check protocol id + switch (id) { + case 1 -> pid = Protocol.proto_id.PHY; + case 3 -> pid = Protocol.proto_id.APP; + case 5 -> pid = Protocol.proto_id.SLP; + case 7 -> pid = Protocol.proto_id.CP; + default -> throw new IllegalMsgException(); + } + // If the second token is "1", call the PhyPingMsg parser + if(pid == Protocol.proto_id.PHY && parts[2].startsWith(PhyPingMsg.PHY_PING_HEADER)) { + pdu = new PhyPingMsg((PhyConfiguration) this.config); + pdu.parse(parts[2]); + } else { + this.data = parts[2]; + pdu = this; + } + return pdu; + } + +} diff --git a/src/main/java/phy/PhyPingMsg.java b/src/main/java/phy/PhyPingMsg.java new file mode 100644 index 0000000000000000000000000000000000000000..51790fddd4287983686d792b1ad0a483271b9b11 --- /dev/null +++ b/src/main/java/phy/PhyPingMsg.java @@ -0,0 +1,51 @@ +package phy; + +import core.Msg; +import exceptions.IllegalMsgException; + +public class PhyPingMsg extends PhyMsg { + protected static final String PHY_PING_HEADER = "ping "; + private int count; + + protected PhyPingMsg(PhyConfiguration config) { + super(config); + } + + public int getCount() { + return count; + } + + /* + * Create ping msg + */ + @Override + protected void create(String data) { + this.data = data; + String sentence = PHY_PING_HEADER + data; + super.create(sentence); + } + + /* + * Does the message start with the correct header + * -> if not illegal message + * Remove header and populate data attribute + * -> try converting data to type Integer + */ + @Override + protected Msg parse(String sentence) throws IllegalMsgException { + this.dataBytes = sentence.getBytes(); + if (!sentence.startsWith(PHY_PING_HEADER)) { + System.out.println("Illeagal ping header: " + sentence); + throw new IllegalMsgException(); + } + this.data = sentence.substring(PHY_PING_HEADER.length()); + try { + this.count = Integer.parseInt(this.data.trim()); + } catch (NumberFormatException e) { + throw new IllegalMsgException(); + } + + return this; + } + +} diff --git a/src/main/java/phy/PhyProtocol.java b/src/main/java/phy/PhyProtocol.java new file mode 100644 index 0000000000000000000000000000000000000000..3a2eb0344a13d63c62257d18f15846fadb1ed70c --- /dev/null +++ b/src/main/java/phy/PhyProtocol.java @@ -0,0 +1,104 @@ +package phy; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.util.concurrent.TimeoutException; + +import core.*; +import exceptions.*; + +public class PhyProtocol extends Protocol { + protected DatagramSocket socket; + + /* + * Create a new PhyProtocol instance connected to UDP port provided + */ + public PhyProtocol(int port) { + try { + this.socket = new DatagramSocket(port); + } catch (SocketException e) { + e.printStackTrace(); + } + } + + /* + * Create msg object and send + */ + @Override + public void send(String s, Configuration config) throws IOException, IWProtocolException { + // Create empty PhyMsg object + PhyMsg m = new PhyMsg((PhyConfiguration) config); + //Populate PhyMsg object with data + m.create(s); + // Call actual send method + this.send(m); + } + + public void send(PhyMsg m) throws IOException, IWProtocolException { + // Create UDP packet + DatagramPacket sendPacket = new DatagramPacket(m.getDataBytes(), m.getLength(), + ((PhyConfiguration) m.getConfiguration()).remoteIPAddress, + ((PhyConfiguration) m.getConfiguration()).remotePort); + // send UDP packet + socket.send(sendPacket); + } + + /* + * receive incoming message from socket and parse -> call blocks on socket until message is received + * return Msg object to caller + */ + @Override + public Msg receive() throws IOException { + // read from UDP socket + // data and meta-data contained in receivedPacket object + byte[] receiveData = new byte[1024]; + DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); + socket.receive(receivePacket); + + // create msg object for parsing + PhyMsg in = new PhyMsg(); + // get data from packet data + String sentence = new String(receivePacket.getData()).trim(); + try { + // parse data to check if message is compliant with protocol specification + in = (PhyMsg) in.parse(sentence); + } catch (IllegalMsgException e) { + e.printStackTrace(); + } + // create a config object from packet meta-data + PhyConfiguration config = new PhyConfiguration(receivePacket.getAddress(), receivePacket.getPort(), in.getPid()); + in.setConfiguration(config); + + // if message was parsed correctly object is returned to caller + return in; + } + + /* + * wrapper method to basic receive method -> call blocks on socket until message is received or + * timeout expires and exception is raised + */ + public Msg receive(int timeout) throws IOException { + socket.setSoTimeout(timeout); + Msg in; + in = receive(); + + socket.setSoTimeout(0); + return in; + } + + // Send three ping messages to another system + public void ping(Configuration config) throws IOException, IWProtocolException { + for(int i=0; i<3;i++) { + // Create empty PhyPingMsg object + PhyPingMsg m = new PhyPingMsg((PhyConfiguration) config); + //Populate PhyPingMsg object with data + m.create(Integer.toString(i)); + // Call actual send method + this.send(m); + } + } + +} diff --git a/src/test/java/phy/CPClientCookieRequestTest.java b/src/test/java/phy/CPClientCookieRequestTest.java new file mode 100644 index 0000000000000000000000000000000000000000..dbe591dd32299aa5cdb2384156e5ce0d72deb60e --- /dev/null +++ b/src/test/java/phy/CPClientCookieRequestTest.java @@ -0,0 +1,149 @@ +package phy; + +import core.Protocol; +import cp.CPProtocol; +import exceptions.CookieRequestException; +import exceptions.IWProtocolException; +import exceptions.IllegalMsgException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class CPClientCookieRequestTest { + String serverName = "localhost"; + int serverPort = 3027; + + @Mock + PhyProtocol phyProtocolMock; + PhyMsg testMsg; + CPProtocol cProtocol; + + @BeforeEach + void setup() throws UnknownHostException { + // Create additionally needed objects for every test + PhyConfiguration phyConfig; + try { + phyConfig = new PhyConfiguration(InetAddress.getByName(serverName), serverPort, Protocol.proto_id.CP); + testMsg = new PhyMsg(phyConfig); + } catch (UnknownHostException e) { + fail(); + } + // Set up the object-under-test + cProtocol = new CPProtocol(InetAddress.getByName(serverName), serverPort, phyProtocolMock); + } + + @Test + void testCookieRequestSuccessful() throws IWProtocolException, IOException { + // Fill the message object that is going to be returned to the object-under-test + // with the message needed for this test case + testMsg = (PhyMsg)testMsg.parse("phy 7 cp cres ACK 12345"); + + // Implement behavior of the mocked object + when(phyProtocolMock.receive(anyInt())).thenReturn(testMsg); + + // Run the test + assertDoesNotThrow(()->cProtocol.requestCookie(0)); + + // verify a specified behavior + verify(phyProtocolMock, times(1)).receive(2000); + verify(phyProtocolMock).send(eq("cp creq"), any(PhyConfiguration.class)); + } + + @Test + void testNoCookie() throws IOException, IWProtocolException { + + // Fill the message object that is going to be returned to the object-under-test + // with the message needed for this test case + testMsg = (PhyMsg) testMsg.parse("phy 7 cp cres NAK no resources"); + + // Implement behavior of the mocked object + when(phyProtocolMock.receive(anyInt())).thenReturn(testMsg); + + // Run the test + assertThrows(CookieRequestException.class, + ()->cProtocol.requestCookie(0)); + verify(phyProtocolMock, times(1)).receive(2000); + + } + + @Test + void testIllegalPhyMsg() throws IOException, IWProtocolException { + + // Fill the message object that is going to be returned to the object-under-test + // with the message needed for this test case (also create corresponding configuration object) + PhyConfiguration phyConfig = new PhyConfiguration(InetAddress.getByName(serverName), serverPort, Protocol.proto_id.SLP); + testMsg = new PhyMsg(phyConfig); + testMsg = (PhyMsg) testMsg.parse("phy 5 cp cres ACK 12345"); + + // Implement behavior of the mocked object + when(phyProtocolMock.receive(anyInt())).thenReturn(testMsg); + + // Run the test + assertThrows(IllegalMsgException.class, + ()->cProtocol.requestCookie(0)); + verify(phyProtocolMock, times(1)).receive(2000); + } + + @Test + void testMaleformedCPMsg() throws IOException, IWProtocolException { + + // Fill the message object that is going to be returned to the object-under-test + // with the message needed for this test case (also create corresponding configuration object) + PhyConfiguration phyConfig = new PhyConfiguration(InetAddress.getByName(serverName), serverPort, Protocol.proto_id.SLP); + testMsg = new PhyMsg(phyConfig); + testMsg = (PhyMsg) testMsg.parse("phy 7 cp cresACK 12345"); + + // Implement behavior of the mocked object + when(phyProtocolMock.receive(anyInt())).thenReturn(testMsg); + + // Run the test + assertThrows(IllegalMsgException.class, + ()->cProtocol.requestCookie(0)); + verify(phyProtocolMock, times(1)).receive(2000); + } + + @Test + void testIncompleteCPMsg() throws IOException, IWProtocolException { + + // Fill the message object that is going to be returned to the object-under-test + // with the message needed for this test case (also create corresponding configuration object) + PhyConfiguration phyConfig = new PhyConfiguration(InetAddress.getByName(serverName), serverPort, Protocol.proto_id.SLP); + testMsg = new PhyMsg(phyConfig); + testMsg = (PhyMsg) testMsg.parse("phy 5 cp cres ACK"); + + // Implement behavior of the mocked object + when(phyProtocolMock.receive(anyInt())).thenReturn(testMsg); + + // Run the test + assertThrows(IllegalMsgException.class, + ()->cProtocol.requestCookie(0)); + verify(phyProtocolMock, times(1)).receive(2000); + } + + @Test + void testMessageLoss() throws IOException, IWProtocolException { + + // Implement behavior of the mocked object + when(phyProtocolMock.receive(anyInt())).thenThrow(new SocketTimeoutException()); + + // Run the test + assertThrows(CookieRequestException.class, + ()->cProtocol.requestCookie(0)); + verify(phyProtocolMock, times(3)).receive(2000); + } + +}