Merge branch 'WIP_etape1' of flavien/Projet_JAVA_P2P_STRI2A into master
Fin de l’étape 1
This commit is contained in:
commit
3fc24dfef6
104
.gitignore
vendored
104
.gitignore
vendored
@ -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
|
||||
|
File diff suppressed because one or more lines are too long
82
doc/protocol.md
Normal file
82
doc/protocol.md
Normal file
@ -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<FILENAME>\n`: ask the server to download file <FILENAME> from server root directory. Only one filename is allowed per request.
|
||||
|
||||
## Server responses
|
||||
- The response to `LIST` request is in the format `LIST\n<FILENAME 1>\n<FILENAME 2>\n[…]<LAST FILENAME>\n\n`
|
||||
- The response to `DOWNLOAD` request is `LOAD\n<FILESIZE (BYTES)>\n<POSITION>\n<CONTENT OF FILE>` 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: [<FILENAME>]
|
||||
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.
|
179
src/clientP2P/ClientManagementUDP.java
Normal file
179
src/clientP2P/ClientManagementUDP.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
25
src/clientP2P/ClientP2P.java
Normal file
25
src/clientP2P/ClientP2P.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
4
src/exception/InternalError.java
Normal file
4
src/exception/InternalError.java
Normal file
@ -0,0 +1,4 @@
|
||||
package exception;
|
||||
public class InternalError extends Exception {
|
||||
private static final long serialVersionUID = 11L;
|
||||
}
|
4
src/exception/ProtocolError.java
Normal file
4
src/exception/ProtocolError.java
Normal file
@ -0,0 +1,4 @@
|
||||
package exception;
|
||||
public class ProtocolError extends Exception {
|
||||
private static final long serialVersionUID = 11L;
|
||||
}
|
5
src/exception/SizeError.java
Normal file
5
src/exception/SizeError.java
Normal file
@ -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;
|
||||
}
|
4
src/exception/TransmissionError.java
Normal file
4
src/exception/TransmissionError.java
Normal file
@ -0,0 +1,4 @@
|
||||
package exception;
|
||||
public class TransmissionError extends Exception {
|
||||
private static final long serialVersionUID = 11L;
|
||||
}
|
4
src/exception/VersionError.java
Normal file
4
src/exception/VersionError.java
Normal file
@ -0,0 +1,4 @@
|
||||
package exception;
|
||||
public class VersionError extends Exception {
|
||||
private static final long serialVersionUID = 11L;
|
||||
}
|
13
src/protocolP2P/CodeType.java
Normal file
13
src/protocolP2P/CodeType.java
Normal file
@ -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
|
||||
}
|
109
src/protocolP2P/FileList.java
Normal file
109
src/protocolP2P/FileList.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
199
src/protocolP2P/FilePart.java
Normal file
199
src/protocolP2P/FilePart.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
90
src/protocolP2P/LoadRequest.java
Normal file
90
src/protocolP2P/LoadRequest.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
116
src/protocolP2P/Payload.java
Normal file
116
src/protocolP2P/Payload.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
243
src/protocolP2P/ProtocolP2PDatagram.java
Normal file
243
src/protocolP2P/ProtocolP2PDatagram.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
65
src/protocolP2P/RequestResponseCode.java
Normal file
65
src/protocolP2P/RequestResponseCode.java
Normal file
@ -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<Byte, RequestResponseCode> 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
4
src/remoteException/EmptyDirectory.java
Normal file
4
src/remoteException/EmptyDirectory.java
Normal file
@ -0,0 +1,4 @@
|
||||
package remoteException;
|
||||
public class EmptyDirectory extends Exception {
|
||||
private static final long serialVersionUID = 11L;
|
||||
}
|
4
src/remoteException/EmptyFile.java
Normal file
4
src/remoteException/EmptyFile.java
Normal file
@ -0,0 +1,4 @@
|
||||
package remoteException;
|
||||
public class EmptyFile extends Exception {
|
||||
private static final long serialVersionUID = 11L;
|
||||
}
|
4
src/remoteException/InternalRemoteError.java
Normal file
4
src/remoteException/InternalRemoteError.java
Normal file
@ -0,0 +1,4 @@
|
||||
package remoteException;
|
||||
public class InternalRemoteError extends Exception {
|
||||
private static final long serialVersionUID = 11L;
|
||||
}
|
4
src/remoteException/NotFound.java
Normal file
4
src/remoteException/NotFound.java
Normal file
@ -0,0 +1,4 @@
|
||||
package remoteException;
|
||||
public class NotFound extends Exception {
|
||||
private static final long serialVersionUID = 11L;
|
||||
}
|
4
src/remoteException/ProtocolRemoteError.java
Normal file
4
src/remoteException/ProtocolRemoteError.java
Normal file
@ -0,0 +1,4 @@
|
||||
package remoteException;
|
||||
public class ProtocolRemoteError extends Exception {
|
||||
private static final long serialVersionUID = 11L;
|
||||
}
|
4
src/remoteException/VersionRemoteError.java
Normal file
4
src/remoteException/VersionRemoteError.java
Normal file
@ -0,0 +1,4 @@
|
||||
package remoteException;
|
||||
public class VersionRemoteError extends Exception {
|
||||
private static final long serialVersionUID = 11L;
|
||||
}
|
160
src/serverP2P/ServerManagementUDP.java
Normal file
160
src/serverP2P/ServerManagementUDP.java
Normal file
@ -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<String> v = new Vector<String>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
23
src/serverP2P/ServerP2P.java
Normal file
23
src/serverP2P/ServerP2P.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
66
src/tools/BytesArrayTools.java
Normal file
66
src/tools/BytesArrayTools.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
89
src/tools/Directories.java
Normal file
89
src/tools/Directories.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user