diff --git a/doc/protocol.md b/doc/protocol.md index 0ea88aa..7a9fb8a 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -13,6 +13,8 @@ All messages begins with `P2P-JAVA-PROJECT VERSION 1.0\n` (this version of the p # P2P-JAVA-PROJECT version 1.1 (Binary protocol for step 1) +All strings in the datagram are utf-8 encoded. + ```Datagram format 1 byte: [0-7: VERSION(0x11, first quartet is major version, second is minor)] @@ -58,7 +60,8 @@ Payload contains ``` 8 bytes: [64-127: OFFSET OF FILE CONTENT IN BYTES] 8 bytes: [128-191: TOTAL FILESIZE] -y bytes: [\n] +4 bytes: [192-223: FILENAME SIZE] (cannot be > to PAYLOAD_SIZE - 20 or be zero) +y bytes: [] z bytes: [FILE CONTENT] ``` diff --git a/src/exception/InternalError.java b/src/exception/InternalError.java index c738615..9a20633 100644 --- a/src/exception/InternalError.java +++ b/src/exception/InternalError.java @@ -1,2 +1,4 @@ package exception; -public class InternalError extends Exception {} +public class InternalError extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/exception/NotFound.java b/src/exception/NotFound.java index 172a0bc..7018d43 100644 --- a/src/exception/NotFound.java +++ b/src/exception/NotFound.java @@ -1,2 +1,4 @@ package exception; -public class NotFound extends Exception {} +public class NotFound extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/exception/ProtocolError.java b/src/exception/ProtocolError.java index 4b86a8c..d5e4811 100644 --- a/src/exception/ProtocolError.java +++ b/src/exception/ProtocolError.java @@ -1,2 +1,4 @@ package exception; -public class ProtocolError extends Exception {} +public class ProtocolError extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/exception/SizeError.java b/src/exception/SizeError.java index 0af8f87..d0cb07d 100644 --- a/src/exception/SizeError.java +++ b/src/exception/SizeError.java @@ -1,3 +1,5 @@ package exception; /** Used on reception side when size as set in datagram is too big, and we cant store this in a int/long as usual. */ -public class SizeError extends Exception {} +public class SizeError extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/exception/TransmissionError.java b/src/exception/TransmissionError.java index 5c64987..d95dd4c 100644 --- a/src/exception/TransmissionError.java +++ b/src/exception/TransmissionError.java @@ -1,2 +1,4 @@ package exception; -public class TransmissionError extends Exception {} +public class TransmissionError extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/exception/VersionError.java b/src/exception/VersionError.java index c271a81..f84c811 100644 --- a/src/exception/VersionError.java +++ b/src/exception/VersionError.java @@ -1,2 +1,4 @@ package exception; -public class VersionError extends Exceptions {} +public class VersionError extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/protocolP2P/FileList.java b/src/protocolP2P/FileList.java index 4372e9f..aaa819b 100644 --- a/src/protocolP2P/FileList.java +++ b/src/protocolP2P/FileList.java @@ -13,7 +13,6 @@ import exception.SizeError; * @version 1.0 */ public class FileList extends Payload { - private final static RequestResponseCode requestResponseCode = RequestResponseCode.LIST_RESPONSE; private ArrayList content; /** Constructor (typically used by the server) with an ArrayList parameter containing @@ -22,10 +21,11 @@ public class FileList extends Payload { * @throws InternalError */ public FileList(ArrayList content) throws InternalError { + super(RequestResponseCode.LIST_RESPONSE); /* assert to help debugging */ assert !content.isEmpty() : "Payload size of FileList must not be empty, use EmptyDirectory from Payload instead"; if (content.isEmpty()) { - throws new InternalError(); + throw new InternalError(); } this.content = content; } @@ -34,24 +34,27 @@ public class FileList extends Payload { * @param datagram the full datagram received * @throws SizeError * @throws InternalError + * @throws ProtocolError */ - protected FileList(byte[] datagram) throws SizeError, InternalError { - //TODO + protected FileList(byte[] datagram) throws SizeError, ProtocolError, InternalError { + super(datagram); /* assert to help debugging */ - assert RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]) == RequestResponseCode.LIST_RESPONSE : "FileList subclass is incompatible with this datagram, request/response code must be checked before using this constructor"; + assert requestResponseCode == RequestResponseCode.LIST_RESPONSE : "FileList subclass is incompatible with this datagram, request/response code must be checked before using this constructor"; /* InternalErrorException */ - if (RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LIST_RESPONSE) { + if (requestResponseCode!= RequestResponseCode.LIST_RESPONSE) { throw new InternalError(); } int size = getPayloadSize(datagram); + //TODO } /** Returns a byte[] containing datagram with padding. * This datagram is still incomplete and should not be send directly. * ProtocolP2PDatagram will use this method to generate the complete datagram. * @return datagram with padding + * @throws InternalError */ - protected byte[] toDatagram() { + /*protected byte[] toDatagram() throws InternalError { //TODO compute size int size = 8 + ; byte[] datagram = new byte[size]; // java initialize all to zero @@ -61,5 +64,5 @@ public class FileList extends Payload { // bits 16-31 are reserved for future use datagram = setPayloadSize(size, datagram); //TODO Write content - } + }*/ } diff --git a/src/protocolP2P/FilePart.java b/src/protocolP2P/FilePart.java index c63801c..d6d6d2c 100644 --- a/src/protocolP2P/FilePart.java +++ b/src/protocolP2P/FilePart.java @@ -5,6 +5,8 @@ import exception.ProtocolError; import exception.InternalError; import exception.SizeError; import tools.BytesArrayTools; +import java.util.Arrays; +import java.io.UnsupportedEncodingException; /** Representation of payload for load response. * @author Louis Royer @@ -13,7 +15,6 @@ import tools.BytesArrayTools; * @version 1.0 */ public class FilePart extends Payload { - private static final RequestResponseCode requestResponseCode = RequestResponseCode.LOAD_RESPONSE; private String filename; private long totalSize; private long offset; @@ -27,10 +28,15 @@ public class FilePart extends Payload { * @throws InternalError */ public FilePart(String filename, long totalSize, long offset, byte[] partialContent) throws InternalError { + super(RequestResponseCode.LOAD_RESPONSE); /* asserts to help debugging */ assert totalSize >= 0 : "totalSize cannot be negative"; + assert partialContent.length != 0 : "partialContent.length cannot be zero"; + assert totalSize >= partialContent.length : "totalSize must be greater than partialContent.length"; assert offset >= 0 : "offset cannot be negative"; - if (totalSize < 0 || offset < 0) { + assert filename != null : "filename is required"; + if (totalSize < 0 || partialContent.length == 0 || totalSize < partialContent.length + || offset < 0 || filename == null) { throw new InternalError(); } this.filename = filename; @@ -45,64 +51,113 @@ public class FilePart extends Payload { * @throws InternalError */ protected FilePart(byte[] datagram) throws SizeError, ProtocolError, InternalError { + super(datagram); /* assert to help debugging */ - assert RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]) == RequestResponseCode.LOAD_RESPONSE : "FilePart subclass is incompatible with this datagram, request/response code must be checked before using this constructor"; + assert requestResponseCode == RequestResponseCode.LOAD_RESPONSE : "FilePart subclass is incompatible with this datagram, request/response code must be checked before using this constructor"; /* InternalErrorException */ - if (RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LOAD_RESPONSE) { + if (requestResponseCode != RequestResponseCode.LOAD_RESPONSE) { throw new InternalError(); } setOffset(datagram); // this can throw SizeError - setTotalFileSize(datagram); // this can throw SizeError - int size = getPayloadSize(datagram); // this can throw SizeError - int partialContentStart = setFilename(datagram, size) + 1; // this can throw ProtocolError - setPartialContent(datagram, partialContentStart, size); + setTotalSize(datagram); // this can throw SizeError + setFilename(datagram); // this can throw ProtocolError, SizeError + setPartialContent(datagram); // this can throw SizeError } /** Returns a byte[] containing datagram with padding. * This datagram is still incomplete and should not be send directly. * ProtocolP2PDatagram will use this method to generate the complete datagram. * @return datagram with padding + * @throws InternalError */ - protected byte[] toDatagram() { + protected byte[] toDatagram() throws InternalError { //TODO : calculate payload size - int size = 24 + filename.length + 1; + int size = 24 + filename.length() + 1; byte[] datagram = new byte[size]; // java initialize all to zero // size is keep blank (ProtocolP2PDatagram will handle it) // set request/response code datagram[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; // bits 16-31 are reserved for future use - datagram = setPayloadSize(size, datagram); - //TODO : write offset to datagram - //TODO : write totalFileSize to datagram - //TODO : write filename\n to datagram + setPayloadSize(size, datagram); + // write offset to datagram (Byte 8) + BytesArrayTools.write(datagram, 8, offset); + // write totalSize to datagram (Byte 16) + BytesArrayTools.write(datagram, 16, totalSize); + // write filename’s size to datagram + BytesArrayTools.write(datagram, 24, filename.length()); + //TODO : write filename to datagram + try { + byte[] bFilename = filename.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new InternalError(); + } //TODO : write partialContent to datagram + + return datagram; } /** Write from bytes 8 to 15 of datagram into offset. * @param datagram received datagram * @throws SizeError */ - private setOffset(byte[] datagram) throws SizeError { + private void setOffset(byte[] datagram) throws SizeError { offset = BytesArrayTools.readLong(datagram, 8); } /** Write from bytes 16 to 23 of datagram into totalSize. * @param datagram received datagram * @throws SizeError */ - private setTotalFileSize(byte[] datagram) throw SizeError { + private void setTotalSize(byte[] datagram) throws SizeError { totalSize = BytesArrayTools.readLong(datagram, 16); } - private int setFilename(byte[] datagram, int payloadSize) throws ProtocolError { - // TODO: copy datagram from byte 24 to the first \n (excluded) - // into filename unless we excess size - // (in this case the wrong size has been - // set by the emitter, it is a protocolerror) - // return position of the \n - + /** Read filename’s size from bytes 24 to 28 of datagram. + * @param datagram received datagram + * @throws ProtocolError + * @throws SizeError + * @return filename’s size + */ + private int getFilenameSize(byte[] datagram) throws SizeError, ProtocolError { + int size = BytesArrayTools.readInt(datagram, 24); // this can throw SizeError + // filename size cannot be zero + if (size == 0) { + throw new ProtocolError(); + } + // offset (8B) + totalSize (8B) + filenameSize (4B) = 20B + if ((20 + size) > getPayloadSize(datagram)) { + throw new ProtocolError(); + } + return size; + } + + /** Write filename from byte 24 to byte (24 + (filenameSize - 1)) of datagram. + * @param datagram received datagram + * @throws ProtocolError + * @throws SizeError + * @throws InternalError + */ + private void setFilename(byte[] datagram) throws ProtocolError, SizeError, InternalError { + int filenameSize = getFilenameSize(datagram); // this can throw ProtocolError or SizeError + try { + filename = new String(datagram, 24, filenameSize, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new InternalError(); + } } - private setPartialContent(byte[] datagram, int start, int payloadSize) { - // TODO: copy datagram from start to size into partialContent + + /** Write partialContent from byte (24 + filenameSize) to byte (8 + payloadSize) of datagram. + * @param datagram received datagram + * @throws SizeError + * @throws ProtocolError + */ + private void setPartialContent(byte[] datagram) throws ProtocolError, SizeError { + int start = 24 + getFilenameSize(datagram); // this can throw SizeError or ProtocolError + int end = 8 + getPayloadSize(datagram); // this can throw SizeError + try { + partialContent = Arrays.copyOfRange(datagram, start, end+1); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ProtocolError(); + } } } diff --git a/src/protocolP2P/Payload.java b/src/protocolP2P/Payload.java index 44a1458..ce55f38 100644 --- a/src/protocolP2P/Payload.java +++ b/src/protocolP2P/Payload.java @@ -13,8 +13,8 @@ import tools.BytesArrayTools; * @version 1.0 */ public class Payload { - private RequestResponseCode requestResponseCode; - protected final static int PAYLOAD_SIZE_POSITON = 4; + protected RequestResponseCode requestResponseCode; + protected final static int PAYLOAD_SIZE_POSITION = 4; protected final static int PAYLOAD_START_POSITION = 8; /** Consructor used to create Payload with a payload size of zero using a RRCode. @@ -23,8 +23,8 @@ public class Payload { */ public Payload(RequestResponseCode requestResponseCode) throws InternalError { /* asserts to help debugging */ - assert requestResponseCode != requestResponseCode.LIST_RESPONSE : "LIST_RESPONSE must use FilePart class"; - assert requestResponseCode != requestResponseCode.LOAD_RESPONSE : "LOAD_RESPONSE must use FileList class"; + assert requestResponseCode != RequestResponseCode.LIST_RESPONSE || (this instanceof FileList) : "LIST_RESPONSE must use FilePart class"; + assert requestResponseCode != RequestResponseCode.LOAD_RESPONSE || (this instanceof FilePart) : "LOAD_RESPONSE must use FileList class"; this.requestResponseCode = requestResponseCode; checkRequestResponseCode(); // this can throw InternalError } @@ -37,8 +37,8 @@ public class Payload { */ protected Payload(byte[] datagram) throws ProtocolError, InternalError { /* asserts to help debugging */ - assert RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LIST_RESPONSE : "LIST_RESPONSE must use FilePart class"; - assert RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LOAD_RESPONSE : "LOAD_RESPONSE must use FileList class"; + assert RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LIST_RESPONSE || (this instanceof FileList) : "LIST_RESPONSE must use FilePart class"; + assert RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LOAD_RESPONSE || (this instanceof FilePart) : "LOAD_RESPONSE must use FileList class"; requestResponseCode = RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]); checkRequestResponseCode(); // this can throw InternalError } @@ -48,7 +48,8 @@ public class Payload { */ private void checkRequestResponseCode() throws InternalError { /* Incorrect use cases (use subclasses instead) */ - if (requestResponseCode == RequestResponseCode.LIST_RESPONSE || requestResponseCode == RequestResponseCode.LOAD_RESPONSE) { + if ((requestResponseCode == RequestResponseCode.LIST_RESPONSE && !(this instanceof FileList)) + || (requestResponseCode == RequestResponseCode.LOAD_RESPONSE && !(this instanceof FilePart))) { throw new InternalError(); } } @@ -57,14 +58,17 @@ public class Payload { * This datagram is still incomplete and should not be send directly. * ProtocolP2PDatagram will use this method to generate the complete datagram. * @return datagram with padding + * @throws InternalError */ - protected byte[] toDatagram() { + protected byte[] toDatagram() throws InternalError { + // InternalError is impossible in this method on Payload class but still on subclasses byte [] datagram = new byte[8]; // java initialize all to zero // size is keep blank (ProtocolP2PDatagram will handle it) // set request/response code datagram[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; // bits 16-31 are reserved for future use // payload size is 0 (this is what java have initialized datagram) + return datagram; } /** Set payload’s size in a datagram. @@ -72,7 +76,7 @@ public class Payload { * @param datagram datagram to be completed * @throws InternalError */ - protected static setPayloadSize(int size, byte[] datagram) throws InternalError { + protected static void setPayloadSize(int size, byte[] datagram) throws InternalError { /* assert to help debugging */ assert size >= 0: "Payload size cannot be negative"; if (size < 0) { @@ -80,7 +84,7 @@ public class Payload { // because this is only for reception side throw new InternalError(); } - BytesArrayTools.write(datagram, PAYLOAD_SIZE_POSITON, size); + BytesArrayTools.write(datagram, PAYLOAD_SIZE_POSITION, size); } /** Get payload’s size from a datagram. @@ -89,6 +93,6 @@ public class Payload { * @throws SizeError */ protected static int getPayloadSize(byte[] datagram) throws SizeError { - return BytesArrayTools.readInt(datagram, PAYLOAD_SIZE_POSITON]; + return BytesArrayTools.readInt(datagram, PAYLOAD_SIZE_POSITION); } } diff --git a/src/protocolP2P/ProtocolP2PDatagram.java b/src/protocolP2P/ProtocolP2PDatagram.java index 1846258..a259028 100644 --- a/src/protocolP2P/ProtocolP2PDatagram.java +++ b/src/protocolP2P/ProtocolP2PDatagram.java @@ -1,6 +1,8 @@ package protocolP2P; import exception.ProtocolError; import exception.VersionError; +import exception.SizeError; +import exception.InternalError; import protocolP2P.Payload; import protocolP2P.RequestResponseCode; import java.util.ArrayList; @@ -30,8 +32,9 @@ public class ProtocolP2PDatagram { /** Send datagram on socket * @param socket DatagramSocket used to send datagram + * @throws InternalError */ - public void send(DatagramSocket socket) { + public void send(DatagramSocket socket) throws InternalError { //TODO byte[] datagram = toDatagram(); // generate DatagramPacket @@ -43,31 +46,34 @@ public class ProtocolP2PDatagram { * @return payload of the datagram * @throws ProtocolError * @throws VersionError + * @throws InternalError + * @throws SizeError */ - public static Payload receive(DatagramSocket socket) throws ProtocolError, VersionError { + public static Payload receive(DatagramSocket socket) throws ProtocolError, VersionError, InternalError, SizeError { //TODO // reception - //datagram = + byte[] datagram = new byte[5]; // contruction ProtocolP2PDatagram p = new ProtocolP2PDatagram(datagram); return p.getPayload(); - } /** Private constructor with datagram as byte[] parameter (typically used when receiving datagram). * @param datagram the full datagram received * @throws ProtocolError * @throws VersionError + * @throws InternalError + * @throws SizeError */ - private ProtocolP2PDatagram(byte[] datagram) throws ProtocolError, VersionError, SizeError { + private ProtocolP2PDatagram(byte[] datagram) throws ProtocolError, VersionError, InternalError, SizeError { // unwrap version version = datagram[VERSION_POSITON]; checkProtocolVersion(); // this can throw VersionError RequestResponseCode r = RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]); // this can throw ProtocolError switch (r) { - case RequestResponseCode.LIST_RESPONSE: + case LIST_RESPONSE: payload = (Payload) new FileList(datagram); break; - case RequestResponseCode.LOAD_RESPONSE: + case LOAD_RESPONSE: payload = (Payload) new FilePart(datagram); break; default: @@ -79,11 +85,13 @@ public class ProtocolP2PDatagram { /** Returns a byte[] containing full datagram (typically used when sending datagram). * This datagram is still complete and ready to be send. * @return the full datagram to send + * @throws InternalError */ - private byte[] toDatagram() { + private byte[] toDatagram() throws InternalError { byte[] datagram = payload.toDatagram(); datagram[VERSION_POSITON] = version; return datagram; + } /** Returns Payload associated with the datagram. diff --git a/src/protocolP2P/RequestResponseCode.java b/src/protocolP2P/RequestResponseCode.java index e23def6..81f3cad 100644 --- a/src/protocolP2P/RequestResponseCode.java +++ b/src/protocolP2P/RequestResponseCode.java @@ -51,11 +51,11 @@ public enum RequestResponseCode { * @return enum element */ public static RequestResponseCode fromCode(byte code) throws ProtocolError { - byte code = BY_CODE.get(Byte.valueOf(code)); - if (code == null) { + RequestResponseCode r= BY_CODE.get(Byte.valueOf(code)); + if (r == null) { throw new ProtocolError(); } - return code; + return r; } diff --git a/src/tools/ByteArrayTools.java b/src/tools/BytesArrayTools.java similarity index 92% rename from src/tools/ByteArrayTools.java rename to src/tools/BytesArrayTools.java index 176d562..9547f64 100644 --- a/src/tools/ByteArrayTools.java +++ b/src/tools/BytesArrayTools.java @@ -14,7 +14,7 @@ public class BytesArrayTools { */ public static void write(byte[] array, int start, int value) { for(int i=0;i<4;i++) { - array[start + i] = (byte) ((size >> (8 * (3 - i))) & 0xFF); + array[start + i] = (byte) ((value >> (8 * (3 - i))) & 0xFF); } } /** Write long in a bytearray @@ -24,7 +24,7 @@ public class BytesArrayTools { */ public static void write(byte[] array, int start, long value) { for(int i=0;i<4;i++) { - array[start + i] = (byte) ((size >> (8 * (4 - i))) & 0xFF); + array[start + i] = (byte) ((value >> (8 * (4 - i))) & 0xFF); } }