Add hashes

This commit is contained in:
Louis Royer 2020-03-04 17:40:21 +01:00
parent 600ef1b2cd
commit 710ce03921
7 changed files with 441 additions and 2 deletions

View File

@ -16,10 +16,12 @@ x bytes: [(bytes 8-?): PAYLOAD]
- REQUESTS (msb is 0):
- `LIST` (0x00)
- `LOAD` (0x01)
- `HASH` (0x02)
- RESPONSES (msb is 1):
- `LIST` (0x80)
- `LOAD` (0x81)
- `HASH` (0x82)
- `VERSION ERROR` (0xC0)
- `PROTOCOL ERROR` (0xC1)
- `INTERNAL ERROR` (0xC2)
@ -63,6 +65,39 @@ Payload contains
y bytes: [<FILENAME>]
```
### Hash
#### Hash request
Get hash of a file. Payload contains
```
4 bytes: [(bytes 8-11): FILENAME SIZE]
y bytes: [<FILENAME>]
z bytes: [ALGO_NAMES requested separated by \n] (ex.: SHA-256, MD5)
```
If file does not exists, a NotFound can be responded.
#### Hash response
Payload contains:
```
4 bytes: [(bytes 8-11): FILENAME SIZE]
y bytes: [<FILENAME>]
[[ multiple algo hashes bloc]]
```
A algo hash bloc contains:
```
4 bytes [ALGO_NAME size]
? [ALGO_NAME]
4 bytes: [HASH SIZE (bytes)] / or 0 if this hash algorithm is unsupported.
?? [HASH]
```
### Other response code (errors)
#### Version error
Response when datagram received use wrong version code.

View File

@ -0,0 +1,49 @@
package protocolP2P;
import java.util.HashMap;
import java.util.Map;
/** HashAlgorithm enum.
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public enum HashAlgorithm {
SHA512("SHA-512"),
MD5("MD5");
private String name;
/* To be able to convert name to enum */
private static final Map<Byte, HashAlgorithm> BY_CODE = new HashMap<>();
/* Initialization of HashMap */
static {
for (HashAlgorithm h: values()) {
assert !BY_CODE.containsKey(Byte.valueOf(h.name)) : "Duplicate in " + HashAlgorithm.class.getCanonicalName();
BY_CODE.put(Byte.valueOf(h.name), h);
}
}
HashAlgorithm(String name) {
this.name = name;
}
public String getName() {
return name;
}
/** Gives enum from name.
* @param name name of the hash
* @return enum element
* @throws InterlanError
*/
protected static HashAlgorithm fromName(String name) throws InternalError {
HashAlgorithm h = BY_CODE.get(Byte.valueOf(name));
if (h == null) {
throw new InternalError();
}
return h;
}
}

View File

@ -0,0 +1,158 @@
package protocolP2P;
import protocolP2P.Payload;
import protocolP2P.HashAlgorithm;
import java.io.UnsupportedEncodingException;
import exception.TransmissionError;
import exception.SizeError;
import exception.ProtocolError;
import exception.InternalError;
import tools.BytesArrayTools;
/** Representation of payload for hash request.
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public class HashRequest extends Payload {
private String filename;
private HashAlgorithm[] algoList;
private static final int FILENAME_SIZE_POSITION = PAYLOAD_START_POSITION;
private static final int FILENAME_POSITION = FILENAME_SIZE_POSITION + 4;
/** Constructor (typically used by the server) with a filename parameter.
* @param filename name of the file to download. Must not be empty.
* @param algoList List of hash algorithms used
* @throws InternalError
*
*/
public HashRequest(String filename, HashAlgorithm[] algoList) throws InternalError {
super(RequestResponseCode.HASH_REQUEST);
/* assert to help debugging */
assert filename.length() != 0 : "Size of filename in HashRequest must not be empty";
if (filename.length() == 0) {
throw new InternalError();
}
this.filename = filename;
assert algoList.length != 0 : "Hash algorithms list must not be empty";
if (algoList.length == 0) {
throw new InternalError();
}
this.algoList = algoList;
}
/** Constructor (typically used by client) with a byte[] parameter containing the Packet received.
* @param packet the full Packet received
* @throws SizeError
* @throws InternalError
* @throws ProtocolError
* @throws TransmissionError
*/
protected HashRequest(byte[] packet) throws TransmissionError, SizeError, ProtocolError, InternalError {
super(packet);
/* assert to help debugging */
assert requestResponseCode == RequestResponseCode.HASH_REQUEST : "HashRequest subclass is incompatible with this Packet, request/response code must be checked before using this constructor";
/* InternalErrorException */
if (requestResponseCode!= RequestResponseCode.HASH_REQUEST) {
throw new InternalError();
}
/* Read filename */
int filenameSize = BytesArrayTools.readInt(packet, FILENAME_SIZE_POSITION);
try {
filename = new String(packet, FILENAME_POSITION, filenameSize, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
int size = getPayloadSize(packet);
try {
String[] l = (new String(packet, FILENAME_POSITION + filenameSize, size, "UTF-8")).split("\n");
int i = 0;
for(String algo : l) {
algoList[i] = HashAlgorithm.fromName(algo);
i++;
}
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
}
/** Returns a byte[] containing Packet with padding.
* This Packet is still incomplete and should not be send directly.
* ProtocolP2PPacket will use this method to generate the complete Packet.
* @return Packet with padding
* @throws InternalError
*/
protected byte[] toPacket() throws InternalError {
// compute size
int filenameSize = filename.length();
int size = FILENAME_POSITION + filenameSize;
for (HashAlgorithm h : algoList) {
size += h.getName().length();
size += 1; // size for '\n'
}
size -=1; // remove trailing '\n'
byte[] packet = new byte[size + 1]; // java initialize all to zero
// set request/response code
packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue;
// set Payload size
setPayloadSize(size - FILENAME_SIZE_POSITION, packet);
BytesArrayTools.write(packet, FILENAME_SIZE_POSITION, filenameSize);
// Write filename
int bCount = FILENAME_POSITION;
try {
byte[] sb = filename.getBytes("UTF-8");
for(byte b : sb) {
packet[bCount] = b;
bCount += 1;
}
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
boolean firstIter = true;
for(HashAlgorithm h : algoList) {
String s = h.getName();
if (!firstIter) {
firstIter = false;
try {
packet[bCount] = "\n".getBytes("UTF-8")[0]; // separator
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
bCount += 1;
}
// Copy algoname
try {
byte[] sb = s.getBytes("UTF-8");
for(byte b : sb) {
packet[bCount] = b;
bCount += 1;
}
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
}
return packet;
}
/** AlgoList getter.
* @return List of HashAlgorithms
*/
public HashAlgorithm[] getAlgoList() {
return algoList;
}
/** Filename getter.
* @return filename
*/
public String getFilename() {
return filename;
}
}

View File

@ -0,0 +1,184 @@
package protocolP2P;
import protocolP2P.Payload;
import java.util.HashMap;
import java.util.Map;
import java.io.UnsupportedEncodingException;
import exception.TransmissionError;
import exception.SizeError;
import exception.ProtocolError;
import exception.InternalError;
import tools.BytesArrayTools;
import java.security.MessageDigest;
import java.security.DigestInputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
/** Representation of payload for hash response.
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public class HashResponse extends Payload {
private String filename;
private Map<HashAlgorithm, String> hashes;
private static final int FILENAME_SIZE_POSITION = PAYLOAD_START_POSITION;
private static final int FILENAME_POSITION = FILENAME_SIZE_POSITION + 4;
/** Constructor (typically used by the server) with a filename parameter.
* @param filename name of the file to download. Must not be empty.
* @param hashes HashMap containing hashes for file.
* @throws InternalError
*
*/
public HashResponse(String filename, Map<HashAlgorithm, String> hashes) throws InternalError {
super(RequestResponseCode.HASH_RESPONSE);
/* assert to help debugging */
assert filename.length() != 0 : "Size of filename in HashResponse must not be empty";
if (filename.length() == 0) {
throw new InternalError();
}
this.filename = filename;
// initialize hashes
assert !hashes.isEmpty() : "At least 1 hash must be provided";
if (hashes.isEmpty()) {
throw new InternalError();
}
this.hashes = hashes;
}
/** Constructor (typically used by client) with a byte[] parameter containing the Packet received.
* @param packet the full Packet received
* @throws SizeError
* @throws InternalError
* @throws ProtocolError
* @throws TransmissionError
*/
protected HashResponse(byte[] packet) throws TransmissionError, SizeError, ProtocolError, InternalError {
super(packet);
/* assert to help debugging */
assert requestResponseCode == RequestResponseCode.HASH_RESPONSE : "HashResponse subclass is incompatible with this Packet, request/response code must be checked before using this constructor";
/* InternalErrorException */
if (requestResponseCode!= RequestResponseCode.HASH_RESPONSE) {
throw new InternalError();
}
/* Read filename */
int filenameSize = BytesArrayTools.readInt(packet, FILENAME_SIZE_POSITION);
try {
filename = new String(packet, FILENAME_POSITION, filenameSize, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
int size = getPayloadSize(packet);
int start = FILENAME_POSITION + filenameSize;
do {
int algoNameSize = BytesArrayTools.readInt(packet, start);
start +=4;
try {
String algoName = new String(packet, start, algoNameSize, "UTF-8");
start += algoNameSize;
int hashSize = BytesArrayTools.readInt(packet, start);
start += 8;
if (hashSize != 0) {
hashes.put(HashAlgorithm.fromName(algoName), new String(packet, start, hashSize, "UTF-8"));
}
start += hashSize;
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
} while (start < size);
}
/** Returns a byte[] containing Packet with padding.
* This Packet is still incomplete and should not be send directly.
* ProtocolP2PPacket will use this method to generate the complete Packet.
* @return Packet with padding
* @throws InternalError
*/
protected byte[] toPacket() throws InternalError {
// compute size
int filenameSize = filename.length();
int size = FILENAME_POSITION + filenameSize;
for (HashAlgorithm h : hashes.keySet()) {
size += 4 + h.getName().length();
}
for (String s : hashes.values()) {
size += 4 + s.length();
}
byte[] packet = new byte[size + 1]; // java initialize all to zero
// set request/response code
packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue;
// set Payload size
setPayloadSize(size - FILENAME_SIZE_POSITION, packet);
BytesArrayTools.write(packet, FILENAME_SIZE_POSITION, filenameSize);
// Write filename
int bCount = FILENAME_POSITION;
try {
byte[] sb = filename.getBytes("UTF-8");
for(byte b : sb) {
packet[bCount] = b;
bCount += 1;
}
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
for(HashAlgorithm h : hashes.keySet()) {
String s = h.getName();
BytesArrayTools.write(packet, bCount, (int)s.length());
bCount += 4;
// Copy algoname
try {
byte[] sb = s.getBytes("UTF-8");
for(byte b : sb) {
packet[bCount] = b;
bCount += 1;
}
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
s = hashes.get(s);
if (s == null) {
BytesArrayTools.write(packet, bCount, (int)0);
bCount += 4;
} else {
BytesArrayTools.write(packet, bCount, (int)s.length());
bCount +=4;
// copy hash
try {
byte[] sb = s.getBytes("UTF-8");
for(byte b : sb) {
packet[bCount] = b;
bCount += 1;
}
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
}
}
return packet;
}
/** hash getter
* @param hashAlgo HashAlgorithm to return hash from
* @return hash
*/
public String getHash(HashAlgorithm hashAlgo) {
return hashes.get(hashAlgo);
}
/** filename getter.
* @return filename
*/
public String getFilename() {
return filename;
}
}

View File

@ -3,6 +3,8 @@ import protocolP2P.RequestResponseCode;
import protocolP2P.FilePart;
import protocolP2P.FileList;
import protocolP2P.LoadRequest;
import protocolP2P.HashRequest;
import protocolP2P.HashResponse;
import exception.ProtocolError;
import exception.InternalError;
import exception.TransmissionError;
@ -28,6 +30,8 @@ public class Payload {
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";
assert requestResponseCode != RequestResponseCode.HASH_REQUEST || (this instanceof HashRequest) : "HASH_REQUEST must use HashRequest class";
assert requestResponseCode != RequestResponseCode.HASH_RESPONSE || (this instanceof HashResponse) : "HASH_RESPONSE must use HashResponse class";
this.requestResponseCode = requestResponseCode;
checkRequestResponseCode(); // this can throw InternalError
}

View File

@ -14,8 +14,10 @@ import java.lang.Byte;
public enum RequestResponseCode {
LIST_REQUEST(CodeType.REQUEST, (byte)0x00),
LOAD_REQUEST(CodeType.REQUEST, (byte)0x01),
HASH_REQUEST(CodeType.REQUEST, (byte)0x02),
LIST_RESPONSE(CodeType.RESPONSE, (byte)0x80),
LOAD_RESPONSE(CodeType.RESPONSE, (byte)0x81),
HASH_RESPONSE(CodeType.RESPONSE, (byte)0x82),
VERSION_ERROR(CodeType.ERROR, (byte)0xC0),
PROTOCOL_ERROR(CodeType.ERROR, (byte)0xC1),
INTERNAL_ERROR(CodeType.ERROR, (byte)0xC2),
@ -49,10 +51,11 @@ public enum RequestResponseCode {
/** Gives enum from Packet code.
* @param code value of the element in packet
* @return enum element
* @return enum element
* @throws ProtocolError
*/
protected static RequestResponseCode fromCode(byte code) throws ProtocolError {
RequestResponseCode r= BY_CODE.get(Byte.valueOf(code));
RequestResponseCode r = BY_CODE.get(Byte.valueOf(code));
if (r == null) {
throw new ProtocolError();
}

View File

@ -1,4 +1,10 @@
package tools;
/** LogLevel Enum.
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public enum LogLevel {
Error,
Info,