diff --git a/doc/protocol.md b/doc/protocol.md index 64dae57..f8d5b94 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -2,7 +2,7 @@ All strings in the datagram are utf-8 encoded. -```Datagram format +```text 1 byte: [(byte 0 ): VERSION(0x11, first quartet is major version, second is minor)] 1 byte: [(byte 1 ): REQUEST/RESPONSE CODE] @@ -20,12 +20,18 @@ x bytes: [(bytes 8-?): PAYLOAD] - `DISCOVER` (0x03) - `REGISTER` (0x04) - `UNREGISTER` (0x05) + - `RATIO` (0x06) + - `UPDATE RATIO` (0x07) + - `SIZE` (0x08) - RESPONSES (msb is 1): - `LIST` (0x80) - `LOAD` (0x81) - `HASH` (0x82) - `DISCOVER` (0x83) + - `RATIO` (0x86) + - `DENIED` (0x87) + - `SIZE` (0x88) - `VERSION ERROR` (0xC0) - `PROTOCOL ERROR` (0xC1) - `INTERNAL ERROR` (0xC2) @@ -33,6 +39,7 @@ x bytes: [(bytes 8-?): PAYLOAD] - `NOT FOUND` (0xC4) - `EMPTY FILE` (0xC5) - `NOT A TRACKER` (0xC6) + - `UNKNOWN HOST` (0xC7) ### List Payload size for list request is always zero. @@ -52,9 +59,8 @@ Payload size for Not found is zero. #### Load response Payload contains -``` +```text 8 bytes: [(bytes 8-15): OFFSET OF FILE CONTENT IN BYTES] -8 bytes: [(bytes 16-23): TOTAL FILESIZE] 4 bytes: [(bytes 24-27): FILENAME SIZE] (cannot be > to PAYLOAD_SIZE - 20 or be zero) y bytes: [] z bytes: [PARTIAL CONTENT] @@ -63,18 +69,38 @@ z bytes: [PARTIAL CONTENT] #### Load request Payload contains -``` +```text 8 bytes: [(bytes 8-15): OFFSET OF FILE CONTENT IN BYTES] 8 bytes: [(bytes 16-23): MAX SIZE OF PARTIAL CONTENT (partial content in response should not excess this size, but this can be less (by example if endoffile is reached or server doesn't have the full block requested) 4 bytes: [(bytes 24-27): FILENAME SIZE] (cannot be > to PAYLOAD_SIZE - 20 or be zero) y bytes: [] +2 bytes: port used to register on tracker +? bytes: hostname used to register on tracker ``` +Possible responses: Load Response, or Denied -### Hash +### Size +#### Size request +Payload contains + +```text +? bytes: filename +``` +Possible responses: Size response, EmptyFile or NotFound + +#### Size response +Payload contains + +```text +8 bytes: file size +? bytes: filename +``` + +### Hash #### Hash request Get hash of a file. Payload contains -``` +```text 4 bytes: [(bytes 8-11): FILENAME SIZE] y bytes: [] z bytes: [ALGO_NAMES requested separated by \n] (ex.: SHA-256, MD5) @@ -86,7 +112,7 @@ If file does not exists, a NotFound can be responded. Payload contains: -``` +```text 4 bytes: [(bytes 8-11): FILENAME SIZE] y bytes: [] [[ multiple algo hashes bloc]] @@ -94,7 +120,7 @@ y bytes: [] A algo hash bloc contains: -``` +```text 4 bytes [ALGO_NAME size] ? [ALGO_NAME] 4 bytes: [HASH SIZE (bytes)] / or 0 if this hash algorithm is unsupported. @@ -108,7 +134,7 @@ Used by a server to register itself on a tracker. Server may want to do a free `DISCOVER` to check if they have been registered. Payload contains: -``` +```text 2 bytes: [] ``` @@ -118,7 +144,7 @@ No error is raised if the server was not registered. Server may want to do a free `DISCOVER` to check if they have been unregistered. Payload contains: -``` +```text 2 bytes: [] ? bytes: [] ``` @@ -127,14 +153,14 @@ Payload contains: If payload size is null, lists all servers registered. If payload contains a filename, list all servers having this file in their list. -``` +```text ? bytes: [] ? bytes: [] ``` #### Discover response Contains: -``` +```text 4 bytes: [(bytes 8-11): FILENAME SIZE] y bytes: [] ? bytes [multiple server blocks] @@ -142,7 +168,7 @@ y bytes: [] Server block is composed with: -``` +```text 2 bytes: [port] ? bytes: hostname \n @@ -152,6 +178,47 @@ Server block is composed with: This error is raised when receiving a DISCOVER, a REGISTER, or an UNREGISTER request, but this application is not a tracker. +#### Ratio Request +Contains: +```text +2 bytes: port +? bytes: hostname +``` +Possible responses: Ratio Response, or Unknown Host + +#### Ratio Response +Contains: +```text +8 bytes: total sent bytes +8 bytes: total received bytes +2 bytes: port +? bytes: hostname +``` + +#### Update Ratio +Contains: +```text +8 bytes: bytes sent by the server +2 bytes: server port +2 bytes: client* port +? bytes: server hostname followed by \n +? bytes: client* hostname +* note: used by client to register on tracker +``` +Possible responses: No response, or Unknown Host (if client is not registered or server is not registered) + +Note: client must have verified hash before sending Update Ratio to tracker for each server which participated. + +#### Unknown Host +Payload size is zero. + +#### Denied +Contains +```text +8 bytes: offset of content asked +? bytes: filename +``` + ### Other response code (errors) #### Version error Response when datagram received use wrong version code. diff --git a/src/clientP2P/ClientDownload.java b/src/clientP2P/ClientDownload.java index ed58c3b..f1b44f9 100644 --- a/src/clientP2P/ClientDownload.java +++ b/src/clientP2P/ClientDownload.java @@ -1,5 +1,7 @@ package clientP2P; +import java.util.Map; +import java.util.HashMap; import java.util.List; import java.util.ArrayList; import java.util.Arrays; @@ -24,13 +26,16 @@ import remoteException.ProtocolRemoteError; import remoteException.NotFound; import remoteException.InternalRemoteError; import remoteException.NotATracker; +import remoteException.UnknownHost; import protocolP2P.HashAlgorithm; import protocolP2P.HashResponse; import protocolP2P.HashRequest; import protocolP2P.Payload; import protocolP2P.FilePart; -import protocolP2P.LoadRequest; +import protocolP2P.SizeRequest; +import protocolP2P.SizeResponse; import protocolP2P.ProtocolP2PPacket; +import protocolP2P.UpdateRatio; import clientP2P.ClientDownloadPart; import tools.HostItem; import tools.Logger; @@ -48,6 +53,7 @@ public abstract class ClientDownload extends ServeErrors implements Runnable { protected String filename; protected byte[] hash512; protected List sockList = new ArrayList(); + protected Map ratioUpdater = new HashMap<>(); protected List offsetsToAsk = new ArrayList(); protected List offsetsPending = new ArrayList(); protected boolean stop; @@ -57,6 +63,8 @@ public abstract class ClientDownload extends ServeErrors implements Runnable { protected String dirStorage; protected boolean success = false; protected Logger logger; + protected HostItem client; + protected HostItem tracker; /** Constructor with parameters: filename, list of hosts, parts subdirectory and dirStorage * @param filename name of file to download @@ -64,13 +72,17 @@ public abstract class ClientDownload extends ServeErrors implements Runnable { * @param partsSubdir directory to store .part files * @param dirStorage directory to write assembled file * @param logger Logger + * @param client HostItem of the application + * @param tracker HostItem of the tracker */ - public ClientDownload(String filename, List hostList, String partsSubdir, String dirStorage, Logger logger) { + public ClientDownload(String filename, List hostList, String partsSubdir, String dirStorage, Logger logger, HostItem client, HostItem tracker) { this.partsSubdir = partsSubdir; this.dirStorage = dirStorage; this.filename = filename; this.hostList = hostList; this.logger = logger; + this.client = client; + this.tracker = tracker; this.stop = false; } @@ -114,8 +126,7 @@ public abstract class ClientDownload extends ServeErrors implements Runnable { try { sockList.get(rand.nextInt(sockList.size())).assignTask(offset); offsetsPending.add(offset); - System.err.println("Assigned task "+ offset); - writeLog("Assigned task "+ offset, LogLevel.Info); + writeLog("Assigned task: #"+ offset, LogLevel.Info); } catch(InterruptedException e) { writeLog(e, LogLevel.Error); throw new InternalError(); @@ -125,16 +136,15 @@ public abstract class ClientDownload extends ServeErrors implements Runnable { } /** Create a clientDownloadPart - * @param filename name of the file to download * @param hostItem Hostitem of the server */ - protected abstract ClientDownloadPart createDownloadPart(String filename, HostItem hostItem); + protected abstract ClientDownloadPart createDownloadPart(HostItem hostItem); /** Starts threads for each server in hostList. */ protected void initThreads() { for(HostItem hostItem: hostList) { - sockList.add(createDownloadPart(filename, hostItem)); + sockList.add(createDownloadPart(hostItem)); } for(ClientDownloadPart c: sockList) { Thread t = new Thread(c); @@ -162,6 +172,7 @@ public abstract class ClientDownload extends ServeErrors implements Runnable { } catch (InterruptedException e) { throw new InternalError(); } + ratioUpdater.put(c.getServer(), c.getReceivedBytesCount()); } writeLog("Task check status: " + offsetsToAsk.size() + " to asks, " + offsetsPending.size() + " pending", LogLevel.Info); if (offsetsToAsk.isEmpty() && offsetsPending.isEmpty()) { @@ -177,6 +188,25 @@ public abstract class ClientDownload extends ServeErrors implements Runnable { } } + /** Send Ratio update to the tracker + */ + public void sendRatioUpdate() { + for(HostItem server: ratioUpdater.keySet()) { + Long r = ratioUpdater.get(server); + if (r != null) { + long rl = r.longValue(); + if (rl != 0) { + try { + ProtocolP2PPacket d = createProtocolP2PPacket(new UpdateRatio(client, server, rl)); + d.sendRequest(getHostItemSocket(tracker)); + } catch (Exception e) { + writeLog(e, LogLevel.Error); + } + } + } + } + } + /** Get hashsum from server. * @param hostItem server to ask hash * @return hash512sum @@ -201,7 +231,6 @@ public abstract class ClientDownload extends ServeErrors implements Runnable { writeLog(e, LogLevel.Error); hash = new byte[0]; } catch (NotFound e) { - writeLog(e, LogLevel.Error); hash = new byte[0]; } catch (LocalException e) { writeLog(e, LogLevel.Error); @@ -227,6 +256,7 @@ public abstract class ClientDownload extends ServeErrors implements Runnable { */ protected void purgeList() throws InternalError { List blackList = new ArrayList(); + writeLog("Potential peers (before purge): " + hostList.size(), LogLevel.Debug); boolean first = false; byte[] hashsum; for(HostItem host: hostList) { @@ -245,6 +275,7 @@ public abstract class ClientDownload extends ServeErrors implements Runnable { for(HostItem host: blackList) { hostList.remove(host); } + writeLog("Peers (after purge): " + hostList.size(), LogLevel.Debug); writeLog("Host list purge: done", LogLevel.Info); } @@ -289,39 +320,22 @@ public abstract class ClientDownload extends ServeErrors implements Runnable { * @throws InternalError */ protected void setSize() throws InternalError { - ProtocolP2PPacket d = createProtocolP2PPacket(new LoadRequest(filename, 0, MAX_PARTIAL_SIZE)); + ProtocolP2PPacket d = createProtocolP2PPacket(new SizeRequest(filename)); try { d.sendRequest(getHostItemSocket(hostList.get(0))); try { Payload p = d.receiveResponse().getPayload(); - assert p instanceof FilePart : "This payload must be instance of FilePart"; - if (!(p instanceof FilePart)) { - System.err.println("Error: cannot get size."); + assert p instanceof SizeResponse : "This payload must be instance of SizeResponse"; + if (!(p instanceof SizeResponse)) { writeLog("cannot get size.", LogLevel.Error); throw new InternalError(); } else { - FilePart fp = (FilePart)p; + SizeResponse fp = (SizeResponse)p; if (!fp.getFilename().equals(filename)) { - System.err.println("Error: wrong file received: `" + fp.getFilename() + "`"); - writeLog("wrong file received: `" + fp.getFilename() + "`", LogLevel.Error); + writeLog("wrong file size received: `" + fp.getFilename() + "`", LogLevel.Error); throw new ProtocolError(); } - if (fp.getOffset() == 0) { - try { - Files.write(new File(partsSubdir + filename + "_0.part").toPath(), fp.getPartialContent()); - } catch (IOException e) { - System.err.println("Error: cannot write file (" + partsSubdir + filename + "_0.part)"); - writeLog("cannot write file (" + partsSubdir + filename + "_0.part)", LogLevel.Error); - } - size = fp.getTotalSize(); - if (fp.getPartialContent().length == size) { - stop = true; - } - } else { - System.err.println("Error: wrong file part received."); - writeLog("wrong file part received.", LogLevel.Error); - throw new InternalError(); - } + size = fp.getTotalSize(); } } catch (EmptyDirectory e) { System.err.println("Error: empty directory."); @@ -354,18 +368,11 @@ public abstract class ClientDownload extends ServeErrors implements Runnable { public void run() { try { init(); - if (stop) { - writeLog("File is smaller than part max size.", LogLevel.Info); - closeHostItemSocket(hostList.get(0)); - } else { - writeLog("File is bigger than part max size.", LogLevel.Info); - purgeList(); - initThreads(); - while(!stop) { - assignTasks(); - checkTasksStatus(); - - } + purgeList(); + initThreads(); + while(!stop) { + assignTasks(); + checkTasksStatus(); } writeLog("Reassembling file parts.", LogLevel.Info); reassembleFile(); @@ -393,7 +400,7 @@ public abstract class ClientDownload extends ServeErrors implements Runnable { // Add tasks if (!stop) { - for(long i=MAX_PARTIAL_SIZE; i toDoTasks; protected List pendingTasks; protected List tasksDone; @@ -46,18 +49,24 @@ public abstract class ClientDownloadPart extends ServeErrors implements Runnable protected static final long MAX_PARTIAL_SIZE = 4096; protected ClientDownload manager; protected Logger logger; + protected HostItem client; + private HostItem server; /** Constructor with filename, socket, and part subdir * @param filename name of file to download * @param socket socket to use * @param partsSubdir directory to store .part files * @param logger Logger + * @param client HostItem of the application + * @param server HostItem of the server application */ - public ClientDownloadPart(ClientDownload manager, String filename, String partsSubdir, Logger logger) { + public ClientDownloadPart(ClientDownload manager, String filename, String partsSubdir, Logger logger, HostItem client, HostItem server) { this.manager = manager; this.partsSubdir = partsSubdir; this.filename = filename; this.logger = logger; + this.client = client; + this.server = server; stop = false; failed = false; pendingTasks = new ArrayList<>(); @@ -65,6 +74,22 @@ public abstract class ClientDownloadPart extends ServeErrors implements Runnable tasksDone = new ArrayList<>(); noTask = true; tasksListsLock = false; + receivedBytesCount = 0; + } + + + /** receivedBytesCount getter + * @return receivedBytesCount + */ + public Long getReceivedBytesCount() { + return Long.valueOf(receivedBytesCount); + } + + /** Server getter + * @return server + */ + public HostItem getServer() { + return server; } /** True if thread has failed to get a file. @@ -179,7 +204,6 @@ public abstract class ClientDownloadPart extends ServeErrors implements Runnable if (p == null) { stop = true; } - failed = downloadPart(p); if (failed) { System.err.println("Error: DownloadPart failed."); @@ -200,7 +224,7 @@ public abstract class ClientDownloadPart extends ServeErrors implements Runnable * @return ProtocolP2PPacketTCP used to send request */ protected ProtocolP2PPacket reqPart(Long offset) { - writeLog("New request: " + offset, LogLevel.Info); + writeLog("New request: #" + offset, LogLevel.Info); // maintain tracking of tasks if (toDoTasks.contains(offset)) { try { @@ -224,7 +248,7 @@ public abstract class ClientDownloadPart extends ServeErrors implements Runnable } // send request try { - ProtocolP2PPacket d = createProtocolP2PPacket(new LoadRequest(filename, offset.longValue(), MAX_PARTIAL_SIZE)); + ProtocolP2PPacket d = createProtocolP2PPacket(new LoadRequest(filename, offset.longValue(), MAX_PARTIAL_SIZE, client)); d.sendRequest(getSocket()); return d; } catch (InternalError e) { @@ -255,6 +279,22 @@ public abstract class ClientDownloadPart extends ServeErrors implements Runnable } try { Payload p = d.receiveResponse().getPayload(); + if (p instanceof Denied) { + Denied denied = (Denied)p; + if (!denied.getFilename().equals(filename)) { + writeLog("wrong file deny response received: `" + denied.getFilename() + "`", LogLevel.Error); + return true; + } + Long offset = Long.valueOf(denied.getOffset()); + if (pendingTasks.contains(offset)) { + pendingTasks.remove(offset); + toDoTasks.add(offset); + return false; + } else { + writeLog("wrong file offset deny received: " + offset, LogLevel.Error); + return true; + } + } assert p instanceof FilePart : "This payload must be instance of FilePart"; if (!(p instanceof FilePart)) { writeLog("cannot get size.", LogLevel.Error); @@ -267,8 +307,10 @@ public abstract class ClientDownloadPart extends ServeErrors implements Runnable } Long offset = Long.valueOf(fp.getOffset()); if (pendingTasks.contains(offset)) { + byte[] partialContent = fp.getPartialContent(); try { - Files.write(new File(partsSubdir + filename + "_" + offset + ".part").toPath(), fp.getPartialContent()); + Files.write(new File(partsSubdir + filename + "_" + offset + ".part").toPath(), partialContent); + receivedBytesCount += partialContent.length; } catch (IOException e) { writeLog("cannot write file (" + partsSubdir + filename + "_" + offset + ".part)", LogLevel.Error); } diff --git a/src/clientP2P/ClientDownloadPartTCP.java b/src/clientP2P/ClientDownloadPartTCP.java index 7928735..f7dde9d 100644 --- a/src/clientP2P/ClientDownloadPartTCP.java +++ b/src/clientP2P/ClientDownloadPartTCP.java @@ -25,6 +25,7 @@ import remoteException.NotFound; import remoteException.NotATracker; import tools.Logger; import tools.LogLevel; +import tools.HostItem; import clientP2P.ClientDownloadPart; /** Class to download file parts on tcp. @@ -42,9 +43,11 @@ public class ClientDownloadPartTCP extends ClientDownloadPart { * @param socket socket to use * @param partsSubdir directory to store .part files * @param logger Logger + * @param client HostItem of the application + * @param server HostItem of the server application */ - public ClientDownloadPartTCP(ClientDownload manager, String filename, Socket socket, String partsSubdir, Logger logger) { - super(manager, filename, partsSubdir, logger); + public ClientDownloadPartTCP(ClientDownload manager, String filename, Socket socket, String partsSubdir, Logger logger, HostItem client, HostItem server) { + super(manager, filename, partsSubdir, logger, client, server); this.socket = socket; } diff --git a/src/clientP2P/ClientDownloadPartUDP.java b/src/clientP2P/ClientDownloadPartUDP.java index 0387c0c..3ab0a80 100644 --- a/src/clientP2P/ClientDownloadPartUDP.java +++ b/src/clientP2P/ClientDownloadPartUDP.java @@ -24,6 +24,7 @@ import java.io.File; import java.io.IOException; import tools.Logger; import tools.LogLevel; +import tools.HostItem; import clientP2P.ClientDownloadPart; /** Class to download file parts on udp. @@ -42,9 +43,11 @@ public class ClientDownloadPartUDP extends ClientDownloadPart { * @param socket socket to use * @param partsSubdir directory to store .part files * @param logger Logger + * @param client HostItem of the application + * @param server HostItem of the server application */ - public ClientDownloadPartUDP(ClientDownload manager, String filename, DatagramSocket socket, String partsSubdir, Logger logger) { - super(manager, filename, partsSubdir, logger); + public ClientDownloadPartUDP(ClientDownload manager, String filename, DatagramSocket socket, String partsSubdir, Logger logger, HostItem client, HostItem server) { + super(manager, filename, partsSubdir, logger, client, server); this.socket = socket; } diff --git a/src/clientP2P/ClientDownloadTCP.java b/src/clientP2P/ClientDownloadTCP.java index 5d32835..9be9afe 100644 --- a/src/clientP2P/ClientDownloadTCP.java +++ b/src/clientP2P/ClientDownloadTCP.java @@ -50,17 +50,18 @@ public class ClientDownloadTCP extends ClientDownload { * @param partsSubdir directory to store .part files * @param dirStorage directory to write assembled file * @param logger Logger + * @param client HostItem of the application + * @param tracker HostItem of the tracker */ - public ClientDownloadTCP(String filename, List hostList, String partsSubdir, String dirStorage, Logger logger) { - super(filename, hostList, partsSubdir, dirStorage, logger); + public ClientDownloadTCP(String filename, List hostList, String partsSubdir, String dirStorage, Logger logger, HostItem client, HostItem tracker) { + super(filename, hostList, partsSubdir, dirStorage, logger, client, tracker); } /** Create a clientDownloadPart - * @param filename name of the file to download * @param hostItem Hostitem of the server */ - protected ClientDownloadPart createDownloadPart(String filename, HostItem hostItem) { - return (ClientDownloadPart)new ClientDownloadPartTCP((ClientDownload)this, filename, hostItem.getTCPSocket(), partsSubdir, logger); + protected ClientDownloadPart createDownloadPart(HostItem hostItem) { + return (ClientDownloadPart)new ClientDownloadPartTCP((ClientDownload)this, filename, hostItem.getTCPSocket(), partsSubdir, logger, client, hostItem); } /** Close HostItem socket diff --git a/src/clientP2P/ClientDownloadUDP.java b/src/clientP2P/ClientDownloadUDP.java index 008caaf..ce37434 100644 --- a/src/clientP2P/ClientDownloadUDP.java +++ b/src/clientP2P/ClientDownloadUDP.java @@ -50,17 +50,18 @@ public class ClientDownloadUDP extends ClientDownload { * @param partsSubdir directory to store .part files * @param dirStorage directory to write assembled file * @param logger Logger + * @param client HostItem of the application + * @param tracker HostItem of the tracker */ - public ClientDownloadUDP(String filename, List hostList, String partsSubdir, String dirStorage, Logger logger) { - super(filename, hostList, partsSubdir, dirStorage, logger); + public ClientDownloadUDP(String filename, List hostList, String partsSubdir, String dirStorage, Logger logger, HostItem client, HostItem tracker) { + super(filename, hostList, partsSubdir, dirStorage, logger, client, tracker); } /** Create a clientDownloadPart - * @param filename name of the file to download * @param hostItem Hostitem of the server */ - protected ClientDownloadPart createDownloadPart(String filename, HostItem hostItem) { - return (ClientDownloadPart)new ClientDownloadPartUDP((ClientDownload)this, filename, hostItem.getUDPSocket(), partsSubdir, logger); + protected ClientDownloadPart createDownloadPart(HostItem hostItem) { + return (ClientDownloadPart)new ClientDownloadPartUDP((ClientDownload)this, filename, hostItem.getUDPSocket(), partsSubdir, logger, client, tracker); } /** Implementation of writeLog diff --git a/src/clientP2P/ClientManagement.java b/src/clientP2P/ClientManagement.java index cf1dad8..97f07d2 100644 --- a/src/clientP2P/ClientManagement.java +++ b/src/clientP2P/ClientManagement.java @@ -26,6 +26,7 @@ import remoteException.NotFound; import remoteException.ProtocolRemoteError; import remoteException.VersionRemoteError; import remoteException.NotATracker; +import remoteException.UnknownHost; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -46,6 +47,7 @@ public abstract class ClientManagement extends ServeErrors implements Runnable { protected String partsSubdir; protected List hostList; protected HostItem tracker; + protected HostItem client; protected Logger logger; protected Scanner scanner; protected ClientDownload downLoader; @@ -56,13 +58,15 @@ public abstract class ClientManagement extends ServeErrors implements Runnable { * @param partsSubdir subdirectory to store file parts * @param logger Loggger * @param scanner Scanner used to read input + * @param client HostItem of the application */ - public ClientManagement(String baseDirectory, HostItem tracker, String partsSubdir, Logger logger, Scanner scanner) { + public ClientManagement(String baseDirectory, HostItem tracker, String partsSubdir, Logger logger, Scanner scanner, HostItem client) { this.scanner = scanner; this.baseDirectory = baseDirectory; this.tracker = tracker; this.partsSubdir = partsSubdir; this.logger = logger; + this.client = client; try { initHostList(); } catch (InternalError e) { @@ -162,6 +166,9 @@ public abstract class ClientManagement extends ServeErrors implements Runnable { } catch (NotATracker e) { writeLog(e, LogLevel.Error); throw new ProtocolError(); + } catch (UnknownHost e) { + writeLog(e, LogLevel.Error); + throw new ProtocolError(); } } @@ -207,6 +214,9 @@ public abstract class ClientManagement extends ServeErrors implements Runnable { line += "\n"; writeLog(line, LogLevel.Info); throw new InternalError(); + } else { + downLoader.sendRatioUpdate(); + writeLog("Ratio updates sent.", LogLevel.Info); } } else { throw new InternalError(); diff --git a/src/clientP2P/ClientManagementTCP.java b/src/clientP2P/ClientManagementTCP.java index 559fd71..90f4c71 100644 --- a/src/clientP2P/ClientManagementTCP.java +++ b/src/clientP2P/ClientManagementTCP.java @@ -24,16 +24,17 @@ public class ClientManagementTCP extends ClientManagement { * @param partsSubdir subdirectory to store file parts * @param logger Loggger * @param scanner Scanner used to read input + * @param client HostItem of the application */ - public ClientManagementTCP(String baseDirectory, HostItem tracker, String partsSubdir, Logger logger, Scanner scanner) { - super(baseDirectory, tracker, partsSubdir, logger, scanner); + public ClientManagementTCP(String baseDirectory, HostItem tracker, String partsSubdir, Logger logger, Scanner scanner, HostItem client) { + super(baseDirectory, tracker, partsSubdir, logger, scanner, client); } /** Initialize downloader * @param filename Name of the file to download */ protected void initDownloader(String filename) { - downLoader = (ClientDownload) new ClientDownloadTCP(filename, hostList, partsSubdir, baseDirectory, logger); + downLoader = (ClientDownload) new ClientDownloadTCP(filename, hostList, partsSubdir, baseDirectory, logger, client, tracker); } diff --git a/src/clientP2P/ClientManagementUDP.java b/src/clientP2P/ClientManagementUDP.java index 19e7a0b..224a466 100644 --- a/src/clientP2P/ClientManagementUDP.java +++ b/src/clientP2P/ClientManagementUDP.java @@ -23,16 +23,17 @@ public class ClientManagementUDP extends ClientManagement { * @param partsSubdir subdirectory to store file parts * @param logger Loggger * @param scanner Scanner used to read input + * @param client HostItem of the application */ - public ClientManagementUDP(String baseDirectory, HostItem tracker, String partsSubdir, Logger logger, Scanner scanner) { - super(baseDirectory, tracker, partsSubdir, logger, scanner); + public ClientManagementUDP(String baseDirectory, HostItem tracker, String partsSubdir, Logger logger, Scanner scanner, HostItem client) { + super(baseDirectory, tracker, partsSubdir, logger, scanner, client); } /** Initialize downloader * @param filename Name of the file to download */ protected void initDownloader(String filename) { - downLoader = (ClientDownload) new ClientDownloadUDP(filename, hostList, partsSubdir, baseDirectory, logger); + downLoader = (ClientDownload) new ClientDownloadUDP(filename, hostList, partsSubdir, baseDirectory, logger, client, tracker); } /** Implementation of writeLog diff --git a/src/clientP2P/ClientP2P.java b/src/clientP2P/ClientP2P.java index 5985479..15c8acc 100644 --- a/src/clientP2P/ClientP2P.java +++ b/src/clientP2P/ClientP2P.java @@ -178,7 +178,7 @@ public class ClientP2P { case "upd": // to avoid users typos case "2" : System.out.println("Starting with UDP"); - ClientManagementUDP cmudp = new ClientManagementUDP(c.directories.getDataHomeDirectory(), c.tracker, c.directories.getDataHomeDirectory() + c.partsDir, c.loggerClient, c.scanner); + ClientManagementUDP cmudp = new ClientManagementUDP(c.directories.getDataHomeDirectory(), c.tracker, c.directories.getDataHomeDirectory() + c.partsDir, c.loggerClient, c.scanner, c.server); tclient = new Thread(cmudp); break; case "TCP": @@ -186,7 +186,7 @@ public class ClientP2P { case "1": default: System.out.println("Starting with TCP"); - ClientManagementTCP cmtcp = new ClientManagementTCP(c.directories.getDataHomeDirectory(), c.tracker, c.directories.getDataHomeDirectory() + c.partsDir, c.loggerClient, c.scanner); + ClientManagementTCP cmtcp = new ClientManagementTCP(c.directories.getDataHomeDirectory(), c.tracker, c.directories.getDataHomeDirectory() + c.partsDir, c.loggerClient, c.scanner, c.server); tclient = new Thread(cmtcp); break; } diff --git a/src/protocolP2P/Denied.java b/src/protocolP2P/Denied.java new file mode 100644 index 0000000..7b20eec --- /dev/null +++ b/src/protocolP2P/Denied.java @@ -0,0 +1,86 @@ +package protocolP2P; +import protocolP2P.Payload; +import protocolP2P.RequestResponseCode; +import localException.ProtocolError; +import localException.InternalError; +import localException.SizeError; +import localException.TransmissionError; +import tools.BytesArrayTools; + +/** Representation of payload for denied response. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class Denied extends Payload { + private String filename; + private long offset; + static final private int OFFSET_POSITION = PAYLOAD_START_POSITION; + static final private int FILENAME_POSITION = OFFSET_POSITION + 8; + + /** Constructor (typically used by server) with informations about file part to send as parameters. + * @param filename name of the file to send + * @param offset where in the file begins the part we are sending + * @throws InternalError + */ + public Denied(String filename, long offset) throws InternalError { + super(RequestResponseCode.DENIED); + /* asserts to help debugging */ + assert offset >= 0 : "offset cannot be negative"; + assert filename != null : "filename is required"; + if (offset < 0 || filename == null) { + throw new InternalError(); + } + this.filename = filename; + this.offset = offset; + } + + /** Constructor (typically used by client) with Packet received as parameter. + * @param packet the full Packet received + * @throws SizeError + * @throws InternalError + * @throws TransmissionError + */ + protected Denied(byte[] packet) throws TransmissionError, SizeError, ProtocolError, InternalError { + super(packet); + int filenameSize = getPayloadSize(packet) - FILENAME_POSITION + PAYLOAD_START_POSITION; + offset = BytesArrayTools.readLong(packet, OFFSET_POSITION); + filename = BytesArrayTools.readString(packet, FILENAME_POSITION, filenameSize); + } + + /** 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 total size + int size = FILENAME_POSITION + filename.length(); + byte[] packet = new byte[size]; // java initialize all to zero + // set request/response code + packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; + // set Payload size + setPayloadSize(size - PAYLOAD_START_POSITION, packet); + // write offset to Packet + BytesArrayTools.write(packet, OFFSET_POSITION, offset); + // write filename to Packet + BytesArrayTools.write(packet, filename, FILENAME_POSITION); + return packet; + } + + /** filename getter. + * @return String + */ + public String getFilename() { + return filename; + } + + /** offset getter. + * @return offset + */ + public long getOffset() { + return offset; + } +} diff --git a/src/protocolP2P/DiscoverRequest.java b/src/protocolP2P/DiscoverRequest.java index 7ef1751..b3cfa09 100644 --- a/src/protocolP2P/DiscoverRequest.java +++ b/src/protocolP2P/DiscoverRequest.java @@ -51,7 +51,7 @@ public class DiscoverRequest extends Payload { protected byte[] toPacket() throws InternalError { // compute total size int size = PAYLOAD_START_POSITION + filename.length(); - byte[] packet = new byte[size + 1]; // java initialize all to zero + byte[] packet = new byte[size]; // java initialize all to zero // set request/response code packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; // set Payload size diff --git a/src/protocolP2P/DiscoverResponse.java b/src/protocolP2P/DiscoverResponse.java index 4c73e36..6fa7bb5 100644 --- a/src/protocolP2P/DiscoverResponse.java +++ b/src/protocolP2P/DiscoverResponse.java @@ -56,7 +56,7 @@ public class DiscoverResponse extends Payload { int port = BytesArrayTools.readInt16Bits(packet, i); i += 2; String hostname = BytesArrayTools.readString(packet, i, "\n"); - i += hostname.length(); + i += hostname.length() + 1; // 1 for the "\n" hostList.add(new HostItem(hostname, port)); } } @@ -75,7 +75,7 @@ public class DiscoverResponse extends Payload { } // compute total size int size = FILENAME_POSITION + filename.length() + hostListSize; - byte[] packet = new byte[size + 1]; // java initialize all to zero + byte[] packet = new byte[size]; // java initialize all to zero // set request/response code packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; // set Payload size diff --git a/src/protocolP2P/FilePart.java b/src/protocolP2P/FilePart.java index d168a05..808972c 100644 --- a/src/protocolP2P/FilePart.java +++ b/src/protocolP2P/FilePart.java @@ -15,12 +15,10 @@ import tools.BytesArrayTools; */ public class FilePart extends Payload { private String filename; - private long totalSize; private long offset; private byte[] partialContent; static final private int OFFSET_POSITION = PAYLOAD_START_POSITION; - static final private int TOTAL_FILESIZE_POSITION = OFFSET_POSITION + 8; - static final private int FILENAME_SIZE_POSITION = TOTAL_FILESIZE_POSITION + 8; + static final private int FILENAME_SIZE_POSITION = OFFSET_POSITION + 8; static final private int FILENAME_POSITION = FILENAME_SIZE_POSITION + 4; /** Constructor (typically used by server) with informations about file part to send as parameters. @@ -30,20 +28,16 @@ public class FilePart extends Payload { * @param partialContent content of the file we send * @throws InternalError */ - public FilePart(String filename, long totalSize, long offset, byte[] partialContent) throws InternalError { + public FilePart(String filename, 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) { + if (partialContent.length == 0 || offset < 0 || filename == null) { throw new InternalError(); } this.filename = filename; - this.totalSize = totalSize; this.offset = offset; this.partialContent = partialContent; } @@ -63,7 +57,6 @@ public class FilePart extends Payload { throw new InternalError(); } setOffset(packet); // this can throw SizeError - setTotalSize(packet); // this can throw SizeError setFilename(packet); // this can throw ProtocolError, SizeError setPartialContent(packet); // this can throw SizeError } @@ -77,15 +70,13 @@ public class FilePart extends Payload { protected byte[] toPacket() throws InternalError { // compute total size int size = FILENAME_POSITION + filename.length() + partialContent.length; - byte[] packet = new byte[size + 1]; // java initialize all to zero + byte[] packet = new byte[size]; // java initialize all to zero // set request/response code packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; // set Payload size setPayloadSize(size - PAYLOAD_START_POSITION, packet); // write offset to Packet BytesArrayTools.write(packet, OFFSET_POSITION, offset); - // write totalSize to Packet - BytesArrayTools.write(packet, TOTAL_FILESIZE_POSITION, totalSize); // write filename’s size to Packet BytesArrayTools.write(packet, FILENAME_SIZE_POSITION, filename.length()); // write filename to Packet @@ -102,13 +93,6 @@ public class FilePart extends Payload { private void setOffset(byte[] packet) throws SizeError { offset = BytesArrayTools.readLong(packet, OFFSET_POSITION); } - /** Write from Packet into totalSize. - * @param packet received Packet - * @throws SizeError - */ - private void setTotalSize(byte[] packet) throws SizeError { - totalSize = BytesArrayTools.readLong(packet, TOTAL_FILESIZE_POSITION); - } /** Read filename’s size from Packet. * @param packet received Packet @@ -171,11 +155,4 @@ public class FilePart extends Payload { public long getOffset() { return offset; } - - /** totalSize getter. - * @return totalSize - */ - public long getTotalSize() { - return totalSize; - } } diff --git a/src/protocolP2P/HashRequest.java b/src/protocolP2P/HashRequest.java index 1eec671..cad74f7 100644 --- a/src/protocolP2P/HashRequest.java +++ b/src/protocolP2P/HashRequest.java @@ -90,7 +90,7 @@ public class HashRequest extends Payload { i++; } int size = FILENAME_POSITION + filenameSize + BytesArrayTools.computeStringArraySize(algoListStr, "\n"); - byte[] packet = new byte[size + 1]; // java initialize all to zero + byte[] packet = new byte[size]; // java initialize all to zero // set request/response code packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; // set Payload size diff --git a/src/protocolP2P/HashResponse.java b/src/protocolP2P/HashResponse.java index 743b13e..041c8a3 100644 --- a/src/protocolP2P/HashResponse.java +++ b/src/protocolP2P/HashResponse.java @@ -98,7 +98,7 @@ public class HashResponse extends Payload { for (byte[] s : hashes.values()) { size += 4 + s.length; } - byte[] packet = new byte[size + 1]; // java initialize all to zero + byte[] packet = new byte[size]; // java initialize all to zero // set request/response code packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; diff --git a/src/protocolP2P/LoadRequest.java b/src/protocolP2P/LoadRequest.java index 86f0b75..2834f9b 100644 --- a/src/protocolP2P/LoadRequest.java +++ b/src/protocolP2P/LoadRequest.java @@ -6,6 +6,7 @@ import localException.ProtocolError; import localException.InternalError; import localException.SizeError; import tools.BytesArrayTools; +import tools.HostItem; /** Representation of payload for load request. * @author Louis Royer @@ -17,18 +18,20 @@ public class LoadRequest extends Payload { private String filename; private long maxSizePartialContent; private long offset; + private HostItem hostItem; static final private int OFFSET_POSITION = PAYLOAD_START_POSITION; static final private int MAX_SIZE_PARTIAL_CONTENT_POSITION = OFFSET_POSITION + 8; static final private int FILENAME_SIZE_POSITION = MAX_SIZE_PARTIAL_CONTENT_POSITION + 8; static final private int FILENAME_POSITION = FILENAME_SIZE_POSITION + 4; - /** Constructor (typically used by the server) with a filename parameter. + /** Constructor (typically used by the client) with a filename parameter. * @param filename name of the file to download. Must not be empty. * @param offset offset of the bloc * @param maxSizePartialContent partial content in response should not excess this size, but this can be less (by example if endoffile is reached or server doesn't have the full block requested) + * @param hostItem hostItem used by the client to register on the tracker * @throws InternalError */ - public LoadRequest(String filename, long offset, long maxSizePartialContent) throws InternalError { + public LoadRequest(String filename, long offset, long maxSizePartialContent, HostItem hostItem) throws InternalError { super(RequestResponseCode.LOAD_REQUEST); /* assert to help debugging */ assert filename.length() != 0 : "Payload size of LoadRequest must not be empty"; @@ -38,9 +41,10 @@ public class LoadRequest extends Payload { this.filename = filename; this.maxSizePartialContent = maxSizePartialContent; this.offset = offset; + this.hostItem = hostItem; } - /** Constructor (typically used by client) with a byte[] parameter containing the Packet received. + /** Constructor (typically used by server) with a byte[] parameter containing the Packet received. * @param packet the full Packet received * @throws SizeError * @throws InternalError @@ -64,6 +68,12 @@ public class LoadRequest extends Payload { /* Read filename */ int size = BytesArrayTools.readInt(packet, FILENAME_SIZE_POSITION); filename = BytesArrayTools.readString(packet, FILENAME_POSITION, size); + + /* Read hostItem */ + int portPosition = FILENAME_POSITION + size; + int hostnameStartPosition = portPosition + 2; + int hostnameSize = getPayloadSize(packet) - hostnameStartPosition + PAYLOAD_START_POSITION; + hostItem = new HostItem(BytesArrayTools.readString(packet, hostnameStartPosition, hostnameSize), BytesArrayTools.readInt16Bits(packet, portPosition)); } /** Returns a byte[] containing Packet with padding. @@ -75,8 +85,9 @@ public class LoadRequest extends Payload { protected byte[] toPacket() throws InternalError { // compute size int filenameSize = filename.length(); - int size = FILENAME_POSITION + filenameSize; - byte[] packet = new byte[size + 1]; // java initialize all to zero + String hostname = hostItem.getHostname(); + int size = FILENAME_POSITION + filenameSize + 2 + hostname.length(); + byte[] packet = new byte[size]; // java initialize all to zero // set request/response code packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; // set Payload size @@ -89,7 +100,15 @@ public class LoadRequest extends Payload { BytesArrayTools.write(packet, FILENAME_SIZE_POSITION, filenameSize); // Write filename BytesArrayTools.write(packet, filename, FILENAME_POSITION); - return packet; + // Write hostitem + int portPosition = FILENAME_POSITION + filenameSize; + try { + BytesArrayTools.write16Bits(packet, portPosition, hostItem.getPort()); + BytesArrayTools.write(packet, hostname, portPosition + 2); + return packet; + } catch (SizeError e) { + throw new InternalError(); + } } /** filename getter. @@ -112,4 +131,11 @@ public class LoadRequest extends Payload { public long getMaxSizePartialContent() { return maxSizePartialContent; } + + /** hostItem getter. + * @return hostItem + */ + public HostItem getHostItem() { + return hostItem; + } } diff --git a/src/protocolP2P/Payload.java b/src/protocolP2P/Payload.java index 990df6b..6fca87d 100644 --- a/src/protocolP2P/Payload.java +++ b/src/protocolP2P/Payload.java @@ -9,6 +9,12 @@ import protocolP2P.DiscoverRequest; import protocolP2P.DiscoverResponse; import protocolP2P.Register; import protocolP2P.Unregister; +import protocolP2P.RatioRequest; +import protocolP2P.RatioResponse; +import protocolP2P.UpdateRatio; +import protocolP2P.Denied; +import protocolP2P.SizeRequest; +import protocolP2P.SizeResponse; import localException.ProtocolError; import localException.InternalError; import localException.TransmissionError; @@ -40,6 +46,12 @@ public class Payload { assert requestResponseCode != RequestResponseCode.DISCOVER_RESPONSE || (this instanceof DiscoverResponse) : "DISCOVER_RESPONSE must use DiscoverResponse class"; assert requestResponseCode != RequestResponseCode.REGISTER || (this instanceof Register) : "REGISTER must use Register class"; assert requestResponseCode != RequestResponseCode.UNREGISTER || (this instanceof Unregister) : "UNREGISTER must use Unregister class"; + assert requestResponseCode != RequestResponseCode.RATIO_REQUEST || (this instanceof RatioRequest) : "RATIO_REQUEST must use RatioRequest class"; + assert requestResponseCode != RequestResponseCode.RATIO_RESPONSE || (this instanceof RatioResponse) : "RATIO_RESPONSE must use RatioResponse class"; + assert requestResponseCode != RequestResponseCode.UPDATE_RATIO || (this instanceof UpdateRatio) : "UPDATE_RATIO must use UpdateRatio class"; + assert requestResponseCode != RequestResponseCode.DENIED || (this instanceof Denied) : "DENIED must use Denied class"; + assert requestResponseCode != RequestResponseCode.SIZE_REQUEST || (this instanceof SizeRequest) : "SIZE_REQUEST must use SizeRequest class"; + assert requestResponseCode != RequestResponseCode.SIZE_RESPONSE || (this instanceof SizeResponse) : "SIZE_RESPONSE must use SizeResponse class"; this.requestResponseCode = requestResponseCode; checkRequestResponseCode(); // this can throw InternalError } @@ -67,6 +79,12 @@ public class Payload { assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.DISCOVER_RESPONSE || (this instanceof DiscoverResponse) : "DISCOVER_RESPONSE must use DiscoverResponse class"; assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.REGISTER || (this instanceof Register) : "REGISTER must use Register class"; assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.UNREGISTER || (this instanceof Unregister) : "UNREGISTER must use Unregister class"; + assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.RATIO_REQUEST || (this instanceof RatioRequest) : "RATIO_REQUEST must use RatioRequest class"; + assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.RATIO_RESPONSE || (this instanceof RatioResponse) : "RATIO_RESPONSE must use RatioResponse class"; + assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.UPDATE_RATIO || (this instanceof UpdateRatio) : "UPDATE_RATIO must use UpdateRatio class"; + assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.DENIED || (this instanceof Denied) : "DENIED must use Denied class"; + assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.SIZE_REQUEST || (this instanceof SizeRequest) : "SIZE_REQUEST must use SizeRequest class"; + assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.SIZE_RESPONSE || (this instanceof SizeResponse) : "SIZE_RESPONSE must use SizeResponse class"; requestResponseCode = RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]); checkRequestResponseCode(); // this can throw InternalError } @@ -85,6 +103,12 @@ public class Payload { || (requestResponseCode == RequestResponseCode.DISCOVER_RESPONSE && !(this instanceof DiscoverResponse)) || (requestResponseCode == RequestResponseCode.REGISTER && !(this instanceof Register)) || (requestResponseCode == RequestResponseCode.UNREGISTER && !(this instanceof Unregister)) + || (requestResponseCode == RequestResponseCode.RATIO_REQUEST && !(this instanceof RatioRequest)) + || (requestResponseCode == RequestResponseCode.RATIO_RESPONSE && !(this instanceof RatioResponse)) + || (requestResponseCode == RequestResponseCode.UPDATE_RATIO && !(this instanceof UpdateRatio)) + || (requestResponseCode == RequestResponseCode.DENIED && !(this instanceof Denied)) + || (requestResponseCode == RequestResponseCode.SIZE_REQUEST && !(this instanceof SizeRequest)) + || (requestResponseCode == RequestResponseCode.SIZE_RESPONSE && !(this instanceof SizeResponse)) ) { throw new InternalError(); } diff --git a/src/protocolP2P/ProtocolP2PPacket.java b/src/protocolP2P/ProtocolP2PPacket.java index db9c4b4..dd1cb79 100644 --- a/src/protocolP2P/ProtocolP2PPacket.java +++ b/src/protocolP2P/ProtocolP2PPacket.java @@ -12,6 +12,7 @@ import remoteException.ProtocolRemoteError; import remoteException.VersionRemoteError; import remoteException.EmptyFile; import remoteException.NotATracker; +import remoteException.UnknownHost; import java.io.IOException; import tools.HostItem; @@ -72,8 +73,9 @@ public abstract class ProtocolP2PPacket < T extends Payload>{ * @throws SizeError * @throws IOException * @throws SocketClosed + * @throws UnknownHost */ - public abstract ProtocolP2PPacket receiveResponse() throws EmptyFile, NotFound, NotATracker, EmptyDirectory, InternalRemoteError, VersionRemoteError, ProtocolRemoteError, TransmissionError, ProtocolError, VersionError, InternalError, SizeError, IOException, SocketClosed; + public abstract ProtocolP2PPacket receiveResponse() throws EmptyFile, NotFound, NotATracker, EmptyDirectory, InternalRemoteError, VersionRemoteError, ProtocolRemoteError, TransmissionError, ProtocolError, VersionError, InternalError, SizeError, IOException, SocketClosed, UnknownHost; /** Receive a request, subclasses must overwrite this constructor. * @param socket socket used to get the request diff --git a/src/protocolP2P/ProtocolP2PPacketTCP.java b/src/protocolP2P/ProtocolP2PPacketTCP.java index 708ee38..69c8e6e 100644 --- a/src/protocolP2P/ProtocolP2PPacketTCP.java +++ b/src/protocolP2P/ProtocolP2PPacketTCP.java @@ -12,12 +12,19 @@ import remoteException.NotFound; import remoteException.ProtocolRemoteError; import remoteException.VersionRemoteError; import remoteException.EmptyFile; +import remoteException.UnknownHost; import tools.HostItem; import protocolP2P.Payload; import protocolP2P.RequestResponseCode; import protocolP2P.LoadRequest; import protocolP2P.FileList; import protocolP2P.FilePart; +import protocolP2P.RatioRequest; +import protocolP2P.RatioResponse; +import protocolP2P.UpdateRatio; +import protocolP2P.Denied; +import protocolP2P.SizeRequest; +import protocolP2P.SizeResponse; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; @@ -141,6 +148,9 @@ public class ProtocolP2PPacketTCP < T extends Payload > extends ProtocolP2PPacke case HASH_RESPONSE: case DISCOVER_RESPONSE: case NOT_A_TRACKER: + case RATIO_RESPONSE: + case DENIED: + case SIZE_RESPONSE: // we were expecting a request, but we are receiving a response throw new ProtocolError(); default : @@ -203,8 +213,9 @@ public class ProtocolP2PPacketTCP < T extends Payload > extends ProtocolP2PPacke * @throws SizeError * @throws IOException * @throws SocketClosed + * @throws UnknownHost */ - public ProtocolP2PPacket receiveResponse() throws EmptyFile, NotFound, NotATracker, EmptyDirectory, InternalRemoteError, VersionRemoteError, ProtocolRemoteError, TransmissionError, ProtocolError, VersionError, InternalError, SizeError, IOException, SocketClosed { + public ProtocolP2PPacket receiveResponse() throws EmptyFile, NotFound, NotATracker, EmptyDirectory, InternalRemoteError, VersionRemoteError, ProtocolRemoteError, TransmissionError, ProtocolError, VersionError, InternalError, SizeError, IOException, SocketClosed, UnknownHost { assert requestSocket != null : "Cannot receive response because request packet not sent."; if (requestSocket == null) { throw new InternalError(); @@ -244,6 +255,8 @@ public class ProtocolP2PPacketTCP < T extends Payload > extends ProtocolP2PPacke throw new EmptyFile(); case NOT_A_TRACKER: throw new NotATracker(); + case UNKNOWN_HOST: + throw new UnknownHost(); default : return (ProtocolP2PPacket)p; } @@ -319,6 +332,24 @@ public class ProtocolP2PPacketTCP < T extends Payload > extends ProtocolP2PPacke case DISCOVER_RESPONSE: payload = (Payload) new DiscoverResponse(packet); break; + case RATIO_REQUEST: + payload = (Payload) new RatioRequest(packet); + break; + case RATIO_RESPONSE: + payload = (Payload) new RatioResponse(packet); + break; + case UPDATE_RATIO: + payload = (Payload) new UpdateRatio(packet); + break; + case DENIED: + payload = (Payload) new Denied(packet); + break; + case SIZE_REQUEST: + payload = (Payload) new SizeRequest(packet); + break; + case SIZE_RESPONSE: + payload = (Payload) new SizeResponse(packet); + break; default: payload = new Payload(packet); // this can throw TransmissionError break; diff --git a/src/protocolP2P/ProtocolP2PPacketUDP.java b/src/protocolP2P/ProtocolP2PPacketUDP.java index a54733b..0b639fe 100644 --- a/src/protocolP2P/ProtocolP2PPacketUDP.java +++ b/src/protocolP2P/ProtocolP2PPacketUDP.java @@ -12,6 +12,7 @@ import remoteException.NotFound; import remoteException.ProtocolRemoteError; import remoteException.VersionRemoteError; import remoteException.EmptyFile; +import remoteException.UnknownHost; import tools.BytesArrayTools; import tools.HostItem; import protocolP2P.Payload; @@ -19,6 +20,12 @@ import protocolP2P.RequestResponseCode; import protocolP2P.LoadRequest; import protocolP2P.FileList; import protocolP2P.FilePart; +import protocolP2P.RatioRequest; +import protocolP2P.RatioResponse; +import protocolP2P.UpdateRatio; +import protocolP2P.Denied; +import protocolP2P.SizeRequest; +import protocolP2P.SizeResponse; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketAddress; @@ -134,6 +141,9 @@ public class ProtocolP2PPacketUDP < T extends Payload> extends ProtocolP2PPacket case HASH_RESPONSE: case DISCOVER_RESPONSE: case NOT_A_TRACKER: + case RATIO_RESPONSE: + case DENIED: + case SIZE_RESPONSE: // we were expecting a request, but we are receiving a response throw new ProtocolError(); default : @@ -198,8 +208,9 @@ public class ProtocolP2PPacketUDP < T extends Payload> extends ProtocolP2PPacket * @throws InternalError * @throws SizeError * @throws IOException + * @throws UnknownHost */ - public ProtocolP2PPacket receiveResponse() throws EmptyFile, NotFound, NotATracker, EmptyDirectory, InternalRemoteError, VersionRemoteError, ProtocolRemoteError, TransmissionError, ProtocolError, VersionError, InternalError, SizeError, IOException { + public ProtocolP2PPacket receiveResponse() throws EmptyFile, NotFound, NotATracker, EmptyDirectory, InternalRemoteError, VersionRemoteError, ProtocolRemoteError, TransmissionError, ProtocolError, VersionError, InternalError, SizeError, IOException, UnknownHost { assert requestSocket != null : "Cannot receive response because request packet not sent."; if (requestSocket == null) { throw new InternalError(); @@ -228,6 +239,8 @@ public class ProtocolP2PPacketUDP < T extends Payload> extends ProtocolP2PPacket throw new EmptyFile(); case NOT_A_TRACKER: throw new NotATracker(); + case UNKNOWN_HOST: + throw new UnknownHost(); default : return (ProtocolP2PPacket)p; } @@ -304,6 +317,24 @@ public class ProtocolP2PPacketUDP < T extends Payload> extends ProtocolP2PPacket case DISCOVER_RESPONSE: payload = (Payload) new DiscoverResponse(packet); break; + case RATIO_REQUEST: + payload = (Payload) new RatioRequest(packet); + break; + case RATIO_RESPONSE: + payload = (Payload) new RatioResponse(packet); + break; + case UPDATE_RATIO: + payload = (Payload) new UpdateRatio(packet); + break; + case DENIED: + payload = (Payload) new Denied(packet); + break; + case SIZE_REQUEST: + payload = (Payload) new SizeRequest(packet); + break; + case SIZE_RESPONSE: + payload = (Payload) new SizeResponse(packet); + break; default: payload = new Payload(packet); // this can throw TransmissionError break; diff --git a/src/protocolP2P/RatioRequest.java b/src/protocolP2P/RatioRequest.java new file mode 100644 index 0000000..ace53c7 --- /dev/null +++ b/src/protocolP2P/RatioRequest.java @@ -0,0 +1,77 @@ +package protocolP2P; +import protocolP2P.Payload; +import tools.HostItem; +import tools.BytesArrayTools; +import localException.SizeError; +import localException.InternalError; +import localException.TransmissionError; +import localException.ProtocolError; + +/** Representation of payload for ratio request. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class RatioRequest extends Payload { + private HostItem hostItem; + private static final int HOSTNAME_START_POSITION = PAYLOAD_START_POSITION + 2; + + /** Constructor with hostItem (typically used by client/server) + * @param hostItem Host to get ratio of. + * @throws InternalError + */ + public RatioRequest(HostItem hostItem) throws InternalError { + super(RequestResponseCode.RATIO_REQUEST); + this.hostItem = hostItem; + } + + /** Constructor (typically used by tracker) with a byte[] parameter containing the Packet received. + * @param packet the full Packet received + * @throws SizeError + * @throws InternalError + * @throws ProtocolError + * @throws TransmissionError + */ + protected RatioRequest(byte[] packet) throws SizeError, ProtocolError, InternalError, TransmissionError { + super(packet); + int size = getPayloadSize(packet); + int port = BytesArrayTools.readInt16Bits(packet, PAYLOAD_START_POSITION); + String hostname = BytesArrayTools.readString(packet, HOSTNAME_START_POSITION, size - HOSTNAME_START_POSITION + PAYLOAD_START_POSITION); + hostItem = new HostItem(hostname, port); + } + + /** 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 { + String hostname = hostItem.getHostname(); + // compute total size + int size = HOSTNAME_START_POSITION + hostname.length(); + byte[] packet = new byte[size]; // java initialize all to zero + // set request/response code + packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; + // set Payload size + setPayloadSize(size - PAYLOAD_START_POSITION, packet); + // write port to Packet + try { + BytesArrayTools.write16Bits(packet, PAYLOAD_START_POSITION, hostItem.getPort()); + } catch (SizeError e) { + throw new InternalError(); + } + // write hostname to Packet + BytesArrayTools.write(packet, hostname, HOSTNAME_START_POSITION); + return packet; + } + + /** HostItem getter. + * @return hostItem + */ + public HostItem getHostItem() { + return hostItem; + } + +} diff --git a/src/protocolP2P/RatioResponse.java b/src/protocolP2P/RatioResponse.java new file mode 100644 index 0000000..b59d7f6 --- /dev/null +++ b/src/protocolP2P/RatioResponse.java @@ -0,0 +1,106 @@ +package protocolP2P; +import protocolP2P.Payload; +import tools.HostItem; +import tools.BytesArrayTools; +import localException.SizeError; +import localException.InternalError; +import localException.TransmissionError; +import localException.ProtocolError; + +/** Representation of payload for ratio response. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class RatioResponse extends Payload { + private HostItem hostItem; + private long totalUp; + private long totalDown; + private static final int TOTAL_UP_START_POSITION = PAYLOAD_START_POSITION; + private static final int TOTAL_DOWN_START_POSITION = TOTAL_UP_START_POSITION + 8; + private static final int PORT_START_POSITION = TOTAL_DOWN_START_POSITION + 8; + private static final int HOSTNAME_START_POSITION = PORT_START_POSITION + 2; + + /** Constructor with hostItem (typically used by tracker) + * @param hostItem Host to get ratio of. + * @param totalUp total bytes uploaded + * @param totalDown total bytes downloaded + * @throws InternalError + */ + public RatioResponse(HostItem hostItem, long totalUp, long totalDown) throws InternalError { + super(RequestResponseCode.RATIO_RESPONSE); + this.hostItem = hostItem; + this.totalUp = totalUp; + this.totalDown = totalDown; + } + + /** Constructor (typically used by client/server) with a byte[] parameter containing the Packet received. + * @param packet the full Packet received + * @throws SizeError + * @throws InternalError + * @throws ProtocolError + * @throws TransmissionError + */ + protected RatioResponse(byte[] packet) throws SizeError, ProtocolError, InternalError, TransmissionError { + super(packet); + int size = getPayloadSize(packet); + totalUp = BytesArrayTools.readLong(packet, TOTAL_UP_START_POSITION); + totalDown = BytesArrayTools.readLong(packet, TOTAL_DOWN_START_POSITION); + int port = BytesArrayTools.readInt16Bits(packet, PORT_START_POSITION); + String hostname = BytesArrayTools.readString(packet, HOSTNAME_START_POSITION, size - HOSTNAME_START_POSITION + PAYLOAD_START_POSITION); + hostItem = new HostItem(hostname, port); + } + + /** 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 { + String hostname = hostItem.getHostname(); + // compute total size + int size = HOSTNAME_START_POSITION + hostname.length(); + byte[] packet = new byte[size]; // java initialize all to zero + // set request/response code + packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; + // set Payload size + setPayloadSize(size - PAYLOAD_START_POSITION, packet); + // write totalUp + BytesArrayTools.write(packet, TOTAL_UP_START_POSITION, totalUp); + // write totalDown + BytesArrayTools.write(packet, TOTAL_DOWN_START_POSITION, totalDown); + // write port to Packet + try { + BytesArrayTools.write16Bits(packet, PORT_START_POSITION, hostItem.getPort()); + } catch (SizeError e) { + throw new InternalError(); + } + // write hostname to Packet + BytesArrayTools.write(packet, hostname, HOSTNAME_START_POSITION); + return packet; + } + + /** HostItem getter. + * @return hostItem + */ + public HostItem getHostItem() { + return hostItem; + } + + /** totalUp getter. + * @return totalUp + */ + public long getTotalUp() { + return totalUp; + } + + /** totalDown getter. + * @return totalDown + */ + public long getTotalDown() { + return totalDown; + } + +} diff --git a/src/protocolP2P/Register.java b/src/protocolP2P/Register.java index 790ac2b..3de3328 100644 --- a/src/protocolP2P/Register.java +++ b/src/protocolP2P/Register.java @@ -51,7 +51,7 @@ public class Register extends Payload { String hostname = hostItem.getHostname(); // compute total size int size = HOSTNAME_START_POSITION + hostname.length(); - byte[] packet = new byte[size + 1]; // java initialize all to zero + byte[] packet = new byte[size]; // java initialize all to zero // set request/response code packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; // set Payload size diff --git a/src/protocolP2P/RequestResponseCode.java b/src/protocolP2P/RequestResponseCode.java index f216d8c..7a51a7d 100644 --- a/src/protocolP2P/RequestResponseCode.java +++ b/src/protocolP2P/RequestResponseCode.java @@ -18,17 +18,24 @@ public enum RequestResponseCode { DISCOVER_REQUEST(CodeType.REQUEST_TRACKER, (byte)0x03), REGISTER(CodeType.REQUEST_TRACKER, (byte)0x04), UNREGISTER(CodeType.REQUEST_TRACKER, (byte)0x05), + RATIO_REQUEST(CodeType.REQUEST_TRACKER, (byte)0x06), + UPDATE_RATIO(CodeType.REQUEST_TRACKER, (byte)0x07), + SIZE_REQUEST(CodeType.REQUEST, (byte)0x08), LIST_RESPONSE(CodeType.RESPONSE, (byte)0x80), LOAD_RESPONSE(CodeType.RESPONSE, (byte)0x81), HASH_RESPONSE(CodeType.RESPONSE, (byte)0x82), DISCOVER_RESPONSE(CodeType.RESPONSE, (byte)0x83), + RATIO_RESPONSE(CodeType.RESPONSE, (byte)0x86), + DENIED(CodeType.RESPONSE, (byte)0x87), + SIZE_RESPONSE(CodeType.RESPONSE, (byte)0x88), 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), - NOT_A_TRACKER(CodeType.ERROR, (byte)0xC6); + NOT_A_TRACKER(CodeType.ERROR, (byte)0xC6), + UNKNOWN_HOST(CodeType.ERROR, (byte)0xC7); public final CodeType codeType; public final byte codeValue; diff --git a/src/protocolP2P/SizeRequest.java b/src/protocolP2P/SizeRequest.java new file mode 100644 index 0000000..0e23b5c --- /dev/null +++ b/src/protocolP2P/SizeRequest.java @@ -0,0 +1,70 @@ +package protocolP2P; +import protocolP2P.Payload; +import protocolP2P.RequestResponseCode; +import localException.TransmissionError; +import localException.ProtocolError; +import localException.InternalError; +import localException.SizeError; +import tools.BytesArrayTools; +import tools.HostItem; + +/** Representation of payload for load request. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class SizeRequest extends Payload { + private String filename; + /** Constructor (typically used by the client) with a filename parameter. + * @param filename name of the file to download. Must not be empty. + */ + public SizeRequest(String filename) throws InternalError { + super(RequestResponseCode.SIZE_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 server) with a byte[] parameter containing the Packet received. + * @param packet the full Packet received + * @throws SizeError + * @throws InternalError + * @throws ProtocolError + * @throws TransmissionError + */ + protected SizeRequest(byte[] packet) throws TransmissionError, SizeError, ProtocolError, InternalError { + super(packet); + /* Read filename */ + int size = getPayloadSize(packet); + filename = BytesArrayTools.readString(packet, PAYLOAD_START_POSITION, 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 { + int size = PAYLOAD_START_POSITION + filename.length(); + byte[] packet = new byte[size]; // java initialize all to zero + // set request/response code + packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; + // set Payload size + setPayloadSize(size - PAYLOAD_START_POSITION, packet); + // Write filename + BytesArrayTools.write(packet, filename, PAYLOAD_START_POSITION); + return packet; + } + + /** filename getter. + * @return filename + */ + public String getFilename() { + return filename; + } +} diff --git a/src/protocolP2P/SizeResponse.java b/src/protocolP2P/SizeResponse.java new file mode 100644 index 0000000..f02b01b --- /dev/null +++ b/src/protocolP2P/SizeResponse.java @@ -0,0 +1,86 @@ +package protocolP2P; +import protocolP2P.Payload; +import protocolP2P.RequestResponseCode; +import localException.ProtocolError; +import localException.InternalError; +import localException.SizeError; +import localException.TransmissionError; +import tools.BytesArrayTools; + +/** Representation of payload for size response. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class SizeResponse extends Payload { + private String filename; + private long totalSize; + static final private int TOTAL_SIZE_POSITION = PAYLOAD_START_POSITION; + static final private int FILENAME_POSITION = TOTAL_SIZE_POSITION + 8; + + /** Constructor (typically used by server) with informations about file part to send as parameters. + * @param filename name of the file + * @param totalSize size of the file + * @throws InternalError + */ + public SizeResponse(String filename, long totalSize) throws InternalError { + super(RequestResponseCode.SIZE_RESPONSE); + /* asserts to help debugging */ + assert totalSize >= 0 : "offset cannot be negative"; + assert filename != null : "filename is required"; + if (filename == null || totalSize < 0) { + throw new InternalError(); + } + this.filename = filename; + this.totalSize = totalSize; + } + + /** Constructor (typically used by client) with Packet received as parameter. + * @param packet the full Packet received + * @throws SizeError + * @throws InternalError + * @throws TransmissionError + */ + protected SizeResponse(byte[] packet) throws TransmissionError, SizeError, ProtocolError, InternalError { + super(packet); + int filenameSize = getPayloadSize(packet) - FILENAME_POSITION + PAYLOAD_START_POSITION; + totalSize = BytesArrayTools.readLong(packet, TOTAL_SIZE_POSITION); + filename = BytesArrayTools.readString(packet, FILENAME_POSITION, filenameSize); + } + + /** 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 total size + int size = FILENAME_POSITION + filename.length(); + byte[] packet = new byte[size]; // java initialize all to zero + // set request/response code + packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; + // set Payload size + setPayloadSize(size - PAYLOAD_START_POSITION, packet); + // write totalSize to Packet + BytesArrayTools.write(packet, TOTAL_SIZE_POSITION, totalSize); + // write filename to Packet + BytesArrayTools.write(packet, filename, FILENAME_POSITION); + return packet; + } + + /** filename getter. + * @return String + */ + public String getFilename() { + return filename; + } + + /** totalSize getter. + * @return totalSize + */ + public long getTotalSize() { + return totalSize; + } +} diff --git a/src/protocolP2P/Unregister.java b/src/protocolP2P/Unregister.java index 8307a5b..88294f9 100644 --- a/src/protocolP2P/Unregister.java +++ b/src/protocolP2P/Unregister.java @@ -51,7 +51,7 @@ public class Unregister extends Payload { String hostname = hostItem.getHostname(); // compute total size int size = HOSTNAME_START_POSITION + hostname.length(); - byte[] packet = new byte[size + 1]; // java initialize all to zero + byte[] packet = new byte[size]; // java initialize all to zero // set request/response code packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; // set Payload size diff --git a/src/protocolP2P/UpdateRatio.java b/src/protocolP2P/UpdateRatio.java new file mode 100644 index 0000000..334cda0 --- /dev/null +++ b/src/protocolP2P/UpdateRatio.java @@ -0,0 +1,112 @@ +package protocolP2P; +import protocolP2P.Payload; +import tools.HostItem; +import tools.BytesArrayTools; +import localException.SizeError; +import localException.InternalError; +import localException.TransmissionError; +import localException.ProtocolError; + +/** Representation of payload for update ratio. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class UpdateRatio extends Payload { + private HostItem client; + private HostItem server; + private long dataSize; + private static final int DATA_SIZE_POSITION = PAYLOAD_START_POSITION; + private static final int SERVER_PORT_START_POSITION = DATA_SIZE_POSITION + 8; + private static final int CLIENT_PORT_START_POSITION = SERVER_PORT_START_POSITION + 2; + private static final int HOSTNAMES_START_POSITION = CLIENT_PORT_START_POSITION + 2; + + /** Constructor with hostItem (typically used by client) + * @param client HostItem of the client application. + * @param server HostItem of the server application. + * @param dataSize size of data sent. + * @throws InternalError + */ + public UpdateRatio(HostItem client, HostItem server, long dataSize) throws InternalError { + super(RequestResponseCode.UPDATE_RATIO); + this.client = client; + this.server = server; + this.dataSize = dataSize; + } + + /** Constructor (typically used by tracker) with a byte[] parameter containing the Packet received. + * @param packet the full Packet received + * @throws SizeError + * @throws InternalError + * @throws ProtocolError + * @throws TransmissionError + */ + protected UpdateRatio(byte[] packet) throws SizeError, ProtocolError, InternalError, TransmissionError { + super(packet); + int size = getPayloadSize(packet); + dataSize = BytesArrayTools.readLong(packet, DATA_SIZE_POSITION); + int portServer = BytesArrayTools.readInt16Bits(packet, SERVER_PORT_START_POSITION); + int portClient = BytesArrayTools.readInt16Bits(packet, CLIENT_PORT_START_POSITION); + String[] hostnames = BytesArrayTools.readStringArray(packet, HOSTNAMES_START_POSITION, size - HOSTNAMES_START_POSITION + PAYLOAD_START_POSITION, "\n"); + server = new HostItem(hostnames[0], portServer); + client = new HostItem(hostnames[1], portClient); + } + + /** 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 { + String[] hostnames = { server.getHostname(), client.getHostname()}; + // compute total size + int size = HOSTNAMES_START_POSITION + BytesArrayTools.computeStringArraySize(hostnames, "\n"); + byte[] packet = new byte[size]; // java initialize all to zero + // set request/response code + packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; + // set Payload size + setPayloadSize(size - PAYLOAD_START_POSITION, packet); + // write dataSize + BytesArrayTools.write(packet, DATA_SIZE_POSITION, dataSize); + // write server port to Packet + try { + BytesArrayTools.write16Bits(packet, SERVER_PORT_START_POSITION, server.getPort()); + } catch (SizeError e) { + throw new InternalError(); + } + // write client port to Packet + try { + BytesArrayTools.write16Bits(packet, CLIENT_PORT_START_POSITION, client.getPort()); + } catch (SizeError e) { + throw new InternalError(); + } + // write hostnames to Packet + BytesArrayTools.write(packet, hostnames, HOSTNAMES_START_POSITION, "\n"); + return packet; + } + + /** Client getter. + * @return client + */ + public HostItem getClient() { + return client; + } + + /** Server getter + * @return server + */ + public HostItem getServer() { + return server; + } + + /** dataSize getter. + * @return dataSize + */ + public long getDataSize() { + return dataSize; + } + + +} diff --git a/src/remoteException/UnknownHost.java b/src/remoteException/UnknownHost.java new file mode 100644 index 0000000..0c2ccad --- /dev/null +++ b/src/remoteException/UnknownHost.java @@ -0,0 +1,7 @@ +package remoteException; + +import exception.RemoteException; + +public class UnknownHost extends RemoteException { + private static final long serialVersionUID = 12L; +} diff --git a/src/serverP2P/FileWatcher.java b/src/serverP2P/FileWatcher.java index d6bf632..39877d2 100644 --- a/src/serverP2P/FileWatcher.java +++ b/src/serverP2P/FileWatcher.java @@ -30,6 +30,7 @@ public abstract class FileWatcher implements Runnable { protected HostItem tracker; protected String baseDirectory; protected Map sha512 = new HashMap<>(); + protected Thread thread; /** Constructor @@ -83,7 +84,7 @@ public abstract class FileWatcher implements Runnable { try { Thread.sleep(time); } catch(InterruptedException e) { - writeLog("File list watcher interrupted", LogLevel.Error); + writeLog("File list watcher interrupted", LogLevel.Info); setStop(); } } @@ -123,6 +124,9 @@ public abstract class FileWatcher implements Runnable { */ public void setStop() { stop = true; + if (thread != null) { + thread.interrupt(); + } } /** Init sha512 map. @@ -153,4 +157,11 @@ public abstract class FileWatcher implements Runnable { */ protected abstract void writeLog(Exception e, LogLevel logLevel); + /** Set thread + * @param thread Thread + */ + public void setThread(Thread thread) { + this.thread = thread; + } + } diff --git a/src/serverP2P/RatioWatcher.java b/src/serverP2P/RatioWatcher.java new file mode 100644 index 0000000..884c818 --- /dev/null +++ b/src/serverP2P/RatioWatcher.java @@ -0,0 +1,177 @@ +package serverP2P; +import tools.Logger; +import tools.LogLevel; +import tools.HostItem; +import java.util.Map; +import java.util.HashMap; +import java.io.IOException; +import exception.RemoteException; +import exception.LocalException; +import protocolP2P.RatioRequest; +import protocolP2P.RatioResponse; +import protocolP2P.Payload; +import protocolP2P.ProtocolP2PPacket; +import remoteException.UnknownHost; +import remoteException.NotATracker; +import localException.InternalError; + +/** Class allowing to keep the tracker informed about ratios + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public abstract class RatioWatcher implements Runnable { + final static double punishmentFactor = 1.2; + protected Logger logger; + protected volatile boolean stop; + protected long time; + protected boolean force; + protected HostItem tracker; + protected Thread thread; + protected Map cachePunishmentProbability = new HashMap<>(); + protected boolean lock; + + + /** Constructor + * @param logger Logger + * @param millis Time interval before recheck + * @param tracker HostItem for the tracker + */ + public RatioWatcher(Logger logger, long millis, HostItem tracker) { + assert logger != null : "Logger is null"; + assert tracker != null : "Tracker is null"; + this.logger = logger; + time = millis; + this.tracker = tracker; + lock = false; + } + + + /** Runnable implementation */ + public void run() { + writeLog("Ratio watcher started : delay " + time + " milliseconds.", LogLevel.Info); + while(!stop) { + try { + clean(); + Thread.sleep(time); + } catch(InterruptedException e) { + writeLog("Ratio watcher interrupted", LogLevel.Info); + setStop(); + } + } + } + + /** Invalidate the cache by cleaning all hashmaps + * @throws InterruptedException + */ + protected synchronized void clean() throws InterruptedException{ + while(lock) { + this.wait(); + } + lock = true; + cachePunishmentProbability.clear(); + lock = false; + this.notifyAll(); + } + + + /** Get Up-ratio for an applications + * @param application HostItem of the application + * @return Punishment Probability + * @throws UnknownHost + */ + protected synchronized double getPunishmentProbability(HostItem application) throws InternalError, UnknownHost { + try { + while(lock) { + this.wait(); + } + lock = true; + if (!cachePunishmentProbability.containsKey(application)) { + // update if not in cache + try { + ProtocolP2PPacket p = createProtocolP2PPacket(new RatioRequest(application)); + p.sendRequest(getTrackerSocket()); + Payload resp = p.receiveResponse().getPayload(); + if (!(resp instanceof RatioResponse)) { + throw new InternalError(); + } + RatioResponse rresp = (RatioResponse)resp; + if (!rresp.getHostItem().equals(application)) { + writeLog("Ratio response host is not the expected one. Expected : " + + application + ". Received : " + rresp.getHostItem(), LogLevel.Debug); + throw new InternalError(); + } + long up = rresp.getTotalUp(); + long down = rresp.getTotalDown(); + assert punishmentFactor > 1 : "The punishment factor must be greater than 1"; + if (down == 0 || (punishmentFactor * up) >= down) { + cachePunishmentProbability.put(application, Double.valueOf(0)); + } else { + cachePunishmentProbability.put(application, Double.valueOf((down - up)/(down * punishmentFactor))); + } + } catch (UnknownHost e) { + throw e; + } catch (IOException e) { + throw new InternalError(); + } catch (LocalException e) { + writeLog(e, LogLevel.Error); + throw new InternalError(); + } catch (RemoteException e) { + writeLog(e, LogLevel.Error); + throw new InternalError(); + } + } + double ret = cachePunishmentProbability.get(application); + lock = false; + this.notifyAll(); + return ret; + } catch (InterruptedException e) { + throw new InternalError(); + } + } + + /** Ask the thread to stop + */ + public void setStop() { + stop = true; + if (thread != null) { + thread.interrupt(); + } + } + + /** Implementation of writeLog + * @param text Text to log + * @param logLevel level of logging + */ + protected abstract void writeLog(String text, LogLevel logLevel); + + /** Implementation of writeLog + * @param e exception to log + * @param logLevel level of logging + */ + protected abstract void writeLog(Exception e, LogLevel logLevel); + + /** Set thread + * @param thread Thread + */ + public void setThread(Thread thread) { + this.thread = thread; + } + + /** Create packets + * @param payload Payload + */ + protected abstract < T extends Payload > ProtocolP2PPacket createProtocolP2PPacket(T payload); + + /** Tracker socket getter + * @return tracker socket + */ + protected abstract Object getTrackerSocket(); + + /** Closes tracker socket + */ + protected abstract void closeTrackerSocket(); + + +} diff --git a/src/serverP2P/RatioWatcherTCP.java b/src/serverP2P/RatioWatcherTCP.java new file mode 100644 index 0000000..5184ce4 --- /dev/null +++ b/src/serverP2P/RatioWatcherTCP.java @@ -0,0 +1,66 @@ +package serverP2P; +import tools.Logger; +import tools.LogLevel; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.ProtocolP2PPacketTCP; +import protocolP2P.Payload; +import tools.HostItem; +import serverP2P.RatioWatcher; + +/** Class allowing to keep the tracker informed about file list (TCP impl.) + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class RatioWatcherTCP extends RatioWatcher { + + /** Constructor + * @param logger Logger + * @param millis Time interval before recheck + * @param tracker HostItem for the tracker + */ + public RatioWatcherTCP(Logger logger, long millis, HostItem tracker) { + super(logger, millis, tracker); + assert logger != null : "Logger is null"; + assert tracker != null : "Tracker is null"; + } + + /** Implementation of writeLog + * @param text Text to log + * @param logLevel level of logging + */ + protected void writeLog(String text, LogLevel logLevel) { + logger.writeTCP(text, logLevel); + } + + /** Implementation of writeLog + * @param e exception to log + * @param logLevel level of logging + */ + protected void writeLog(Exception e, LogLevel logLevel) { + logger.writeTCP(e, logLevel); + } + + /** Create packets + * @param payload Payload + */ + protected < T extends Payload > ProtocolP2PPacket createProtocolP2PPacket(T payload) { + return (ProtocolP2PPacket)new ProtocolP2PPacketTCP(payload); + } + + /** Tracker socket getter + * @return tracker socket + */ + protected Object getTrackerSocket() { + return tracker.getTCPSocket(); + } + + /** Closes tracker socket + */ + protected void closeTrackerSocket() { + tracker.closeTCPSocket(); + } + + +} diff --git a/src/serverP2P/RatioWatcherUDP.java b/src/serverP2P/RatioWatcherUDP.java new file mode 100644 index 0000000..dca0789 --- /dev/null +++ b/src/serverP2P/RatioWatcherUDP.java @@ -0,0 +1,64 @@ +package serverP2P; +import tools.Logger; +import tools.LogLevel; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.ProtocolP2PPacketUDP; +import protocolP2P.Payload; +import tools.HostItem; +import serverP2P.RatioWatcher; + +/** Class allowing to keep the tracker informed about file list (UDP impl.) + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class RatioWatcherUDP extends RatioWatcher { + + /** Constructor + * @param logger Logger + * @param millis Time interval before recheck + * @param tracker HostItem for the tracker + */ + public RatioWatcherUDP(Logger logger, long millis, HostItem tracker) { + super(logger, millis, tracker); + assert logger != null : "Logger is null"; + assert tracker != null : "Tracker is null"; + } + + /** Implementation of writeLog + * @param text Text to log + * @param logLevel level of logging + */ + protected void writeLog(String text, LogLevel logLevel) { + logger.writeUDP(text, logLevel); + } + + /** Implementation of writeLog + * @param e exception to log + * @param logLevel level of logging + */ + protected void writeLog(Exception e, LogLevel logLevel) { + logger.writeUDP(e, logLevel); + } + + /** Create packets + * @param payload Payload + */ + protected < T extends Payload > ProtocolP2PPacket createProtocolP2PPacket(T payload) { + return (ProtocolP2PPacket)new ProtocolP2PPacketUDP(payload); + } + + /** Tracker socket getter + * @return tracker socket + */ + protected Object getTrackerSocket() { + return tracker.getUDPSocket(); + } + + /** Closes tracker socket + */ + protected void closeTrackerSocket() { + tracker.closeUDPSocket(); + } +} diff --git a/src/serverP2P/ServerManagement.java b/src/serverP2P/ServerManagement.java index de4fdd2..af7ed6f 100644 --- a/src/serverP2P/ServerManagement.java +++ b/src/serverP2P/ServerManagement.java @@ -1,5 +1,6 @@ package serverP2P; import serverP2P.FileWatcher; +import serverP2P.RatioWatcher; import tools.Logger; import tools.LogLevel; import tools.HostItem; @@ -14,13 +15,20 @@ import protocolP2P.HashRequest; import protocolP2P.HashResponse; import protocolP2P.HashAlgorithm; import protocolP2P.Unregister; +import protocolP2P.SizeRequest; +import protocolP2P.SizeResponse; +import protocolP2P.Denied; import java.nio.file.Paths; import java.nio.file.Files; +import java.io.File; import java.util.Arrays; import java.util.Map; import java.util.HashMap; +import java.util.Random; import java.io.IOException; import exception.LocalException; +import localException.InternalError; +import remoteException.UnknownHost; /** Implementation of P2P-JAVA-PROJECT VERSION 1.0 protocol. * @author Louis Royer @@ -36,6 +44,8 @@ public abstract class ServerManagement extends ServeErrors implements Runnable { protected String baseDirectory; protected HostItem server; protected HostItem tracker; + protected Random punisher = new Random(); + protected RatioWatcher ratioWatcher; /** Constructor */ public ServerManagement(String baseDirectory, HostItem server, HostItem tracker, Logger logger) { @@ -54,8 +64,15 @@ public abstract class ServerManagement extends ServeErrors implements Runnable { /** Stop the thread */ public void setStop() { stop = true; + fileListWatcher.setStop(); + ratioWatcher.setStop(); + sendUnregisterRequest(); + closeSocket(); } + /** Closes socket */ + protected abstract void closeSocket(); + /** Trigger a manual check of the file list */ public void updateFileList() { @@ -136,27 +153,48 @@ public abstract class ServerManagement extends ServeErrors implements Runnable { long offset = ((LoadRequest)p).getOffset(); long maxSizePartialContent = ((LoadRequest)p).getMaxSizePartialContent(); try { - byte[] fullLoad = Files.readAllBytes(Paths.get(baseDirectory + filename)); - long sizeToSend = 0; - if (fullLoad.length - offset < maxSizePartialContent) { - writeLog("Sending last partialContent", LogLevel.Debug); - sizeToSend = fullLoad.length - offset; - } else { - sizeToSend = maxSizePartialContent; - } - writeLog("maxSizePartialContent: " + maxSizePartialContent, LogLevel.Debug); - writeLog("Sending " + filename + " from " + offset + " to " + (offset + sizeToSend), LogLevel.Debug); - byte[] load = Arrays.copyOfRange(fullLoad, (int)offset, (int)(offset + sizeToSend)); String[] fileList = fileListWatcher.getFileList(); if (Arrays.binarySearch(fileList, filename) >= 0) { try { - if (load.length == 0) { - sendEmptyFile(pd); + double proba = ratioWatcher.getPunishmentProbability(((LoadRequest)p).getHostItem()); + if (punisher.nextDouble() <= proba) { + writeLog("Sending punishment", LogLevel.Debug); + pd.sendResponse(createProtocolP2PPacket(new Denied(filename, offset))); } else { - pd.sendResponse(createProtocolP2PPacket((Payload)(new FilePart(filename, fullLoad.length, offset, load)))); + byte[] fullLoad = Files.readAllBytes(Paths.get(baseDirectory + filename)); + long sizeToSend = 0; + if (fullLoad.length - offset < maxSizePartialContent) { + writeLog("Sending last partialContent", LogLevel.Debug); + sizeToSend = fullLoad.length - offset; + } else { + sizeToSend = maxSizePartialContent; + } + writeLog("maxSizePartialContent: " + maxSizePartialContent, LogLevel.Debug); + writeLog("Sending " + filename + " from " + offset + " to " + (offset + sizeToSend), LogLevel.Debug); + byte[] load = Arrays.copyOfRange(fullLoad, (int)offset, (int)(offset + sizeToSend)); + try { + if (load.length == 0) { + sendEmptyFile(pd); + } else { + pd.sendResponse(createProtocolP2PPacket((Payload)(new FilePart(filename, offset, load)))); + } + } catch (Exception e2) { + writeLog(e2, LogLevel.Error); + } } - } catch (Exception e2) { - writeLog(e2, LogLevel.Error); + } catch (InternalError e) { + writeLog("InternalError", LogLevel.Debug); + writeLog(e, LogLevel.Debug); + sendInternalError(pd); + return; + } catch (UnknownHost e) { + writeLog("Unknown host: " + ((LoadRequest)p).getHostItem(), LogLevel.Debug); + writeLog(e, LogLevel.Debug); + sendInternalError(pd); + return; + } catch(LocalException e) { + sendInternalError(pd); + return; } } else { writeLog("File requested not found: `" + filename + "` " + Arrays.binarySearch(fileList, filename), LogLevel.Debug); @@ -177,6 +215,42 @@ public abstract class ServerManagement extends ServeErrors implements Runnable { } } + /** Send response to size request + * @param pd Request received + */ + protected < T extends ProtocolP2PPacket > void sendSizeResponse(T pd) { + Payload p = pd.getPayload(); + assert p instanceof SizeRequest : "payload must be an instance of SizeRequest"; + if (!(p instanceof SizeRequest)) { + sendInternalError(pd); + } else { + String filename = ((SizeRequest)p).getFilename(); + try { + long size = (new File(baseDirectory + filename)).length(); + String[] fileList = fileListWatcher.getFileList(); + if (Arrays.binarySearch(fileList, filename) >= 0) { + try { + if (size == 0) { + sendEmptyFile(pd); + } else { + pd.sendResponse(createProtocolP2PPacket((new SizeResponse(filename, size)))); + } + } catch (Exception e2) { + writeLog(e2, LogLevel.Error); + } + } else { + throw new IOException(); // to send a NOT_FOUND in the catch block + } + } catch (IOException e) { + try { + sendNotFound(pd); + } catch (Exception e2) { + writeLog(e2, LogLevel.Debug); + } + } + } + } + /** Getter for tracker socket */ @@ -205,6 +279,10 @@ public abstract class ServerManagement extends ServeErrors implements Runnable { writeLog("Received LOAD_REQUEST from host " + pd.getHostItem(), LogLevel.Action); sendLoadResponse(pd); break; + case SIZE_REQUEST: + writeLog("Received SIZE_REQUEST from host " + pd.getHostItem(), LogLevel.Action); + sendSizeResponse(pd); + break; case LIST_REQUEST: writeLog("Received LIST_REQUEST from host " + pd.getHostItem(), LogLevel.Action); sendListResponse(pd); diff --git a/src/serverP2P/ServerManagementTCP.java b/src/serverP2P/ServerManagementTCP.java index fad6ddf..da84132 100644 --- a/src/serverP2P/ServerManagementTCP.java +++ b/src/serverP2P/ServerManagementTCP.java @@ -79,18 +79,22 @@ public class ServerManagementTCP extends ServerManagement { public void run() { writeLog("Server sucessfully started", LogLevel.Info); fileListWatcher = (FileWatcher)new FileWatcherTCP(logger, 10000, server, tracker, baseDirectory); // checking every 10 seconds - (new Thread(fileListWatcher)).start(); + Thread flwt = new Thread(fileListWatcher); + flwt.start(); + fileListWatcher.setThread(flwt); + ratioWatcher = (RatioWatcher)new RatioWatcherTCP(logger, 10000, tracker); + Thread rwt = new Thread(ratioWatcher); + rwt.start(); + ratioWatcher.setThread(rwt); while(!stop) { try { Socket s = socket.accept(); ClientHandler c = new ClientHandler(s); (new Thread(c)).start(); } catch (IOException e) { - writeLog("Error while accepting new connection", LogLevel.Warning); + writeLog("Socket has been closed", LogLevel.Info); } } - fileListWatcher.setStop(); - sendUnregisterRequest(); } /** Private runnable class allowing to serve one client. @@ -162,4 +166,13 @@ public class ServerManagementTCP extends ServerManagement { protected Object getTrackerSocket() { return (Object)tracker.getTCPSocket(); } + + /** Closes socket */ + protected void closeSocket() { + try { + socket.close(); + } catch (IOException e) { + writeLog("Could not close socket", LogLevel.Error); + } + } } diff --git a/src/serverP2P/ServerManagementUDP.java b/src/serverP2P/ServerManagementUDP.java index 7ea6b39..7e541a9 100644 --- a/src/serverP2P/ServerManagementUDP.java +++ b/src/serverP2P/ServerManagementUDP.java @@ -74,7 +74,13 @@ public class ServerManagementUDP extends ServerManagement { public void run() { logger.writeUDP("Server sucessfully started", LogLevel.Info); fileListWatcher = (FileWatcher)new FileWatcherUDP(logger, 10000, server, tracker, baseDirectory); // checking every 10 seconds - (new Thread(fileListWatcher)).start(); + Thread flwt = new Thread(fileListWatcher); + flwt.start(); + fileListWatcher.setThread(flwt); + ratioWatcher = (RatioWatcher)new RatioWatcherUDP(logger, 10000, tracker); + Thread rwt = new Thread(ratioWatcher); + rwt.start(); + ratioWatcher.setThread(rwt); while(!stop) { try { ProtocolP2PPacketUDP pd = new ProtocolP2PPacketUDP<>((Object)socket); @@ -83,8 +89,6 @@ public class ServerManagementUDP extends ServerManagement { } catch (LocalException e) { } } - fileListWatcher.setStop(); - sendUnregisterRequest(); } @@ -116,4 +120,9 @@ public class ServerManagementUDP extends ServerManagement { protected Object getTrackerSocket() { return (Object)tracker.getUDPSocket(); } + + /** Closes socket */ + protected void closeSocket() { + socket.close(); + } } diff --git a/src/tools/HostItem.java b/src/tools/HostItem.java index 95960af..3467a73 100644 --- a/src/tools/HostItem.java +++ b/src/tools/HostItem.java @@ -128,7 +128,7 @@ public class HostItem { boolean result = false; if (other instanceof HostItem) { HostItem that = (HostItem) other; - result = this.getHostname() == that.getHostname() && this.getPort() == that.getPort(); + result = this.getHostname().equals(that.getHostname()) && this.getPort() == that.getPort(); } return result; } diff --git a/src/tools/Logger.java b/src/tools/Logger.java index ce0f82e..b08f15a 100644 --- a/src/tools/Logger.java +++ b/src/tools/Logger.java @@ -34,7 +34,7 @@ public class Logger { * @param text Text to log */ public void write(String text, LogLevel logLevel) { - String colorize = "\u001B[0"; + String colorize = "\u001B[0m"; String msg = "[" + new Timestamp(System.currentTimeMillis()) + "] " + text + "\n"; String level = null; switch (logLevel) { @@ -55,6 +55,7 @@ public class Logger { break; case Debug: level = "[Debug]"; + colorize = "\u001B[36m"; // CYAN break; default: System.err.println("Error: incorrect logLevel"); diff --git a/src/tools/ServeErrors.java b/src/tools/ServeErrors.java index ff083f9..4392aa6 100644 --- a/src/tools/ServeErrors.java +++ b/src/tools/ServeErrors.java @@ -79,4 +79,15 @@ public abstract class ServeErrors { } } + /** Send an unknown host message. + * @param pd Request received + */ + protected < T extends ProtocolP2PPacket > void sendUnknownHost(T pd) { + try { + pd.sendResponse(createProtocolP2PPacket(new Payload(RequestResponseCode.UNKNOWN_HOST))); + } catch (Exception e) { + writeLog(e, LogLevel.Error); + } + } + } diff --git a/src/tracker/TrackerManagement.java b/src/tracker/TrackerManagement.java index 37c3cbc..4d1a37a 100644 --- a/src/tracker/TrackerManagement.java +++ b/src/tracker/TrackerManagement.java @@ -15,6 +15,9 @@ import protocolP2P.FileList; import protocolP2P.Unregister; import protocolP2P.Register; import protocolP2P.RequestResponseCode; +import protocolP2P.RatioRequest; +import protocolP2P.RatioResponse; +import protocolP2P.UpdateRatio; import localException.InternalError; import remoteException.EmptyDirectory; import exception.LocalException; @@ -30,6 +33,8 @@ public abstract class TrackerManagement extends ServeErrors implements Runnable protected HostItem tracker; protected Logger logger; protected List hostList = new ArrayList<>(); + protected Map ratioUp = new HashMap<>(); + protected Map ratioDown = new HashMap<>(); protected Map> fileList = new HashMap<>(); protected volatile boolean stop; @@ -62,6 +67,38 @@ public abstract class TrackerManagement extends ServeErrors implements Runnable } } + + /** Handle Ratio request + * @param pd Received request + * @throws InternalError + */ + protected < T extends ProtocolP2PPacket > void handleRatio(T pd) throws InternalError { + Payload p = pd.getPayload(); + assert p instanceof RatioRequest : "payload must be an instance of RatioRequest"; + if (!(p instanceof RatioRequest)) { + sendInternalError(pd); + } else { + HostItem host = ((RatioRequest)p).getHostItem(); + writeLog("Ratio request for host " + host, LogLevel.Debug); + try { + if (!hostList.contains(host)) { + String l = ""; + for (HostItem h: hostList) { + l += h + " "; + } + writeLog(host + " is not in hostlist [ " + l + "]", LogLevel.Debug); + pd.sendResponse(createProtocolP2PPacket(new Payload(RequestResponseCode.UNKNOWN_HOST))); + } else { + pd.sendResponse(createProtocolP2PPacket(new RatioResponse(host, + ratioUp.get(host).longValue(), + ratioDown.get(host).longValue()))); + } + } catch (Exception e) { + writeLog(e, LogLevel.Error); + } + } + } + /** Handle List Responses * @param pd Received response * @throws InternalError @@ -108,6 +145,10 @@ public abstract class TrackerManagement extends ServeErrors implements Runnable fileList.remove(f); } } + + /* Note: we do not remove host from ratioUp and ratioDown since Unregistering is only here to indicate + * the host is temporary not serving any files (ie. is down). + */ } /** Getter for HostItem socket @@ -120,6 +161,30 @@ public abstract class TrackerManagement extends ServeErrors implements Runnable */ protected abstract void closeHostItemSocket(HostItem hostItem); + /** Handle Update Ratio + * @param pd Received request + * @throws InternalError + */ + protected < T extends ProtocolP2PPacket > void handleUpdateRatio(T pd) throws InternalError { + Payload p = pd.getPayload(); + assert p instanceof UpdateRatio : "payload must be an instance of UpdateRatio"; + if (!(p instanceof UpdateRatio)) { + throw new InternalError(); + } + + UpdateRatio updateRatio = (UpdateRatio) p; + HostItem updateRatioServer = updateRatio.getServer(); + HostItem updateRatioClient = updateRatio.getClient(); + long ratioSize = updateRatio.getDataSize(); + writeLog("Ratio += " + ratioSize + ", client: " + updateRatioClient + " / server: " + updateRatioServer, LogLevel.Debug); + if (!ratioDown.containsKey(updateRatioClient) || ! ratioUp.containsKey(updateRatioServer)) { + sendUnknownHost(pd); + } else { + ratioDown.put(updateRatioClient, Long.valueOf(ratioDown.get(updateRatioClient).longValue() + ratioSize)); + ratioUp.put(updateRatioServer, Long.valueOf(ratioUp.get(updateRatioServer).longValue() + ratioSize)); + } + } + /** Handle Registering * @param pd Received request * @throws InternalError @@ -135,6 +200,11 @@ public abstract class TrackerManagement extends ServeErrors implements Runnable if (!hostList.contains(host)) { hostList.add(host); } + + // initialize ratios if this is a new host + ratioUp.putIfAbsent(host, Long.valueOf(0)); + ratioDown.putIfAbsent(host, Long.valueOf(0)); + // send a list request try { ProtocolP2PPacket pLReq = createProtocolP2PPacket(new Payload(RequestResponseCode.LIST_REQUEST)); @@ -186,6 +256,14 @@ public abstract class TrackerManagement extends ServeErrors implements Runnable writeLog("Received DISCOVER REQUEST from host " + pd.getHostItem(), LogLevel.Action); handleDiscover(pd); break; + case RATIO_REQUEST: + writeLog("Received RATIO REQUEST from host " + pd.getHostItem(), LogLevel.Action); + handleRatio(pd); + break; + case UPDATE_RATIO: + writeLog("Received UPDATE RATIO from host " + pd.getHostItem(), LogLevel.Action); + handleUpdateRatio(pd); + break; default: writeLog("Received grabbage from host " + pd.getHostItem(), LogLevel.Action); sendInternalError(pd); diff --git a/src/tracker/TrackerManagementTCP.java b/src/tracker/TrackerManagementTCP.java index 603bab5..0f8abec 100644 --- a/src/tracker/TrackerManagementTCP.java +++ b/src/tracker/TrackerManagementTCP.java @@ -86,11 +86,11 @@ public class TrackerManagementTCP extends TrackerManagement { public void run() { boolean end = false; - writeLog("[ " + addr + "] New connection", LogLevel.Action); + writeLog("[" + addr + "] New connection", LogLevel.Action); do { end = handleClientRequest(); } while(!end); - writeLog("[ " + addr + "] End of connection", LogLevel.Action); + writeLog("[" + addr + "] End of connection", LogLevel.Action); } /** Respond to next request incomming on socket s.