From 710ce03921088c507308c1d9fc9ed4013ebe7f93 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 4 Mar 2020 17:40:21 +0100 Subject: [PATCH 1/2] Add hashes --- doc/protocol.md | 35 +++++ src/protocolP2P/HashAlgorithm.java | 49 ++++++ src/protocolP2P/HashRequest.java | 158 +++++++++++++++++++ src/protocolP2P/HashResponse.java | 184 +++++++++++++++++++++++ src/protocolP2P/Payload.java | 4 + src/protocolP2P/RequestResponseCode.java | 7 +- src/tools/LogLevel.java | 6 + 7 files changed, 441 insertions(+), 2 deletions(-) create mode 100644 src/protocolP2P/HashAlgorithm.java create mode 100644 src/protocolP2P/HashRequest.java create mode 100644 src/protocolP2P/HashResponse.java diff --git a/doc/protocol.md b/doc/protocol.md index 02c28bf..b21a615 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -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: [] ``` +### Hash +#### Hash request +Get hash of a file. Payload contains + +``` +4 bytes: [(bytes 8-11): FILENAME SIZE] +y bytes: [] +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: [] +[[ 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. diff --git a/src/protocolP2P/HashAlgorithm.java b/src/protocolP2P/HashAlgorithm.java new file mode 100644 index 0000000..283190a --- /dev/null +++ b/src/protocolP2P/HashAlgorithm.java @@ -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 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; + } + +} + + diff --git a/src/protocolP2P/HashRequest.java b/src/protocolP2P/HashRequest.java new file mode 100644 index 0000000..f711ec8 --- /dev/null +++ b/src/protocolP2P/HashRequest.java @@ -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; + } +} diff --git a/src/protocolP2P/HashResponse.java b/src/protocolP2P/HashResponse.java new file mode 100644 index 0000000..9cb6d2f --- /dev/null +++ b/src/protocolP2P/HashResponse.java @@ -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 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 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; + } + +} diff --git a/src/protocolP2P/Payload.java b/src/protocolP2P/Payload.java index 6868b56..cbe9767 100644 --- a/src/protocolP2P/Payload.java +++ b/src/protocolP2P/Payload.java @@ -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 } diff --git a/src/protocolP2P/RequestResponseCode.java b/src/protocolP2P/RequestResponseCode.java index 2549c95..b69ebb3 100644 --- a/src/protocolP2P/RequestResponseCode.java +++ b/src/protocolP2P/RequestResponseCode.java @@ -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(); } diff --git a/src/tools/LogLevel.java b/src/tools/LogLevel.java index 5ff9900..4bc7169 100644 --- a/src/tools/LogLevel.java +++ b/src/tools/LogLevel.java @@ -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, From 9493a8cb30be8dd6acd147e0de127f3cf904662d Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 4 Mar 2020 22:29:54 +0100 Subject: [PATCH 2/2] Add hashsum --- src/clientP2P/ClientManagementTCP.java | 50 +++++++++++++++- src/clientP2P/ClientManagementUDP.java | 50 +++++++++++++++- src/protocolP2P/HashAlgorithm.java | 13 +++-- src/protocolP2P/HashRequest.java | 7 +++ src/protocolP2P/HashResponse.java | 47 +++++++-------- src/protocolP2P/ProtocolP2PPacketTCP.java | 7 +++ src/protocolP2P/ProtocolP2PPacketUDP.java | 7 +++ src/serverP2P/ServerManagementTCP.java | 69 +++++++++++++++++++++++ src/serverP2P/ServerManagementUDP.java | 69 ++++++++++++++++++++++- src/tools/BytesArrayTools.java | 16 ++++++ src/tools/Logger.java | 2 + 11 files changed, 305 insertions(+), 32 deletions(-) diff --git a/src/clientP2P/ClientManagementTCP.java b/src/clientP2P/ClientManagementTCP.java index 3c7f4b0..c7c16e6 100644 --- a/src/clientP2P/ClientManagementTCP.java +++ b/src/clientP2P/ClientManagementTCP.java @@ -21,12 +21,18 @@ import java.nio.file.Files; import java.io.File; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.util.Arrays; import protocolP2P.ProtocolP2PPacketTCP; import protocolP2P.Payload; import protocolP2P.RequestResponseCode; import protocolP2P.FileList; import protocolP2P.FilePart; import protocolP2P.LoadRequest; +import protocolP2P.HashAlgorithm; +import protocolP2P.HashRequest; +import protocolP2P.HashResponse; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; /** Implementation of P2P-JAVA-PROJECT CLIENT * @author Louis Royer @@ -134,8 +140,30 @@ public class ClientManagementTCP implements Runnable { * @throws EmptyFile */ private void download(String filename) throws EmptyFile, NotFound, InternalError, UnknownHostException, IOException, SocketClosed, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError { + byte [] hash512; final long MAX_PARTIAL_SIZE = 4096; - ProtocolP2PPacketTCP d = new ProtocolP2PPacketTCP((Payload) new LoadRequest(filename, 0, MAX_PARTIAL_SIZE)); + HashAlgorithm[] hashesAlgo = new HashAlgorithm[1]; + hashesAlgo[0] = HashAlgorithm.SHA512; + ProtocolP2PPacketTCP d = new ProtocolP2PPacketTCP((Payload) new HashRequest(filename, hashesAlgo)); + d.sendRequest((Object)socket); + try { + Payload pHash = d.receiveResponse().getPayload(); + assert pHash instanceof HashResponse : "This payload must be instance of HashResponse"; + if (!(pHash instanceof HashResponse)) { + throw new InternalError(); + } else { + hash512 = ((HashResponse)pHash).getHash(HashAlgorithm.SHA512); + if (hash512.length == 0) { + System.err.println("Error: server do not support sha512 hashes"); + throw new InternalError(); + } + } + } catch (EmptyDirectory e) { + System.err.println("Error: empty directory, but request was not LIST_REQUEST"); + throw new ProtocolError(); + } + + d = new ProtocolP2PPacketTCP((Payload) new LoadRequest(filename, 0, MAX_PARTIAL_SIZE)); d.sendRequest((Object)socket); boolean fileFullyWritten = false; long offset = 0; @@ -199,6 +227,10 @@ public class ClientManagementTCP implements Runnable { throw new ProtocolError(); } } while(!fileFullyWritten); + if (!Arrays.equals(hash512, computeHashsum(filename, HashAlgorithm.SHA512))) { + System.err.println("Error: Hashsum does not match"); + throw new InternalError(); + } socket.close(); } @@ -234,4 +266,20 @@ public class ClientManagementTCP implements Runnable { throw new ProtocolError(); } } + + /** Compute Hashsum of a file. + * @param filename + * @return hashsum + */ + private byte[] computeHashsum(String filename, HashAlgorithm h) { + try { + MessageDigest md = MessageDigest.getInstance(HashAlgorithm.SHA512.getName()); + return md.digest(Files.readAllBytes(Paths.get(baseDirectory + filename))); + } catch (NoSuchAlgorithmException e) { + System.out.println("Error: " + h.getName() + " not supported"); + } catch (IOException e) { + System.out.println("Error: cannot read " + filename); + } + return new byte[0]; + } } diff --git a/src/clientP2P/ClientManagementUDP.java b/src/clientP2P/ClientManagementUDP.java index 78d84c5..4d92f84 100644 --- a/src/clientP2P/ClientManagementUDP.java +++ b/src/clientP2P/ClientManagementUDP.java @@ -20,12 +20,18 @@ import java.nio.file.Files; import java.io.File; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.util.Arrays; import protocolP2P.ProtocolP2PPacketUDP; import protocolP2P.Payload; import protocolP2P.RequestResponseCode; import protocolP2P.FileList; import protocolP2P.FilePart; import protocolP2P.LoadRequest; +import protocolP2P.HashAlgorithm; +import protocolP2P.HashRequest; +import protocolP2P.HashResponse; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; /** Implementation of P2P-JAVA-PROJECT CLIENT * @author Louis Royer @@ -121,8 +127,30 @@ public class ClientManagementUDP implements Runnable { * @throws EmptyFile */ private void download(String filename) throws EmptyFile, NotFound, InternalError, UnknownHostException, IOException, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError { + byte[] hash512; final long MAX_PARTIAL_SIZE = 4096; - ProtocolP2PPacketUDP d = new ProtocolP2PPacketUDP((Payload) new LoadRequest(filename, 0, MAX_PARTIAL_SIZE)); + HashAlgorithm[] hashesAlgo = new HashAlgorithm[1]; + hashesAlgo[0] = HashAlgorithm.SHA512; + ProtocolP2PPacketUDP d = new ProtocolP2PPacketUDP((Payload) new HashRequest(filename, hashesAlgo)); + d.sendRequest((Object)socket); + try { + Payload pHash = d.receiveResponse().getPayload(); + assert pHash instanceof HashResponse : "This payload must be instance of HashResponse"; + if (!(pHash instanceof HashResponse)) { + throw new InternalError(); + } else { + hash512 = ((HashResponse)pHash).getHash(HashAlgorithm.SHA512); + if (hash512.length == 0) { + System.err.println("Error: server do not support sha512 hashes"); + throw new InternalError(); + } + } + } catch (EmptyDirectory e) { + System.err.println("Error: empty directory, but request was not LIST_REQUEST"); + throw new ProtocolError(); + } + + d = new ProtocolP2PPacketUDP((Payload) new LoadRequest(filename, 0, MAX_PARTIAL_SIZE)); d.sendRequest((Object)socket); boolean fileFullyWritten = false; long offset = 0; @@ -186,6 +214,10 @@ public class ClientManagementUDP implements Runnable { throw new ProtocolError(); } } while(!fileFullyWritten); + if (!Arrays.equals(hash512, computeHashsum(filename, HashAlgorithm.SHA512))) { + System.err.println("Error: Hashsum does not match"); + throw new InternalError(); + } } /** list server’s directory content @@ -219,4 +251,20 @@ public class ClientManagementUDP implements Runnable { throw new ProtocolError(); } } + + /** Compute Hashsum of a file. + * @param filename + * @return hashsum + */ + private byte[] computeHashsum(String filename, HashAlgorithm h) { + try { + MessageDigest md = MessageDigest.getInstance(HashAlgorithm.SHA512.getName()); + return md.digest(Files.readAllBytes(Paths.get(baseDirectory + filename))); + } catch (NoSuchAlgorithmException e) { + System.out.println("Error: " + h.getName() + " not supported"); + } catch (IOException e) { + System.out.println("Error: cannot read " + filename); + } + return new byte[0]; + } } diff --git a/src/protocolP2P/HashAlgorithm.java b/src/protocolP2P/HashAlgorithm.java index 283190a..1472d9d 100644 --- a/src/protocolP2P/HashAlgorithm.java +++ b/src/protocolP2P/HashAlgorithm.java @@ -1,6 +1,7 @@ package protocolP2P; import java.util.HashMap; import java.util.Map; +import tools.BytesArrayTools; /** HashAlgorithm enum. * @author Louis Royer @@ -14,16 +15,16 @@ public enum HashAlgorithm { private String name; /* To be able to convert name to enum */ - private static final Map BY_CODE = new HashMap<>(); + private static final Map BY_NAME = 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); + assert !BY_NAME.containsKey(h.name) : "Duplicate in " + HashAlgorithm.class.getCanonicalName(); + BY_NAME.put(h.name, h); } } - HashAlgorithm(String name) { + private HashAlgorithm(String name) { this.name = name; } @@ -37,11 +38,13 @@ public enum HashAlgorithm { * @throws InterlanError */ protected static HashAlgorithm fromName(String name) throws InternalError { - HashAlgorithm h = BY_CODE.get(Byte.valueOf(name)); + HashAlgorithm h = BY_NAME.get(BytesArrayTools.cleanStrings(name)); + assert h != null : "Bad algorithm name: " + name; if (h == null) { throw new InternalError(); } return h; + } } diff --git a/src/protocolP2P/HashRequest.java b/src/protocolP2P/HashRequest.java index f711ec8..8caf2d7 100644 --- a/src/protocolP2P/HashRequest.java +++ b/src/protocolP2P/HashRequest.java @@ -70,6 +70,7 @@ public class HashRequest extends Payload { try { String[] l = (new String(packet, FILENAME_POSITION + filenameSize, size, "UTF-8")).split("\n"); int i = 0; + algoList = new HashAlgorithm[l.length]; for(String algo : l) { algoList[i] = HashAlgorithm.fromName(algo); i++; @@ -90,6 +91,9 @@ public class HashRequest extends Payload { int filenameSize = filename.length(); int size = FILENAME_POSITION + filenameSize; for (HashAlgorithm h : algoList) { + if (h == null) { + continue; + } size += h.getName().length(); size += 1; // size for '\n' } @@ -117,6 +121,9 @@ public class HashRequest extends Payload { } boolean firstIter = true; for(HashAlgorithm h : algoList) { + if (h == null) { + continue; + } String s = h.getName(); if (!firstIter) { firstIter = false; diff --git a/src/protocolP2P/HashResponse.java b/src/protocolP2P/HashResponse.java index 9cb6d2f..64ea71c 100644 --- a/src/protocolP2P/HashResponse.java +++ b/src/protocolP2P/HashResponse.java @@ -8,10 +8,7 @@ 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 @@ -22,7 +19,7 @@ import java.nio.file.Paths; public class HashResponse extends Payload { private String filename; - private Map hashes; + private Map hashes = new HashMap<>(); private static final int FILENAME_SIZE_POSITION = PAYLOAD_START_POSITION; private static final int FILENAME_POSITION = FILENAME_SIZE_POSITION + 4; @@ -32,7 +29,7 @@ public class HashResponse extends Payload { * @throws InternalError * */ - public HashResponse(String filename, Map hashes) throws InternalError { + public HashResponse(String filename, Map hashes) throws InternalError { super(RequestResponseCode.HASH_RESPONSE); /* assert to help debugging */ assert filename.length() != 0 : "Size of filename in HashResponse must not be empty"; @@ -81,9 +78,13 @@ public class HashResponse extends Payload { String algoName = new String(packet, start, algoNameSize, "UTF-8"); start += algoNameSize; int hashSize = BytesArrayTools.readInt(packet, start); - start += 8; + start += 4; if (hashSize != 0) { - hashes.put(HashAlgorithm.fromName(algoName), new String(packet, start, hashSize, "UTF-8")); + byte[] b = new byte[hashSize]; + for (int i=0;i sha512 = new HashMap<>(); private String baseDirectory; private int TCPPort; private ServerSocket socket; @@ -55,6 +63,7 @@ public class ServerManagementTCP implements Runnable { this.baseDirectory = baseDirectory; this.TCPPort = TCPPort; initFileList(); + initSha512(); try { socket = new ServerSocket(TCPPort); } catch (SocketException e) { @@ -123,6 +132,10 @@ public class ServerManagementTCP implements Runnable { logger.writeTCP(addr + "LIST_REQUEST", LogLevel.Action); sendListResponse(pd); break; + case HASH_REQUEST: + logger.writeTCP(addr + "HASH_REQUEST", LogLevel.Action); + sendHashResponse(pd); + break; default: logger.writeTCP(addr + "Received grabbage", LogLevel.Action); sendInternalError(pd); @@ -158,6 +171,22 @@ public class ServerManagementTCP implements Runnable { Arrays.sort(fileList); } + /** Init sha512 map. + */ + private void initSha512() { + for(String f: fileList) { + try { + MessageDigest md = MessageDigest.getInstance(HashAlgorithm.SHA512.getName()); + sha512.put(f, md.digest(Files.readAllBytes(Paths.get(baseDirectory + f)))); + md.reset(); + } catch (NoSuchAlgorithmException e) { + logger.writeTCP("sha512 not supported", LogLevel.Error); + } catch (IOException e) { + logger.writeTCP("cannot read " + f, LogLevel.Warning); + } + } + } + /** Send an internal error message. * @param pd ProtocolP2PPacketTCP to respond */ @@ -187,6 +216,46 @@ public class ServerManagementTCP implements Runnable { } } + /** Send hash response to hash request + * @param pd Request received + */ + private void sendHashResponse(ProtocolP2PPacketTCP pd) { + Payload p = pd.getPayload(); + assert p instanceof HashRequest : "payload must be an instance of HashRequest"; + if (!(p instanceof HashRequest)) { + sendInternalError(pd); + } else { + String filename = ((HashRequest)p).getFilename(); + if (Arrays.binarySearch(fileList, filename) >= 0) { + Map hashes = new HashMap<>(); + for (HashAlgorithm h : ((HashRequest)p).getAlgoList()) { + switch (h) { + case SHA512: + hashes.put(h, sha512.get(filename)); + break; + case MD5: + default: + hashes.put(h, new byte[0]); + break; + } + } + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP((Payload)(new HashResponse(filename, hashes)))); + } catch (Exception e) { + logger.writeTCP(e, LogLevel.Error); + } + } else { + // file not found + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.NOT_FOUND))); + } catch (Exception e) { + logger.writeTCP(e, LogLevel.Error); + } + } + } + } + + /** Send response to load request * @param pd Request received */ diff --git a/src/serverP2P/ServerManagementUDP.java b/src/serverP2P/ServerManagementUDP.java index 1044ab0..ced8643 100644 --- a/src/serverP2P/ServerManagementUDP.java +++ b/src/serverP2P/ServerManagementUDP.java @@ -7,6 +7,8 @@ import java.net.InetAddress; import java.net.SocketException; import java.nio.file.Paths; import java.nio.file.Files; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import protocolP2P.ProtocolP2PPacketUDP; import protocolP2P.ProtocolP2PPacket; import protocolP2P.RequestResponseCode; @@ -29,7 +31,11 @@ import remoteException.EmptyFile; import java.util.Arrays; import tools.Logger; import tools.LogLevel; - +import java.util.HashMap; +import java.util.Map; +import protocolP2P.HashAlgorithm; +import protocolP2P.HashRequest; +import protocolP2P.HashResponse; /** Implementation of P2P-JAVA-PROJECT VERSION 1.0 protocol for UDP. * @author Louis Royer @@ -40,6 +46,7 @@ import tools.LogLevel; public class ServerManagementUDP implements Runnable { private String[] fileList; + private Map sha512 = new HashMap<>(); private String baseDirectory; private int UDPPort; private DatagramSocket socket; @@ -54,6 +61,7 @@ public class ServerManagementUDP implements Runnable { this.baseDirectory = baseDirectory; this.UDPPort = UDPPort; initFileList(); + initSha512(); try { socket = new DatagramSocket(UDPPort); } catch (SocketException e) { @@ -134,6 +142,10 @@ public class ServerManagementUDP implements Runnable { logger.writeUDP(e2, LogLevel.Error); } break; + case HASH_REQUEST: + logger.writeUDP("Received HASH_REQUEST", LogLevel.Action); + sendHashResponse(pd); + break; default: sendInternalError(pd); } @@ -165,7 +177,21 @@ public class ServerManagementUDP implements Runnable { Arrays.sort(fileList); } - + /** Init sha512 map. + */ + private void initSha512() { + for(String f: fileList) { + try { + MessageDigest md = MessageDigest.getInstance(HashAlgorithm.SHA512.getName()); + sha512.put(f, md.digest(Files.readAllBytes(Paths.get(baseDirectory + f)))); + md.reset(); + } catch (NoSuchAlgorithmException e) { + logger.writeUDP("sha512 not supported", LogLevel.Error); + } catch (IOException e) { + logger.writeUDP("cannot read " + f, LogLevel.Warning); + } + } + } /** Send an internal error message. * @param pd ProtocolP2PPacketUDP to respond @@ -179,5 +205,44 @@ public class ServerManagementUDP implements Runnable { } } + /** Send hash response to hash request + * @param pd Request received + */ + private void sendHashResponse(ProtocolP2PPacketUDP pd) { + Payload p = pd.getPayload(); + assert p instanceof HashRequest : "payload must be an instance of HashRequest"; + if (!(p instanceof HashRequest)) { + sendInternalError(pd); + } else { + String filename = ((HashRequest)p).getFilename(); + if (Arrays.binarySearch(fileList, filename) >= 0) { + Map hashes = new HashMap<>(); + for (HashAlgorithm h : ((HashRequest)p).getAlgoList()) { + switch (h) { + case SHA512: + hashes.put(h, sha512.get(filename)); + break; + case MD5: + default: + hashes.put(h, new byte[0]); + break; + } + } + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP((Payload)(new HashResponse(filename, hashes)))); + } catch (Exception e) { + logger.writeUDP(e, LogLevel.Error); + } + } else { + // file not found + try { + pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.NOT_FOUND))); + } catch (Exception e) { + logger.writeUDP(e, LogLevel.Error); + } + } + } + } + } diff --git a/src/tools/BytesArrayTools.java b/src/tools/BytesArrayTools.java index 7e21038..924f4fc 100644 --- a/src/tools/BytesArrayTools.java +++ b/src/tools/BytesArrayTools.java @@ -100,4 +100,20 @@ public class BytesArrayTools { return size; } + /** Remove trailing null bytes from string. + * @param str input string + * @return str without trailing null bytes + */ + public static String cleanStrings(String str) { + int i = 0; + int cpt = 0; + byte[] bArray = str.getBytes(); + for (byte b : bArray) { + if (b != 0) { + cpt = i; + } + i++; + } + return new String(bArray, 0, cpt + 1); + } } diff --git a/src/tools/Logger.java b/src/tools/Logger.java index 3ab3b5a..047c4b8 100644 --- a/src/tools/Logger.java +++ b/src/tools/Logger.java @@ -98,6 +98,7 @@ public class Logger { */ public void writeTCP(Exception e, LogLevel logLevel) { writeTCP(e.toString(), logLevel); + e.printStackTrace(); } /** Appends log to filelog and print to stderr. @@ -116,6 +117,7 @@ public class Logger { */ public void writeUDP(Exception e, LogLevel logLevel) { writeUDP(e.toString(), logLevel); + e.printStackTrace(); } }