diff --git a/.gitignore b/.gitignore index 36020d3..4da5402 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,108 @@ .DS_Store *.swp +*.class bin project.geany +P2P_JAVA_PROJECT_SERVER +P2P_JAVA_PROJECT_CLIENT +.classpath +.project + +# Created by https://www.gitignore.io/api/java,eclipse +# Edit at https://www.gitignore.io/?templates=java,eclipse + +### Eclipse ### +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +### Eclipse Patch ### +# Eclipse Core +.project + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Annotation Processing +.apt_generated + +.sts4-cache/ + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# End of https://www.gitignore.io/api/java,eclipse diff --git a/doc/classdiagram.xml b/doc/classdiagram.xml index 04352dc..8c652a6 100644 --- a/doc/classdiagram.xml +++ b/doc/classdiagram.xml @@ -1 +1 @@ -7VrbcqM4EP0aV808eMqAr4+x4+zsrjObGk/tbB4VUEAbgVxCjs18/bZA3CzFOAlOdmp4SaGDJKM+3ac7DT1nEe5/42gTXDMP05498PY957Jn29PxFP5KIMmA4WyWAT4nXgZZJbAmP7ACBwrdEg/HtYmCMSrIpg66LIqwK2oY4pzt6tPuGa3/6gb5WAPWLqI6+p14IlDHsicl/hkTP8h/2Rqr84Uon6xOEgfIY7sK5Cx7zoIzJrKrcL/AVNout0u27uqJu8WDcRyJUxb8FXu3qP9HePHF36HF9nf0fbru22P1cCLJT4w9MIAaMi4C5rMI0WWJzjnbRh6W2w5gVM5ZMbYB0ALwXyxEothEW8EACkRI1V28J+IfufzTSI1uK3cu92rndJDkg0jwRC1yRjlwmwKDwTQHysXpqLb6BnMSYoF5AXoX0kVgGLEIZ8gVoVTdz0wj7fGkyRUUsy131ayEUX8pPPfrjv75bZncPXwmq/5IuS7iPhZH+LBKz4CIwgwelyewkGOKBHmsPwhSvu0X80r64UJ5gNkbjj3lI6Jb9UtrzB/BXoc+Eu9ISFFqsnsWidxdJIOIEj+CaxdsJC09h/WCQEBdqBtCOsncDQj1VihhW2mOWCD3IR/NA8bJD9gW5f4Ct7lQ3gQeW52xlisVXxzHMOcmp8c6gK7RvjZxhWKhAJdRijYxuSuOEQJTJJozIVhYcwcD+/KEeF+BdP7UXXuoxCDJVU6Nd6W2WGOFBRVdybHWKZ9olGtkU5ISHQvOHgoRlDa6h1BZMMp4GUA5+xTfCwP3IfG8VELiDXJJ5H9LBaNvlcgqXXjplMhXZQUnVR6BBMpYkpRQdIfpDYuJIEzuz7O58w0jkUhNNZr3RpcpwsWCRXAIRFL6MLC/w9IDDMQejY5mthW7ubg2kTs9E7dTA7dzCTBYJjMUiT581OiGw4mC7gN6n894Fu11coc6uRJisPaepnIcgJ/gyEB4ndg5GHMhkwhQbC9gbJXjd2PdGZ7Geu4dr6HdnNMn75LTy/w8nBTAbS0b/z9z80zPzcZ5lvOeuXmmBfOCEnnWLje3k5uns3punrx3arYsjfIuNzdEx0+Tmy27S87vQPtbJmcz73rB/YWBiA+uZALuxLwlMR85dTG3nRPVPHeQ1qsya9is5nrRUy2wGmu4sgSrt0fsol3yRAV2QjF11JmbS6fJK0sntfRGKkzJcn90+O90fYes9FOLqq2xg30s6/g+2fm0fVI/KE7zCk3Qa7su0Tfp50+T6W29jFvuXbxJrdXp/Xn03hm/od6baXe6oD41qJ/B99sHtTGb653y5V5A+o6P5fQ7ymRIyU6GijlrrDU2Gl+TFG9GGl+TVKuAoiPz8hLg1H5K8Z7upVWBidRXVgqjg0rBsaxPo5cVCxOrcatz1wu6tGS9oGsUIR+HXVeovcQyPSwxZ3picYYG5bEm58osli4+XWZpCpXXZBYjv2drDI0N7FYaQx7bRZQh78NacLA0IEApjlCIP0rNDdJPHuzBFyZURwH2c/YXqckql3LHYgdKYnFJOHYF44nsOlUmdg2odv3L0IEy68ewhb6ysXYxOVi7jYhTi4nWGwLj2WHbZ/bSNH8o/Iat2kvzR74FMXyO0aV5U5rXQvAkh8sXDJv7heYwdc4VpobXP30Y/p2qNEg0lYzkEj72M9FKk8EK5PwXF+4GZ3jWt1fPLAzO9l7f+Mro16z7zkDvyH6zus/8eKZ+0fykEq1SHW43sjaEi5eUh51itOtShh6k0aUmz08hMCy/HM4KjvLza2f5Hw== \ No newline at end of file +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 \ No newline at end of file diff --git a/doc/protocol.md b/doc/protocol.md new file mode 100644 index 0000000..d5b9171 --- /dev/null +++ b/doc/protocol.md @@ -0,0 +1,82 @@ +# P2P-JAVA-PROJECT version 1 (Protocol for step 1) +All messages begins with `P2P-JAVA-PROJECT VERSION 1.0\n` (this version of the protocol). + +## Client messages +- `LIST\n`: ask the server to list files from server root directory +- `DOWNLOAD\n\n`: ask the server to download file from server root directory. Only one filename is allowed per request. + +## Server responses +- The response to `LIST` request is in the format `LIST\n\n\n[…]\n\n` +- The response to `DOWNLOAD` request is `LOAD\n\n\n` or `NOT FOUND\n` if the file doesn't exists. +- The server send a `PROTOCOL ERROR\n` message if it doesn't understands what the client sent. +- The server send a `INTERNAL ERROR\n` message if it encounters an internal error. + +# 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)] +1 byte: [8-15: REQUEST/RESPONSE CODE] +2 bytes: [16-31: RESERVED FOR FUTURE USE] +4 bytes: [32-63: PAYLOAD SIZE IN BYTES] +x bytes: [64-xx: PAYLOAD] + +``` + +## Requests and responses codes +- REQUESTS (msb is 0): + - `LIST` (0x00) + - `LOAD` (0x01) + +- RESPONSES (msb is 1): + - `LIST` (0x80) + - `LOAD` (0x81) + - `VERSION ERROR` (0xC0) + - `PROTOCOL ERROR` (0xC1) + - `INTERNAL ERROR` (0xC2) + - `EMPTY DIRECTORY` (0xC3) + - `NOT FOUND` (0xC4) + - `EMPTY FILE` (0xC5) + +### List +Payload size for list request is always zero. +Payload for list response is filenames separated by `\n`. Payload size for list response is never zero. + +#### Empty directory +When directory is empty. +Payload size for empty directory is always zero. + + +### Load +#### Not found +Response when the file requested is not found on the server. +Payload size for Not found is zero. + + +#### Load response +Payload contains + +``` +8 bytes: [64-127: OFFSET OF FILE CONTENT IN BYTES] +8 bytes: [128-191: TOTAL FILESIZE] +4 bytes: [192-223: FILENAME SIZE] (cannot be > to PAYLOAD_SIZE - 20 or be zero) +y bytes: [] +z bytes: [FILE CONTENT] +``` + +#### Load request +Payload contains only the name of the file to load. + +### Other response code (errors) +#### Version error +Response when datagram received use wrong version code. + +#### Protocol error +Response when the request cannot be interpreted (but version is correct). +Payload size for Protocol error is zero + +#### Internal error +Response in internal failure case. +Payload size for Internal error is zero. diff --git a/src/clientP2P/ClientManagementUDP.java b/src/clientP2P/ClientManagementUDP.java new file mode 100644 index 0000000..20f8eb8 --- /dev/null +++ b/src/clientP2P/ClientManagementUDP.java @@ -0,0 +1,179 @@ +package clientP2P; +import exception.InternalError; +import exception.ProtocolError; +import exception.SizeError; +import exception.TransmissionError; +import exception.VersionError; +import remoteException.EmptyFile; +import remoteException.EmptyDirectory; +import remoteException.InternalRemoteError; +import remoteException.NotFound; +import remoteException.ProtocolRemoteError; +import remoteException.VersionRemoteError; +import java.net.UnknownHostException; +import java.util.Scanner; +import java.net.InetAddress; +import java.net.DatagramSocket; +import java.net.SocketException; +import java.io.IOException; +import java.nio.file.Files; +import java.io.File; +import protocolP2P.ProtocolP2PDatagram; +import protocolP2P.Payload; +import protocolP2P.RequestResponseCode; +import protocolP2P.FileList; +import protocolP2P.FilePart; +import protocolP2P.LoadRequest; + +/** Implementation of P2P-JAVA-PROJECT CLIENT + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class ClientManagementUDP implements Runnable { + private String baseDirectory; + private int UDPPort; + private String host; + private DatagramSocket socket; + + + + /** Constructor for UDP implementation, with baseDirectory and UDPPort parameters. + * @param baseDirectory the root directory where files are stored + * @param host hostname of the server + * @param UDPPort the server will listen on this port + */ + public ClientManagementUDP(String baseDirectory, String host, int UDPPort) { + this.baseDirectory = baseDirectory; + this.host = host; + this.UDPPort = UDPPort; + try { + socket = new DatagramSocket(); + } catch (SocketException e) { + System.err.println("Error: No socket available."); + System.exit(-1); + } + } + + /** Implementation of Runnable + */ + public void run() { + try { + String[] list = listDirectory(); + System.out.println("Files present on the server:"); + for(String listItem: list) { + System.out.println(listItem); + } + System.out.println("Name of the file to download:"); + Scanner scanner = new Scanner(System.in); + String f = scanner.nextLine(); + download(f); + System.out.println("File sucessfully downloaded"); + } catch (EmptyDirectory e) { + System.err.println("Error: Server has no file in directory"); + } catch (InternalError e) { + System.err.println("Error: Client internal error"); + } catch (UnknownHostException e) { + System.err.println("Error: Server host is unknown"); + } catch (IOException e) { + System.err.println("Error: Request cannot be send or response cannot be received"); + } catch (TransmissionError e) { + System.err.println("Error: Message received is too big"); + } catch (ProtocolError e) { + System.err.println("Error: Cannot decode server’s response"); + } catch (VersionError e) { + System.err.println("Error: Server’s response use bad version of the protocol"); + } catch (SizeError e) { + System.err.println("Error: Cannot handle this packets because of internal representation limitations of numbers on the client"); + } catch (InternalRemoteError e) { + System.err.println("Error: Server internal error"); + } catch (ProtocolRemoteError e) { + System.err.println("Error: Server cannot decode client’s request"); + } catch (VersionRemoteError e) { + System.err.println("Error: Server cannot decode this version of the protocol"); + } catch (NotFound e) { + System.err.println("Error: Server has not this file in directory"); + } catch (EmptyFile e) { + System.err.println("Error: File is empty"); + } + } + + /** Try to download a file + * @param filename name of the file to download + * @throws NotFound + * @throws InternalError + * @throws UnknownHostException + * @throws IOException + * @throws TransmissionError + * @throws ProtocolError + * @throws VersionError + * @throws SizeError + * @throws InternalRemoteError + * @throws ProtocolRemoteError + * @throws VersionRemoteError + * @throws EmptyFile + */ + private void download(String filename) throws EmptyFile, NotFound, InternalError, UnknownHostException, IOException, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError { + ProtocolP2PDatagram d = new ProtocolP2PDatagram((Payload) new LoadRequest(filename)); + d.send(socket, host, UDPPort); + try { + Payload p = ProtocolP2PDatagram.receive(socket).getPayload(); + assert p instanceof FilePart : "This payload must be instance of FilePart"; + if (!(p instanceof FilePart)) { + throw new InternalError(); + } else { + FilePart fp = (FilePart)p; + if (!fp.getFilename().equals(filename)) { + System.err.println("Error: wrong file received"); + throw new ProtocolError(); + } + if (fp.getOffset() != 0 || fp.getPartialContent().length != fp.getTotalSize()) { + System.err.println("offset: " + fp.getOffset() + " ; content.length: " + fp.getPartialContent().length + " ; totalSize: " + fp.getTotalSize()); + System.err.println("Error: cannot handle partial files (not implemented)"); + throw new InternalError(); + } + try { + Files.write(new File(baseDirectory + filename).toPath(), fp.getPartialContent()); + } catch (IOException e) { + System.err.println("Error: cannot write file (" + baseDirectory + filename + ")"); + } + } + } catch (EmptyDirectory e) { + throw new ProtocolError(); + } + + } + + /** list server’s directory content + * @return list of files + * @throws InternalError + * @throws UnknowHostException + * @throws IOException + * @throws TransmissionError + * @throws ProtocolError + * @throws VersionError + * @throws SizeError + * @throws EmptyDirectory + * @throws InternalRemoteError + * @throws ProtocolRemoteError + * @throws VersionRemoteError + */ + private String[] listDirectory() throws EmptyDirectory, InternalError, UnknownHostException, IOException, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError { + ProtocolP2PDatagram d = new ProtocolP2PDatagram(new Payload(RequestResponseCode.LIST_REQUEST)); + d.send(socket, host, UDPPort); + try { + Payload p = ProtocolP2PDatagram.receive(socket).getPayload(); + assert p instanceof FileList : "This payload must be instance of Filelist"; + if (!(p instanceof FileList)) { + throw new InternalError(); + } else { + return ((FileList)p).getFileList(); + } + } catch (NotFound e) { + throw new ProtocolError(); + } catch (EmptyFile e) { + throw new ProtocolError(); + } + } +} diff --git a/src/clientP2P/ClientP2P.java b/src/clientP2P/ClientP2P.java new file mode 100644 index 0000000..8ee0186 --- /dev/null +++ b/src/clientP2P/ClientP2P.java @@ -0,0 +1,25 @@ +package clientP2P; +import clientP2P.ClientManagementUDP; +import tools.Directories; + + +public class ClientP2P { + private String host; + private int port; + private Directories directories; + public ClientP2P() { + directories = new Directories("P2P_JAVA_PROJECT_CLIENT"); + host = "localhost"; + port = 40001; + System.out.println("Client will try to contact server at " + host + " on port " + port + ". It will save files in " + directories.getDataHomeDirectory()); + directories.askOpenDataHomeDirectory(); + } + + public static void main(String [] args) { + ClientP2P c = new ClientP2P(); + ClientManagementUDP cm = new ClientManagementUDP(c.directories.getDataHomeDirectory(), c.host, c.port); + Thread t = new Thread(cm); + t.setName("client P2P-JAVA-PROJECT"); + t.start(); + } +} diff --git a/src/exception/InternalError.java b/src/exception/InternalError.java new file mode 100644 index 0000000..9a20633 --- /dev/null +++ b/src/exception/InternalError.java @@ -0,0 +1,4 @@ +package exception; +public class InternalError extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/exception/ProtocolError.java b/src/exception/ProtocolError.java new file mode 100644 index 0000000..d5e4811 --- /dev/null +++ b/src/exception/ProtocolError.java @@ -0,0 +1,4 @@ +package exception; +public class ProtocolError extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/exception/SizeError.java b/src/exception/SizeError.java new file mode 100644 index 0000000..d0cb07d --- /dev/null +++ b/src/exception/SizeError.java @@ -0,0 +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 { + private static final long serialVersionUID = 11L; +} diff --git a/src/exception/TransmissionError.java b/src/exception/TransmissionError.java new file mode 100644 index 0000000..d95dd4c --- /dev/null +++ b/src/exception/TransmissionError.java @@ -0,0 +1,4 @@ +package exception; +public class TransmissionError extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/exception/VersionError.java b/src/exception/VersionError.java new file mode 100644 index 0000000..f84c811 --- /dev/null +++ b/src/exception/VersionError.java @@ -0,0 +1,4 @@ +package exception; +public class VersionError extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/protocolP2P/CodeType.java b/src/protocolP2P/CodeType.java new file mode 100644 index 0000000..e38de49 --- /dev/null +++ b/src/protocolP2P/CodeType.java @@ -0,0 +1,13 @@ +package protocolP2P; + +/** Request/Response code's type enum. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public enum CodeType { + REQUEST, + RESPONSE, + ERROR +} diff --git a/src/protocolP2P/FileList.java b/src/protocolP2P/FileList.java new file mode 100644 index 0000000..1dc0887 --- /dev/null +++ b/src/protocolP2P/FileList.java @@ -0,0 +1,109 @@ +package protocolP2P; +import java.util.Arrays; +import protocolP2P.Payload; +import protocolP2P.RequestResponseCode; +import exception.TransmissionError; +import exception.ProtocolError; +import exception.InternalError; +import exception.SizeError; +import java.io.UnsupportedEncodingException; + +/** Representation of payload for list response. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class FileList extends Payload { + private String[] fileList; + + /** Constructor (typically used by the server) with an ArrayList parameter containing + * filenames. + * @param fileList a list of files. Must not be empty. + * @throws InternalError + */ + public FileList(String[] fileList) throws InternalError { + super(RequestResponseCode.LIST_RESPONSE); + /* assert to help debugging */ + assert fileList.length != 0 : "Payload size of FileList must not be empty, use EmptyDirectory from Payload instead"; + if (fileList.length == 0) { + throw new InternalError(); + } + this.fileList = fileList; + } + + /** Constructor (typically used by client) with a byte[] parameter containing the datagram received. + * @param datagram the full datagram received + * @throws SizeError + * @throws InternalError + * @throws ProtocolError + * @throws TransmissionError + */ + protected FileList(byte[] datagram) throws TransmissionError, SizeError, ProtocolError, InternalError { + super(datagram); + /* assert to help debugging */ + 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!= RequestResponseCode.LIST_RESPONSE) { + throw new InternalError(); + } + int size = getPayloadSize(datagram); + try { + fileList = (new String(datagram, 8, size, "UTF-8")).split("\n"); + } catch (UnsupportedEncodingException e) { + throw new InternalError(); + } + } + + /** 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() throws InternalError { + // compute size + int size = 8; + for (String s : fileList) { + size += s.length(); + size += 1; + } + size -=1; + byte[] datagram = new byte[size]; // java initialize all to zero + // set request/response code + datagram[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; + // bits 16-31 are reserved for future use + setPayloadSize(size - 8, datagram); + // Write fileList + int bCount = 8; + for(String s : fileList) { + if (bCount != 8) { // not on first iteration + try { + datagram[bCount] = "\n".getBytes("UTF-8")[0]; // separator + } catch (UnsupportedEncodingException e) { + throw new InternalError(); + } + bCount += 1; + } + // Copy filename + try { + byte[] sb = s.getBytes("UTF-8"); + for(byte b : sb) { + datagram[bCount] = b; + bCount += 1; + } + } catch (UnsupportedEncodingException e) { + throw new InternalError(); + } + + } + return datagram; + } + + /** fileList getter. + * @return fileList + */ + public String[] getFileList() { + return fileList; + } +} diff --git a/src/protocolP2P/FilePart.java b/src/protocolP2P/FilePart.java new file mode 100644 index 0000000..f8b35b3 --- /dev/null +++ b/src/protocolP2P/FilePart.java @@ -0,0 +1,199 @@ +package protocolP2P; +import protocolP2P.Payload; +import protocolP2P.RequestResponseCode; +import exception.ProtocolError; +import exception.InternalError; +import exception.SizeError; +import exception.TransmissionError; +import tools.BytesArrayTools; +import java.util.Arrays; +import java.io.UnsupportedEncodingException; + +/** Representation of payload for load response. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class FilePart extends Payload { + private String filename; + private long totalSize; + private long offset; + private byte[] partialContent; + + /** Constructor (typically used by server) with informations about file part to send as parameters. + * @param filename name of the file to send + * @param totalSize total size of the file to send + * @param offset where in the file begins the part we are sending + * @param partialContent content of the file we send + * @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, see RRCode.EMPTY_FILE"; + assert totalSize >= partialContent.length : "totalSize must be greater than partialContent.length"; + assert offset >= 0 : "offset cannot be negative"; + 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; + this.totalSize = totalSize; + this.offset = offset; + this.partialContent = partialContent; + } + + /** Constructor (typically used by client) with datagram received as parameter. + * @param datagram the full datagram received + * @throws SizeError + * @throws InternalError + * @throws TransmissionError + */ + protected FilePart(byte[] datagram) throws TransmissionError, SizeError, ProtocolError, InternalError { + super(datagram); + /* assert to help debugging */ + 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 != RequestResponseCode.LOAD_RESPONSE) { + throw new InternalError(); + } + setOffset(datagram); // this can throw SizeError + 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() throws InternalError { + // compute payload size + int size = 28 + filename.length() + partialContent.length; + byte[] datagram = new byte[size]; // java initialize all to zero + // set request/response code + datagram[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; + // bits 16-31 are reserved for future use + setPayloadSize(size - 8, 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()); + // write filename to datagram + try { + byte[] bFilename = filename.getBytes("UTF-8"); + int i = filename.length() + 24; + for (byte b : bFilename) { + datagram[i] = b; + i += 1; + } + // write partialContent to datagram + for (byte b: partialContent) { + datagram[i] = b; + i += 1; + } + return datagram; + } catch (UnsupportedEncodingException e) { + throw new InternalError(); + } + } + + /** Write from bytes 8 to 15 of datagram into offset. + * @param datagram received 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 void setTotalSize(byte[] datagram) throws SizeError { + totalSize = BytesArrayTools.readLong(datagram, 16); + } + + /** Read filename’s size from bytes 24 to 27 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 28 to byte (28 + (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, 28, filenameSize, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new InternalError(); + } + } + + /** Write partialContent from byte (28 + 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 = 28 + getFilenameSize(datagram); // this can throw SizeError or ProtocolError + int end = 8 + getPayloadSize(datagram); // this can throw SizeError + try { + partialContent = Arrays.copyOfRange(datagram, start, end); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ProtocolError(); + } + } + + /** partialContent getter. + * @return partialcontent + */ + public byte[] getPartialContent() { + return partialContent; + } + + /** filename getter. + * @return String + */ + public String getFilename() { + return filename; + } + + /** offset getter. + * @return offset + */ + public long getOffset() { + return offset; + } + + /** totalSize getter. + * @return totalSize + */ + public long getTotalSize() { + return totalSize; + } +} diff --git a/src/protocolP2P/LoadRequest.java b/src/protocolP2P/LoadRequest.java new file mode 100644 index 0000000..af3082f --- /dev/null +++ b/src/protocolP2P/LoadRequest.java @@ -0,0 +1,90 @@ +package protocolP2P; +import protocolP2P.Payload; +import protocolP2P.RequestResponseCode; +import exception.TransmissionError; +import exception.ProtocolError; +import exception.InternalError; +import exception.SizeError; +import java.io.UnsupportedEncodingException; + +/** Representation of payload for load request. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class LoadRequest extends Payload { + private String filename; + + /** Constructor (typically used by the server) with a filename parameter. + * @param filename name of the file to download. Must not be empty. + * @throws InternalError + */ + public LoadRequest(String filename) throws InternalError { + super(RequestResponseCode.LOAD_REQUEST); + /* assert to help debugging */ + assert filename.length() != 0 : "Payload size of LoadRequest must not be empty"; + if (filename.length() == 0) { + throw new InternalError(); + } + this.filename = filename; + } + + /** Constructor (typically used by client) with a byte[] parameter containing the datagram received. + * @param datagram the full datagram received + * @throws SizeError + * @throws InternalError + * @throws ProtocolError + * @throws TransmissionError + */ + protected LoadRequest(byte[] datagram) throws TransmissionError, SizeError, ProtocolError, InternalError { + super(datagram); + /* assert to help debugging */ + assert requestResponseCode == RequestResponseCode.LOAD_REQUEST : "LoadRequest subclass is incompatible with this datagram, request/response code must be checked before using this constructor"; + /* InternalErrorException */ + if (requestResponseCode!= RequestResponseCode.LOAD_REQUEST) { + throw new InternalError(); + } + int size = getPayloadSize(datagram); + try { + filename = new String(datagram, 8, size, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new InternalError(); + } + } + + /** 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() throws InternalError { + // compute size + int size = 8 + filename.length(); + byte[] datagram = new byte[size]; // java initialize all to zero + // set request/response code + datagram[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; + // bits 16-31 are reserved for future use + setPayloadSize(size - 8, datagram); + // Write filename + int bCount = 8; + try { + byte[] sb = filename.getBytes("UTF-8"); + for(byte b : sb) { + datagram[bCount] = b; + bCount += 1; + } + } catch (UnsupportedEncodingException e) { + throw new InternalError(); + } + return datagram; + } + + /** filename getter. + * @return filename + */ + public String getFilename() { + return filename; + } +} diff --git a/src/protocolP2P/Payload.java b/src/protocolP2P/Payload.java new file mode 100644 index 0000000..c3638f6 --- /dev/null +++ b/src/protocolP2P/Payload.java @@ -0,0 +1,116 @@ +package protocolP2P; +import protocolP2P.RequestResponseCode; +import protocolP2P.FilePart; +import protocolP2P.FileList; +import protocolP2P.LoadRequest; +import exception.ProtocolError; +import exception.InternalError; +import exception.TransmissionError; +import exception.SizeError; +import tools.BytesArrayTools; +/** Representation of payload. If payload has a size, use subclasses instead. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class Payload { + 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. + * @param requestResponseCode Request/Response code associated with the payload + * @throws InternalError + */ + public Payload(RequestResponseCode requestResponseCode) throws InternalError { + /* asserts to help debugging */ + 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"; + assert requestResponseCode != RequestResponseCode.LOAD_REQUEST || (this instanceof LoadRequest) : "LOAD_REQUEST must use LoadRequest class"; + this.requestResponseCode = requestResponseCode; + checkRequestResponseCode(); // this can throw InternalError + } + + /** Constructor used to create a Payload (when no more specific subclasses exists) using datagram as parameter. + * If payload size is not empty, using subclass is required. + * @param datagram the full datagram received + * @throws ProtocolError + * @throws InternalError + * @throws TransmissionError + * @throws SizeError + */ + protected Payload(byte[] datagram) throws SizeError, ProtocolError, InternalError, TransmissionError { + /* asserts to help debugging */ + assert getPayloadSize(datagram) + 8 <= datagram.length : "Payload is truncated"; + if (datagram.length < getPayloadSize(datagram) + 8) { + throw new TransmissionError(); + } + 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"; + assert RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LOAD_REQUEST || (this instanceof LoadRequest) : "LOAD_REQUEST must use LoadRequest class"; + requestResponseCode = RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]); + checkRequestResponseCode(); // this can throw InternalError + } + + /** Used to check RRCode used is compatible with this class use, or if a more specific subclass is required. + * @throws InternalError + */ + private void checkRequestResponseCode() throws InternalError { + /* Incorrect use cases (use subclasses instead) */ + if ((requestResponseCode == RequestResponseCode.LIST_RESPONSE && !(this instanceof FileList)) + || (requestResponseCode == RequestResponseCode.LOAD_RESPONSE && !(this instanceof FilePart)) + || (requestResponseCode == RequestResponseCode.LOAD_REQUEST && !(this instanceof LoadRequest))) { + throw new InternalError(); + } + } + + /** 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() 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 zero (and this is the default) + // 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. + * @param size integer representing payload size + * @param datagram datagram to be completed + * @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) { + // We don't throw SizeError + // because this is only for reception side + throw new InternalError(); + } + BytesArrayTools.write(datagram, PAYLOAD_SIZE_POSITION, size); + } + + /** Get payload’s size from a datagram. + * @param datagram the full datagram received + * @return integer representing payload size + * @throws SizeError + */ + protected static int getPayloadSize(byte[] datagram) throws SizeError { + return BytesArrayTools.readInt(datagram, PAYLOAD_SIZE_POSITION); + } + + /** RRCode getter. + * @return Request/Response code + */ + public RequestResponseCode getRequestResponseCode() { + return requestResponseCode; + } +} diff --git a/src/protocolP2P/ProtocolP2PDatagram.java b/src/protocolP2P/ProtocolP2PDatagram.java new file mode 100644 index 0000000..1c31073 --- /dev/null +++ b/src/protocolP2P/ProtocolP2PDatagram.java @@ -0,0 +1,243 @@ +package protocolP2P; +import exception.InternalError; +import exception.ProtocolError; +import exception.SizeError; +import exception.TransmissionError; +import exception.VersionError; +import remoteException.EmptyDirectory; +import remoteException.InternalRemoteError; +import remoteException.NotFound; +import remoteException.ProtocolRemoteError; +import remoteException.VersionRemoteError; +import remoteException.EmptyFile; +import protocolP2P.Payload; +import protocolP2P.RequestResponseCode; +import protocolP2P.LoadRequest; +import protocolP2P.FileList; +import protocolP2P.FilePart; +import java.util.ArrayList; +import java.lang.Byte; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.io.IOException; +import java.net.UnknownHostException; + +/** Representation of datagram. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class ProtocolP2PDatagram { + private final static byte PROTOCOL_VERSION = 0x11; + private final static int VERSION_POSITON = 0; + private byte version; + private Payload payload; + private InetAddress hostR; + private int portR; + + /** Constructor with payload parameter (typically used when sending datagram). + * @param payload the payload associated with the datagram to send + */ + public ProtocolP2PDatagram(Payload payload) { + version = PROTOCOL_VERSION; + this.payload = payload; + } + + /** Send datagram on socket (from client) + * @param socket DatagramSocket used to send datagram. + * @param host host to send datagram (null if this is a response) + * @param port port to send datagram (null if this is a response) + * @throws InternalError + * @throws UnknownHostException + * @throws IOException + */ + public void send(DatagramSocket socket, String host, int port) throws InternalError, UnknownHostException, IOException { + InetAddress dst = InetAddress.getByName(host); + byte[] datagram = toDatagram(); + // generate DatagramPacket + DatagramPacket datagramPacket = new DatagramPacket(datagram, datagram.length, dst, port); + // send it + socket.send(datagramPacket); + } + + /** Send datagram on socket (from server, as a response) + * @param socket DatagramSocket used to send datagram. + * @param received datagram to respond (aka request) + * @throws InternalError + * @throws IOException + */ + public void send(DatagramSocket socket, ProtocolP2PDatagram received) throws InternalError, IOException { + assert received.getPortR() != 0 && received.getHostR() != null : "This method should be used only as response to a request"; + if (received.getPortR() == 0 || received.getHostR() == null) { + throw new InternalError(); + } + byte[] datagram = toDatagram(); + // generate DatagramPacket + DatagramPacket datagramPacket = new DatagramPacket(datagram, datagram.length, received.getHostR(), received.getPortR()); + socket.send(datagramPacket); + } + + /** Send a response. + * @param socket DatagramSocket used to send response + * @param host host to send response + * @param port port to send response + * @throws InternalError + * @throws IOException + */ + protected void sendResponse(DatagramSocket socket, InetAddress host, int port) throws InternalError, IOException { + assert port != 0 && host != null : "This method should be used only as response to a request"; + if (port == 0 || host == null) { + throw new InternalError(); + } + byte[] datagram = toDatagram(); + DatagramPacket datagramPacket = new DatagramPacket(datagram, datagram.length, host, port); + socket.send(datagramPacket); + } + + /** Receive datagram on socket + * @param socket DatagramSocket used to receive datagram + * @throws TransmissionError + * @throws ProtocolError + * @throws VersionError + * @throws InternalError + * @throws SizeError + * @throws ProtocolRemoteError + * @throws VersionRemoteError + * @throws InternalRemoteError + * @throws EmptyDirectory + * @throws NotFound + * @throws IOException + * @throws EmptyFile + */ + public static ProtocolP2PDatagram receive(DatagramSocket socket) throws EmptyFile, NotFound, EmptyDirectory, InternalRemoteError, VersionRemoteError, ProtocolRemoteError, TransmissionError, ProtocolError, VersionError, InternalError, SizeError, IOException { + // reception + byte[] datagram = new byte[4096]; + DatagramPacket reception = new DatagramPacket(datagram, datagram.length); + socket.receive(reception); + // contruction + try { + ProtocolP2PDatagram p = new ProtocolP2PDatagram(datagram); + p.setHostR(reception.getAddress()); + p.setPortR(reception.getPort()); + Payload payload = p.getPayload(); + switch (payload.getRequestResponseCode()) { + case PROTOCOL_ERROR : + throw new ProtocolRemoteError(); + case VERSION_ERROR : + throw new VersionRemoteError(); + case INTERNAL_ERROR : + throw new InternalRemoteError(); + case EMPTY_DIRECTORY : + throw new EmptyDirectory(); + case NOT_FOUND : + throw new NotFound(); + case EMPTY_FILE: + throw new EmptyFile(); + default : + return p; + } + } catch (TransmissionError e) { + (new ProtocolP2PDatagram(new Payload(RequestResponseCode.INTERNAL_ERROR))).sendResponse(socket, reception.getAddress(), reception.getPort()); + throw e; + } catch (ProtocolError e) { + (new ProtocolP2PDatagram(new Payload(RequestResponseCode.PROTOCOL_ERROR))).sendResponse(socket, reception.getAddress(), reception.getPort()); + throw e; + } catch (VersionError e) { + (new ProtocolP2PDatagram(new Payload(RequestResponseCode.VERSION_ERROR))).sendResponse(socket, reception.getAddress(), reception.getPort()); + throw e; + } catch (InternalError e) { + (new ProtocolP2PDatagram(new Payload(RequestResponseCode.INTERNAL_ERROR))).sendResponse(socket, reception.getAddress(), reception.getPort()); + throw e; + } catch (SizeError e) { + (new ProtocolP2PDatagram(new Payload(RequestResponseCode.INTERNAL_ERROR))).sendResponse(socket, reception.getAddress(), reception.getPort()); + throw e; + } + } + /** Private constructor with datagram as byte[] parameter (typically used when receiving datagram). + * @param datagram the full datagram received + * @throws TransmissionError + * @throws ProtocolError + * @throws VersionError + * @throws InternalError + * @throws SizeError + */ + private ProtocolP2PDatagram(byte[] datagram) throws TransmissionError, 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 LIST_RESPONSE: + payload = (Payload) new FileList(datagram); + break; + case LOAD_RESPONSE: + payload = (Payload) new FilePart(datagram); + break; + case LOAD_REQUEST: + payload = (Payload) new LoadRequest(datagram); + break; + default: + payload = new Payload(datagram); // this can throw TransmissionError + break; + } + } + + /** 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 + */ + protected byte[] toDatagram() throws InternalError { + byte[] datagram = payload.toDatagram(); + datagram[VERSION_POSITON] = version; + return datagram; + + } + + /** Returns Payload associated with the datagram. + * @return payload associated with the datagram + */ + public Payload getPayload() { + return payload; + } + + /** Used to check protocol version when a datagram is constructed from bytes[]. + * @throws VersionError + */ + private void checkProtocolVersion() throws VersionError { + if (PROTOCOL_VERSION != version) { + throw new VersionError(); + } + } + + /** portR getter. + * @return portR + */ + protected int getPortR() { + return portR; + } + + /** portR setter. + * @param portR portR + */ + protected void setPortR(int portR) { + this.portR = portR; + } + + /** hostR getter. + * @return hostR + */ + protected InetAddress getHostR() { + return hostR; + } + + /** hostH setter. + * @param hostR hostR + */ + protected void setHostR(InetAddress hostR) { + this.hostR = hostR; + } +} + diff --git a/src/protocolP2P/RequestResponseCode.java b/src/protocolP2P/RequestResponseCode.java new file mode 100644 index 0000000..8b4e20f --- /dev/null +++ b/src/protocolP2P/RequestResponseCode.java @@ -0,0 +1,65 @@ +package protocolP2P; +import protocolP2P.CodeType; +import exception.ProtocolError; +import java.util.HashMap; +import java.util.Map; +import java.lang.Byte; + +/** Request/Response code enum. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public enum RequestResponseCode { + LIST_REQUEST(CodeType.REQUEST, (byte)0x00), + LOAD_REQUEST(CodeType.REQUEST, (byte)0x01), + LIST_RESPONSE(CodeType.RESPONSE, (byte)0x80), + LOAD_RESPONSE(CodeType.RESPONSE, (byte)0x81), + VERSION_ERROR(CodeType.ERROR, (byte)0xC0), + PROTOCOL_ERROR(CodeType.ERROR, (byte)0xC1), + INTERNAL_ERROR(CodeType.ERROR, (byte)0xC2), + EMPTY_DIRECTORY(CodeType.ERROR, (byte)0xC3), + NOT_FOUND(CodeType.ERROR, (byte)0xC4), + EMPTY_FILE(CodeType.ERROR, (byte)0xC5); + + public final CodeType codeType; + public final byte codeValue; + protected final static int RRCODE_POSITION = 1; + + /* To be able to convert code to enum */ + private static final Map BY_CODE = new HashMap<>(); + /* Initialization of HashMap */ + static { + for (RequestResponseCode r: values()) { + assert !BY_CODE.containsKey(Byte.valueOf(r.codeValue)) : "Duplicate in " + RequestResponseCode.class.getCanonicalName(); + BY_CODE.put(Byte.valueOf(r.codeValue), r); + } + } + + /** Private constructor + * @param codeType type of code (request or response) + * @param codeValue value of the element in datagram + * @return enum element + */ + private RequestResponseCode(CodeType codeType, byte codeValue) { + this.codeType = codeType; + this.codeValue = codeValue; + } + + /** Gives enum from datagram code. + * @param code value of the element in datagram + * @return enum element + */ + protected static RequestResponseCode fromCode(byte code) throws ProtocolError { + RequestResponseCode r= BY_CODE.get(Byte.valueOf(code)); + if (r == null) { + throw new ProtocolError(); + } + return r; + } + + +} + + diff --git a/src/remoteException/EmptyDirectory.java b/src/remoteException/EmptyDirectory.java new file mode 100644 index 0000000..39146fd --- /dev/null +++ b/src/remoteException/EmptyDirectory.java @@ -0,0 +1,4 @@ +package remoteException; +public class EmptyDirectory extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/remoteException/EmptyFile.java b/src/remoteException/EmptyFile.java new file mode 100644 index 0000000..a05cdd7 --- /dev/null +++ b/src/remoteException/EmptyFile.java @@ -0,0 +1,4 @@ +package remoteException; +public class EmptyFile extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/remoteException/InternalRemoteError.java b/src/remoteException/InternalRemoteError.java new file mode 100644 index 0000000..9ca91ea --- /dev/null +++ b/src/remoteException/InternalRemoteError.java @@ -0,0 +1,4 @@ +package remoteException; +public class InternalRemoteError extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/remoteException/NotFound.java b/src/remoteException/NotFound.java new file mode 100644 index 0000000..40c9507 --- /dev/null +++ b/src/remoteException/NotFound.java @@ -0,0 +1,4 @@ +package remoteException; +public class NotFound extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/remoteException/ProtocolRemoteError.java b/src/remoteException/ProtocolRemoteError.java new file mode 100644 index 0000000..6199f8f --- /dev/null +++ b/src/remoteException/ProtocolRemoteError.java @@ -0,0 +1,4 @@ +package remoteException; +public class ProtocolRemoteError extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/remoteException/VersionRemoteError.java b/src/remoteException/VersionRemoteError.java new file mode 100644 index 0000000..4c372c9 --- /dev/null +++ b/src/remoteException/VersionRemoteError.java @@ -0,0 +1,4 @@ +package remoteException; +public class VersionRemoteError extends Exception { + private static final long serialVersionUID = 11L; +} diff --git a/src/serverP2P/ServerManagementUDP.java b/src/serverP2P/ServerManagementUDP.java new file mode 100644 index 0000000..b7e1cbb --- /dev/null +++ b/src/serverP2P/ServerManagementUDP.java @@ -0,0 +1,160 @@ +package serverP2P; +import java.util.Vector; +import java.io.File; +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.SocketException; +import java.nio.file.Paths; +import java.nio.file.Files; +import protocolP2P.ProtocolP2PDatagram; +import protocolP2P.RequestResponseCode; +import protocolP2P.Payload; +import protocolP2P.LoadRequest; +import protocolP2P.FileList; +import protocolP2P.FilePart; +import exception.InternalError; +import exception.ProtocolError; +import exception.SizeError; +import exception.TransmissionError; +import exception.VersionError; +import remoteException.EmptyDirectory; +import remoteException.InternalRemoteError; +import remoteException.NotFound; +import remoteException.ProtocolRemoteError; +import remoteException.VersionRemoteError; +import remoteException.EmptyFile; +import java.util.Arrays; + + +/** Implementation of P2P-JAVA-PROJECT VERSION 1.0 protocol for UDP. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class ServerManagementUDP implements Runnable { + + private String[] fileList; + private String baseDirectory; + private int UDPPort; + private DatagramSocket socket; + + /** Constructor for UDP implementation, with baseDirectory and UDPPort parameters. + * @param baseDirectory the root directory where files are stored + * @param UDPPort the server will listen on this port + */ + public ServerManagementUDP(String baseDirectory, int UDPPort) { + this.baseDirectory = baseDirectory; + this.UDPPort = UDPPort; + initFileList(); + try { + socket = new DatagramSocket(UDPPort); + } catch (SocketException e) { + System.err.println("Error: cannot listen on port " + UDPPort); + System.exit(-1); + } + } + + /** Implementation of runnable. This methods allows to run the server. + */ + public void run() { + while(true) { + try { + ProtocolP2PDatagram pd = ProtocolP2PDatagram.receive(socket); + Payload p = pd.getPayload(); + switch (p.getRequestResponseCode()) { + case LOAD_REQUEST: + System.out.println("Received LOAD_REQUEST"); + assert p instanceof LoadRequest : "payload must be an instance of LoadRequest"; + if (!(p instanceof LoadRequest)) { + sendInternalError(pd); + } else { + String filename = ((LoadRequest)p).getFilename(); + try { + byte[] load = Files.readAllBytes(Paths.get(baseDirectory + filename)); + if (Arrays.binarySearch(fileList, filename) >= 0) { + try { + if (load.length == 0) { + (new ProtocolP2PDatagram(new Payload(RequestResponseCode.EMPTY_FILE))).send(socket, pd); + } else { + (new ProtocolP2PDatagram((Payload)(new FilePart(filename, load.length, 0, load)))).send(socket, pd); + } + } catch (Exception e2) { + System.err.println(e2); + } + } else { + throw new IOException(); // to send a NOT_FOUND in the catch block + } + } catch (IOException e) { + try { + (new ProtocolP2PDatagram(new Payload(RequestResponseCode.NOT_FOUND))).send(socket, pd); + } catch (Exception e2) { + System.err.println(e2); + } + } + } + break; + case LIST_REQUEST: + System.out.println("Received LIST_REQUEST"); + try { + if (fileList.length == 0) { + System.err.println("Sending EMPTY_DIRECTORY"); + (new ProtocolP2PDatagram(new Payload(RequestResponseCode.EMPTY_DIRECTORY))).send(socket, pd); + } else { + System.out.println("Sending LIST_RESPONSE"); + (new ProtocolP2PDatagram((Payload)(new FileList(fileList)))).send(socket, pd); + } + } catch (Exception e2) { + System.err.println(e2); + } + break; + default: + sendInternalError(pd); + } + } catch (NotFound e) { + } catch (EmptyDirectory e) { + } catch (InternalRemoteError e) { + } catch (VersionRemoteError e) { + } catch (ProtocolRemoteError e) { + } catch (IOException e) { + } catch (TransmissionError e) { + } catch (ProtocolError e) { + } catch (VersionError e) { + } catch (InternalError e) { + } catch (SizeError e) { + } catch (EmptyFile e) { + } + } + } + + /** Initialize local list of all files allowed to be shared. + */ + private void initFileList() { + File folder = new File(baseDirectory); + Vector v = new Vector(); + File[] files = folder.listFiles(); + /* Add non-recursively files's names to fileList */ + for (File f : files) { + if (f.isFile()) { + v.add(f.getName()); + } + } + fileList = new String[v.size()]; + v.toArray(fileList); + } + + + + /** Send an internal error message. + * @param pd ProtocolP2PDatagram to respond + */ + private void sendInternalError(ProtocolP2PDatagram pd) { + try { + (new ProtocolP2PDatagram(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(socket, pd); + } catch (Exception e) { + System.err.println(e); + } + } + +} + diff --git a/src/serverP2P/ServerP2P.java b/src/serverP2P/ServerP2P.java new file mode 100644 index 0000000..b22c715 --- /dev/null +++ b/src/serverP2P/ServerP2P.java @@ -0,0 +1,23 @@ +package serverP2P; +import serverP2P.ServerManagementUDP; +import tools.Directories; + +public class ServerP2P { + private int port; + private Directories directories; + + public ServerP2P() { + directories = new Directories("P2P_JAVA_PROJECT_SERVER"); + port = 40001; + System.out.println("Server will listen on port " + port + " and serve files from " + directories.getDataHomeDirectory()); + directories.askOpenDataHomeDirectory(); + } + public static void main(String [] args) { + ServerP2P s = new ServerP2P(); + ServerManagementUDP sm = new ServerManagementUDP(s.directories.getDataHomeDirectory(), s.port); + Thread t = new Thread(sm); + t.setName("server P2P-JAVA-PROJECT"); + t.start(); + } + +} diff --git a/src/tools/BytesArrayTools.java b/src/tools/BytesArrayTools.java new file mode 100644 index 0000000..3813349 --- /dev/null +++ b/src/tools/BytesArrayTools.java @@ -0,0 +1,66 @@ +package tools; +import exception.SizeError; +/** Helper to manipulate byte[]. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class BytesArrayTools { + /** Write int in a bytearray + * @param array the array to write + * @param start where to begin writting + * @param value int to write + */ + public static void write(byte[] array, int start, int value) { + for(int i=0;i<4;i++) { + array[start + i] = (byte) ((value >> (8 * (3 - i))) & 0xFF); + } + } + /** Write long in a bytearray + * @param array the array to write + * @param start where to begin writting + * @param value long to write + */ + public static void write(byte[] array, int start, long value) { + for(int i=0;i<8;i++) { + array[start + i] = (byte) ((value >> (8 * (7 - i))) & 0xFF); + } + } + + /** Read int from a bytearray + * @param array the array to read + * @param start where to begin reading + * @return value read as int + */ + public static int readInt(byte[] array, int start) throws SizeError { + int size = 0; + for(int i=0;i<4;i++) { + size |= ((int)array[start + i]) << (8* (3 -i)); + } + if (size < 0) { + // Size in array is probably correct + // but we cannot store it into a int (or this will be negative) + throw new SizeError(); + } + return size; + } + /** Read long from a bytearray + * @param array the array to read + * @param start where to begin reading + * @return value read as long + */ + public static long readLong(byte[] array, int start) throws SizeError { + long size = 0; + for(int i=0;i<8;i++) { + size |= ((int)array[start + i]) << (8* (7 - i)); + } + if (size < 0) { + // Size in array is probably correct + // but we cannot store it into a int (or this will be negative) + throw new SizeError(); + } + return size; + } + +} diff --git a/src/tools/Directories.java b/src/tools/Directories.java new file mode 100644 index 0000000..da6363f --- /dev/null +++ b/src/tools/Directories.java @@ -0,0 +1,89 @@ +package tools; +import java.util.Scanner; +import java.io.File; +import java.lang.Runtime; +import java.io.IOException; + + +/** Helper to get application directories. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class Directories { + private String projectName; + private String dataHomeDirectory; + private String os; + + /** Constructor with projectName parameter. + * @param projectName name of the project + */ + public Directories(String projectName) { + this.projectName = projectName; + os = System.getProperty("os.name"); + setDataHomeDirectory(); + } + + /** Setter for dataHomeDirectory. Will create the directory if not already exists. + */ + private void setDataHomeDirectory() { + /* Follow XDG Base Directory Specification + * https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + */ + if (os.equals("Linux")) { + dataHomeDirectory = System.getenv().get("XDG_DATA_HOME"); + if (dataHomeDirectory == null || dataHomeDirectory.equals("")) { + dataHomeDirectory = System.getProperty("user.home") + "/.local/share"; + } + } else if (os.equals("Mac")||os.equals("Mac OS X")) { + /* Apple MacOS X User Data Directory + * https://developer.apple.com/library/archive/qa/qa1170/_index.html */ + dataHomeDirectory = System.getProperty("user.home") + "/Library"; + } else { + dataHomeDirectory = "."; + } + dataHomeDirectory += "/" + projectName + "/"; + // create directory if not already exists + new File(dataHomeDirectory).mkdirs(); + } + + /** Getter for dataHomeDirectory. + * @return path to the application home directory + */ + public String getDataHomeDirectory() { + return dataHomeDirectory; + } + + /** Opens dataHomeDirectory if supported. + */ + private void openDataHomeDirectory() { + try { + if (os.equals("Linux")) { + Runtime runtime = Runtime.getRuntime(); + runtime.exec(new String[] { "xdg-open", dataHomeDirectory }); + } else if (os.equals("Mac")||os.equals("Mac OS X")) { + Runtime runtime = Runtime.getRuntime(); + runtime.exec(new String[] { "open", dataHomeDirectory }); + } + } catch (IOException e) { + System.out.println("Error encountered while trying to open directory"); + } + } + + /** Asks the user to choose opening dataHomeDirectory or not. + */ + public void askOpenDataHomeDirectory() { + if (os.equals("Linux") || os.equals("Mac") || os.equals("Mac OS X")) { + System.out.println("Do you want to open this directory? (y/N)"); + Scanner scanner = new Scanner(System.in); + String resp = scanner.nextLine(); + if (resp.equals("y") || resp.equals("Y")) { + System.out.println("Openning"); + openDataHomeDirectory(); + } + } + } + + +}