From 09c4f98056eb090c2b12b38fbe248335367ebffb Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Thu, 10 Aug 2017 19:01:30 +1200 Subject: [PATCH] Implemented new client - server handshake protocol - Implemented new packet types - Changed server & client logic to use new protocol Tags: #story[1124] (Issue 39) --- .../java/seng302/gameServer/GameState.java | 1 + .../gameServer/ServerPacketParser.java | 27 ++-- .../gameServer/ServerToClientThread.java | 125 ++++++++++-------- .../server/messages/ClientType.java | 33 +++++ .../server/messages/MessageType.java | 6 +- .../messages/RegistrationRequestMessage.java | 22 +++ .../messages/RegistrationResponseMessage.java | 20 +++ .../messages/RegistrationResponseStatus.java | 44 ++++++ .../model/stream/packets/PacketType.java | 8 +- .../visualiser/ClientToServerThread.java | 98 +++++++++----- .../java/seng302/visualiser/GameClient.java | 1 + 11 files changed, 274 insertions(+), 111 deletions(-) create mode 100644 src/main/java/seng302/gameServer/server/messages/ClientType.java create mode 100644 src/main/java/seng302/gameServer/server/messages/RegistrationRequestMessage.java create mode 100644 src/main/java/seng302/gameServer/server/messages/RegistrationResponseMessage.java create mode 100644 src/main/java/seng302/gameServer/server/messages/RegistrationResponseStatus.java diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 913f4f37..1bb968d1 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -20,6 +20,7 @@ public class GameState implements Runnable { private Logger logger = LoggerFactory.getLogger(MarkOrder.class); private static Integer STATE_UPDATES_PER_SECOND = 60; + public static Integer MAX_PLAYERS = 8; private static Long previousUpdateTime; public static Double windDirection; diff --git a/src/main/java/seng302/gameServer/ServerPacketParser.java b/src/main/java/seng302/gameServer/ServerPacketParser.java index 0d631eb1..21841f18 100644 --- a/src/main/java/seng302/gameServer/ServerPacketParser.java +++ b/src/main/java/seng302/gameServer/ServerPacketParser.java @@ -1,37 +1,26 @@ package seng302.gameServer; import java.util.Arrays; + +import seng302.gameServer.server.messages.ClientType; +import seng302.gameServer.server.messages.Message; import seng302.model.stream.packets.StreamPacket; import seng302.gameServer.server.messages.BoatActionType; public class ServerPacketParser { - public static BoatActionType extractBoatAction(StreamPacket packet) { byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; - long actionTypeValue = bytesToLong(Arrays.copyOfRange(payload, 0, 1)); + long actionTypeValue = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1)); return BoatActionType.getType((int) actionTypeValue); } - /** - * takes an array of up to 7 bytes and returns a positive - * long constructed from the input bytes - * - * @return a positive long if there is less than 7 bytes -1 otherwise - */ - private static long bytesToLong(byte[] bytes) { - long partialLong = 0; - int index = 0; - for (byte b : bytes) { - if (index > 6) { - return -1; - } - partialLong = partialLong | (b & 0xFFL) << (index * 8); - index++; - } - return partialLong; + public static ClientType extractClientType(StreamPacket packet){ + byte[] payload = packet.getPayload(); + long value = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1)); + return ClientType.getClientType((int) value); } } diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index ff2c4f23..20001fc1 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -18,6 +18,8 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import java.util.zip.CRC32; import java.util.zip.Checksum; + +import seng302.gameServer.server.messages.*; import seng302.model.Player; import seng302.model.Yacht; import seng302.model.stream.packets.PacketType; @@ -25,16 +27,6 @@ import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.xml.generator.Race; import seng302.model.stream.xml.generator.Regatta; import seng302.utilities.XMLGenerator; -import seng302.gameServer.server.messages.BoatActionType; -import seng302.gameServer.server.messages.BoatLocationMessage; -import seng302.gameServer.server.messages.BoatStatus; -import seng302.gameServer.server.messages.BoatSubMessage; -import seng302.gameServer.server.messages.Message; -import seng302.gameServer.server.messages.RaceStatus; -import seng302.gameServer.server.messages.RaceStatusMessage; -import seng302.gameServer.server.messages.RaceType; -import seng302.gameServer.server.messages.XMLMessage; -import seng302.gameServer.server.messages.XMLMessageSubType; /** * A class describing a single connection to a Client for the purposes of sending and receiving on @@ -62,10 +54,27 @@ public class ServerToClientThread implements Runnable, Observer { private Integer seqNo; private Integer sourceId; + private ClientType clientType; + private Boolean isRegistered = false; + private XMLGenerator xml; public ServerToClientThread(Socket socket) { this.socket = socket; + seqNo = 0; + + try{ + is = socket.getInputStream(); + os = socket.getOutputStream(); + } catch (IOException e) { + return; + } + + thread = new Thread(this); + thread.start(); + } + + private void setUpYacht(){ BufferedReader fn; String fName = ""; BufferedReader ln; @@ -74,45 +83,33 @@ public class ServerToClientThread implements Runnable, Observer { is = socket.getInputStream(); os = socket.getOutputStream(); fn = new BufferedReader( - new InputStreamReader( - ServerToClientThread.class.getResourceAsStream( - "/server_config/CSV_Database_of_First_Names.csv" + new InputStreamReader( + ServerToClientThread.class.getResourceAsStream( + "/server_config/CSV_Database_of_First_Names.csv" + ) ) - ) ); List all = fn.lines().collect(Collectors.toList()); fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); ln = new BufferedReader( - new InputStreamReader( - ServerToClientThread.class.getResourceAsStream( - "/server_config/CSV_Database_of_Last_Names.csv" + new InputStreamReader( + ServerToClientThread.class.getResourceAsStream( + "/server_config/CSV_Database_of_Last_Names.csv" + ) ) - ) ); all = ln.lines().collect(Collectors.toList()); lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); } catch (IOException e) { serverLog("IO error in server thread upon grabbing streams", 1); } - //Attempt threeway handshake with connection - sourceId = GameState.getUniquePlayerID(); - if (threeWayHandshake(sourceId)) { - serverLog("Successful handshake. Client allocated id: " + sourceId, 0); - Yacht yacht = new Yacht( - "Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ" - ); -// Yacht yacht = new Yacht("Kappa", "Kap", new GeoPoint(57.6708220, 11.8321340), 90.0); - GameState.addYacht(sourceId, yacht); - GameState.addPlayer(new Player(socket, yacht)); - } else { - serverLog("Unsuccessful handshake. Connection rejected", 1); - closeSocket(); - return; - } - seqNo = 0; - thread = new Thread(this); - thread.start(); + Yacht yacht = new Yacht( + "Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ" + ); + + GameState.addYacht(sourceId, yacht); + GameState.addPlayer(new Player(socket, yacht)); } static void serverLog(String message, int logLevel) { @@ -127,11 +124,39 @@ public class ServerToClientThread implements Runnable, Observer { sendSetupMessages(); } + private void completeRegistration(ClientType clientType) throws IOException { + // Fail if not a player + if (!clientType.equals(ClientType.PLAYER)){ + RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_GENERAL); + os.write(responseMessage.getBuffer()); + return; + } + + if (GameState.getPlayers().size() >= GameState.MAX_PLAYERS){ + RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_FULL); + os.write(responseMessage.getBuffer()); + return; + } + + Integer sourceId = GameState.getUniquePlayerID(); + RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(sourceId, RegistrationResponseStatus.SUCCESS_PLAYING); + + this.clientType = clientType; + this.sourceId = sourceId; + setUpYacht(); + + isRegistered = true; + os.write(responseMessage.getBuffer()); + sendSetupMessages(); + + } + public void run() { int sync1; int sync2; // TODO: 14/07/17 wmu16 - Work out how to fix this while loop + while (socket.isConnected()) { try { @@ -169,10 +194,17 @@ public class ServerToClientThread implements Runnable, Observer { switch (PacketType.assignPacketType(type, payload)) { case BOAT_ACTION: BoatActionType actionType = ServerPacketParser - .extractBoatAction( - new StreamPacket(type, payloadLength, timeStamp, payload)); + .extractBoatAction( + new StreamPacket(type, payloadLength, timeStamp, payload)); GameState.updateBoat(sourceId, actionType); break; + + case RACE_REGISTRATION_REQUEST: + ClientType requestedType = ServerPacketParser.extractClientType( + new StreamPacket(type, payloadLength, timeStamp, payload)); + + completeRegistration(requestedType); + break; } } else { serverLog("Packet has been dropped", 1); @@ -230,23 +262,6 @@ public class ServerToClientThread implements Runnable, Observer { * @return A boolean indicating if it was a successful handshake */ private Boolean threeWayHandshake(Integer id) { - Integer confirmationID = null; - Integer identificationAttempt = 0; - while (!userIdentified) { - try { - os.write(id); //Send out new ID looking for echo - confirmationID = is.read(); - } catch (IOException e) { - serverLog("Three way handshake failed", 1); - } - - if (id.equals(confirmationID)) { //ID is echoed back. Connection is a client - return true; - } else if (identificationAttempt > MAX_ID_ATTEMPTS) { //No response. not a client. tidy up and go home. - return false; - } - identificationAttempt++; - } return true; } diff --git a/src/main/java/seng302/gameServer/server/messages/ClientType.java b/src/main/java/seng302/gameServer/server/messages/ClientType.java new file mode 100644 index 00000000..b96ca5c7 --- /dev/null +++ b/src/main/java/seng302/gameServer/server/messages/ClientType.java @@ -0,0 +1,33 @@ +package seng302.gameServer.server.messages; + +public enum ClientType { + SPECTATOR(0x00), + PLAYER(0x01), + CONTROL_TUTORIAL(0x02), + GHOST_MODE(0x03); + + private int type; + + ClientType(int type){ + this.type = type; + } + + public int getCode(){ + return type; + } + + public static ClientType getClientType(int typeCode){ + switch (typeCode){ + case 0x00: + return SPECTATOR; + case 0x01: + return PLAYER; + case 0x02: + return CONTROL_TUTORIAL; + case 0x03: + return GHOST_MODE; + default: + return PLAYER; + } + } +} diff --git a/src/main/java/seng302/gameServer/server/messages/MessageType.java b/src/main/java/seng302/gameServer/server/messages/MessageType.java index 1d25e61d..4552d781 100644 --- a/src/main/java/seng302/gameServer/server/messages/MessageType.java +++ b/src/main/java/seng302/gameServer/server/messages/MessageType.java @@ -17,7 +17,9 @@ public enum MessageType { MARK_ROUNDING(38), COURSE_WIND(44), AVERAGE_WIND(47), - BOAT_ACTION(100); + BOAT_ACTION(100), + REGISTRATION_REQUEST(101), + REGISTRATION_RESPONSE(102); private int code; @@ -32,4 +34,6 @@ public enum MessageType { int getCode(){ return this.code; } + + } diff --git a/src/main/java/seng302/gameServer/server/messages/RegistrationRequestMessage.java b/src/main/java/seng302/gameServer/server/messages/RegistrationRequestMessage.java new file mode 100644 index 00000000..59757a18 --- /dev/null +++ b/src/main/java/seng302/gameServer/server/messages/RegistrationRequestMessage.java @@ -0,0 +1,22 @@ +package seng302.gameServer.server.messages; + + +public class RegistrationRequestMessage extends Message { + private static int MESSAGE_LENGTH = 2; + + public RegistrationRequestMessage(ClientType type){ + setHeader(new Header(MessageType.REGISTRATION_REQUEST, 1, (short) getSize())); + + allocateBuffer(); + writeHeaderToBuffer(); + + putInt(type.getCode(), 2); + + writeCRC(); + } + + @Override + public int getSize() { + return MESSAGE_LENGTH; + } +} diff --git a/src/main/java/seng302/gameServer/server/messages/RegistrationResponseMessage.java b/src/main/java/seng302/gameServer/server/messages/RegistrationResponseMessage.java new file mode 100644 index 00000000..e2174da4 --- /dev/null +++ b/src/main/java/seng302/gameServer/server/messages/RegistrationResponseMessage.java @@ -0,0 +1,20 @@ +package seng302.gameServer.server.messages; + +public class RegistrationResponseMessage extends Message{ + + public RegistrationResponseMessage(int clientSourceID, RegistrationResponseStatus status){ + setHeader(new Header(MessageType.REGISTRATION_RESPONSE, 1, (short) getSize())); + allocateBuffer(); + writeHeaderToBuffer(); + + putInt(clientSourceID, 4); + putInt(status.getCode(), 1); + + writeCRC(); + } + + @Override + public int getSize() { + return 5; + } +} diff --git a/src/main/java/seng302/gameServer/server/messages/RegistrationResponseStatus.java b/src/main/java/seng302/gameServer/server/messages/RegistrationResponseStatus.java new file mode 100644 index 00000000..2ae47a92 --- /dev/null +++ b/src/main/java/seng302/gameServer/server/messages/RegistrationResponseStatus.java @@ -0,0 +1,44 @@ +package seng302.gameServer.server.messages; + +public enum RegistrationResponseStatus { + SUCCESS_SPECTATING(0x00), + SUCCESS_PLAYING(0x01), + SUCCESS_TUTORIAL(0x02), + SUCCESS_GHOSTING(0x03), + + FAILURE_GENERAL(0x10), + FAILURE_FULL(0x11); + + private int code; + + RegistrationResponseStatus(int code){ + this.code = code; + } + + /** + * Get the message code (From the API Spec) + * @return the message code + */ + int getCode(){ + return this.code; + } + + public static RegistrationResponseStatus getResponseStatus(int typeCode){ + switch (typeCode){ + case 0x00: + return SUCCESS_SPECTATING; + case 0x01: + return SUCCESS_PLAYING; + case 0x02: + return SUCCESS_TUTORIAL; + case 0x03: + return SUCCESS_GHOSTING; + case 0x10: + return FAILURE_GENERAL; + case 0x11: + return FAILURE_FULL; + default: + return FAILURE_GENERAL; + } + } +} diff --git a/src/main/java/seng302/model/stream/packets/PacketType.java b/src/main/java/seng302/model/stream/packets/PacketType.java index e7f55b49..1a38ae14 100644 --- a/src/main/java/seng302/model/stream/packets/PacketType.java +++ b/src/main/java/seng302/model/stream/packets/PacketType.java @@ -19,7 +19,9 @@ public enum PacketType { COURSE_WIND, AVG_WIND, BOAT_ACTION, - OTHER; + OTHER, + RACE_REGISTRATION_REQUEST, + RACE_REGISTRATION_RESPONSE; public static PacketType assignPacketType(int packetType, byte[] payload){ switch(packetType){ @@ -56,6 +58,10 @@ public enum PacketType { return AVG_WIND; case 100: return BOAT_ACTION; + case 101: + return RACE_REGISTRATION_REQUEST; + case 102: + return RACE_REGISTRATION_RESPONSE; default: } return OTHER; diff --git a/src/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index 414696c8..f59fa81d 100644 --- a/src/main/java/seng302/visualiser/ClientToServerThread.java +++ b/src/main/java/seng302/visualiser/ClientToServerThread.java @@ -7,6 +7,7 @@ import java.io.OutputStream; import java.net.Socket; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -15,9 +16,12 @@ import java.util.zip.Checksum; import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import seng302.gameServer.server.messages.*; +import seng302.model.stream.packets.PacketType; import seng302.model.stream.packets.StreamPacket; -import seng302.gameServer.server.messages.BoatActionMessage; -import seng302.gameServer.server.messages.Message; /** * A class describing a single connection to a Server for the purposes of sending and receiving on @@ -48,8 +52,9 @@ public class ClientToServerThread implements Runnable { private Socket socket; private InputStream is; private OutputStream os; + private Logger logger = LoggerFactory.getLogger(ClientToServerThread.class); - private int clientId; + private int clientId = -1; // private Boolean updateClient = true; private ByteArrayOutputStream crcBuffer; @@ -71,15 +76,8 @@ public class ClientToServerThread implements Runnable { socket = new Socket(ipAddress, portNumber); is = socket.getInputStream(); os = socket.getOutputStream(); - Integer allocatedID = threeWayHandshake(); - if (allocatedID != null) { - clientId = allocatedID; - clientLog("Successful handshake. Allocated ID: " + clientId, 1); - } else { - clientLog("Unsuccessful handshake", 1); - closeSocket(); - return; - } + + sendRegistrationRequest(); thread = new Thread(this); thread.start(); @@ -128,9 +126,15 @@ public class ClientToServerThread implements Runnable { if (streamPackets.size() > 0) { streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); } else { - streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); - for (ClientSocketListener csl : listeners) - csl.newPacket(); + if (PacketType.RACE_REGISTRATION_RESPONSE == PacketType.assignPacketType(type, payload)){ + processRegistrationResponse(new StreamPacket(type, payloadLength, timeStamp, payload)); + } + else { + if (clientId == -1) continue; // Do not continue if not registered + streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); + for (ClientSocketListener csl : listeners) + csl.newPacket(); + } } } else { clientLog("Packet has been dropped", 1); @@ -155,36 +159,60 @@ public class ClientToServerThread implements Runnable { /** - * Listens for an allocated sourceID and returns it to the server - * - * @return the sourceID allocated to us by the server + * Sends a request to the server asking for a source ID */ - private Integer threeWayHandshake() { - Integer ourSourceID = null; - while (true) { - try { - ourSourceID = is.read(); - } catch (IOException e) { - clientLog("Three way handshake failed", 1); - } - if (ourSourceID != null) { - try { - os.write(ourSourceID); - return ourSourceID; - } catch (IOException e) { - clientLog("Three way handshake failed", 1); - return null; - } - } + private void sendRegistrationRequest() { + RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER); + + try { + os.write(requestMessage.getBuffer()); + } catch (IOException e) { + logger.error("Could not send registration request. Exiting"); + System.exit(1); } } + /** + * Accepts a response to the registration request message, and updates the client OR quits + * @param packet The registration requests packet + */ + private void processRegistrationResponse(StreamPacket packet){ + int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 3)); + int statusCode = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 4,5)); + + RegistrationResponseStatus status = RegistrationResponseStatus.getResponseStatus(statusCode); + + if (status.equals(RegistrationResponseStatus.SUCCESS_PLAYING)){ + clientId = sourceId; + + return; + } + + logger.error("Server Denied Connection, Exiting"); + + final String alertErrorText; + + if (status.equals(RegistrationResponseStatus.FAILURE_FULL)){ + alertErrorText = "Server is full"; + } + else{ + alertErrorText = "Could not connect to server"; + } + + Platform.runLater(() -> { + new Alert(AlertType.ERROR, alertErrorText, ButtonType.OK).showAndWait(); + System.exit(1); + }); + } + /** * Send the post-start race course information * @param boatActionMessage The message to send */ public void sendBoatActionMessage(BoatActionMessage boatActionMessage) { + if (clientId == -1) return; + try { os.write(boatActionMessage.getBuffer()); } catch (IOException e) { diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index b00a6cda..c74a99ef 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -62,6 +62,7 @@ public class GameClient { ioe.printStackTrace(); System.out.println("Unable to connect to host..."); } + socketThread.addStreamObserver(this::parsePackets); LobbyController lobbyController = loadLobby(); lobbyController.setPlayerListSource(clientLobbyList);