From dbd38618142ef1349452532765712510f6525258 Mon Sep 17 00:00:00 2001 From: m-gues Date: Sun, 14 Feb 2021 22:58:07 +0100 Subject: [PATCH] =?UTF-8?q?derni=C3=A8re=20version=20de=20l'appli=20+=20ne?= =?UTF-8?q?ttoyage=20code=20serveur?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- POO/src/communication/CommunicationUDP.java | 200 ----- POO/src/communication/TCPClient.java | 57 -- POO/src/communication/TCPInputThread.java | 65 -- POO/src/communication/TCPServer.java | 40 - POO/src/communication/UDPClient.java | 41 - POO/src/communication/UDPServer.java | 78 -- .../filetransfer/FileTransferClient.java | 37 +- .../FileTransferReceivingThread.java | 38 +- .../FileTransferSendingThread.java | 32 +- .../filetransfer/FileTransferServer.java | 19 +- .../filetransfer/FileTransferUtils.java | 88 +- POO/src/communication/tcp/TCPClient.java | 63 +- POO/src/communication/tcp/TCPInputThread.java | 61 +- POO/src/communication/tcp/TCPServer.java | 39 +- .../communication/udp/CommunicationUDP.java | 229 ++++-- POO/src/communication/udp/UDPClient.java | 60 +- POO/src/communication/udp/UDPServer.java | 35 +- POO/src/connexion/ControleurConnexion.java | 57 +- POO/src/connexion/VueConnexion.java | 116 ++- POO/src/database/SQLiteCreateTables.java | 76 +- POO/src/database/SQLiteEncprytion.java | 84 -- POO/src/database/SQLiteEncryption.java | 158 ++++ POO/src/database/SQLiteManager.java | 758 +++++++++++------- POO/src/main/Observer.java | 6 - POO/src/main/Utilisateur.java | 79 +- POO/src/main/Vue.java | 3 + POO/src/messages/Message.java | 86 +- POO/src/messages/MessageFichier.java | 36 + POO/src/messages/MessageSysteme.java | 52 +- POO/src/messages/MessageTexte.java | 22 + POO/src/observers/ObserverInputMessage.java | 9 +- POO/src/observers/ObserverSocketState.java | 6 + POO/src/observers/ObserverUserList.java | 6 + POO/src/session/ControleurSession.java | 282 ++++--- POO/src/session/VueSession.java | 142 +++- POO/src/standard/ControleurStandard.java | 9 +- 36 files changed, 1816 insertions(+), 1353 deletions(-) delete mode 100644 POO/src/communication/CommunicationUDP.java delete mode 100644 POO/src/communication/TCPClient.java delete mode 100644 POO/src/communication/TCPInputThread.java delete mode 100644 POO/src/communication/TCPServer.java delete mode 100644 POO/src/communication/UDPClient.java delete mode 100644 POO/src/communication/UDPServer.java delete mode 100644 POO/src/database/SQLiteEncprytion.java create mode 100644 POO/src/database/SQLiteEncryption.java delete mode 100644 POO/src/main/Observer.java diff --git a/POO/src/communication/CommunicationUDP.java b/POO/src/communication/CommunicationUDP.java deleted file mode 100644 index 2028e7d..0000000 --- a/POO/src/communication/CommunicationUDP.java +++ /dev/null @@ -1,200 +0,0 @@ -package communication; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; - -import main.Observer; -import main.Utilisateur; - -import messages.*; - - -public class CommunicationUDP extends Thread { - - // public enum Mode {PREMIERE_CONNEXION, CHANGEMENT_PSEUDO, DECONNEXION}; - - private UDPClient client; - private int portServer; - private ArrayList portOthers; - private ArrayList users = new ArrayList(); - - private Observer observer; - - public CommunicationUDP(int portClient, int portServer, int[] portsOther) throws IOException { - this.portServer = portServer; - this.portOthers = this.getArrayListFromArray(portsOther); - new UDPServer(portServer, this).start(); - this.client = new UDPClient(portClient); - } - - - private ArrayList getArrayListFromArray(int ports[]) { - ArrayList tmp = new ArrayList(); - for (int port : ports) { - tmp.add(port); - } - tmp.remove(Integer.valueOf(portServer)); - - return tmp; - } - - public void setObserver (Observer obs) { - this.observer=obs; - } - - protected boolean containsUserFromID(String id) { - for(Utilisateur u : users) { - if(u.getId().equals(id) ) { - return true; - } - } - return false; - } - - public boolean containsUserFromPseudo(String pseudo) { - for(Utilisateur u : users) { - if(u.getPseudo().equals(pseudo) ) { - return true; - } - } - - return false; - } - - public int getPortFromPseudo(String pseudo) { - for(int i=0; i < users.size() ; i++) { - - if(users.get(i).getPseudo().equals(pseudo) ) { - return users.get(i).getPort(); - } - } - return -1; - } - - private int getIndexFromID(String id) { - for(int i=0; i < users.size() ; i++) { - if(users.get(i).getId().equals(id) ) { - return i; - } - } - return -1; - } - - private int getIndexFromIP(InetAddress ip) { - for(int i=0; i < users.size() ; i++) { - if(users.get(i).getIp().equals(ip)) { - return i; - } - } - return -1; - } - - - protected synchronized void addUser(String idClient, String pseudoClient, InetAddress ipClient, int port) throws IOException { - users.add(new Utilisateur(idClient, pseudoClient, ipClient, port)); - observer.update(this, users); - - } - - protected synchronized void changePseudoUser(String idClient, String pseudoClient, InetAddress ipClient, int port) { - int index = getIndexFromID(idClient); - users.get(index).setPseudo(pseudoClient); - observer.update(this, users); - } - - - protected synchronized void removeUser(String idClient, String pseudoClient,InetAddress ipClient, int port) { - int index = getIndexFromIP(ipClient); - if( index != -1) { - users.remove(index); - } - observer.update(this, users); - } - - public void removeAll(){ - int oSize = users.size(); - for(int i=0; i files = null; + private ArrayList files; private ObserverInputMessage obsInput; - - public FileTransferClient(int port, ArrayList filesToSend, ObserverInputMessage obs) throws UnknownHostException, IOException { + /** + * Create a client to transfer one or several files on the specified port of + * localhost. A new Thread is created for each file. The files are sent one by + * one to save bandwidth and avoid issues. + * + * @param port The port of localhost on which to send the files. + * @param filesToSend The file(s) to send. + * @param o The observer to notify each time a file is fully sent. + */ + public FileTransferClient(int port, ArrayList filesToSend, ObserverInputMessage o) { this.port = port; this.files = filesToSend; - this.obsInput = obs; + this.obsInput = o; } + /** + * Try to send every file on localhost on the specified port with a new thread. + * An observer is passed to the thread and it is notified each time a file is + * fully sent. + * + * @throws IOException + * @throws InterruptedException + */ public void sendFiles() throws IOException, InterruptedException { - for(File f: this.files) { - FileTransferSendingThread ftc = new FileTransferSendingThread(this.port, f,this.obsInput); + for (File f : this.files) { + FileTransferSendingThread ftc = new FileTransferSendingThread(this.port, f, this.obsInput); ftc.start(); ftc.join(); } - + } } diff --git a/POO/src/communication/filetransfer/FileTransferReceivingThread.java b/POO/src/communication/filetransfer/FileTransferReceivingThread.java index 75f3062..85cfc71 100644 --- a/POO/src/communication/filetransfer/FileTransferReceivingThread.java +++ b/POO/src/communication/filetransfer/FileTransferReceivingThread.java @@ -1,6 +1,5 @@ package communication.filetransfer; - import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -15,20 +14,31 @@ import messages.MessageFichier; import observers.ObserverInputMessage; public class FileTransferReceivingThread extends Thread { - + private SocketChannel sockTransfert; private ObserverInputMessage obsInput; - - public FileTransferReceivingThread(SocketChannel sock, ObserverInputMessage obs) { + + /** + * Create the thread that will receive one file during a file transfer. This + * allows users to write in the chat while sending/receiving files. + * + * @param sock The SocketChannel returned by ServerSocketChannel.accept(). + * @param o The observer to notify once the file is fully received. + */ + public FileTransferReceivingThread(SocketChannel sock, ObserverInputMessage o) { this.sockTransfert = sock; - this.obsInput = obs; + this.obsInput = o; } public void run() { try { int nbByteRead = 0; + + // Buffer to receive a chunk of the file ByteBuffer fileData = ByteBuffer.allocate(4 * FileTransferUtils.KB_SIZE); + // InputStream to read the first object which is a message containing the name + // and size of the file ObjectInputStream inputFileInformation = new ObjectInputStream( this.sockTransfert.socket().getInputStream()); @@ -41,10 +51,11 @@ public class FileTransferReceivingThread extends Thread { String[] fileInfo = this.processFileInformation(m); String filePath = FileTransferUtils.DOWNLOADS_RELATIVE_PATH + fileInfo[0]; long fileSize = Long.parseLong(fileInfo[1]); - + // OutputStream to create the file if it does not exist FileOutputStream fOutStream = new FileOutputStream(filePath); + // Channel to write the data received in the file FileChannel fileWriter = fOutStream.getChannel(); while (nbTotalBytesRead < fileSize && (nbByteRead = this.sockTransfert.read(fileData)) > 0) { @@ -58,11 +69,13 @@ public class FileTransferReceivingThread extends Thread { fileWriter.close(); fOutStream.close(); - + inputFileInformation.close(); + + // Process the message to display (thumbnails in the case of images) and notify + // the observer Message mUpdate = FileTransferUtils.processMessageToDisplay(new File(filePath)); mUpdate.setSender("other"); - this.obsInput.update(this, mUpdate); - + this.obsInput.updateInput(this, mUpdate); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); @@ -75,6 +88,13 @@ public class FileTransferReceivingThread extends Thread { } } + /** + * Split the content of a message with the separator ";". This function is only + * used to read the name and the size of the file to receive. + * + * @param m message containing the file's information (name and size). + * @return An array with the file's name and the file's size respectively. + */ private String[] processFileInformation(MessageFichier m) { return m.getContenu().split(";"); } diff --git a/POO/src/communication/filetransfer/FileTransferSendingThread.java b/POO/src/communication/filetransfer/FileTransferSendingThread.java index 61aaa2f..60da3e4 100644 --- a/POO/src/communication/filetransfer/FileTransferSendingThread.java +++ b/POO/src/communication/filetransfer/FileTransferSendingThread.java @@ -17,33 +17,46 @@ import messages.MessageFichier; import messages.Message.TypeMessage; import observers.ObserverInputMessage; -public class FileTransferSendingThread extends Thread{ +public class FileTransferSendingThread extends Thread { private SocketChannel sockTransfert; private File file; private ObserverInputMessage obsInput; - public FileTransferSendingThread(int port, File fileToSend, ObserverInputMessage obs) throws IOException { + /** + * Create the thread that will send one file during a file transfer. This allows + * users to write in the chat while sending/receiving files. + * + * @param port The port on localhost to which the SocketChannel will connect. + * @param fileToSend The file to send. + * @param o The observer to notify once the file is fully received. + * @throws IOException if the socket's creation fails. + */ + public FileTransferSendingThread(int port, File fileToSend, ObserverInputMessage o) throws IOException { SocketChannel sock = SocketChannel.open(); SocketAddress addr = new InetSocketAddress(port); sock.connect(addr); this.sockTransfert = sock; this.file = fileToSend; - this.obsInput = obs; + this.obsInput = o; } public void run() { try { + // Buffer to send a chunk of the file ByteBuffer fileData = ByteBuffer.allocate(4 * FileTransferUtils.KB_SIZE); + // OutputStream to write the first object which is a message containing the name + // and size of the file ObjectOutputStream outputFileInformation = new ObjectOutputStream( this.sockTransfert.socket().getOutputStream()); + // Channel to read the data of the file FileChannel fileReader = FileChannel.open(Paths.get(file.getPath())); String str = file.getName() + ";" + file.getTotalSpace(); - // Send file datas (name + size); + // Send file data (name + size); outputFileInformation.writeObject(new MessageFichier(TypeMessage.FICHIER, str, "")); while (fileReader.read(fileData) > 0) { @@ -53,11 +66,14 @@ public class FileTransferSendingThread extends Thread{ } fileReader.close(); - + outputFileInformation.close(); + + // Process the message to display (thumbnails in the case of images) and notify + // the observer Message mUpdate = FileTransferUtils.processMessageToDisplay(this.file); mUpdate.setSender("Moi"); - this.obsInput.update(this, mUpdate); - + this.obsInput.updateInput(this, mUpdate); + } catch (IOException | MauvaisTypeMessageException e) { e.printStackTrace(); } finally { @@ -68,7 +84,5 @@ public class FileTransferSendingThread extends Thread{ } } } - - } diff --git a/POO/src/communication/filetransfer/FileTransferServer.java b/POO/src/communication/filetransfer/FileTransferServer.java index e16db67..3e8530d 100644 --- a/POO/src/communication/filetransfer/FileTransferServer.java +++ b/POO/src/communication/filetransfer/FileTransferServer.java @@ -8,21 +8,32 @@ import java.nio.channels.SocketChannel; import observers.ObserverInputMessage; - public class FileTransferServer extends Thread { private ServerSocketChannel sockFTListen; private int nbFile; private ObserverInputMessage obsInput; - - public FileTransferServer(int nbFile, ObserverInputMessage obs) throws UnknownHostException, IOException { + /** + * Create a server to transfer one or several files. A new socket and thread is + * created for each file to receive. The files are received one by one to save + * bandwidth and avoid issues. + * + * @param nbFile The number of file to receive. + * @param o The observer to notify once a file is fully received. + * @throws UnknownHostException + * @throws IOException + */ + public FileTransferServer(int nbFile, ObserverInputMessage o) throws UnknownHostException, IOException { this.sockFTListen = ServerSocketChannel.open(); this.sockFTListen.socket().bind(new InetSocketAddress(0)); this.nbFile = nbFile; - this.obsInput = obs; + this.obsInput = o; } + /** + * @return The port binded to the ServerSocketChannel. + */ public int getPort() { return this.sockFTListen.socket().getLocalPort(); } diff --git a/POO/src/communication/filetransfer/FileTransferUtils.java b/POO/src/communication/filetransfer/FileTransferUtils.java index cd561ac..ee7535a 100644 --- a/POO/src/communication/filetransfer/FileTransferUtils.java +++ b/POO/src/communication/filetransfer/FileTransferUtils.java @@ -23,55 +23,76 @@ import messages.Message.TypeMessage; public class FileTransferUtils { + // Relative path of the folder where are put the received files protected static final String DOWNLOADS_RELATIVE_PATH = "../downloads/"; - protected static final ArrayList IMAGE_EXTENSIONS = new ArrayList(List.of("tif","tiff","bmp","jpg","jpeg","gif", "png", "eps", "svg")); + protected static final ArrayList IMAGE_EXTENSIONS = new ArrayList( + List.of("tif", "tiff", "bmp", "jpg", "jpeg", "gif", "png", "eps", "svg")); protected static final int KB_SIZE = 1024; - - + + /** + * Process what to display on the chat depending on the file sent/received. A + * thumbnail will be created in the case of an image, A String with the file's + * name will be created otherwise. + * + * @param file The file to process. + * @return A message of which content is either a thumbnail or the file's name. + * @throws IOException + */ protected static MessageFichier processMessageToDisplay(File file) throws IOException { String nameFile = file.getName(); - String extension = processFileExtension(nameFile); + String extension = processFileExtension(nameFile); TypeMessage type; String contenu; - - if(IMAGE_EXTENSIONS.contains(extension)) { + + if (IMAGE_EXTENSIONS.contains(extension)) { type = TypeMessage.IMAGE; BufferedImage img = ImageIO.read(file); - contenu = encodeImage(createThumbnail(img), extension) ; - - }else { + contenu = encodeImage(createThumbnail(img), extension); + + } else { type = TypeMessage.FICHIER; contenu = nameFile; } - + try { - //return new MessageFichier(type, contenu, extension); return new MessageFichier(type, contenu, extension); } catch (MauvaisTypeMessageException e) { - System.out.println("Should never go in"); e.printStackTrace(); } return null; } - + + /** + * @param fileName The name of the file (with its extension). + * @return The extension of the file. + */ protected static String processFileExtension(String fileName) { String extension = ""; - + int i = fileName.indexOf('.'); if (i >= 0 || i != -1) { - extension = fileName.substring(i+1).toLowerCase(); + extension = fileName.substring(i + 1).toLowerCase(); } return extension; } - - - private static BufferedImage createThumbnail(BufferedImage image){ + + /** + * @param image A buffered image. + * @return A thumbnail of the image. + */ + private static BufferedImage createThumbnail(BufferedImage image) { float w = image.getWidth(); - float ratio = (w > 150) ? (150F/w) : 1; + float ratio = (w > 150) ? (150F / w) : 1; BufferedImage scaled = scale(image, ratio); return scaled; } - + + /** + * @param img A buffered image. + * @param extension The extension of the image. + * @return The base64 encoded string corresponding to the given image. + * @throws IOException + */ private static String encodeImage(BufferedImage img, String extension) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ImageIO.write(img, extension, bos); @@ -79,16 +100,21 @@ public class FileTransferUtils { bos.close(); return imgString; } - - + + /** + * @param imageString The base64 encoded string of an image. + * @return A buffered image corresponding to the given base64 encoded string. + * @throws IOException + */ public static BufferedImage decodeImage(String imageString) throws IOException { byte[] imgData = Base64.getDecoder().decode(imageString); InputStream is = new ByteArrayInputStream(imgData); - BufferedImage img = ImageIO.read(is); - is.close(); - return img; + BufferedImage img = ImageIO.read(is); + is.close(); + return img; } - + + // Used to scale an image with a given ratio private static BufferedImage scale(BufferedImage source, double ratio) { int w = (int) (source.getWidth() * ratio); int h = (int) (source.getHeight() * ratio); @@ -109,11 +135,15 @@ public class FileTransferUtils { BufferedImage image = gc.createCompatibleImage(w, h); return image; } - + + /** + * Create the folder with the path "DOWNLOADS_RELATIVE_PATH" if it does not + * exist. + */ public static void createDownloads() { File downloads = new File(FileTransferUtils.DOWNLOADS_RELATIVE_PATH); - - if(!downloads.exists()) { + + if (!downloads.exists()) { downloads.mkdir(); } } diff --git a/POO/src/communication/tcp/TCPClient.java b/POO/src/communication/tcp/TCPClient.java index aeaa6c0..76e445a 100644 --- a/POO/src/communication/tcp/TCPClient.java +++ b/POO/src/communication/tcp/TCPClient.java @@ -5,63 +5,86 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.net.InetAddress; import java.net.Socket; import observers.ObserverInputMessage; import observers.ObserverSocketState; -import messages.MauvaisTypeMessageException; import messages.Message; public class TCPClient { private Socket sockTCP; private ObjectOutputStream output; + private ObjectInputStream input; private TCPInputThread inputThread; - + /** + * Create a TCP client from an existing socket. It will ensure the transmission + * of messages during a session. Two ObjectStream are created in order to + * read/write messages. The ObjectInputStream is given to a thread to read + * continuously the incoming message and allowing the user to write a message + * anytime. + * + * @param sockTCP + * @throws IOException + */ public TCPClient(Socket sockTCP) throws IOException { this.sockTCP = sockTCP; - + this.output = new ObjectOutputStream(sockTCP.getOutputStream()); - ObjectInputStream input = new ObjectInputStream(sockTCP.getInputStream()); - this.inputThread = new TCPInputThread(input); - } - - - public TCPClient(InetAddress addr, int port) throws IOException { - this(new Socket(addr, port)); - + this.input = new ObjectInputStream(sockTCP.getInputStream()); + this.inputThread = new TCPInputThread(this.input); } + /** + * Start the thread that will continuously read from the socket. + */ public void startInputThread() { this.inputThread.start(); } - - public void sendMessage(Message message) throws IOException, MauvaisTypeMessageException { - System.out.println("dans write"); + /** + * Send a message by writing it in the ObjectOutputStream of the socket + * + * @param message + * @throws IOException + */ + public void sendMessage(Message message) throws IOException { this.output.writeObject(message); } - + /** + * Set the observer to notify when a message is received + * + * @param o The observer + */ public void setObserverInputThread(ObserverInputMessage o) { this.inputThread.setObserverInputMessage(o); } - + /** + * Set the observer to notify when the session is closed/the communication is + * broken. + * + * @param o The observer + */ public void setObserverSocketState(ObserverSocketState o) { this.inputThread.setObserverSocketState(o); } - - + + /** + * Method used when the session is over. Set all attributes' references to null, + * interrupt the inputThread and close the streams and the socket. + */ public void destroyAll() { try { if (!this.sockTCP.isClosed()) { + this.inputThread.setObserverSocketState(null); + this.inputThread.interrupt(); + this.input.close(); this.output.close(); this.sockTCP.close(); - this.inputThread.setObserverSocketState(null); } this.inputThread = null; this.sockTCP = null; diff --git a/POO/src/communication/tcp/TCPInputThread.java b/POO/src/communication/tcp/TCPInputThread.java index 5287bf5..ff45b4c 100644 --- a/POO/src/communication/tcp/TCPInputThread.java +++ b/POO/src/communication/tcp/TCPInputThread.java @@ -12,7 +12,12 @@ public class TCPInputThread extends Thread { private ObserverInputMessage obsInput; private ObserverSocketState obsState; - public TCPInputThread(ObjectInputStream input) { + /** + * Create the thread used to read the messages + * + * @param input The ObjectInputStream to read data from + */ + protected TCPInputThread(ObjectInputStream input) { this.input = input; this.running = true; } @@ -22,10 +27,9 @@ public class TCPInputThread extends Thread { while (this.running) { try { - - System.out.println("dans read"); - Object o = this.input.readObject(); - this.obsInput.update(this, o); + Object o = this.input.readObject(); + // Notify the observer a message was received + this.obsInput.updateInput(this, o); } catch (IOException | ClassNotFoundException e) { this.interrupt(); @@ -37,34 +41,35 @@ public class TCPInputThread extends Thread { @Override public void interrupt() { - - try { - //Stop the thread - this.running = false; - //Close the stream and the socket - this.input.close(); - - if(this.obsState != null) { - //Send an update to the controller - this.obsState.updateSocketState(this, true); - } - - - //Set every attribute to null so they're collected by the GC - this.obsInput = null; - this.obsState = null; - this.input = null; - - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + // Stop the thread + this.running = false; + // Close the stream and the socket + + if (this.obsState != null) { + // Send an update to the controller + this.obsState.updateSocketState(this, true); } + + // Set every attribute to null so they're collected by the GC + this.obsInput = null; + this.obsState = null; + this.input = null; } - + + /** + * Set the observer to notify when a message is received + * + * @param o The observer + */ protected void setObserverInputMessage(ObserverInputMessage o) { this.obsInput = o; } - + + /** + * Set the observer to notify when the session is cut/closed. + * + * @param o The observer + */ protected void setObserverSocketState(ObserverSocketState o) { this.obsState = o; } diff --git a/POO/src/communication/tcp/TCPServer.java b/POO/src/communication/tcp/TCPServer.java index bdbb16f..6ceb657 100644 --- a/POO/src/communication/tcp/TCPServer.java +++ b/POO/src/communication/tcp/TCPServer.java @@ -8,36 +8,45 @@ import java.net.UnknownHostException; import observers.ObserverInputMessage; - public class TCPServer extends Thread { - - //**** - public static int PORT_SERVER = 7000; private ServerSocket sockListenTCP; - private ObserverInputMessage obs; - + private ObserverInputMessage obsInput; + + /** + * Create a TCP Server on the specified port. It will listen continuously for + * connections in order to create new sessions between users. + * + * @param port The port on which the server will listen + * @throws UnknownHostException + * @throws IOException + */ public TCPServer(int port) throws UnknownHostException, IOException { this.sockListenTCP = new ServerSocket(port, 50, InetAddress.getLocalHost()); } - + @Override public void run() { - System.out.println("TCP running"); Socket sockAccept; - while(true) { + while (true) { try { sockAccept = this.sockListenTCP.accept(); - this.obs.update(this, sockAccept); + + // Notify the observer of the new connexion + this.obsInput.updateInput(this, sockAccept); } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } - + } } - - public void addObserver(ObserverInputMessage obs) { - this.obs = obs; + + /** + * Set the observer to notify when a new connection is made. + * + * @param o The observer + */ + public void addObserver(ObserverInputMessage o) { + this.obsInput = o; } } diff --git a/POO/src/communication/udp/CommunicationUDP.java b/POO/src/communication/udp/CommunicationUDP.java index 91eec76..12021e9 100644 --- a/POO/src/communication/udp/CommunicationUDP.java +++ b/POO/src/communication/udp/CommunicationUDP.java @@ -2,7 +2,6 @@ package communication.udp; import java.io.IOException; import java.net.InetAddress; -import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; @@ -13,18 +12,23 @@ import observers.ObserverUserList; public class CommunicationUDP extends Thread { - // **** - protected static int PORT_SERVEUR = 3000; - // **** - protected static int PORT_CLIENT = 2000; - private UDPClient client; private UDPServer server; private int portServer; private ArrayList portOthers; private ArrayList users = new ArrayList(); - private ObserverUserList observer; + private ObserverUserList obsList; + /** + * Create the object that will manage the userlist and contain a UDPClient and a + * UDPServer. Since the applications will run on localhost, it needs to know + * every UDPServer ports used in order to replicate a broadcast behaviour. + * + * @param portClient The port number for the UDPClient + * @param portServer The port number for the UDPServer + * @param portsOther The port numbers for every other application's UDPServer + * @throws IOException + */ public CommunicationUDP(int portClient, int portServer, int[] portsOther) throws IOException { this.portServer = portServer; this.portOthers = this.getArrayListFromArray(portsOther); @@ -33,14 +37,13 @@ public class CommunicationUDP extends Thread { this.client = new UDPClient(portClient); } - // **** - public CommunicationUDP() throws SocketException, UnknownHostException { - this.portServer = PORT_SERVEUR; - this.server = new UDPServer(portServer, this); - this.server.start(); - this.client = new UDPClient(PORT_CLIENT); - } - + /** + * Create an ArrayList from the int[] list of every servers' ports and + * remove the port of this application UDPServer. + * + * @param ports The UDPServer port numbers. + * @return An ArrayList without the port of this UDPServer. + */ private ArrayList getArrayListFromArray(int ports[]) { ArrayList tmp = new ArrayList(); for (int port : ports) { @@ -51,42 +54,83 @@ public class CommunicationUDP extends Thread { return tmp; } - public void setObserver(ObserverUserList obs) { - this.observer = obs; + /** + * Set the observer to notify when the userList is updated. + * + * @param obs The observer + */ + public void setObserver(ObserverUserList o) { + this.obsList = o; } - // -------------- USER LIST UPDATE FUNCTION --------------// + // -------------- USER LIST UPDATE METHODS -------------- // - protected synchronized void addUser(String idClient, String pseudoClient, InetAddress ipClient, int port) - throws IOException { - users.add(new Utilisateur(idClient, pseudoClient, ipClient, port)); + /** + * Add a new user to the userlist and notify the observer. + * + * @param idClient + * @param pseudoClient + * @param ipClient + * @param port + * @throws UnknownHostException + */ + protected synchronized void addUser(String idUser, String pseudoUser, InetAddress ipUser, int portTCPServer) + throws UnknownHostException { + users.add(new Utilisateur(idUser, pseudoUser, ipUser, portTCPServer)); this.sendUpdate(); } - protected synchronized void changePseudoUser(String idClient, String pseudoClient, InetAddress ipClient, int port) { - int index = getIndexFromID(idClient); - users.get(index).setPseudo(pseudoClient); - this.sendUpdate(); + /** + * Change the pseudo of an user and notify the observer if it exists in the + * userlist. Do nothing otherwise. + * + * @param idClient + * @param pseudoClient + * @param ipClient + * @param port + */ + protected synchronized void changePseudoUser(String idUser, String pseudoUser, InetAddress ipUser, + int portTCPServer) { + int index = getIndexFromID(idUser); + if (index != -1) { + users.get(index).setPseudo(pseudoUser); + this.sendUpdate(); + } + } - protected synchronized void removeUser(String idClient, String pseudoClient, InetAddress ipClient, int port) { - int index = getIndexFromID(idClient); + /** + * Remove an user from the userlist and notify the observer if it exists in the + * userlist. Do nothing otherwise. + * + * @param idUser + * @param pseudoUser + * @param ipUser + * @param portTCPServer + */ + protected synchronized void removeUser(String idUser, String pseudoUser, InetAddress ipUser, int portTCPServer) { + int index = getIndexFromID(idUser); if (index != -1) { users.remove(index); + this.sendUpdate(); } - this.sendUpdate(); + } - public void removeAll() { - int oSize = users.size(); - for (int i = 0; i < oSize; i++) { - users.remove(0); - } + public void removeAllUsers() { + this.users.clear(); } - // -------------- CHECKERS --------------// + // -------------- CHECKERS -------------- // + /** + * Check if there is an user in the list that has the given id. + * + * @param id The user's id. + * @return True if the user is in the list + * false otherwise. + */ protected boolean containsUserFromID(String id) { for (Utilisateur u : users) { if (u.getId().equals(id)) { @@ -96,6 +140,13 @@ public class CommunicationUDP extends Thread { return false; } + /** + * Check if there is an user in the list that has the given pseudo. + * + * @param pseudo The user's pseudo. + * @return True if the user is in the list + * false otherwise. + */ public boolean containsUserFromPseudo(String pseudo) { for (Utilisateur u : users) { if (u.getPseudo().toLowerCase().equals(pseudo)) { @@ -106,8 +157,15 @@ public class CommunicationUDP extends Thread { return false; } - // -------------- GETTERS --------------// + // -------------- GETTERS -------------- // + /** + * Return the user with the given pseudo if it exists in the list. + * + * @param pseudo The user's pseudo. + * @return The user if it exists in the list. + * null otherwise. + */ public Utilisateur getUserFromPseudo(String pseudo) { for (int i = 0; i < users.size(); i++) { if (users.get(i).getPseudo().equals(pseudo)) { @@ -117,6 +175,13 @@ public class CommunicationUDP extends Thread { return null; } + /** + * Return the index of the user with the given id if it exists in the list. + * + * @param id The user's id. + * @return The index if the user exists in the list. + * null otherwise + */ private int getIndexFromID(String id) { for (int i = 0; i < users.size(); i++) { if (users.get(i).getId().equals(id)) { @@ -126,81 +191,105 @@ public class CommunicationUDP extends Thread { return -1; } - // -------------- SEND MESSAGES --------------// + // -------------- SEND MESSAGES METHODS -------------- // + /** + * Send a message indicating this application's user is connected to every + * UDPServer. + * + * @throws UnknownHostException + * @throws IOException + */ public void sendMessageConnecte() throws UnknownHostException, IOException { - for (int port : this.portOthers) { - try { - this.client.sendMessageUDP_local(new MessageSysteme(Message.TypeMessage.JE_SUIS_CONNECTE), port, - InetAddress.getLocalHost()); - } catch (MauvaisTypeMessageException e) { - /* Si ça marche pas essayer là */} + + try { + Message msgOut = new MessageSysteme(Message.TypeMessage.JE_SUIS_CONNECTE); + for (int port : this.portOthers) { + + this.client.sendMessageUDP_local(msgOut, port); + } + } catch (MauvaisTypeMessageException e) { + e.printStackTrace(); } } - // Send the message "add,id,pseudo" to localhost on all the ports in - // "portOthers" - // This allows the receivers' agent (portOthers) to create or modify an entry - // with the - // data of this agent - // Typically used to notify of a name change + /** + * Send a message containing this application's user's data to every UDPServer. + * This method is used to first add this user in the userlist or update this + * user's pseudo. + * + * @throws UnknownHostException + * @throws IOException + */ public void sendMessageInfoPseudo() throws UnknownHostException, IOException { Utilisateur self = Utilisateur.getSelf(); try { - Message msgOut = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId(), self.getPort()); + Message msgOut = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId(), + self.getPort()); for (int port : this.portOthers) { - this.client.sendMessageUDP_local(msgOut, port, InetAddress.getLocalHost()); + this.client.sendMessageUDP_local(msgOut, port); } - } catch (Exception e) { + } catch (MauvaisTypeMessageException e) { e.printStackTrace(); } } - // Same, but on only one port - // Typically used to give your current name and id to a newly arrived host + /** + * Send a message containing this application's user's data to one user. This + * method is used to answer back when receiving a message with the type + * "JE_SUIS_CONNECTE" + * + * @param portOther The port on which the other user's UDPServer is listening + * @throws UnknownHostException + * @throws IOException + */ public void sendMessageInfoPseudo(int portOther) throws UnknownHostException, IOException { Utilisateur self = Utilisateur.getSelf(); try { Message msgOut = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId(), self.getPort()); - this.client.sendMessageUDP_local(msgOut, portOther, InetAddress.getLocalHost()); + this.client.sendMessageUDP_local(msgOut, portOther); } catch (MauvaisTypeMessageException e) { e.printStackTrace(); } } - // Send the message "del,id,pseudo" to localhost on all the ports in - // "portOthers" - // This allows the receivers' agent (portOthers) to delete the entry - // corresponding to this agent + /** + * Send a message indicating this application's user is disconnected to every + * UDPServer. + * + * @throws UnknownHostException + * @throws IOException + */ public void sendMessageDelete() throws UnknownHostException, IOException { Utilisateur self = Utilisateur.getSelf(); try { - - Message msgOut = new MessageSysteme(Message.TypeMessage.JE_SUIS_DECONNECTE, self.getPseudo(), self.getId(), self.getPort()); + + Message msgOut = new MessageSysteme(Message.TypeMessage.JE_SUIS_DECONNECTE, self.getPseudo(), self.getId(), + self.getPort()); for (int port : this.portOthers) { - this.client.sendMessageUDP_local(msgOut, port, InetAddress.getLocalHost()); + this.client.sendMessageUDP_local(msgOut, port); } - + } catch (MauvaisTypeMessageException e) { - + e.printStackTrace(); } } + // -------------- OTHERS -------------- // + + /** + * Notify the observer with the updated list + */ private void sendUpdate() { - if(this.observer != null) { - this.observer.updateList(this, users); + if (this.obsList != null) { + this.obsList.updateList(this, users); } } - public void destroyAll() { - this.client.destroyAll(); - this.server.interrupt(); - } - } diff --git a/POO/src/communication/udp/UDPClient.java b/POO/src/communication/udp/UDPClient.java index 54839df..c796e6b 100644 --- a/POO/src/communication/udp/UDPClient.java +++ b/POO/src/communication/udp/UDPClient.java @@ -4,40 +4,52 @@ import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; -import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; import messages.*; -public class UDPClient { +class UDPClient { private DatagramSocket sockUDP; - private InetAddress broadcast; - + + /** + * Create an UDP client on the specified port. It will be used to notify the + * other users of this application's user state (Connected/Disconnected/Pseudo + * changed). + * + * @param port + * @throws SocketException + * @throws UnknownHostException + */ public UDPClient(int port) throws SocketException, UnknownHostException { this.sockUDP = new DatagramSocket(port); - - InetAddress localHost = InetAddress.getLocalHost(); - NetworkInterface networkInterface = NetworkInterface.getByInetAddress(localHost); - this.broadcast = networkInterface.getInterfaceAddresses().get(0).getBroadcast(); - System.out.println(this.broadcast); - System.out.println(InetAddress.getLocalHost()); } - - - //Send a message casted as string to the specified port on localhost - protected void sendMessageUDP_local(Message message, int port, InetAddress clientAddress) throws IOException { - String messageString= message.toString(); - DatagramPacket outpacket = new DatagramPacket(messageString.getBytes(), messageString.length(), clientAddress, port); + + /** + * Send a message to the specified port on localhost. + * + * @param message + * @param port + * @throws IOException + */ + protected void sendMessageUDP_local(Message message, int port) throws IOException { + sendMessageUDP(message, port, InetAddress.getLocalHost()); + } + + /** + * Send a message to the given address on the specified port. + * + * @param message + * @param port + * @param clientAddress + * @throws IOException + */ + private void sendMessageUDP(Message message, int port, InetAddress clientAddress) throws IOException { + String messageString = message.toString(); + DatagramPacket outpacket = new DatagramPacket(messageString.getBytes(), messageString.length(), clientAddress, + port); this.sockUDP.send(outpacket); - } - - protected void destroyAll() { - this.sockUDP.close(); - this.sockUDP = null; - this.broadcast = null; - } - + } \ No newline at end of file diff --git a/POO/src/communication/udp/UDPServer.java b/POO/src/communication/udp/UDPServer.java index 529d303..ee117dd 100644 --- a/POO/src/communication/udp/UDPServer.java +++ b/POO/src/communication/udp/UDPServer.java @@ -8,13 +8,22 @@ import java.net.SocketException; import main.Utilisateur; import messages.*; -public class UDPServer extends Thread { +class UDPServer extends Thread { private DatagramSocket sockUDP; private CommunicationUDP commUDP; private byte[] buffer; private boolean running; + + /** + * Create an UDP Server on the specified port. It will be used to read the + * other users states (Connected/Disconnected/Pseudo). + * + * @param port + * @param commUDP + * @throws SocketException + */ public UDPServer(int port, CommunicationUDP commUDP) throws SocketException { this.running = true; this.commUDP = commUDP; @@ -27,12 +36,14 @@ public class UDPServer extends Thread { while (this.running) { try { - + + //When a datagram is received, converts its data in a Message DatagramPacket inPacket = new DatagramPacket(buffer, buffer.length); this.sockUDP.receive(inPacket); String msgString = new String(inPacket.getData(), 0, inPacket.getLength()); Message msg = Message.stringToMessage(msgString); + //Depending on the type of the message switch (msg.getTypeMessage()) { case JE_SUIS_CONNECTE: @@ -40,34 +51,38 @@ public class UDPServer extends Thread { int portClient = inPacket.getPort(); int portServer = portClient+1; + + //Answer back with this application's user data this.commUDP.sendMessageInfoPseudo(portServer); } break; case INFO_PSEUDO: + MessageSysteme m = (MessageSysteme) msg; - + + //Update the userlist with the data received (Add the user or update it) if (this.commUDP.containsUserFromID(m.getId())) { this.commUDP.changePseudoUser(m.getId(), m.getPseudo(), inPacket.getAddress(), m.getPort()); } else { - this.commUDP.addUser(m.getId(), m.getPseudo(), inPacket.getAddress(), m.getPort()); - System.out.println(m.getId() + ", " + m.getPseudo()); } break; case JE_SUIS_DECONNECTE: - this.commUDP.removeUser(((MessageSysteme) msg).getId(), ((MessageSysteme) msg).getPseudo(), - inPacket.getAddress(), inPacket.getPort()); + + MessageSysteme m2 = (MessageSysteme) msg; + //Remove the user from the userlist + this.commUDP.removeUser(m2.getId(), m2.getPseudo(), inPacket.getAddress(), m2.getPort()); break; - default: // Others types of messages are ignored because they are supposed to be - // transmitted by TCP and not UDP + //Do nothing + default: } } catch (IOException e) { - System.out.println("receive exception"); + e.printStackTrace(); } } diff --git a/POO/src/connexion/ControleurConnexion.java b/POO/src/connexion/ControleurConnexion.java index ffb4c4c..5de1cd2 100644 --- a/POO/src/connexion/ControleurConnexion.java +++ b/POO/src/connexion/ControleurConnexion.java @@ -13,6 +13,7 @@ import standard.VueStandard; public class ControleurConnexion implements ActionListener{ + //Controller state : either DEBUT at initialization or ID_OK if the user has signed in private enum Etat {DEBUT, ID_OK}; private VueConnexion vue; @@ -23,16 +24,22 @@ public class ControleurConnexion implements ActionListener{ private SQLiteManager sqlManager; private VueStandard vueStd; + + /** + * Create and initialize the object in charge of monitoring all actions depending on what the user do. + * + * @param vue : associated instance of VueConnexion + * @param numtest : on local mode, allows you to choose which port to use. Integer between 0 and 3 + * + */ public ControleurConnexion(VueConnexion vue, int numtest) { this.vue = vue; this.etat = Etat.DEBUT; this.username = ""; this.sqlManager = new SQLiteManager(0); this.vueStd = null; - //Pour les tests, changer pour un truc plus général quand on change CommunicationUDP - //Note : 3334 est le port du serveur de présence - int[] portServer = {2209, 2309, 2409, 2509, 3334}; + int[] portServer = {2209, 2309, 2409, 2509}; try { switch(numtest) { case 0 : @@ -54,14 +61,13 @@ public class ControleurConnexion implements ActionListener{ default : this.comUDP = new CommunicationUDP(2408, 2409, portServer); this.portTCP = 7040; - } -// + } } catch (IOException e) { - e.printStackTrace(); } } + @Override public void actionPerformed(ActionEvent e) { String pseudo; @@ -76,32 +82,24 @@ public class ControleurConnexion implements ActionListener{ inputOK = (res == 1); } catch (SQLException e2) { - e2.printStackTrace(); } if (inputOK) { this.etat=Etat.ID_OK; - //Envoi broadcast du message "JeSuisActif" et, attente du retour de la liste des utilisateurs actifs + //Broadcast "JE_SUIS_CONNECTE" message and waits for the other devices' answers try { comUDP.sendMessageConnecte(); - } catch (UnknownHostException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } catch (IOException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); + } catch (IOException e2) { } - + try { Thread.sleep(2); } catch (InterruptedException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); } - //Mise en place de la demande du pseudo + //setup pseudo ask this.vue.setConnexionInfo(""); this.vue.removePasswordPanel(); @@ -118,26 +116,22 @@ public class ControleurConnexion implements ActionListener{ else { pseudo = vue.getUsernameValue(); - //Recherche dans la liste locale des utilisateurs connectes, report sur inputOK + //Search in the local list of active users id the chosen pseudo is already in use inputOK = !this.comUDP.containsUserFromPseudo(pseudo); if(pseudo.equals("")) { this.vue.setConnexionInfo("Votre pseudonyme doit contenir au moins 1 caratère"); }else if (inputOK) { - //Reglage de l'utilisateur + //setup Utilisateur "self" static attribute try { Utilisateur.setSelf(this.username, pseudo, "localhost", this.portTCP); } catch (UnknownHostException e2) { - // TODO Auto-generated catch block - e2.printStackTrace(); } - //Broadcast du pseudo + //broadcast new pseudo try { this.comUDP.sendMessageInfoPseudo(); } catch (UnknownHostException e1) { - e1.printStackTrace(); } catch (IOException e1) { - e1.printStackTrace(); } try { @@ -145,7 +139,6 @@ public class ControleurConnexion implements ActionListener{ this.vue.setVisible(false); this.setVueStandard(); } catch (IOException e1) { - e1.printStackTrace(); } } else this.vue.setConnexionInfo("Ce nom est déjà utilisé, veuillez en choisir un autre"); @@ -153,6 +146,12 @@ public class ControleurConnexion implements ActionListener{ } + // ----- SETTING & RESETTING VIEW ----- // + + /** + * Create a new VueStandard instance and give it the hand. + * + */ private void setVueStandard() throws IOException { if(this.vueStd == null) { this.vueStd = new VueStandard("Standard", this.comUDP, this.portTCP, this.sqlManager, this.vue); @@ -163,7 +162,11 @@ public class ControleurConnexion implements ActionListener{ this.vueStd.setVisible(true); } } - + + /** + * Restore the associated instance of VueConnexion to its initial state + * + */ private void resetView() { this.etat = Etat.DEBUT; this.vue.addPasswordPanel(); diff --git a/POO/src/connexion/VueConnexion.java b/POO/src/connexion/VueConnexion.java index e5ec22d..fc46a9b 100644 --- a/POO/src/connexion/VueConnexion.java +++ b/POO/src/connexion/VueConnexion.java @@ -1,6 +1,6 @@ package connexion; -//Importe les librairies +//Import librairies import java.awt.*; import javax.swing.*; @@ -10,7 +10,8 @@ import main.Vue; public class VueConnexion extends Vue { private static final long serialVersionUID = 1L; - //Elements vue + + //Graphical elements private JButton boutonValider; private JTextField inputUsername; private JPasswordField inputPassword; @@ -20,40 +21,53 @@ public class VueConnexion extends Vue { private JPanel main; private JPanel panelPassword; - //Controleur + //Controller private ControleurConnexion controle; - //penser à enlever le numtest + /** + * Create and initialize the view SWING window that will be used during the connection phase. + * Doing so, it also creates the controller that will monitor all changes during the connection phase. + * + * @param numtest : to be passed down to the controller + * + */ public VueConnexion(int numtest) { super("Connexion"); controle = new ControleurConnexion(this, numtest); - //Creation fenetre + //Window creation this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(400, 300); this.setLocationRelativeTo(null); - //Ajout elements + //Adding graphical elements ajouterElements(); - //Regle le bouton par défaut + //Setting default button this.getRootPane().setDefaultButton(boutonValider); this.inputUsername.setText(SQLiteManager.hardcodedNames[numtest]+numtest); this.inputPassword.setText("aze1$"+SQLiteManager.hardcodedNames[numtest].charAt(0)+numtest); - //Affiche la fenetre + //Display window this.setVisible(true); } + + // ----- ADDING ELEMENTS TO AND REMOVING ELEMENTS FROM THE MAIN WINDOW ----- // + + + /** + * Add various graphical elements to the main window : used when initializing the window + */ private void ajouterElements() { - //Creation panel + //Create a panel main = new JPanel(new GridLayout(4,1)); JPanel panelUsername = new JPanel(new GridLayout(1, 2)); this.panelPassword = new JPanel(new GridLayout(1, 2)); - //Cree les elements + //Create various elements this.connexionInfo = new JLabel(""); this.inputUsername = new JTextField(); @@ -70,10 +84,10 @@ public class VueConnexion extends Vue { boutonValider = new JButton("Valider"); - //Le controleur guette les evenements du bouton + //Make it so the controller is monitoring the button boutonValider.addActionListener(controle); - //Ajoute les elements + panelUsername.add(this.labelUsername); panelUsername.add(this.inputUsername); @@ -82,7 +96,6 @@ public class VueConnexion extends Vue { this.panelPassword.add(this.inputPassword); - main.add(connexionInfo); main.add(panelUsername); main.add(this.panelPassword); @@ -91,29 +104,6 @@ public class VueConnexion extends Vue { this.add(main); } - - //Getters et setters - protected void setConnexionInfo(String text) { - this.connexionInfo.setText(text); - } - - protected void setTextUsernameField(String text) { - this.labelUsername.setText(text); - } - - protected String getUsernameValue() { - return this.inputUsername.getText(); - } - - protected char[] getPasswordValue() { - return this.inputPassword.getPassword(); - } - - - protected void resetUsernameField() { - this.inputUsername.setText(""); - } - protected void removePasswordPanel() { this.main.remove(2); } @@ -122,8 +112,60 @@ public class VueConnexion extends Vue { this.main.add(this.panelPassword, 2); } + + //----- GETTERS -----// + + /** + * Returns the current value of the field inputUsername + * + * @return current value of the field inputUsername as String + */ + protected String getUsernameValue() { + return this.inputUsername.getText(); + } + + /** + * Returns the current value of the field inputPassword + * + * @return current value of the field inputPassword as String + */ + protected char[] getPasswordValue() { + return this.inputPassword.getPassword(); + } + + + //----- SETTERS -----// + + /** + * Set a displayed message that will give the user information (for example if they entered a wrong password) + * + * @param text : message to display as String + */ + protected void setConnexionInfo(String text) { + this.connexionInfo.setText(text); + } + + /** + * Set the label for the inputUsername fiel + * + * @param text : label to display as String + */ + protected void setTextUsernameField(String text) { + this.labelUsername.setText(text); + } + + /** + * Empty the inputUsername text field + */ + protected void resetUsernameField() { + this.inputUsername.setText(""); + } + + /** + * Empty the inputPassword text field + */ protected void resetPasswordField() { this.inputPassword.setText(""); } -} +} \ No newline at end of file diff --git a/POO/src/database/SQLiteCreateTables.java b/POO/src/database/SQLiteCreateTables.java index 45aa061..754931e 100644 --- a/POO/src/database/SQLiteCreateTables.java +++ b/POO/src/database/SQLiteCreateTables.java @@ -6,57 +6,101 @@ import java.sql.Statement; class SQLiteCreateTables { - + /** + * Create the user table if it does not exist in the database. + * An user is characterized by : + * - an unique username, + * - a password salt, + * - a database_key salt, + * - a password hash encrypted by the database_key, + * - the encrypted database_key, + * - the initialization vector used to encrypt the database key. + * + * @param connec The opened connection to the database. + * @throws SQLException + */ protected static void createTableUser(Connection connec) throws SQLException { String createTableUser = "CREATE TABLE IF NOT EXISTS user (\r\n" + " id INTEGER PRIMARY KEY AUTOINCREMENT,\r\n" + " username VARCHAR (50) NOT NULL\r\n" + " UNIQUE ON CONFLICT ROLLBACK,\r\n" - + " pwd_salt BLOB,\r\n" + + " pwd_salt BLOB,\r\n" + " db_datakey_salt BLOB,\r\n" - + " encrypted_pwd_hashsalt BLOB,\r\n" + + " encrypted_pwd_hashsalt BLOB,\r\n" + " encrypted_db_datakey BLOB,\r\n" - + " iv_datakey BLOB\r\n" - + ");"; + + " iv_datakey BLOB\r\n" + ");"; Statement stmt = connec.createStatement(); stmt.execute(createTableUser); } + /** + * Create the conversation table if it does not exist in the database. + * A conversation is characterized by : + * - the id of the user who sends the messages, + * - the id of the user who receives the messages, + * - an initialization vector used to encrypt the conversation's messages. + * + * During an session between two users, two conversations are created. + * + * @param connec The opened connection to the database. + * @throws SQLException + */ protected static void createTableConversation(Connection connec) throws SQLException { String createTableConversation = "CREATE TABLE IF NOT EXISTS conversation (\r\n" + " id_conversation INTEGER PRIMARY KEY AUTOINCREMENT,\r\n" - + " id_emetteur INTEGER REFERENCES user (id) \r\n" - + " NOT NULL,\r\n" - + " id_recepteur INTEGER REFERENCES user (id) \r\n" - + " NOT NULL,\r\n" - +" iv_conversation BLOB NOT NULL" - + ");"; + + " id_sender INTEGER REFERENCES user (id) \r\n" + " NOT NULL,\r\n" + + " id_receiver INTEGER REFERENCES user (id) \r\n" + " NOT NULL,\r\n" + + " iv_conversation BLOB NOT NULL" + ");"; Statement stmt = connec.createStatement(); stmt.execute(createTableConversation); } + /** + * Create the message table if it does not exist in the database. + * A message is characterized by : + * - the id of the conversation it belongs, + * - the id of its type, + * - its content, + * - the date when it was emitted, + * - its extension if it is a file (text or image). + * + * @param connec The opened connection to the database. + * @throws SQLException + */ protected static void createTableMessage(Connection connec) throws SQLException { String createTableMessage = "CREATE TABLE IF NOT EXISTS message (\r\n" + " id_message INTEGER PRIMARY KEY AUTOINCREMENT,\r\n" + " id_conversation INTEGER REFERENCES conversation (id_conversation) \r\n" + " NOT NULL,\r\n" + " id_type INTEGER REFERENCES type (id_type) \r\n" - + " NOT NULL,\r\n" + + " NOT NULL,\r\n" + " content BLOB,\r\n" - + " date INTEGER NOT NULL,\r\n" - + " extension VARCHAR (20) \r\n" - + ");\r\n"; + + " date INTEGER NOT NULL,\r\n" + + " extension VARCHAR (20) \r\n" + ");\r\n"; Statement stmt = connec.createStatement(); stmt.execute(createTableMessage); } + /** + * Create the (message) type table if it does not exist and insert the different + * types of message in the database. + * A type is characterized by : + * - a label. + * + * This table only exists because the type "enumeration" does not exist in SQLite. + * It is a static table that contains the different types of message stored in the database. + * + * @param connec The opened connection to the database. + * @throws SQLException + */ protected static void createTableType(Connection connec) throws SQLException { - String createTableType = "CREATE TABLE IF NOT EXISTS type (\r\n" + " id_type INTEGER PRIMARY KEY,\r\n" + String createTableType = "CREATE TABLE IF NOT EXISTS type (\r\n" + + " id_type INTEGER PRIMARY KEY,\r\n" + " label VARCHAR (20) NOT NULL\r\n" + ");"; Statement stmt = connec.createStatement(); diff --git a/POO/src/database/SQLiteEncprytion.java b/POO/src/database/SQLiteEncprytion.java deleted file mode 100644 index 38c0916..0000000 --- a/POO/src/database/SQLiteEncprytion.java +++ /dev/null @@ -1,84 +0,0 @@ -package database; - - -import java.util.Base64; -import java.util.Random; - -import javax.crypto.*; -import javax.crypto.spec.*; -import java.security.*; -import java.security.spec.*; - -class SQLiteEncprytion { - - private static final Random RANDOM = new SecureRandom(); - private static final int ITERATIONS = 10000; - private static final int KEY_LENGTH = 256; - protected static final String encryptAlgorithm = "AES/CBC/PKCS5Padding"; - - protected static byte[] getNextSalt() { - byte[] salt = new byte[24]; - RANDOM.nextBytes(salt); - return salt; - } - - - protected static byte[] hash(char[] password, byte[] salt) { - return SQLiteEncprytion.getKey(password, salt).getEncoded(); - } - - - protected static SecretKey getKey(char[] password, byte[] salt) { - PBEKeySpec saltpwd = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH); - - try { - SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); - SecretKey tmp = skf.generateSecret(saltpwd); - SecretKey key = new SecretKeySpec(tmp.getEncoded(), "AES"); - return key; - } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { - e.printStackTrace(); - } finally { - saltpwd.clearPassword(); - } - return null; - } - - - public static IvParameterSpec generateIv() { - byte[] iv = new byte[16]; - new SecureRandom().nextBytes(iv); - return new IvParameterSpec(iv); - } - - - protected static byte[] encrypt(String algorithm, byte[] input, SecretKey key, IvParameterSpec iv) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException{ - Cipher cipher = Cipher.getInstance(algorithm); - cipher.init(Cipher.ENCRYPT_MODE, key, iv); - byte[] cipherText = cipher.doFinal(input); - return Base64.getEncoder().encode(cipherText); - } - - - protected static byte[] decryptByte(String algorithm, byte[] cipherText, SecretKey key, IvParameterSpec iv) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException{ - Cipher cipher = Cipher.getInstance(algorithm); - cipher.init(Cipher.DECRYPT_MODE, key, iv); - byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText)); - return plainText; - } - - protected static String decryptString(String algorithm, byte[] cipherText, SecretKey key, IvParameterSpec iv) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - return new String(SQLiteEncprytion.decryptByte(algorithm, cipherText, key, iv) ); - } - - - public static byte[] keyToByte(SecretKey key) { - return Base64.getEncoder().encode(key.getEncoded()); - } - - - public static SecretKey byteToKey(byte[] encodedKey) { - byte[] decodedKey = Base64.getDecoder().decode(encodedKey); - return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); - } -} diff --git a/POO/src/database/SQLiteEncryption.java b/POO/src/database/SQLiteEncryption.java new file mode 100644 index 0000000..c7fb761 --- /dev/null +++ b/POO/src/database/SQLiteEncryption.java @@ -0,0 +1,158 @@ +package database; + +import java.util.Base64; +import java.util.Random; + +import javax.crypto.*; +import javax.crypto.spec.*; +import java.security.*; +import java.security.spec.*; + +class SQLiteEncryption { + + private static final Random RANDOM = new SecureRandom(); + private static final int ITERATIONS = 10000; + private static final int KEY_LENGTH = 256; + protected static final String encryptAlgorithm = "AES/CBC/PKCS5Padding"; + + /** + * Return a 24 bytes salt. + * + * @return The salt in a byte array. + */ + protected static byte[] getNextSalt() { + byte[] salt = new byte[24]; + RANDOM.nextBytes(salt); + return salt; + } + + /** + * Return the hash of the given password with the given salt. + * + * @param password + * @param salt + * @return The hash in a byte array. + */ + protected static byte[] hash(char[] password, byte[] salt) { + return SQLiteEncryption.getKey(password, salt).getEncoded(); + } + + /** + * Return a secret key generated with the given password and salt. + * + * @param password + * @param salt + * @return The secret key. + */ + protected static SecretKey getKey(char[] password, byte[] salt) { + PBEKeySpec saltpwd = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH); + + try { + SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + SecretKey tmp = skf.generateSecret(saltpwd); + SecretKey key = new SecretKeySpec(tmp.getEncoded(), "AES"); + return key; + } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { + e.printStackTrace(); + } finally { + saltpwd.clearPassword(); + } + return null; + } + + /** + * Return a 16 bytes Initialization vector. + * + * @return The Initialization vector. + */ + protected static IvParameterSpec generateIv() { + byte[] iv = new byte[16]; + RANDOM.nextBytes(iv); + return new IvParameterSpec(iv); + } + + /** + * Encrypt the given input (byte array) with the given algorithm, secretKey and + * initialization vector. + * + * + * @param algorithm + * @param input + * @param key + * @param iv + * @return The encrypted input in a byte array. + * + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws InvalidAlgorithmParameterException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + protected static byte[] encrypt(String algorithm, byte[] input, SecretKey key, IvParameterSpec iv) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, + InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + Cipher cipher = Cipher.getInstance(algorithm); + cipher.init(Cipher.ENCRYPT_MODE, key, iv); + byte[] cipherText = cipher.doFinal(input); + return Base64.getEncoder().encode(cipherText); + } + + /** + * Decrypt the given input (byte array) with the given algorithm, secretKey and + * initialization vector. + * + * @param algorithm + * @param input + * @param key + * @param iv + * @return The decrypted input in a byte array. + * + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws InvalidAlgorithmParameterException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + protected static byte[] decryptByte(String algorithm, byte[] input, SecretKey key, IvParameterSpec iv) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, + InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + Cipher cipher = Cipher.getInstance(algorithm); + cipher.init(Cipher.DECRYPT_MODE, key, iv); + byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(input)); + return plainText; + } + + /** + * Decrypt the given input (byte array) with the given algorithm, secretKey and + * initialization vector. + * + * @param algorithm + * @param input + * @param key + * @param iv + * @return The decrypted input as a String. + * + * @throws InvalidKeyException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidAlgorithmParameterException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + protected static String decryptString(String algorithm, byte[] input, SecretKey key, IvParameterSpec iv) + throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, + InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + return new String(SQLiteEncryption.decryptByte(algorithm, input, key, iv)); + } + + protected static byte[] keyToByte(SecretKey key) { + return Base64.getEncoder().encode(key.getEncoded()); + } + + protected static SecretKey byteToKey(byte[] encodedKey) { + byte[] decodedKey = Base64.getDecoder().decode(encodedKey); + return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); + } +} diff --git a/POO/src/database/SQLiteManager.java b/POO/src/database/SQLiteManager.java index fcf0764..60a3135 100644 --- a/POO/src/database/SQLiteManager.java +++ b/POO/src/database/SQLiteManager.java @@ -1,6 +1,5 @@ package database; -import java.io.File; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -19,7 +18,6 @@ import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; - import main.Utilisateur; import messages.MauvaisTypeMessageException; import messages.Message; @@ -30,50 +28,64 @@ import messages.MessageFichier; public class SQLiteManager { private static final String DATABASE_RELATIVE_PATH = "../database"; - - public static String[] hardcodedNames = {"Olivia","Liam","Benjamin","Sophia","Charlotte","Noah","Elijah","Isabella", - "Oliver","Emma","William","Amelia","Evelyn","James","Mia","Ava","Lucas","Mason","Ethan","Harper"}; - + + public static String[] hardcodedNames = { "Olivia", "Liam", "Benjamin", "Sophia", "Charlotte", "Noah", "Elijah", + "Isabella", "Oliver", "Emma", "William", "Amelia", "Evelyn", "James", "Mia", "Ava", "Lucas", "Mason", + "Ethan", "Harper" }; + private Connection connec; private int numDatabase; private SecretKey dbDataKey; + + /** + * Create the object that will interact with the database. Each time a + * SQLiteManager is created, it creates the tables for the application to work + * if they do not exist already. In the context of a local usage of the + * application, this constructor takes a number that is used to manage different + * database's files (see openConnection() ). + * + * @param numDatabase + */ public SQLiteManager(int numDatabase) { this.numDatabase = numDatabase; this.openConnection(); try { - + SQLiteCreateTables.createTableUser(this.connec); SQLiteCreateTables.createTableConversation(this.connec); SQLiteCreateTables.createTableType(this.connec); SQLiteCreateTables.createTableMessage(this.connec); - + } catch (SQLException e) { this.closeConnection(); - File db = new File("../database"+this.numDatabase+".db"); - if(db.delete()) { - System.out.println("supp"); - }else { - System.out.println("no supp"); - } e.printStackTrace(); } this.closeConnection(); } + + // -------------- CONNECTION METHODS -------------- // + + /** + * Open a connection to the file DATABASE_RELATIVE_PATH+this.numDatabase+".db" + */ private void openConnection() { - String url = "jdbc:sqlite:"+ DATABASE_RELATIVE_PATH + this.numDatabase + ".db"; + String url = "jdbc:sqlite:" + DATABASE_RELATIVE_PATH + this.numDatabase + ".db"; try { this.connec = DriverManager.getConnection(url); -// System.out.println("Connection to bdd established"); } catch (SQLException e) { System.out.println(e.getMessage()); } } + + /** + * Close this object connection, if it exists + */ private void closeConnection() { try { if (this.connec != null) { @@ -84,36 +96,52 @@ public class SQLiteManager { } } + - public int insertAllMessages(ArrayList messages, String usernameSender, String usernameReceiver) throws SQLException{ + // -------------- INSERT METHODS -------------- // + + /** + * Insert a collection of message in the database. They all correspond to the + * conversation between the sender and the receiver. The users and conversations + * are inserted in the database if they do not exist when this method is called. + * + * @param messages + * @param usernameSender + * @param usernameReceiver + * @return The number of messages inserted + * @throws SQLException + */ + public int insertAllMessages(ArrayList messages, String usernameSender, String usernameReceiver) + throws SQLException { int nbRows = 0; this.openConnection(); - + int idSender = this.getIDUser(usernameSender); int idReceiver = this.getIDUser(usernameReceiver); - - if(idSender == -1) { + + if (idSender == -1) { this.insertUser(usernameSender); idSender = this.getIDUser(usernameSender); } - - if(idReceiver == -1) { + + if (idReceiver == -1) { this.insertUser(usernameReceiver); - idReceiver = this.getIDUser(usernameReceiver); + idReceiver = this.getIDUser(usernameReceiver); } - + int idConversation = getIDConversation(idSender, idReceiver); - - if(idConversation == -1) { + + if (idConversation == -1) { this.insertConversation(idSender, idReceiver); idConversation = getIDConversation(idSender, idReceiver); } - + IvParameterSpec ivConversation = this.getIvConversation(idConversation); - + + // Disable autocommit to efficiently insert all the messages. this.connec.setAutoCommit(false); - - for(Message m : messages) { + + for (Message m : messages) { try { nbRows += this.insertMessage(idConversation, m, ivConversation); } catch (SQLException e) { @@ -121,103 +149,66 @@ public class SQLiteManager { this.connec.rollback(); } } - + + // Commit once all the messages are inserted this.connec.commit(); - + this.closeConnection(); - - //System.out.println("Nombre de message(s) insérée(s) : " + nbRows); - + return nbRows; } + + /** + * Insert a message corresponding to a given conversation (respresented by its + * id). The message content is first encrypted with the database key and the + * given iv. + * + * @param idConversation + * @param m + * @param iv + * @return The number of rows inserted (it should be 1). + * @throws SQLException + */ + private int insertMessage(int idConversation, Message m, IvParameterSpec iv) throws SQLException { - public ArrayList getHistoriquesMessages(String usernameOther, String pseudoOther) throws SQLException { - - this.openConnection(); - - - ArrayList messages = new ArrayList(); - - String usernameSelf = Utilisateur.getSelf().getId(); - - int idSelf = this.getIDUser(usernameSelf); - int idOther = this.getIDUser(usernameOther); - - int idConversationSelf = this.getIDConversation(idSelf, idOther); - int idConversationOther = this.getIDConversation(idOther, idSelf); - IvParameterSpec ivConversation = this.getIvConversation(idConversationSelf); -// String str = "datetime(d1,'unixepoch','localtime')"; - - - String getHistoriqueRequest = "SELECT id_conversation, id_type, content, date, extension " - + "FROM message " - + "WHERE id_conversation IN (?,?) " - + "ORDER by date"; - - PreparedStatement prepStmt = this.connec.prepareStatement(getHistoriqueRequest); - prepStmt.setInt(1, idConversationSelf); - prepStmt.setInt(2, idConversationOther); - ResultSet res = prepStmt.executeQuery(); - - //Retrieve the messages one by one - //Create the appropriate message object depending on the type and sender/receiver - //and add the message in the list - while(res.next()) { - int idType = res.getInt("id_type"); - String type = this.getType(idType); - - String content = null; - try { - content = this.bytesToStringContent(res.getBytes("content"), ivConversation); - } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException - | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException - | SQLException e1) { - //System.out.println("erreur déchiffrement"); - } - - Message message = null; - String extension; - - if(!type.equals("")) { - try { - switch(type) { - case "text": - message = new MessageTexte(TypeMessage.TEXTE, content); - break; - case "file": - extension = res.getString("extension"); - message = new MessageFichier(TypeMessage.FICHIER, content, extension); - break; - default: - extension = res.getString("extension"); - message = new MessageFichier(TypeMessage.IMAGE, content, extension); - } - - } catch (MauvaisTypeMessageException e) { - e.printStackTrace(); - } - } - - if(res.getInt("id_conversation") == idConversationSelf) { - message.setSender("Moi"); - }else{ - message.setSender(pseudoOther); - } - message.setDateMessage(res.getString("date")); - if(content != null) { - messages.add(message); - } - + String dateMessage = m.getDateMessage(); + String extension = this.processExtension(m); + String type = this.processMessageType(m); + int idType = this.getIDType(type); + + byte[] content = null; + + try { + content = this.stringToBytesContent(m, iv); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException + | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { + + e.printStackTrace(); } - - - this.closeConnection(); - - return messages; + + String insertMessageRequest = "INSERT INTO message(id_conversation, id_type, content, date, extension) " + + "VALUES (?, ?, ?, ?, ?);"; + + PreparedStatement prepStmt = this.connec.prepareStatement(insertMessageRequest); + prepStmt.setInt(1, idConversation); + prepStmt.setInt(2, idType); + prepStmt.setBytes(3, content); + prepStmt.setString(4, dateMessage); + prepStmt.setString(5, extension); + + int nbRows = prepStmt.executeUpdate(); + + return nbRows; } - + + /** + * Insert an user in the database with only its username. + * + * @param username + * @throws SQLException + */ private void insertUser(String username) throws SQLException { String insertUserRequest = "INSERT INTO user (username) " + "VALUES (?);"; @@ -225,8 +216,141 @@ public class SQLiteManager { prepStmt.setString(1, username); prepStmt.executeUpdate(); } + + /** + * Insert the two conversations corresponding to a session between two users + * (they both can be sender and receiver). + * + * @param idUser1 + * @param idUser2 + * @throws SQLException + */ + private void insertConversation(int idUser1, int idUser2) throws SQLException { + String insertConversationRequest = "INSERT INTO conversation (id_sender, id_receiver, iv_conversation) " + + "VALUES " + "(?, ?, ?)," + "(?, ?, ?);"; + + byte[] ivConversation = SQLiteEncryption.generateIv().getIV(); + + PreparedStatement prepStmt = this.connec.prepareStatement(insertConversationRequest); + prepStmt.setInt(1, idUser1); + prepStmt.setInt(2, idUser2); + prepStmt.setBytes(3, ivConversation); + prepStmt.setInt(4, idUser2); + prepStmt.setInt(5, idUser1); + prepStmt.setBytes(6, ivConversation); + + prepStmt.executeUpdate(); + } + + // -------------- GET METHODS -------------- // + + /** + * Get the message record between two users. In context, it only needs the + * username and the id of the other user since we can get this application's + * user's data with Utilisateur.getSelf(). + * + * @param usernameOther + * @param pseudoOther + * @return the messages previously exchanged in chronological order or null if + * there is none. + * @throws SQLException + */ + public ArrayList getMessageRecord(String usernameOther, String pseudoOther) throws SQLException { + + this.openConnection(); + + ArrayList messages = new ArrayList(); + + String usernameSelf = Utilisateur.getSelf().getId(); + + // Get the ids from the usernames + int idSelf = this.getIDUser(usernameSelf); + int idOther = this.getIDUser(usernameOther); + + // Get the two conversations corresponding to the exchanges between the two + // users + int idConversationSelf = this.getIDConversation(idSelf, idOther); + int idConversationOther = this.getIDConversation(idOther, idSelf); + IvParameterSpec ivConversation = this.getIvConversation(idConversationSelf); + + // Get all the messages + String getHistoriqueRequest = "SELECT id_conversation, id_type, content, date, extension " + "FROM message " + + "WHERE id_conversation IN (?,?) " + "ORDER by date"; + + PreparedStatement prepStmt = this.connec.prepareStatement(getHistoriqueRequest); + prepStmt.setInt(1, idConversationSelf); + prepStmt.setInt(2, idConversationOther); + ResultSet res = prepStmt.executeQuery(); + + // Process the messages one by one + // Create the appropriate message object depending on the type and + // sender/receiver + // and add the message in the list + while (res.next()) { + int idType = res.getInt("id_type"); + String type = this.getType(idType); + + String content = null; + try { + // Decrypt the message's content with the database key and the conversation's + // iv. + content = this.bytesToStringContent(res.getBytes("content"), ivConversation); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException + | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException + | SQLException e1) { + } + + Message message = null; + String extension; + + if (!type.equals("")) { + try { + switch (type) { + case "text": + message = new MessageTexte(TypeMessage.TEXTE, content); + break; + case "file": + extension = res.getString("extension"); + message = new MessageFichier(TypeMessage.FICHIER, content, extension); + break; + default: + extension = res.getString("extension"); + message = new MessageFichier(TypeMessage.IMAGE, content, extension); + } + + } catch (MauvaisTypeMessageException e) { + e.printStackTrace(); + } + } + + if (res.getInt("id_conversation") == idConversationSelf) { + message.setSender("Moi"); + } else { + message.setSender(pseudoOther); + } + message.setDateMessage(res.getString("date")); + if (content != null) { + messages.add(message); + } + + } + + this.closeConnection(); + + return messages; + } + + + /** + * Return the id of the user with the given username if it exists in the + * database. + * + * @param username + * @return The id of the user or -1 if he does not exist in the database. + * @throws SQLException + */ private int getIDUser(String username) throws SQLException { String getIDRequest = "SELECT id " + " FROM user" + " WHERE username = ? ;"; @@ -241,29 +365,19 @@ public class SQLiteManager { } - - private void insertConversation(int idSender, int idReceiver) throws SQLException { - String insertConversationRequest = "INSERT INTO conversation (id_emetteur, id_recepteur, iv_conversation) " + "VALUES " - + "(?, ?, ?)," - + "(?, ?, ?);"; - - byte[] ivConversation = SQLiteEncprytion.generateIv().getIV(); - - PreparedStatement prepStmt = this.connec.prepareStatement(insertConversationRequest); - prepStmt.setInt(1, idSender); - prepStmt.setInt(2, idReceiver); - prepStmt.setBytes(3, ivConversation); - prepStmt.setInt(4, idReceiver); - prepStmt.setInt(5, idSender); - prepStmt.setBytes(6, ivConversation); - - prepStmt.executeUpdate(); - } - + /** + * Get the id of the conversation between two users (represented by their ids). + * + * @param idSender + * @param idReceiver + * @return The id of the conversation or -1 if the conversation does not exists + * in the database. + * @throws SQLException + */ private int getIDConversation(int idSender, int idReceiver) throws SQLException { - String getIDRequest = "SELECT id_conversation " + "FROM conversation " + "WHERE id_emetteur = ? " - + "AND id_recepteur = ? ;"; + String getIDRequest = "SELECT id_conversation " + "FROM conversation " + "WHERE id_sender = ? " + + "AND id_receiver = ? ;"; PreparedStatement prepStmt = this.connec.prepareStatement(getIDRequest); prepStmt.setInt(1, idSender); @@ -275,10 +389,20 @@ public class SQLiteManager { } return -1; } + + /** + * Get the initialization vector for a given conversation (represented by its + * id). + * + * @param idConversation + * @return The iv corresponding to the conversation or null if the conversation + * does not exist in the database. + * @throws SQLException + */ private IvParameterSpec getIvConversation(int idConversation) throws SQLException { String getIvRequest = "SELECT iv_conversation " + "FROM conversation " + "WHERE id_conversation = ?;"; - + PreparedStatement prepStmt = this.connec.prepareStatement(getIvRequest); prepStmt.setInt(1, idConversation); ResultSet res = prepStmt.executeQuery(); @@ -286,121 +410,170 @@ public class SQLiteManager { if (res.next()) { return new IvParameterSpec(res.getBytes("iv_conversation")); } - + return null; } - - + /** + * Get the id of the message type corresponding to the given label. + * + * @param label + * @return The id of the message type or -1 if it does not exist in the + * database. + * @throws SQLException + */ private int getIDType(String label) throws SQLException { String getIDRequest = "SELECT id_type FROM type WHERE label = ?;"; - + PreparedStatement prepStmt = this.connec.prepareStatement(getIDRequest); prepStmt.setString(1, label); - + ResultSet res = prepStmt.executeQuery(); - + if (res.next()) { return res.getInt("id_type"); } return -1; } + - + /** + * Get the label of the message type corresponding to the given id. + * + * @param idType + * @return The label of the message type or "" if it does not exist in the + * database. + * @throws SQLException + */ private String getType(int idType) throws SQLException { String getTypeRequest = "SELECT label FROM type WHERE id_type = ?;"; - + PreparedStatement prepStmt = this.connec.prepareStatement(getTypeRequest); prepStmt.setInt(1, idType); - + ResultSet res = prepStmt.executeQuery(); - - if(res.next()) { + + if (res.next()) { return res.getString("label"); } - + return ""; } + + // -------------- PROCESSING MESSAGE METHODS -------------- // - private byte[] stringToBytesContent(Message m, IvParameterSpec iv) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + /** + * Convert a message in bytes. First get the content of the message. Then + * encrypt it with the database_key and the given iv (initialization vector). + * + * @param m + * @param iv + * @return The bytes of the encrypted message's content. + * + * @throws InvalidKeyException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidAlgorithmParameterException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + private byte[] stringToBytesContent(Message m, IvParameterSpec iv) + throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, + InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { String content; if (m.getTypeMessage() == TypeMessage.TEXTE) { MessageTexte messageTxt = (MessageTexte) m; content = messageTxt.getContenu(); - }else { + } else { MessageFichier messageFichier = (MessageFichier) m; content = messageFichier.getContenu(); } - byte[] encryptedContent = SQLiteEncprytion.encrypt(SQLiteEncprytion.encryptAlgorithm, content.getBytes(), this.dbDataKey, iv); + byte[] encryptedContent = SQLiteEncryption.encrypt(SQLiteEncryption.encryptAlgorithm, content.getBytes(), + this.dbDataKey, iv); return encryptedContent; - - } - - private String bytesToStringContent(byte[] encryptedContent, IvParameterSpec iv) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - return SQLiteEncprytion.decryptString(SQLiteEncprytion.encryptAlgorithm, encryptedContent, this.dbDataKey, iv); + } + /** + * Convert bytes in a String. In this context, the bytes correspond to the + * encrypted content of a message. + * + * @param encryptedContent + * @param iv + * @return The String corresponding to the decrypted bytes. + * + * @throws InvalidKeyException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidAlgorithmParameterException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + private String bytesToStringContent(byte[] encryptedContent, IvParameterSpec iv) + throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, + InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + return SQLiteEncryption.decryptString(SQLiteEncryption.encryptAlgorithm, encryptedContent, this.dbDataKey, iv); + } + + + /** + * Process the type of a message + * + * @param m + * @return The type of the message + */ private String processMessageType(Message m) { switch (m.getTypeMessage()) { - case TEXTE: return "text"; - case FICHIER: return "file"; - case IMAGE: return "image"; - default: return ""; + case TEXTE: + return "text"; + case FICHIER: + return "file"; + case IMAGE: + return "image"; + default: + return ""; } } + + /** + * Process the extension of a message + * + * @param m + * @return The extension of the message. null if it is a simple text message. + */ private String processExtension(Message m) { - if(m.getTypeMessage() == TypeMessage.TEXTE) { + if (m.getTypeMessage() == TypeMessage.TEXTE) { return null; - }else { + } else { MessageFichier mFile = (MessageFichier) m; return mFile.getExtension(); } } + // -------------- USER SECURITY RELATED METHODS -------------- // + - private int insertMessage(int idConversation, Message m, IvParameterSpec iv) throws SQLException { - - - String dateMessage = m.getDateMessage(); - String extension = this.processExtension(m); - String type = this.processMessageType(m); - int idType = this.getIDType(type); - - byte[] content = null; - - try { - content = this.stringToBytesContent(m, iv); - } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException - | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { - - e.printStackTrace(); - } - - String insertMessageRequest = "INSERT INTO message(id_conversation, id_type, content, date, extension) " - + "VALUES (?, ?, ?, ?, ?);"; - - PreparedStatement prepStmt = this.connec.prepareStatement(insertMessageRequest); - prepStmt.setInt(1, idConversation); - prepStmt.setInt(2, idType); - prepStmt.setBytes(3, content); - prepStmt.setString(4, dateMessage); - prepStmt.setString(5, extension); - - int nbRows = prepStmt.executeUpdate(); - - return nbRows; - } - + /** + * Creates a new user in the database from a given username and password. The + * username is stored in plain text. An encrypted hash of the password is + * stored. The key used to encrypt the password's hash is itself encrypted then + * stored. Every other useful data to decrypt the key and compare the password's + * hash is stored as plaintext. + * + * Fail if a user with the given username already exists in the database. + * + * @param username + * @param password + */ public void createNewUserEncrypt(String username, String password) { - - - String algo = SQLiteEncprytion.encryptAlgorithm; - + + String algo = SQLiteEncryption.encryptAlgorithm; + KeyGenerator keyGen = null; try { keyGen = KeyGenerator.getInstance("AES"); @@ -408,37 +581,35 @@ public class SQLiteManager { } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } - - byte[] passwordSalt = SQLiteEncprytion.getNextSalt(); - byte[] dbDataKeySalt = SQLiteEncprytion.getNextSalt(); - - SecretKey dbDataKey = keyGen.generateKey(); - - SecretKey dbDataEncryptKey = SQLiteEncprytion.getKey(password.toCharArray(), dbDataKeySalt); - IvParameterSpec ivDbDataKey = SQLiteEncprytion.generateIv(); - - byte[] passwordHash = SQLiteEncprytion.hash(password.toCharArray(), passwordSalt); - + + byte[] passwordSalt = SQLiteEncryption.getNextSalt(); + byte[] dbDataKeySalt = SQLiteEncryption.getNextSalt(); + + SecretKey dbDataKey = keyGen.generateKey(); + + SecretKey dbDataEncryptKey = SQLiteEncryption.getKey(password.toCharArray(), dbDataKeySalt); + IvParameterSpec ivDbDataKey = SQLiteEncryption.generateIv(); + + byte[] passwordHash = SQLiteEncryption.hash(password.toCharArray(), passwordSalt); + byte[] dbDataKeyEncrypted = null; byte[] encryptedPasswordHash = null; - + try { - dbDataKeyEncrypted = SQLiteEncprytion.encrypt( - algo, SQLiteEncprytion.keyToByte(dbDataKey), dbDataEncryptKey, ivDbDataKey); - encryptedPasswordHash = SQLiteEncprytion.encrypt( - algo, passwordHash , dbDataKey, ivDbDataKey); - + dbDataKeyEncrypted = SQLiteEncryption.encrypt(algo, SQLiteEncryption.keyToByte(dbDataKey), dbDataEncryptKey, + ivDbDataKey); + encryptedPasswordHash = SQLiteEncryption.encrypt(algo, passwordHash, dbDataKey, ivDbDataKey); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { - // TODO Auto-generated catch block e.printStackTrace(); } - + this.openConnection(); - + String createUserRequest = "INSERT INTO user(username, pwd_salt, db_datakey_salt, encrypted_pwd_hashsalt, encrypted_db_datakey, iv_datakey) " + "VALUES (?, ?, ?, ?, ?, ?); "; - + PreparedStatement prepStmt = null; try { prepStmt = this.connec.prepareStatement(createUserRequest); @@ -448,115 +619,142 @@ public class SQLiteManager { prepStmt.setBytes(4, encryptedPasswordHash); prepStmt.setBytes(5, dbDataKeyEncrypted); prepStmt.setBytes(6, ivDbDataKey.getIV()); - + } catch (SQLException e) { - // TODO Auto-generated catch block e.printStackTrace(); } - + try { prepStmt.executeUpdate(); System.out.println("Utilisateur crée"); } catch (SQLException e) { System.out.println("Nom d'utilisateur déjà pris"); } - + this.closeConnection(); - + } + /** + * Check if a given password has the same hash (= they're the same) as the one + * stored in the database for a given username. * * @param username * @param password - * @return -1 if user do not exists, - * 0 if password incorrect, - * 1 if password correct + * @return -1 if user do not exists, 0 if password is incorrect, 1 if password + * is correct * @throws SQLException */ public int checkPwd(String username, char[] password) throws SQLException { - + this.openConnection(); - + String selectUserDataRequest = "SELECT pwd_salt, db_datakey_salt, encrypted_pwd_hashsalt, encrypted_db_datakey, iv_datakey " - + "FROM user " - + "WHERE username = ?;"; + + "FROM user " + "WHERE username = ?;"; PreparedStatement prepStmt; prepStmt = this.connec.prepareStatement(selectUserDataRequest); prepStmt.setString(1, username); - + ResultSet res = prepStmt.executeQuery(); - if(!res.next()) { + if (!res.next()) { return -1; } - + byte[] passwordSalt = res.getBytes("pwd_salt"); + + if (passwordSalt == null) { + return 0; + } + byte[] dbDataKeySalt = res.getBytes("db_datakey_salt"); - - SecretKey dbDataEncryptKey = SQLiteEncprytion.getKey(password, dbDataKeySalt); - IvParameterSpec iv = new IvParameterSpec(res.getBytes("iv_datakey")); - + + SecretKey dbDataEncryptKey = SQLiteEncryption.getKey(password, dbDataKeySalt); + + byte[] ivBytes = res.getBytes("iv_datakey"); + + if (ivBytes == null) { + return 0; + } + + IvParameterSpec iv = new IvParameterSpec(ivBytes); byte[] encryptedDbDataKey = res.getBytes("encrypted_db_datakey"); - - + SecretKey dbDataKey = null; try { - dbDataKey = SQLiteEncprytion.byteToKey( - SQLiteEncprytion.decryptByte(SQLiteEncprytion.encryptAlgorithm, encryptedDbDataKey, dbDataEncryptKey, iv) - ); + dbDataKey = SQLiteEncryption.byteToKey(SQLiteEncryption.decryptByte(SQLiteEncryption.encryptAlgorithm, + encryptedDbDataKey, dbDataEncryptKey, iv)); } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { - //System.out.println("Problème déchiffrement clé db"); } - - this.dbDataKey = dbDataKey; - + byte[] encryptedPasswordHash = res.getBytes("encrypted_pwd_hashsalt"); - - - byte[] passwordHash = SQLiteEncprytion.hash(password, passwordSalt); - + + byte[] passwordHash = SQLiteEncryption.hash(password, passwordSalt); + this.closeConnection(); - - boolean checkHash = this.checkHashPwd(passwordHash ,encryptedPasswordHash, dbDataKey, iv); - if(checkHash) { + + boolean checkHash = this.checkHashPwd(passwordHash, encryptedPasswordHash, dbDataKey, iv); + if (checkHash) { + + // Set the database key to be used in future encryptions if the given password is + // correct. + this.dbDataKey = dbDataKey; return 1; } - return 0; + + return 0; } - - private boolean checkHashPwd(byte[] passwordHash, byte[] encryptedPasswordHash, SecretKey dbDataKey, IvParameterSpec iv) { - + + /** + * Check if two given hash are the same once the second one has been decrypted + * with the given key and iv. + * + * @param passwordHash + * @param encryptedPasswordHash + * @param dbDataKey + * @param iv + * @return true if the first hash is equal to the second one once decrypted, + * false otherwise. + * + */ + private boolean checkHashPwd(byte[] passwordHash, byte[] encryptedPasswordHash, SecretKey dbDataKey, + IvParameterSpec iv) { + byte[] expectedHash = "".getBytes(); try { - expectedHash = SQLiteEncprytion.decryptByte(SQLiteEncprytion.encryptAlgorithm, encryptedPasswordHash, dbDataKey, iv); + expectedHash = SQLiteEncryption.decryptByte(SQLiteEncryption.encryptAlgorithm, encryptedPasswordHash, + dbDataKey, iv); } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { } - - - - if (passwordHash.length != expectedHash.length) return false; - for (int i = 0; i < passwordHash.length; i++) { - if (passwordHash[i] != expectedHash[i]) return false; - } - return true; + + if (passwordHash.length != expectedHash.length) + return false; + for (int i = 0; i < passwordHash.length; i++) { + if (passwordHash[i] != expectedHash[i]) + return false; + } + return true; } + + // Main to create 20 users in the database with the given number public static void main(String[] args) { - String[] hardcodedNames = {"Olivia","Liam","Benjamin","Sophia","Charlotte","Noah","Elijah","Isabella", - "Oliver","Emma","William","Amelia","Evelyn","James","Mia","Ava","Lucas","Mason","Ethan","Harper"}; - + String[] hardcodedNames = { "Olivia", "Liam", "Benjamin", "Sophia", "Charlotte", "Noah", "Elijah", "Isabella", + "Oliver", "Emma", "William", "Amelia", "Evelyn", "James", "Mia", "Ava", "Lucas", "Mason", "Ethan", + "Harper" }; + String pwdPrefix = "aze1$"; - + SQLiteManager sqlManager = new SQLiteManager(0); - - for(int i=0; i userList); } diff --git a/POO/src/session/ControleurSession.java b/POO/src/session/ControleurSession.java index 3516fa1..9f3eb2d 100644 --- a/POO/src/session/ControleurSession.java +++ b/POO/src/session/ControleurSession.java @@ -38,8 +38,23 @@ public class ControleurSession implements ActionListener, ObserverInputMessage, private ArrayList messagesOut; private SQLiteManager sqlManager; private ArrayList files; - - protected ControleurSession(VueSession vue, Socket socketComm, String idOther, String pseudoOther, SQLiteManager sqlManager) throws IOException { + + /** + * Create the controller for this session. It will manage all the objects used + * to send/receive messages and files as well as the ones interacting with the + * database. It will handle the actions performed on the view and call the + * appropriate methods to display messages and data on the view. + * + * @param vue The corresponding view + * @param socketComm The socket used to send/receive messages + * @param idOther The other user's id + * @param pseudoOther The other user's pseudo + * @param sqlManager The SQLManager instance to retrieve/insert + * users,conversations,messages from/into the database + * @throws IOException + */ + protected ControleurSession(VueSession vue, Socket socketComm, String idOther, String pseudoOther, + SQLiteManager sqlManager) throws IOException { this.vue = vue; this.tcpClient = new TCPClient(socketComm); this.tcpClient.setObserverInputThread(this); @@ -47,176 +62,247 @@ public class ControleurSession implements ActionListener, ObserverInputMessage, this.tcpClient.startInputThread(); this.messagesIn = new ArrayList(); this.messagesOut = new ArrayList(); - + this.idOther = idOther; this.pseudoOther = pseudoOther; - + this.sqlManager = sqlManager; - + this.files = new ArrayList(); } - // ---------- ACTION LISTENER OPERATIONS ----------// + + // ---------- ACTION LISTENER OPERATIONS ---------- // @Override public void actionPerformed(ActionEvent e) { - //Quand le bouton envoyer est presse + // If the button "Envoyer" is pressed if ((JButton) e.getSource() == this.vue.getButtonEnvoyer()) { String messageContent = this.vue.getInputedText(); System.out.println(messageContent); - - if(!this.files.isEmpty()) { + + if (!this.files.isEmpty()) { this.processSelectedFiles(messageContent); - if(!this.files.isEmpty()) { + if (!this.files.isEmpty()) { this.askFileTransfer(); - + this.vue.resetZoneSaisie(); messageContent = ""; } } - - - //If the text field is not empty + + // If the text field is not empty if (!messageContent.equals("")) { - - //Retrieve the date and prepare the messages to send/display + + // Retrieve the date and prepare the messages to send/display MessageTexte messageOut = null; - + try { messageOut = new MessageTexte(TypeMessage.TEXTE, messageContent); messageOut.setSender(Utilisateur.getSelf().getPseudo()); } catch (MauvaisTypeMessageException e2) { e2.printStackTrace(); } - - + try { - this.tcpClient.sendMessage(messageOut); - } catch (MauvaisTypeMessageException | IOException e1) { - // TODO Auto-generated catch block + this.tcpClient.sendMessage(messageOut); + } catch (IOException e1) { e1.printStackTrace(); } - + messageOut.setSender("Moi"); this.vue.appendMessage(messageOut); this.vue.resetZoneSaisie(); - + this.messagesOut.add(messageOut); } } - - if((JButton) e.getSource() == this.vue.getButtonImportFile()) { + + // If the button "Importer" is pressed + if ((JButton) e.getSource() == this.vue.getButtonImportFile()) { + + // Display a file chooser to select one or several files JFileChooser fc = new JFileChooser(); fc.setMultiSelectionEnabled(true); int returVal = fc.showDialog(this.vue, "Importer"); - - - if(returVal == JFileChooser.APPROVE_OPTION) { + + // If the user clicked on "Importer", + // Retrieve all the files he clicked on. + // The files are stored in this.files + // and their names are display in the ChatInput. + if (returVal == JFileChooser.APPROVE_OPTION) { File[] files = fc.getSelectedFiles(); Collections.addAll(this.files, files); - for(File file : files) { + for (File file : files) { this.vue.appendInputedText(file.getName()); this.vue.appendInputedText(";"); } } - + } } + + // ---------- KEY LISTENER METHODS ---------- // @Override public void keyTyped(KeyEvent e) { - // TODO Auto-generated method stub - } + @Override public void keyPressed(KeyEvent e) { - if(e.getKeyCode() == KeyEvent.VK_ENTER) { - if(!e.isShiftDown()) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + if (!e.isShiftDown()) { this.vue.getButtonEnvoyer().doClick(); } - - } + + } } + @Override public void keyReleased(KeyEvent e) { - // TODO Auto-generated method stub - } + - - protected ArrayList getHistorique(){ + // ---------- OTHERS ---------- // + + /** + * Create and send the message to ask for a file transfer. + */ + private void askFileTransfer() { try { - ArrayList historique = this.sqlManager.getHistoriquesMessages(idOther, pseudoOther); + MessageFichier messageOut = new MessageFichier(TypeMessage.FICHIER_INIT, "" + this.files.size(), ""); + this.tcpClient.sendMessage(messageOut); + } catch (MauvaisTypeMessageException | IOException e1) { + e1.printStackTrace(); + } + } + + + /** + * Create and send the answer with the port on which the FileTransferServer is + * listening. + * + * @param port + */ + private void answerFileTransfer(int port) { + try { + MessageFichier messageOut = new MessageFichier(TypeMessage.FICHIER_ANSWER, "" + port, ""); + this.tcpClient.sendMessage(messageOut); + } catch (MauvaisTypeMessageException | IOException e1) { + e1.printStackTrace(); + } + } + + + /** + * Retrieve the files' names from the given input using ";" as a separator + * Removes the files whose names are missing. + * + * This method is used to check if a file's name has been deleted/overwritten in + * the ChatInput. Indeed, the only way to cancel the import of a file is by + * deleting its name from the ChatInput. + * + * @param input + */ + private void processSelectedFiles(String input) { + String[] tmp = input.split(";"); + ArrayList potentialFiles = new ArrayList(); + Collections.addAll(potentialFiles, tmp); + + for (File file : this.files) { + if (!potentialFiles.contains(file.getName())) { + this.files.remove(file); + } + } + } + + + /** + * Retrieve the messages previously exchanged between the current user of the + * application and the other user of this session + * + * @return The ArrayList of all previous messages + */ + protected ArrayList getHistorique() { + try { + ArrayList historique = this.sqlManager.getMessageRecord(idOther, pseudoOther); return historique; } catch (SQLException e) { e.printStackTrace(); return new ArrayList(); - + } } + - private void processSelectedFiles(String input) { - String[] tmp = input.split(";"); - ArrayList potentialFiles = new ArrayList(); - Collections.addAll(potentialFiles, tmp); - - for(File file: this.files) { - if(!potentialFiles.contains(file.getName()) ) { - this.files.remove(file); - } - } - } - - private void askFileTransfer() { + /** + * Method used when the session is over. Insert every message exchanged in the + * database, set all attributes' references to null, and call destroyAll() on + * the TCPClient. + */ + protected void destroyAll() { + String idSelf = Utilisateur.getSelf().getId(); + String idOther = this.idOther; + try { - MessageFichier messageOut = new MessageFichier(TypeMessage.FICHIER_INIT, ""+this.files.size(), ""); - this.tcpClient.sendMessage(messageOut); - } catch (MauvaisTypeMessageException | IOException e1) { - e1.printStackTrace(); + this.sqlManager.insertAllMessages(messagesOut, idSelf, idOther); + this.sqlManager.insertAllMessages(messagesIn, idOther, idSelf); + } catch (SQLException e) { + e.printStackTrace(); } + + this.vue = null; + this.tcpClient.destroyAll(); + this.tcpClient = null; } + - private void answerFileTransfer(int port) { - try { - MessageFichier messageOut = new MessageFichier(TypeMessage.FICHIER_ANSWER, ""+port, ""); - this.tcpClient.sendMessage(messageOut); - } catch (MauvaisTypeMessageException | IOException e1) { - e1.printStackTrace(); - } - } - - //Methode appelee quand l'inputStream de la socket de communication recoit des donnees + // ---------- OBSERVERS ---------- // + + // Method called when a message is received from the TCP socket @Override - public void update(Object o, Object arg) { + public void updateInput(Object o, Object arg) { Message message = (Message) arg; - switch(message.getTypeMessage()) { + + switch (message.getTypeMessage()) { + + // If it is a simple text message, display it case TEXTE: System.out.println(message.toString()); this.vue.appendMessage(message); this.messagesIn.add(message); break; + + // If it is an image, display a thumbnail case IMAGE: this.vue.appendImage(message); - - if(message.getSender().equals("Moi")) { + + if (message.getSender().equals("Moi")) { this.messagesOut.add(message); - }else { + } else { this.messagesIn.add(message); } break; + + // If it is a file, display a message saying whether it has been sent/received. case FICHIER: this.vue.appendMessage(message); - - if(message.getSender().equals("Moi")) { + + if (message.getSender().equals("Moi")) { this.messagesOut.add(message); - }else { + } else { this.messagesIn.add(message); } break; + + // If it is a demand for a file transfer, create a new FileTransferServer, start + // it + // and answer back with "FICHIER_ANSWER" message containing the port of the + // server. case FICHIER_INIT: try { MessageFichier mFichier = (MessageFichier) arg; @@ -225,52 +311,42 @@ public class ControleurSession implements ActionListener, ObserverInputMessage, int port = fts.getPort(); fts.start(); this.answerFileTransfer(port); - + } catch (IOException e) { e.printStackTrace(); } break; + + // If it is an answer for a file transfer, create a FilteTransferClient + // with the port received and the path of the file(s) to send, + // and send the files. case FICHIER_ANSWER: try { MessageFichier mFichier = (MessageFichier) arg; int port = Integer.parseInt(mFichier.getContenu()); - + @SuppressWarnings("unchecked") - FileTransferClient ftc = new FileTransferClient(port ,(ArrayList) this.files.clone(), this); - + FileTransferClient ftc = new FileTransferClient(port, (ArrayList) this.files.clone(), this); + ftc.sendFiles(); this.files.clear(); } catch (IOException | InterruptedException e) { e.printStackTrace(); } - + break; + + // Do nothing default: } - + } + // If the other user closes the session or the communication is broken + // Disable the view (TextArea, Buttons..) and display a message @Override public void updateSocketState(Object o, Object arg) { - this.vue.endSession(this.pseudoOther); - } - - protected void destroyAll() { - String idSelf = Utilisateur.getSelf().getId(); - String idOther = this.idOther; - - try { - this.sqlManager.insertAllMessages(messagesOut, idSelf, idOther); - this.sqlManager.insertAllMessages(messagesIn, idOther, idSelf); - } catch (SQLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - - this.vue = null; - this.tcpClient.destroyAll(); - this.tcpClient = null; + this.vue.endSession(this.pseudoOther); } } \ No newline at end of file diff --git a/POO/src/session/VueSession.java b/POO/src/session/VueSession.java index 98b6e96..6f50bd2 100644 --- a/POO/src/session/VueSession.java +++ b/POO/src/session/VueSession.java @@ -39,9 +39,6 @@ import messages.Message.TypeMessage; public class VueSession extends JPanel { - /** - * - */ private static final long serialVersionUID = 1L; private JButton sendMessage; @@ -58,12 +55,7 @@ public class VueSession extends JPanel { this.setBorder(new EmptyBorder(5, 5, 5, 5)); this.setLayout(new BorderLayout(0, 0)); - this.chatInput = new JTextArea(); - this.chatInput.setColumns(10); - this.chatInput.setLineWrap(true); - this.chatInput.setWrapStyleWord(true); - this.chatInput.addKeyListener(this.c); - + // Create the display zone this.chatWindow = new JTextPane(); this.chatWindow.setEditable(false); this.chatWindow.setEditorKit(new WrapEditorKit()); @@ -71,21 +63,32 @@ public class VueSession extends JPanel { JScrollPane chatScroll = new JScrollPane(this.chatWindow); chatScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + // Create the input zone + this.chatInput = new JTextArea(); + this.chatInput.setColumns(10); + this.chatInput.setLineWrap(true); + this.chatInput.setWrapStyleWord(true); + this.chatInput.addKeyListener(this.c); + JPanel bottom = new JPanel(); bottom.setLayout(new BorderLayout(0, 0)); - // remap "ENTER" to "none" to avoid "\n" in the input area after sending message + // Remap "ENTER" to "none" to avoid "\n" in the input area when pressing "ENTER" + // to send a message KeyStroke enter = KeyStroke.getKeyStroke("ENTER"); this.chatInput.getInputMap().put(enter, "none"); KeyStroke shiftEnter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK); this.chatInput.getInputMap().put(shiftEnter, "insert-break"); - this.importFile = new JButton("Importer.."); - this.importFile.addActionListener(this.c); - + // Create a scroller to be able to send messages of several lines JScrollPane inputScroll = new JScrollPane(this.chatInput); inputScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + // Import file button + this.importFile = new JButton("Importer.."); + this.importFile.addActionListener(this.c); + + // Send message button this.sendMessage = new JButton("Envoyer"); this.sendMessage.addActionListener(this.c); @@ -93,6 +96,7 @@ public class VueSession extends JPanel { bottom.add(inputScroll, BorderLayout.CENTER); bottom.add(this.sendMessage, BorderLayout.EAST); + // Add the components to the view this.add(chatScroll, BorderLayout.CENTER); this.add(bottom, BorderLayout.SOUTH); @@ -102,32 +106,30 @@ public class VueSession extends JPanel { } + // -------------- GETTERS -------------- // + protected JButton getButtonEnvoyer() { return this.sendMessage; } + protected JButton getButtonImportFile() { return this.importFile; } + protected String getInputedText() { return this.chatInput.getText(); } - - protected void appendInputedText(String str) { - this.chatInput.append(str); - } - - - protected void resetZoneSaisie() { - this.chatInput.setText(""); - - } - - private void setCaretToEnd() { - this.chatWindow.setCaretPosition(this.chatWindow.getDocument().getLength()); - } + + // -------------- DISPLAY METHODS -------------- // + + /** + * Append the given string to the ChatWindow. + * + * @param str + */ protected void appendString(String str) { try { Document doc = this.chatWindow.getDocument(); @@ -137,12 +139,16 @@ public class VueSession extends JPanel { } } + + /** + * Append the given Message to the ChatWindow. + * + * @param message + */ protected void appendMessage(Message message) { try { StyledDocument sdoc = this.chatWindow.getStyledDocument(); - // sdoc.setParagraphAttributes(sdoc.getLength(), message.toString().length()-1, - // style, false); sdoc.insertString(sdoc.getLength(), message.toString(), null); } catch (BadLocationException e) { @@ -150,9 +156,17 @@ public class VueSession extends JPanel { } } + + /** + * Append an icon with the image contained in the given message. The message has + * to contain the base64 encoded bytes of the image as a String for it to be + * decoded and displayed. + * + * @param message + */ protected void appendImage(Message message) { this.setCaretToEnd(); - + String imgString = message.toString(); Icon ic; try { @@ -160,34 +174,52 @@ public class VueSession extends JPanel { ic = new ImageIcon(img); this.chatWindow.insertIcon(ic); this.appendString("\n"); - + } catch (IOException e) { e.printStackTrace(); } - - + } + private void printLineSeparator() { this.appendString("------------------------------------------\n"); } - protected void endSession(String pseudoOther) { - this.printLineSeparator(); - this.appendString(pseudoOther + " a mis fin à la session."); - this.chatInput.setEnabled(false); - this.chatInput.setFocusable(false); - this.sendMessage.setEnabled(false); - this.importFile.setEnabled(false); + /** + * Append the given string to the ChatInput. + * + * @param str + */ + protected void appendInputedText(String str) { + this.chatInput.append(str); } + + protected void resetZoneSaisie() { + this.chatInput.setText(""); + + } + + + // -------------- OTHERS -------------- // + + private void setCaretToEnd() { + this.chatWindow.setCaretPosition(this.chatWindow.getDocument().getLength()); + } + + + /** + * Retrieve all the previous messages from the controller and display them by + * appending them one by one to the ChatWindow. + */ private void displayHistorique() { ArrayList historique = this.c.getHistorique(); for (Message m : historique) { - if(m.getTypeMessage() == TypeMessage.IMAGE) { + if (m.getTypeMessage() == TypeMessage.IMAGE) { this.appendImage(m); - }else { + } else { this.appendMessage(m); } } @@ -197,6 +229,27 @@ public class VueSession extends JPanel { } } + + /** + * Disable the ChatInput, the buttons "Importer" and "Envoyer" and display a + * message indicating the other user ended the session. + * + * @param pseudoOther + */ + protected void endSession(String pseudoOther) { + this.printLineSeparator(); + this.appendString(pseudoOther + " a mis fin à la session."); + this.chatInput.setEnabled(false); + this.chatInput.setFocusable(false); + this.sendMessage.setEnabled(false); + this.importFile.setEnabled(false); + } + + + /** + * Method used when the user closes the session. Set all attributes' references + * to null, and call destroyAll() on the controller. + */ public void destroyAll() { if (this.c != null) { this.c.destroyAll(); @@ -207,12 +260,13 @@ public class VueSession extends JPanel { this.sendMessage = null; } + // ------------- PRIVATE CLASS TO WRAP TEXT -------------// class WrapEditorKit extends StyledEditorKit { - + private static final long serialVersionUID = 1L; - + ViewFactory defaultFactory = new WrapColumnFactory(); public ViewFactory getViewFactory() { diff --git a/POO/src/standard/ControleurStandard.java b/POO/src/standard/ControleurStandard.java index 58bc043..607068b 100644 --- a/POO/src/standard/ControleurStandard.java +++ b/POO/src/standard/ControleurStandard.java @@ -114,9 +114,9 @@ public class ControleurStandard implements ActionListener, ListSelectionListener socketComm.close(); System.out.println("refused"); } - + } catch (IOException e1) { - vue.displayJOptionResponse("refusee"); + e1.printStackTrace(); } } @@ -242,10 +242,10 @@ public class ControleurStandard implements ActionListener, ListSelectionListener return input.readLine(); } - // ------------OBSERVERS-------------// + // ------------OBSERVERS------------- // @Override - public void update(Object o, Object arg) { + public void updateInput(Object o, Object arg) { if (o == this.tcpServ) { @@ -304,6 +304,7 @@ public class ControleurStandard implements ActionListener, ListSelectionListener private void setVueConnexion() throws UnknownHostException, IOException { this.commUDP.sendMessageDelete(); + this.commUDP.removeAllUsers(); this.vue.removeAllUsers(); this.vue.closeAllSession(); this.idsSessionEnCours.clear();