+ */
+public class StoreProcessor extends Thread implements AutoCloseable {
+ // Variables
+ private final StoreProcessorFFE fileFrontEnd;
+ private final Socket store;
+ private final String domain;
+ private boolean client_run;
+
+ private BufferedReader reader;
+ private PrintWriter writer;
+ private ProtocolWriter.ProtocolResult protocolResult;
+ private ProtocolRepository protocolRep;
+
+ // Constructor
+ public StoreProcessor(Socket socket, String domain, StoreProcessorFFE ffe, ProtocolRepository protocolRep) {
+ this.domain = domain;
+ this.fileFrontEnd = ffe;
+ this.store = socket;
+ this.protocolRep = protocolRep;
+ this.client_run = false;
+ initStore();
+ }
+
+ /**
+ * Initialise the Store's Reader and Writer.
+ *
+ * @see BufferedReader
+ * @see PrintWriter
+ * @since 1.0
+ */
+ private void initStore() {
+ try {
+ this.reader = new BufferedReader(new InputStreamReader(
+ this.store.getInputStream(),
+ StandardCharsets.UTF_8
+ ));
+ this.writer = new PrintWriter(new OutputStreamWriter(
+ this.store.getOutputStream(),
+ StandardCharsets.UTF_8
+ ), true);
+
+ this.start();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Thread Function
+ * Start the dialogue with the storebackend.
+ *
+ * @since 1.0
+ */
+ @Override
+ public void run() {
+ this.client_run = true;
+ while (this.client_run) {
+ try {
+ waitAction();
+ System.out.println("[SBE] Envoie commande : " + protocolResult.getCommand());
+
+ // Request
+ this.writer.write(protocolResult.getCommand());
+ this.writer.flush();
+ protocolResult.write(this.store.getOutputStream());
+
+
+ // Response
+ String responseCommand = this.reader.readLine() + "\r\n";
+ if (responseCommand != null)
+ System.out.println("StoreBackEnd: " + responseCommand);
+ ProtocolReader.ProtocolResult responseResult = protocolRep.executeReader(responseCommand);
+ responseResult.read(
+ this.store.getInputStream()
+ );
+ System.out.println("StoreBackEnd response to client: " + responseResult.getResultCommand());
+
+ alertAvalaible(responseResult.getResultCommand());
+
+ } catch (IOException ignore) { }
+ }
+ }
+
+ /**
+ * Permet de demander au StoreBackEnd d'effectuer une commande
+ * @param protocolResult La commande à effectuer
+ */
+ public void executeCommand(ProtocolWriter.ProtocolResult protocolResult) {
+ synchronized (this) {
+ this.protocolResult = protocolResult;
+ this.notify();
+ }
+ }
+
+ /**
+ * Permet de déclarer le StoreBackEnd disponible
+ */
+ private void alertAvalaible(ProtocolWriter.ProtocolResult responseCommand) {
+ synchronized (this) {
+ this.protocolResult = null;
+ fileFrontEnd.onStoreAvailable(this, responseCommand);
+ waitAction();
+ }
+ }
+
+ /**
+ * Permet au Store d'attendre une action à réaliser
+ */
+ private void waitAction() {
+ synchronized (this) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) { e.printStackTrace(); }
+ }
+ }
+
+ /**
+ * AutoClosable Function
+ * Close the Storage thread and resources.
+ *
+ * @since 1.0
+ */
+ @Override
+ public void close() {
+ if (this.client_run) {
+ try {
+ this.client_run = false;
+ this.store.close();
+ } catch (IOException ignored) { }
+ }
+ }
+
+ /**
+ * Compare two StoreProcessor object to check if equals.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ StoreProcessor that = (StoreProcessor) o;
+ return Objects.equals(domain, that.domain);
+ }
+
+ /**
+ * Get hash code
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(domain);
+ }
+
+ public boolean canProcessTask(Task task) {
+ return this.protocolResult == null; // Vérifier si tâche veut ce SBE
+ }
+
+ public String getDomain() {
+ return this.domain;
+ }
+}
diff --git a/app/src/main/java/lightcontainer/domains/server/MulticastServerListener.java b/app/src/main/java/lightcontainer/domains/server/MulticastServerListener.java
new file mode 100644
index 0000000..868ada1
--- /dev/null
+++ b/app/src/main/java/lightcontainer/domains/server/MulticastServerListener.java
@@ -0,0 +1,97 @@
+package lightcontainer.domains.server;
+
+import lightcontainer.domains.client.StoreProcessor;
+import lightcontainer.interfaces.MulticastSPR;
+import lightcontainer.interfaces.ProtocolRepository;
+import lightcontainer.protocol.ProtocolReader;
+import lightcontainer.protocol.rules.reader.HelloRule;
+import lightcontainer.repository.FileFrontEnd;
+
+import java.io.IOException;
+import java.net.*;
+
+/**
+ * StoreMulticastRunnable
+ *
+ * Class listening to the announcement of new StoreBackEnd.
+ * Allowing it to be used as a storage unit.
+ *
+ * @author Jérémi NIHART
+ * @version 1.0
+ * @see Runnable
+ * @since 1.0
+ */
+public class MulticastServerListener implements Runnable {
+ // Variable
+ private final String multicast_address;
+ private final int multicast_port;
+ private FileFrontEnd ffe;
+ private final MulticastSPR repository;
+ private final ProtocolRepository protocolRep;
+
+ private final byte[] buffer = new byte[256];
+ private MulticastSocket listener;
+
+ // Constructor
+ public MulticastServerListener(FileFrontEnd ffe, MulticastSPR repository, ProtocolRepository protocolRep, String multicast_address, int multicast_port) {
+ this.ffe = ffe;
+ this.repository = repository;
+ this.protocolRep = protocolRep;
+ this.multicast_address = multicast_address;
+ this.multicast_port = multicast_port;
+ repository.setServerListener(this);
+ }
+
+ /**
+ * Start Multicast listening on indicated port and IP group.
+ *
+ * @see MulticastSocket#receive(DatagramPacket)
+ * @see DatagramPacket
+ * @since 1.0
+ */
+ @Override
+ public void run() {
+ try {
+ // Create a new listening socket
+ this.listener = new MulticastSocket(this.multicast_port);
+ // Create an identifier for the multicast group on the specified ip
+ InetAddress listener_group = InetAddress.getByName(this.multicast_address);
+ // Creation of a packet for the information received
+ DatagramPacket packet = new DatagramPacket(this.buffer, this.buffer.length);
+ // Add the listener to the multicast group
+ this.listener.joinGroup(listener_group);
+ while (true) {
+ // Read the packet received and build a string of characters
+ this.listener.receive(packet);
+ String data = new String(packet.getData(), 0, packet.getLength());
+ // Create a new StoreBacked (try used in the case of an error to maintain the listening loop)
+ try {
+ // TODO Récupérer le port du message du packet et le setup (add description of the line).
+ HelloRule.Result readerResult = protocolRep.executeReader(data);
+ System.out.printf("Nouveau SBE : Domain=%s | Port=%d\n", readerResult.getDomain(), readerResult.getPort());
+
+ Socket socket = new Socket(packet.getAddress(), readerResult.getPort());
+
+ // Create the store processor
+ StoreProcessor storeProcessor = new StoreProcessor(socket, readerResult.getDomain(), ffe, protocolRep); // TODO : Voir comment on procède get via repo ou ici ?!
+
+ // Add the store processor to its repository
+ this.repository.addStore(storeProcessor);
+ } catch (IOException ignore) {
+ ignore.printStackTrace();
+ }
+ }
+ } catch (Exception ignore) {
+ }
+ }
+
+ /**
+ * Closes the MulticastSocket connection and aborts the listening and infinite listening loop.
+ *
+ * @see MulticastServerListener#run()
+ * @since 1.0
+ */
+ public void stop() {
+ this.listener.close();
+ }
+}
diff --git a/app/src/main/java/lightcontainer/domains/server/UnicastServerListener.java b/app/src/main/java/lightcontainer/domains/server/UnicastServerListener.java
new file mode 100644
index 0000000..5970d61
--- /dev/null
+++ b/app/src/main/java/lightcontainer/domains/server/UnicastServerListener.java
@@ -0,0 +1,75 @@
+package lightcontainer.domains.server;
+
+import lightcontainer.domains.client.ClientHandler;
+import lightcontainer.interfaces.ProtocolRepository;
+import lightcontainer.interfaces.UnicastCHR;
+import lightcontainer.repository.FileFrontEnd;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+public class UnicastServerListener implements Runnable {
+ // Variables
+ private ServerSocket server;
+ private FileFrontEnd ffe;
+ private final UnicastCHR repository;
+ private ProtocolRepository protocolRep;
+ private final int server_port;
+ private boolean server_run;
+
+ // Constructor
+ public UnicastServerListener(FileFrontEnd ffe, UnicastCHR repository, ProtocolRepository protocolRep, int port) {
+ this.ffe = ffe;
+ this.repository = repository;
+ this.protocolRep = protocolRep;
+ this.server_port = port;
+ this.server_run = false;
+ repository.setServerListener(this);
+ }
+
+ /**
+ * Initializes the server and starts it on the previously selected port.
+ *
+ * @since 1.0
+ *
+ * @see Thread#start()
+ * @see ClientHandler
+ */
+ @Override
+ public void run() {
+ try {
+ // Allow looping in the loop and create a socket server
+ this.server_run = true;
+ this.server = new ServerSocket(this.server_port);
+ while (this.server_run) {
+ // Accepting connection requests (blocking)
+ Socket client = this.server.accept();
+ System.out.println("New Client");
+ // Create a new Handler client by passing these dependencies to it
+ ClientHandler clientHandler = new ClientHandler(client, ffe, protocolRep); // TODO passer FileFrontEnd ou faire ca dans le repository ?!
+ // Add the client handler to its repository (clienthandlerrepository)
+ this.repository.addClient(clientHandler);
+ // Start the thread
+ (new Thread(clientHandler)).start();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Stops the server and terminates the new connection.
+ *
+ * @since 1.0
+ */
+ public void stop() {
+ if (this.server_run) {
+ try {
+ this.server_run = false;
+ this.server.close();
+ } catch (IOException ignored) { }
+ }
+ }
+
+}
diff --git a/app/src/main/java/lightcontainer/enumerations/TaskStatus.java b/app/src/main/java/lightcontainer/enumerations/TaskStatus.java
new file mode 100644
index 0000000..12200e6
--- /dev/null
+++ b/app/src/main/java/lightcontainer/enumerations/TaskStatus.java
@@ -0,0 +1,27 @@
+package lightcontainer.enumerations;
+
+import lightcontainer.domains.Task;
+
+/**
+ * Enumeration defining the status of a {@link Task}.
+ */
+public enum TaskStatus {
+
+ /**
+ * En attente d'être traitée
+ */
+ PENDING,
+
+ /**
+ * En train d'être traitée
+ */
+ PROCESSING,
+
+ /**
+ * Une erreur est survenue
+ */
+ ERROR,
+
+
+ SUCCESS
+}
diff --git a/app/src/main/java/lightcontainer/enumerations/TaskType.java b/app/src/main/java/lightcontainer/enumerations/TaskType.java
new file mode 100644
index 0000000..347e6ce
--- /dev/null
+++ b/app/src/main/java/lightcontainer/enumerations/TaskType.java
@@ -0,0 +1,12 @@
+package lightcontainer.enumerations;
+
+import lightcontainer.domains.Task;
+
+/**
+ * enumeration to define the type of a {@link Task}.
+ */
+public enum TaskType {
+ SEND,
+ RECEIVE,
+ DELETE
+}
diff --git a/app/src/main/java/lightcontainer/interfaces/ClientHandlerFFE.java b/app/src/main/java/lightcontainer/interfaces/ClientHandlerFFE.java
new file mode 100644
index 0000000..3f1ca8c
--- /dev/null
+++ b/app/src/main/java/lightcontainer/interfaces/ClientHandlerFFE.java
@@ -0,0 +1,20 @@
+package lightcontainer.interfaces;
+
+import lightcontainer.domains.client.ClientHandler;
+import lightcontainer.protocol.ProtocolWriter;
+import lightcontainer.repository.FileFrontEnd;
+
+/**
+ * A communication interface between a {@link ClientHandler} and the {@link FileFrontEnd}.
+ */
+public interface ClientHandlerFFE {
+ // functions
+
+ /**
+ * Demande le traitement d'une commande
+ * @param command Commande à traiter
+ * @param client identifiant du client à qui est affilié cette commande
+ */
+ void newCommand(ProtocolWriter.ProtocolResult command, String client);
+
+}
diff --git a/app/src/main/java/lightcontainer/interfaces/MulticastSPR.java b/app/src/main/java/lightcontainer/interfaces/MulticastSPR.java
new file mode 100644
index 0000000..1d634c4
--- /dev/null
+++ b/app/src/main/java/lightcontainer/interfaces/MulticastSPR.java
@@ -0,0 +1,27 @@
+package lightcontainer.interfaces;
+
+import lightcontainer.domains.Task;
+import lightcontainer.domains.client.StoreProcessor;
+import lightcontainer.domains.server.MulticastServerListener;
+import lightcontainer.repository.StoreProcessorRepository;
+
+/**
+ * A communication interface between a {@link StoreProcessor} and the {@link StoreProcessorRepository}.
+ */
+public interface MulticastSPR {
+ /**
+ * Setter, allow to define the ServerListener of a repository.
+ * @param server ServerListener to set as default.
+ */
+ void setServerListener(MulticastServerListener server);
+
+ /**
+ * Add a StorePorcessor.
+ * @param store Store processor to add.
+ */
+ void addStore(StoreProcessor store);
+
+ String findDomain(Task task);
+
+ void assignTask(String stor, Task task);
+}
diff --git a/app/src/main/java/lightcontainer/interfaces/ProtocolRepository.java b/app/src/main/java/lightcontainer/interfaces/ProtocolRepository.java
new file mode 100644
index 0000000..1f9f48f
--- /dev/null
+++ b/app/src/main/java/lightcontainer/interfaces/ProtocolRepository.java
@@ -0,0 +1,15 @@
+package lightcontainer.interfaces;
+
+import lightcontainer.protocol.ProtocolReader;
+import lightcontainer.protocol.ProtocolWriter;
+
+public interface ProtocolRepository {
+
+ T executeReader(String data);
+
+ T executeWriter(String cmdName, String... data);
+
+ void addReader(ProtocolReader reader);
+
+ void addWriter(ProtocolWriter writer);
+}
diff --git a/app/src/main/java/lightcontainer/interfaces/StoreProcessorFFE.java b/app/src/main/java/lightcontainer/interfaces/StoreProcessorFFE.java
new file mode 100644
index 0000000..26be68b
--- /dev/null
+++ b/app/src/main/java/lightcontainer/interfaces/StoreProcessorFFE.java
@@ -0,0 +1,17 @@
+package lightcontainer.interfaces;
+
+import lightcontainer.domains.client.StoreProcessor;
+import lightcontainer.protocol.ProtocolWriter;
+import lightcontainer.repository.FileFrontEnd;
+
+/**
+ * A communication interface between a {@link StoreProcessor} and the {@link FileFrontEnd}.
+ */
+public interface StoreProcessorFFE {
+ /**
+ * Allows a {@link StoreProcessor} to notify the FFE that it's available.
+ * @param store The store processor that is now available.
+ * @param responseCommand
+ */
+ void onStoreAvailable(StoreProcessor store, ProtocolWriter.ProtocolResult response);
+}
diff --git a/app/src/main/java/lightcontainer/interfaces/UnicastCHR.java b/app/src/main/java/lightcontainer/interfaces/UnicastCHR.java
new file mode 100644
index 0000000..160b533
--- /dev/null
+++ b/app/src/main/java/lightcontainer/interfaces/UnicastCHR.java
@@ -0,0 +1,26 @@
+package lightcontainer.interfaces;
+
+import lightcontainer.domains.client.ClientHandler;
+import lightcontainer.domains.server.MulticastServerListener;
+import lightcontainer.domains.server.UnicastServerListener;
+import lightcontainer.protocol.ProtocolWriter;
+import lightcontainer.repository.ClientHandlerRepository;
+
+/**
+ * A communication interface between a {@link ClientHandler} and the {@link ClientHandlerRepository}.
+ */
+public interface UnicastCHR {
+ /**
+ * Setter, allow to define the ServerListener of a repository.
+ * @param server ServerListener to set as default.
+ */
+ void setServerListener(UnicastServerListener server);
+
+ /**
+ * Add a ClientHandler.
+ * @param client Client Handler to add.
+ */
+ void addClient(ClientHandler client);
+
+ void respondToClient(String client, ProtocolWriter.ProtocolResult response);
+}
diff --git a/app/src/main/java/lightcontainer/protocol/ProtocolReader.java b/app/src/main/java/lightcontainer/protocol/ProtocolReader.java
new file mode 100644
index 0000000..6d37951
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/ProtocolReader.java
@@ -0,0 +1,92 @@
+package lightcontainer.protocol;
+
+import java.io.InputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class ProtocolReader {
+ // Variables
+ private final Pattern rulePattern;
+
+ // Constructor
+ protected ProtocolReader(String pattern) {
+ this.rulePattern = Pattern.compile(pattern);
+ }
+
+ public enum ResultCmdReceiver {
+ CLIENT,
+ STOREBACKEND
+ }
+
+ /**
+ * Modèle utilisé par tout les résultats des règles de protocol.
+ * Lorsqu'il retourne son résultat, on vérifie si il y a une demande de lecture/écriture de fichier depuis le réseau. Si oui on appel ces méthodes, sinon on ne fait rien.
+ * Ensuite on regarde après l'exécution de ces méthode ou non si il y a une commande de retour ou non et l'envoyons au receiver spécifié par la commande.
+ */
+ public class ProtocolResult {
+ /**
+ * Command qui sera renvoyée par exemple au client
+ */
+ private ProtocolWriter.ProtocolResult resultCommand;
+
+ /**
+ * Désigne vers ou cette commande est envoyée.
+ * ResultCmdReceiver.CLIENT : Signifie que cette commande va être directement revoyée au client.
+ * ResultCmdReceiver.STOREBACKEND : Signifie que cette commande va être envoyée dans la file de tâche du server et êre en attente d'envoie à un StorBackEnd
+ */
+ private ResultCmdReceiver receiver;
+
+ public ResultCmdReceiver getReceiver() {
+ return receiver;
+ }
+
+ /**
+ * Récupérer la commande à envoyer
+ * @return Commande
+ */
+ public ProtocolWriter.ProtocolResult getResultCommand() {
+ return this.resultCommand;
+ }
+
+ /**
+ * Mettre la commande à envoyer
+ * @param resultCommand Commande à envoyer
+ * @param receiver Le receveur de cette commande
+ */
+ public void setResultCommand(ProtocolWriter.ProtocolResult resultCommand, ResultCmdReceiver receiver) {
+ this.resultCommand = resultCommand;
+ this.receiver = receiver;
+ }
+
+ /**
+ * Permet de lire un fichier. Cad reçevoir le contenu d'un fichier provenant du réseau.
+ * Redéfinissez cette méthode pour l'utiliser
+ * @param reader Buffer rempli du fichier
+ */
+ public void read(InputStream reader) {}
+ }
+
+ /**
+ * Permet de lancer la décomposition d'une commande pour en extraire les données
+ * @param data Contenu de la commande
+ */
+ public T execute(String data) {
+ Matcher ruleMatcher = this.rulePattern.matcher(data);
+
+ if (ruleMatcher.matches()) {
+ String[] groups = new String[ruleMatcher.groupCount()];
+
+ for (int i = 1; i <= groups.length; ++i)
+ groups[i - 1] = ruleMatcher.group(i);
+
+ return onExecuted(groups);
+ }
+ return null;
+ }
+
+ /**
+ * Cette méthode est appelée lors de l'exécution de la règle
+ * @param data Paramètres pour créer la commande.
+ */
+ protected abstract T onExecuted(String... data);
+}
diff --git a/app/src/main/java/lightcontainer/protocol/ProtocolWriter.java b/app/src/main/java/lightcontainer/protocol/ProtocolWriter.java
new file mode 100644
index 0000000..87f6682
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/ProtocolWriter.java
@@ -0,0 +1,79 @@
+package lightcontainer.protocol;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.StringJoiner;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class ProtocolWriter {
+ // Variables
+ private final Pattern rulePattern;
+ private final String cmdName;
+
+ // Constructor
+ protected ProtocolWriter(String cmdName, String pattern) {
+ this.rulePattern = Pattern.compile(pattern);
+ this.cmdName = cmdName;
+ }
+
+ /**
+ * Permet de récupérer le nom de la commande.
+ * @return Nom de la commande.
+ */
+ public final String getCmdName() {
+ return cmdName;
+ }
+
+ public static class ProtocolResult {
+
+ private String command;
+
+ public String getCommand() {
+ return command;
+ }
+
+ private void setCommand(String command) {
+ this.command = command;
+ }
+
+ /**
+ * Permet d'écrire un fichier sur le réseau. Cad envoyer le contenu d'un fichier sur le réseau.
+ * Redéfinissez cette méthode pour l'utiliser.
+ * @param writer Buffer à remplir qui sera envoyer via le réseau
+ */
+ public void write(OutputStream writer) {}
+ }
+
+ /**
+ * Permet de contruire une commande selon une règle établie.
+ * @param data Les données à ajouter dans la commande; L'ordre défini leur position dans la commande
+ * @return La commande construite
+ */
+ public final T execute(String... data) {
+ // Concatatène le nom de la commande avec les données (trim), avec un espace entre chaque
+ StringBuilder builder = new StringBuilder(this.cmdName);
+
+ for (String param : data)
+ builder.append(" " + param);
+
+ String command = builder + "\r\n";
+ Matcher ruleMatcher = this.rulePattern.matcher(command); // Vérifie que tout match (cf. Matcher). Si match alors on retourne la commande build, sinon on retourne NULL
+
+ if (ruleMatcher.matches()) {
+ ProtocolResult result = onExecuted(data);
+ result.setCommand(command);
+ return (T) result;
+ }
+
+ return null;
+ }
+
+ /**
+ * Cette méthode est appelée lors de l'exécution de la règle
+ */
+ protected T onExecuted(String... data) {
+ return (T) new ProtocolResult();
+ }
+
+}
diff --git a/app/src/main/java/lightcontainer/protocol/rules/reader/FilelistRule.java b/app/src/main/java/lightcontainer/protocol/rules/reader/FilelistRule.java
new file mode 100644
index 0000000..3b292be
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/rules/reader/FilelistRule.java
@@ -0,0 +1,33 @@
+package lightcontainer.protocol.rules.reader;
+
+import lightcontainer.interfaces.ProtocolRepository;
+import lightcontainer.protocol.ProtocolReader;
+import lightcontainer.protocol.ProtocolWriter;
+import lightcontainer.protocol.rules.writer.FilesRule;
+import lightcontainer.protocol.rules.writer.SignOkRule;
+
+public class FilelistRule extends ProtocolReader {
+ // Constants
+ private static final String PATTERN = "^FILELIST\r\n$";
+
+ private ProtocolRepository protocolRep;
+
+ // Constructor
+ public FilelistRule(ProtocolRepository protocolRep) {
+ super(PATTERN);
+ this.protocolRep = protocolRep;
+ }
+
+ public class Result extends ProtocolResult { }
+
+ /**
+ * Cette méthode est appelée lors de l'exécution de la règle
+ * @param data Paramètres pour créer la commande.
+ */
+ @Override
+ protected FilelistRule.Result onExecuted(String... data) {
+ FilelistRule.Result result = new Result();
+ result.setResultCommand(this.protocolRep.executeWriter(FilesRule.NAME, "endbenja.txt!500"), ResultCmdReceiver.CLIENT);
+ return result;
+ }
+}
diff --git a/app/src/main/java/lightcontainer/protocol/rules/reader/HelloRule.java b/app/src/main/java/lightcontainer/protocol/rules/reader/HelloRule.java
new file mode 100644
index 0000000..407e2dc
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/rules/reader/HelloRule.java
@@ -0,0 +1,54 @@
+package lightcontainer.protocol.rules.reader;
+
+import lightcontainer.protocol.ProtocolReader;
+
+import java.io.BufferedReader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class HelloRule extends ProtocolReader {
+
+ private static final String PATTERN = "^HELLO ([A-Za-z0-9]{5,20}) ([0-9]{1,5})\r\n$";
+
+ // Index du domain dans le tableau de donnée
+ private static final int DOMAIN = 0;
+
+ //Index du port dans le tableau de donnée
+ private static final int PORT = 1;
+
+ public HelloRule() {
+ super(PATTERN);
+ }
+
+
+ public class Result extends ProtocolResult {
+
+ private final String domain;
+ private final int port;
+
+ public Result(String domain, int port) {
+ this.domain = domain;
+ this.port = port;
+ }
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+
+ }
+
+
+ @Override
+ protected HelloRule.Result onExecuted(String... data) {
+ String domain = data[DOMAIN];
+ int port = Integer.parseInt(data[PORT]);
+
+ return new HelloRule.Result(domain, port);
+ }
+
+}
diff --git a/app/src/main/java/lightcontainer/protocol/rules/reader/SavefileRule.java b/app/src/main/java/lightcontainer/protocol/rules/reader/SavefileRule.java
new file mode 100644
index 0000000..444a916
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/rules/reader/SavefileRule.java
@@ -0,0 +1,66 @@
+package lightcontainer.protocol.rules.reader;
+
+import lightcontainer.interfaces.ProtocolRepository;
+import lightcontainer.protocol.ProtocolReader;
+import lightcontainer.protocol.rules.writer.SaveFileErrorRule;
+import lightcontainer.protocol.rules.writer.SaveFileOkRule;
+import lightcontainer.protocol.rules.writer.SendfileRule;
+import lightcontainer.utils.FileReceiver;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class SavefileRule extends ProtocolReader {
+ // Constants
+ private static final String PATTERN = "^SAVEFILE ([^ !]{1,20}) ([0-9]{1,10})\r\n$";
+ private static final int FILE_NAME = 0; // Index file name.
+ private static final int FILE_SIZE = 1; // Index file size.
+
+ private ProtocolRepository protocolRep;
+
+ // Constructor
+ public SavefileRule(ProtocolRepository protocolRep) {
+ super(PATTERN);
+ this.protocolRep = protocolRep;
+ }
+
+ public class Result extends ProtocolResult {
+ // Variables
+ private final String filename;
+ private final int size;
+
+ // Construct
+ public Result(String filename, int size) {
+ this.filename = filename;
+ this.size = size;
+ }
+
+ @Override
+ public void read(InputStream reader) {
+ super.read(reader);
+ System.out.printf("Sauvegarde du fichier : %s %d\n", filename, size);
+
+ try {
+ FileReceiver fileReceiver = new FileReceiver("/home/benjamin/ffe"); // "D:\\");
+ if (!fileReceiver.receiveFile(reader, this.filename, this.size))
+ throw new IOException();
+
+ this.setResultCommand(protocolRep.executeWriter(SendfileRule.NAME, this.filename, String.valueOf(this.size), "EMPREINTEBLBLBLBLBLABLABLBALBALBALBALBALBALBALBALBALABLBALBALBALABLABLABLABLABLABLABALBLABALABLABLABLABKJABKAHBHKBHJbhjvgkh"), ResultCmdReceiver.STOREBACKEND);
+ } catch (IOException e) {
+ this.setResultCommand(protocolRep.executeWriter(SaveFileErrorRule.NAME), ResultCmdReceiver.CLIENT);
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Cette méthode est appelée lors de l'exécution de la règle
+ * @param data Paramètres pour créer la commande.
+ */
+ @Override
+ protected SavefileRule.Result onExecuted(String... data) {
+ SavefileRule.Result result = new SavefileRule.Result(data[FILE_NAME], Integer.parseInt(data[FILE_SIZE]));
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/lightcontainer/protocol/rules/reader/SendOkRule.java b/app/src/main/java/lightcontainer/protocol/rules/reader/SendOkRule.java
new file mode 100644
index 0000000..8243e43
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/rules/reader/SendOkRule.java
@@ -0,0 +1,29 @@
+package lightcontainer.protocol.rules.reader;
+
+import lightcontainer.interfaces.ProtocolRepository;
+import lightcontainer.protocol.ProtocolReader;
+import lightcontainer.protocol.ProtocolWriter;
+import lightcontainer.protocol.rules.writer.SaveFileOkRule;
+
+public class SendOkRule extends ProtocolReader {
+
+
+ // Constants
+ private static final String PATTERN = "^SEND_OK\r\n$";
+
+ private ProtocolRepository protocolRep;
+
+ // Constructor
+ public SendOkRule(ProtocolRepository protocolRep) {
+ super(PATTERN);
+ this.protocolRep = protocolRep;
+ }
+
+
+ @Override
+ protected ProtocolReader.ProtocolResult onExecuted(String... data) {
+ ProtocolReader.ProtocolResult result = new ProtocolReader.ProtocolResult();
+ result.setResultCommand(protocolRep.executeWriter(SaveFileOkRule.NAME), ResultCmdReceiver.CLIENT);
+ return result;
+ }
+}
diff --git a/app/src/main/java/lightcontainer/protocol/rules/reader/SigninRule.java b/app/src/main/java/lightcontainer/protocol/rules/reader/SigninRule.java
new file mode 100644
index 0000000..4e498dd
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/rules/reader/SigninRule.java
@@ -0,0 +1,65 @@
+package lightcontainer.protocol.rules.reader;
+
+import lightcontainer.interfaces.ProtocolRepository;
+import lightcontainer.protocol.ProtocolReader;
+import lightcontainer.protocol.rules.writer.SignErrorRule;
+import lightcontainer.protocol.rules.writer.SignOkRule;
+
+import java.io.InputStream;
+
+public class SigninRule extends ProtocolReader {
+ // Constants
+ private static final String PATTERN = "^SIGNIN ([A-Za-z0-9]{2,20}) ([^ !]{5,50})\r\n$";
+ private static final int LOGIN = 0; // Index du domain dans le tableau de données
+ private static final int PASSWORD = 1; // Index du port dans le tableau de données
+
+ private ProtocolRepository protocolRep;
+
+ // Constructor
+ public SigninRule(ProtocolRepository protocolRep) {
+ super(PATTERN);
+ this.protocolRep = protocolRep;
+ }
+
+ public class Result extends ProtocolResult {
+ // Variables
+ private final String login;
+ private final String password;
+
+ // Result constructor
+ public Result(String login, String password) {
+ this.login = login;
+ this.password = password;
+ }
+
+ public String getLogin() {
+ return login;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public boolean checkCredentials() {
+ return getLogin().equals("aa") && getPassword().equals("aaaaa");
+ }
+
+ @Override
+ public void read(InputStream reader) {
+
+ }
+ }
+
+ @Override
+ protected SigninRule.Result onExecuted(String... data) {
+ SigninRule.Result result = new SigninRule.Result(data[LOGIN], data[PASSWORD]);
+
+ // TODO : Création d'une règle d'écriture SIGN_OK et SIGN_ERROR proprement
+ if (result.checkCredentials()) {
+ result.setResultCommand(this.protocolRep.executeWriter(SignOkRule.NAME), ResultCmdReceiver.CLIENT);
+ } else {
+ result.setResultCommand(this.protocolRep.executeWriter(SignErrorRule.NAME), ResultCmdReceiver.CLIENT);
+ }
+ return result;
+ }
+}
diff --git a/app/src/main/java/lightcontainer/protocol/rules/writer/FilesRule.java b/app/src/main/java/lightcontainer/protocol/rules/writer/FilesRule.java
new file mode 100644
index 0000000..a611163
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/rules/writer/FilesRule.java
@@ -0,0 +1,15 @@
+package lightcontainer.protocol.rules.writer;
+
+import lightcontainer.protocol.ProtocolWriter;
+
+public class FilesRule extends ProtocolWriter {
+
+ private static final String PATTERN = "^FILES( ([^ !]{1,20})!([0-9]{1,10})){0,50}\r\n$";
+
+ public static final String NAME = "FILES";
+
+ public FilesRule() {
+ super(NAME, PATTERN);
+ }
+
+}
diff --git a/app/src/main/java/lightcontainer/protocol/rules/writer/SaveFileErrorRule.java b/app/src/main/java/lightcontainer/protocol/rules/writer/SaveFileErrorRule.java
new file mode 100644
index 0000000..8dda3a7
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/rules/writer/SaveFileErrorRule.java
@@ -0,0 +1,15 @@
+package lightcontainer.protocol.rules.writer;
+
+import lightcontainer.protocol.ProtocolWriter;
+
+public class SaveFileErrorRule extends ProtocolWriter {
+
+ private static final String PATTERN = "^SAVEFILE_ERROR\r\n$";
+
+ public static final String NAME = "SAVEFILE_ERROR";
+
+ public SaveFileErrorRule() {
+ super(NAME, PATTERN);
+ }
+
+}
diff --git a/app/src/main/java/lightcontainer/protocol/rules/writer/SaveFileOkRule.java b/app/src/main/java/lightcontainer/protocol/rules/writer/SaveFileOkRule.java
new file mode 100644
index 0000000..1bd90d3
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/rules/writer/SaveFileOkRule.java
@@ -0,0 +1,15 @@
+package lightcontainer.protocol.rules.writer;
+
+import lightcontainer.protocol.ProtocolWriter;
+
+public class SaveFileOkRule extends ProtocolWriter {
+
+ private static final String PATTERN = "^SAVEFILE_OK\r\n$";
+
+ public static final String NAME = "SAVEFILE_OK";
+
+ public SaveFileOkRule() {
+ super(NAME, PATTERN);
+ }
+
+}
diff --git a/app/src/main/java/lightcontainer/protocol/rules/writer/SendfileRule.java b/app/src/main/java/lightcontainer/protocol/rules/writer/SendfileRule.java
new file mode 100644
index 0000000..be51736
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/rules/writer/SendfileRule.java
@@ -0,0 +1,49 @@
+package lightcontainer.protocol.rules.writer;
+
+import lightcontainer.protocol.ProtocolWriter;
+import lightcontainer.utils.FileReceiver;
+import lightcontainer.utils.FileSender;
+
+import java.io.OutputStream;
+
+public class SendfileRule extends ProtocolWriter {
+
+ private static final String PATTERN = "^SENDFILE [A-Za-z0-9.]{0,200} [0-9]{1,10} [A-Za-z0-9.]{50,200}\r\n$";
+
+ public static final String NAME = "SENDFILE";
+
+ private static final int HASHED_FILE_NAME = 0; // Index file name hashed.
+ private static final int FILE_SIZE = 1; // Index file size.
+ private static final int HASHED_FILE_CONTENT = 2; // Index file content hashed.
+
+ public SendfileRule() {
+ super(NAME, PATTERN);
+ }
+
+ public class Result extends ProtocolWriter.ProtocolResult {
+
+ private final String hashedFileName;
+ private final int fileSize;
+ private final String hashedFileContent;
+
+ public Result(String hashedFileName, int fileSize, String hashedFileContent) {
+ this.hashedFileName = hashedFileName;
+ this.fileSize = fileSize;
+ this.hashedFileContent = hashedFileContent;
+ }
+
+ @Override
+ public void write(OutputStream writer) {
+ super.write(writer);
+ System.out.println("Envoie du fichier au SBE");
+ FileSender fileSender = new FileSender("/home/benjamin/ffe");
+ fileSender.sendFile(hashedFileName, writer);
+ }
+ }
+
+
+ @Override
+ protected SendfileRule.Result onExecuted(String... data) {
+ return new SendfileRule.Result(data[HASHED_FILE_NAME], Integer.parseInt(data[FILE_SIZE]), data[HASHED_FILE_CONTENT]);
+ }
+}
diff --git a/app/src/main/java/lightcontainer/protocol/rules/writer/SignErrorRule.java b/app/src/main/java/lightcontainer/protocol/rules/writer/SignErrorRule.java
new file mode 100644
index 0000000..b78a66f
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/rules/writer/SignErrorRule.java
@@ -0,0 +1,15 @@
+package lightcontainer.protocol.rules.writer;
+
+import lightcontainer.protocol.ProtocolWriter;
+
+public class SignErrorRule extends ProtocolWriter {
+
+ private static final String PATTERN = "^SIGN_ERROR\r\n$";
+
+ public static final String NAME = "SIGN_ERROR";
+
+ public SignErrorRule() {
+ super(NAME, PATTERN);
+ }
+
+}
diff --git a/app/src/main/java/lightcontainer/protocol/rules/writer/SignOkRule.java b/app/src/main/java/lightcontainer/protocol/rules/writer/SignOkRule.java
new file mode 100644
index 0000000..44b3f3c
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/rules/writer/SignOkRule.java
@@ -0,0 +1,14 @@
+package lightcontainer.protocol.rules.writer;
+
+import lightcontainer.protocol.ProtocolWriter;
+
+public class SignOkRule extends ProtocolWriter {
+
+ private static final String PATTERN = "^SIGN_OK\r\n$";
+
+ public static final String NAME = "SIGN_OK";
+
+ public SignOkRule() {
+ super(NAME, PATTERN);
+ }
+}
diff --git a/app/src/main/java/lightcontainer/protocol/rules/writer/SignoutRule.java b/app/src/main/java/lightcontainer/protocol/rules/writer/SignoutRule.java
new file mode 100644
index 0000000..ff42d2c
--- /dev/null
+++ b/app/src/main/java/lightcontainer/protocol/rules/writer/SignoutRule.java
@@ -0,0 +1,14 @@
+package lightcontainer.protocol.rules.writer;
+
+import lightcontainer.protocol.ProtocolWriter;
+
+public class SignoutRule extends ProtocolWriter {
+
+ private static final String PATTERN = "^SIGNOUT\r\n$";
+
+ public static final String NAME = "SIGNOUT";
+
+ public SignoutRule() {
+ super(NAME, PATTERN);
+ }
+}
diff --git a/app/src/main/java/lightcontainer/repository/ClientHandlerRepository.java b/app/src/main/java/lightcontainer/repository/ClientHandlerRepository.java
new file mode 100644
index 0000000..1f288ea
--- /dev/null
+++ b/app/src/main/java/lightcontainer/repository/ClientHandlerRepository.java
@@ -0,0 +1,81 @@
+package lightcontainer.repository;
+
+import lightcontainer.domains.client.ClientHandler;
+import lightcontainer.domains.server.UnicastServerListener;
+import lightcontainer.interfaces.UnicastCHR;
+import lightcontainer.protocol.ProtocolWriter;
+
+import java.util.ArrayList;
+import java.util.List;
+// TODO : C'est genre un ClientHandlerManager quoi hein, normal qu'il fasse blinder de chose ;)
+/**
+ * ClientHandlerRepository
+ *
+ * Repository storing ClientHandler class.
+ * Contains some utility functions.
+ *
+ * @version 1.0
+ * @since 1.0
+ *
+ * @see ClientHandler
+ * @see AutoCloseable
+ * @author Jérémi NIHART
+ */
+public class ClientHandlerRepository implements AutoCloseable, UnicastCHR {
+ // Variable
+ private final List handlers;
+ private UnicastServerListener server;
+
+ // Constructor
+ public ClientHandlerRepository() {
+ this.handlers = new ArrayList<>();
+ }
+
+ /**
+ * Setter, allow to define the ServerListener of a repository.
+ * & Start the server.
+ * @param server ServerListener to set as default.
+ *
+ * @since 1.0
+ */
+ @Override
+ public void setServerListener(UnicastServerListener server) {
+ this.server = server;
+ new Thread(server).start();
+ }
+
+ /**
+ * Add a ClientHandler.
+ * @param client Client Handler to add.
+ *
+ * @since 1.0
+ */
+ @Override
+ public void addClient(ClientHandler client) {
+ this.handlers.add(client);
+ }
+
+ @Override
+ public void respondToClient(String login, ProtocolWriter.ProtocolResult response) {
+ for (ClientHandler client : handlers) {
+ if (client.getLogin().equals(login)) {
+ client.respond(response);
+ break;
+ }
+ }
+ }
+
+ /**
+ * AutoClosable Function
+ * Closes all ClientHandlers stored in this repository and deallocates all resources.
+ *
+ * @since 1.0
+ */
+ @Override
+ public void close() {
+ // Stop the server.
+ this.server.stop();
+ // Stop each clients.
+ this.handlers.forEach(ClientHandler::close);
+ }
+}
diff --git a/app/src/main/java/lightcontainer/repository/FileFrontEnd.java b/app/src/main/java/lightcontainer/repository/FileFrontEnd.java
new file mode 100644
index 0000000..db6ec0c
--- /dev/null
+++ b/app/src/main/java/lightcontainer/repository/FileFrontEnd.java
@@ -0,0 +1,84 @@
+package lightcontainer.repository;
+
+import lightcontainer.domains.client.StoreProcessor;
+import lightcontainer.domains.Task;
+import lightcontainer.enumerations.TaskStatus;
+import lightcontainer.interfaces.ClientHandlerFFE;
+import lightcontainer.interfaces.ProtocolRepository;
+import lightcontainer.interfaces.StoreProcessorFFE;
+import lightcontainer.protocol.ProtocolWriter;
+
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
+public class FileFrontEnd implements ClientHandlerFFE, StoreProcessorFFE {
+ // Variables
+ private Deque tasks = new ConcurrentLinkedDeque<>();
+ private ClientHandlerRepository clientRepository; // TODO -> pourquoi pas une interface ? end
+ private StoreProcessorRepository storeRepository; // TODO -> pourquoi pas une interface ? end
+ private ProtocolRepository protocolRepository;
+
+ // Constructor
+ public FileFrontEnd(ClientHandlerRepository clientRepo, StoreProcessorRepository storeRepo, ProtocolRepository protocolRepository) {
+ this.clientRepository = clientRepo;
+ this.storeRepository = storeRepo;
+ this.protocolRepository = protocolRepository;
+ }
+
+ /**
+ * Appelé quand nouvelle tâche
+ */
+ public void alertStoreProcessors(Task task) {
+ // On avertit les stor processors d'une nouvelle tâche
+ String stor = storeRepository.findDomain(task);
+ if (stor != null) {
+ storeRepository.assignTask(stor, task);
+ task.setDomain(stor);
+ }
+ }
+
+ /**
+ * Permet à un {@link StoreProcessor} d'avertir le FFE qu'il est disponible
+ *
+ * @param store Le SBE qui s'est occupé de la tâche
+ * @param response La réponse à envoyer au client
+ */
+ @Override
+ public void onStoreAvailable(StoreProcessor store, ProtocolWriter.ProtocolResult response) {
+ // TODO : Chercher une tâche appropriée
+ if (response != null) {
+ Iterator it = tasks.iterator();
+ while (it.hasNext()) {
+ Task task = it.next();
+ if (task.isResponseOfClient(store.getDomain())) {
+ clientRepository.respondToClient(task.getClient(), response);
+ it.remove(); // Suppression de la tâche
+ break;
+ }
+ }
+ }
+
+ assignOtherTask(store);
+ }
+
+ private void assignOtherTask(StoreProcessor store) {
+ Iterator it = tasks.iterator();
+
+ while (it.hasNext()) {
+ Task task = it.next();
+ if (store.canProcessTask(task)) {
+ storeRepository.assignTask(store.getDomain(), task);
+ task.setDomain(store.getDomain());
+ }
+ }
+ }
+
+
+ @Override
+ public void newCommand(ProtocolWriter.ProtocolResult command, String client) {
+ Task task = Task.newInstance(command, client);
+ tasks.add(task);
+ alertStoreProcessors(task);
+ }
+}
diff --git a/app/src/main/java/lightcontainer/repository/ProtocolRepositoryImpl.java b/app/src/main/java/lightcontainer/repository/ProtocolRepositoryImpl.java
new file mode 100644
index 0000000..86da726
--- /dev/null
+++ b/app/src/main/java/lightcontainer/repository/ProtocolRepositoryImpl.java
@@ -0,0 +1,45 @@
+package lightcontainer.repository;
+
+import lightcontainer.interfaces.ProtocolRepository;
+import lightcontainer.protocol.ProtocolReader;
+import lightcontainer.protocol.ProtocolWriter;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class ProtocolRepositoryImpl implements ProtocolRepository {
+ private final Set readers = new HashSet<>();
+ private final Set writers = new HashSet<>();
+
+ @Override
+ public T executeReader(String data) {
+ for (ProtocolReader reader : readers) {
+ T readerResult = reader.execute(data);
+ if (readerResult != null) {
+ return readerResult;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public T executeWriter(String cmdName, String... data) {
+ for (ProtocolWriter writer : writers) {
+ T command;
+ if (cmdName.equals(writer.getCmdName()) && (command = writer.execute(data)) != null) {
+ return command;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void addReader(ProtocolReader reader) {
+ this.readers.add(reader);
+ }
+
+ @Override
+ public void addWriter(ProtocolWriter writer) {
+ this.writers.add(writer);
+ }
+}
diff --git a/app/src/main/java/lightcontainer/repository/StoreProcessorRepository.java b/app/src/main/java/lightcontainer/repository/StoreProcessorRepository.java
new file mode 100644
index 0000000..fd6bd4d
--- /dev/null
+++ b/app/src/main/java/lightcontainer/repository/StoreProcessorRepository.java
@@ -0,0 +1,93 @@
+package lightcontainer.repository;
+
+import lightcontainer.domains.Task;
+import lightcontainer.domains.client.StoreProcessor;
+import lightcontainer.domains.server.MulticastServerListener;
+import lightcontainer.interfaces.MulticastSPR;
+
+import java.util.HashSet;
+import java.util.Set;
+// TODO : C'est genre un ClientHandlerManager quoi hein, normal qu'il fasse blinder de chose ;)
+/**
+ * StoreProcessorRepository
+ *
+ * Repository storing StorePorcessor class.
+ * Contains some utility functions.
+ *
+ * @version 1.0
+ * @since 1.0
+ *
+ * @see StoreProcessor
+ * @author Jérémi NIHART
+ */
+public class StoreProcessorRepository implements AutoCloseable, MulticastSPR {
+ // Variables
+ private final Set handlers;
+ private MulticastServerListener server;
+
+ // Constructor
+ public StoreProcessorRepository() {
+ this.handlers = new HashSet<>();
+ }
+
+ /**
+ * Setter, allow to define the ServerListener of a repository.
+ * & start the server.
+ * @param server ServerListener to set as default.
+ *
+ * @since 1.0
+ */
+ @Override
+ public void setServerListener(MulticastServerListener server) {
+ this.server = server;
+ new Thread(server).start();
+ }
+
+ /**
+ * Add a StorePorcessor.
+ * @param store Store processor to add.
+ *
+ * @since 1.0
+ */
+ @Override
+ public void addStore(StoreProcessor store) {
+ this.handlers.add(store);
+
+ }
+
+ @Override
+ public String findDomain(Task task) {
+ StoreProcessor handler = findSBE(task);
+ return handler == null ? null : handler.getDomain();
+ }
+
+ private StoreProcessor findSBE(Task task) {
+ for (StoreProcessor handler : handlers) {
+ if (handler.canProcessTask(task)) {
+ return handler;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void assignTask(String stor, Task task) {
+ StoreProcessor handler = findSBE(task);
+ handler.executeCommand(task.getCommand());
+ }
+
+ /**
+ * AutoClosable Function
+ * Closes all StoreProcessor stored in this repository and deallocates all resources.
+ *
+ * @since 1.0
+ */
+ @Override
+ public void close() {
+ // Stop the server.
+ this.server.stop();
+ // Close each client.
+ this.handlers.forEach(StoreProcessor::close);
+ }
+}
diff --git a/app/src/main/java/lightcontainer/storage/Adapter.java b/app/src/main/java/lightcontainer/storage/Adapter.java
new file mode 100644
index 0000000..29d5dc3
--- /dev/null
+++ b/app/src/main/java/lightcontainer/storage/Adapter.java
@@ -0,0 +1,8 @@
+package lightcontainer.storage;
+
+public interface Adapter {
+
+ String toString();
+
+ AppData fromString(String appDataString);
+}
diff --git a/app/src/main/java/lightcontainer/storage/AppConfig.java b/app/src/main/java/lightcontainer/storage/AppConfig.java
new file mode 100644
index 0000000..7f767b8
--- /dev/null
+++ b/app/src/main/java/lightcontainer/storage/AppConfig.java
@@ -0,0 +1,88 @@
+package lightcontainer.storage;
+
+/**
+ * AppConfig represents all network related information needed for the program to work.
+ *
+ * @author Maximilien LEDOUX
+ * @version 1.0
+ * @since 1.0
+ */
+public class AppConfig {
+
+ private static AppConfig instance = null;
+ private int unicastPort;
+ private String multicastIp;
+ private int multicastPort;
+ private String networkInterface;
+ private boolean isTls;
+
+ /**
+ * Constructs a new instance of AppConfig.
+ * Sets all data to default values.
+ */
+ private AppConfig() {
+ this.unicastPort = -1;
+ this.multicastIp = "NONE";
+ this.multicastPort = -1;
+ this.networkInterface = "NONE";
+ this.isTls = false;
+ }
+
+ /**
+ * @return An instance of this class. Always returns the same instance.
+ */
+ public static AppConfig getInstance() {
+ if (instance == null) {
+ instance = new AppConfig();
+ }
+ return instance;
+ }
+
+ public int getUnicastPort() {
+ return unicastPort;
+ }
+
+ public void setUnicastPort(int unicastPort) {
+ if (this.unicastPort == -1) {
+ this.unicastPort = unicastPort;
+ }
+ }
+
+ public String getMulticastIp() {
+ return multicastIp;
+ }
+
+ public void setMulticastIp(String multicastIp) {
+ if (this.multicastIp.equals("NONE")) {
+ this.multicastIp = multicastIp;
+ }
+ }
+
+ public int getMulticastPort() {
+ return multicastPort;
+ }
+
+ public void setMulticastPort(int multicastPort) {
+ if (this.multicastPort == -1) {
+ this.multicastPort = multicastPort;
+ }
+ }
+
+ public String getNetworkInterface() {
+ return networkInterface;
+ }
+
+ public void setNetworkInterface(String networkInterface) {
+ if (this.networkInterface.equals("NONE")) {
+ this.networkInterface = networkInterface;
+ }
+ }
+
+ public boolean isTls() {
+ return isTls;
+ }
+
+ public void setTls(boolean tls) {
+ this.isTls = tls;
+ }
+}
diff --git a/app/src/main/java/lightcontainer/storage/AppData.java b/app/src/main/java/lightcontainer/storage/AppData.java
new file mode 100644
index 0000000..2a693b2
--- /dev/null
+++ b/app/src/main/java/lightcontainer/storage/AppData.java
@@ -0,0 +1,152 @@
+package lightcontainer.storage;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * AppData represents the database of the FileFrontEnd program.
+ * It contains an AppConfig instance and a collection of Users.
+ *
+ * @author Maximilien LEDOUX
+ * @version 1.0
+ * @see AppConfig
+ * @see User
+ * @since 1.0
+ */
+public class AppData {
+
+ private static AppData instance = null;
+ private AppConfig appConfig;
+ private final Map users;
+
+
+ /**
+ * Constructs a new instance of AppData.
+ * Sets appConfig to null and creates a new Hashmap of users.
+ */
+ private AppData() {
+ this.appConfig = null;
+ this.users = new HashMap<>();
+ }
+
+ /**
+ * @return An instance of this class. Always returns the same instance.
+ */
+ public static AppData getInstance() {
+ if (instance == null) {
+ instance = new AppData();
+ }
+ return instance;
+ }
+
+ /**
+ * @return The AppConfig
+ */
+ public AppConfig getAppConfig() {
+ return appConfig;
+ }
+
+ /**
+ * Sets the AppConfig. This method sets the AppConfig for once and for all.
+ * It is locked after first call.
+ *
+ * @param appConfig The network configuration of the program.
+ */
+ public void setAppConfig(AppConfig appConfig) {
+ if (this.appConfig == null) {
+ this.appConfig = appConfig;
+ }
+ }
+
+ /**
+ * @param userName The name of the user.
+ * @return The user corresponding to userName, null otherwise.
+ */
+ public User getUser(String userName) {
+ return this.users.get(userName);
+ }
+
+ /**
+ * Use this method when a user signs up.
+ *
+ * @param user The user to add.
+ * @return True if the user was added. False if a user with the same name already exists.
+ */
+ public boolean addUser(User user) {
+ if (this.users.containsKey(user.getName())) {
+ return false;
+ } else {
+ this.users.put(user.getName(), user);
+ return true;
+ }
+ }
+
+ public Iterator usersIterator() {
+ return users.values().iterator();
+ }
+
+ /**
+ * @param fileName The name of the file
+ * @param user The user
+ * @return The file corresponding to the given name and belonging to the user. Null if the user cannot be found or the file cannot be found
+ * @deprecated Maybe not useful. DO NOT USE FOR THE TIME BEING
+ */
+ public File getFileOf(String fileName, User user) {
+ return this.users.get(user.getName()).getFile(fileName);
+ }
+
+ /**
+ * Call this method after receiving SAVEFILE_OK from the StorBackEnd.
+ * Do NOT call when receiving SAVEFILE_ERROR, or it will break the system's synchronization.
+ *
+ * Adds the file of for a specific user.
+ * True indicates the success of the operation.
+ * False indicates the failure of the operation.
+ *
+ * @param file The file to add
+ * @param user The user who wants to add the file
+ * @return True if the user is found and a file with the same name doesn't already exist for this user. False otherwise.
+ */
+ public boolean addFileFor(File file, User user) {
+ if (!this.users.containsKey(user.getName())) {
+ return false;
+ } else {
+ this.users.get(user.getName()).addFile(file);
+ return true;
+ }
+ }
+
+ /**
+ * Call this method after receiving REMOVEFILE_OK from the StorBackEnd.
+ * Do NOT call when receiving REMOVEFILE_ERROR, or it will break the system's synchronization.
+ * Deletes the file of for a specific user.
+ * True indicates the success of the operation.
+ * False indicates the failure of the operation.
+ *
+ * @param fileName The name of the file to delete
+ * @param user The user who wants to delete the file
+ * @return True if the user is found and the file was deleted. False otherwise.
+ */
+ public boolean deleteFileOf(String fileName, User user) {
+ if (!this.users.containsKey(user.getName())) {
+ return false;
+ } else {
+ return this.users.get(user.getName()).deleteFile(fileName);
+ }
+ }
+
+ /**
+ * @param user The user who wants to add a storage for their file
+ * @param file The file that needs a new storage
+ * @param storage The storage to add
+ * @return True if the storage was added. False otherwise.
+ */
+ public boolean addStorage(User user, File file, String storage) {
+ if (!this.users.containsKey(user.getName())) {
+ return false;
+ } else {
+ return this.users.get(user.getName()).addStorage(file, storage);
+ }
+ }
+}
diff --git a/app/src/main/java/lightcontainer/storage/File.java b/app/src/main/java/lightcontainer/storage/File.java
new file mode 100644
index 0000000..e7d5d5f
--- /dev/null
+++ b/app/src/main/java/lightcontainer/storage/File.java
@@ -0,0 +1,47 @@
+package lightcontainer.storage;
+
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * File represents all information related to a file
+ */
+public class File {
+
+ private final String name;
+ private final int size;
+ private final String iv;
+ private final Set storage;
+
+ public File(String name, int size, String iv, Set storage) {
+ this.name = name;
+ this.size = size;
+ this.iv = iv;
+ this.storage = storage;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public String getIv() {
+ return iv;
+ }
+
+ public Iterator getStorageIterator() {
+ return storage.iterator();
+ }
+
+ public boolean addStorage(String storage) {
+ if (this.storage.contains(storage)) {
+ return false;
+ } else {
+ this.storage.add(storage);
+ return true;
+ }
+ }
+}
diff --git a/app/src/main/java/lightcontainer/storage/JsonAdapter.java b/app/src/main/java/lightcontainer/storage/JsonAdapter.java
new file mode 100644
index 0000000..aaecc55
--- /dev/null
+++ b/app/src/main/java/lightcontainer/storage/JsonAdapter.java
@@ -0,0 +1,145 @@
+package lightcontainer.storage;
+
+import com.google.gson.*;
+
+import java.util.*;
+
+/**
+ * Specific implementation of Adapter that converts AppData to Json and vice-versa
+ */
+public class JsonAdapter implements Adapter {
+
+ private AppData appData;
+
+ public JsonAdapter(AppData appData) {
+ this.appData = appData;
+ }
+
+ /**
+ *
+ * @return A Json String containing AppData properties
+ */
+ @Override
+ public String toString() {
+ return addData(appData);
+ }
+
+ private String addData(AppData appData) {
+ AppConfig appConfig = appData.getAppConfig();
+ JsonObject config = new JsonObject();
+ config.addProperty("unicast_port", appConfig.getUnicastPort());
+ config.addProperty("multicast_ip", appConfig.getMulticastIp());
+ config.addProperty("multicast_port", appConfig.getMulticastPort());
+ config.addProperty("network_interface", appConfig.getNetworkInterface());
+ config.addProperty("tls", appConfig.isTls());
+ JsonArray users = new JsonArray();
+ Iterator userIterator = appData.usersIterator();
+ addUsers(users, userIterator);
+ config.add("users", users);
+ return config.toString();
+ }
+
+ private void addUsers(JsonArray users, Iterator userIterator) {
+ while (userIterator.hasNext()) {
+ User current = userIterator.next();
+ JsonObject user = new JsonObject();
+ user.addProperty("name", current.getName());
+ user.addProperty("password", current.getPassword());
+ user.addProperty("aes_key", current.getAesKey());
+ JsonArray files = new JsonArray();
+ Iterator fileIterator = current.fileIterator();
+ addFiles(fileIterator, files);
+ user.add("files", files);
+ users.add(user);
+ }
+ }
+
+ private void addFiles(Iterator fileIterator, JsonArray files) {
+ while (fileIterator.hasNext()) {
+ File currentFile = fileIterator.next();
+ JsonObject file = new JsonObject();
+ file.addProperty("name", currentFile.getName());
+ file.addProperty("size", currentFile.getSize());
+ file.addProperty("iv", currentFile.getIv());
+ JsonArray storage = new JsonArray();
+ Iterator storageIterator = currentFile.getStorageIterator();
+ addStorage(storage, storageIterator);
+ file.add("storage", storage);
+ files.add(file);
+ }
+ }
+
+ private void addStorage(JsonArray storage, Iterator storageIterator) {
+ while (storageIterator.hasNext()) {
+ String storageString = storageIterator.next();
+ storage.add(storageString);
+ }
+ }
+
+ /**
+ *
+ * @param appDataString The Json String to convert
+ * @return An AppData instance
+ */
+ @Override
+ public AppData fromString(String appDataString) {
+ try {
+ JsonElement jsonString = JsonParser.parseString(appDataString);
+ JsonObject jsonAppData = jsonString.getAsJsonObject();
+ AppConfig appConfig = AppConfig.getInstance();
+ appConfig.setUnicastPort(jsonAppData.get("unicast_port").getAsInt());
+ appConfig.setMulticastIp(jsonAppData.get("multicast_ip").getAsString());
+ appConfig.setMulticastPort(jsonAppData.get("multicast_port").getAsInt());
+ appConfig.setNetworkInterface(jsonAppData.get("network_interface").getAsString());
+ appConfig.setTls(jsonAppData.get("tls").getAsBoolean());
+ JsonArray jsonUsers = jsonAppData.getAsJsonArray("users");
+ List users = new ArrayList<>();
+ getUsers(jsonUsers, users);
+ AppData appData = AppData.getInstance();
+ appData.setAppConfig(appConfig);
+ for (User user : users) {
+ appData.addUser(user);
+ }
+ this.appData = appData;
+ return this.appData;
+ } catch (JsonParseException parseException) {
+ System.out.println("[FFE] : Error while loading configuration file"); //TODO - changer en log
+ return null;
+ }
+ }
+
+ private void getUsers(JsonArray jsonUsers, List users) {
+ for (JsonElement element : jsonUsers) {
+ JsonObject jsonUser = element.getAsJsonObject();
+ String name = jsonUser.get("name").getAsString();
+ String password = jsonUser.get("password").getAsString();
+ String aeskey = jsonUser.get("aes_key").getAsString();
+ Map userFiles = new HashMap<>();
+ JsonArray jsonFiles = jsonUser.getAsJsonArray("files");
+ getFiles(userFiles, jsonFiles);
+ User user = new User(name, password, aeskey, userFiles);
+ users.add(user);
+ }
+ }
+
+ private void getFiles(Map userFiles, JsonArray jsonFiles) {
+ for (JsonElement fileElement : jsonFiles) {
+ JsonObject jsonFile = fileElement.getAsJsonObject();
+ String fileName = jsonFile.get("name").getAsString();
+ int size = jsonFile.get("size").getAsInt();
+ String iv = jsonFile.get("iv").getAsString();
+ Set storage = new HashSet<>();
+ JsonArray jsonStorage = jsonFile.getAsJsonArray("storage");
+ getStorage(storage, jsonStorage);
+ File file = new File(fileName, size, iv, storage);
+ userFiles.put(file.getName(), file);
+ }
+ }
+
+ private void getStorage(Set storage, JsonArray jsonStorage) {
+ for (JsonElement storageElement : jsonStorage) {
+ String storageName = storageElement.getAsString();
+ storage.add(storageName);
+ }
+ }
+}
diff --git a/app/src/main/java/lightcontainer/storage/Repository.java b/app/src/main/java/lightcontainer/storage/Repository.java
new file mode 100644
index 0000000..1f085a6
--- /dev/null
+++ b/app/src/main/java/lightcontainer/storage/Repository.java
@@ -0,0 +1,51 @@
+package lightcontainer.storage;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+
+public class Repository {
+
+ /**
+ * @param filePath The path where the file must be saved
+ * @param adapter The service that converts Objects to Strings
+ */
+ static void save(String filePath, Adapter adapter) {
+ if (filePath != null) {
+ String jsonAppData = adapter.toString();
+ try (BufferedWriter bufferedWriter = Files.newBufferedWriter(Paths.get(filePath).toAbsolutePath(), StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ bufferedWriter.write(jsonAppData);
+ bufferedWriter.flush();
+ } catch (IOException e) {
+ System.out.println("Error while saving configuration file !");
+ }
+ }
+ }
+
+ /**
+ * @param filePath The path where the file is stored
+ * @param adapter The service that converts Strings to objects
+ * @return
+ */
+ static AppData load(String filePath, Adapter adapter) {
+ String jsonString = readFile(filePath);
+ return adapter.fromString(jsonString);
+ }
+
+ private static String readFile(String filePath) {
+ StringBuilder builder = new StringBuilder();
+ try (BufferedReader reader = Files.newBufferedReader(Paths.get(filePath).toAbsolutePath(), StandardCharsets.UTF_8)) {
+ while (reader.ready()) {
+ builder.append(reader.readLine());
+ }
+ } catch (IOException e) {
+ System.out.println("Error while reading configuration file");
+ builder.setLength(0);
+ }
+ return builder.toString();
+ }
+}
diff --git a/app/src/main/java/lightcontainer/storage/User.java b/app/src/main/java/lightcontainer/storage/User.java
new file mode 100644
index 0000000..47a65f9
--- /dev/null
+++ b/app/src/main/java/lightcontainer/storage/User.java
@@ -0,0 +1,80 @@
+package lightcontainer.storage;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * User represents a user of the system.
+ *
+ * @author Maximilien LEDOUX
+ * @version 1.0
+ * @since 1.0
+ */
+public class User {
+
+ private final String Name;
+ private final String password;
+ private final String aesKey;
+ private final Map files;
+
+ public User(String Name, String password, String aesKey, Map files) {
+ this.Name = Name;
+ this.password = password;
+ this.aesKey = aesKey;
+ this.files = files;
+ }
+
+ public String getName() {
+ return Name;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public String getAesKey() {
+ return aesKey;
+ }
+
+ public Iterator fileIterator() {
+ return files.values().iterator();
+ }
+
+ public File getFile(String fileName) {
+ return this.files.get(fileName);
+ }
+
+ /**
+ * @param file The file to add.
+ * @return False if a file with the same name already exists. Otherwise, adds the file and returns true.
+ */
+ public void addFile(File file) {
+ this.files.put(file.getName(), file);
+ }
+
+ /**
+ * @param fileName The name of the file to delete.
+ * @return True if the file was deleted. False otherwise.
+ */
+ public boolean deleteFile(String fileName) {
+ if (this.files.containsKey(fileName)) {
+ this.files.remove(fileName);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @param file The file that needs a storage
+ * @param storage The storage name
+ * @return True if the storage was added to the file. False otherwise.
+ */
+ public boolean addStorage(File file, String storage) {
+ if (this.files.containsKey(file.getName())) {
+ return file.addStorage(storage);
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/app/src/main/java/lightcontainer/storage/json-example.json b/app/src/main/java/lightcontainer/storage/json-example.json
new file mode 100644
index 0000000..6f982b6
--- /dev/null
+++ b/app/src/main/java/lightcontainer/storage/json-example.json
@@ -0,0 +1,38 @@
+{
+ "unicast_port": 32000,
+ "multicast_ip": "224.25.0.1",
+ "multicast_port": 70000,
+ "network_interface": "LALALALA",
+ "tls": false,
+ "users": [
+ {
+ "username": "endmove",
+ "password": "notre-hash",
+ "aes_key": "jkjiezjijfizef8e4864",
+ "files": [
+ {
+ "file_name": "lol.jpeg",
+ "file_size": 15558,
+ "iv": "8d484e8e84 (lié à la méthode de cryptage)",
+ "storage": [
+ "lol.benjamin"
+ ]
+ },
+ {
+ "file_name": "super-man.png",
+ "file_size": 24457,
+ "iv": "zf4fe84f (lié à la méthode de cryptage)",
+ "storage": [
+ "lol.benjamin"
+ ]
+ }
+ ]
+ },
+ {
+ "username": "michel",
+ "password": "notre-hash du mdp",
+ "aes_key": "fjshufihehehuhuhuehu",
+ "files": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/src/main/java/lightcontainer/utils/AES_GCM.java b/app/src/main/java/lightcontainer/utils/AES_GCM.java
new file mode 100644
index 0000000..5aeed9d
--- /dev/null
+++ b/app/src/main/java/lightcontainer/utils/AES_GCM.java
@@ -0,0 +1,126 @@
+package lightcontainer.utils;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Base64;
+
+public class AES_GCM {
+ // Constants
+ public static final int AES_KEY_SIZE = 256;
+ public static final int GCM_IV_LENGTH = 16;
+ public static final int GCM_TAG_LENGTH = 16;
+
+ public static void main(String[] args) throws Exception
+ {
+ // Text pour test :
+ String plainText = "salut fils de pute";
+
+ String IV = generateIV();
+ String key = generateSecretKey();
+
+ System.out.println("Original Text : " + plainText);
+
+ byte[] cipherText = encrypt(plainText.getBytes(), key, IV);
+ System.out.println("Encrypted Text : " + Base64.getEncoder().encodeToString(cipherText));
+
+ String decryptedText = decrypt(cipherText, key, IV);
+ System.out.println("DeCrypted Text : " + decryptedText);
+ }
+
+ /**
+ * Decoder to decode base64 vector to byte vector.
+ * @param base64Vector A base64 encoded vector.
+ * @return Byte vector.
+ */
+ private static byte[] decodeBase64(String base64Vector) {
+ Base64.Decoder b64Decoder = Base64.getDecoder();
+ return b64Decoder.decode(base64Vector);
+ }
+
+ /**
+ * Encoder to encode vector to base64 string.
+ * @param rawVector A raw vector.
+ * @return A base64 encoded vector.
+ */
+ private static String encodeBase64(byte[] rawVector) {
+ Base64.Encoder b64Encoder = Base64.getEncoder();
+ return b64Encoder.encodeToString(rawVector);
+ }
+
+ /**
+ * Generate a secret key base64 encoded.
+ * @return New Secret key b64 encoded.
+ */
+ public static String generateSecretKey() throws NoSuchAlgorithmException {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
+ keyGenerator.init(AES_KEY_SIZE);
+ SecretKey key = keyGenerator.generateKey();
+ return encodeBase64(key.getEncoded());
+ }
+
+ /**
+ * Generate an IV (initialisation vector) base64 encoded.
+ * @return New generated IV b64 encoded.
+ */
+ public static String generateIV() {
+ byte[] IV = new byte[GCM_IV_LENGTH];
+ SecureRandom random = new SecureRandom();
+ random.nextBytes(IV);
+ return encodeBase64(IV);
+ }
+
+ /**
+ * Encrypt, with AES GCM.
+ * @param plainContent Content to encrypt.
+ * @param key Base64 encoded secret key.
+ * @param IV Base64 encoded vector.
+ * @return The encrypted cipherContent.
+ */
+ public static byte[] encrypt(byte[] plainContent, String key, String IV) throws Exception
+ {
+ // Get Cipher Instance
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+
+ // Create SecretKeySpec
+ SecretKeySpec keySpec = new SecretKeySpec(decodeBase64(key), "AES");
+
+ // Create GCMParameterSpec
+ GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, decodeBase64(IV));
+
+ // Initialize Cipher for ENCRYPT_MODE
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
+
+ // Perform Encryption
+ return cipher.doFinal(plainContent);
+ }
+
+ /**
+ * Decrypt, with AES GCM.
+ * @param cipherContent The encrypted cipherContent
+ * @param key Base64 encoded secret key.
+ * @param IV Base64 encoded vector.
+ * @return The decrypted plainContent.
+ */
+ public static String decrypt(byte[] cipherContent, String key, String IV) throws Exception
+ {
+ // Get Cipher Instance
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+
+ // Create SecretKeySpec
+ SecretKeySpec keySpec = new SecretKeySpec(decodeBase64(key), "AES");
+
+ // Create GCMParameterSpec
+ GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH*8, decodeBase64(IV));
+
+ // Initialize Cipher for DECRYPT_MODE
+ cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
+
+ // Perform Decryption
+ return new String(cipher.doFinal(cipherContent));
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/lightcontainer/utils/FileReceiver.java b/app/src/main/java/lightcontainer/utils/FileReceiver.java
new file mode 100644
index 0000000..c59452f
--- /dev/null
+++ b/app/src/main/java/lightcontainer/utils/FileReceiver.java
@@ -0,0 +1,37 @@
+package lightcontainer.utils;
+
+import java.io.BufferedOutputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+
+public class FileReceiver {
+ private static final int DEFAULT_BUFFER = 8000;
+ private String path;
+
+ public FileReceiver(String path) { this.path = path; }
+
+ public boolean receiveFile(InputStream input, String fileName, long fileSize) {
+ int bytesReceived = 0;
+ BufferedOutputStream bosFile = null;
+
+ try {
+ byte[] buffer = new byte[DEFAULT_BUFFER];
+ bosFile = new BufferedOutputStream(new FileOutputStream(String.format("%s/%s", path, fileName)));
+ long currentOffset = 0;
+
+ while((currentOffset < fileSize) && ((bytesReceived = input.read(buffer)) > 0)) {
+ bosFile.write(buffer, 0, bytesReceived);
+ currentOffset += bytesReceived;
+ }
+ bosFile.flush();
+ bosFile.close();
+
+ return true;
+ } catch(Exception ex) {
+ ex.printStackTrace();
+ if(bosFile != null) { try { bosFile.close(); } catch(Exception e) {} }
+ return false;
+ }
+ }
+
+}
diff --git a/app/src/main/java/lightcontainer/utils/FileSender.java b/app/src/main/java/lightcontainer/utils/FileSender.java
new file mode 100644
index 0000000..92f93f3
--- /dev/null
+++ b/app/src/main/java/lightcontainer/utils/FileSender.java
@@ -0,0 +1,35 @@
+package lightcontainer.utils;
+
+import java.io.*;
+
+public class FileSender {
+ private static final int DEFAULT_BUFFER=8000;
+ private String path;
+
+ public FileSender(String path) { this.path = path; }
+
+ public boolean sendFile(String filename, OutputStream out) {
+ BufferedInputStream bisFile = null;
+ int bytesReaded = 0;
+
+ try {
+ File f = new File(String.format("%s/%s", path, filename));
+ long fileSize = f.length();
+ if(f.exists()) {
+ byte[] buffer = new byte[DEFAULT_BUFFER];
+ bisFile = new BufferedInputStream(new FileInputStream(f));
+ long currentOffset = 0;
+ while((currentOffset < fileSize) && (bytesReaded = bisFile.read(buffer)) > 0) {
+ out.write(buffer, 0, bytesReaded); out.flush();
+ currentOffset+= bytesReaded;
+ }
+ bisFile.close();
+ return true;
+ } else
+ return false;
+ } catch(IOException ex) {
+ ex.printStackTrace();
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/lightcontainer/utils/NetChooser.java b/app/src/main/java/lightcontainer/utils/NetChooser.java
new file mode 100644
index 0000000..f9ecd98
--- /dev/null
+++ b/app/src/main/java/lightcontainer/utils/NetChooser.java
@@ -0,0 +1,69 @@
+package lightcontainer.utils;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Scanner;
+
+public class NetChooser {
+ private List interfaces;
+
+ public NetChooser() {
+ loadInterfaces();
+ Scanner console = new Scanner(System.in);
+ String[] allInterfaceNames = getInterfaces();
+ for(int index=0; index < allInterfaceNames.length; ++index) {
+ System.out.printf("%d. %s\n", index, allInterfaceNames[index]);
+ }
+ System.out.printf("Select your interface :");
+ NetworkInterface selected = getInterfacesByIndex(console.nextInt());
+ System.out.printf("Selected interface: %s\n", selected.getDisplayName());
+
+ }
+
+ private void loadInterfaces() {
+ try {
+ interfaces = new ArrayList<>();
+ Enumeration discoveredInterfaces = NetworkInterface.getNetworkInterfaces();
+ while (discoveredInterfaces.hasMoreElements()) {
+ NetworkInterface currentInterface = discoveredInterfaces.nextElement();
+ Enumeration inetAddresses = currentInterface.getInetAddresses();
+ int ipCount = 0;
+ while(inetAddresses.hasMoreElements()) {
+ InetAddress currentAddress = inetAddresses.nextElement();
+ ipCount++;
+ }
+ if(ipCount > 0)
+ interfaces.add(currentInterface);
+ }
+ } catch(SocketException ex) {
+ ex.printStackTrace();
+ }
+
+ }
+
+ public NetworkInterface getInterfacesByIndex(int i) {
+ if(i >= 0)
+ return interfaces.get(i);
+ else
+ return null;
+ }
+
+ public String[] getInterfaces() {
+ if(interfaces.size() > 0) {
+ String[] result = new String[interfaces.size()];
+ for (int i = 0; i < interfaces.size(); ++i) {
+ result[i] = interfaces.get(i).getDisplayName();
+ }
+ return result;
+ } else
+ return null;
+ }
+
+ public static void main(String[] args) {
+ new NetChooser();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/resources/rules.txt b/app/src/main/resources/rules.txt
new file mode 100644
index 0000000..8be9487
--- /dev/null
+++ b/app/src/main/resources/rules.txt
@@ -0,0 +1,43 @@
+//Standardized definitions
+digit = [0-9]
+port = (6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4})
+size = [0-9]{1,10}
+line = \r\n
+visiblechar = \p{Print}
+passchar = [^ !]
+binary = .
+password = [^ !]{5,50}
+bl = //espace
+letter = [A-Za-z]
+digit_letter = [A-Za-z0-9]
+filename = [^ !]{1,20}
+domain = [A-Za-z0-9.]{5,20}
+hash_filename = [A-Za-z0-9.]{50,200}
+hash_filecontent = [A-Za-z0-9.]{50,200}
+file_info = [A-Za-z0-9.]{50,200} [0-9]{1,10} [A-Za-z0-9.]{50,200}
+login = [A-Za-z0-9]{2,20}
+
+//StorBackEnd to FileFrontEnd
+sbe_hello = ^HELLO ([A-Za-z0-9.]{5,20}) ([\d]{0,5})\r\n$ //TODO \r\n -> à tester pour voir si déjà dans le flux ou doit être construit
+
+//FileFrontEnd to StorBackEnd
+ffe_sendfile = ^SENDFILE ([A-Za-z0-9.]{50,200} [0-9]{1,10} [A-Za-z0-9.]{50,200})\r\n$
+sbe_sendresult = ^(SEND_OK|SEND_ERROR)\r\n$
+ffe_erasefile = ^ERASEFILE ([A-Za-z0-9.]{50,200})\r\n$
+sbe_eraseresult = ^(ERASE_OK|ERASE_ERROR)\r\n$
+ffe_retrievefile = ^RETRIEVEFILE ([A-Za-z0-9.]{50,200})\r\n$
+sbe_retrieveresult = ^(RETRIEVE_OK ([A-Za-z0-9.]{50,200} [0-9]{1,10} [A-Za-z0-9.]{50,200})\r\n)|(RETRIEVE_ERROR\r\n)$
+
+//Client to FileFrontEnd
+client_signin = ^SIGNIN ([A-Za-z0-9]{2,20}) ([^ !]{5,50})\r\n$
+client_signup = ^SIGNUP ([A-Za-z0-9]{2,20}) ([^ !]{5,50})\r\n$
+ffe_signresult = ^(SIGN_OK|SIGN_ERROR)\r\n$
+client_filelist = ^FILELIST\r\n$
+ffe_filelistresult = ^FILES( ([^ !]{1,20})!([0-9]{1,10})){0,50}$
+client_savefile = ^SAVE_FILE ([^ !]{1,20}) ([0-9]{1,10})\r\n$
+ffe_savefileresult = ^(SAVEFILE_OK|SAVEFILE_ERROR)\r\n$
+client_getfile = ^GETFILE ([^ !]{1,20})\r\n$
+ffe_getfileresult = ^(GETFILE_OK (^ !]{1,20}) ([0-9]{1,10})\r\n)|(GETFILE_ERROR\r\n)$
+client_removefile = ^REMOVEFILE ([^ !]{1,20})\r\n$
+ffe_removefileresult = ^(REMOVEFILE_OK|REMOVEFILE_ERROR)\r\n$
+client_signout = ^SIGNOUT\r\n$
\ No newline at end of file
diff --git a/app/src/test/java/lightcontainer/AppTest.java b/app/src/test/java/lightcontainer/AppTest.java
new file mode 100644
index 0000000..9d64c1d
--- /dev/null
+++ b/app/src/test/java/lightcontainer/AppTest.java
@@ -0,0 +1,14 @@
+/*
+ * This Java source file was generated by the Gradle 'init' task.
+ */
+package lightcontainer;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class AppTest {
+// @Test void appHasAGreeting() {
+// App classUnderTest = new App();
+// assertNotNull(classUnderTest.getGreeting(), "app should have a greeting");
+// }
+}
diff --git a/app/src/test/java/lightcontainer/protocol/rules/reader/HelloRuleTest.java b/app/src/test/java/lightcontainer/protocol/rules/reader/HelloRuleTest.java
new file mode 100644
index 0000000..a356fb5
--- /dev/null
+++ b/app/src/test/java/lightcontainer/protocol/rules/reader/HelloRuleTest.java
@@ -0,0 +1,24 @@
+package lightcontainer.protocol.rules.reader;
+
+import lightcontainer.protocol.ProtocolReader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class HelloRuleTest {
+
+ @Test
+ public void whenRuleIsRightThenIsExecute() {
+ // GIVEN
+ ProtocolReader protocolReader = new HelloRule();
+ String request = "HELLO bento 42890\r\n";
+
+ // WHEN
+ HelloRule.Result ruleResult = protocolReader.execute(request);
+
+ // THEN
+ assertEquals("bento", ruleResult.getDomain());
+ assertEquals(42890, ruleResult.getPort());
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/test/java/lightcontainer/protocol/rules/writer/SignoutRuleTest.java b/app/src/test/java/lightcontainer/protocol/rules/writer/SignoutRuleTest.java
new file mode 100644
index 0000000..31bf799
--- /dev/null
+++ b/app/src/test/java/lightcontainer/protocol/rules/writer/SignoutRuleTest.java
@@ -0,0 +1,21 @@
+package lightcontainer.protocol.rules.writer;
+
+import lightcontainer.protocol.ProtocolWriter;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SignoutRuleTest {
+
+ @Test
+ public void whenRuleIsRightThenReturnCommand() {
+ //GIVEN
+ ProtocolWriter protocolWriter = new SignoutRule();
+ String[] datas = {};
+
+ //EXPECT
+ assertNotNull(protocolWriter.execute(datas));
+ assertEquals("SIGNOUT\r\n", protocolWriter.execute(datas));
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/test/java/lightcontainer/storage/JsonAdapterTests.java b/app/src/test/java/lightcontainer/storage/JsonAdapterTests.java
new file mode 100644
index 0000000..bd9006a
--- /dev/null
+++ b/app/src/test/java/lightcontainer/storage/JsonAdapterTests.java
@@ -0,0 +1,66 @@
+package lightcontainer.storage;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+
+public class JsonAdapterTests {
+
+ @Test
+ public void convertAppDataToJson() {
+ //GIVEN an AppData instance and a Json Adapter
+ AppData appData = AppData.getInstance();
+ AppConfig appConfig = AppConfig.getInstance();
+ appConfig.setUnicastPort(32000);
+ appConfig.setMulticastIp("224.25.0.1");
+ appConfig.setMulticastPort(15502);
+ appConfig.setNetworkInterface("My network interface");
+ appConfig.setTls(false);
+ Map files = new HashMap<>();
+ Set storage = new HashSet<>();
+ storage.add("StorBackEnd1");
+ File file1 = new File("File1", 15, "8d8d8d8d", storage);
+ files.put(file1.getName(), file1);
+ User user1 = new User("User1", "Password", "djdjjdj", files);
+ appData.setAppConfig(appConfig);
+ appData.addUser(user1);
+ JsonAdapter jsonAdapter = new JsonAdapter(appData);
+ //WHEN the adapter converts AppData to Json
+ String jsonAppData = jsonAdapter.toString();
+ //THEN
+ assertTrue(jsonAppData.contains("32000"));
+ assertTrue(jsonAppData.contains("224.25.0.1"));
+ assertTrue(jsonAppData.contains("15502"));
+ assertTrue(jsonAppData.contains("My network interface"));
+ assertTrue(jsonAppData.contains("false"));
+ assertTrue(jsonAppData.contains("User1"));
+ assertTrue(jsonAppData.contains("Password"));
+ assertTrue(jsonAppData.contains("djdjjdj"));
+ assertTrue(jsonAppData.contains("File1"));
+ assertTrue(jsonAppData.contains("15"));
+ assertTrue(jsonAppData.contains("8d8d8d8d"));
+ assertTrue(jsonAppData.contains("StorBackEnd1"));
+ }
+
+ @Test
+ public void convertJsonToAppData() {
+ //GIVEN a Json string
+ String json = "{\"unicast_port\":32000,\"multicast_ip\":\"224.25.0.1\",\"multicast_port\":15502,\"network_interface\":\"My network interface\",\"tls\":false,\"users\":[{\"name\":\"User1\",\"password\":\"Password\",\"aes_key\":\"djdjjdj\",\"files\":[{\"name\":\"File1\",\"size\":15,\"iv\":\"8d8d8d8d\",\"storage\":[\"StorBackEnd1\"]}]}]}";
+ //WHEN the adapter converts Json to Appdata
+ JsonAdapter jsonAdapter = new JsonAdapter(null);
+ AppData appData = jsonAdapter.fromString(json);
+ //THEN
+ assertNotNull(appData.getAppConfig());
+ assertEquals("My network interface", appData.getAppConfig().getNetworkInterface());
+ assertEquals(32000, appData.getAppConfig().getUnicastPort());
+ assertEquals("224.25.0.1", appData.getAppConfig().getMulticastIp());
+ assertEquals(15502, appData.getAppConfig().getMulticastPort());
+ assertFalse(appData.getAppConfig().isTls());
+ }
+}
diff --git a/app/src/test/java/lightcontainer/storage/RepositoryTests.java b/app/src/test/java/lightcontainer/storage/RepositoryTests.java
new file mode 100644
index 0000000..ed90a68
--- /dev/null
+++ b/app/src/test/java/lightcontainer/storage/RepositoryTests.java
@@ -0,0 +1,71 @@
+package lightcontainer.storage;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+
+public class RepositoryTests {
+
+ @AfterEach
+ public void destroyTestFile() {
+ try {
+ Files.deleteIfExists(Paths.get("src", "test", "resources", "test.json").toAbsolutePath());
+ } catch (IOException e) {
+ System.out.println("Error while destroying file");
+ }
+ }
+
+ @Test
+ public void save() {
+ //GIVEN an AppData instance and a Json Adapter
+ AppData appData = AppData.getInstance();
+ AppConfig appConfig = AppConfig.getInstance();
+ appConfig.setUnicastPort(32000);
+ appConfig.setMulticastIp("224.25.0.1");
+ appConfig.setMulticastPort(15502);
+ appConfig.setNetworkInterface("My network interface");
+ appConfig.setTls(false);
+ Map files = new HashMap<>();
+ Set storage = new HashSet<>();
+ storage.add("StorBackEnd1");
+ File file1 = new File("File1", 15, "8d8d8d8d", storage);
+ files.put(file1.getName(), file1);
+ User user1 = new User("User1", "Password", "djdjjdj", files);
+ appData.setAppConfig(appConfig);
+ appData.addUser(user1);
+ JsonAdapter jsonAdapter = new JsonAdapter(appData);
+ //WHEN Repository calls save method
+ String filePath = "src/test/resources/test.json";
+ Repository.save(filePath, jsonAdapter);
+ //THEN
+ assertTrue(Files.exists(Paths.get("src/test/resources/test.json").toAbsolutePath()));
+ }
+
+ @Test
+ public void load() {
+ //GIVEN a test json file loadTest.json
+ JsonAdapter jsonAdapter = new JsonAdapter(null);
+ //WHEN repository calls load method
+ AppData appData = Repository.load("src/test/resources/loadTest.json", jsonAdapter);
+ //THEN
+ assertNotNull(appData.getAppConfig());
+ assertEquals("My network interface", appData.getAppConfig().getNetworkInterface());
+ assertEquals(32000, appData.getAppConfig().getUnicastPort());
+ assertEquals("224.25.0.1", appData.getAppConfig().getMulticastIp());
+ assertEquals(15502, appData.getAppConfig().getMulticastPort());
+ assertFalse(appData.getAppConfig().isTls());
+ }
+}
diff --git a/app/src/test/resources/loadTest.json b/app/src/test/resources/loadTest.json
new file mode 100644
index 0000000..f06f3af
--- /dev/null
+++ b/app/src/test/resources/loadTest.json
@@ -0,0 +1,24 @@
+{
+ "unicast_port": 32000,
+ "multicast_ip": "224.25.0.1",
+ "multicast_port": 15502,
+ "network_interface": "My network interface",
+ "tls": false,
+ "users": [
+ {
+ "name": "User1",
+ "password": "Password",
+ "aes_key": "djdjjdj",
+ "files": [
+ {
+ "name": "File1",
+ "size": 15,
+ "iv": "8d8d8d8d",
+ "storage": [
+ "StorBackEnd1"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e708b1c..7454180 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index da9702f..69a9715 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 4f906e0..744e882 100644
--- a/gradlew
+++ b/gradlew
@@ -72,7 +72,7 @@ case "`uname`" in
Darwin* )
darwin=true
;;
- MINGW* )
+ MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e41310b
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,11 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ *
+ * The settings file is used to specify which projects to include in your build.
+ *
+ * Detailed information about configuring a multi-project build in Gradle can be found
+ * in the user manual at https://docs.gradle.org/7.1/userguide/multi_project_builds.html
+ */
+
+rootProject.name = 'FileFrontEnd'
+include('app')