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(); } }