diff --git a/Jenkinsfile b/Jenkinsfile index 9e9770e..399f133 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,6 +11,10 @@ sh 'echo "BUILDING SERVER"' sh 'echo Main-Class: serverP2P/ServerP2P > MANIFEST.MF' sh 'jar -cvmf MANIFEST.MF server.jar $(find bin/ -maxdepth 1 -mindepth 1 -printf "-C bin %f\n")' + sh 'echo "BUILDING TRACKER"' + sh 'echo Main-Class: tracker/Tracker > MANIFEST.MF' + sh 'jar -cvmf MANIFEST.MF tracker.jar $(find bin/ -maxdepth 1 -mindepth 1 -printf "-C bin %f\n")' + sh 'echo "CREATING sources.tar.gz"' sh 'tar -zcvf sources.tar.gz src/' } } @@ -24,6 +28,7 @@ success { archiveArtifacts artifacts: 'client.jar', fingerprint: true archiveArtifacts artifacts: 'server.jar', fingerprint: true + archiveArtifacts artifacts: 'tracker.jar', fingerprint: true archiveArtifacts artifacts: 'sources.tar.gz', fingerprint: true } } diff --git a/README.md b/README.md index 3a7cac2..00e9a8c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,12 @@ Lien vers le [document original](https://stri-online.net/FTLV/mod/resource/view. **But** : le but de ce projet est de créer une application répartie en Java de téléchargement de fichier en mode P2P (peer to peer ou poste à poste). Les étapes suivantes sont conseillées. +# Usage + +tracker : java tracker.Tracker (interactive) or java trackerP2P.trackerP2P -- (default port 6969 (range 6000 -> 6999)) +server : java serveurP2P.ServeurP2P (interactive) or java serveurP2P.ServeurP2P -- (default server port: server 7070 (range 7000->7070) and tracker port 6969 (range 7000 -> 7999)) +client/serveur : java clientP2P.ClientP2P or java clientP2P.ClientP2P -- (default tracker port 6969 (range 7000 -> 7999) and server port: server 7070 (range 7000->7070)) + ## Étape 1 : Téléchargement à la FTP La première étape doit permettre de télécharger un fichier en intégralité d'une machine vers une autre machine de façon similaire aux applications suivant le protocole FTP. @@ -47,4 +53,4 @@ Options : - Permettre la recherche de fichiers à partir de leur nom ou de toute autre caractéristique. À l'issu de la recherche on devra pouvoir connaître un ensemble d'application possédant le fichier et commencer le téléchargement. - Gérer le protocole d'une application de téléchargement P2P existante (bittorrent, emule ou autre). -Note : toute fonctionnalité supplémentaire ne sera prise en compte dans la notation que si toutes les étapes ont été correctement traitées. \ No newline at end of file +Note : toute fonctionnalité supplémentaire ne sera prise en compte dans la notation que si toutes les étapes ont été correctement traitées. diff --git a/doc/protocol.md b/doc/protocol.md index b21a615..64dae57 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -17,17 +17,22 @@ x bytes: [(bytes 8-?): PAYLOAD] - `LIST` (0x00) - `LOAD` (0x01) - `HASH` (0x02) + - `DISCOVER` (0x03) + - `REGISTER` (0x04) + - `UNREGISTER` (0x05) - RESPONSES (msb is 1): - `LIST` (0x80) - `LOAD` (0x81) - `HASH` (0x82) + - `DISCOVER` (0x83) - `VERSION ERROR` (0xC0) - `PROTOCOL ERROR` (0xC1) - `INTERNAL ERROR` (0xC2) - `EMPTY DIRECTORY` (0xC3) - `NOT FOUND` (0xC4) - `EMPTY FILE` (0xC5) + - `NOT A TRACKER` (0xC6) ### List Payload size for list request is always zero. @@ -97,6 +102,55 @@ A algo hash bloc contains: ``` +### Tracker specific messages +#### Register +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: + +``` +2 bytes: [] +``` + +#### Unregister +Used by a server to unregister itself from a tracker. +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: + +``` +2 bytes: [] +? bytes: [] +``` + +#### Discover request +If payload size is null, lists all servers registered. +If payload contains a filename, list all servers having this file in their list. + +``` +? bytes: [] +? bytes: [] +``` + +#### Discover response +Contains: +``` +4 bytes: [(bytes 8-11): FILENAME SIZE] +y bytes: [] +? bytes [multiple server blocks] +``` + +Server block is composed with: + +``` +2 bytes: [port] +? bytes: hostname +\n +``` + +#### Not a Tracker +This error is raised when receiving a DISCOVER, a REGISTER, or an UNREGISTER request, +but this application is not a tracker. ### Other response code (errors) #### Version error diff --git a/doc/tracker_sequence_diagram.drawio b/doc/tracker_sequence_diagram.drawio new file mode 100644 index 0000000..85919a8 --- /dev/null +++ b/doc/tracker_sequence_diagram.drawio @@ -0,0 +1 @@ +7Vtfc+I2EP80zFwfkrEt28BjQkh60+tdEtJrp2/CFqBGWFSI/LlP3xWWDZbAEJ8hZpo8MNJqvZJ2f1rtSkoL9aYvNwLPJr/zmLCW58QvLXTV8rxO2IFfRXhNCX63mxLGgsYpyV0RBvQH0URHUxc0JvMCo+ScSTorEiOeJCSSBRoWgj8X2UacFXud4TGxCIMIM5v6J43lRFNdx1k1/EroeKK77gS6YYijx7Hgi0T31/LQaPmXNk9xJkvzzyc45s9rJNRvoZ7gXKal6UuPMKXaTG3pd9dbWvNxC5LIfT5oD/EQu5E/jLz2yB86Z14q4QmzhdbFg4A5EaHHK18zHcHQZ6q4mLIvdEQYTaB2OSOCTokEfnTFNPl2Rbt8nlBJBjMcqU+fATxAm8gpg5oLRTCoxPCJyOuM4dmcDpe9OkARJFqIOX0i92Se4kZR+UKqnno5Hpasyg4k1qJyVTtLuVMa6TLDQ8Iuc8P1OOOq+4QvJzSXgj/mKFCCRjDGazylTIH7OxExTrAmayS7YMdLzOg4gUoEplhO3baNNtcTEZK8rJG0rW4IB7WJV2DRrZ6vcaPXVUdXn9dAmmF0sobPIGPEemGMc9ErcEBB42NPrCALKz1GYXLuB1YagBXfaRJW2hZWWuji2/AfZYEPtLw/Wrphk9DSsdAyIAIm8+FZmoAVt1GexQ8tsHzlkmxFSqIad0JAW9X1oaw0QyE4vNC6lHy2pllGRlJ9AKJoMn5QbVdnYcHwztENX79DQHsaGQU12Lj9RzIVf9Hf/h7Iq3+vF/0wnm6ITO/7N58HD/37T79Yxl6zpGm9IZeST6GBJPGFyhUUjfHosWxxSCzGRG5BoB4ZiQtJha3ZNdVlK0EQhiW4kUJnm1Snpd1yCiNbrcSgaCQ3NFbYnC9ERPRX63mAISg37zZBqQIsQUtL5lOsblzfMu4XMC1Q7vt3lYw7hwHLzLwc5Ga0a8qyjzcAYMmil1GnMiA6bwdEWA8gvG7Rjp5TERAo2CFoCyBAofh1jW2mGOYlA+5s7meFr1RirWgLtqNtcPtpRBkBxffUwQEUvWre5V0BuK9H0kZwzl2/bbn3miBp+qg8h32zj3J3CNoCyZWgjJGPRnNyED/WtZB19XnQ+/a9f5+ha54FsgpfacVbr6Bmwi01UjncSiGJ3s0nItMnmmHovgD0TSSbgmryiabv9dERfGK2uKycy7Pw+JFzHT/nCoL3zLk2Asa1APOTAflJbJn1+6fQqSmID8P3DeKzvPNEo/gyr9iEUL4qLKxQ/siw2HD064VMnZnE9AmKY1UshEl3yoGkLNDjGlcjQVQaGqFGux4roulWxFhgxuamoJpCI98r9oPaTum4svYt/AcKpeyLMTO/9Fb55SkE/GWusanQNnbVymmnH+4QVBO0A/PA1by6N8eFSvkPBO3/50GdVvmZc+6gUAs+AGSN232/sjd2dghqwEmJax/CpfkmsmD0kW8eP98MjY2zAfmmfc/X8Hxz9xYanNQWmu0wP72FWoJq2kJDw4G63WNsifZjhVPaEstw2YSstyrmrKx3T8zVBgv7KqDkkulkk4APD1ZvEmAG9f6O/LZTyn8Yj5fNfb/8tqH3p6WnNp09UL3vhVZ+x+o4CBW3JzumquvO1cxZg4q497rd86BcVF0PAYwTpCyQ3Dqydil/Efm2gopzOFyak0feW26E72C5RBNKxCkuk4YfbppZsFuT87cE1XXvaxxuhsc40fE8C6BfASpg1QmB32j5oh8Kj4n6r5rslYKrppXEeR2pNBA/Ee3zAXUXUIQsOGEcK7YIJ/A7JGOanFtAP/rTzK1wf8MjWMP/hBsy5MyAhQy5bYCnvgeS9rHzHo9PTiHyPK33JqbfCaq+wTNPeMMDbb3muWN2j1zR70B19Q9sKfvqnwRR/z8= \ No newline at end of file diff --git a/doc/tracker_sequence_diagram.svg b/doc/tracker_sequence_diagram.svg new file mode 100644 index 0000000..7018a9a --- /dev/null +++ b/doc/tracker_sequence_diagram.svg @@ -0,0 +1,3 @@ + + +
Tracker
Tracker
Client1
Client1
:Object
:Object
Server1
Server1
Note
Note
REGISTER()
REGISTER()
LIST REQ
LIST REQ
LIST RESP(file1, file2)
LIST RESP(f...
DISCOVER RESP(server1, server2, server3)
DISCOVER RE...
Server2
Server2
REGISTER()
REGISTER()
LIST REQ
LIST REQ
DISCOVER REQ()
DISCOVER RE...
LIST RESP(file2, file3)
LIST RESP(f...
LIST REQ
LIST REQ
Server3
Server3
REGISTER()
REGISTER()
LIST REQ
LIST REQ
LIST RESP(file1, file3)
LIST RESP(f...
LIST RESP(file2, file2)
LIST RESP(f...
DISCOVER REQ(fichier2)
DISCOVER RE...
Now the client knows server1 and server3 have file1: download can begin.
Now the client knows server1 and server3 have file1: download can begin.
DISCOVER RESP(server1, server3)
DISCOVER RE...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/src/clientP2P/ClientDownload.java b/src/clientP2P/ClientDownload.java new file mode 100644 index 0000000..ed58c3b --- /dev/null +++ b/src/clientP2P/ClientDownload.java @@ -0,0 +1,409 @@ +package clientP2P; + +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Random; +import java.io.IOException; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.nio.file.StandardCopyOption; +import exception.LocalException; +import exception.RemoteException; +import localException.SocketClosed; +import localException.ProtocolError; +import localException.InternalError; +import localException.TransmissionError; +import localException.SizeError; +import localException.VersionError; +import remoteException.EmptyDirectory; +import remoteException.EmptyFile; +import remoteException.VersionRemoteError; +import remoteException.ProtocolRemoteError; +import remoteException.NotFound; +import remoteException.InternalRemoteError; +import remoteException.NotATracker; +import protocolP2P.HashAlgorithm; +import protocolP2P.HashResponse; +import protocolP2P.HashRequest; +import protocolP2P.Payload; +import protocolP2P.FilePart; +import protocolP2P.LoadRequest; +import protocolP2P.ProtocolP2PPacket; +import clientP2P.ClientDownloadPart; +import tools.HostItem; +import tools.Logger; +import tools.LogLevel; +import tools.ServeErrors; + +/** Class to download file +* @author Louis Royer +* @author Flavien Haas +* @author JS Auge +* @version 1.0 +*/ +public abstract class ClientDownload extends ServeErrors implements Runnable { + protected List hostList; + protected String filename; + protected byte[] hash512; + protected List sockList = new ArrayList(); + protected List offsetsToAsk = new ArrayList(); + protected List offsetsPending = new ArrayList(); + protected boolean stop; + protected long size; + protected static final long MAX_PARTIAL_SIZE = 4096; + protected String partsSubdir; + protected String dirStorage; + protected boolean success = false; + protected Logger logger; + + /** Constructor with parameters: filename, list of hosts, parts subdirectory and dirStorage + * @param filename name of file to download + * @param hostList list of servers + * @param partsSubdir directory to store .part files + * @param dirStorage directory to write assembled file + * @param logger Logger + */ + public ClientDownload(String filename, List hostList, String partsSubdir, String dirStorage, Logger logger) { + this.partsSubdir = partsSubdir; + this.dirStorage = dirStorage; + this.filename = filename; + this.hostList = hostList; + this.logger = logger; + this.stop = false; + } + + /** Success getter. + * @return true when file have successfully been reassembled. + */ + public boolean getSuccess() { + return success; + } + + /** Getter for hash512sum + * @return hash512sum + */ + public byte[] getHashSum512() { + return hash512; + } + + /** Stop threads */ + protected void stopTasks() { + for(ClientDownloadPart c : sockList) { + try { + c.setStop(); + } catch (InterruptedException e) { + writeLog(e, LogLevel.Error); + } + } + } + + /** Asks thread to stop + */ + public void setStop() { + stop = true; + } + + /** Assign tasks randomly to threads. + * @throws InternalError + */ + protected void assignTasks() throws InternalError { + Random rand = new Random(); + for(long offset : offsetsToAsk) { + try { + sockList.get(rand.nextInt(sockList.size())).assignTask(offset); + offsetsPending.add(offset); + System.err.println("Assigned task "+ offset); + writeLog("Assigned task "+ offset, LogLevel.Info); + } catch(InterruptedException e) { + writeLog(e, LogLevel.Error); + throw new InternalError(); + } + } + offsetsToAsk.removeAll(offsetsPending); + } + + /** 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); + + /** Starts threads for each server in hostList. + */ + protected void initThreads() { + for(HostItem hostItem: hostList) { + sockList.add(createDownloadPart(filename, hostItem)); + } + for(ClientDownloadPart c: sockList) { + Thread t = new Thread(c); + t.start(); + } + writeLog("Threads initialized", LogLevel.Info); + } + + /** Remove tasks from failed threads. Update done status. + * @throws InternalError + */ + protected void checkTasksStatus() throws InternalError { + try { + synchronized(this) { + this.wait(); + List sockListCpy = new ArrayList<>(sockList); + for(ClientDownloadPart c: sockListCpy) { + if (c.hasFailed() == true) { + sockList.remove(c); + offsetsPending.removeAll(c.getFailed()); + offsetsToAsk.addAll(c.getFailed()); + } + try { + offsetsPending.removeAll(c.getDone()); + } catch (InterruptedException e) { + throw new InternalError(); + } + } + writeLog("Task check status: " + offsetsToAsk.size() + " to asks, " + offsetsPending.size() + " pending", LogLevel.Info); + if (offsetsToAsk.isEmpty() && offsetsPending.isEmpty()) { + stop = true; + } + if (sockList.size() == 0) { + logger.writeUDP("No thread working", LogLevel.Error); + throw new InternalError(); + } + } + } catch (InterruptedException e) { + throw new InternalError(); + } + } + + /** Get hashsum from server. + * @param hostItem server to ask hash + * @return hash512sum + * @throws InternalError + */ + protected byte[] getHashSum512(HostItem hostItem) throws InternalError { + byte[] hash; + HashAlgorithm[] hashesAlgo = new HashAlgorithm[1]; + hashesAlgo[0] = HashAlgorithm.SHA512; + ProtocolP2PPacket d = createProtocolP2PPacket(new HashRequest(filename, hashesAlgo)); + try { + d.sendRequest(getHostItemSocket(hostItem)); + try { + Payload pHash = d.receiveResponse().getPayload(); + assert pHash instanceof HashResponse : "This payload must be instance of HashResponse"; + if (!(pHash instanceof HashResponse)) { + throw new InternalError(); + } else { + hash = ((HashResponse)pHash).getHash(HashAlgorithm.SHA512); + } + } catch (EmptyDirectory e) { + 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); + throw new InternalError(); + } catch (RemoteException e) { + writeLog(e, LogLevel.Error); + throw new InternalError(); + } + return hash; + } catch (IOException e) { + writeLog(e, LogLevel.Error); + throw new InternalError(); + } catch (SocketClosed e){ + System.err.println("getHashSum512 : SocketClosed"); + writeLog("getHashSum512 : SocketClosed", LogLevel.Error); + throw new InternalError(); + } + } + + /** Removes servers not owning the correct file to download from list. + * This is done by comparing hash512sum. + * @throws InternalError + */ + protected void purgeList() throws InternalError { + List blackList = new ArrayList(); + boolean first = false; + byte[] hashsum; + for(HostItem host: hostList) { + // already have hashsum from 1st server + if (!first) { + first = true; + continue; + } + // ask hashsum + hashsum = getHashSum512(host); + if (!Arrays.equals(hash512, hashsum)) { + blackList.add(host); + } + } + // purge list + for(HostItem host: blackList) { + hostList.remove(host); + } + writeLog("Host list purge: done", LogLevel.Info); + } + + + /** Reassemble file from file parts. + * Set success to true if file is reassembled successfully. + */ + protected void reassembleFile() { + boolean firstPart = true; + boolean abort = false; + long nextOffset = 0; + do { + if (firstPart) { + writeLog("Reassembling: First part", LogLevel.Info); + try { + // create file + Files.copy(new File(partsSubdir + filename + "_" + nextOffset + ".part").toPath(), new File(dirStorage + filename).toPath(), StandardCopyOption.REPLACE_EXISTING); + nextOffset = (new File(dirStorage + filename)).length(); + firstPart = false; + } catch (IOException e) { + writeLog("Reassembling: aborting on first part", LogLevel.Warning); + abort = true; + } + } else if (nextOffset >= size) { + success = true; + writeLog("Reassembling: success", LogLevel.Info); + } else { + // append to file + try { + Files.write(new File(dirStorage + filename).toPath(), Files.readAllBytes(new File(partsSubdir + filename + "_" + nextOffset + ".part").toPath()), StandardOpenOption.APPEND); + nextOffset = (new File(dirStorage + filename)).length(); + } catch (IOException e) { + abort = true; + writeLog("Aborting: bad number " + nextOffset, LogLevel.Error); + } + } + } while((!success) && (!abort)); + } + + + /** Set size of file to download. Also download first file part. + * @throws InternalError + */ + protected void setSize() throws InternalError { + ProtocolP2PPacket d = createProtocolP2PPacket(new LoadRequest(filename, 0, MAX_PARTIAL_SIZE)); + 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."); + writeLog("cannot get size.", LogLevel.Error); + throw new InternalError(); + } else { + FilePart fp = (FilePart)p; + if (!fp.getFilename().equals(filename)) { + System.err.println("Error: wrong file received: `" + fp.getFilename() + "`"); + writeLog("wrong file 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(); + } + } + } catch (EmptyDirectory e) { + System.err.println("Error: empty directory."); + writeLog("empty directory.", LogLevel.Error); + throw new InternalError(); + } catch (LocalException e) { + writeLog(e, LogLevel.Error); + throw new InternalError(); + } catch (RemoteException e) { + writeLog(e, LogLevel.Error); + throw new InternalError(); + } + } catch (IOException e) { + writeLog(e, LogLevel.Error); + throw new InternalError(); + } catch (SocketClosed e){ + System.err.println("setSize : SocketClosed"); + writeLog("setSize : SocketClosed", LogLevel.Error); + } + } + + /** Close HostItem socket + * @param hostItem HostItem + */ + protected abstract void closeHostItemSocket(HostItem hostItem); + + + /** Runnable implementation + */ + 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(); + + } + } + writeLog("Reassembling file parts.", LogLevel.Info); + reassembleFile(); + } catch(InternalError e) { + writeLog("Error while downloading file. Aborting.", LogLevel.Error); + } finally { + stopTasks(); + } + } + + /** Initialize infos about file to download (size, hash512sum, partslist to dl). + * Also download first partfile (to get size). + * @throws InternalError + */ + protected void init() throws InternalError { + // get size + setSize(); + + // get hashsum from 1st server in list + hash512 = getHashSum512(hostList.get(0)); + if (hash512.length == 0) { + writeLog("no hash512sum support.", LogLevel.Error); + throw new InternalError(); + } + + // Add tasks + if (!stop) { + for(long i=MAX_PARTIAL_SIZE; i toDoTasks; + protected List pendingTasks; + protected List tasksDone; + protected volatile boolean tasksListsLock; + protected volatile boolean stop; + protected volatile boolean failed; + protected String filename; + protected volatile boolean noTask; + protected String partsSubdir; + protected static final long MAX_PARTIAL_SIZE = 4096; + protected ClientDownload manager; + protected Logger logger; + + /** 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 + */ + public ClientDownloadPart(ClientDownload manager, String filename, String partsSubdir, Logger logger) { + this.manager = manager; + this.partsSubdir = partsSubdir; + this.filename = filename; + this.logger = logger; + stop = false; + failed = false; + pendingTasks = new ArrayList<>(); + toDoTasks = new ArrayList<>(); + tasksDone = new ArrayList<>(); + noTask = true; + tasksListsLock = false; + } + + /** True if thread has failed to get a file. + * @return true if thread has failed to get a file + */ + public boolean hasFailed() { + return failed; + } + + /** Asks to stop thread. + * @throws InterruptedException + */ + public synchronized void setStop() throws InterruptedException { + stop = true; + this.notifyAll(); + } + + /** Runnable implementation */ + public void run() { + while(!stop) { + try { + doTasks(); + synchronized(manager) { + manager.notify(); + } + } catch(InterruptedException e) { + try { + setStop(); + synchronized(manager) { + manager.notify(); + } + } catch (InterruptedException e2) { + } + } + } + writeLog("Closing socket", LogLevel.Info); + try{ + closeSocket(); + } catch(IOException e){ + writeLog("can't close socket", LogLevel.Error); + } + } + + /** Close the socket + */ + protected abstract void closeSocket() throws IOException; + + /** Get list of offsets that have not be downloaded if failed, else + * empty list. + * @return list of offsets + */ + public List getFailed() { + List ret = new ArrayList<>(); + if (failed) { + ret.addAll(pendingTasks); + ret.addAll(toDoTasks); + } + return ret; + } + + /** Get list of downloaded file parts offset, then clear this list. + * @return list of offsets + * @throws InterruptedException + */ + public List getDone() throws InterruptedException { + if (tasksDone.size() == 0) { + return new ArrayList<>(); + } else { + synchronized (this) { + while(tasksListsLock) { + this.wait(); + } + tasksListsLock = true; + List ret = new ArrayList<>(tasksDone); + tasksDone.clear(); + tasksListsLock = false; + this.notifyAll(); + return ret; + } + } + } + + /** Adds offset of files parts to download. + * @param task offset to download + * @throws InterruptedException + */ + public synchronized void assignTask(Long task) throws InterruptedException { + synchronized(this) { + while(tasksListsLock) { + this.wait(); + } + tasksListsLock = true; + toDoTasks.add(task); + noTask = false; + tasksListsLock = false; + this.notifyAll(); + } + } + + + /** Send one request and wait for one response. Blocks when no task. + * @throws InterruptedException + */ + public synchronized void doTasks() throws InterruptedException { + while(noTask && !stop) { + this.wait(); + } + if (!stop) { + try { + Long offset = toDoTasks.get(0); + ProtocolP2PPacket p = reqPart(offset); + if (p == null) { + stop = true; + } + + failed = downloadPart(p); + if (failed) { + System.err.println("Error: DownloadPart failed."); + writeLog("DownloadPart failed.", LogLevel.Error); + stop = true; + } else if (toDoTasks.isEmpty()) { + noTask = true; + } + } catch (IndexOutOfBoundsException e) { + writeLog(e, LogLevel.Error); + noTask = true; + } + } + } + + /** Send a request for a specific offset. + * @param offset Offset of the file part to download + * @return ProtocolP2PPacketTCP used to send request + */ + protected ProtocolP2PPacket reqPart(Long offset) { + writeLog("New request: " + offset, LogLevel.Info); + // maintain tracking of tasks + if (toDoTasks.contains(offset)) { + try { + synchronized (this) { + while(tasksListsLock) { + this.wait(); + } + tasksListsLock = true; + toDoTasks.remove(offset); + pendingTasks.add(offset); + tasksListsLock = false; + this.notifyAll(); + } + } catch(InterruptedException e) { + writeLog("reqPart interruptedException", LogLevel.Error); + return null; + } + } else { + writeLog("reqPart (offset " + offset + " not in toDoTasks)", LogLevel.Error); + return null; + } + // send request + try { + ProtocolP2PPacket d = createProtocolP2PPacket(new LoadRequest(filename, offset.longValue(), MAX_PARTIAL_SIZE)); + d.sendRequest(getSocket()); + return d; + } catch (InternalError e) { + writeLog("reqPart internalError", LogLevel.Error); + return null; + } catch (IOException e) { + writeLog("reqPart ioexception", LogLevel.Error); + writeLog(e, LogLevel.Error); + return null; + } catch (SocketClosed e){ + writeLog("reqPart SocketClosed", LogLevel.Error); + return null; + } + } + + /** Get the socket */ + protected abstract Object getSocket(); + + + /** Download file part associated to the request send (d). + * @param d request packet + * @return true on failure, else false + */ + public < T extends ProtocolP2PPacket > boolean downloadPart(T d) { + if (d == null) { + writeLog("downloadPart -> d is null.", LogLevel.Error); + return true; + } + try { + Payload p = d.receiveResponse().getPayload(); + assert p instanceof FilePart : "This payload must be instance of FilePart"; + if (!(p instanceof FilePart)) { + writeLog("cannot get size.", LogLevel.Error); + return true; + } else { + FilePart fp = (FilePart)p; + if (!fp.getFilename().equals(filename)) { + writeLog("wrong file received: `" + fp.getFilename() + "`", LogLevel.Error); + return true; + } + Long offset = Long.valueOf(fp.getOffset()); + if (pendingTasks.contains(offset)) { + try { + Files.write(new File(partsSubdir + filename + "_" + offset + ".part").toPath(), fp.getPartialContent()); + } catch (IOException e) { + writeLog("cannot write file (" + partsSubdir + filename + "_" + offset + ".part)", LogLevel.Error); + } + } else { + writeLog("wrong file part received.", LogLevel.Error); + return true; + } + try { + synchronized(this) { + while(tasksListsLock) { + this.wait(); + } + tasksListsLock = true; + pendingTasks.remove(offset); + tasksDone.add(offset); + tasksListsLock = false; + this.notifyAll(); + } + } catch(InterruptedException e) { + writeLog("DownloadPart Interrupted exception", LogLevel.Error); + return true; + } + } + } catch (LocalException e) { + writeLog(e, LogLevel.Error); + return true; + } catch (RemoteException e) { + writeLog(e, LogLevel.Error); + return true; + } catch (IOException e) { + System.err.println("Error: downloadPart ioexception"); + writeLog("downloadPart ioexception", LogLevel.Error); + return true; + } + return false; + } + + +} diff --git a/src/clientP2P/ClientDownloadPartTCP.java b/src/clientP2P/ClientDownloadPartTCP.java index 6895c83..7928735 100644 --- a/src/clientP2P/ClientDownloadPartTCP.java +++ b/src/clientP2P/ClientDownloadPartTCP.java @@ -2,26 +2,30 @@ package clientP2P; import java.util.List; import java.util.ArrayList; import java.net.Socket; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import protocolP2P.ProtocolP2PPacketTCP; +import protocolP2P.ProtocolP2PPacket; import protocolP2P.Payload; import protocolP2P.LoadRequest; import protocolP2P.FilePart; -import exception.InternalError; +import localException.InternalError; +import localException.ProtocolError; +import localException.TransmissionError; +import localException.VersionError; +import localException.SizeError; +import localException.SocketClosed; import remoteException.EmptyDirectory; import remoteException.EmptyFile; -import exception.ProtocolError; import remoteException.InternalRemoteError; import remoteException.VersionRemoteError; -import exception.TransmissionError; import remoteException.ProtocolRemoteError; -import exception.VersionError; -import exception.SizeError; import remoteException.NotFound; -import java.nio.file.Files; -import java.io.File; -import java.nio.file.Paths; -import java.io.IOException; -import exception.SocketClosed; +import remoteException.NotATracker; +import tools.Logger; +import tools.LogLevel; +import clientP2P.ClientDownloadPart; /** Class to download file parts on tcp. * @author Louis Royer @@ -29,297 +33,55 @@ import exception.SocketClosed; * @author JS Auge * @version 1.0 */ -public class ClientDownloadPartTCP implements Runnable { +public class ClientDownloadPartTCP extends ClientDownloadPart { - private List toDoTasks; - private List pendingTasks; - private List tasksDone; - private volatile boolean tasksListsLock; - private volatile boolean stop; - private volatile boolean failed; - private String filename; private Socket socket; - private volatile boolean noTask; - private String partsSubdir; - private static final long MAX_PARTIAL_SIZE = 4096; - private ClientDownloadTCP manager; /** 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 */ - public ClientDownloadPartTCP(ClientDownloadTCP manager, String filename, Socket socket, String partsSubdir) { - this.manager = manager; - this.partsSubdir = partsSubdir; - this.filename = filename; + public ClientDownloadPartTCP(ClientDownload manager, String filename, Socket socket, String partsSubdir, Logger logger) { + super(manager, filename, partsSubdir, logger); this.socket = socket; - stop = false; - failed = false; - pendingTasks = new ArrayList<>(); - toDoTasks = new ArrayList<>(); - tasksDone = new ArrayList<>(); - noTask = true; - tasksListsLock = false; - } - - /** True if thread has failed to get a file. - * @return true if thread has failed to get a file - */ - public boolean hasFailed() { - return failed; - } - - /** Asks to stop thread. - * @throws InterruptedException - */ - public synchronized void setStop() throws InterruptedException { - stop = true; - this.notifyAll(); } - /** Runnable implementation */ - public void run() { - while(!stop) { - try { - doTasks(); - synchronized(manager) { - manager.notify(); - } - } catch(InterruptedException e) { - try { - setStop(); - synchronized(manager) { - manager.notify(); - } - } catch (InterruptedException e2) { - } - } - } - System.err.println("Closing socket"); - try{ - socket.close(); - } catch(IOException e){ - System.err.println("can't close socket"); - } - } - /** Get list of offsets that have not be downloaded if failed, else - * empty list. - * @return list of offsets - */ - public List getFailed() { - List ret = new ArrayList<>(); - if (failed) { - ret.addAll(pendingTasks); - ret.addAll(toDoTasks); - } - return ret; + + /** Get the socket */ + protected Object getSocket() { + return (Object) socket; } - - /** Get list of downloaded file parts offset, then clear this list. - * @return list of offsets - * @throws InterruptedException + + /** Close the socket */ - public List getDone() throws InterruptedException { - if (tasksDone.size() == 0) { - return new ArrayList<>(); - } else { - synchronized (this) { - while(tasksListsLock) { - this.wait(); - } - tasksListsLock = true; - List ret = new ArrayList<>(tasksDone); - tasksDone.clear(); - tasksListsLock = false; - this.notifyAll(); - return ret; - } - } + protected void closeSocket() throws IOException { + socket.close(); } - /** Adds offset of files parts to download. - * @param task offset to download - * @throws InterruptedException + /** Implementation of writeLog + * @param text Text to log + * @param logLevel level of logging */ - public synchronized void assignTask(Long task) throws InterruptedException { - synchronized(this) { - while(tasksListsLock) { - this.wait(); - } - tasksListsLock = true; - toDoTasks.add(task); - noTask = false; - tasksListsLock = false; - this.notifyAll(); - } + protected void writeLog(String text, LogLevel logLevel) { + logger.writeTCP(text, logLevel); } - /** Send one request and wait for one response. Blocks when no task. - * @throws InterruptedException + /** Implementation of writeLog + * @param e exception to log + * @param logLevel level of logging */ - public synchronized void doTasks() throws InterruptedException { - while(noTask && !stop) { - this.wait(); - } - if (!stop) { - try { - Long offset = toDoTasks.get(0); - ProtocolP2PPacketTCP p = reqPart(offset); - if (p == null) { - stop = true; - } - - failed = downloadPart(p); - if (failed) { - System.err.println("Error: DownloadPart failed."); - stop = true; - } else if (toDoTasks.isEmpty()) { - noTask = true; - } - } catch (IndexOutOfBoundsException e) { - noTask = true; - } - - } + protected void writeLog(Exception e, LogLevel logLevel) { + logger.writeTCP(e, logLevel); } - /** Send a request for a specific offset. - * @param offset Offset of the file part to download - * @return ProtocolP2PPacketTCP used to send request - */ - private ProtocolP2PPacketTCP reqPart(Long offset) { - System.err.println("New request: "+ offset); - // maintain tracking of tasks - if (toDoTasks.contains(offset)) { - try { - synchronized (this) { - while(tasksListsLock) { - this.wait(); - } - tasksListsLock = true; - toDoTasks.remove(offset); - pendingTasks.add(offset); - tasksListsLock = false; - this.notifyAll(); - } - } catch(InterruptedException e) { - System.err.println("Error: reqPart interruptedException"); - return null; - } - } else { - System.err.println("Error: reqPart (offset " + offset + " not in toDoTasks)"); - return null; - } - // send request - try { - ProtocolP2PPacketTCP d = new ProtocolP2PPacketTCP((Payload) new LoadRequest(filename, offset.longValue(), MAX_PARTIAL_SIZE)); - d.sendRequest((Object)socket); - return d; - } catch (InternalError e) { - System.err.println("Error: reqPart internalError"); - return null; - } catch (IOException e) { - e.printStackTrace(); - System.err.println("Error: reqPart ioexception"); - return null; - } catch (SocketClosed e){ - System.err.println("Error: reqPart SocketClosed"); - return null; - } - } - /** Download file part associated to the request send (d). - * @param d request packet - * @return true on failure, else false + /** Create packets + * @param payload Payload */ - public boolean downloadPart(ProtocolP2PPacketTCP d) { - if (d == null) { - System.err.println("Error: downloadPart -> d is null."); - return true; - } - 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."); - return true; - } else { - FilePart fp = (FilePart)p; - if (!fp.getFilename().equals(filename)) { - System.err.println("Error: wrong file received: `" + fp.getFilename() + "`"); - return true; - } - Long offset = Long.valueOf(fp.getOffset()); - if (pendingTasks.contains(offset)) { - try { - Files.write(new File(partsSubdir + filename + "_" + offset + ".part").toPath(), fp.getPartialContent()); - } catch (IOException e) { - System.err.println("Error: cannot write file (" + partsSubdir + filename + "_" + offset + ".part)"); - } - } else { - System.err.println("Error: wrong file part received."); - return true; - } - try { - synchronized(this) { - while(tasksListsLock) { - this.wait(); - } - tasksListsLock = true; - pendingTasks.remove(offset); - tasksDone.add(offset); - tasksListsLock = false; - this.notifyAll(); - } - } catch(InterruptedException e) { - System.err.println("Error: DownloadPart Interrupted exception"); - return true; - } - } - } catch (EmptyDirectory e) { - System.err.println("Error: empty directory."); - return true; - } catch (EmptyFile e) { - System.err.println("Error: downloadPart emptyFile"); - // TODO: use more specific errors - return true; - } catch (ProtocolError e) { - System.err.println("Error: downloadPart protocolError"); - return true; - } catch (InternalRemoteError e) { - System.err.println("Error: downloadPart internalRemoteError"); - return true; - } catch (VersionRemoteError e) { - System.err.println("Error: downloadPart versionRemoteError"); - return true; - } catch (ProtocolRemoteError e) { - System.err.println("Error: downloadPart protocolRemoteError"); - return true; - } catch (TransmissionError e) { - System.err.println("Error: downloadPart transmissionError"); - return true; - } catch (VersionError e) { - System.err.println("Error: downloadPart versionError"); - return true; - } catch (SizeError e) { - System.err.println("Error: downloadPart sizeError"); - return true; - } catch (NotFound e) { - System.err.println("Error: downloadPart notFound"); - return true; - } catch (IOException e) { - System.err.println("Error: downloadPart ioexception"); - return true; - } catch (InternalError e) { - System.err.println("Error: downloadPart internalError"); - return true; - } catch (SocketClosed e){ - System.err.println("Error: downloadPart SocketClosed"); - return true; - } - return false; + protected < T extends Payload > ProtocolP2PPacket createProtocolP2PPacket(T payload) { + return (ProtocolP2PPacket)new ProtocolP2PPacketTCP(payload); } - } diff --git a/src/clientP2P/ClientDownloadPartUDP.java b/src/clientP2P/ClientDownloadPartUDP.java index aa85296..0387c0c 100644 --- a/src/clientP2P/ClientDownloadPartUDP.java +++ b/src/clientP2P/ClientDownloadPartUDP.java @@ -3,24 +3,28 @@ import java.util.List; import java.util.ArrayList; import java.net.DatagramSocket; import protocolP2P.ProtocolP2PPacketUDP; +import protocolP2P.ProtocolP2PPacket; import protocolP2P.Payload; import protocolP2P.LoadRequest; import protocolP2P.FilePart; -import exception.InternalError; +import localException.InternalError; import remoteException.EmptyDirectory; import remoteException.EmptyFile; -import exception.ProtocolError; +import localException.ProtocolError; import remoteException.InternalRemoteError; import remoteException.VersionRemoteError; -import exception.TransmissionError; +import localException.TransmissionError; import remoteException.ProtocolRemoteError; -import exception.VersionError; -import exception.SizeError; +import localException.VersionError; +import localException.SizeError; import remoteException.NotFound; +import remoteException.NotATracker; import java.nio.file.Files; import java.io.File; -import java.nio.file.Paths; import java.io.IOException; +import tools.Logger; +import tools.LogLevel; +import clientP2P.ClientDownloadPart; /** Class to download file parts on udp. * @author Louis Royer @@ -28,287 +32,54 @@ import java.io.IOException; * @author JS Auge * @version 1.0 */ -public class ClientDownloadPartUDP implements Runnable { +public class ClientDownloadPartUDP extends ClientDownloadPart { - private List toDoTasks; - private List pendingTasks; - private List tasksDone; - private volatile boolean tasksListsLock; - private volatile boolean stop; - private volatile boolean failed; - private String filename; private DatagramSocket socket; - private volatile boolean noTask; - private String partsSubdir; - private static final long MAX_PARTIAL_SIZE = 4096; - private ClientDownloadUDP manager; + /** 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 */ - public ClientDownloadPartUDP(ClientDownloadUDP manager, String filename, DatagramSocket socket, String partsSubdir) { - this.manager = manager; - this.partsSubdir = partsSubdir; - this.filename = filename; + public ClientDownloadPartUDP(ClientDownload manager, String filename, DatagramSocket socket, String partsSubdir, Logger logger) { + super(manager, filename, partsSubdir, logger); this.socket = socket; - stop = false; - failed = false; - pendingTasks = new ArrayList<>(); - toDoTasks = new ArrayList<>(); - tasksDone = new ArrayList<>(); - noTask = true; - tasksListsLock = false; } - /** True if thread has failed to get a file. - * @return true if thread has failed to get a file - */ - public boolean hasFailed() { - return failed; + /** Get the socket */ + protected Object getSocket() { + return (Object) socket; } - - /** Asks to stop thread. - * @throws InterruptedException + + /** Close the socket */ - public synchronized void setStop() throws InterruptedException { - stop = true; - this.notifyAll(); - } - - /** Runnable implementation */ - public void run() { - while(!stop) { - try { - doTasks(); - synchronized(manager) { - manager.notify(); - } - } catch(InterruptedException e) { - try { - setStop(); - synchronized(manager) { - manager.notify(); - } - } catch (InterruptedException e2) { - } - } - } - System.err.println("Closing socket"); + protected void closeSocket() throws IOException { socket.close(); } - /** Get list of offsets that have not be downloaded if failed, else - * empty list. - * @return list of offsets - */ - public List getFailed() { - List ret = new ArrayList<>(); - if (failed) { - ret.addAll(pendingTasks); - ret.addAll(toDoTasks); - } - return ret; - } - - /** Get list of downloaded file parts offset, then clear this list. - * @return list of offsets - * @throws InterruptedException - */ - public List getDone() throws InterruptedException { - if (tasksDone.size() == 0) { - return new ArrayList<>(); - } else { - synchronized (this) { - while(tasksListsLock) { - this.wait(); - } - tasksListsLock = true; - List ret = new ArrayList<>(tasksDone); - tasksDone.clear(); - tasksListsLock = false; - this.notifyAll(); - return ret; - } - } - } - /** Adds offset of files parts to download. - * @param task offset to download - * @throws InterruptedException + /** Implementation of writeLog + * @param text Text to log + * @param logLevel level of logging */ - public synchronized void assignTask(Long task) throws InterruptedException { - synchronized(this) { - while(tasksListsLock) { - this.wait(); - } - tasksListsLock = true; - toDoTasks.add(task); - noTask = false; - tasksListsLock = false; - this.notifyAll(); - } + protected void writeLog(String text, LogLevel logLevel) { + logger.writeUDP(text, logLevel); } - /** Send one request and wait for one response. Blocks when no task. - * @throws InterruptedException + /** Implementation of writeLog + * @param e exception to log + * @param logLevel level of logging */ - public synchronized void doTasks() throws InterruptedException { - while(noTask && !stop) { - this.wait(); - } - if (!stop) { - try { - Long offset = toDoTasks.get(0); - ProtocolP2PPacketUDP p = reqPart(offset); - if (p == null) { - stop = true; - } - - failed = downloadPart(p); - if (failed) { - System.err.println("Error: DownloadPart failed."); - stop = true; - } else if (toDoTasks.isEmpty()) { - noTask = true; - } - } catch (IndexOutOfBoundsException e) { - noTask = true; - } - - } + protected void writeLog(Exception e, LogLevel logLevel) { + logger.writeUDP(e, logLevel); } - /** Send a request for a specific offset. - * @param offset Offset of the file part to download - * @return ProtocolP2PPacketTCP used to send request + /** Create packets + * @param payload Payload */ - private ProtocolP2PPacketUDP reqPart(Long offset) { - System.err.println("New request: "+ offset); - // maintain tracking of tasks - if (toDoTasks.contains(offset)) { - try { - synchronized (this) { - while(tasksListsLock) { - this.wait(); - } - tasksListsLock = true; - toDoTasks.remove(offset); - pendingTasks.add(offset); - tasksListsLock = false; - this.notifyAll(); - } - } catch(InterruptedException e) { - System.err.println("Error: reqPart interruptedException"); - return null; - } - } else { - System.err.println("Error: reqPart (offset " + offset + " not in toDoTasks)"); - return null; - } - // send request - try { - ProtocolP2PPacketUDP d = new ProtocolP2PPacketUDP((Payload) new LoadRequest(filename, offset.longValue(), MAX_PARTIAL_SIZE)); - d.sendRequest((Object)socket); - return d; - } catch (InternalError e) { - System.err.println("Error: reqPart internalError"); - return null; - } catch (IOException e) { - e.printStackTrace(); - System.err.println("Error: reqPart ioexception"); - return null; - } + protected < T extends Payload > ProtocolP2PPacket createProtocolP2PPacket(T payload) { + return (ProtocolP2PPacket)new ProtocolP2PPacketUDP(payload); } - - /** Download file part associated to the request send (d). - * @param d request packet - * @return true on failure, else false - */ - public boolean downloadPart(ProtocolP2PPacketUDP d) { - if (d == null) { - System.err.println("Error: downloadPart -> d is null."); - return true; - } - 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."); - return true; - } else { - FilePart fp = (FilePart)p; - if (!fp.getFilename().equals(filename)) { - System.err.println("Error: wrong file received: `" + fp.getFilename() + "`"); - return true; - } - Long offset = Long.valueOf(fp.getOffset()); - if (pendingTasks.contains(offset)) { - try { - Files.write(new File(partsSubdir + filename + "_" + offset + ".part").toPath(), fp.getPartialContent()); - } catch (IOException e) { - System.err.println("Error: cannot write file (" + partsSubdir + filename + "_" + offset + ".part)"); - } - } else { - System.err.println("Error: wrong file part received."); - return true; - } - try { - synchronized(this) { - while(tasksListsLock) { - this.wait(); - } - tasksListsLock = true; - pendingTasks.remove(offset); - tasksDone.add(offset); - tasksListsLock = false; - this.notifyAll(); - } - } catch(InterruptedException e) { - System.err.println("Error: DownloadPart Interrupted exception"); - return true; - } - } - } catch (EmptyDirectory e) { - System.err.println("Error: empty directory."); - return true; - } catch (EmptyFile e) { - System.err.println("Error: downloadPart emptyFile"); - // TODO: use more specific errors - return true; - } catch (ProtocolError e) { - System.err.println("Error: downloadPart protocolError"); - return true; - } catch (InternalRemoteError e) { - System.err.println("Error: downloadPart internalRemoteError"); - return true; - } catch (VersionRemoteError e) { - System.err.println("Error: downloadPart versionRemoteError"); - return true; - } catch (ProtocolRemoteError e) { - System.err.println("Error: downloadPart protocolRemoteError"); - return true; - } catch (TransmissionError e) { - System.err.println("Error: downloadPart transmissionError"); - return true; - } catch (VersionError e) { - System.err.println("Error: downloadPart versionError"); - return true; - } catch (SizeError e) { - System.err.println("Error: downloadPart sizeError"); - return true; - } catch (NotFound e) { - System.err.println("Error: downloadPart notFound"); - return true; - } catch (IOException e) { - System.err.println("Error: downloadPart ioexception"); - return true; - } catch (InternalError e) { - System.err.println("Error: downloadPart internalError"); - return true; - } - return false; - } - } diff --git a/src/clientP2P/ClientDownloadTCP.java b/src/clientP2P/ClientDownloadTCP.java index b52bbe7..5d32835 100644 --- a/src/clientP2P/ClientDownloadTCP.java +++ b/src/clientP2P/ClientDownloadTCP.java @@ -1,35 +1,40 @@ package clientP2P; -import clientP2P.ClientDownloadPartTCP; -import tools.HostItem; + import java.util.List; import java.util.ArrayList; import java.util.Arrays; import java.util.Random; +import java.io.IOException; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.nio.file.StandardCopyOption; import remoteException.EmptyDirectory; import remoteException.EmptyFile; import remoteException.VersionRemoteError; import remoteException.ProtocolRemoteError; import remoteException.NotFound; import remoteException.InternalRemoteError; +import remoteException.NotATracker; +import localException.ProtocolError; +import localException.InternalError; +import localException.TransmissionError; +import localException.SizeError; +import localException.VersionError; +import localException.SocketClosed; import protocolP2P.HashAlgorithm; import protocolP2P.HashResponse; import protocolP2P.HashRequest; import protocolP2P.ProtocolP2PPacketTCP; +import protocolP2P.ProtocolP2PPacket; import protocolP2P.Payload; -import exception.ProtocolError; -import exception.InternalError; -import exception.TransmissionError; -import exception.SizeError; -import exception.VersionError; import protocolP2P.FilePart; import protocolP2P.LoadRequest; -import java.io.IOException; -import java.nio.file.Files; -import java.io.File; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.nio.file.StandardCopyOption; -import exception.SocketClosed; +import clientP2P.ClientDownloadPartTCP; +import clientP2P.ClientDownload; +import tools.HostItem; +import tools.Logger; +import tools.LogLevel; /** Class to download file from tcp * @author Louis Royer @@ -37,353 +42,61 @@ import exception.SocketClosed; * @author JS Auge * @version 1.0 */ -public class ClientDownloadTCP implements Runnable { - - private List hostList; - private String filename; - private byte[] hash512; - private List sockList = new ArrayList(); - private List offsetsToAsk = new ArrayList(); - private List offsetsPending = new ArrayList(); - private boolean stop; - private long size; - private static final long MAX_PARTIAL_SIZE = 4096; - private String partsSubdir; - private String dirStorage; - private boolean success = false; +public class ClientDownloadTCP extends ClientDownload { /** Constructor with parameters: filename, list of hosts, parts subdirectory and dirStorage * @param filename name of file to download * @param hostList list of servers * @param partsSubdir directory to store .part files * @param dirStorage directory to write assembled file + * @param logger Logger */ - public ClientDownloadTCP(String filename, List hostList, String partsSubdir, String dirStorage) { - this.partsSubdir = partsSubdir; - this.dirStorage = dirStorage; - this.filename = filename; - this.hostList = hostList; - this.stop = false; - } - - /** Asks thread to stop - */ - public void setStop() { - stop = true; - } - - /** Runnable implementation - */ - public void run() { - try { - init(); - if (stop) { - System.err.println("File is smaller than part max size."); - hostList.get(0).closeTCPSocket(); - } else { - System.err.println("File is bigger than part max size."); - purgeList(); - initThreads(); - while(!stop) { - assignTasks(); - checkTasksStatus(); - - } - } - System.err.println("Reassembling file parts."); - reassembleFile(); - } catch(InternalError e) { - System.err.println("Error while downloading file. Aborting."); - } finally { - stopTasks(); - } - } - - /** Starts threads for each server in hostList. - */ - private void initThreads() { - for(HostItem hostItem: hostList) { - sockList.add(new ClientDownloadPartTCP(this, filename, hostItem.getTCPSocket(), partsSubdir)); - } - for(ClientDownloadPartTCP c: sockList) { - Thread t = new Thread(c); - t.start(); - } - System.err.println("Threads initialized"); - } - - /** Remove tasks from failed threads. Update done status. - * @throws InternalError - */ - private void checkTasksStatus() throws InternalError { - try { - synchronized(this) { - this.wait(); - List sockListCpy = new ArrayList<>(sockList); - for(ClientDownloadPartTCP c: sockListCpy) { - if (c.hasFailed() == true) { - sockList.remove(c); - offsetsPending.removeAll(c.getFailed()); - offsetsToAsk.addAll(c.getFailed()); - } - try { - offsetsPending.removeAll(c.getDone()); - } catch (InterruptedException e) { - throw new InternalError(); - } - } - System.err.println("Task check status: " + offsetsToAsk.size() + " to asks, " + offsetsPending.size() + " pending"); - if (offsetsToAsk.isEmpty() && offsetsPending.isEmpty()) { - stop = true; - } - if (sockList.size() == 0) { - System.err.println("No thread working"); - throw new InternalError(); - } - } - } catch (InterruptedException e) { - throw new InternalError(); - } - } - - /** Assign tasks randomly to threads. - * @throws InternalError - */ - private void assignTasks() throws InternalError { - Random rand = new Random(); - for(long offset : offsetsToAsk) { - try { - sockList.get(rand.nextInt(sockList.size())).assignTask(offset); - offsetsPending.add(offset); - System.err.println("Assigned task "+ offset); - } catch(InterruptedException e) { - throw new InternalError(); - } - } - offsetsToAsk.removeAll(offsetsPending); - } - - /** Stop threads */ - private void stopTasks() { - for(ClientDownloadPartTCP c : sockList) { - try { - c.setStop(); - } catch (InterruptedException e) {} - } - } - - /** Get hashsum from server. - * @param hostItem server to ask hash - * @return hash512sum - * @throws InternalError - */ - private byte[] getHashSum512(HostItem hostItem) throws InternalError { - byte[] hash; - HashAlgorithm[] hashesAlgo = new HashAlgorithm[1]; - hashesAlgo[0] = HashAlgorithm.SHA512; - ProtocolP2PPacketTCP d = new ProtocolP2PPacketTCP((Payload) new HashRequest(filename, hashesAlgo)); - try { - d.sendRequest((Object)hostItem.getTCPSocket()); - try { - Payload pHash = d.receiveResponse().getPayload(); - assert pHash instanceof HashResponse : "This payload must be instance of HashResponse"; - if (!(pHash instanceof HashResponse)) { - throw new InternalError(); - } else { - hash = ((HashResponse)pHash).getHash(HashAlgorithm.SHA512); - } - } catch (EmptyDirectory e) { - hash = new byte[0]; - } catch (NotFound e) { - hash = new byte[0]; - // TODO: use more specific errors - } catch (EmptyFile e) { - throw new InternalError(); - } catch (ProtocolError e) { - throw new InternalError(); - } catch (InternalRemoteError e) { - throw new InternalError(); - } catch (VersionRemoteError e) { - throw new InternalError(); - } catch (ProtocolRemoteError e) { - throw new InternalError(); - } catch (TransmissionError e) { - throw new InternalError(); - } catch (VersionError e) { - throw new InternalError(); - } catch (SizeError e) { - throw new InternalError(); - } - return hash; - } catch (IOException e) { - throw new InternalError(); - } catch (SocketClosed e){ - System.err.println("getHashSum512 : SocketClosed"); - throw new InternalError(); - } + public ClientDownloadTCP(String filename, List hostList, String partsSubdir, String dirStorage, Logger logger) { + super(filename, hostList, partsSubdir, dirStorage, logger); } - /** Removes servers not owning the correct file to download from list. - * This is done by comparing hash512sum. - * @throws InternalError + /** Create a clientDownloadPart + * @param filename name of the file to download + * @param hostItem Hostitem of the server */ - private void purgeList() throws InternalError { - List blackList = new ArrayList(); - boolean first = false; - byte[] hashsum; - for(HostItem host: hostList) { - // already have hashsum from 1st server - if (!first) { - first = true; - continue; - } - // ask hashsum - hashsum = getHashSum512(host); - if (!Arrays.equals(hash512, hashsum)) { - blackList.add(host); - } - } - // purge list - for(HostItem host: blackList) { - hostList.remove(host); - } - System.err.println("Host list purge: done"); + protected ClientDownloadPart createDownloadPart(String filename, HostItem hostItem) { + return (ClientDownloadPart)new ClientDownloadPartTCP((ClientDownload)this, filename, hostItem.getTCPSocket(), partsSubdir, logger); } - - /** Getter for hash512sum - * @return hash512sum + + /** Close HostItem socket + * @param hostItem HostItem */ - public byte[] getHashSum512() { - return hash512; + protected void closeHostItemSocket(HostItem hostItem) { + hostItem.closeTCPSocket(); } - /** Initialize infos about file to download (size, hash512sum, partslist to dl). - * Also download first partfile (to get size). - * @throws InternalError + /** Implementation of writeLog + * @param text Text to log + * @param logLevel level of logging */ - private void init() throws InternalError { - // get size - setSize(); - - // get hashsum from 1st server in list - hash512 = getHashSum512(hostList.get(0)); - if (hash512.length == 0) { - System.err.println("Error: no hash512sum support."); - throw new InternalError(); - } - - // Add tasks - if (!stop) { - for(long i=MAX_PARTIAL_SIZE; i ProtocolP2PPacket createProtocolP2PPacket(T payload) { + return (ProtocolP2PPacket)new ProtocolP2PPacketTCP(payload); } - /** Reassemble file from file parts. - * Set success to true if file is reassembled successfully. + /** Getter for HostItem socket + * @param hostItem HostItem */ - private void reassembleFile() { - boolean firstPart = true; - boolean abort = false; - long nextOffset = 0; - do { - if (firstPart) { - System.err.println("Reassembling: First part"); - try { - // create file - Files.copy(new File(partsSubdir + filename + "_" + nextOffset + ".part").toPath(), new File(dirStorage + filename).toPath(), StandardCopyOption.REPLACE_EXISTING); - nextOffset = (new File(dirStorage + filename)).length(); - firstPart = false; - } catch (IOException e) { - System.err.println("Reassembling: aborting on first part"); - abort = true; - } - } else if (nextOffset >= size) { - success = true; - System.err.println("Reassembling: success"); - } else { - // append to file - try { - Files.write(new File(dirStorage + filename).toPath(), Files.readAllBytes(new File(partsSubdir + filename + "_" + nextOffset + ".part").toPath()), StandardOpenOption.APPEND); - nextOffset = (new File(dirStorage + filename)).length(); - } catch (IOException e) { - abort = true; - System.err.println("Aborting: bad number " + nextOffset); - } - } - } while((!success) && (!abort)); + protected Object getHostItemSocket(HostItem hostItem) { + return (Object)hostItem.getTCPSocket(); } } diff --git a/src/clientP2P/ClientDownloadUDP.java b/src/clientP2P/ClientDownloadUDP.java index c6d335d..008caaf 100644 --- a/src/clientP2P/ClientDownloadUDP.java +++ b/src/clientP2P/ClientDownloadUDP.java @@ -1,34 +1,40 @@ package clientP2P; -import clientP2P.ClientDownloadPartUDP; -import tools.HostItem; + import java.util.List; import java.util.ArrayList; import java.util.Arrays; import java.util.Random; +import java.io.IOException; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.nio.file.StandardCopyOption; +import localException.ProtocolError; +import localException.InternalError; +import localException.TransmissionError; +import localException.SizeError; +import localException.VersionError; import remoteException.EmptyDirectory; import remoteException.EmptyFile; import remoteException.VersionRemoteError; import remoteException.ProtocolRemoteError; import remoteException.NotFound; import remoteException.InternalRemoteError; +import remoteException.NotATracker; import protocolP2P.HashAlgorithm; import protocolP2P.HashResponse; import protocolP2P.HashRequest; import protocolP2P.ProtocolP2PPacketUDP; +import protocolP2P.ProtocolP2PPacket; import protocolP2P.Payload; -import exception.ProtocolError; -import exception.InternalError; -import exception.TransmissionError; -import exception.SizeError; -import exception.VersionError; import protocolP2P.FilePart; import protocolP2P.LoadRequest; -import java.io.IOException; -import java.nio.file.Files; -import java.io.File; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.nio.file.StandardCopyOption; +import clientP2P.ClientDownloadPartUDP; +import clientP2P.ClientDownload; +import tools.HostItem; +import tools.Logger; +import tools.LogLevel; + /** Class to download file from udp * @author Louis Royer @@ -36,348 +42,61 @@ import java.nio.file.StandardCopyOption; * @author JS Auge * @version 1.0 */ -public class ClientDownloadUDP implements Runnable { - - private List hostList; - private String filename; - private byte[] hash512; - private List sockList = new ArrayList(); - private List offsetsToAsk = new ArrayList(); - private List offsetsPending = new ArrayList(); - private boolean stop; - private long size; - private static final long MAX_PARTIAL_SIZE = 4096; - private String partsSubdir; - private String dirStorage; - private boolean success = false; +public class ClientDownloadUDP extends ClientDownload { /** Constructor with parameters: filename, list of hosts, parts subdirectory and dirStorage * @param filename name of file to download * @param hostList list of servers * @param partsSubdir directory to store .part files * @param dirStorage directory to write assembled file + * @param logger Logger */ - public ClientDownloadUDP(String filename, List hostList, String partsSubdir, String dirStorage) { - this.partsSubdir = partsSubdir; - this.dirStorage = dirStorage; - this.filename = filename; - this.hostList = hostList; - this.stop = false; - } - - /** Asks thread to stop - */ - public void setStop() { - stop = true; - } - - /** Runnable implementation - */ - public void run() { - try { - init(); - if (stop) { - System.err.println("File is smaller than part max size."); - hostList.get(0).closeUDPSocket(); - } else { - System.err.println("File is bigger than part max size."); - purgeList(); - initThreads(); - while(!stop) { - assignTasks(); - checkTasksStatus(); - - } - } - System.err.println("Reassembling file parts."); - reassembleFile(); - } catch(InternalError e) { - System.err.println("Error while downloading file. Aborting."); - } finally { - stopTasks(); - } - } - - /** Starts threads for each server in hostList. - */ - private void initThreads() { - for(HostItem hostItem: hostList) { - sockList.add(new ClientDownloadPartUDP(this, filename, hostItem.getUDPSocket(), partsSubdir)); - } - for(ClientDownloadPartUDP c: sockList) { - Thread t = new Thread(c); - t.start(); - } - System.err.println("Threads initialized"); - } - - /** Remove tasks from failed threads. Update done status. - * @throws InternalError - */ - private void checkTasksStatus() throws InternalError { - try { - synchronized(this) { - this.wait(); - List sockListCpy = new ArrayList<>(sockList); - for(ClientDownloadPartUDP c: sockListCpy) { - if (c.hasFailed() == true) { - sockList.remove(c); - offsetsPending.removeAll(c.getFailed()); - offsetsToAsk.addAll(c.getFailed()); - } - try { - offsetsPending.removeAll(c.getDone()); - } catch (InterruptedException e) { - throw new InternalError(); - } - } - System.err.println("Task check status: " + offsetsToAsk.size() + " to asks, " + offsetsPending.size() + " pending"); - if (offsetsToAsk.isEmpty() && offsetsPending.isEmpty()) { - stop = true; - } - if (sockList.size() == 0) { - System.err.println("No thread working"); - throw new InternalError(); - } - } - } catch (InterruptedException e) { - throw new InternalError(); - } - } - - /** Assign tasks randomly to threads. - * @throws InternalError - */ - private void assignTasks() throws InternalError { - Random rand = new Random(); - for(long offset : offsetsToAsk) { - try { - sockList.get(rand.nextInt(sockList.size())).assignTask(offset); - offsetsPending.add(offset); - System.err.println("Assigned task "+ offset); - } catch(InterruptedException e) { - throw new InternalError(); - } - } - offsetsToAsk.removeAll(offsetsPending); - } - - /** Stop threads */ - private void stopTasks() { - for(ClientDownloadPartUDP c : sockList) { - try { - c.setStop(); - } catch (InterruptedException e) {} - } - } - - /** Get hashsum from server. - * @param hostItem server to ask hash - * @return hash512sum - * @throws InternalError - */ - private byte[] getHashSum512(HostItem hostItem) throws InternalError { - byte[] hash; - HashAlgorithm[] hashesAlgo = new HashAlgorithm[1]; - hashesAlgo[0] = HashAlgorithm.SHA512; - ProtocolP2PPacketUDP d = new ProtocolP2PPacketUDP((Payload) new HashRequest(filename, hashesAlgo)); - try { - d.sendRequest((Object)hostItem.getUDPSocket()); - try { - Payload pHash = d.receiveResponse().getPayload(); - assert pHash instanceof HashResponse : "This payload must be instance of HashResponse"; - if (!(pHash instanceof HashResponse)) { - throw new InternalError(); - } else { - hash = ((HashResponse)pHash).getHash(HashAlgorithm.SHA512); - } - } catch (EmptyDirectory e) { - hash = new byte[0]; - } catch (NotFound e) { - hash = new byte[0]; - // TODO: use more specific errors - } catch (EmptyFile e) { - throw new InternalError(); - } catch (ProtocolError e) { - throw new InternalError(); - } catch (InternalRemoteError e) { - throw new InternalError(); - } catch (VersionRemoteError e) { - throw new InternalError(); - } catch (ProtocolRemoteError e) { - throw new InternalError(); - } catch (TransmissionError e) { - throw new InternalError(); - } catch (VersionError e) { - throw new InternalError(); - } catch (SizeError e) { - throw new InternalError(); - } - return hash; - } catch (IOException e) { - throw new InternalError(); - } + public ClientDownloadUDP(String filename, List hostList, String partsSubdir, String dirStorage, Logger logger) { + super(filename, hostList, partsSubdir, dirStorage, logger); } - /** Removes servers not owning the correct file to download from list. - * This is done by comparing hash512sum. - * @throws InternalError + /** Create a clientDownloadPart + * @param filename name of the file to download + * @param hostItem Hostitem of the server */ - private void purgeList() throws InternalError { - List blackList = new ArrayList(); - boolean first = false; - byte[] hashsum; - for(HostItem host: hostList) { - // already have hashsum from 1st server - if (!first) { - first = true; - continue; - } - // ask hashsum - hashsum = getHashSum512(host); - if (!Arrays.equals(hash512, hashsum)) { - blackList.add(host); - } - } - // purge list - for(HostItem host: blackList) { - hostList.remove(host); - } - System.err.println("Host list purge: done"); + protected ClientDownloadPart createDownloadPart(String filename, HostItem hostItem) { + return (ClientDownloadPart)new ClientDownloadPartUDP((ClientDownload)this, filename, hostItem.getUDPSocket(), partsSubdir, logger); } - /** Getter for hash512sum - * @return hash512sum + /** Implementation of writeLog + * @param text Text to log + * @param logLevel level of logging */ - public byte[] getHashSum512() { - return hash512; + protected void writeLog(String text, LogLevel logLevel) { + logger.writeUDP(text, logLevel); } - /** Initialize infos about file to download (size, hash512sum, partslist to dl). - * Also download first partfile (to get size). - * @throws InternalError + /** Implementation of writeLog + * @param e exception to log + * @param logLevel level of logging */ - private void init() throws InternalError { - // get size - setSize(); - - // get hashsum from 1st server in list - hash512 = getHashSum512(hostList.get(0)); - if (hash512.length == 0) { - System.err.println("Error: no hash512sum support."); - throw new InternalError(); - } - - // Add tasks - if (!stop) { - for(long i=MAX_PARTIAL_SIZE; i ProtocolP2PPacket createProtocolP2PPacket(T payload) { + return (ProtocolP2PPacket)new ProtocolP2PPacketUDP(payload); } - /** Success getter. - * @return true when file have successfully been reassembled. + /** Getter for HostItem socket + * @param hostItem HostItem */ - public boolean getSuccess() { - return success; + protected Object getHostItemSocket(HostItem hostItem) { + return (Object)hostItem.getUDPSocket(); } - /** Reassemble file from file parts. - * Set success to true if file is reassembled successfully. + /** Close HostItem socket + * @param hostItem HostItem */ - private void reassembleFile() { - boolean firstPart = true; - boolean abort = false; - long nextOffset = 0; - do { - if (firstPart) { - System.err.println("Reassembling: First part"); - try { - // create file - Files.copy(new File(partsSubdir + filename + "_" + nextOffset + ".part").toPath(), new File(dirStorage + filename).toPath(), StandardCopyOption.REPLACE_EXISTING); - nextOffset = (new File(dirStorage + filename)).length(); - firstPart = false; - } catch (IOException e) { - System.err.println("Reassembling: aborting on first part"); - abort = true; - } - } else if (nextOffset >= size) { - success = true; - System.err.println("Reassembling: success"); - } else { - // append to file - try { - Files.write(new File(dirStorage + filename).toPath(), Files.readAllBytes(new File(partsSubdir + filename + "_" + nextOffset + ".part").toPath()), StandardOpenOption.APPEND); - nextOffset = (new File(dirStorage + filename)).length(); - } catch (IOException e) { - abort = true; - System.err.println("Aborting: bad number " + nextOffset); - } - } - } while((!success) && (!abort)); + protected void closeHostItemSocket(HostItem hostItem) { + hostItem.closeUDPSocket(); } } diff --git a/src/clientP2P/ClientManagement.java b/src/clientP2P/ClientManagement.java new file mode 100644 index 0000000..cf1dad8 --- /dev/null +++ b/src/clientP2P/ClientManagement.java @@ -0,0 +1,262 @@ +package clientP2P; +import tools.HostItem; +import tools.Logger; +import tools.LogLevel; +import java.util.Scanner; +import java.util.List; +import localException.ProtocolError; +import tools.ServeErrors; +import protocolP2P.RequestResponseCode; +import protocolP2P.FileList; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.DiscoverRequest; +import protocolP2P.DiscoverResponse; +import protocolP2P.Payload; +import protocolP2P.HashAlgorithm; +import localException.InternalError; +import localException.ProtocolError; +import localException.SizeError; +import localException.TransmissionError; +import localException.VersionError; +import localException.SocketClosed; +import remoteException.EmptyFile; +import remoteException.EmptyDirectory; +import remoteException.InternalRemoteError; +import remoteException.NotFound; +import remoteException.ProtocolRemoteError; +import remoteException.VersionRemoteError; +import remoteException.NotATracker; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; + + +/** Implementation of P2P-JAVA-PROJECT CLIENT + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public abstract class ClientManagement extends ServeErrors implements Runnable { + protected String baseDirectory; + protected String partsSubdir; + protected List hostList; + protected HostItem tracker; + protected Logger logger; + protected Scanner scanner; + protected ClientDownload downLoader; + + /** Constructor with baseDirectory, tracker, partsSubdir, logger, and scanner parameters. + * @param baseDirectory the root directory where files are stored + * @param tracker Tracker hostItem + * @param partsSubdir subdirectory to store file parts + * @param logger Loggger + * @param scanner Scanner used to read input + */ + public ClientManagement(String baseDirectory, HostItem tracker, String partsSubdir, Logger logger, Scanner scanner) { + this.scanner = scanner; + this.baseDirectory = baseDirectory; + this.tracker = tracker; + this.partsSubdir = partsSubdir; + this.logger = logger; + try { + initHostList(); + } catch (InternalError e) { + System.exit(-1); + } catch (ProtocolError e) { + System.exit(-2); + } + } + + /** Getter for tracker socket + */ + protected abstract Object getTrackerSocket(); + + + /** Initialize hostList from tracker + * @throws ProtocolError + * @throws InternalError + */ + private void initHostList() throws ProtocolError, InternalError { + ProtocolP2PPacket d = createProtocolP2PPacket(new DiscoverRequest(null)); + try { + d.sendRequest(getTrackerSocket()); + Payload p = d.receiveResponse().getPayload(); + assert p instanceof DiscoverResponse : "This payload must be instance of Filelist"; + if (!(p instanceof DiscoverResponse)) { + throw new InternalError(); + } else { + hostList = ((DiscoverResponse)p).getHostList(); + } + } catch (SocketClosed e){ + writeLog("listDirectory : SocketClosed", LogLevel.Error); + throw new ProtocolError(); + } catch (NotATracker e) { + writeLog(e, LogLevel.Error); + throw new ProtocolError(); + } catch (Exception e) { + writeLog(e, LogLevel.Error); + throw new ProtocolError(); + } + } + + /** Compute Hashsum of a file. + * @param filename + * @return hashsum + */ + protected byte[] computeHashsum(String filename, HashAlgorithm h) { + try { + MessageDigest md = MessageDigest.getInstance(HashAlgorithm.SHA512.getName()); + return md.digest(Files.readAllBytes(Paths.get(baseDirectory + filename))); + } catch (NoSuchAlgorithmException e) { + writeLog(h.getName() + " not supported", LogLevel.Error); + } catch (IOException e) { + writeLog("cannot read " + filename, LogLevel.Error); + } + return new byte[0]; + } + + /** Getter for HostItem socket + * @param hostItem HostItem + */ + protected abstract Object getHostItemSocket(HostItem hostItem); + + /** list server’s directory content + * @return list of files + * @throws InternalError + * @throws UnknowHostException + * @throws IOException + * @throws TransmissionError + * @throws ProtocolError + * @throws VersionError + * @throws SizeError + * @throws EmptyDirectory + * @throws InternalRemoteError + * @throws ProtocolRemoteError + * @throws VersionRemoteError + */ + protected String[] listDirectory() throws EmptyDirectory, InternalError, UnknownHostException, IOException, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError { + ProtocolP2PPacket d = createProtocolP2PPacket(new Payload(RequestResponseCode.LIST_REQUEST)); + try { + d.sendRequest(getHostItemSocket(hostList.get(0))); + Payload p = d.receiveResponse().getPayload(); + assert p instanceof FileList : "This payload must be instance of Filelist"; + if (!(p instanceof FileList)) { + throw new InternalError(); + } else { + return ((FileList)p).getFileList(); + } + } catch (NotFound e) { + writeLog(e, LogLevel.Error); + throw new ProtocolError(); + } catch (EmptyFile e) { + writeLog(e, LogLevel.Error); + throw new ProtocolError(); + } catch (SocketClosed e){ + writeLog("listDirectory : SocketClosed", LogLevel.Error); + throw new ProtocolError(); + } catch (NotATracker e) { + writeLog(e, LogLevel.Error); + throw new ProtocolError(); + } + } + + /** Initialize downloader + * @param filename Name of the file to download + */ + protected abstract void initDownloader(String filename); + + /** Try to download a file + * @param filename name of the file to download + * @throws NotFound + * @throws InternalError + * @throws UnknownHostException + * @throws IOException + * @throws TransmissionError + * @throws ProtocolError + * @throws VersionError + * @throws SizeError + * @throws InternalRemoteError + * @throws ProtocolRemoteError + * @throws VersionRemoteError + * @throws EmptyFile + */ + private void download(String filename) throws EmptyFile, NotFound, InternalError, UnknownHostException, IOException, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError { + initDownloader(filename); + Thread t = new Thread(downLoader); + t.start(); + try { + t.join(); + if (downLoader.getSuccess()) { + byte[] hash512 = downLoader.getHashSum512(); + if (!Arrays.equals(hash512, computeHashsum(filename, HashAlgorithm.SHA512))) { + writeLog("Hashsum does not match", LogLevel.Error); + String line = "Computed checksum:\n"; + byte[] c = computeHashsum(filename, HashAlgorithm.SHA512); + for (byte b: c) { + line += String.format("%02X", b); + } + line += "\nReceived checksum:\n"; + for (byte b: hash512) { + line += String.format("%02X", b); + } + line += "\n"; + writeLog(line, LogLevel.Info); + throw new InternalError(); + } + } else { + throw new InternalError(); + } + } catch (InterruptedException e) { + throw new InternalError(); + } + } + + /** Implementation of Runnable + */ + public void run() { + try { + String[] list = listDirectory(); + System.out.println("Files present on the server:"); + for(String listItem: list) { + System.out.println(listItem); + } + System.out.println("Name of the file to download:"); + String f = scanner.nextLine(); + download(f); + System.out.println("File " + f + " sucessfully downloaded"); + writeLog("File " + f + " sucessfully downloaded", LogLevel.Info); + } catch (EmptyDirectory e) { + writeLog("Server has no file in directory", LogLevel.Error); + } catch (InternalError e) { + writeLog("Client internal error", LogLevel.Error); + } catch (UnknownHostException e) { + writeLog("Server host is unknown", LogLevel.Error); + } catch (IOException e) { + writeLog("Request cannot be send or response cannot be received", LogLevel.Error); + } catch (TransmissionError e) { + writeLog("Message received is too big", LogLevel.Error); + } catch (ProtocolError e) { + writeLog("Cannot decode server’s response", LogLevel.Error); + } catch (VersionError e) { + writeLog("Server’s response use bad version of the protocol", LogLevel.Error); + } catch (SizeError e) { + writeLog("Cannot handle this packets because of internal representation limitations of numbers on the client", LogLevel.Error); + } catch (InternalRemoteError e) { + writeLog("Server internal error", LogLevel.Error); + } catch (ProtocolRemoteError e) { + writeLog("Server cannot decode client’s request", LogLevel.Error); + } catch (VersionRemoteError e) { + writeLog("Server cannot decode this version of the protocol", LogLevel.Error); + } catch (NotFound e) { + writeLog("Server has not this file in directory", LogLevel.Error); + } catch (EmptyFile e) { + writeLog("File is empty", LogLevel.Error); + } + } + +} diff --git a/src/clientP2P/ClientManagementTCP.java b/src/clientP2P/ClientManagementTCP.java index cdabf6e..559fd71 100644 --- a/src/clientP2P/ClientManagementTCP.java +++ b/src/clientP2P/ClientManagementTCP.java @@ -1,38 +1,15 @@ package clientP2P; -import exception.InternalError; -import exception.ProtocolError; -import exception.SizeError; -import exception.TransmissionError; -import exception.VersionError; -import remoteException.EmptyFile; -import remoteException.EmptyDirectory; -import remoteException.InternalRemoteError; -import remoteException.NotFound; -import remoteException.ProtocolRemoteError; -import remoteException.VersionRemoteError; -import java.net.UnknownHostException; + import java.util.Scanner; -import java.io.IOException; -import java.nio.file.Files; -import java.io.File; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.List; -import tools.HostItem; -import protocolP2P.ProtocolP2PPacketTCP; import protocolP2P.Payload; -import protocolP2P.RequestResponseCode; -import protocolP2P.FileList; -import protocolP2P.FilePart; -import protocolP2P.LoadRequest; -import protocolP2P.HashAlgorithm; -import protocolP2P.HashRequest; -import protocolP2P.HashResponse; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.ProtocolP2PPacketTCP; +import tools.HostItem; +import tools.Logger; +import tools.LogLevel; import clientP2P.ClientDownloadTCP; -import exception.SocketClosed; +import clientP2P.ClientManagement; + /** Implementation of P2P-JAVA-PROJECT CLIENT * @author Louis Royer @@ -40,160 +17,60 @@ import exception.SocketClosed; * @author JS Auge * @version 1.0 */ -public class ClientManagementTCP implements Runnable { - private String baseDirectory; - private String partsSubdir; - private List hostList; - - /** Constructor for TCP implementation, with baseDirectory and TCPPort parameters. +public class ClientManagementTCP extends ClientManagement { + /** Constructor for TCP implementation, with baseDirectory, tracker, partsSubdir, logger, and scanner parameters. * @param baseDirectory the root directory where files are stored - * @param host hostname of the server - * @param TCPPort the server will listen on this port + * @param tracker Tracker hostItem + * @param partsSubdir subdirectory to store file parts + * @param logger Loggger + * @param scanner Scanner used to read input + */ + public ClientManagementTCP(String baseDirectory, HostItem tracker, String partsSubdir, Logger logger, Scanner scanner) { + super(baseDirectory, tracker, partsSubdir, logger, scanner); + } + + /** Initialize downloader + * @param filename Name of the file to download + */ + protected void initDownloader(String filename) { + downLoader = (ClientDownload) new ClientDownloadTCP(filename, hostList, partsSubdir, baseDirectory, logger); + } + + + + /** Implementation of writeLog + * @param text Text to log + * @param logLevel level of logging */ - public ClientManagementTCP(String baseDirectory, List hostList, String partsSubdir) { - this.baseDirectory = baseDirectory; - this.hostList = hostList; - this.partsSubdir = partsSubdir; + protected void writeLog(String text, LogLevel logLevel) { + logger.writeTCP(text, logLevel); } - /** Implementation of Runnable + /** Implementation of writeLog + * @param e exception to log + * @param logLevel level of logging */ - public void run() { - try { - System.out.println("Enter all servers: type \"stop\" when finished"); - Scanner scanner = new Scanner(System.in); - String[] list = listDirectory(); - System.out.println("Files present on the server:"); - for(String listItem: list) { - System.out.println(listItem); - } - System.out.println("Name of the file to download:"); - String f = scanner.nextLine(); - download(f); - System.out.println("File sucessfully downloaded"); - } catch (EmptyDirectory e) { - System.err.println("Error: Server has no file in directory"); - } catch (InternalError e) { - System.err.println("Error: Client internal error"); - } catch (UnknownHostException e) { - System.err.println("Error: Server host is unknown"); - } catch (IOException e) { - System.err.println("Error: Request cannot be send or response cannot be received"); - } catch (TransmissionError e) { - System.err.println("Error: Message received is too big"); - } catch (ProtocolError e) { - System.err.println("Error: Cannot decode server’s response"); - } catch (VersionError e) { - System.err.println("Error: Server’s response use bad version of the protocol"); - } catch (SizeError e) { - System.err.println("Error: Cannot handle this packets because of internal representation limitations of numbers on the client"); - } catch (InternalRemoteError e) { - System.err.println("Error: Server internal error"); - } catch (ProtocolRemoteError e) { - System.err.println("Error: Server cannot decode client’s request"); - } catch (VersionRemoteError e) { - System.err.println("Error: Server cannot decode this version of the protocol"); - } catch (NotFound e) { - System.err.println("Error: Server has not this file in directory"); - } catch (EmptyFile e) { - System.err.println("Error: File is empty"); - } + protected void writeLog(Exception e, LogLevel logLevel) { + logger.writeTCP(e, logLevel); } - /** Try to download a file - * @param filename name of the file to download - * @throws NotFound - * @throws InternalError - * @throws UnknownHostException - * @throws IOException - * @throws TransmissionError - * @throws ProtocolError - * @throws VersionError - * @throws SizeError - * @throws InternalRemoteError - * @throws ProtocolRemoteError - * @throws VersionRemoteError - * @throws EmptyFile + /** Create packets + * @param payload Payload */ - private void download(String filename) throws EmptyFile, NotFound, InternalError, UnknownHostException, IOException, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError { - ClientDownloadTCP downLoader = new ClientDownloadTCP(filename, hostList, partsSubdir, baseDirectory); - Thread t = new Thread(downLoader); - t.start(); - try { - t.join(); - if (downLoader.getSuccess()) { - byte[] hash512 = downLoader.getHashSum512(); - if (!Arrays.equals(hash512, computeHashsum(filename, HashAlgorithm.SHA512))) { - System.err.println("Error: Hashsum does not match"); - System.err.println("Computed checksum:"); - byte[] c = computeHashsum(filename, HashAlgorithm.SHA512); - for (byte b: c) { - System.err.print(String.format("%02X", b)); - } - System.err.println(""); - System.err.println("Received checksum:"); - for (byte b: hash512) { - System.err.print(String.format("%02X", b)); - } - System.err.println(""); - throw new InternalError(); - } - } else { - throw new InternalError(); - } - } catch (InterruptedException e) { - throw new InternalError(); - } + protected < T extends Payload > ProtocolP2PPacket createProtocolP2PPacket(T payload) { + return (ProtocolP2PPacket)new ProtocolP2PPacketTCP(payload); } - /** list server’s directory content - * @return list of files - * @throws InternalError - * @throws UnknowHostException - * @throws IOException - * @throws TransmissionError - * @throws ProtocolError - * @throws VersionError - * @throws SizeError - * @throws EmptyDirectory - * @throws InternalRemoteError - * @throws ProtocolRemoteError - * @throws VersionRemoteError + /** Getter for tracker socket */ - private String[] listDirectory() throws EmptyDirectory, InternalError, UnknownHostException, IOException, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError { - ProtocolP2PPacketTCP d = new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.LIST_REQUEST)); - try { - d.sendRequest((Object)hostList.get(0).getTCPSocket()); - Payload p = d.receiveResponse().getPayload(); - assert p instanceof FileList : "This payload must be instance of Filelist"; - if (!(p instanceof FileList)) { - throw new InternalError(); - } else { - return ((FileList)p).getFileList(); - } - } catch (NotFound e) { - throw new ProtocolError(); - } catch (EmptyFile e) { - throw new ProtocolError(); - } catch (SocketClosed e){ - System.err.println("listDirectory : SocketClosed"); - throw new ProtocolError(); - } + protected Object getTrackerSocket() { + return (Object)tracker.getTCPSocket(); } - /** Compute Hashsum of a file. - * @param filename - * @return hashsum + /** Getter for HostItem socket + * @param hostItem HostItem */ - private byte[] computeHashsum(String filename, HashAlgorithm h) { - try { - MessageDigest md = MessageDigest.getInstance(HashAlgorithm.SHA512.getName()); - return md.digest(Files.readAllBytes(Paths.get(baseDirectory + filename))); - } catch (NoSuchAlgorithmException e) { - System.out.println("Error: " + h.getName() + " not supported"); - } catch (IOException e) { - System.out.println("Error: cannot read " + filename); - } - return new byte[0]; + protected Object getHostItemSocket(HostItem hostItem) { + return (Object)hostItem.getTCPSocket(); } } diff --git a/src/clientP2P/ClientManagementUDP.java b/src/clientP2P/ClientManagementUDP.java index c50c001..19e7a0b 100644 --- a/src/clientP2P/ClientManagementUDP.java +++ b/src/clientP2P/ClientManagementUDP.java @@ -1,38 +1,14 @@ package clientP2P; -import exception.InternalError; -import exception.ProtocolError; -import exception.SizeError; -import exception.TransmissionError; -import exception.VersionError; -import remoteException.EmptyFile; -import remoteException.EmptyDirectory; -import remoteException.InternalRemoteError; -import remoteException.NotFound; -import remoteException.ProtocolRemoteError; -import remoteException.VersionRemoteError; -import java.net.UnknownHostException; + import java.util.Scanner; -import java.net.DatagramSocket; -import java.io.IOException; -import java.nio.file.Files; -import java.io.File; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.List; -import tools.HostItem; -import protocolP2P.ProtocolP2PPacketUDP; import protocolP2P.Payload; -import protocolP2P.RequestResponseCode; -import protocolP2P.FileList; -import protocolP2P.FilePart; -import protocolP2P.LoadRequest; -import protocolP2P.HashAlgorithm; -import protocolP2P.HashRequest; -import protocolP2P.HashResponse; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.ProtocolP2PPacketUDP; +import tools.HostItem; +import tools.Logger; +import tools.LogLevel; import clientP2P.ClientDownloadUDP; +import clientP2P.ClientManagement; /** Implementation of P2P-JAVA-PROJECT CLIENT * @author Louis Royer @@ -40,157 +16,65 @@ import clientP2P.ClientDownloadUDP; * @author JS Auge * @version 1.0 */ -public class ClientManagementUDP implements Runnable { - private String baseDirectory; - private String partsSubdir; - private List hostList; - - /** Constructor for UDP implementation, with baseDirectory and UDPPort parameters. +public class ClientManagementUDP extends ClientManagement { + /** Constructor for UDP implementation, with baseDirectory, tracker, partsSubdir, logger and scanner parameters. * @param baseDirectory the root directory where files are stored - * @param host hostname of the server - * @param UDPPort the server will listen on this port + * @param tracker tracker HostItem + * @param partsSubdir subdirectory to store file parts + * @param logger Loggger + * @param scanner Scanner used to read input + */ + public ClientManagementUDP(String baseDirectory, HostItem tracker, String partsSubdir, Logger logger, Scanner scanner) { + super(baseDirectory, tracker, partsSubdir, logger, scanner); + } + + /** Initialize downloader + * @param filename Name of the file to download + */ + protected void initDownloader(String filename) { + downLoader = (ClientDownload) new ClientDownloadUDP(filename, hostList, partsSubdir, baseDirectory, logger); + } + + /** 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 */ - public ClientManagementUDP(String baseDirectory, List hostList, String partsSubdir) { - this.baseDirectory = baseDirectory; - this.hostList = hostList; - this.partsSubdir = partsSubdir; + protected void writeLog(Exception e, LogLevel logLevel) { + logger.writeUDP(e, logLevel); } - /** Implementation of Runnable + /** Create packets + * @param payload Payload */ - public void run() { - try { - System.out.println("Enter all servers: type \"stop\" when finished"); - Scanner scanner = new Scanner(System.in); - String[] list = listDirectory(); - System.out.println("Files present on the server:"); - for(String listItem: list) { - System.out.println(listItem); - } - System.out.println("Name of the file to download:"); - String f = scanner.nextLine(); - download(f); - System.out.println("File sucessfully downloaded"); - } catch (EmptyDirectory e) { - System.err.println("Error: Server has no file in directory"); - } catch (InternalError e) { - System.err.println("Error: Client internal error"); - } catch (UnknownHostException e) { - System.err.println("Error: Server host is unknown"); - } catch (IOException e) { - System.err.println("Error: Request cannot be send or response cannot be received"); - } catch (TransmissionError e) { - System.err.println("Error: Message received is too big"); - } catch (ProtocolError e) { - System.err.println("Error: Cannot decode server’s response"); - } catch (VersionError e) { - System.err.println("Error: Server’s response use bad version of the protocol"); - } catch (SizeError e) { - System.err.println("Error: Cannot handle this packets because of internal representation limitations of numbers on the client"); - } catch (InternalRemoteError e) { - System.err.println("Error: Server internal error"); - } catch (ProtocolRemoteError e) { - System.err.println("Error: Server cannot decode client’s request"); - } catch (VersionRemoteError e) { - System.err.println("Error: Server cannot decode this version of the protocol"); - } catch (NotFound e) { - System.err.println("Error: Server has not this file in directory"); - } catch (EmptyFile e) { - System.err.println("Error: File is empty"); - } + protected < T extends Payload > ProtocolP2PPacket createProtocolP2PPacket(T payload) { + return (ProtocolP2PPacket)new ProtocolP2PPacketUDP(payload); } - /** Try to download a file - * @param filename name of the file to download - * @throws NotFound - * @throws InternalError - * @throws UnknownHostException - * @throws IOException - * @throws TransmissionError - * @throws ProtocolError - * @throws VersionError - * @throws SizeError - * @throws InternalRemoteError - * @throws ProtocolRemoteError - * @throws VersionRemoteError - * @throws EmptyFile + /** Getter for tracker socket */ - private void download(String filename) throws EmptyFile, NotFound, InternalError, UnknownHostException, IOException, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError { - ClientDownloadUDP downLoader = new ClientDownloadUDP(filename, hostList, partsSubdir, baseDirectory); - Thread t = new Thread(downLoader); - t.start(); - try { - t.join(); - if (downLoader.getSuccess()) { - byte[] hash512 = downLoader.getHashSum512(); - if (!Arrays.equals(hash512, computeHashsum(filename, HashAlgorithm.SHA512))) { - System.err.println("Error: Hashsum does not match"); - System.err.println("Computed checksum:"); - byte[] c = computeHashsum(filename, HashAlgorithm.SHA512); - for (byte b: c) { - System.err.print(String.format("%02X", b)); - } - System.err.println(""); - System.err.println("Received checksum:"); - for (byte b: hash512) { - System.err.print(String.format("%02X", b)); - } - System.err.println(""); - throw new InternalError(); - } - } else { - throw new InternalError(); - } - } catch (InterruptedException e) { - throw new InternalError(); - } + protected Object getTrackerSocket() { + return (Object)tracker.getUDPSocket(); } - /** list server’s directory content - * @return list of files - * @throws InternalError - * @throws UnknowHostException - * @throws IOException - * @throws TransmissionError - * @throws ProtocolError - * @throws VersionError - * @throws SizeError - * @throws EmptyDirectory - * @throws InternalRemoteError - * @throws ProtocolRemoteError - * @throws VersionRemoteError + /** Getter for HostItem socket + * @param hostItem HostItem */ - private String[] listDirectory() throws EmptyDirectory, InternalError, UnknownHostException, IOException, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError { - ProtocolP2PPacketUDP d = new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.LIST_REQUEST)); - d.sendRequest((Object)hostList.get(0).getUDPSocket()); - try { - Payload p = d.receiveResponse().getPayload(); - assert p instanceof FileList : "This payload must be instance of Filelist"; - if (!(p instanceof FileList)) { - throw new InternalError(); - } else { - return ((FileList)p).getFileList(); - } - } catch (NotFound e) { - throw new ProtocolError(); - } catch (EmptyFile e) { - throw new ProtocolError(); - } + protected Object getHostItemSocket(HostItem hostItem) { + return (Object)hostItem.getUDPSocket(); } - /** Compute Hashsum of a file. - * @param filename - * @return hashsum + /** Close HostItem socket + * @param hostItem HostItem */ - private byte[] computeHashsum(String filename, HashAlgorithm h) { - try { - MessageDigest md = MessageDigest.getInstance(HashAlgorithm.SHA512.getName()); - return md.digest(Files.readAllBytes(Paths.get(baseDirectory + filename))); - } catch (NoSuchAlgorithmException e) { - System.out.println("Error: " + h.getName() + " not supported"); - } catch (IOException e) { - System.out.println("Error: cannot read " + filename); - } - return new byte[0]; + protected void closeHostItemSocket(HostItem hostItem) { + hostItem.closeUDPSocket(); } } diff --git a/src/clientP2P/ClientP2P.java b/src/clientP2P/ClientP2P.java index f857427..5985479 100644 --- a/src/clientP2P/ClientP2P.java +++ b/src/clientP2P/ClientP2P.java @@ -1,16 +1,19 @@ package clientP2P; + +import java.util.Scanner; +import java.util.List; import clientP2P.ClientManagementUDP; import clientP2P.ClientManagementTCP; + import serverP2P.ServerManagementUDP; import serverP2P.ServerManagementTCP; -import tools.Directories; import tools.Logger; import tools.LogLevel; import tools.Directories; -import java.util.Scanner; -import java.util.List; import tools.HostItem; -import tools.HostList; +import tools.ServerPortRange; +import tools.TrackerPortRange; + /** Client + Server implementation. * @author Louis Royer @@ -18,106 +21,181 @@ import tools.HostList; * @author JS Auge * @version 1.0 */ + public class ClientP2P { - static private final String subdir = "seeded/"; - static private String parts = ".parts"; - private Logger logger; - private String host; - private int port; + private String logDir = "logs/"; + private String partsDir = ".parts/"; + private Logger loggerServer; + private Logger loggerClient; private Directories directories; - private List hostList; - private static final int defaultPort = 20000; - - - /** Initialize logger if directories and logger are null, + private HostItem tracker; + private HostItem server; + private Scanner scanner; + + /** Initialize loggers if directories and logger are null, * else fail silently. */ - public void initLogger() { - if (directories == null && logger == null) { - directories = new Directories("P2P_JAVA_PROJECT" + port); - logger = new Logger(directories.getDataHomeDirectory() + "server.log"); + public void initDirectoriesAndLoggers() { + if (directories == null && loggerServer == null && loggerClient == null) { + directories = new Directories("P2P_JAVA_PROJECT_" + server.getPort()); + directories.createSubdir(logDir); + loggerServer = new Logger(directories.getDataHomeDirectory() + logDir + "server.log"); + loggerClient = new Logger(directories.getDataHomeDirectory() + logDir + "client.log"); + directories.createSubdir(partsDir); } } - /** Constructor with portStr as parameter. - * @param portStr String containing port for server listenning. + /** Constructor. + * @param hostnameServer hostname to bind + * @param portServer port to bind + * @param hostnameTracker hostname of tracker + * @param portTracker port of tracker */ - public ClientP2P(String portStr) { - try{ - port = Integer.valueOf(Integer.parseInt(portStr)); - } catch (NumberFormatException e){ - int oldPort = port; - port = defaultPort; - initLogger(); - System.err.println("Error incorrect port " + oldPort + " using default port " + defaultPort); - logger.write("incorrect port " + oldPort + " using default port " + defaultPort, LogLevel.Info); - } - initLogger(); - directories.createSubdir(subdir); - directories.createSubdir(parts); - host = "localhost"; - System.out.println("Server will listen on port " + port + " and serve files from " + directories.getDataHomeDirectory() + subdir); - directories.askOpenDataHomeDirectory(subdir); - System.out.println("Please enter list of servers to use; first one will be used to ask list of files"); + public ClientP2P(String hostnameServer, int portServer, String hostnameTracker, int portTracker) { + scanner = new Scanner(System.in); + server = new HostItem(hostnameServer, portServer); + tracker = new HostItem(hostnameTracker, portTracker); + initDirectoriesAndLoggers(); + System.out.println("Server will listen on port " + portServer + " and serve files from " + directories.getDataHomeDirectory()); + directories.askOpenDataHomeDirectory(null, scanner); + } + + /** Print cli usage + * @param serverPortRange range of server ports + * @param trackerPortRange range of tracker ports + */ + private static void printUsage(ServerPortRange serverPortRange, TrackerPortRange trackerPortRange) { + System.out.println("usage :"); + System.out.println("\tjava clientP2P.ClientP2P"); + System.out.println("or"); + System.out.println("java clientP2P.ClientP2P -- " + + " " + + " " + + " "); + System.out.println("(" + trackerPortRange + " and " + serverPortRange +")"); } /** Main program entry point. * 1rst parameter is optionnal, and is used to * define port used by the server module to listen. If not provided, default to another port. * @param args server listenning port - */ + */ public static void main(String [] args) { - ClientP2P c; - try { - c = new ClientP2P(args[1]); - } catch (IndexOutOfBoundsException e){ - c = new ClientP2P("" + defaultPort); + final String defaultHostname = "localhost"; + String hostnameServer = ""; + int portServer = 0; + String hostnameTracker = ""; + int portTracker = 0; + String protocolClient = ""; + Scanner scanner = new Scanner(System.in); + final ServerPortRange serverPortRange = new ServerPortRange(); + final TrackerPortRange trackerPortRange = new TrackerPortRange(); + + if ((args.length != 6) && (args.length != 0)){ + ClientP2P.printUsage(serverPortRange, trackerPortRange); + System.exit(1); + } + else if(args.length == 6){ + protocolClient = args[1]; + hostnameServer = args[2]; + portServer = Integer.valueOf(Integer.parseInt(args[3])); + hostnameTracker = args[4]; + portTracker = Integer.valueOf(Integer.parseInt(args[5])); + } else{ + System.out.println("Client, wich transport protocol do you want to use (default = TCP): "); + protocolClient = scanner.nextLine(); + System.out.println("server side, enter hostname to bind (default = localhost): "); + hostnameServer = scanner.nextLine(); + if(hostnameServer.equals("")){ + hostnameServer = defaultHostname; + System.out.println("using default hostname : " + hostnameServer); + } + System.out.println("enter port (default = " + serverPortRange.getDefaultPort() +"): "); + String portServerStr = scanner.nextLine(); + if(portServerStr.equals("")){ + portServer = serverPortRange.getDefaultPort(); + System.out.println("using default port : " + portServer); + } else { + portServer = Integer.valueOf(Integer.parseInt(portServerStr)); + } + System.out.println("enter hostname of tracker (default = localhost): "); + hostnameTracker = scanner.nextLine(); + if(hostnameTracker.equals("")){ + hostnameTracker = defaultHostname; + System.out.println("tracker default hostname : " + hostnameTracker); + } + System.out.println("enter tracker's port (default = "+trackerPortRange.getDefaultPort() + "): "); + String portTrackerStr = scanner.nextLine(); + if(portTrackerStr.equals("")){ + portTracker = trackerPortRange.getDefaultPort(); + System.out.println("using default port : " + portTracker); + } else { + portTracker = Integer.valueOf(Integer.parseInt(portTrackerStr)); + } + } + + System.out.println("using hostname : " + hostnameServer); + if(serverPortRange.isPortInRange(portServer)){ + System.out.println("using port : " + portServer); + } + else { + System.out.println("Port not in range. " + serverPortRange); + portServer = serverPortRange.getDefaultPort(); } + System.out.println("tracker hostname : " + hostnameTracker); - // Server threads - ServerManagementUDP smudp = new ServerManagementUDP(c.directories.getDataHomeDirectory() + subdir, c.port, c.logger); - ServerManagementTCP smtcp = new ServerManagementTCP(c.directories.getDataHomeDirectory() + subdir, c.port, c.logger); + if(trackerPortRange.isPortInRange(portTracker)){ + System.out.println("using port : " + portTracker); + } + else { + System.out.println("Port not in range. " + trackerPortRange); + portTracker = trackerPortRange.getDefaultPort(); + } + + ClientP2P c = new ClientP2P(hostnameServer, portServer, hostnameTracker, portTracker); + + ServerManagementUDP smudp = new ServerManagementUDP(c.directories.getDataHomeDirectory(), c.server, c.tracker, c.loggerServer); + ServerManagementTCP smtcp = new ServerManagementTCP(c.directories.getDataHomeDirectory(), c.server, c.tracker, c.loggerServer); Thread tudp = new Thread(smudp); - tudp.setName("server UDP P2P-JAVA-PROJECT (port: " + c.port + ")"); + tudp.setName("server UDP P2P-JAVA-PROJECT"); tudp.start(); Thread ttcp = new Thread(smtcp); - ttcp.setName("server TCP P2P-JAVA-PROJECT (port: " + c.port + ")"); + ttcp.setName("server TCP P2P-JAVA-PROJECT"); ttcp.start(); // Wait a bit before printing client interface // This is not required, but allow to have a cleaner interface try { - Thread.sleep(100); + Thread.sleep(200); } catch(InterruptedException e) { Thread.currentThread().interrupt(); } - // initialize Host lists - c.hostList = HostList.getServList(); - System.out.println("Client : Which transport protocol do you want to use? [TCP/udp]"); - Scanner sc = new Scanner(System.in); - String transportchoosen = sc.nextLine(); - Thread t; - switch(transportchoosen){ + Thread tclient; + switch(protocolClient){ case "UDP": case "udp": - case "upd": // alias typo + case "upd": // to avoid users typos case "2" : System.out.println("Starting with UDP"); - ClientManagementUDP cmudp = new ClientManagementUDP(c.directories.getDataHomeDirectory(), c.hostList, c.directories.getDataHomeDirectory() + c.parts + "/"); - t = new Thread(cmudp); + ClientManagementUDP cmudp = new ClientManagementUDP(c.directories.getDataHomeDirectory(), c.tracker, c.directories.getDataHomeDirectory() + c.partsDir, c.loggerClient, c.scanner); + tclient = new Thread(cmudp); break; case "TCP": case "tcp": case "1": default: System.out.println("Starting with TCP"); - ClientManagementTCP cmtcp = new ClientManagementTCP(c.directories.getDataHomeDirectory(), c.hostList, c.directories.getDataHomeDirectory() + c.parts + "/"); - t = new Thread(cmtcp); + ClientManagementTCP cmtcp = new ClientManagementTCP(c.directories.getDataHomeDirectory(), c.tracker, c.directories.getDataHomeDirectory() + c.partsDir, c.loggerClient, c.scanner); + tclient = new Thread(cmtcp); break; - } - - t.setName("client P2P-JAVA-PROJECT"); - t.start(); + } + tclient.setName("client P2P-JAVA-PROJECT"); + tclient.start(); + try { + tclient.join(); + } catch (InterruptedException e) {} + smudp.setStop(); + smtcp.setStop(); } } diff --git a/src/exception/InternalError.java b/src/exception/LocalException.java similarity index 55% rename from src/exception/InternalError.java rename to src/exception/LocalException.java index 80949cf..2fbc0f2 100644 --- a/src/exception/InternalError.java +++ b/src/exception/LocalException.java @@ -1,4 +1,5 @@ package exception; -public class InternalError extends Exception { + +public abstract class LocalException extends Exception { private static final long serialVersionUID = 12L; } diff --git a/src/exception/ProtocolError.java b/src/exception/RemoteException.java similarity index 54% rename from src/exception/ProtocolError.java rename to src/exception/RemoteException.java index a9f6130..968c8db 100644 --- a/src/exception/ProtocolError.java +++ b/src/exception/RemoteException.java @@ -1,4 +1,5 @@ package exception; -public class ProtocolError extends Exception { + +public abstract class RemoteException extends Exception { private static final long serialVersionUID = 12L; } diff --git a/src/exception/SocketClosed.java b/src/exception/SocketClosed.java deleted file mode 100644 index 4972843..0000000 --- a/src/exception/SocketClosed.java +++ /dev/null @@ -1,4 +0,0 @@ -package exception; -public class SocketClosed extends Exception { - private static final long serialVersionUID = 12L; -} diff --git a/src/exception/TransmissionError.java b/src/exception/TransmissionError.java deleted file mode 100644 index 042c0ed..0000000 --- a/src/exception/TransmissionError.java +++ /dev/null @@ -1,4 +0,0 @@ -package exception; -public class TransmissionError extends Exception { - private static final long serialVersionUID = 12L; -} diff --git a/src/exception/VersionError.java b/src/exception/VersionError.java deleted file mode 100644 index 400fdcb..0000000 --- a/src/exception/VersionError.java +++ /dev/null @@ -1,4 +0,0 @@ -package exception; -public class VersionError extends Exception { - private static final long serialVersionUID = 12L; -} diff --git a/src/localException/InternalError.java b/src/localException/InternalError.java new file mode 100644 index 0000000..42794a5 --- /dev/null +++ b/src/localException/InternalError.java @@ -0,0 +1,7 @@ +package localException; + +import exception.LocalException; + +public class InternalError extends LocalException { + private static final long serialVersionUID = 12L; +} diff --git a/src/localException/ProtocolError.java b/src/localException/ProtocolError.java new file mode 100644 index 0000000..14aeca8 --- /dev/null +++ b/src/localException/ProtocolError.java @@ -0,0 +1,7 @@ +package localException; + +import exception.LocalException; + +public class ProtocolError extends LocalException { + private static final long serialVersionUID = 12L; +} diff --git a/src/exception/SizeError.java b/src/localException/SizeError.java similarity index 61% rename from src/exception/SizeError.java rename to src/localException/SizeError.java index ccf9ef1..bb13830 100644 --- a/src/exception/SizeError.java +++ b/src/localException/SizeError.java @@ -1,5 +1,9 @@ -package exception; +package localException; + +import exception.LocalException; + + /** Used on reception side when size as set in Packet is too big, and we cant store this in a int/long as usual. */ -public class SizeError extends Exception { +public class SizeError extends LocalException { private static final long serialVersionUID = 12L; } diff --git a/src/localException/SocketClosed.java b/src/localException/SocketClosed.java new file mode 100644 index 0000000..d3e68c5 --- /dev/null +++ b/src/localException/SocketClosed.java @@ -0,0 +1,7 @@ +package localException; + +import exception.LocalException; + +public class SocketClosed extends LocalException { + private static final long serialVersionUID = 12L; +} diff --git a/src/localException/TransmissionError.java b/src/localException/TransmissionError.java new file mode 100644 index 0000000..d5a539b --- /dev/null +++ b/src/localException/TransmissionError.java @@ -0,0 +1,7 @@ +package localException; + +import exception.LocalException; + +public class TransmissionError extends LocalException { + private static final long serialVersionUID = 12L; +} diff --git a/src/localException/VersionError.java b/src/localException/VersionError.java new file mode 100644 index 0000000..a793e27 --- /dev/null +++ b/src/localException/VersionError.java @@ -0,0 +1,7 @@ +package localException; + +import exception.LocalException; + +public class VersionError extends LocalException { + private static final long serialVersionUID = 12L; +} diff --git a/src/protocolP2P/CodeType.java b/src/protocolP2P/CodeType.java index e38de49..87f3e49 100644 --- a/src/protocolP2P/CodeType.java +++ b/src/protocolP2P/CodeType.java @@ -8,6 +8,7 @@ package protocolP2P; */ public enum CodeType { REQUEST, + REQUEST_TRACKER, RESPONSE, ERROR } diff --git a/src/protocolP2P/DiscoverRequest.java b/src/protocolP2P/DiscoverRequest.java new file mode 100644 index 0000000..7ef1751 --- /dev/null +++ b/src/protocolP2P/DiscoverRequest.java @@ -0,0 +1,71 @@ +package protocolP2P; +import protocolP2P.Payload; +import tools.BytesArrayTools; +import localException.InternalError; +import localException.SizeError; +import localException.ProtocolError; +import localException.TransmissionError; + +/** Representation of payload for discover request. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class DiscoverRequest extends Payload { + + private String filename; + + /** Constructor with filename (typically used by client). If filename is null, it is initialized with "". + * @param filename Name of the file you want a server list of. + * @throws InternalError + */ + public DiscoverRequest(String filename) throws InternalError { + super(RequestResponseCode.DISCOVER_REQUEST); + if (filename == null) { + this.filename = ""; + } else { + 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 DiscoverRequest(byte[] packet) throws SizeError, ProtocolError, InternalError, TransmissionError { + super(packet); + int size = getPayloadSize(packet); + filename = BytesArrayTools.readString(packet, Payload.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 { + // compute total size + int size = PAYLOAD_START_POSITION + filename.length(); + byte[] packet = new byte[size + 1]; // java initialize all to zero + // set request/response code + packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; + // set Payload size + setPayloadSize(size - PAYLOAD_START_POSITION, packet); + // write filename to Packet + BytesArrayTools.write(packet, filename, PAYLOAD_START_POSITION); + return packet; + } + + /** Filename getter. + * @return filename + */ + public String getFilename() { + return filename; + } + +} diff --git a/src/protocolP2P/DiscoverResponse.java b/src/protocolP2P/DiscoverResponse.java new file mode 100644 index 0000000..4c73e36 --- /dev/null +++ b/src/protocolP2P/DiscoverResponse.java @@ -0,0 +1,117 @@ +package protocolP2P; +import protocolP2P.Payload; +import tools.HostItem; +import java.util.ArrayList; +import java.util.List; +import localException.InternalError; +import localException.SizeError; +import localException.ProtocolError; +import localException.TransmissionError; +import tools.BytesArrayTools; + +/** Representation of payload for discover response. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class DiscoverResponse extends Payload { + + private List hostList; + private String filename; + private static final int FILENAME_SIZE_POSITION = PAYLOAD_START_POSITION; + private static final int FILENAME_POSITION = FILENAME_SIZE_POSITION + 4; + + /** Constructor with filename (typically used by tracker). If filename is null, it is initialized with "". + * @param filename Name of the file related to the server list. + * @param hostList List of servers + * @throws InternalError + */ + public DiscoverResponse(String filename, List hostList) throws InternalError { + super(RequestResponseCode.DISCOVER_RESPONSE); + this.filename = filename; + this.hostList = hostList; + } + + /** 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 DiscoverResponse(byte[] packet) throws SizeError, ProtocolError, InternalError, TransmissionError { + super(packet); + int size = getPayloadSize(packet); + /* Read filename size */ + int filenameSize = BytesArrayTools.readInt(packet, FILENAME_SIZE_POSITION); + + /* Read filename */ + filename = BytesArrayTools.readString(packet, FILENAME_POSITION, filenameSize); + + // TODO + hostList = new ArrayList<>(); + int i = FILENAME_POSITION + filenameSize; + while(i getHostList() { + return hostList; + } + + /** Filename getter. + * @return filename + */ + public String getFilename() { + return filename; + } +} diff --git a/src/protocolP2P/FileList.java b/src/protocolP2P/FileList.java index 1c04f30..b013bea 100644 --- a/src/protocolP2P/FileList.java +++ b/src/protocolP2P/FileList.java @@ -1,12 +1,10 @@ package protocolP2P; -import java.util.Arrays; import protocolP2P.Payload; import protocolP2P.RequestResponseCode; -import exception.TransmissionError; -import exception.ProtocolError; -import exception.InternalError; -import exception.SizeError; -import java.io.UnsupportedEncodingException; +import localException.TransmissionError; +import localException.ProtocolError; +import localException.InternalError; +import localException.SizeError; import tools.BytesArrayTools; /** Representation of payload for list response. diff --git a/src/protocolP2P/FilePart.java b/src/protocolP2P/FilePart.java index df5699f..d168a05 100644 --- a/src/protocolP2P/FilePart.java +++ b/src/protocolP2P/FilePart.java @@ -1,13 +1,11 @@ package protocolP2P; import protocolP2P.Payload; import protocolP2P.RequestResponseCode; -import exception.ProtocolError; -import exception.InternalError; -import exception.SizeError; -import exception.TransmissionError; +import localException.ProtocolError; +import localException.InternalError; +import localException.SizeError; +import localException.TransmissionError; import tools.BytesArrayTools; -import java.util.Arrays; -import java.io.UnsupportedEncodingException; /** Representation of payload for load response. * @author Louis Royer @@ -36,7 +34,7 @@ public class FilePart extends Payload { 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 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"; @@ -83,7 +81,7 @@ public class FilePart extends Payload { // set request/response code packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; // set Payload size - setPayloadSize(size - OFFSET_POSITION, packet); + setPayloadSize(size - PAYLOAD_START_POSITION, packet); // write offset to Packet BytesArrayTools.write(packet, OFFSET_POSITION, offset); // write totalSize to Packet diff --git a/src/protocolP2P/HashRequest.java b/src/protocolP2P/HashRequest.java index 92ab812..1eec671 100644 --- a/src/protocolP2P/HashRequest.java +++ b/src/protocolP2P/HashRequest.java @@ -1,11 +1,10 @@ package protocolP2P; import protocolP2P.Payload; import protocolP2P.HashAlgorithm; -import java.io.UnsupportedEncodingException; -import exception.TransmissionError; -import exception.SizeError; -import exception.ProtocolError; -import exception.InternalError; +import localException.TransmissionError; +import localException.SizeError; +import localException.ProtocolError; +import localException.InternalError; import tools.BytesArrayTools; @@ -21,12 +20,12 @@ public class HashRequest extends Payload { private HashAlgorithm[] algoList; private static final int FILENAME_SIZE_POSITION = PAYLOAD_START_POSITION; private static final int FILENAME_POSITION = FILENAME_SIZE_POSITION + 4; - + /** Constructor (typically used by the server) with a filename parameter. * @param filename name of the file to download. Must not be empty. * @param algoList List of hash algorithms used * @throws InternalError - * + * */ public HashRequest(String filename, HashAlgorithm[] algoList) throws InternalError { super(RequestResponseCode.HASH_REQUEST); diff --git a/src/protocolP2P/HashResponse.java b/src/protocolP2P/HashResponse.java index 2ff0440..743b13e 100644 --- a/src/protocolP2P/HashResponse.java +++ b/src/protocolP2P/HashResponse.java @@ -2,11 +2,10 @@ package protocolP2P; import protocolP2P.Payload; import java.util.HashMap; import java.util.Map; -import java.io.UnsupportedEncodingException; -import exception.TransmissionError; -import exception.SizeError; -import exception.ProtocolError; -import exception.InternalError; +import localException.TransmissionError; +import localException.SizeError; +import localException.ProtocolError; +import localException.InternalError; import tools.BytesArrayTools; @@ -22,12 +21,12 @@ public class HashResponse extends Payload { private Map hashes = new HashMap<>(); private static final int FILENAME_SIZE_POSITION = PAYLOAD_START_POSITION; private static final int FILENAME_POSITION = FILENAME_SIZE_POSITION + 4; - + /** Constructor (typically used by the server) with a filename parameter. * @param filename name of the file to download. Must not be empty. * @param hashes HashMap containing hashes for file. * @throws InternalError - * + * */ public HashResponse(String filename, Map hashes) throws InternalError { super(RequestResponseCode.HASH_RESPONSE); @@ -80,7 +79,7 @@ public class HashResponse extends Payload { } start += hashSize; } while (start < size); - + } /** Returns a byte[] containing Packet with padding. @@ -100,7 +99,7 @@ public class HashResponse extends Payload { size += 4 + s.length; } byte[] packet = new byte[size + 1]; // java initialize all to zero - + // set request/response code packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; // set Payload size @@ -112,14 +111,14 @@ public class HashResponse extends Payload { int bCount = FILENAME_POSITION + filename.length(); for(HashAlgorithm h : hashes.keySet()) { String s = h.getName(); - BytesArrayTools.write(packet, bCount, (int)s.length()); + BytesArrayTools.write(packet, bCount, s.length()); bCount += 4; // write algoname BytesArrayTools.write(packet, s, bCount); bCount += s.length(); // write hash size byte[] hashb = hashes.get(HashAlgorithm.fromName(s)); - BytesArrayTools.write(packet, bCount, (int)hashb.length); + BytesArrayTools.write(packet, bCount, hashb.length); bCount += 4; if (hashb.length != 0) { // write hash diff --git a/src/protocolP2P/LoadRequest.java b/src/protocolP2P/LoadRequest.java index 4017cb0..86f0b75 100644 --- a/src/protocolP2P/LoadRequest.java +++ b/src/protocolP2P/LoadRequest.java @@ -1,12 +1,11 @@ package protocolP2P; import protocolP2P.Payload; import protocolP2P.RequestResponseCode; -import exception.TransmissionError; -import exception.ProtocolError; -import exception.InternalError; -import exception.SizeError; +import localException.TransmissionError; +import localException.ProtocolError; +import localException.InternalError; +import localException.SizeError; import tools.BytesArrayTools; -import java.io.UnsupportedEncodingException; /** Representation of payload for load request. * @author Louis Royer @@ -61,7 +60,7 @@ public class LoadRequest extends Payload { /* Read maxSizePartialContent */ maxSizePartialContent = BytesArrayTools.readLong(packet, MAX_SIZE_PARTIAL_CONTENT_POSITION); - + /* Read filename */ int size = BytesArrayTools.readInt(packet, FILENAME_SIZE_POSITION); filename = BytesArrayTools.readString(packet, FILENAME_POSITION, size); @@ -83,11 +82,11 @@ public class LoadRequest extends Payload { // set Payload size setPayloadSize(size - OFFSET_POSITION, packet); // Write offset - BytesArrayTools.write(packet, OFFSET_POSITION, (long)offset); + BytesArrayTools.write(packet, OFFSET_POSITION, offset); // Write maxSizePartialContent - BytesArrayTools.write(packet, MAX_SIZE_PARTIAL_CONTENT_POSITION, (long)maxSizePartialContent); + BytesArrayTools.write(packet, MAX_SIZE_PARTIAL_CONTENT_POSITION, maxSizePartialContent); // Write filenameSize - BytesArrayTools.write(packet, FILENAME_SIZE_POSITION, (int)filenameSize); + BytesArrayTools.write(packet, FILENAME_SIZE_POSITION, filenameSize); // Write filename BytesArrayTools.write(packet, filename, FILENAME_POSITION); return packet; diff --git a/src/protocolP2P/Payload.java b/src/protocolP2P/Payload.java index cbe9767..990df6b 100644 --- a/src/protocolP2P/Payload.java +++ b/src/protocolP2P/Payload.java @@ -5,10 +5,14 @@ import protocolP2P.FileList; import protocolP2P.LoadRequest; import protocolP2P.HashRequest; import protocolP2P.HashResponse; -import exception.ProtocolError; -import exception.InternalError; -import exception.TransmissionError; -import exception.SizeError; +import protocolP2P.DiscoverRequest; +import protocolP2P.DiscoverResponse; +import protocolP2P.Register; +import protocolP2P.Unregister; +import localException.ProtocolError; +import localException.InternalError; +import localException.TransmissionError; +import localException.SizeError; import tools.BytesArrayTools; /** Representation of payload. If payload has a size, use subclasses instead. * @author Louis Royer @@ -27,11 +31,15 @@ public class Payload { */ public Payload(RequestResponseCode requestResponseCode) throws InternalError { /* asserts to help debugging */ - assert requestResponseCode != RequestResponseCode.LIST_RESPONSE || (this instanceof FileList) : "LIST_RESPONSE must use FilePart class"; - assert requestResponseCode != RequestResponseCode.LOAD_RESPONSE || (this instanceof FilePart) : "LOAD_RESPONSE must use FileList class"; - assert requestResponseCode != RequestResponseCode.LOAD_REQUEST || (this instanceof LoadRequest) : "LOAD_REQUEST must use LoadRequest class"; - assert requestResponseCode != RequestResponseCode.HASH_REQUEST || (this instanceof HashRequest) : "HASH_REQUEST must use HashRequest class"; - assert requestResponseCode != RequestResponseCode.HASH_RESPONSE || (this instanceof HashResponse) : "HASH_RESPONSE must use HashResponse class"; + assert requestResponseCode != RequestResponseCode.LIST_RESPONSE || (this instanceof FileList) : "LIST_RESPONSE must use FilePart class"; + assert requestResponseCode != RequestResponseCode.LOAD_RESPONSE || (this instanceof FilePart) : "LOAD_RESPONSE must use FileList class"; + assert requestResponseCode != RequestResponseCode.LOAD_REQUEST || (this instanceof LoadRequest) : "LOAD_REQUEST must use LoadRequest class"; + assert requestResponseCode != RequestResponseCode.HASH_REQUEST || (this instanceof HashRequest) : "HASH_REQUEST must use HashRequest class"; + assert requestResponseCode != RequestResponseCode.HASH_RESPONSE || (this instanceof HashResponse) : "HASH_RESPONSE must use HashResponse class"; + assert requestResponseCode != RequestResponseCode.DISCOVER_REQUEST || (this instanceof DiscoverRequest) : "DISCOVER_REQUEST must use DiscoverRequest class"; + 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"; this.requestResponseCode = requestResponseCode; checkRequestResponseCode(); // this can throw InternalError } @@ -46,29 +54,42 @@ public class Payload { */ protected Payload(byte[] packet) throws SizeError, ProtocolError, InternalError, TransmissionError { /* asserts to help debugging */ - assert getPayloadSize(packet) + 8 <= packet.length : "Payload is truncated"; - if (packet.length < getPayloadSize(packet) + 8) { + assert getPayloadSize(packet) + PAYLOAD_START_POSITION <= packet.length : "Payload is truncated"; + if (packet.length < getPayloadSize(packet) + PAYLOAD_START_POSITION) { throw new TransmissionError(); } - assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LIST_RESPONSE || (this instanceof FileList) : "LIST_RESPONSE must use FilePart class"; - assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LOAD_RESPONSE || (this instanceof FilePart) : "LOAD_RESPONSE must use FileList class"; - assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LOAD_REQUEST || (this instanceof LoadRequest) : "LOAD_REQUEST must use LoadRequest class"; + assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LIST_RESPONSE || (this instanceof FileList) : "LIST_RESPONSE must use FilePart class"; + assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LOAD_RESPONSE || (this instanceof FilePart) : "LOAD_RESPONSE must use FileList class"; + assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LOAD_REQUEST || (this instanceof LoadRequest) : "LOAD_REQUEST must use LoadRequest class"; + assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.HASH_REQUEST || (this instanceof HashRequest) : "HASH_REQUEST must use HashRequest class"; + assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.HASH_RESPONSE || (this instanceof HashResponse) : "HASH_RESPONSE must use HashResponse class"; + assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.DISCOVER_REQUEST || (this instanceof DiscoverRequest) : "DISCOVER_REQUEST must use DiscoverRequest class"; + 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"; requestResponseCode = RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]); checkRequestResponseCode(); // this can throw InternalError } /** Used to check RRCode used is compatible with this class use, or if a more specific subclass is required. * @throws InternalError - */ + */ private void checkRequestResponseCode() throws InternalError { /* Incorrect use cases (use subclasses instead) */ - if ((requestResponseCode == RequestResponseCode.LIST_RESPONSE && !(this instanceof FileList)) - || (requestResponseCode == RequestResponseCode.LOAD_RESPONSE && !(this instanceof FilePart)) - || (requestResponseCode == RequestResponseCode.LOAD_REQUEST && !(this instanceof LoadRequest))) { + if ((requestResponseCode == RequestResponseCode.LIST_RESPONSE && !(this instanceof FileList)) + || (requestResponseCode == RequestResponseCode.LOAD_RESPONSE && !(this instanceof FilePart)) + || (requestResponseCode == RequestResponseCode.LOAD_REQUEST && !(this instanceof LoadRequest)) + || (requestResponseCode == RequestResponseCode.HASH_REQUEST && !(this instanceof HashRequest)) + || (requestResponseCode == RequestResponseCode.HASH_RESPONSE && !(this instanceof HashResponse)) + || (requestResponseCode == RequestResponseCode.DISCOVER_REQUEST && !(this instanceof DiscoverRequest)) + || (requestResponseCode == RequestResponseCode.DISCOVER_RESPONSE && !(this instanceof DiscoverResponse)) + || (requestResponseCode == RequestResponseCode.REGISTER && !(this instanceof Register)) + || (requestResponseCode == RequestResponseCode.UNREGISTER && !(this instanceof Unregister)) + ) { throw new InternalError(); } } - + /** Returns a byte[] containing Packet with padding. * This Packet is still incomplete and should not be send directly. * ProtocolP2PPacket will use this method to generate the complete Packet. @@ -90,7 +111,7 @@ public class Payload { * @param size integer representing payload size * @param packet Packet to be completed * @throws InternalError - */ + */ protected static void setPayloadSize(int size, byte[] packet) throws InternalError { /* assert to help debugging */ assert size >= 0: "Payload size cannot be negative"; diff --git a/src/protocolP2P/ProtocolP2PPacket.java b/src/protocolP2P/ProtocolP2PPacket.java index d1298d5..db9c4b4 100644 --- a/src/protocolP2P/ProtocolP2PPacket.java +++ b/src/protocolP2P/ProtocolP2PPacket.java @@ -1,18 +1,19 @@ package protocolP2P; -import exception.InternalError; -import exception.ProtocolError; -import exception.SizeError; -import exception.TransmissionError; -import exception.VersionError; -import exception.SocketClosed; +import localException.InternalError; +import localException.ProtocolError; +import localException.SizeError; +import localException.TransmissionError; +import localException.VersionError; +import localException.SocketClosed; import remoteException.EmptyDirectory; import remoteException.InternalRemoteError; import remoteException.NotFound; import remoteException.ProtocolRemoteError; import remoteException.VersionRemoteError; import remoteException.EmptyFile; -import java.net.InetAddress; +import remoteException.NotATracker; import java.io.IOException; +import tools.HostItem; /** Representation of packet. * @author Louis Royer @@ -20,7 +21,7 @@ import java.io.IOException; * @author JS Auge * @version 1.0 */ -public abstract class ProtocolP2PPacket { +public abstract class ProtocolP2PPacket < T extends Payload>{ private final static byte PROTOCOL_VERSION = 0x12; protected final static int VERSION_POSITION = 0; protected byte version; @@ -29,9 +30,9 @@ public abstract class ProtocolP2PPacket { /** Constructor with payload parameter (typically used when sending Packet). * @param payload the payload associated with the Packet to send */ - public ProtocolP2PPacket(Payload payload) { + public ProtocolP2PPacket (T payload) { version = PROTOCOL_VERSION; - this.payload = payload; + this.payload = (Payload)payload; } /** Send a request @@ -48,11 +49,18 @@ public abstract class ProtocolP2PPacket { * @throws IOException * @throws SocketClosed */ - public abstract void sendResponse(ProtocolP2PPacket response) throws InternalError, IOException, SocketClosed; + public abstract > void sendResponse(U response) throws InternalError, IOException, SocketClosed; + + /** Get hostItem of the sender + * @return hostItem of the sender + * @throws InternalError + */ + public abstract HostItem getHostItem() throws InternalError; /** Receive a response * @throws EmptyFile * @throws NotFound + * @throws NotATracker * @throws EmptyDirectory * @throws InternalRemoteError * @throws VersionRemoteError @@ -65,7 +73,7 @@ public abstract class ProtocolP2PPacket { * @throws IOException * @throws SocketClosed */ - public abstract ProtocolP2PPacket receiveResponse() throws EmptyFile, NotFound, 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; /** Receive a request, subclasses must overwrite this constructor. * @param socket socket used to get the request @@ -88,7 +96,7 @@ public abstract class ProtocolP2PPacket { * @throws SizeError */ protected ProtocolP2PPacket(byte[] packet) throws TransmissionError, ProtocolError, VersionError, InternalError, SizeError {} - + /** Returns Payload associated with the Packet. * @return payload associated with the Packet. */ @@ -101,6 +109,7 @@ public abstract class ProtocolP2PPacket { */ protected void checkProtocolVersion() throws VersionError { if (PROTOCOL_VERSION != version) { + System.err.println("Error: wrong version in packet:" + version); throw new VersionError(); } } diff --git a/src/protocolP2P/ProtocolP2PPacketTCP.java b/src/protocolP2P/ProtocolP2PPacketTCP.java index 40280d3..708ee38 100644 --- a/src/protocolP2P/ProtocolP2PPacketTCP.java +++ b/src/protocolP2P/ProtocolP2PPacketTCP.java @@ -1,24 +1,23 @@ package protocolP2P; -import exception.InternalError; -import exception.ProtocolError; -import exception.SizeError; -import exception.TransmissionError; -import exception.VersionError; -import exception.SocketClosed; +import localException.InternalError; +import localException.ProtocolError; +import localException.SizeError; +import localException.TransmissionError; +import localException.VersionError; +import localException.SocketClosed; +import remoteException.NotATracker; import remoteException.EmptyDirectory; import remoteException.InternalRemoteError; import remoteException.NotFound; import remoteException.ProtocolRemoteError; import remoteException.VersionRemoteError; import remoteException.EmptyFile; -import tools.BytesArrayTools; +import tools.HostItem; import protocolP2P.Payload; import protocolP2P.RequestResponseCode; import protocolP2P.LoadRequest; import protocolP2P.FileList; import protocolP2P.FilePart; -import java.util.ArrayList; -import java.lang.Byte; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; @@ -29,15 +28,15 @@ import java.net.Socket; * @author JS Auge * @version 1.0 */ -public class ProtocolP2PPacketTCP extends ProtocolP2PPacket { - +public class ProtocolP2PPacketTCP < T extends Payload > extends ProtocolP2PPacket < T > { + private Socket responseSocket; // socket used to recept request and send response private Socket requestSocket; // socket used to send request and to reception response - + /** Constructor with payload parameter (typically used when sending packet). * @param payload the payload associated with the packet to send */ - public ProtocolP2PPacketTCP(Payload payload) { + public ProtocolP2PPacketTCP(T payload) { super(payload); } @@ -67,9 +66,9 @@ public class ProtocolP2PPacketTCP extends ProtocolP2PPacket { socket.close(); } catch (IOException e2) { System.err.println("Cannot close socket"); - } finally { throw new SocketClosed(); } + } } @@ -109,14 +108,16 @@ public class ProtocolP2PPacketTCP extends ProtocolP2PPacket { Socket ss = (Socket)socket; byte[] packet = new byte[1024]; try { - System.err.println("Reading " + ss.getInputStream().read(packet) + " bytes"); + if (-1 == ss.getInputStream().read(packet)) { + throw new IOException(); + } } catch (IOException e) { // Error: cannot read request, closing socket try { ss.close(); + throw new SocketClosed(); } catch (IOException e2) { System.err.println("Cannot close socket"); - } finally { throw new SocketClosed(); } } @@ -138,25 +139,27 @@ public class ProtocolP2PPacketTCP extends ProtocolP2PPacket { case LOAD_RESPONSE: case LIST_RESPONSE: case HASH_RESPONSE: + case DISCOVER_RESPONSE: + case NOT_A_TRACKER: // we were expecting a request, but we are receiving a response throw new ProtocolError(); default : break; } } catch (TransmissionError e) { - (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss); + (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss); throw e; } catch (ProtocolError e) { - (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.PROTOCOL_ERROR))).send(ss); + (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.PROTOCOL_ERROR))).send(ss); throw e; } catch (VersionError e) { - (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.VERSION_ERROR))).send(ss); + (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.VERSION_ERROR))).send(ss); throw e; } catch (InternalError e) { - (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss); + (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss); throw e; } catch (SizeError e) { - (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss); + (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss); throw e; } if (protocolError) { @@ -170,10 +173,10 @@ public class ProtocolP2PPacketTCP extends ProtocolP2PPacket { * @throws IOException * @throws SocketClosed */ - public void sendResponse(ProtocolP2PPacket response) throws InternalError, IOException, SocketClosed { + public > void sendResponse(U response) throws InternalError, IOException, SocketClosed { assert response instanceof ProtocolP2PPacketTCP: "Wrong Packet type"; if (response instanceof ProtocolP2PPacketTCP) { - ProtocolP2PPacketTCP r = (ProtocolP2PPacketTCP) response; + ProtocolP2PPacketTCP r = (ProtocolP2PPacketTCP) response; assert responseSocket != null : "Cannot send response to a packet not received"; if (responseSocket == null) { throw new InternalError(); @@ -188,6 +191,7 @@ public class ProtocolP2PPacketTCP extends ProtocolP2PPacket { * @return ProtocolP2PPacket received * @throws EmptyFile * @throws NotFound + * @throws NotATracker * @throws EmptyDirectory * @throws InternalRemoteError * @throws VersionRemoteError @@ -200,17 +204,30 @@ public class ProtocolP2PPacketTCP extends ProtocolP2PPacket { * @throws IOException * @throws SocketClosed */ - public ProtocolP2PPacket receiveResponse() throws EmptyFile, NotFound, 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 { assert requestSocket != null : "Cannot receive response because request packet not sent."; if (requestSocket == null) { throw new InternalError(); } // reception byte[] packet = new byte[8192]; - requestSocket.getInputStream().read(packet); + try { + if (-1== requestSocket.getInputStream().read(packet)) { + throw new IOException(); + } + } catch (IOException e) { + // Error: cannot read request, closing socket + try { + requestSocket.close(); + throw new SocketClosed(); + } catch (IOException e2) { + System.err.println("Cannot close socket"); + throw new SocketClosed(); + } + } // contruction try { - ProtocolP2PPacketTCP p = new ProtocolP2PPacketTCP(packet); + ProtocolP2PPacketTCP p = new ProtocolP2PPacketTCP<>(packet); Payload payload = p.getPayload(); switch (payload.getRequestResponseCode()) { case PROTOCOL_ERROR : @@ -225,23 +242,25 @@ public class ProtocolP2PPacketTCP extends ProtocolP2PPacket { throw new NotFound(); case EMPTY_FILE: throw new EmptyFile(); + case NOT_A_TRACKER: + throw new NotATracker(); default : return (ProtocolP2PPacket)p; } } catch (TransmissionError e) { - (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); + (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); throw e; } catch (ProtocolError e) { - (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.PROTOCOL_ERROR))).send(requestSocket); + (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.PROTOCOL_ERROR))).send(requestSocket); throw e; } catch (VersionError e) { - (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.VERSION_ERROR))).send(requestSocket); + (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.VERSION_ERROR))).send(requestSocket); throw e; } catch (InternalError e) { - (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); + (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); throw e; } catch (SizeError e) { - (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); + (new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); throw e; } } @@ -288,6 +307,18 @@ public class ProtocolP2PPacketTCP extends ProtocolP2PPacket { case HASH_RESPONSE: payload = (Payload) new HashResponse(packet); break; + case REGISTER: + payload = (Payload) new Register(packet); + break; + case UNREGISTER: + payload = (Payload) new Unregister(packet); + break; + case DISCOVER_REQUEST: + payload = (Payload) new DiscoverRequest(packet); + break; + case DISCOVER_RESPONSE: + payload = (Payload) new DiscoverResponse(packet); + break; default: payload = new Payload(packet); // this can throw TransmissionError break; @@ -319,4 +350,15 @@ public class ProtocolP2PPacketTCP extends ProtocolP2PPacket { return packet; } + /** Get hostItem of the sender + * @return hostItem of the sender + * @throws InternalError + */ + public HostItem getHostItem() throws InternalError { + if (responseSocket == null) { + throw new InternalError(); + } + return new HostItem(responseSocket.getInetAddress().getHostName(), responseSocket.getPort()); + } + } diff --git a/src/protocolP2P/ProtocolP2PPacketUDP.java b/src/protocolP2P/ProtocolP2PPacketUDP.java index a997754..a54733b 100644 --- a/src/protocolP2P/ProtocolP2PPacketUDP.java +++ b/src/protocolP2P/ProtocolP2PPacketUDP.java @@ -1,10 +1,11 @@ package protocolP2P; -import exception.InternalError; -import exception.ProtocolError; -import exception.SizeError; -import exception.TransmissionError; -import exception.VersionError; -import exception.SocketClosed; +import localException.InternalError; +import localException.ProtocolError; +import localException.SizeError; +import localException.TransmissionError; +import localException.VersionError; +import localException.SocketClosed; +import remoteException.NotATracker; import remoteException.EmptyDirectory; import remoteException.InternalRemoteError; import remoteException.NotFound; @@ -12,13 +13,12 @@ import remoteException.ProtocolRemoteError; import remoteException.VersionRemoteError; import remoteException.EmptyFile; import tools.BytesArrayTools; +import tools.HostItem; import protocolP2P.Payload; import protocolP2P.RequestResponseCode; import protocolP2P.LoadRequest; import protocolP2P.FileList; import protocolP2P.FilePart; -import java.util.ArrayList; -import java.lang.Byte; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketAddress; @@ -30,17 +30,18 @@ import java.io.IOException; * @author JS Auge * @version 1.0 */ -public class ProtocolP2PPacketUDP extends ProtocolP2PPacket { - +public class ProtocolP2PPacketUDP < T extends Payload> extends ProtocolP2PPacket < T > { + private final static int CHECKSUM_POSITION = 2; + private HostItem remoteHost; private SocketAddress responseSocketAddress; // socket address used when receptionning request and to sending response private DatagramSocket responseSocket; // socket used to recept request and send response private DatagramSocket requestSocket; // socket used to send request and to reception response - + /** Constructor with payload parameter (typically used when sending packet). * @param payload the payload associated with the packet to send */ - public ProtocolP2PPacketUDP(Payload payload) { + public ProtocolP2PPacketUDP(T payload) { super(payload); } @@ -112,6 +113,7 @@ public class ProtocolP2PPacketUDP extends ProtocolP2PPacket { DatagramPacket reception = new DatagramPacket(packet, packet.length); ss.receive(reception); responseSocketAddress = reception.getSocketAddress(); + remoteHost = new HostItem(reception.getAddress().getHostName(), reception.getPort()); // contruction boolean protocolError = false; try { @@ -130,25 +132,27 @@ public class ProtocolP2PPacketUDP extends ProtocolP2PPacket { case LOAD_RESPONSE: case LIST_RESPONSE: case HASH_RESPONSE: + case DISCOVER_RESPONSE: + case NOT_A_TRACKER: // we were expecting a request, but we are receiving a response throw new ProtocolError(); default : break; } } catch (TransmissionError e) { - (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss, responseSocketAddress); + (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss, responseSocketAddress); throw e; } catch (ProtocolError e) { - (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.PROTOCOL_ERROR))).send(ss, responseSocketAddress); + (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.PROTOCOL_ERROR))).send(ss, responseSocketAddress); throw e; } catch (VersionError e) { - (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.VERSION_ERROR))).send(ss, responseSocketAddress); + (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.VERSION_ERROR))).send(ss, responseSocketAddress); throw e; } catch (InternalError e) { - (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss, responseSocketAddress); + (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss, responseSocketAddress); throw e; } catch (SizeError e) { - (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss, responseSocketAddress); + (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss, responseSocketAddress); throw e; } if (protocolError) { @@ -161,10 +165,10 @@ public class ProtocolP2PPacketUDP extends ProtocolP2PPacket { * @throws InternalError * @throws IOException */ - public void sendResponse(ProtocolP2PPacket response) throws InternalError, IOException { + public >void sendResponse(U response) throws InternalError, IOException { assert response instanceof ProtocolP2PPacketUDP: "Wrong Packet type"; if (response instanceof ProtocolP2PPacketUDP) { - ProtocolP2PPacketUDP r = (ProtocolP2PPacketUDP) response; + ProtocolP2PPacketUDP r = (ProtocolP2PPacketUDP) response; assert responseSocket != null : "Cannot send response to a packet not received"; if (responseSocket == null) { throw new InternalError(); @@ -183,6 +187,7 @@ public class ProtocolP2PPacketUDP extends ProtocolP2PPacket { * @return ProtocolP2PPacket received * @throws EmptyFile * @throws NotFound + * @throws NotATracker * @throws EmptyDirectory * @throws InternalRemoteError * @throws VersionRemoteError @@ -194,7 +199,7 @@ public class ProtocolP2PPacketUDP extends ProtocolP2PPacket { * @throws SizeError * @throws IOException */ - public ProtocolP2PPacket receiveResponse() throws EmptyFile, NotFound, 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 { assert requestSocket != null : "Cannot receive response because request packet not sent."; if (requestSocket == null) { throw new InternalError(); @@ -203,9 +208,10 @@ public class ProtocolP2PPacketUDP extends ProtocolP2PPacket { byte[] packet = new byte[8192]; DatagramPacket reception = new DatagramPacket(packet, packet.length); requestSocket.receive(reception); + // contruction try { - ProtocolP2PPacketUDP p = new ProtocolP2PPacketUDP(packet); + ProtocolP2PPacketUDP p = new ProtocolP2PPacketUDP<>(packet); Payload payload = p.getPayload(); switch (payload.getRequestResponseCode()) { case PROTOCOL_ERROR : @@ -220,23 +226,25 @@ public class ProtocolP2PPacketUDP extends ProtocolP2PPacket { throw new NotFound(); case EMPTY_FILE: throw new EmptyFile(); + case NOT_A_TRACKER: + throw new NotATracker(); default : return (ProtocolP2PPacket)p; } } catch (TransmissionError e) { - (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); + (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); throw e; } catch (ProtocolError e) { - (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.PROTOCOL_ERROR))).send(requestSocket); + (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.PROTOCOL_ERROR))).send(requestSocket); throw e; } catch (VersionError e) { - (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.VERSION_ERROR))).send(requestSocket); + (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.VERSION_ERROR))).send(requestSocket); throw e; } catch (InternalError e) { - (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); + (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); throw e; } catch (SizeError e) { - (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); + (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); throw e; } } @@ -284,6 +292,18 @@ public class ProtocolP2PPacketUDP extends ProtocolP2PPacket { case HASH_RESPONSE: payload = (Payload) new HashResponse(packet); break; + case REGISTER: + payload = (Payload) new Register(packet); + break; + case UNREGISTER: + payload = (Payload) new Unregister(packet); + break; + case DISCOVER_REQUEST: + payload = (Payload) new DiscoverRequest(packet); + break; + case DISCOVER_RESPONSE: + payload = (Payload) new DiscoverResponse(packet); + break; default: payload = new Payload(packet); // this can throw TransmissionError break; @@ -353,7 +373,6 @@ public class ProtocolP2PPacketUDP extends ProtocolP2PPacket { * @param packet full packet * @throws TransmissionError */ - private void checkCheckSum(byte [] packet) throws TransmissionError { try { int checksum = BytesArrayTools.readInt16Bits(packet, CHECKSUM_POSITION); @@ -365,4 +384,15 @@ public class ProtocolP2PPacketUDP extends ProtocolP2PPacket { throw new TransmissionError(); } } + + /** Get hostItem of the sender + * @return hostItem of the sender + * @throws InternalError + */ + public HostItem getHostItem() throws InternalError { + if (remoteHost == null) { + throw new InternalError(); + } + return remoteHost; + } } diff --git a/src/protocolP2P/Register.java b/src/protocolP2P/Register.java new file mode 100644 index 0000000..790ac2b --- /dev/null +++ b/src/protocolP2P/Register.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 unregister. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class Register extends Payload { + private HostItem hostItem; + private static final int HOSTNAME_START_POSITION = PAYLOAD_START_POSITION + 2; + + /** Constructor with hostItem (typically used by client) + * @param hostItem Host you want to register. + * @throws InternalError + */ + public Register(HostItem hostItem) throws InternalError { + super(RequestResponseCode.REGISTER); + this.hostItem = hostItem; + } + + /** 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 Register(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 + 1]; // 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/RequestResponseCode.java b/src/protocolP2P/RequestResponseCode.java index b69ebb3..f216d8c 100644 --- a/src/protocolP2P/RequestResponseCode.java +++ b/src/protocolP2P/RequestResponseCode.java @@ -1,6 +1,6 @@ package protocolP2P; import protocolP2P.CodeType; -import exception.ProtocolError; +import localException.ProtocolError; import java.util.HashMap; import java.util.Map; import java.lang.Byte; @@ -15,16 +15,21 @@ public enum RequestResponseCode { LIST_REQUEST(CodeType.REQUEST, (byte)0x00), LOAD_REQUEST(CodeType.REQUEST, (byte)0x01), HASH_REQUEST(CodeType.REQUEST, (byte)0x02), + DISCOVER_REQUEST(CodeType.REQUEST_TRACKER, (byte)0x03), + REGISTER(CodeType.REQUEST_TRACKER, (byte)0x04), + UNREGISTER(CodeType.REQUEST_TRACKER, (byte)0x05), LIST_RESPONSE(CodeType.RESPONSE, (byte)0x80), LOAD_RESPONSE(CodeType.RESPONSE, (byte)0x81), HASH_RESPONSE(CodeType.RESPONSE, (byte)0x82), + DISCOVER_RESPONSE(CodeType.RESPONSE, (byte)0x83), 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); - + EMPTY_FILE(CodeType.ERROR, (byte)0xC5), + NOT_A_TRACKER(CodeType.ERROR, (byte)0xC6); + public final CodeType codeType; public final byte codeValue; protected final static int RRCODE_POSITION = 1; @@ -62,7 +67,5 @@ public enum RequestResponseCode { return r; } - -} - +} diff --git a/src/protocolP2P/Unregister.java b/src/protocolP2P/Unregister.java new file mode 100644 index 0000000..8307a5b --- /dev/null +++ b/src/protocolP2P/Unregister.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 unregister. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class Unregister extends Payload { + private HostItem hostItem; + private static final int HOSTNAME_START_POSITION = PAYLOAD_START_POSITION + 2; + + /** Constructor with hostItem (typically used by client) + * @param hostItem Host you want to register. + * @throws InternalError + */ + public Unregister(HostItem hostItem) throws InternalError { + super(RequestResponseCode.UNREGISTER); + this.hostItem = hostItem; + } + + /** 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 Unregister(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 + 1]; // 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/remoteException/EmptyDirectory.java b/src/remoteException/EmptyDirectory.java index 5d4f765..45e477a 100644 --- a/src/remoteException/EmptyDirectory.java +++ b/src/remoteException/EmptyDirectory.java @@ -1,4 +1,7 @@ package remoteException; -public class EmptyDirectory extends Exception { + +import exception.RemoteException; + +public class EmptyDirectory extends RemoteException { private static final long serialVersionUID = 12L; } diff --git a/src/remoteException/EmptyFile.java b/src/remoteException/EmptyFile.java index deba543..e5911d9 100644 --- a/src/remoteException/EmptyFile.java +++ b/src/remoteException/EmptyFile.java @@ -1,4 +1,7 @@ package remoteException; -public class EmptyFile extends Exception { + +import exception.RemoteException; + +public class EmptyFile extends RemoteException { private static final long serialVersionUID = 12L; } diff --git a/src/remoteException/InternalRemoteError.java b/src/remoteException/InternalRemoteError.java index c3d5c57..1d2e92f 100644 --- a/src/remoteException/InternalRemoteError.java +++ b/src/remoteException/InternalRemoteError.java @@ -1,4 +1,7 @@ package remoteException; -public class InternalRemoteError extends Exception { + +import exception.RemoteException; + +public class InternalRemoteError extends RemoteException { private static final long serialVersionUID = 12L; } diff --git a/src/remoteException/NotATracker.java b/src/remoteException/NotATracker.java new file mode 100644 index 0000000..1f22ce0 --- /dev/null +++ b/src/remoteException/NotATracker.java @@ -0,0 +1,7 @@ +package remoteException; + +import exception.RemoteException; + +public class NotATracker extends RemoteException { + private static final long serialVersionUID = 12L; +} diff --git a/src/remoteException/NotFound.java b/src/remoteException/NotFound.java index 8f9d27e..c0e61d9 100644 --- a/src/remoteException/NotFound.java +++ b/src/remoteException/NotFound.java @@ -1,4 +1,7 @@ package remoteException; -public class NotFound extends Exception { + +import exception.RemoteException; + +public class NotFound extends RemoteException { private static final long serialVersionUID = 12L; } diff --git a/src/remoteException/ProtocolRemoteError.java b/src/remoteException/ProtocolRemoteError.java index fbc857f..1560083 100644 --- a/src/remoteException/ProtocolRemoteError.java +++ b/src/remoteException/ProtocolRemoteError.java @@ -1,4 +1,7 @@ package remoteException; -public class ProtocolRemoteError extends Exception { + +import exception.RemoteException; + +public class ProtocolRemoteError extends RemoteException { private static final long serialVersionUID = 12L; } diff --git a/src/remoteException/VersionRemoteError.java b/src/remoteException/VersionRemoteError.java index 8da9c1a..4e7a1d5 100644 --- a/src/remoteException/VersionRemoteError.java +++ b/src/remoteException/VersionRemoteError.java @@ -1,4 +1,7 @@ package remoteException; -public class VersionRemoteError extends Exception { + +import exception.RemoteException; + +public class VersionRemoteError extends RemoteException { private static final long serialVersionUID = 12L; } diff --git a/src/serverP2P/FileWatcher.java b/src/serverP2P/FileWatcher.java new file mode 100644 index 0000000..d6bf632 --- /dev/null +++ b/src/serverP2P/FileWatcher.java @@ -0,0 +1,156 @@ +package serverP2P; +import tools.Logger; +import tools.LogLevel; +import tools.HostItem; +import java.io.File; +import java.util.Vector; +import java.util.Arrays; +import java.util.Map; +import java.util.HashMap; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import protocolP2P.HashAlgorithm; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.io.IOException; + +/** Class allowing to keep the tracker informed about file list + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public abstract class FileWatcher implements Runnable { + protected String[] fileList = new String[0]; + protected Logger logger; + protected volatile boolean stop; + protected long time; + protected boolean force; + protected HostItem server; + protected HostItem tracker; + protected String baseDirectory; + protected Map sha512 = new HashMap<>(); + + + /** Constructor + * @param logger Logger + * @param millis Time interval before recheck + * @param server HostItem for the server + * @param tracker HostItem for the tracker + * @param baseDirectory Directory to search files + */ + public FileWatcher(Logger logger, long millis, HostItem server, HostItem tracker, String baseDirectory) { + assert logger != null : "Logger is null"; + assert server != null : "Server is null"; + assert tracker != null : "Tracker is null"; + assert baseDirectory != null : "baseDirectory is null"; + this.logger = logger; + time = millis; + this.server = server; + this.tracker = tracker; + this.baseDirectory = baseDirectory; + } + + /** FileList getter + * @return fileList + */ + public String[] getFileList() { + return fileList; + } + + /** Sha512 map getter + * @return sha512 hashmap + */ + public Map getSha512Map() { + return sha512; + } + + /** Allow a manual check + */ + public void trigger() { + if (updateFileList() || force) { + force = false; + writeLog("File list watcher detected changes. Informing tracker.", LogLevel.Info); + registerTracker(); + } + } + + /** Runnable implementation */ + public void run() { + writeLog("File list watcher started : delay " + time + " milliseconds.", LogLevel.Info); + while(!stop) { + trigger(); + try { + Thread.sleep(time); + } catch(InterruptedException e) { + writeLog("File list watcher interrupted", LogLevel.Error); + setStop(); + } + } + } + + /** Register server on tracker + */ + protected abstract void registerTracker(); + + /** Update fileList and returns true if different than old list. + * @return true if changed + */ + protected boolean updateFileList() { + File folder = new File(baseDirectory); + Vector v = new Vector(); + File[] files = folder.listFiles(); + /* Add non-recursively files's names to fileList */ + for (File f : files) { + if (f.isFile()) { + v.add(f.getName()); + } + } + String[] newFileList = new String[v.size()]; + v.toArray(newFileList); + Arrays.sort(newFileList); + if (!Arrays.equals(newFileList, fileList)) { + fileList = newFileList; + initSha512(); + return true; + } else { + return false; + } + } + + + /** Ask the thread to stop + */ + public void setStop() { + stop = true; + } + + /** Init sha512 map. + */ + protected void initSha512() { + for(String f: fileList) { + try { + MessageDigest md = MessageDigest.getInstance(HashAlgorithm.SHA512.getName()); + sha512.put(f, md.digest(Files.readAllBytes(Paths.get(baseDirectory + f)))); + md.reset(); + } catch (NoSuchAlgorithmException e) { + writeLog("sha512 not supported", LogLevel.Error); + } catch (IOException e) { + writeLog("cannot read " + f, LogLevel.Warning); + } + } + } + + /** 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); + +} diff --git a/src/serverP2P/FileWatcherTCP.java b/src/serverP2P/FileWatcherTCP.java new file mode 100644 index 0000000..7aa9df7 --- /dev/null +++ b/src/serverP2P/FileWatcherTCP.java @@ -0,0 +1,65 @@ +package serverP2P; +import tools.Logger; +import tools.LogLevel; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.ProtocolP2PPacketTCP; +import protocolP2P.Register; +import protocolP2P.Payload; +import tools.HostItem; + +/** 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 FileWatcherTCP extends FileWatcher { + + /** Constructor + * @param logger Logger + * @param millis Time interval before recheck + * @param server HostItem for the server + * @param tracker HostItem for the tracker + * @param baseDirectory Directory to search files + */ + public FileWatcherTCP(Logger logger, long millis, HostItem server, HostItem tracker, String baseDirectory) { + super(logger, millis, server, tracker, baseDirectory); + assert logger != null : "Logger is null"; + assert server != null : "Server is null"; + assert tracker != null : "Tracker is null"; + assert baseDirectory != null : "baseDirectory 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); + } + + /** Register server on tracker + */ + protected void registerTracker() { + try { + writeLog("Trying to into tracker", LogLevel.Info); + ProtocolP2PPacket p = (ProtocolP2PPacket)new ProtocolP2PPacketTCP(new Register(server)); + p.sendRequest((Object)tracker.tryGetTCPSocket()); + writeLog("Register request sent.", LogLevel.Debug); + tracker.closeTCPSocket(); + } catch (Exception e) { + // error, trying again at next iteration + force = true; + writeLog("Cannot contact tracker, trying again at next iteration (" + time + " milliseconds).", LogLevel.Error); + } + } + +} diff --git a/src/serverP2P/FileWatcherUDP.java b/src/serverP2P/FileWatcherUDP.java new file mode 100644 index 0000000..194039e --- /dev/null +++ b/src/serverP2P/FileWatcherUDP.java @@ -0,0 +1,64 @@ +package serverP2P; +import tools.Logger; +import tools.LogLevel; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.ProtocolP2PPacketUDP; +import protocolP2P.Register; +import protocolP2P.Payload; +import tools.HostItem; + +/** 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 FileWatcherUDP extends FileWatcher { + + /** Constructor + * @param logger Logger + * @param millis Time interval before recheck + * @param server HostItem for the server + * @param tracker HostItem for the tracker + * @param baseDirectory Directory to search files + */ + public FileWatcherUDP(Logger logger, long millis, HostItem server, HostItem tracker, String baseDirectory) { + super(logger, millis, server, tracker, baseDirectory); + assert logger != null : "Logger is null"; + assert server != null : "Server is null"; + assert tracker != null : "Tracker is null"; + assert baseDirectory != null : "baseDirectory 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); + } + + /** Register server on tracker + */ + protected void registerTracker() { + try { + writeLog("Trying to register into tracker", LogLevel.Info); + ProtocolP2PPacket p = (ProtocolP2PPacket)new ProtocolP2PPacketUDP(new Register(server)); + p.sendRequest((Object)tracker.getUDPSocket()); + writeLog("Register request sent (but cannot ensure reception).", LogLevel.Debug); + tracker.closeUDPSocket(); + } catch (Exception e) { + force = true; + writeLog("Cannot contact tracker, trying again at next iteration (" + time + " milliseconds).", LogLevel.Error); + } + } + +} diff --git a/src/serverP2P/ServerManagement.java b/src/serverP2P/ServerManagement.java new file mode 100644 index 0000000..de4fdd2 --- /dev/null +++ b/src/serverP2P/ServerManagement.java @@ -0,0 +1,234 @@ +package serverP2P; +import serverP2P.FileWatcher; +import tools.Logger; +import tools.LogLevel; +import tools.HostItem; +import tools.ServeErrors; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.Payload; +import protocolP2P.RequestResponseCode; +import protocolP2P.FileList; +import protocolP2P.FilePart; +import protocolP2P.LoadRequest; +import protocolP2P.HashRequest; +import protocolP2P.HashResponse; +import protocolP2P.HashAlgorithm; +import protocolP2P.Unregister; +import java.nio.file.Paths; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Map; +import java.util.HashMap; +import java.io.IOException; +import exception.LocalException; + +/** Implementation of P2P-JAVA-PROJECT VERSION 1.0 protocol. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public abstract class ServerManagement extends ServeErrors implements Runnable { + + protected volatile boolean stop; + protected FileWatcher fileListWatcher; + protected Logger logger; + protected String baseDirectory; + protected HostItem server; + protected HostItem tracker; + + /** Constructor */ + public ServerManagement(String baseDirectory, HostItem server, HostItem tracker, Logger logger) { + super(); + assert baseDirectory != null : "baseDirectory is null"; + assert server != null : "server is null"; + assert tracker != null : "tracker is null"; + assert logger != null : "logger is null"; + stop = false; + this.baseDirectory = baseDirectory; + this.server = server; + this.tracker = tracker; + this.logger = logger; + } + + /** Stop the thread */ + public void setStop() { + stop = true; + } + + /** Trigger a manual check of the file list + */ + public void updateFileList() { + if (fileListWatcher != null) { + fileListWatcher.trigger(); + } + } + + /** Send response to list request + * @param pd Request received + */ + protected < T extends ProtocolP2PPacket > void sendListResponse(T pd) { + try { + String[] fileList = fileListWatcher.getFileList(); + if (fileList.length == 0) { + writeLog("Sending EMPTY_DIRECTORY to host " + pd.getHostItem(), LogLevel.Action); + sendEmptyDirectory(pd); + } else { + writeLog("Sending LIST_RESPONSE to host " + pd.getHostItem(), LogLevel.Action); + pd.sendResponse(createProtocolP2PPacket((Payload)(new FileList(fileList)))); + } + } catch (Exception e2) { + writeLog(e2, LogLevel.Error); + } + } + + + + /** Send hash response to hash request + * @param pd Request received + */ + protected < T extends ProtocolP2PPacket > void sendHashResponse(T pd) { + Payload p = pd.getPayload(); + assert p instanceof HashRequest : "payload must be an instance of HashRequest"; + if (!(p instanceof HashRequest)) { + sendInternalError(pd); + } else { + String filename = ((HashRequest)p).getFilename(); + if (Arrays.binarySearch(fileListWatcher.getFileList(), filename) >= 0) { + Map hashes = new HashMap<>(); + for (HashAlgorithm h : ((HashRequest)p).getAlgoList()) { + switch (h) { + case SHA512: + hashes.put(h, fileListWatcher.getSha512Map().get(filename)); + break; + case MD5: + default: + hashes.put(h, new byte[0]); + break; + } + } + try { + pd.sendResponse(createProtocolP2PPacket((Payload)(new HashResponse(filename, hashes)))); + } catch (Exception e) { + writeLog(e, LogLevel.Error); + } + } else { + // file not found + try { + sendNotFound(pd); + } catch (Exception e) { + writeLog(e, LogLevel.Error); + } + } + } + } + + /** Send response to load request + * @param pd Request received + */ + protected < T extends ProtocolP2PPacket > void sendLoadResponse(T pd) { + Payload p = pd.getPayload(); + assert p instanceof LoadRequest : "payload must be an instance of LoadRequest"; + if (!(p instanceof LoadRequest)) { + sendInternalError(pd); + } else { + String filename = ((LoadRequest)p).getFilename(); + 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); + } else { + pd.sendResponse(createProtocolP2PPacket((Payload)(new FilePart(filename, fullLoad.length, offset, load)))); + } + } catch (Exception e2) { + writeLog(e2, LogLevel.Error); + } + } else { + writeLog("File requested not found: `" + filename + "` " + Arrays.binarySearch(fileList, filename), LogLevel.Debug); + writeLog("File list:", LogLevel.Debug); + for (String f: fileList) { + writeLog("- " + f, LogLevel.Debug); + } + + 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 + */ + protected abstract Object getTrackerSocket(); + + /** Send unregister request to tracker + */ + protected void sendUnregisterRequest() { + // unregistering from tracker + try { + writeLog("Unregistering from tracker", LogLevel.Info); + createProtocolP2PPacket(new Unregister(server)).sendRequest(getTrackerSocket()); + } catch (Exception e) { + writeLog("Cannot unregister from tracker", LogLevel.Error); + writeLog(e, LogLevel.Error); + } + } + + /** Handle request. + * @throws LocalException + */ + protected < T extends ProtocolP2PPacket > void handleRequest(T pd) throws LocalException { + Payload p = pd.getPayload(); + switch (p.getRequestResponseCode()) { + case LOAD_REQUEST: + writeLog("Received LOAD_REQUEST from host " + pd.getHostItem(), LogLevel.Action); + sendLoadResponse(pd); + break; + case LIST_REQUEST: + writeLog("Received LIST_REQUEST from host " + pd.getHostItem(), LogLevel.Action); + sendListResponse(pd); + break; + case HASH_REQUEST: + writeLog("Received HASH_REQUEST from host " + pd.getHostItem(), LogLevel.Action); + sendHashResponse(pd); + break; + case DISCOVER_REQUEST: + writeLog("Received DISCOVER_REQUEST from host " + pd.getHostItem(), LogLevel.Action); + sendNotATracker(pd); + break; + case UNREGISTER: + writeLog("Received UNREGISTER from host " + pd.getHostItem(), LogLevel.Action); + sendNotATracker(pd); + break; + case REGISTER: + writeLog("Received REGISTER from host " + pd.getHostItem(), LogLevel.Action); + sendNotATracker(pd); + break; + default: + writeLog("Received grabbage from host " + pd.getHostItem(), LogLevel.Action); + sendInternalError(pd); + } + } + +} diff --git a/src/serverP2P/ServerManagementTCP.java b/src/serverP2P/ServerManagementTCP.java index e1bc711..fad6ddf 100644 --- a/src/serverP2P/ServerManagementTCP.java +++ b/src/serverP2P/ServerManagementTCP.java @@ -17,26 +17,26 @@ import protocolP2P.Payload; import protocolP2P.LoadRequest; import protocolP2P.FileList; import protocolP2P.FilePart; -import exception.InternalError; -import exception.ProtocolError; -import exception.SizeError; -import exception.TransmissionError; -import exception.VersionError; -import exception.SocketClosed; -import remoteException.EmptyDirectory; -import remoteException.InternalRemoteError; -import remoteException.NotFound; -import remoteException.ProtocolRemoteError; -import remoteException.VersionRemoteError; -import remoteException.EmptyFile; +import localException.InternalError; +import localException.ProtocolError; +import localException.SizeError; +import localException.TransmissionError; +import localException.VersionError; +import localException.SocketClosed; +import exception.LocalException; import java.util.Arrays; import tools.Logger; import tools.LogLevel; +import tools.HostItem; import java.util.HashMap; import java.util.Map; import protocolP2P.HashAlgorithm; import protocolP2P.HashRequest; import protocolP2P.HashResponse; +import protocolP2P.Register; +import protocolP2P.Unregister; +import serverP2P.ServerManagement; +import serverP2P.FileWatcherTCP; /** Implementation of P2P-JAVA-PROJECT VERSION 1.0 protocol for TCP. @@ -45,268 +45,121 @@ import protocolP2P.HashResponse; * @author JS Auge * @version 1.0 */ -public class ServerManagementTCP implements Runnable { +public class ServerManagementTCP extends ServerManagement { - private String[] fileList; - private Map sha512 = new HashMap<>(); - private String baseDirectory; - private int TCPPort; private ServerSocket socket; - private Logger logger; /** Constructor for TCP implementation, with baseDirectory and TCPPort parameters. * @param baseDirectory the root directory where files are stored + * @param hostName the server will bind on this address * @param TCPPort the server will listen on this port + * @param logger Logger item + * @param tracker Tracker */ - public ServerManagementTCP(String baseDirectory, int TCPPort, Logger logger) { - this.logger = logger; - this.baseDirectory = baseDirectory; - this.TCPPort = TCPPort; - initFileList(); - initSha512(); + public ServerManagementTCP(String baseDirectory, HostItem server, HostItem tracker, Logger logger) { + super(baseDirectory, server, tracker, logger); + assert baseDirectory != null : "baseDirectory is null"; + assert server != null : "server is null"; + assert tracker != null : "tracker is null"; + assert logger != null : "logger is null"; try { - socket = new ServerSocket(TCPPort); + socket = new ServerSocket(server.getPort(), 10, server.getInetAddress()); } catch (SocketException e) { - logger.writeTCP("Error: cannot listen on port " + TCPPort, LogLevel.Error); + writeLog("Error: cannot listen on " + server, LogLevel.Error); System.exit(-1); } catch (IOException e) { - logger.writeTCP("Error: cannot openning socket", LogLevel.Error); + writeLog("Error: cannot openning socket", LogLevel.Error); System.exit(-2); } } + /** Implementation of runnable. This methods allows to run the server. */ public void run() { - logger.writeTCP("Server sucessfully started", LogLevel.Info); - do { + writeLog("Server sucessfully started", LogLevel.Info); + fileListWatcher = (FileWatcher)new FileWatcherTCP(logger, 10000, server, tracker, baseDirectory); // checking every 10 seconds + (new Thread(fileListWatcher)).start(); + while(!stop) { try { Socket s = socket.accept(); ClientHandler c = new ClientHandler(s); (new Thread(c)).start(); } catch (IOException e) { - logger.writeTCP("Error while accepting new connection", LogLevel.Warning); + writeLog("Error while accepting new connection", LogLevel.Warning); } - } while(true); + } + fileListWatcher.setStop(); + sendUnregisterRequest(); } /** Private runnable class allowing to serve one client. */ private class ClientHandler implements Runnable { - private Socket s; - private String addr; + private HostItem addr; /** Constructor with a socket. * @param s Socket of this client */ public ClientHandler(Socket s) { - this.s = s; - this.addr = "[" +s.getInetAddress().getHostAddress() + "]:" + s.getPort() + " "; + addr = new HostItem(s); } /** Implementation of runnable. This method allow to serve one client. */ public void run() { - + boolean end = false; - logger.writeTCP(addr + "New connection", LogLevel.Action); + writeLog("[" + addr + "] New connection", LogLevel.Action); do { - end = handleRequest(); + end = handleClientRequest(); } while(!end); - logger.writeTCP(addr + "End of connection", LogLevel.Action); + writeLog("[" + addr + "] End of connection", LogLevel.Action); } /** Respond to next request incomming on socket s. * @param s Socket used to read request and send response * @return true if cannot expect another request (ie, socket is closed) */ - private boolean handleRequest() { + private boolean handleClientRequest() { try { - ProtocolP2PPacketTCP pd = new ProtocolP2PPacketTCP((Object)s); - Payload p = pd.getPayload(); - switch (p.getRequestResponseCode()) { - case LOAD_REQUEST: - logger.writeTCP(addr + "LOAD_REQUEST", LogLevel.Action); - sendLoadResponse(pd); - break; - case LIST_REQUEST: - logger.writeTCP(addr + "LIST_REQUEST", LogLevel.Action); - sendListResponse(pd); - break; - case HASH_REQUEST: - logger.writeTCP(addr + "HASH_REQUEST", LogLevel.Action); - sendHashResponse(pd); - break; - default: - logger.writeTCP(addr + "Received grabbage", LogLevel.Action); - sendInternalError(pd); - } + ProtocolP2PPacketTCP pd = new ProtocolP2PPacketTCP<>((Object)addr.getTCPSocket()); + handleRequest(pd); } catch (IOException e) { return true; } catch (SocketClosed e) { return true; } - catch (TransmissionError e) {} - catch (ProtocolError e) {} - catch (VersionError e) {} - catch (InternalError e) {} - catch (SizeError e) {} + catch (LocalException e) {} return false; } } - /** Initialize local list of all files allowed to be shared. + /** Implementation of writeLog + * @param text Text to log + * @param logLevel level of logging */ - private void initFileList() { - File folder = new File(baseDirectory); - Vector v = new Vector(); - File[] files = folder.listFiles(); - /* Add non-recursively files's names to fileList */ - for (File f : files) { - if (f.isFile()) { - v.add(f.getName()); - } - } - fileList = new String[v.size()]; - v.toArray(fileList); - Arrays.sort(fileList); + protected void writeLog(String text, LogLevel logLevel) { + logger.writeTCP(text, logLevel); } - /** Init sha512 map. + /** Implementation of writeLog + * @param e exception to log + * @param logLevel level of logging */ - private void initSha512() { - for(String f: fileList) { - try { - MessageDigest md = MessageDigest.getInstance(HashAlgorithm.SHA512.getName()); - sha512.put(f, md.digest(Files.readAllBytes(Paths.get(baseDirectory + f)))); - md.reset(); - } catch (NoSuchAlgorithmException e) { - logger.writeTCP("sha512 not supported", LogLevel.Error); - } catch (IOException e) { - logger.writeTCP("cannot read " + f, LogLevel.Warning); - } - } + protected void writeLog(Exception e, LogLevel logLevel) { + logger.writeTCP(e, logLevel); } - /** Send an internal error message. - * @param pd ProtocolP2PPacketTCP to respond + /** Create packets + * @param payload Payload */ - private void sendInternalError(ProtocolP2PPacketTCP pd) { - logger.writeTCP("Internal Error", LogLevel.Warning); - try { - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.INTERNAL_ERROR))); - } catch (Exception e) { - logger.writeTCP(e, LogLevel.Error); - } + protected < T extends Payload > ProtocolP2PPacket createProtocolP2PPacket(T payload) { + return (ProtocolP2PPacket)new ProtocolP2PPacketTCP(payload); } - /** Send response to list request - * @param pd Request received + /** Getter for tracker socket */ - private void sendListResponse(ProtocolP2PPacketTCP pd) { - try { - if (fileList.length == 0) { - logger.writeTCP("Sending EMPTY_DIRECTORY", LogLevel.Action); - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.EMPTY_DIRECTORY))); - } else { - logger.writeTCP("Sending LIST_RESPONSE", LogLevel.Action); - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP((Payload)(new FileList(fileList)))); - } - } catch (Exception e2) { - logger.writeTCP(e2, LogLevel.Error); - } + protected Object getTrackerSocket() { + return (Object)tracker.getTCPSocket(); } - - /** Send hash response to hash request - * @param pd Request received - */ - private void sendHashResponse(ProtocolP2PPacketTCP pd) { - Payload p = pd.getPayload(); - assert p instanceof HashRequest : "payload must be an instance of HashRequest"; - if (!(p instanceof HashRequest)) { - sendInternalError(pd); - } else { - String filename = ((HashRequest)p).getFilename(); - if (Arrays.binarySearch(fileList, filename) >= 0) { - Map hashes = new HashMap<>(); - for (HashAlgorithm h : ((HashRequest)p).getAlgoList()) { - switch (h) { - case SHA512: - hashes.put(h, sha512.get(filename)); - break; - case MD5: - default: - hashes.put(h, new byte[0]); - break; - } - } - try { - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP((Payload)(new HashResponse(filename, hashes)))); - } catch (Exception e) { - logger.writeTCP(e, LogLevel.Error); - } - } else { - // file not found - try { - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.NOT_FOUND))); - } catch (Exception e) { - logger.writeTCP(e, LogLevel.Error); - } - } - } - } - - - /** Send response to load request - * @param pd Request received - */ - private void sendLoadResponse(ProtocolP2PPacketTCP pd) { - Payload p = pd.getPayload(); - assert p instanceof LoadRequest : "payload must be an instance of LoadRequest"; - if (!(p instanceof LoadRequest)) { - sendInternalError(pd); - } else { - String filename = ((LoadRequest)p).getFilename(); - 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) { - logger.writeTCP("Sending last partialContent", LogLevel.Debug); - sizeToSend = fullLoad.length - offset; - } else { - sizeToSend = maxSizePartialContent; - } - logger.writeTCP("maxSizePartialContent: " + maxSizePartialContent, LogLevel.Debug); - logger.writeTCP("Sending " + filename + " from " + offset + " to " + (offset + sizeToSend), LogLevel.Debug); - byte[] load = Arrays.copyOfRange(fullLoad, (int)offset, (int)(offset + sizeToSend)); - if (Arrays.binarySearch(fileList, filename) >= 0) { - try { - if (load.length == 0) { - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.EMPTY_FILE))); - } else { - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP((Payload)(new FilePart(filename, fullLoad.length, offset, load)))); - } - } catch (Exception e2) { - logger.writeTCP(e2, LogLevel.Error); - } - } else { - logger.writeTCP("File requested not found: `" + filename + "` " + Arrays.binarySearch(fileList, filename), LogLevel.Debug); - logger.writeTCP("File list:", LogLevel.Debug); - for (String f: fileList) { - logger.writeTCP("- " + f, LogLevel.Debug); - } - - throw new IOException(); // to send a NOT_FOUND in the catch block - } - } catch (IOException e) { - try { - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketTCP(new Payload(RequestResponseCode.NOT_FOUND))); - } catch (Exception e2) { - logger.writeTCP(e2, LogLevel.Debug); - } - } - } - } - } diff --git a/src/serverP2P/ServerManagementUDP.java b/src/serverP2P/ServerManagementUDP.java index ced8643..7ea6b39 100644 --- a/src/serverP2P/ServerManagementUDP.java +++ b/src/serverP2P/ServerManagementUDP.java @@ -16,18 +16,13 @@ import protocolP2P.Payload; import protocolP2P.LoadRequest; import protocolP2P.FileList; import protocolP2P.FilePart; -import exception.InternalError; -import exception.ProtocolError; -import exception.SizeError; -import exception.TransmissionError; -import exception.VersionError; -import exception.SocketClosed; -import remoteException.EmptyDirectory; -import remoteException.InternalRemoteError; -import remoteException.NotFound; -import remoteException.ProtocolRemoteError; -import remoteException.VersionRemoteError; -import remoteException.EmptyFile; +import localException.InternalError; +import localException.ProtocolError; +import localException.SizeError; +import localException.TransmissionError; +import localException.VersionError; +import localException.SocketClosed; +import exception.LocalException; import java.util.Arrays; import tools.Logger; import tools.LogLevel; @@ -36,6 +31,11 @@ import java.util.Map; import protocolP2P.HashAlgorithm; import protocolP2P.HashRequest; import protocolP2P.HashResponse; +import tools.HostItem; +import protocolP2P.Register; +import protocolP2P.Unregister; +import java.net.UnknownHostException; +import serverP2P.ServerManagement; /** Implementation of P2P-JAVA-PROJECT VERSION 1.0 protocol for UDP. * @author Louis Royer @@ -43,206 +43,77 @@ import protocolP2P.HashResponse; * @author JS Auge * @version 1.0 */ -public class ServerManagementUDP implements Runnable { +public class ServerManagementUDP extends ServerManagement { - private String[] fileList; - private Map sha512 = new HashMap<>(); - private String baseDirectory; - private int UDPPort; private DatagramSocket socket; - private Logger logger; /** Constructor for UDP implementation, with baseDirectory and UDPPort parameters. * @param baseDirectory the root directory where files are stored - * @param UDPPort the server will listen on this port + * @param hostName the server will bind on this address + * @param port the server will listen on this port + * @param logger Logger item + * @param tracker Tracker */ - public ServerManagementUDP(String baseDirectory, int UDPPort, Logger logger) { - this.logger = logger; - this.baseDirectory = baseDirectory; - this.UDPPort = UDPPort; - initFileList(); - initSha512(); + public ServerManagementUDP(String baseDirectory, HostItem server, HostItem tracker, Logger logger) { + super(baseDirectory, server, tracker, logger); + assert baseDirectory != null : "baseDirectory is null"; + assert server != null : "server is null"; + assert tracker != null : "tracker is null"; + assert logger != null : "logger is null"; try { - socket = new DatagramSocket(UDPPort); + socket = new DatagramSocket(server.getPort(), server.getInetAddress()); } catch (SocketException e) { - logger.writeUDP("Error: cannot listen on port " + UDPPort, LogLevel.Error); + logger.writeUDP("Error: cannot listen on " + server, LogLevel.Error); System.exit(-1); } } + /** Implementation of runnable. This methods allows to run the server. */ public void run() { logger.writeUDP("Server sucessfully started", LogLevel.Info); - while(true) { + fileListWatcher = (FileWatcher)new FileWatcherUDP(logger, 10000, server, tracker, baseDirectory); // checking every 10 seconds + (new Thread(fileListWatcher)).start(); + while(!stop) { try { - ProtocolP2PPacketUDP pd = new ProtocolP2PPacketUDP((Object)socket); - Payload p = pd.getPayload(); - switch (p.getRequestResponseCode()) { - case LOAD_REQUEST: - logger.writeUDP("Received LOAD_REQUEST", LogLevel.Action); - assert p instanceof LoadRequest : "payload must be an instance of LoadRequest"; - if (!(p instanceof LoadRequest)) { - sendInternalError(pd); - } else { - String filename = ((LoadRequest)p).getFilename(); - 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) { - logger.writeUDP("Sending last partialContent", LogLevel.Debug); - sizeToSend = fullLoad.length - offset; - } else { - sizeToSend = maxSizePartialContent; - } - logger.writeUDP("maxSizePartialContent: " + maxSizePartialContent, LogLevel.Debug); - logger.writeUDP("Sending " + filename + " from " + offset + " to " + (offset + sizeToSend), LogLevel.Debug); - byte[] load = Arrays.copyOfRange(fullLoad, (int)offset, (int)(offset + sizeToSend)); - if (Arrays.binarySearch(fileList, filename) >= 0) { - try { - if (load.length == 0) { - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.EMPTY_FILE))); - } else { - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP((Payload)(new FilePart(filename, fullLoad.length, offset, load)))); - } - } catch (Exception e2) { - logger.writeUDP(e2, LogLevel.Error); - } - } else { - logger.writeUDP("File requested not found: `" + filename + "` " + Arrays.binarySearch(fileList, filename), LogLevel.Debug); - logger.writeUDP("File list:", LogLevel.Debug); - for (String f: fileList) { - logger.writeUDP("- " + f, LogLevel.Debug); - } - - throw new IOException(); // to send a NOT_FOUND in the catch block - } - } catch (IOException e) { - try { - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.NOT_FOUND))); - } catch (Exception e2) { - logger.writeUDP(e2, LogLevel.Error); - } - } - } - break; - case LIST_REQUEST: - logger.writeUDP("Received LIST_REQUEST", LogLevel.Action); - try { - if (fileList.length == 0) { - logger.writeUDP("Sending EMPTY_DIRECTORY", LogLevel.Action); - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.EMPTY_DIRECTORY))); - } else { - logger.writeUDP("Sending LIST_RESPONSE", LogLevel.Action); - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP((Payload)(new FileList(fileList)))); - } - } catch (Exception e2) { - logger.writeUDP(e2, LogLevel.Error); - } - break; - case HASH_REQUEST: - logger.writeUDP("Received HASH_REQUEST", LogLevel.Action); - sendHashResponse(pd); - break; - default: - sendInternalError(pd); - } + ProtocolP2PPacketUDP pd = new ProtocolP2PPacketUDP<>((Object)socket); + handleRequest(pd); } catch (IOException e) { - } catch (SocketClosed e) { - } catch (TransmissionError e) { - } catch (ProtocolError e) { - } catch (VersionError e) { - } catch (InternalError e) { - } catch (SizeError e) { + } catch (LocalException e) { } } + fileListWatcher.setStop(); + sendUnregisterRequest(); } - /** Initialize local list of all files allowed to be shared. + + /** Implementation of writeLog + * @param text Text to log + * @param logLevel level of logging */ - private void initFileList() { - File folder = new File(baseDirectory); - Vector v = new Vector(); - File[] files = folder.listFiles(); - /* Add non-recursively files's names to fileList */ - for (File f : files) { - if (f.isFile()) { - v.add(f.getName()); - } - } - fileList = new String[v.size()]; - v.toArray(fileList); - Arrays.sort(fileList); + protected void writeLog(String text, LogLevel logLevel) { + logger.writeUDP(text, logLevel); } - /** Init sha512 map. + /** Implementation of writeLog + * @param e exception to log + * @param logLevel level of logging */ - private void initSha512() { - for(String f: fileList) { - try { - MessageDigest md = MessageDigest.getInstance(HashAlgorithm.SHA512.getName()); - sha512.put(f, md.digest(Files.readAllBytes(Paths.get(baseDirectory + f)))); - md.reset(); - } catch (NoSuchAlgorithmException e) { - logger.writeUDP("sha512 not supported", LogLevel.Error); - } catch (IOException e) { - logger.writeUDP("cannot read " + f, LogLevel.Warning); - } - } + protected void writeLog(Exception e, LogLevel logLevel) { + logger.writeUDP(e, logLevel); } - - /** Send an internal error message. - * @param pd ProtocolP2PPacketUDP to respond + + /** Create packets + * @param payload Payload */ - private void sendInternalError(ProtocolP2PPacketUDP pd) { - logger.writeUDP("Internal Error", LogLevel.Warning); - try { - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))); - } catch (Exception e) { - logger.writeUDP(e, LogLevel.Error); - } + protected < T extends Payload > ProtocolP2PPacket createProtocolP2PPacket(T payload) { + return (ProtocolP2PPacket)new ProtocolP2PPacketUDP(payload); } - /** Send hash response to hash request - * @param pd Request received + /** Getter for tracker socket */ - private void sendHashResponse(ProtocolP2PPacketUDP pd) { - Payload p = pd.getPayload(); - assert p instanceof HashRequest : "payload must be an instance of HashRequest"; - if (!(p instanceof HashRequest)) { - sendInternalError(pd); - } else { - String filename = ((HashRequest)p).getFilename(); - if (Arrays.binarySearch(fileList, filename) >= 0) { - Map hashes = new HashMap<>(); - for (HashAlgorithm h : ((HashRequest)p).getAlgoList()) { - switch (h) { - case SHA512: - hashes.put(h, sha512.get(filename)); - break; - case MD5: - default: - hashes.put(h, new byte[0]); - break; - } - } - try { - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP((Payload)(new HashResponse(filename, hashes)))); - } catch (Exception e) { - logger.writeUDP(e, LogLevel.Error); - } - } else { - // file not found - try { - pd.sendResponse((ProtocolP2PPacket)new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.NOT_FOUND))); - } catch (Exception e) { - logger.writeUDP(e, LogLevel.Error); - } - } - } + protected Object getTrackerSocket() { + return (Object)tracker.getUDPSocket(); } - } - diff --git a/src/serverP2P/ServerP2P.java b/src/serverP2P/ServerP2P.java index 313d263..7c5a3d3 100644 --- a/src/serverP2P/ServerP2P.java +++ b/src/serverP2P/ServerP2P.java @@ -1,8 +1,14 @@ package serverP2P; + +import java.util.Scanner; import serverP2P.ServerManagementUDP; import serverP2P.ServerManagementTCP; import tools.Directories; import tools.Logger; +import tools.HostItem; +import tools.ServerPortRange; +import tools.TrackerPortRange; + /** Server only implementation * First argument of main method is port listened by the server, and is mandatory. @@ -12,38 +18,111 @@ import tools.Logger; * @version 1.0 */ public class ServerP2P { - private int port; private Directories directories; static private final String subdir = "seeded/"; private Logger logger; + private HostItem tracker; + private HostItem server; - /** Constructor with portStr containing a port number. - * @param portStr String containing port number of listening. + /** Constructor with portServerstr containing a port number. + * @param hostnameServer binded hostname + * @param portServer binded port + * @param hostnameTracker tracker hostname + * @param portTracker tracker port */ - public ServerP2P(String portStr) { - port = Integer.valueOf(Integer.parseInt(portStr)); - directories = new Directories("P2P_JAVA_PROJECT_SERVER_" + port); + public ServerP2P(String hostnameServer, int portServer, String hostnameTracker, int portTracker) { + Scanner scanner = new Scanner(System.in); + server = new HostItem(hostnameServer, portServer); + tracker = new HostItem(hostnameTracker, portTracker); + directories = new Directories("P2P_JAVA_PROJECT_SERVER_" + portServer); directories.createSubdir(subdir); logger = new Logger(directories.getDataHomeDirectory() + "server.log"); - System.out.println("Server will listen on port " + port + " and serve files from " + directories.getDataHomeDirectory() + subdir); - directories.askOpenDataHomeDirectory(subdir); + System.out.println("Server will listen on port " + portServer + " and serve files from " + directories.getDataHomeDirectory() + subdir); + directories.askOpenDataHomeDirectory(subdir, scanner); + scanner.close(); } /** Main program entry point * first parameter is port number and is mandatory - * to test, run with: java -ea serverP2P.ServerP2P -- + * to test, run with: java serverP2P.ServerP2P * @param args parameters */ public static void main(String [] args) { - ServerP2P s = new ServerP2P(args[1]); - ServerManagementUDP smudp = new ServerManagementUDP(s.directories.getDataHomeDirectory() + subdir, s.port, s.logger); - ServerManagementTCP smtcp = new ServerManagementTCP(s.directories.getDataHomeDirectory() + subdir, s.port, s.logger); - Thread tudp = new Thread(smudp); - tudp.setName("server UDP P2P-JAVA-PROJECT"); - tudp.start(); - Thread ttcp = new Thread(smtcp); - ttcp.setName("server TCP P2P-JAVA-PROJECT"); - ttcp.start(); - } + final String defaultHostname = "localhost"; + final ServerPortRange serverPortRange = new ServerPortRange(); + final TrackerPortRange trackerPortRange = new TrackerPortRange(); + String hostnameServer = ""; + int portServer = 0; + String hostnameTracker = ""; + int portTracker = 0; + Scanner scanner = new Scanner(System.in); + + if ((args.length != 5) && (args.length != 0)){ + System.out.println("usage : java serveurP2P.ServeurP2P (interactive) or java serveurP2P.ServeurP2P -- (" + serverPortRange + " and " + trackerPortRange +")"); + System.exit(1); + } + else if(args.length == 5){ + hostnameServer = args[1]; + portServer = Integer.valueOf(Integer.parseInt(args[2])); + hostnameTracker = args[3]; + portTracker = Integer.valueOf(Integer.parseInt(args[4])); + } else { + System.out.println("Server, enter hostname to bind (default = localhost): "); + hostnameServer = scanner.nextLine(); + if(hostnameServer.equals("")){ + hostnameServer = defaultHostname; + System.out.println("using default hostname : " + hostnameServer); + } + System.out.println("enter port (default = " + serverPortRange.getDefaultPort() + "): "); + String portServerStr = scanner.nextLine(); + if(portServerStr.equals("")){ + portServer = serverPortRange.getDefaultPort(); + System.out.println("using default port : " + portServer); + } else { + portServer = Integer.valueOf(Integer.parseInt(portServerStr)); + } + System.out.println("enter hostname of tracker (default = localhost): "); + hostnameTracker = scanner.nextLine(); + if(hostnameTracker.equals("")){ + hostnameTracker = defaultHostname; + System.out.println("tracker default hostname : " + hostnameTracker); + } + System.out.println("enter tracker's port (default = " + trackerPortRange.getDefaultPort() + "): "); + String portTrackerStr = scanner.nextLine(); + if(portTrackerStr.equals("")){ + portTracker = serverPortRange.getDefaultPort(); + System.out.println("using default port : " + portTracker); + } else { + portTracker = Integer.valueOf(Integer.parseInt(portTrackerStr)); + } + } + + System.out.println("using hostname : " + hostnameServer); + if(serverPortRange.isPortInRange(portServer)) { + System.out.println("using port : " + portServer); + } + else { + System.out.println("Port not in range. " + serverPortRange); + portServer = serverPortRange.getDefaultPort(); + } + System.out.println("tracker hostname : " + hostnameTracker); + + if(trackerPortRange.isPortInRange(portTracker)) { + System.out.println("using port : " + portTracker); + } + else { + System.out.println("Port not in range. " + trackerPortRange); + portTracker = trackerPortRange.getDefaultPort(); + } + ServerP2P s = new ServerP2P(hostnameServer, portServer, hostnameTracker, portTracker); + ServerManagementUDP smudp = new ServerManagementUDP(s.directories.getDataHomeDirectory() + subdir, s.server, s.tracker, s.logger); + ServerManagementTCP smtcp = new ServerManagementTCP(s.directories.getDataHomeDirectory() + subdir, s.server, s.tracker, s.logger); + Thread tudp = new Thread(smudp); + tudp.setName("server UDP P2P-JAVA-PROJECT"); + tudp.start(); + Thread ttcp = new Thread(smtcp); + ttcp.setName("server TCP P2P-JAVA-PROJECT"); + ttcp.start(); + } } diff --git a/src/tools/BytesArrayTools.java b/src/tools/BytesArrayTools.java index c11088c..554bc3a 100644 --- a/src/tools/BytesArrayTools.java +++ b/src/tools/BytesArrayTools.java @@ -1,6 +1,6 @@ package tools; -import exception.SizeError; -import exception.ProtocolError; +import localException.SizeError; +import localException.ProtocolError; import java.io.UnsupportedEncodingException; import java.util.Arrays; @@ -219,6 +219,39 @@ public class BytesArrayTools { } } + /** Read string from byte array + * @param array Byte array to read + * @param start start position in byte array + * @param endStr End string delimiter + * @return String read + * @throws InternalError + */ + public static String readString(byte[] array, int start, String endStr) throws InternalError { + boolean failed = false; + try { + int i = start; + while(true) { + int j = i; + for(byte b: endStr.getBytes()) { + if (b != array[j]) { + failed = true; + break; + } + j++; + } + if (failed) { + i++; + failed = false; + } else { + break; + } + } + return readString(array, start, i - start); + } catch(IndexOutOfBoundsException e) { + throw new InternalError(); + } + } + /** Write byte Array to byte Array. * @param dst Destination byte Array * @param src Source byte Array diff --git a/src/tools/Directories.java b/src/tools/Directories.java index 97ec091..0ee49c6 100644 --- a/src/tools/Directories.java +++ b/src/tools/Directories.java @@ -70,6 +70,7 @@ public class Directories { } /** Opens dataHomeDirectory if supported. + * @param subdir Subdir to open (optional) */ private void openDataHomeDirectory(String subdir) { String d = dataHomeDirectory; @@ -93,11 +94,12 @@ public class Directories { } /** Asks the user to choose opening dataHomeDirectory or not. + * @param subdir subdirectory to open (optional) + * @param scanner Scanner to use for reading input */ - public void askOpenDataHomeDirectory(String subdir) { + public void askOpenDataHomeDirectory(String subdir, Scanner scanner) { if (os.equals("Linux") || os.equals("Mac") || os.equals("Mac OS X")) { System.out.println("Do you want to open this directory? (y/N)"); - Scanner scanner = new Scanner(System.in); String resp = scanner.nextLine(); if (resp.equals("y") || resp.equals("Y")) { System.out.println("Openning"); diff --git a/src/tools/HostItem.java b/src/tools/HostItem.java index ffe2f58..95960af 100644 --- a/src/tools/HostItem.java +++ b/src/tools/HostItem.java @@ -17,6 +17,7 @@ public class HostItem { private int port; private Socket tcpSocket; private DatagramSocket udpSocket; + private InetAddress inetAddress; /** Constructor with hostname and port * @param hostname Hostname @@ -35,19 +36,32 @@ public class HostItem { try { tcpSocket = new Socket(InetAddress.getByName(hostname), port); } catch (SocketException e) { - System.err.println("Error: No TCP socket available."); + System.err.println("getTCPSocket error: No TCP socket available."); System.exit(-1); } catch (UnknownHostException e) { - System.err.println("Error: Unknown host."); + System.err.println("getTCPSocket error: Unknown host (" + this + ")."); System.exit(-1); } catch (IOException e) { - System.err.println("Error: Cannot create TCP socket"); + System.err.println("getTCPSocket error: Cannot create TCP socket (" + this + ")."); System.exit(-1); } } return tcpSocket; } + /** Get TCP Socket. + * @return TCP Socket + * @throws SocketException + * @throws UnknownHostException + * @throws IOException + */ + public Socket tryGetTCPSocket() throws SocketException, UnknownHostException, IOException { + if (tcpSocket == null) { + tcpSocket = new Socket(InetAddress.getByName(hostname), port); + } + return tcpSocket; + } + /** Closes tcp socket */ public void closeTCPSocket() { @@ -70,19 +84,86 @@ public class HostItem { udpSocket = new DatagramSocket(); udpSocket.connect(InetAddress.getByName(hostname), port); } catch (SocketException e) { - System.err.println("Error: No UDP socket available."); + System.err.println("getUDPSocket error: No UDP socket available." ); System.exit(-1); } catch (UnknownHostException e) { - System.err.println("Error: Unknown host."); + System.err.println("getUDPSocket error: Unknown host (" + this + ")."); System.exit(-1); } } return udpSocket; } + + /** Closes udp socket + */ public void closeUDPSocket() { if (udpSocket != null) { udpSocket.close(); } udpSocket = null; } + + /** Getter for hostname */ + public String getHostname() { + return hostname; + } + + /** Getter for port */ + public int getPort() { + return port; + } + + /** To string + * @return String representation + */ + public String toString() { + return getHostname() + " (port " + getPort() + ")"; + } + + /** Override of equals method + * @param other Object to test equality with + * @return true if equals + */ + public boolean equals(Object other) { + boolean result = false; + if (other instanceof HostItem) { + HostItem that = (HostItem) other; + result = this.getHostname() == that.getHostname() && this.getPort() == that.getPort(); + } + return result; + } + + /** Override of hashCode method + * @return a hash code for this object. + */ + public int hashCode() { + return hostname.hashCode() ^ port; + } + + /** Get InetAddress associated to this HostItem. + * @return InetAddress + */ + public InetAddress getInetAddress() { + if (inetAddress == null) { + try { + inetAddress = InetAddress.getByName(getHostname()); + } catch (UnknownHostException e) { + System.err.println("getInetAddress error: Unknown host (" + this + ")."); + System.exit(-1); + } + } + return inetAddress; + } + + /** Constructor from Socket. + * @param s socket + */ + public HostItem(Socket s) { + tcpSocket = s; + inetAddress = s.getInetAddress(); + hostname = inetAddress.getCanonicalHostName(); + port = s.getPort(); + } + + } diff --git a/src/tools/HostList.java b/src/tools/HostList.java deleted file mode 100644 index 510e3ba..0000000 --- a/src/tools/HostList.java +++ /dev/null @@ -1,55 +0,0 @@ -package tools; - -import java.util.Scanner; -import java.util.ArrayList; -import java.util.List; -import java.util.InputMismatchException; -import tools.HostItem; - -/** Helper to get the server list from the user -* @author Louis Royer -* @author Flavien Haas -* @author JS Auge -* @version 1.0 -*/ -public class HostList { - /** - * Let the user enter all server and puts it in a list - * @return list of servers - */ - public static List getServList() { - List serverList = new ArrayList(); - Scanner scanner = new Scanner(System.in); - String servName; - int port = 0; - do { - System.out.println("Enter hostname of next server: (or \"stop\" when finished, default: localhost)."); - servName = scanner.nextLine(); - if (servName.equals("")) { - servName = "localhost"; - } - if (!servName.equals("stop")) { - boolean err = false; - do { - System.out.println("Enter port for this server"); - try { - port = scanner.nextInt(); - scanner.nextLine(); - if (port > 65535 || port <= 0) { - err = true; - System.out.println("Port number must be in 1-65535 range. Try again."); - } else { - err = false; - } - } catch (InputMismatchException e) { - System.out.println("Invalid number. Try again."); - err = true; - } - } while (err); - serverList.add(new HostItem(servName, port)); - } - - } while (!servName.equals("stop")); - return serverList; - } -} diff --git a/src/tools/Logger.java b/src/tools/Logger.java index 63c2107..1fdb4ad 100644 --- a/src/tools/Logger.java +++ b/src/tools/Logger.java @@ -17,6 +17,9 @@ import java.sql.Timestamp; public class Logger { private Path logFile; + /** Constructor with logFile. + * @param logFile name of the file to store logs. + */ public Logger(String logFile) { assert logFile != null : "Logfile name is null"; this.logFile = Paths.get(logFile); @@ -120,4 +123,5 @@ public class Logger { e.printStackTrace(); } + } diff --git a/src/tools/PortRange.java b/src/tools/PortRange.java new file mode 100644 index 0000000..290cc94 --- /dev/null +++ b/src/tools/PortRange.java @@ -0,0 +1,49 @@ +package tools; + +/** Test ports. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class PortRange { + + protected int portMax; + protected int portMin; + protected int defaultPort; + protected String type; + + /** Port range constructor + * @param portMin minimum port + * @param portMax maximum port + * @param defaultPort default port + * @param type type of range + */ + public PortRange(int portMin, int portMax, int defaultPort, String type) { + this.portMax = portMax; + this.portMin = portMin; + this.defaultPort = defaultPort; + this.type = type; + } + + /** test if port given correspond a range : registered ports, can be used without superuser privileges + * @return true if port was valid + */ + public boolean isPortInRange(int port) { + return ((port >= portMin) && (port <= portMax)); + } + + /** To String + * @return String representation + */ + public String toString() { + return "default " + type + "port: " + defaultPort + "(range: " + portMin + " -> " + portMax + ")"; + } + + /** Default port getter + * @return default port + */ + public int getDefaultPort() { + return defaultPort; + } +} diff --git a/src/tools/ServeErrors.java b/src/tools/ServeErrors.java new file mode 100644 index 0000000..ff083f9 --- /dev/null +++ b/src/tools/ServeErrors.java @@ -0,0 +1,82 @@ +package tools; +import tools.LogLevel; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.Payload; +import protocolP2P.RequestResponseCode; + +public abstract class ServeErrors { + + /** 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); + + /** Create packets + * @param payload Payload + */ + protected abstract < T extends Payload > ProtocolP2PPacket createProtocolP2PPacket(T payload); + + /** Send a NotATracker error message. + * @param pd Request received + */ + protected < T extends ProtocolP2PPacket > void sendNotATracker(T pd) { + try { + pd.sendResponse(createProtocolP2PPacket(new Payload(RequestResponseCode.NOT_A_TRACKER))); + } catch (Exception e) { + writeLog(e, LogLevel.Error); + } + } + + /** Send an internal error message. + * @param pd Request received + */ + protected < T extends ProtocolP2PPacket > void sendInternalError(T pd) { + writeLog("Internal Error", LogLevel.Warning); + try { + pd.sendResponse(createProtocolP2PPacket(new Payload(RequestResponseCode.INTERNAL_ERROR))); + } catch (Exception e) { + writeLog(e, LogLevel.Error); + } + } + + /** Send a not found message. + * @param pd Request received + */ + protected < T extends ProtocolP2PPacket > void sendNotFound(T pd) { + try { + pd.sendResponse(createProtocolP2PPacket(new Payload(RequestResponseCode.NOT_FOUND))); + } catch (Exception e) { + writeLog(e, LogLevel.Error); + } + } + + /** Send an empty directory message. + * @param pd Request received + */ + protected < T extends ProtocolP2PPacket > void sendEmptyDirectory(T pd) { + try { + pd.sendResponse(createProtocolP2PPacket(new Payload(RequestResponseCode.EMPTY_DIRECTORY))); + } catch (Exception e) { + writeLog(e, LogLevel.Error); + } + } + + /** Send an empty file message. + * @param pd Request received + */ + protected < T extends ProtocolP2PPacket > void sendEmptyFile(T pd) { + try { + pd.sendResponse(createProtocolP2PPacket(new Payload(RequestResponseCode.EMPTY_FILE))); + } catch (Exception e) { + writeLog(e, LogLevel.Error); + } + } + +} diff --git a/src/tools/ServerPortRange.java b/src/tools/ServerPortRange.java new file mode 100644 index 0000000..96722c5 --- /dev/null +++ b/src/tools/ServerPortRange.java @@ -0,0 +1,10 @@ +package tools; +import tools.PortRange; + +public class ServerPortRange extends PortRange { + + /** Constructor */ + public ServerPortRange() { + super(7000, 7999, 7070, "server"); + } +} diff --git a/src/tools/TrackerPortRange.java b/src/tools/TrackerPortRange.java new file mode 100644 index 0000000..0ca491b --- /dev/null +++ b/src/tools/TrackerPortRange.java @@ -0,0 +1,10 @@ +package tools; +import tools.PortRange; + +public class TrackerPortRange extends PortRange { + + /** Constructor */ + public TrackerPortRange() { + super(6000, 6999, 6969, "tracker"); + } +} diff --git a/src/tracker/Tracker.java b/src/tracker/Tracker.java new file mode 100644 index 0000000..92ff5f5 --- /dev/null +++ b/src/tracker/Tracker.java @@ -0,0 +1,93 @@ +package tracker; + +import java.util.Scanner; +import tracker.TrackerManagementTCP; +import tracker.TrackerManagementUDP; +import tools.Directories; +import tools.Logger; +import tools.LogLevel; +import tools.TrackerPortRange; +import tools.HostItem; + +/** Tracker implementation + * First argument of main method is port listened by the tracker, and is mandatory. + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class Tracker { + private HostItem tracker; + private Directories directories; + private Logger logger; + + /** Constructor with portStr containing a port number. + * @param hostname hostname to bind + * @param port port to bind + */ + public Tracker(String hostname, int port) { + tracker = new HostItem(hostname, port); + directories = new Directories("P2P_JAVA_PROJECT_TRACKER_" + port); + logger = new Logger(directories.getDataHomeDirectory() + "tracker.log"); + System.out.println("Tracker will listen on port " + port + " and write logs into " + directories.getDataHomeDirectory()); + Scanner scanner = new Scanner(System.in); + directories.askOpenDataHomeDirectory(null, scanner); + scanner.close(); + } + + /** Main program entry point + * first parameter is port number and is mandatory + * to test, run with: java serverP2P.ServerP2P + * @param args parameters + */ + public static void main(String [] args) { + final TrackerPortRange trackerPortRange = new TrackerPortRange(); + final String defaultHostname = "localhost"; + Scanner scanner = new Scanner(System.in); + String hostname = ""; + int port = 0; + Tracker t; + + if ((args.length != 3) && (args.length != 0)){ + System.out.println("usage : java tracker.Tracker (interactive) or java trackerP2P.trackerP2P -- (" + trackerPortRange +")"); + System.exit(1); + } else if (args.length == 3){ + hostname = args[1]; + port = Integer.valueOf(Integer.parseInt(args[2])); + } else { + System.out.println("Tracker Server, enter hostname to bind (default = localhost): "); + hostname = scanner.nextLine(); + if(hostname.equals("")){ + hostname = defaultHostname; + System.out.println("using default hostname : " + hostname); + } + System.out.println("enter port (default = " + trackerPortRange.getDefaultPort() +"): "); + String portStr = scanner.nextLine(); + if(portStr.equals("")){ + port = trackerPortRange.getDefaultPort(); + System.out.println("using default port : " + port); + } else { + port = Integer.valueOf(Integer.parseInt(portStr)); + } + } + + System.out.println("using hostname : " + hostname); + if(trackerPortRange.isPortInRange(port)) { + System.out.println("using port : " + port); + t = new Tracker(hostname, port); + } + else { + System.out.println("Port not in range. " + trackerPortRange); + t = new Tracker(hostname, trackerPortRange.getDefaultPort()); + } + + TrackerManagementUDP tmudp = new TrackerManagementUDP(t.tracker, t.logger); + TrackerManagementTCP tmtcp = new TrackerManagementTCP(t.tracker, t.logger); + Thread tudp = new Thread(tmudp); + tudp.setName("Tracker UDP P2P-JAVA-PROJECT"); + tudp.start(); + Thread ttcp = new Thread(tmtcp); + ttcp.setName("Tracker TCP P2P-JAVA-PROJECT"); + ttcp.start(); + } +} diff --git a/src/tracker/TrackerManagement.java b/src/tracker/TrackerManagement.java new file mode 100644 index 0000000..37c3cbc --- /dev/null +++ b/src/tracker/TrackerManagement.java @@ -0,0 +1,201 @@ +package tracker; +import tools.ServeErrors; +import tools.HostItem; +import tools.Logger; +import tools.LogLevel; +import java.util.Map; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.List; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.Payload; +import protocolP2P.DiscoverRequest; +import protocolP2P.DiscoverResponse; +import protocolP2P.FileList; +import protocolP2P.Unregister; +import protocolP2P.Register; +import protocolP2P.RequestResponseCode; +import localException.InternalError; +import remoteException.EmptyDirectory; +import exception.LocalException; + + +/** Tracker management implementation + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public abstract class TrackerManagement extends ServeErrors implements Runnable { + protected HostItem tracker; + protected Logger logger; + protected List hostList = new ArrayList<>(); + protected Map> fileList = new HashMap<>(); + protected volatile boolean stop; + + /** Constructor + * @param tracker Tracker HostItem + * @param logger Logger + */ + public TrackerManagement(HostItem tracker, Logger logger) { + stop = false; + this.tracker = tracker; + this.logger = logger; + } + + /** Handle Discover request + * @param pd Received request + * @throws InternalError + */ + protected < T extends ProtocolP2PPacket > void handleDiscover(T pd) throws InternalError { + Payload p = pd.getPayload(); + assert p instanceof DiscoverRequest : "payload must be an instance of DiscoverRequest"; + if (!(p instanceof DiscoverRequest)) { + sendInternalError(pd); + } else { + String filename = ((DiscoverRequest)p).getFilename(); + try { + pd.sendResponse(createProtocolP2PPacket(new DiscoverResponse(filename, fileList.getOrDefault(filename, hostList)))); + } catch (Exception e) { + writeLog(e, LogLevel.Error); + } + } + } + + /** Handle List Responses + * @param pd Received response + * @throws InternalError + */ + protected > void handleListResponse(T pd, HostItem host) throws InternalError { + Payload p = pd.getPayload(); + assert p instanceof FileList: "payload must be an instance of FileList"; + if (!(p instanceof FileList)) { + throw new InternalError(); + } else { + String[] f = ((FileList)p).getFileList(); + for (String file: f) { + List h = fileList.get(file); + if (h != null) { + if (!h.contains(host)) { + h.add(host); + } + } else { + List emptyH = new ArrayList<>(); + emptyH.add(host); + fileList.put(file, emptyH); + } + } + } + } + + /** Handle Unregistering + * @param pd Request received + * @throws InternalError + */ + protected < T extends ProtocolP2PPacket > void handleUnregister(T pd) throws InternalError { + Payload p = pd.getPayload(); + assert p instanceof Unregister : "payload must be an instance of Unregister"; + if (!(p instanceof Unregister)) { + sendInternalError(pd); + throw new InternalError(); + } + HostItem host = ((Unregister)p).getHostItem(); + writeLog("Received UNREGISTER from host " + pd.getHostItem() + ". Removing host " + host, LogLevel.Action); + hostList.remove(host); + for(String f: fileList.keySet()) { + fileList.get(f).remove(host); + if(fileList.get(f).isEmpty()) { + fileList.remove(f); + } + } + } + + /** Getter for HostItem socket + * @param hostItem HostItem + */ + protected abstract Object getHostItemSocket(HostItem hostItem); + + /** Close HostItem socket + * @param hostItem HostItem + */ + protected abstract void closeHostItemSocket(HostItem hostItem); + + /** Handle Registering + * @param pd Received request + * @throws InternalError + */ + protected < T extends ProtocolP2PPacket > void handleRegister(T pd) throws InternalError { + Payload p = pd.getPayload(); + assert p instanceof Register : "payload must be an instance of Register"; + if (!(p instanceof Register)) { + throw new InternalError(); + } + // add host to known host list + HostItem host = ((Register)p).getHostItem(); + if (!hostList.contains(host)) { + hostList.add(host); + } + // send a list request + try { + ProtocolP2PPacket pLReq = createProtocolP2PPacket(new Payload(RequestResponseCode.LIST_REQUEST)); + pLReq.sendRequest(getHostItemSocket(host)); + writeLog("Received REGISTER from host " + pd.getHostItem() + ". Adding host " + host + " to list. Sending List request", LogLevel.Action); + handleListResponse(pLReq.receiveResponse(), host); + writeLog("Received LIST RESPONSE from host " + pd.getHostItem(), LogLevel.Action); + closeHostItemSocket(host); + } catch (EmptyDirectory e) { + writeLog("Empty Directory", LogLevel.Debug); + hostList.remove(host); + writeLog("Received EMPTY DIRECTORY from host " + pd.getHostItem() + ". Aborting.", LogLevel.Action); + } catch (Exception e) { + // remove from list because list request could not be send + hostList.remove(host); + writeLog("Aborting the add of host " + host, LogLevel.Action); + writeLog(e, LogLevel.Error); + } + } + + + /** Handle requests + * @throws LocalException + */ + protected > void handleRequest(T pd) throws LocalException { + Payload p = pd.getPayload(); + switch (p.getRequestResponseCode()) { + case LOAD_REQUEST: + writeLog("Received LOAD_REQUEST from host " + pd.getHostItem() + ", sending NOT_FOUND", LogLevel.Action); + sendNotFound(pd); + break; + case LIST_REQUEST: + writeLog("Received LIST_REQUEST from host " + pd.getHostItem() + ", sending EMPTY_DIRECTORY", LogLevel.Action); + sendEmptyDirectory(pd); + break; + case HASH_REQUEST: + writeLog("Received HASH_REQUEST from host " + pd.getHostItem() + ", sending NOT_FOUND", LogLevel.Action); + sendNotFound(pd); + break; + case REGISTER: + writeLog("Received REGISTER from host " + pd.getHostItem(), LogLevel.Debug); + handleRegister(pd); + break; + case UNREGISTER: + writeLog("Received UNREGISTER from host " + pd.getHostItem(), LogLevel.Debug); + handleUnregister(pd); + break; + case DISCOVER_REQUEST: + writeLog("Received DISCOVER REQUEST from host " + pd.getHostItem(), LogLevel.Action); + handleDiscover(pd); + break; + default: + writeLog("Received grabbage from host " + pd.getHostItem(), LogLevel.Action); + sendInternalError(pd); + break; + } + } + + /** Stop the thread */ + public void setStop() { + stop = true; + } + +} diff --git a/src/tracker/TrackerManagementTCP.java b/src/tracker/TrackerManagementTCP.java new file mode 100644 index 0000000..603bab5 --- /dev/null +++ b/src/tracker/TrackerManagementTCP.java @@ -0,0 +1,155 @@ +package tracker; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.net.InetAddress; +import protocolP2P.ProtocolP2PPacketTCP; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.RequestResponseCode; +import protocolP2P.Payload; +import protocolP2P.Register; +import protocolP2P.Unregister; +import protocolP2P.DiscoverRequest; +import protocolP2P.DiscoverResponse; +import protocolP2P.FileList; +import exception.LocalException; +import remoteException.EmptyDirectory; +import localException.InternalError; +import localException.SocketClosed; +import tracker.TrackerManagement; +import tools.HostItem; +import tools.Logger; +import tools.LogLevel; + +/** Tracker management implementation with tcp + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class TrackerManagementTCP extends TrackerManagement { + private ServerSocket socket; + + /** Constructor with port and logger. + * @param tracker hostitem of the tracker. + * @param logger Logger object + */ + public TrackerManagementTCP(HostItem tracker, Logger logger) { + super(tracker, logger); + try { + socket = new ServerSocket(tracker.getPort(), 10, tracker.getInetAddress()); + } catch (SocketException e) { + writeLog("Error: cannot listen on" + tracker, LogLevel.Error); + System.exit(-1); + } catch (IOException e) { + writeLog("Error: cannot open socket", LogLevel.Error); + System.exit(-2); + } + } + + /** Implementation of runnable. This methods allows to run the server. + */ + public void run() { + writeLog("Tracker sucessfully started", LogLevel.Info); + 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); + } + } + } + + + /** Private runnable class allowing to serve one client. + */ + private class ClientHandler implements Runnable { + private HostItem addr; + /** Constructor with a socket. + * @param s Socket of this client + */ + public ClientHandler(Socket s) { + this.addr = new HostItem(s); + } + + /** Implementation of runnable. This method allow to serve one client. + */ + public void run() { + + boolean end = false; + writeLog("[ " + addr + "] New connection", LogLevel.Action); + do { + end = handleClientRequest(); + } while(!end); + writeLog("[ " + addr + "] End of connection", LogLevel.Action); + } + + /** Respond to next request incomming on socket s. + * @param s Socket used to read request and send response + * @return true if cannot expect another request (ie, socket is closed) + */ + private boolean handleClientRequest() { + try { + ProtocolP2PPacketTCP pd = new ProtocolP2PPacketTCP<>((Object)addr.getTCPSocket()); + handleRequest(pd); + } catch (IOException e) { + writeLog(e, LogLevel.Warning); + return true; + } catch (SocketClosed e) { + return true; + } catch (LocalException e) { + writeLog(e, LogLevel.Warning); + return true; + } + return false; + } + } + + + /** 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); + } + + + /** Getter for HostItem socket + * @param hostItem HostItem + */ + protected Object getHostItemSocket(HostItem hostItem) { + return (Object)hostItem.getTCPSocket(); + } + + /** Close HostItem socket + * @param hostItem HostItem + */ + protected void closeHostItemSocket(HostItem hostItem) { + hostItem.closeTCPSocket(); + } +} diff --git a/src/tracker/TrackerManagementUDP.java b/src/tracker/TrackerManagementUDP.java new file mode 100644 index 0000000..cdfb890 --- /dev/null +++ b/src/tracker/TrackerManagementUDP.java @@ -0,0 +1,103 @@ +package tracker; +import tools.Logger; +import tools.LogLevel; +import java.net.DatagramSocket; +import protocolP2P.ProtocolP2PPacketUDP; +import protocolP2P.ProtocolP2PPacket; +import protocolP2P.RequestResponseCode; +import protocolP2P.Payload; +import protocolP2P.Register; +import protocolP2P.Unregister; +import tools.HostItem; +import java.util.ArrayList; +import java.util.List; +import java.io.IOException; +import java.net.SocketException; +import exception.LocalException; +import java.util.Map; +import java.util.HashMap; +import protocolP2P.DiscoverRequest; +import protocolP2P.DiscoverResponse; +import protocolP2P.FileList; +import localException.InternalError; +import remoteException.EmptyDirectory; +import java.net.InetAddress; +import java.net.UnknownHostException; +import tracker.TrackerManagement; + +/** Tracker management implementation with udp + * @author Louis Royer + * @author Flavien Haas + * @author JS Auge + * @version 1.0 + */ +public class TrackerManagementUDP extends TrackerManagement { + private DatagramSocket socket; + /** Constructor with port and logger. + * @param tracker hostitem of the tracker. + * @param logger Logger object + */ + public TrackerManagementUDP(HostItem tracker, Logger logger) { + super(tracker, logger); + try { + socket = new DatagramSocket(tracker.getPort(), tracker.getInetAddress()); + } catch (SocketException e) { + logger.writeUDP("Error: cannot listen on " + tracker, LogLevel.Error); + System.exit(-1); + } + } + + /** Implementation of runnable. This methods allows to run the tracker. + */ + public void run() { + logger.writeUDP("Tracker successfully started", LogLevel.Info); + while(!stop) { + try { + ProtocolP2PPacketUDP pd = new ProtocolP2PPacketUDP<>((Object)socket); + handleRequest(pd); + } catch (IOException e) { + logger.writeUDP(e, LogLevel.Warning); + } catch (LocalException e) { + logger.writeUDP(e, LogLevel.Warning); + } + } + } + + /** 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); + } + + + /** Getter for HostItem socket + * @param hostItem HostItem + */ + protected Object getHostItemSocket(HostItem hostItem) { + return (Object)hostItem.getUDPSocket(); + } + + /** Close HostItem socket + * @param hostItem HostItem + */ + protected void closeHostItemSocket(HostItem hostItem) { + hostItem.closeUDPSocket(); + } +}