diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index f84887c2..68015574 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -15,6 +15,7 @@ public class ServerThread implements Runnable{ private StreamingServerSocket server; private final int HEARTBEAT_PERIOD = 5000; private final int RACE_STATUS_PERIOD = 1000; + private final int BOAT_LOCATION_PERIOD = 1000/5; private final int PORT_NUMBER = 8085; public ServerThread(String threadName){ @@ -68,6 +69,19 @@ public class ServerThread implements Runnable{ 100, 3, RaceType.MATCH_RACE, 1, boats); } + /** + * @return A list of sample boat location messages + */ + public List getTestBoatLocationMessages(){ + List messages = new ArrayList<>(); + + messages.add(new BoatLocationMessage(1, 1, 100, 200, 231, 23)); + messages.add(new BoatLocationMessage(2, 2, 400, 300, 211, 13)); + + return messages; + } + + public void run() { try{ server = new StreamingServerSocket(PORT_NUMBER); @@ -105,7 +119,7 @@ public class ServerThread implements Runnable{ try { server.send(hb); } catch (IOException e) { - e.printStackTrace(); + System.out.print(""); } } }, 0, HEARTBEAT_PERIOD); @@ -120,11 +134,27 @@ public class ServerThread implements Runnable{ try { server.send(statusMessage); } catch (IOException e) { - e.printStackTrace(); + System.out.print(""); } } }, 100, RACE_STATUS_PERIOD); + t1.schedule(new TimerTask() { + @Override + public void run() { + List messages = getTestBoatLocationMessages(); + + for (Message m : messages){ + try { + server.send(m); + } catch (IOException e) { + System.out.print(""); + } + } + + } + }, 100, BOAT_LOCATION_PERIOD); + } catch (IOException e) { System.err.println(e.getMessage()); } diff --git a/src/main/java/seng302/server/messages/BoatLocationMessage.java b/src/main/java/seng302/server/messages/BoatLocationMessage.java new file mode 100644 index 00000000..a475128b --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatLocationMessage.java @@ -0,0 +1,109 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; + +public class BoatLocationMessage extends Message { + private final int MESSAGE_SIZE = 56; + + private long messageVersionNumber; + private long time; + private long sourceId; + private long sequenceNum; + private DeviceType deviceType; + private long latitude; + private long longitude; + private long altitude; + private long heading; + private long pitch; + private long roll; + private long boatSpeed; + private long COG; + private long SOG; + private long apparentWindSpeed; + private long apparentWindAngle; + private long trueWindSpeed; + private long trueWindDirection; + private long trueWindAngle; + private long currentDrift; + private long currentSet; + private long rudderAngle; + + /** + * Describes the location, altitude and sensor data from the boat. + * @param sourceId ID of the boat + * @param sequenceNum Sequence number of the message + * @param latitude The boats latitude + * @param longitude The boats longitude + * @param heading The boats heading + * @param boatSpeed The boats speed + */ + public BoatLocationMessage(int sourceId, int sequenceNum, long latitude, long longitude, long heading, long boatSpeed){ + messageVersionNumber = 1; + time = System.currentTimeMillis() / 1000L; + this.sourceId = sourceId; + this.sequenceNum = sequenceNum; + this.deviceType = DeviceType.RACING_YACHT; + this.latitude = -latitude; + this.longitude = longitude; + this.altitude = 0; + this.heading = heading; + this.pitch = 0; + this.roll = 0; + this.boatSpeed = boatSpeed; + this.COG = 0; + this.SOG = 0; + this.apparentWindSpeed = 0; + this.apparentWindAngle = 0; + this.trueWindSpeed = 0; + this.trueWindDirection = 0; + this.trueWindAngle = 0; + this.currentDrift = 0; + this.currentSet = 0; + this.rudderAngle = 0; + + setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize())); + } + + @Override + public int getSize() { + return 56; + } + + @Override + public void send(DataOutputStream outputStream) { + allocateBuffer(); + writeHeaderToBuffer(); + + putByte((byte) messageVersionNumber); + putInt((int) time, 6); + putInt((int) sourceId, 4); + putUnsignedInt((int) sequenceNum, 4); + putByte((byte) deviceType.getCode()); + putInt((int) latitude, 4); + putInt((int) longitude, 4); + putInt((int) altitude, 4); + putUnsignedInt((int) heading, 2); + putInt((int) pitch, 2); + putInt((int) roll, 2); + putUnsignedInt((int) boatSpeed, 2); + putUnsignedInt((int) COG, 2); + putUnsignedInt((int) SOG, 2); + putUnsignedInt((int) apparentWindSpeed, 2); + putInt((int) apparentWindAngle, 2); + putUnsignedInt((int) trueWindSpeed, 2); + putUnsignedInt((int) trueWindDirection, 2); + putInt((int) trueWindAngle, 2); + putUnsignedInt((int) currentDrift, 2); + putUnsignedInt((int) currentSet, 2); + putInt((int) rudderAngle, 2); + + + writeCRC(); + try { + outputStream.write(getBuffer().array()); + } catch (IOException e) { + System.out.print(""); + } + } +} diff --git a/src/main/java/seng302/server/messages/BoatSubMessage.java b/src/main/java/seng302/server/messages/BoatSubMessage.java index d403d85b..ebe0f6a2 100644 --- a/src/main/java/seng302/server/messages/BoatSubMessage.java +++ b/src/main/java/seng302/server/messages/BoatSubMessage.java @@ -1,11 +1,12 @@ package seng302.server.messages; +import java.io.DataOutputStream; import java.nio.ByteBuffer; /** * The status of each boat, sent within a race status message */ -public class BoatSubMessage { +public class BoatSubMessage{ private final int MESSAGE_SIZE = 20; private long sourceId; @@ -57,22 +58,22 @@ public class BoatSubMessage { buff.position(buffPos); // Boat Status, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte) boatStatus.getCode()).array()); + buff.put(ByteBuffer.allocate(1).put((byte) (boatStatus.getCode() & 0xff)).array()); buffPos += 1; buff.position(buffPos); // Leg number, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte) legNumber).array()); + buff.put(ByteBuffer.allocate(1).put((byte) (legNumber & 0xff)).array()); buffPos += 1; buff.position(buffPos); // Number of penalties awarded, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesAwarded).array()); + buff.put(ByteBuffer.allocate(1).put((byte) (numberPenaltiesAwarded & 0xff)).array()); buffPos += 1; buff.position(buffPos); // Number of penalties served, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesServed).array()); + buff.put(ByteBuffer.allocate(1).put((byte) (numberPenaltiesServed & 0xff)).array()); buffPos += 1; buff.position(buffPos); diff --git a/src/main/java/seng302/server/messages/DeviceType.java b/src/main/java/seng302/server/messages/DeviceType.java new file mode 100644 index 00000000..d245c2b1 --- /dev/null +++ b/src/main/java/seng302/server/messages/DeviceType.java @@ -0,0 +1,16 @@ +package seng302.server.messages; + +public enum DeviceType { + UNKNOWN(0), + RACING_YACHT(1); + + private long code; + + DeviceType(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/server/messages/Heartbeat.java b/src/main/java/seng302/server/messages/Heartbeat.java index a6470240..e5815039 100644 --- a/src/main/java/seng302/server/messages/Heartbeat.java +++ b/src/main/java/seng302/server/messages/Heartbeat.java @@ -26,24 +26,15 @@ public class Heartbeat extends Message { public void send(DataOutputStream outputStream) { setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize())); - ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + getSize()); + allocateBuffer(); + writeHeaderToBuffer(); - // Write header - buff.put(getHeader().getByteBuffer()); - buff.position(Header.getSize()); + putUnsignedInt(seqNo, 4); - // Write seq num - buff.put(ByteBuffer.allocate(4).putInt(seqNo).array()); - buff.position(Header.getSize()+4); - - // Write CRC - CRC32 crc = new CRC32(); - crc.update(buff.array()); - - buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + writeCRC(); try { - outputStream.write(buff.array()); + outputStream.write(getBuffer().array()); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/seng302/server/messages/Message.java b/src/main/java/seng302/server/messages/Message.java index 8e5c48b4..889a37fa 100644 --- a/src/main/java/seng302/server/messages/Message.java +++ b/src/main/java/seng302/server/messages/Message.java @@ -1,21 +1,27 @@ package seng302.server.messages; import java.io.DataOutputStream; +import java.nio.ByteBuffer; +import java.util.zip.CRC32; public abstract class Message { - Header header; + private final int CRC_SIZE = 4; + private Header header; + private ByteBuffer buffer; + private int bufferPosition; + private CRC32 crc; /** * @param header Set the header for this message */ - public void setHeader(Header header){ + void setHeader(Header header){ this.header = header; } /** * @return the header specified for this message */ - public Header getHeader(){ + Header getHeader(){ return header; } @@ -28,4 +34,124 @@ public abstract class Message { * Send the message as through the outputStream */ public abstract void send(DataOutputStream outputStream); + + /** + * Allocate byte buffer to correct size + */ + void allocateBuffer(){ + buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE); + bufferPosition = 0; + } + + /** + * Write the set header to the byte buffer + */ + void writeHeaderToBuffer(){ + buffer.put(getHeader().getByteBuffer()); + bufferPosition += Header.getSize(); + buffer.position(bufferPosition); + } + + /** + * Move the buffer position by n bytes + * @param size Number of bytes to move the buffer by + */ + private void moveBufferPositionBy(int size){ + bufferPosition += size; + buffer.position(bufferPosition); + } + + /** + * Put an unsigned byte in the buffer + */ + void putUnsignedByte(byte b){ + buffer.put(ByteBuffer.allocate(1).put((byte) (b & 0xff)).array()); + moveBufferPositionBy(1); + } + + /** + * Put an signed byte in the buffer + */ + void putByte(byte b){ + buffer.put(ByteBuffer.allocate(1).put(b).array()); + moveBufferPositionBy(1); + } + + /** + * Place an unsigned integer of the specified length in the buffer + * @param val The integer value to add (Note: This must be long due to java not supporting unsigned integers) + * @param size The size of the int to be added to the buffer + */ + void putUnsignedInt(long val, int size){ + if (size <= 1){ + putUnsignedByte((byte) val); + + } + else if (size < 4){ + // Use short + buffer.put(ByteBuffer.allocate(size).putShort((short) (val & 0xffff)).array()); + moveBufferPositionBy(size); + } + else{ + // Use int + buffer.put(ByteBuffer.allocate(size).putInt((int) (val & 0xffffffffL)).array()); + moveBufferPositionBy(size); + } + } + + /** + * Put a signed int of a specified length in the buffer + * @param val The integer value to add + * @param size The size of the integer to be added to the buffer + */ + void putInt(int val, int size){ + if (size < 4){ + buffer.put(ByteBuffer.allocate(size).putShort((short) val).array()); + } + else{ + buffer.put(ByteBuffer.allocate(size).putInt((short) val).array()); + } + moveBufferPositionBy(size); + } + + /** + * Write an array of bytes to the buffer + * @param bytes to write + */ + void putBytes(byte[] bytes){ + buffer.put(bytes); + moveBufferPositionBy(bytes.length); + } + + /** + * Write a ByteBuffer of bytes to the buffer + * @param bytes to write + * @param size number of bytes + */ + void putBytes(ByteBuffer bytes, int size){ + buffer.put(bytes); + moveBufferPositionBy(size); + } + + + /** + * Calculate the CRC of the buffer and append it to the end of the buffer + */ + void writeCRC(){ + crc = new CRC32(); + + buffer.position(0); + crc.update(buffer.array()); + buffer.position(bufferPosition); + + putInt((int) crc.getValue(), CRC_SIZE); + } + + /** + * @return The current buffer + */ + public ByteBuffer getBuffer(){ + return buffer; + } + } diff --git a/src/main/java/seng302/server/messages/RaceStartNotificationType.java b/src/main/java/seng302/server/messages/RaceStartNotificationType.java new file mode 100644 index 00000000..29db3f1e --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceStartNotificationType.java @@ -0,0 +1,21 @@ +package seng302.server.messages; + +/** + * The types of race start status messages + */ +public enum RaceStartNotificationType { + SET_RACE_START_TIME(1), + RACE_POSTPONED(2), + RACE_ABANDONED(3), + RACE_TERMINATED(4); + + private final long type; + + RaceStartNotificationType(long type) { + this.type = type; + } + + long getType(){ + return type; + } +} diff --git a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java new file mode 100644 index 00000000..d2878bc2 --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java @@ -0,0 +1,59 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; + +public class RaceStartStatusMessage extends Message { + private final int MESSAGE_SIZE = 20; + + private long version; + private long timeStamp; + private long ackNumber; + private long raceStartTime; + private long raceId; + private RaceStartNotificationType notificationType; + + /** + * Message sent to clients with the expected start time of the race + * @param ackNumber Sequence number of message. + * @param raceStartTime Expected race start time + * @param raceId Race ID# + * @param notificationType Type of this notification + */ + public RaceStartStatusMessage(long ackNumber, long raceStartTime, long raceId, RaceStartNotificationType notificationType){ + this.version = 1; + this.timeStamp = System.currentTimeMillis() / 1000L; + this.ackNumber = ackNumber; + this.raceStartTime = raceStartTime; + this.notificationType = notificationType; + this.raceId = raceId; + + setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize())); + } + + @Override + public int getSize() { + return MESSAGE_SIZE; + } + + @Override + public void send(DataOutputStream outputStream) { + allocateBuffer(); + writeHeaderToBuffer(); + + putUnsignedByte((byte) version); + putInt((int) timeStamp, 6); + putInt((int) ackNumber, 2); + putInt((int) raceStartTime, 6); + putInt((int) raceId, 4); + putUnsignedByte((byte) notificationType.getType()); + + writeCRC(); + + try { + outputStream.write(getBuffer().array()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/server/messages/RaceStatusMessage.java index 17ab82fc..77c0c67a 100644 --- a/src/main/java/seng302/server/messages/RaceStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStatusMessage.java @@ -11,7 +11,6 @@ public class RaceStatusMessage extends Message{ private final int MESSAGE_VERSION = 2; //Always set to 1 private final int MESSAGE_BASE_SIZE = 24; - // fields private long currentTime; private long raceId; private RaceStatus raceStatus; @@ -56,7 +55,7 @@ public class RaceStatusMessage extends Message{ */ @Override public int getSize() { - return MESSAGE_BASE_SIZE + (20 * (int) numBoatsInRace); + return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace)); } /** @@ -65,64 +64,28 @@ public class RaceStatusMessage extends Message{ */ @Override public void send(DataOutputStream outputStream) { - ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + 4/*CRC*/); + allocateBuffer(); + writeHeaderToBuffer(); - buff.put(getHeader().getByteBuffer()); - buff.position(Header.getSize()); - - // Version Number, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)MESSAGE_VERSION).array()); - buff.position(Header.getSize() + 1); - - // Current time, 2 bytes - buff.put(ByteBuffer.allocate(6).putInt((int)currentTime).array()); - buff.position(Header.getSize() + 7); - - // Race id, 4 bytes - buff.put(ByteBuffer.allocate(4).putInt((int)raceId).array()); - buff.position(Header.getSize() + 11); - - // Race status, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)raceStatus.getCode()).array()); - buff.position(Header.getSize() + 12); - - // Expected start time, 6 bytes - buff.put(ByteBuffer.allocate(6).putInt((int)expectedStartTime).array()); - buff.position(Header.getSize() + 18); - - // Wind direction, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort((short)raceWindDirection.getCode()).array()); - buff.position(Header.getSize() + 20); - - // Wind Speed, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort((short)windSpeed).array()); - buff.position(Header.getSize() + 22); - - // Number of boats, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)numBoatsInRace).array()); - buff.position(Header.getSize() + 23); - - // Race Type, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)raceType.getCode()).array()); - buff.position(Header.getSize() + 24); - - int buffPosition = Header.getSize() + 24; + putByte((byte) MESSAGE_VERSION); + putInt((int) currentTime, 6); + putInt((int) raceId, 4); + putByte((byte) raceStatus.getCode()); + putInt((int) expectedStartTime, 6); + putInt((int) raceWindDirection.getCode(), 2); + putInt((int) windSpeed, 2); + putByte((byte) numBoatsInRace); + putByte((byte) raceType.getCode()); for (BoatSubMessage boatSubMessage : boats){ - buff.put(boatSubMessage.getByteBuffer()); - buffPosition += boatSubMessage.getSize(); - buff.position(buffPosition); + putBytes(boatSubMessage.getByteBuffer(), boatSubMessage.getSize()); } - // calculate CRC - crc.update(buff.array()); - - // Add CRC to message - buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + writeCRC(); // Send try { - outputStream.write(buff.array()); + outputStream.write(getBuffer().array()); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/seng302/server/messages/XMLMessage.java b/src/main/java/seng302/server/messages/XMLMessage.java index 2d7e0431..cc862d2f 100644 --- a/src/main/java/seng302/server/messages/XMLMessage.java +++ b/src/main/java/seng302/server/messages/XMLMessage.java @@ -11,26 +11,24 @@ public class XMLMessage extends Message{ private final int MESSAGE_SIZE = 14; // Message fields - private int timeStamp; - private short ack = 0x00; //Unused + private long timeStamp; + private long ack = 0x00; //Unused private XMLMessageSubType xmlMessageSubType; - private Short length; - private Short sequence; + private long length; + private long sequence; private String content; - private CRC32 crc; /** * XML Message from the AC35 Streaming data spec * @param content The XML content * @param type The XML Message Sub Type */ - public XMLMessage(String content, XMLMessageSubType type, short sequenceNum){ + public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){ this.content = content; this.xmlMessageSubType = type; - crc = new CRC32(); - timeStamp = (int) (System.currentTimeMillis() / 1000L); + timeStamp = System.currentTimeMillis() / 1000L; ack = 0; - length = (short) this.content.length(); + length = this.content.length(); sequence = sequenceNum; setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize())); @@ -48,47 +46,23 @@ public class XMLMessage extends Message{ * @param outputStream The output stream to send the message */ public void send(DataOutputStream outputStream) { - ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + 4); - buff.put(getHeader().getByteBuffer()); - buff.position(Header.getSize()); + allocateBuffer(); + writeHeaderToBuffer(); - // Version Number, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)MESSAGE_VERSION).array()); - buff.position(Header.getSize() + 1); + // Write message fields + putUnsignedByte((byte) MESSAGE_VERSION); + putInt((int) ack, 2); + putInt((int) timeStamp, 6); + putByte((byte)xmlMessageSubType.getType()); + putInt((int) sequence, 2); + putInt((int) length, 2); + putBytes(content.getBytes()); - // Ack, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort(ack).array()); - buff.position(Header.getSize() + 3); - - // Timestamp, 6 bytes - buff.put(ByteBuffer.allocate(6).putInt(timeStamp).array()); - buff.position(Header.getSize() + 9); - - // XML message sub type, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)xmlMessageSubType.getType()).array()); - buff.position(Header.getSize() + 10); - - // Seq num, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort(sequence).array()); - buff.position(Header.getSize() + 12); - - // Message length, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort(length).array()); - buff.position(Header.getSize() + 14); - - // XML Content - buff.put(this.content.getBytes()); - buff.position(Header.getSize() + 14 + this.content.getBytes().length); - - // calculate CRC - crc.update(buff.array()); - - // Add CRC to message - buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + writeCRC(); // Send try { - outputStream.write(buff.array()); + outputStream.write(getBuffer().array()); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/test/java/seng302/server/TestHeader.java b/src/test/java/seng302/server/TestHeader.java new file mode 100644 index 00000000..4fa2bc3d --- /dev/null +++ b/src/test/java/seng302/server/TestHeader.java @@ -0,0 +1,26 @@ +package seng302.server; + +import org.junit.Test; +import seng302.server.messages.Header; +import seng302.server.messages.MessageType; + +import static junit.framework.TestCase.assertTrue; + +/** + * Tests message header + */ +public class TestHeader { + + @Test + public void testHeaderSizeEqualsActualSize(){ + Header h = new Header(MessageType.DISPLAY_TEXT_MESSAGE, 1, (short) 1); + assertTrue(h.getSize() == h.getByteBuffer().array().length); + + } + + @Test + public void headerSizeIsSameAsSpec(){ + Header h = new Header(MessageType.DISPLAY_TEXT_MESSAGE, 1, (short) 1); + assertTrue(h.getSize() == 15); // Spec specifies 15 bytes + } +} diff --git a/src/test/java/seng302/server/TestMessage.java b/src/test/java/seng302/server/TestMessage.java new file mode 100644 index 00000000..71521014 --- /dev/null +++ b/src/test/java/seng302/server/TestMessage.java @@ -0,0 +1,136 @@ +package seng302.server; + +import org.junit.Test; +import seng302.server.messages.*; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import static junit.framework.TestCase.assertTrue; + +public class TestMessage { + private static int XML_MESSAGE_LEN = 14; + private static int RACE_STATUS_BASE_LEN = 24; + private static int BOAT_SUB_MESSAGE_LEN = 20; + private static int CRC_LEN = 4; + + /** + * Test generated output is the same as the expected output + */ + @Test + public void testHeatBetBufferOutputLength(){ + Message m = new Heartbeat(1); + List output = new ArrayList<>(); + + DataOutputStream ds = new DataOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + output.add(b); + } + }); + + m.send(ds); + assertTrue(output.size() == (m.getSize() + CRC_LEN + Header.getSize())); + } + + /** + * Test output expected is the same as the spec + */ + @Test + public void testXmlMessageSize(){ + Message m = new XMLMessage("12345", XMLMessageSubType.BOAT, 1); + assertTrue(m.getSize() == (XML_MESSAGE_LEN + "12345".length())); + } + + /** + * Ensure that when no boats are in the race, that only the base message is sent + */ + @Test + public void testRaceStatusMessageBufferLenNoBoats(){ + Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1, + 0,RaceType.MATCH_RACE,1, new ArrayList()); + + List output = new ArrayList<>(); + + DataOutputStream ds = new DataOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + output.add(b); + } + }); + + m.send(ds); + assertTrue(output.size() == RACE_STATUS_BASE_LEN + Header.getSize() + CRC_LEN); + } + + /** + * Test that each boat status is added to the message + */ + @Test + public void testRaceStatusMessageBufferLenWithBoats(){ + List boatMessages = new ArrayList<>(); + List output = new ArrayList<>(); + + BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + boatMessages.add(boat1); + boatMessages.add(boat2); + boatMessages.add(boat3); + + Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1, + 3,RaceType.MATCH_RACE,1, boatMessages); + + DataOutputStream ds = new DataOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + output.add(b); + } + }); + + m.send(ds); + assertTrue(output.size() == (RACE_STATUS_BASE_LEN + (BOAT_SUB_MESSAGE_LEN * 3) + CRC_LEN + Header.getSize())); + } + + /** + * IllegalArgumentException should be thrown when numBoatsInRace is smaller + * than the number of boats actually in the race + */ + @Test(expected = IllegalArgumentException.class) + public void testRaceStatusTooManyBoats(){ + List boatMessages = new ArrayList<>(); + List output = new ArrayList<>(); + + BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + boatMessages.add(boat1); + boatMessages.add(boat2); + boatMessages.add(boat3); + + Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1, + 1,RaceType.MATCH_RACE,1, boatMessages); + + m.send(new DataOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + System.out.print(""); + } + })); + } +}