diff --git a/specs/tiffanybonzon/CalcServer/pom.xml b/specs/tiffanybonzon/CalcServer/pom.xml new file mode 100644 index 0000000..5bf7956 --- /dev/null +++ b/specs/tiffanybonzon/CalcServer/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + res.tiffanybonzon + CalcServer + 1.0-SNAPSHOT + jar + + + + UTF-8 + 1.8 + 1.8 + + Calc Server + + + + org.junit.jupiter + junit-jupiter-api + 5.4.0 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.4.0 + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + server.impl.tiffanybonzon.CalcServer + + + + + + maven-surefire-plugin + 2.22.1 + + + + + + \ No newline at end of file diff --git a/specs/tiffanybonzon/CalcServer/src/main/java/server/impl/tiffanybonzon/CalcServer.java b/specs/tiffanybonzon/CalcServer/src/main/java/server/impl/tiffanybonzon/CalcServer.java new file mode 100644 index 0000000..a29cb36 --- /dev/null +++ b/specs/tiffanybonzon/CalcServer/src/main/java/server/impl/tiffanybonzon/CalcServer.java @@ -0,0 +1,14 @@ +package server.impl.tiffanybonzon; + +/** + * Main class for the CalcServer + * Initializes the server on port 2112 as defined in our specifications + * + * @author Tiffany Bonzon + */ +public class CalcServer { + public static void main(String[] args) { + Server srv = new Server(2112); + srv.serveClients(); + } +} diff --git a/specs/tiffanybonzon/CalcServer/src/main/java/server/impl/tiffanybonzon/Server.java b/specs/tiffanybonzon/CalcServer/src/main/java/server/impl/tiffanybonzon/Server.java new file mode 100644 index 0000000..bfebcc4 --- /dev/null +++ b/specs/tiffanybonzon/CalcServer/src/main/java/server/impl/tiffanybonzon/Server.java @@ -0,0 +1,187 @@ +package server.impl.tiffanybonzon; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Implementation of the multithreaded server. + * ***Strongly**** inspired by the Multi-Threaded TCP server example + * + * @author Tiffany Bonzon + */ +public class Server { + final static Logger LOG = Logger.getLogger(Server.class.getName()); + int port; + + /** + * Server Constructor + * @param port The port number on which the server listens + */ + public Server(int port) { + this.port = port; + } + + /** + * Initializes the process on a new thread and starts it + */ + public void serveClients() { + LOG.info("Starting the CalcServer Worker on a new thread..."); + new Thread(new CalcServerWorker()).start(); + } + + /** + * Listens for incoming connections and starts a new thread once a client connects + */ + private class CalcServerWorker implements Runnable { + public void run() { + ServerSocket ss; + + // Tries to create a socket binded to the specified port + try { + ss = new ServerSocket(port); + } catch(IOException e) { + LOG.log(Level.SEVERE, null, e); + return; + } + + while(true) { + LOG.log(Level.INFO, "Waiting for a new client on port {0}", port); + + try { + Socket cs = ss.accept(); + LOG.info("A new client has arrived. Starting a new thread and delegating work..."); + new Thread(new ServantWorker(cs)).start(); + } catch(IOException e) { + LOG.log(Level.SEVERE, null, e); + } + } + } + + /** + * Takes care of clients once they have connected. + * This is where we implement the application protocol logic + */ + private class ServantWorker implements Runnable { + Socket cs; + BufferedReader in = null; + PrintWriter out = null; + + public ServantWorker(Socket cs) { + try { + this.cs = cs; + in = new BufferedReader(new InputStreamReader(cs.getInputStream())); + out = new PrintWriter(cs.getOutputStream()); + } catch (IOException e) { + LOG.log(Level.SEVERE, null, e); + } + } + + public void run() { + String clientMessage; + boolean clientSentEND = false; + + try { + LOG.info("Reading until the client sends END..."); + while(!clientSentEND && (clientMessage = in.readLine()) != null) { + clientMessage = clientMessage.toUpperCase(); + + if(clientMessage.equals("HELLO")) { + out.println("> Available operations: ADD SUB MUL DIV POW"); + out.flush(); + } else if(clientMessage.equals("HELP")) { + out.println("> Syntax: Each element is separated by a space, decimal is specified with a dot. Connection can be closed by typing END"); + out.flush(); + } else if(clientMessage.startsWith("END")) { + clientSentEND = true; + } else if(clientMessage.startsWith("ADD") || clientMessage.startsWith("SUB") || clientMessage.startsWith("MUL") || clientMessage.startsWith("DIV") || clientMessage.startsWith("POW")) { + out.println("> " + processCalc(clientMessage)); + out.flush(); + } else { + out.println("> ERROR: Unknown command!"); + out.flush(); + } + } + + LOG.info("Cleaning up resources..."); + cs.close(); + in.close(); + out.close(); + + } catch(IOException e) { + if(in != null) { + try { + in.close(); + } catch(IOException ein) { + LOG.log(Level.SEVERE, ein.getMessage(), ein); + } + } + + if(out != null) { + out.close(); + } + + if(cs != null) { + try { + cs.close(); + } catch(IOException ecs) { + LOG.log(Level.SEVERE, ecs.getMessage(), ecs); + } + } + } + } + + /** + * Calculates the result of the operation sent by the client + * @param message The operation sent by the client + * @return The result of the operation or the appropriate error message + */ + private String processCalc(String message) { + String[] op = message.split(" "); + double operand1, operand2, result; + String ret; + + if(op.length != 3) { + return "ERROR: Invalid syntax!"; + } + + try { + operand1 = Double.parseDouble(op[1]); + operand2 = Double.parseDouble(op[2]); + } catch(NumberFormatException e) { + return "ERROR: Invalid operands!"; + } + + switch(op[0]) { + case "ADD": + result = operand1 + operand2; + break; + case "SUB": + result = operand1 - operand2; + break; + case "MUL": + result = operand1 * operand2; + break; + case "DIV": + if(operand2 == 0) { + return "ERROR: Can't divide by 0!"; + } + result = operand1 / operand2; + break; + case "POW": + result = Math.pow(operand1, operand2); + break; + default: + return "ERROR: Unknown operation!"; + } + + return "Result: " + result; + } + } + } +} diff --git a/specs/tiffanybonzon/CalcServer/src/test/java/server/test/tiffanybonzon/Tests.java b/specs/tiffanybonzon/CalcServer/src/test/java/server/test/tiffanybonzon/Tests.java new file mode 100644 index 0000000..76a55ce --- /dev/null +++ b/specs/tiffanybonzon/CalcServer/src/test/java/server/test/tiffanybonzon/Tests.java @@ -0,0 +1,562 @@ +package server.test.tiffanybonzon; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import server.impl.tiffanybonzon.Server; + +import java.io.*; +import java.net.Socket; + +import static org.junit.jupiter.api.Assertions.*; + +public class Tests { + @BeforeAll + static void initServ() { + Server srv = new Server(2112); + srv.serveClients(); + } + + /***************************************** + * CONNECTION TESTS + *****************************************/ + @Test + void theServerShouldAcceptAConnection() { + Socket client = null; + + try { + client = new Socket("localhost", 2112); + assertTrue(client.isConnected()); + + } catch(IOException e) { + fail(); + } finally { + try { + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + @Test + void theServerShouldAcceptMultipleConnections() { + int clientNum = 5; + Socket[] clients = new Socket[clientNum]; + + try { + for(int i = 0; i < clientNum; ++i) { + clients[i] = new Socket("localhost", 2112); + } + + assertTrue(clients[0].isConnected() && clients[1].isConnected() && clients[2].isConnected() && clients[3].isConnected() && clients[4].isConnected()); + + } catch(IOException e) { + fail(); + } finally { + try { + for(int i = 0; i < clientNum; ++i) { + clients[i].close(); + } + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + /***************************************** + * INIT TESTS + *****************************************/ + @Test + void theServerShouldSendOPListWhenReceivingHello() { + Socket client = null; + String expectedResponse = "> Available operations: ADD SUB MUL DIV POW"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("HELLO\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + @Test + void theServerShouldSendOPListWhenReceivingMultipleHello() { + Socket client = null; + String expectedResponse = "> Available operations: ADD SUB MUL DIV POW"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("HELLO\n"); + clientRequest.flush(); + if(serverResponse.readLine().equals(expectedResponse)) { + clientRequest.write("HELLO\n"); + clientRequest.flush(); + assertEquals(expectedResponse, serverResponse.readLine()); + } else { + fail(); + } + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + /***************************************** + * COMMANDS TESTS + *****************************************/ + @Test + void theServerShouldRespondToTheHelpCommand() { + Socket client = null; + String expectedResponse = "> Syntax: Each element is separated by a space, decimal is specified with a dot. Connection can be closed by typing END"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("HELP\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + @Test + void theServerShouldNotAnswerToEndCommand() { + Socket client = null; + String expectedResponse = "> Syntax: Each element is separated by a space, decimal is specified with a dot. Connection can be closed by typing END"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("END\n"); + clientRequest.flush(); + + assertNull(serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + @Test + void theServerShouldSendAnErrorWhenReceivingAnUnknownCommand() { + Socket client = null; + String expectedResponse = "> ERROR: Unknown command!"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("HALLO\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + /***************************************** + * OPERATIONS TESTS + *****************************************/ + @Test + void theServerCanAddTwoIntergers() { + Socket client = null; + String expectedResponse = "> Result: 5.0"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("ADD 2 3\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + @Test + void theServerCanAddTwoFloats() { + Socket client = null; + String expectedResponse = "> Result: 7.321"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("ADD 3.3 4.021\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + @Test + void theServerCanAddNegativeNumbers() { + Socket client = null; + String expectedResponse = "> Result: -2.4"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("ADD -1.2 -1.2\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + @Test + void theServerCanSubstractTwoNumbers() { + Socket client = null; + String expectedResponse = "> Result: 3.9"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("SUB 7 3.1\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + @Test + void theServerCanMulitplyTwoNumbers() { + Socket client = null; + String expectedResponse = "> Result: 15.0"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("MUL 5 3\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + @Test + void theServerCanDivideTwoNumbers() { + Socket client = null; + String expectedResponse = "> Result: 5.0"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("DIV 15 3\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + @Test + void theServerCanExponentiateTwoNumbers() { + Socket client = null; + String expectedResponse = "> Result: 27.0"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("POW 3 3\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + /***************************************** + * INVALID OPERATIONS TESTS + *****************************************/ + @Test + void theServerSendsErrorWhenOperationIsNotCorrectlyFormatted() { + Socket client = null; + String expectedResponse = "> ERROR: Invalid syntax!"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("ADD 3 3 3\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + @Test + void theServerSendsErrorWhenOperandsAreIncorrectlyFormatted() { + Socket client = null; + String expectedResponse = "> ERROR: Invalid operands!"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("ADD 3 3a\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + @Test + void theServerShouldSendErrorWhenTheClientWantsTODivideByZero() { + Socket client = null; + String expectedResponse = "> ERROR: Can't divide by 0!"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("DIV 79 0\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } + + @Test + void theServerSendsErrorWhenOperationsAreIncorrectlyFormatted() { + Socket client = null; + String expectedResponse = "> ERROR: Unknown operation!"; + BufferedWriter clientRequest = null; + BufferedReader serverResponse = null; + + try { + client = new Socket("localhost", 2112); + clientRequest = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); + serverResponse = new BufferedReader(new InputStreamReader(client.getInputStream())); + + clientRequest.write("ADDE 3 3\n"); + clientRequest.flush(); + + assertEquals(expectedResponse, serverResponse.readLine()); + + } catch(IOException e) { + fail(); + } finally { + try { + clientRequest.close(); + serverResponse.close(); + client.close(); + } catch(Exception e1) { + System.out.println(e1.getMessage()); + } + } + } +} diff --git a/specs/tiffanybonzon/PROTOCOL.md b/specs/tiffanybonzon/PROTOCOL.md new file mode 100644 index 0000000..c492d71 --- /dev/null +++ b/specs/tiffanybonzon/PROTOCOL.md @@ -0,0 +1,32 @@ +# Phase 1: write the specification +## What transport protocol do we use? +We use TCP + +## How does the client find the server (addresses and ports)? +The server can be reached by its IP address (127.0.0.1 if server and clients are on the same machine), and the port 2112 + +## Who speaks first? +The client initiates the exchange + +## What is the sequence of messages exchanged by the client and the server? (flow) +1. Client initializes connection ("HELLO") +2. Servers answers with operation list ("OP : ADD SUB MUL DIV POW") +3. Client answers ("OP NUM1 NUM2") +4. Server sends answer +5. Server waits for next operation ("OP NUM1 NUM") or for client to end the session ("END") + +## What happens when a message is received from the other party? (semantics) +The server processes the requests +The client displays the answers + +## What is the syntax of the messages? How we generate and parse them? (syntax) +1. Initialization message: "HELLO" +2. First server reply: + "Available operations: ADD SUB MULT DIV POW. + Syntax: (Each element is separated by a space, decimal is specified with a dot)" +3. Client replies: "OP NUM1 NUM2" +4. Server return calc answer: "" or "ERROR: Syntax Error / Operation Unknown / Command Unknown" +5. Client sends new calc or ends the session: "OP NUM1 NUM2" or "END" + +## Who closes the connection and when? +The clients can tell the server to close the connection by sending "END"