Kaynağa Gözat

Merge branch 'develop' into us15

Adding a ConfigMock implementation which stubs the Config to return an
emtpy string. It easily be changed to fill the map with other stub data.
Jonas Pflanzer 4 yıl önce
ebeveyn
işleme
2b4d3394ab
55 değiştirilmiş dosya ile 3392 ekleme ve 1277 silme
  1. 55 3
      Client-Server Protocol.md
  2. 213 0
      GUI-CLI Protocol.md
  3. 2 1
      cli/CMakeLists.txt
  4. 0 0
      cli/include/base64.h
  5. 150 0
      cli/include/cmdman.h
  6. 0 37
      cli/include/commands.hpp
  7. 135 0
      cli/include/fileman.h
  8. 8 0
      cli/include/global.h
  9. 0 6
      cli/include/global.hpp
  10. 159 0
      cli/include/ioman.h
  11. 0 42
      cli/include/iomanager.hpp
  12. 31 0
      cli/include/machineioman.h
  13. 0 15
      cli/include/machineiomanager.hpp
  14. 78 0
      cli/include/userioman.h
  15. 0 15
      cli/include/useriomanager.hpp
  16. 36 27
      cli/src/base64.cpp
  17. 649 0
      cli/src/cmdman.cpp
  18. 0 51
      cli/src/commands.cpp
  19. 167 0
      cli/src/fileman.cpp
  20. 557 0
      cli/src/ioman.cpp
  21. 0 157
      cli/src/iomanager.cpp
  22. 38 0
      cli/src/machineioman.cpp
  23. 0 274
      cli/src/machineiomanager.cpp
  24. 11 17
      cli/src/main.cpp
  25. 182 0
      cli/src/userioman.cpp
  26. 0 401
      cli/src/useriomanager.cpp
  27. 0 1
      daemon/CMakeLists.txt
  28. 17 0
      daemon/Daemon-Config-Reference.md
  29. 34 0
      daemon/include/Config.h
  30. 8 1
      daemon/include/FileManager.h
  31. 1 1
      daemon/include/JsonCommander.h
  32. 69 0
      daemon/include/UserManager.h
  33. 1 4
      daemon/src/CMakeLists.txt
  34. 36 0
      daemon/src/Config.cpp
  35. 25 14
      daemon/src/FileManager.cpp
  36. 7 3
      daemon/src/JsonCommander.cpp
  37. 4 1
      daemon/src/Server.cpp
  38. 83 0
      daemon/src/UserManager.cpp
  39. 35 26
      daemon/src/base64.cpp
  40. 12 4
      daemon/src/main.cpp
  41. 2 5
      daemon/test/CMakeLists.txt
  42. 19 0
      daemon/test/ConfigMock.cpp
  43. 1 0
      gui/src/FooterForm.ui.qml
  44. 26 18
      gui/src/IpPopup.ui.qml
  45. 91 0
      gui/src/LoginForm.ui.qml
  46. 61 0
      gui/src/LoginSignupPopup.ui.qml
  47. 1 0
      gui/src/MessagesForm.ui.qml
  48. 45 1
      gui/src/ReceivingForm.ui.qml
  49. 111 0
      gui/src/SignupForm.ui.qml
  50. 0 97
      gui/src/SwitchIpPopupForm.ui.qml
  51. 8 0
      gui/src/main.cpp
  52. 6 5
      gui/src/main.qml
  53. 4 2
      gui/src/qml.qrc
  54. 191 44
      gui/src/qmlhandler.cpp
  55. 23 4
      gui/src/qmlhandler.h

+ 55 - 3
Client-Server Protocol.md

@@ -132,6 +132,7 @@ Server:
 	"error": string
 }
 ```
+`chunks` is the number of chunks that will be sent.
 
 #### 2.2.2 listdata - download file list
 Client:
@@ -142,6 +143,8 @@ Client:
 	"cancel": bool
 }
 ```
+Here `chunk` is the number of remaining chunks. (Thus, when requesting the first chunk the number of chunks - 1 is expected.)
+<br /><br />
 Server:
 ```
 {
@@ -169,7 +172,8 @@ Client:
 	"chunks": int
 }
 ```
-
+`chunks` is the number of chunks of the file to send.
+<br /><br />
 Server:
 ```
 {
@@ -207,7 +211,8 @@ Server:
 	"error": string
 }
 ```
-If `cancel` is `true` then the upload of the file is canceled and an error message should be in `error`.
+If `cancel` is `true` then the upload of the file is canceled and an error message should be in `error`. <br />
+Note that `received` is the number of remaining (!!!) chunks, here the server responds with the `remaining` value originally sent in the request.
 
 ### 2.4 Get command
 
@@ -246,6 +251,8 @@ Client:
 	"cancel": bool
 }
 ```
+
+Here `chunk` is a counter how many chunks will follow. (Thus, when requesting the first chunk, the number of chunks - 1 is expected.)
 If `cancel` is `true` then cancel the download of the file.
 <br /><br />
 Server:
@@ -260,9 +267,54 @@ Server:
 }
 ```
 `data` is a base64 string and contains a piece of the file. The client must request every chunk.
-`remaining` is a counter how many chunks will follow and the transfer is over after it hits `0`.
+`remaining` is a counter how many chunks will follow (so it equals the `chunk` value originally sent in the request) and the transfer is over after it hits `0`.
 If `cancel` is `true` then the download of the file is canceled and an error message should be in `error`.
 
+### 2.5 Head command
+
+The `head` command requests the first 4 bytes of `file` from the server for further inspection by the client.
+
+Client:
+```
+{
+	"command": "head",
+	"file": string
+}
+```
+
+Server:
+```
+{
+	"command": "head",
+	"accept": bool,
+	"file": string,
+	"data": string,
+	"error": string
+}
+```
+`data` is a base64 encoded string containing the requested bytes of the file.
+
+### 2.6 Deleteme command
+
+The `deleteme` command allows a logged in user to delete their account on the server. This action needs to be confirmed with the users password.
+
+Client:
+```
+{
+	"command": "deleteme",
+	"pass": string
+}
+```
+
+Server:
+```
+{
+	"command": "deleteme",
+	"accept": bool,
+	"error": string
+}
+```
+If `accept` is true the user has been deleted and the connection will be closed by the server.
 
 ### TODO
 

+ 213 - 0
GUI-CLI Protocol.md

@@ -0,0 +1,213 @@
+# GUI-CLI Protocol
+
+Protocol version: <b>"0.2"</b>
+
+Every json message must be minimized and be followed by a newline. This rule makes receiving and parsing the messages easier because you can read until a newline comes and you will parse every json message seperately because you won't read multiple json messages from the buffer at a time.
+
+## 0. Connect to the server
+
+The GUI creates a new instance of the CLI with the entered IP Address as a paramater. The CLI then tries to establish a connection to the given server and tells the GUI if the connection was successful.
+
+CLI:
+```
+{
+	"command": "connect",
+	"accept": bool,
+	"error": string
+}
+```
+
+## 1. Start a connection
+
+To connect to a server, the CLI has to do a version check. Once that is successful, the GUI will pass the username and password to establish a connection.
+
+### 1.1 Version check
+CLI:
+```
+{
+	"command": "version",
+	"serverversion": string,
+	"clientversion": string,
+	"accept": bool
+}
+```
+
+If `accept` is `true` the connection is valid. Else the connection will be terminated after the server answered.
+
+
+## 1.2 Login / Signup
+
+The GUI will send the username and password right after another into the pipe. If the response contains a "true" for accept, the login was successful.
+
+### 1.2.1 Login
+GUI:
+```
+	write: username
+	write: password
+```
+
+CLI:
+```
+{
+	"command": "login",
+	"accept": bool,
+	"error": string
+}
+```
+
+If `accept` is `true` the connection is valid and the user is logged in. Else `error` has an error string and the connection will be terminated after the server answered.
+
+### 1.2.2 Signup
+GUI:
+```
+	write: #ToBeAdded
+```
+
+Server:
+```
+{
+	"command": "signup",
+	#ToBeAdded
+}
+```
+
+If `accept` is `true` the connection is valid and the user is logged in and signed up. Else `error` has an error string and the connection will be terminated after the server answered.
+
+
+## 2. Sending commands
+
+Commands can be sent by the client after a connection has been negotiated (See 1).
+Commands can be used unrelated to each other.
+
+### 2.1 Status command
+GUI:
+```
+write: "status"
+```
+
+CLI:
+```
+{
+	"command": "status",
+	"response": string
+}
+```
+
+### 2.2 List command
+`list` to request a list download.
+
+#### 2.2.1 list - request file list download
+GUI:
+```
+{
+	write: "list"
+}
+```
+CLI:
+```
+{
+	"command": "list",
+  "accept": bool,
+	"names": string[],
+	"error": string
+}
+```
+
+
+### 2.3 Put command
+
+Put is split in two commands. `put` to request a file upload and `putdata` to get the information about an ongoing upload.
+
+#### 2.3.1 put - request upload
+GUI:
+```
+write: "put" file_path
+```
+
+CLI:
+```
+{
+	"command": "put",
+	"accept": bool,
+	"file": string,
+	"error": string
+}
+```
+If `accept` is `true` the connection is valid and the client can start sending the file. Else the put request was rejected and is hereby canceled. `error` should contain an error string if the request was rejected.
+
+
+#### 2.3.2 putdata - upload data
+CLI:
+```
+{
+	"command": "putdata",
+	"file": string,
+	"speed": float,
+	"cancel": bool,
+	"error": string
+}
+```
+If `cancel` is `true` then the upload of the file is canceled and an error message should be in `error`.
+`speed` shall contain a float describing the upload speed in kB/s.
+
+
+### 2.4 Get command
+
+Get is split in two commands. `get` to request a file download and `getdata` to get the information about an ongoing download.
+
+#### 2.4.1 get - request download
+
+GUI:
+```
+write: "get" file_name
+```
+
+CLI:
+```
+{
+	"command": "get",
+	"accept": bool,
+	"file": string,
+	"error": string
+}
+```
+If `accept` is `true` the connection is valid and the server can start sending the file. Else the get request was rejected and is hereby canceled. `error` should contain an error string if the request was rejected.
+
+#### 2.4.2 getdata - download data
+
+CLI:
+```
+{
+	"command": "getdata",
+	"file": string,
+	"speed": float,
+	"cancel": bool,
+	"error": string
+}
+```
+
+If `cancel` is `true` then the download of the file is canceled and an error message should be in `error`.
+`speed` shall contain a float describing the download speed in kB/s.
+
+
+### TODO
+
+
+## 3. Disconnect
+
+GUI:
+```
+{
+	write: "disconnect"
+}
+```
+
+CLI:
+```
+{
+	"command": "disconnect",
+	"accept": bool
+}
+```
+
+If `accept` is true, the connection is closed and the program can exit.

+ 2 - 1
cli/CMakeLists.txt

@@ -4,7 +4,8 @@ cmake_minimum_required(VERSION 2.8)
 
 project(ccats-cli)
 
-add_executable(ccats-cli src/main.cpp src/iomanager.cpp src/machineiomanager.cpp src/useriomanager.cpp src/commands.cpp src/base64.cpp)
+# add_executable(ccats-cli src/main.cpp src/ioman.cpp src/machineioman.cpp src/userioman.cpp src/base64.cpp src/netman.cpp src/cmdman.cpp src/fileman.cpp)
+add_executable(ccats-cli src/main.cpp src/ioman.cpp src/machineioman.cpp src/userioman.cpp src/base64.cpp src/cmdman.cpp src/fileman.cpp)
 
 # use pkg-config to find readline as it doesnt provide cmake files
 find_package(PkgConfig REQUIRED)

+ 0 - 0
cli/include/base64.hpp → cli/include/base64.h


+ 150 - 0
cli/include/cmdman.h

@@ -0,0 +1,150 @@
+#ifndef CMDMAN_H
+#define CMDMAN_H
+
+#include "fileman.h"
+
+#include <json/json.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+using std::map;
+using std::string;
+using std::vector;
+
+/**
+ * @class CmdMan
+ *
+ * A class that provides handling of user provided commands as well as
+ * commands as provided by json responses from a server.
+ */
+class CmdMan {
+public:
+  /**
+   * Flags for type of message returned in CmdRet.
+   * print	- print something to the user
+   * send	- send something to the server
+   * error	- an error occured, do not send to the server
+   * close	- terminate the connection
+   * seton	- contextually change state, used for version check and login
+   */
+  enum rettype {
+    print = (1 << 0),
+    send = (1 << 1),
+    error = (1 << 2),
+    close = (1 << 3),
+    seton = (1 << 4)
+  };
+  /**
+   * Response to user or command input
+   *
+   * msg is to be handled depending on type
+   * if nextcommand isnt an empty string it should be handled;
+   */
+  struct CmdRet {
+    unsigned int type;
+    string nextcommand;
+    Json::Value msg;
+  };
+  /**
+   * Constructor and destructor
+   */
+  CmdMan(FileMan &fm, void (*dpf)(string));
+  ~CmdMan();
+
+  /**
+   * Executes a user provided command with optional arguments
+   */
+  CmdRet execute(string cmd, vector<string> args);
+  /**
+   * Handles a server provided response json
+   */
+  CmdRet handle(Json::Value root);
+
+  /**
+   * Internal json reader
+   */
+  Json::CharReader *reader;
+
+private:
+  /**
+   * internal json writer and error string member
+   */
+  Json::StreamWriterBuilder wbuilder;
+  string jsonerror;
+
+  /**
+   * FileMan instance used to file commands
+   */
+  FileMan &fileman;
+
+  void (*debugprintfunc)(string);
+
+  /**
+   * Maps containing pointers to the appropriate member functions for executing
+   * or handling commands
+   */
+  map<string, CmdRet (CmdMan::*)(vector<string>)> execmap;
+  map<string, CmdRet (CmdMan::*)(Json::Value)> handlemap;
+  /**
+   * Map containing help strings to show to a user
+   */
+  map<string, string> helpmap;
+
+  /**
+   * State used to internally format received json to allow easy handling using
+   * handlemap and to disallow the use of certain commands when logged in/not
+   * logged in.
+   */
+  bool doversion, loginpossible, dologin, dosignup;
+
+  /**
+   * Help strings and method prototypes for commands to be used by a user
+   */
+  /* execute command descriptions and methods go here */
+  const string descHelp = "print available commands";
+  CmdRet cmdHelp(vector<string> args);
+  const string descStatus = "request status from server";
+  CmdRet cmdStatus(vector<string> args);
+  const string descDisconnect = "disconnect from server";
+  CmdRet cmdDisconnect(vector<string> args);
+  const string descPut = "upload file to server and add to queue";
+  CmdRet cmdPut(vector<string> args);
+  const string descGet = "retrieve file from server";
+  CmdRet cmdGet(vector<string> args);
+  const string descList = "list files available on server";
+  CmdRet cmdList(vector<string> args);
+
+  const string descLogin = "login to the server";
+  CmdRet cmdLogin(vector<string> args);
+  const string descSignup = "sign up and login to the server";
+  CmdRet cmdSignup(vector<string> args);
+
+  /**
+   * Method prototypes for commands used internally
+   */
+  /* internal execute commands */
+  CmdRet cmdVersion(vector<string> args);
+  CmdRet cmdPutdata(vector<string> args);
+  CmdRet cmdGetdata(vector<string> args);
+  CmdRet cmdListdata(vector<string> args);
+
+  /**
+   * Method prototypes for handling json responses
+   */
+  /* handle commands go here */
+  CmdRet handleStatus(Json::Value);
+  CmdRet handleClose(Json::Value);
+  CmdRet handlePut(Json::Value);
+  CmdRet handleGet(Json::Value);
+  CmdRet handleList(Json::Value);
+  CmdRet handlePutdata(Json::Value);
+  CmdRet handleGetdata(Json::Value);
+  CmdRet handleListdata(Json::Value);
+  CmdRet handleVersion(Json::Value);
+  CmdRet handleLogin(Json::Value);
+  CmdRet handleSignup(Json::Value);
+};
+
+#endif

+ 0 - 37
cli/include/commands.hpp

@@ -1,37 +0,0 @@
-#ifndef COMMANDS_HPP
-#define COMMANDS_HPP
-
-#include <cstring>
-#include <cctype>
-
-#define COMMANDLEN 10
-#define sizeofarr(a) (sizeof(a) / sizeof(a[0]))
-
-typedef enum {
-  CMD_HELP,
-  CMD_CONNECT,
-  CMD_DISCONNECT,
-  CMD_PUT,
-  CMD_REMOVE,
-  CMD_GET,
-  CMD_LIST,
-  CMD_STATUS,
-  CMD_SETUP,
-  CMD_LOG,
-  CMD_UNKNOWN
-} COMMANDID;
-
-typedef struct {
-  COMMANDID cmd;
-  const char *name;
-  const char *desc;
-} CMD;
-
-extern CMD commands[];
-
-COMMANDID getCmdIdFromString(const char *str);
-const char *getCmdStringFromId(COMMANDID id);
-// TODO
-void printCmds(void);
-
-#endif

+ 135 - 0
cli/include/fileman.h

@@ -0,0 +1,135 @@
+#ifndef FILEMAN_H
+#define FILEMAN_H
+
+#include <fstream>
+#include <string>
+#include <vector>
+
+#define BLOCKSIZE 8
+
+/**
+ * @class FileMan
+ *
+ * Provides File I/O abstraction
+ */
+class FileMan {
+private:
+  /**
+   * Internal state
+   *
+   * Filestreams for put and get
+   * Vector for holding received filenames from listing
+   * Paths and filenames for put and get
+   * Size for internal read
+   * Total and Remaining chunks for put, get and list
+   * Boolean replacement for filestreams being open for list
+   *
+   */
+  std::ifstream putfile;
+  std::ofstream getfile;
+  std::vector<std::string> listdata;
+  std::string getpath, getname, putpath, putname;
+  const unsigned int max_read_len = 8;
+  int putsize;
+  int putchunks;
+  int putchunksRemaining;
+  int getchunks;
+  int getchunksRemaining;
+  int listchunks;
+  int listchunksRemaining;
+
+  bool islisting;
+
+public:
+  /**
+   * Constructor and Destructor
+   */
+  FileMan();
+  ~FileMan();
+
+  /**
+   * Query internal state
+   *
+   * Return true if the corresponding action is being performed, false otherwise
+   */
+  bool isGetting();
+  bool isPutting();
+  bool isListing();
+
+  /**
+   * Check for and prepare state and streams for reading/writing
+   *
+   * Return true if successful, false otherwise
+   */
+  bool openPut(const std::string &path);
+  bool openGet(const std::string &path);
+  bool openList();
+
+  /**
+   * Close the respective filestream
+   */
+  void closePut();
+  void closeGet();
+  void closeList();
+
+  /**
+   * Query the names of the file currently being put or get
+   */
+  std::string getPutName();
+  std::string getGetName();
+
+  /**
+   * Cancel a put, get or list, depreparing internal state (closing streams if
+   * required)
+   */
+  void cancelPut();
+  void cancelGet();
+  void cancelList();
+
+  /**
+   * Read max_rea_len bytes from the current file opened for put
+   */
+  std::vector<char> readPut();
+  /**
+   * Write the provided vector to the current file opened for get
+   */
+  void writeGet(std::vector<char> data);
+
+  /**
+   * Wrapper methods for reading and writing base64 encoded data instead of raw
+   * bytes
+   */
+  std::string readBase64();
+  void writeBase64(std::string data);
+
+  /**
+   * read and write emulating methods for list
+   */
+  void putListData(std::vector<std::string> names);
+  std::vector<std::string> getListData();
+
+  /**
+   * Query internal state, requesting the corresponding size
+   */
+  int getPutChunks();
+  int getGetChunks();
+  int getListChunks();
+  int getPutRemainingChunks();
+  int getGetRemainingChunks();
+  int getListRemainingChunks();
+  int getPutSize();
+
+  /**
+   * Set internal state, adjusting the chunks as well as chunks remaining for
+   * get and list
+   */
+  void setGetChunks(int chunks);
+  void setListChunks(int chunks);
+
+  /**
+   * Returns the filename of the passed (relative) path of a file
+   */
+  std::string pathToFilename(std::string path);
+};
+
+#endif

+ 8 - 0
cli/include/global.h

@@ -0,0 +1,8 @@
+#ifndef GLOBAL_H
+#define GLOBAL_H
+
+#include <string>
+
+const std::string protocolVersion = "0.2";
+
+#endif

+ 0 - 6
cli/include/global.hpp

@@ -1,6 +0,0 @@
-#ifndef GLOBAL_HPP
-#define GLOBAL_HPP
-
-#define VERSION "0.1"
-
-#endif

+ 159 - 0
cli/include/ioman.h

@@ -0,0 +1,159 @@
+#ifndef IOMAN_H
+#define IOMAN_H
+
+#include "cmdman.h"
+#include "fileman.h"
+
+#include <boost/asio.hpp>
+#include <condition_variable>
+#include <json/json.h>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <vector>
+
+using boost::asio::ip::tcp;
+
+/**
+ * @class IoMan
+ *
+ * Input/Output manager
+ *
+ * Provides the glue logic for getting user and network input
+ * as well as providing user and network output
+ */
+class IoMan {
+  /* this is technically private and protected stuff which needs to be public
+   * for the readline callback */
+public:
+  /**
+   * Type of message to be output
+   */
+  enum OutMsgType { normal, error, debug };
+  /**
+   * Output msg to the user, treating it as type
+   */
+  virtual void printMessage(std::string msg, OutMsgType type);
+  /**
+   * Flag wether to keep the main thread running
+   * Matching mutex to provide syncthonizes access
+   */
+  bool runmain;
+  std::mutex mainmutex;
+  /**
+   * Vector to hold input generated/fetched locally (e.g. from the user)
+   * Matching condition variable to wake up waiting threads
+   * Matching mutex to provide synchronized access
+   */
+  std::mutex localmutex;
+  std::vector<std::string> localinput;
+  std::condition_variable localcv;
+
+private:
+  /**
+   * Internal state to provide class-wide asio networking functionality
+   */
+  boost::asio::io_service ios;
+  boost::asio::streambuf recvbuf;
+  boost::system::error_code errcode;
+  tcp::socket *tcpsock;
+  /**
+   * The IP and port to connect to
+   * Flag telling wether one is connected
+   */
+  std::string ipstring;
+  unsigned short port;
+  bool connected;
+  /**
+   * Instances of CmdMan and FileMan to process user input and handle File I/O
+   */
+  CmdMan cmdman;
+  FileMan fileman;
+
+  /**
+   * Thread handles for processing local and network input as well as generating
+   * responses to both Matching mutexes for the flags wether the threads should
+   * keep running Function prototypes for the main thread functions
+   */
+  std::thread tinput, tnetwork, tresponse;
+  std::mutex inputmutex, networkmutex, responsemutex;
+  bool runinput, runnetwork, runresponse;
+  void networkMain();
+  void inputMain();
+  void responseMain();
+
+  /**
+   * Vector to hold preprocessed input from the network
+   * Matching condition variable to wake up waiting threads
+   * Matching mutex to provide synchronized access
+   */
+  std::vector<Json::Value> netinput;
+  std::mutex netmutex;
+  std::condition_variable netcv;
+
+  /**
+   * Class-wide json functionality
+   */
+  Json::CharReader *reader;
+  Json::StreamWriterBuilder wbuilder;
+  string jsonerror;
+
+  /**
+   * Enum for internal login and version state
+   * Variables for keeping said state
+   * Matching mutex and condition variable
+   */
+  enum Status { off, on, err };
+  Status versionstatus;
+  Status loginstatus;
+  std::mutex initmutex;
+  std::condition_variable initcv;
+
+  /**
+   * Tokenizes input based on space as seperator
+   * Respects double-quoted tokens
+   * Returns a vector with the tokens as elements in order
+   */
+  std::vector<std::string> tokenizeInput(std::string in);
+
+protected:
+  /**
+   * Prompt messages for readline prompt
+   */
+  virtual void printWelcomeMessage() = 0;
+  virtual std::string getCmdPrompt() = 0;
+  // following prompts currently not in use because login happens by entering
+  // command
+  //~  virtual std::string getUserPrompt() = 0;
+  //~  virtual std::string getPassPrompt() = 0;
+
+public:
+  /**
+   * Constructor and destructor
+   */
+  IoMan(char *ipcstring);
+  virtual ~IoMan();
+
+  /**
+   * Establish connection to server and perform vesion check
+   * Return true if successful, false otherwise
+   */
+  bool init();
+
+  /**
+   * Main loop, call init first
+   */
+  void run();
+
+  /**
+   * Establish connection to server
+   * Return true if successful, false otherwise
+   */
+  bool connect();
+  /**
+   * Disconnect from a connected server
+   */
+  void disconnect();
+};
+
+#endif

+ 0 - 42
cli/include/iomanager.hpp

@@ -1,42 +0,0 @@
-#ifndef IOMANAGER_HPP
-#define IOMANAGER_HPP
-
-#include "global.hpp"
-
-#include <string>
-#include <boost/asio.hpp>
-#include <json/json.h>
-
-#define BLOCKSIZE 8
-
-using boost::asio::ip::tcp;
-
-class IoManager {
-protected:
-	boost::asio::io_service ios;
-	tcp::socket *tcpsock;
-	boost::system::error_code errcode;
-	std::string ipstring;
-	int port;
-
-	std::string jsonerror;
-
-	Json::CharReaderBuilder rbuilder;
-	Json::CharReader* reader;
-	Json::StreamWriterBuilder wbuilder;
-
-	bool sendJson(Json::Value root);
-	bool receiveJson(boost::asio::streambuf &recvbuf);
-	bool parseJson(Json::Value *root, boost::asio::streambuf &recvbuf);
-public:
-	// Basic constructor
-	IoManager(char *ipcstring);
-	// destructor to clean up all generic stuff
-	~IoManager();
-	// enters loop to handle further interaction based on derived class
-	virtual void run() = 0;
-	// tries to establish connection, returns false on error
-	bool connect();
-};
-
-#endif

+ 31 - 0
cli/include/machineioman.h

@@ -0,0 +1,31 @@
+#ifndef MACHINEIOMAN_H
+#define MACHINEIOMAN_H
+
+#include "ioman.h"
+
+/**
+ * @class MachineIoMan
+ *
+ * Provides specific implementations of IoMan outputs and prompts
+ * for interactive non-user sessions
+ */
+class MachineIoMan : public IoMan {
+public:
+  using IoMan::IoMan;
+
+protected:
+  /**
+   * Specific implementations for printing messages
+   */
+  void printMessage(std::string msg, OutMsgType type);
+  void printWelcomeMessage();
+
+  /**
+   * Return the specific prompt strings for IoMan prompts
+   */
+  std::string getCmdPrompt();
+  std::string getUserPrompt();
+  std::string getPassPrompt();
+};
+
+#endif

+ 0 - 15
cli/include/machineiomanager.hpp

@@ -1,15 +0,0 @@
-#ifndef MACHINEIOMANAGER_HPP
-#define MACHINEIOMANAGER_HPP
-
-#include "iomanager.hpp"
-
-class MachineIoManager : public IoManager {
-
-public:
-	// MachineIoManager(char *ipstring) : IoManager(ipstring) {};
-	using IoManager::IoManager;
-	// enters loop to handle machine based interaction (other frontends)
-	void run();
-};
-
-#endif

+ 78 - 0
cli/include/userioman.h

@@ -0,0 +1,78 @@
+#ifndef USERIOMAN_H
+#define USERIOMAN_H
+
+#include "ioman.h"
+
+/**
+ * @class UserIoMan
+ *
+ * Provides specific implementations of IoMan outputs and prompts
+ * for interactive user sessions
+ */
+class UserIoMan : public IoMan {
+private:
+  /**
+   * Map containing pointers to the appropriate member functions for printing
+   * formatted json output
+   */
+  map<string, void (UserIoMan::*)(Json::Value)> printmap;
+
+  /**
+   * Class-wide json functionality
+   */
+  Json::CharReader *reader;
+  Json::StreamWriterBuilder wbuilder;
+  string jsonerror;
+
+  /**
+   * Format and pretty print json for terminal output
+   */
+  void printJson(Json::Value root);
+
+  /**
+   * Method prototypes for printing json output
+   */
+  /* printing commands go here */
+  void printError(Json::Value root);
+  void printConnect(Json::Value root);
+  void printHelp(Json::Value root);
+  void printStatus(Json::Value root);
+  void printDisconnect(Json::Value root);
+  void printPut(Json::Value root);
+  void printGet(Json::Value root);
+  void printList(Json::Value root);
+  void printVersion(Json::Value root);
+  void printLogin(Json::Value root);
+  void printSignup(Json::Value root);
+  void printPutdata(Json::Value root);
+  void printGetdata(Json::Value root);
+  void printListdata(Json::Value root);
+
+  /**
+   * Mutex for synchronized message output
+   */
+  std::recursive_mutex msgmutex;
+
+public:
+  /**
+   * Constructor and destructor
+   */
+  UserIoMan(char *ipcstring);
+  ~UserIoMan();
+
+protected:
+  /**
+   * Specific implementations for printing messages
+   */
+  void printMessage(std::string msg, OutMsgType type);
+  void printWelcomeMessage();
+
+  /**
+   * Return the specific prompt strings for IoMan prompts
+   */
+  std::string getCmdPrompt();
+  std::string getUserPrompt();
+  std::string getPassPrompt();
+};
+
+#endif

+ 0 - 15
cli/include/useriomanager.hpp

@@ -1,15 +0,0 @@
-#ifndef USERIOMANAGER_HPP
-#define USERIOMANAGER_HPP
-
-#include "iomanager.hpp"
-
-class UserIoManager : public IoManager {
-
-public:
-	// UserIoManager(char *ipstring) : IoManager(ipstring) {};
-	using IoManager::IoManager;
-	// enters loop to handle user based interaction (from the terminal)
-	void run();
-};
-
-#endif

+ 36 - 27
cli/src/base64.cpp

@@ -1,40 +1,49 @@
-#include "../include/base64.hpp"
+#include "../include/base64.h"
 
-#include <boost/algorithm/string.hpp>
-#include <boost/archive/iterators/base64_from_binary.hpp>
-#include <boost/archive/iterators/binary_from_base64.hpp>
-#include <boost/archive/iterators/transform_width.hpp>
+#include <boost/beast.hpp>
 
 std::string base64::decode(const std::string &val) {
-  using namespace boost::archive::iterators;
-  using It =
-      transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>;
-  return boost::algorithm::trim_right_copy_if(
-      std::string(It(std::begin(val)), It(std::end(val))),
-      [](char c) { return c == '\0'; });
+  std::string ret;
+  ret.resize(boost::beast::detail::base64::decoded_size(val.size()));
+
+  std::size_t read = boost::beast::detail::base64::decode(
+                         &ret.front(), val.c_str(), val.size())
+                         .first;
+
+  ret.resize(read);
+  return ret;
 }
 
 std::vector<char> base64::decodeVector(const std::string &val) {
-  using namespace boost::archive::iterators;
-  using It =
-      transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>;
-  return boost::algorithm::trim_right_copy_if(
-      std::vector<char>(It(std::begin(val)), It(std::end(val))),
-      [](char c) { return c == '\0'; });
+  std::vector<char> ret;
+  ret.resize(boost::beast::detail::base64::decoded_size(val.size()));
+
+  std::size_t read =
+      boost::beast::detail::base64::decode(ret.data(), val.c_str(), val.size())
+          .first;
+
+  ret.resize(read);
+  return ret;
 }
 
 std::string base64::encode(const std::string &val) {
-  using namespace boost::archive::iterators;
-  using It =
-      base64_from_binary<transform_width<std::string::const_iterator, 6, 8>>;
-  auto tmp = std::string(It(std::begin(val)), It(std::end(val)));
-  return tmp.append((3 - val.size() % 3) % 3, '=');
+  std::string ret;
+  ret.resize(boost::beast::detail::base64::encoded_size(val.size()));
+
+  std::size_t read = boost::beast::detail::base64::encode(
+      &ret.front(), val.data(), val.size());
+
+  ret.resize(read);
+  return ret;
 }
 
 std::string base64::encodeVector(const std::vector<char> &val) {
-  using namespace boost::archive::iterators;
-  using It = base64_from_binary<
-      transform_width<std::vector<char>::const_iterator, 6, 8>>;
-  auto tmp = std::string(It(std::begin(val)), It(std::end(val)));
-  return tmp.append((3 - val.size() % 3) % 3, '=');
+  std::string ret;
+  ret.resize(boost::beast::detail::base64::encoded_size(val.size()));
+
+  std::size_t read = boost::beast::detail::base64::encode(
+      &ret.front(), val.data(), val.size());
+
+  ret.resize(read);
+  return ret;
 }

+ 649 - 0
cli/src/cmdman.cpp

@@ -0,0 +1,649 @@
+#include "../include/cmdman.h"
+#include "../include/global.h"
+
+#include <iostream>
+
+#define DEBUGPRINT(x) debugprintfunc(x)
+//~ #define DEBUGPRINT(x) std::cerr << x
+//~ #define DEBUGPRINT(x)
+
+CmdMan::CmdMan(FileMan &fm, void (*dpf)(string)) : fileman(fm) {
+
+  /* setup json stuff */
+  Json::CharReaderBuilder rbuilder;
+  wbuilder.settings_["indentation"] = "";
+  reader = rbuilder.newCharReader();
+
+  doversion = false;
+  loginpossible = false;
+  dologin = false;
+  dosignup = false;
+
+  /* initialize execute command map */
+  execmap["help"] = &CmdMan::cmdHelp;
+  execmap["status"] = &CmdMan::cmdStatus;
+  execmap["disconnect"] = &CmdMan::cmdDisconnect;
+  execmap["put"] = &CmdMan::cmdPut;
+  execmap["get"] = &CmdMan::cmdGet;
+  execmap["list"] = &CmdMan::cmdList;
+  execmap["version"] = &CmdMan::cmdVersion;
+  execmap["login"] = &CmdMan::cmdLogin;
+  execmap["signup"] = &CmdMan::cmdSignup;
+  execmap["putdata"] = &CmdMan::cmdPutdata;
+  execmap["getdata"] = &CmdMan::cmdGetdata;
+
+  /* initialize description map */
+  helpmap["help"] = descHelp;
+  helpmap["status"] = descStatus;
+  helpmap["disconnect"] = descDisconnect;
+  helpmap["put"] = descPut;
+  helpmap["get"] = descGet;
+  helpmap["list"] = descList;
+
+  /* initialize handle command map */
+  handlemap["status"] = &CmdMan::handleStatus;
+  handlemap["close"] = &CmdMan::handleClose;
+  handlemap["put"] = &CmdMan::handlePut;
+  handlemap["get"] = &CmdMan::handleGet;
+  handlemap["putdata"] = &CmdMan::handlePutdata;
+  handlemap["getdata"] = &CmdMan::handleGetdata;
+  handlemap["list"] = &CmdMan::handleList;
+  handlemap["version"] = &CmdMan::handleVersion;
+  handlemap["login"] = &CmdMan::handleLogin;
+  handlemap["signup"] = &CmdMan::handleSignup;
+
+  debugprintfunc = dpf;
+}
+
+CmdMan::~CmdMan() { delete reader; }
+
+CmdMan::CmdRet CmdMan::cmdHelp(vector<string> args) {
+  CmdRet retval;
+  Json::Value root, arr;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  map<string, string>::iterator it;
+  root["command"] = "help";
+  for (it = helpmap.begin(); it != helpmap.end(); it++) {
+    arr.append(it->first + " - " + it->second);
+  }
+  root["names"] = arr;
+  retval.type = print;
+  retval.msg = root;
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdStatus(vector<string> args) {
+  CmdRet retval;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  Json::Value root;
+  root["command"] = "status";
+  retval.type = send;
+  retval.msg = root;
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdDisconnect(vector<string> args) {
+  CmdRet retval;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  Json::Value root;
+  retval.type = send;
+  if (loginpossible) {
+    // not logged in, send appropriate login message instead of normal close
+    root["login"] = false;
+    root["user"] = "";
+    root["pass"] = "";
+    root["cancel"] = true;
+    retval.type |= close;
+  } else {
+    root["command"] = "close";
+  }
+  retval.msg = root;
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdPut(vector<string> args) {
+  CmdRet retval;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  Json::Value root;
+
+  bool opened = fileman.openPut(args[0]);
+  root["command"] = "put";
+  root["file"] = fileman.getPutName();
+  if (opened) {
+    root["size"] = fileman.getPutSize();
+    root["chunks"] = fileman.getPutChunks();
+    retval.type = send;
+  } else {
+    retval.type = error;
+    root["accept"] = false;
+    root["error"] = "couldnt open local file \"" + args[0] + "\"";
+  }
+
+  retval.msg = root;
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdPutdata(vector<string> args) {
+  CmdRet retval;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  Json::Value root;
+
+  root["command"] = "putdata";
+  root["file"] = fileman.getPutName();
+  root["cancel"] = false;
+  root["data"] = fileman.readBase64();
+  root["remaining"] =
+      fileman
+          .getPutRemainingChunks(); // number already decremented by readBase64
+  retval.type = send;
+  retval.msg = root;
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdGet(vector<string> args) {
+  CmdRet retval;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  Json::Value root;
+
+  bool opened = fileman.openGet(args[0]);
+  root["command"] = "get";
+  root["file"] = fileman.getGetName();
+  if (opened) {
+    retval.type = send;
+  } else {
+    retval.type = error;
+    root["accept"] = false;
+    root["error"] = "local file \"" + args[0] + "\" already exists";
+  }
+
+  retval.msg = root;
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdGetdata(vector<string> args) {
+  CmdRet retval;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  Json::Value root;
+
+  root["command"] = "getdata";
+  root["file"] = fileman.getGetName();
+  root["chunk"] = fileman.getGetRemainingChunks();
+  root["cancel"] = false;
+  retval.type = send;
+  retval.msg = root;
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdList(vector<string> args) {
+  CmdRet retval;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  Json::Value root;
+
+  bool opened = fileman.openList();
+  root["command"] = "list";
+  if (opened) {
+    retval.type = send;
+  } else {
+    retval.type = error;
+    root["accept"] = false;
+    root["names"] = "";
+    root["error"] = "cannot list, already listing";
+  }
+
+  retval.msg = root;
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdListdata(vector<string> args) {
+  CmdRet retval;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  Json::Value root;
+  root["command"] = "listdata";
+  root["chunk"] = fileman.getListRemainingChunks();
+  root["cancel"] = false;
+  retval.type = send;
+  retval.msg = root;
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::execute(string cmd, vector<string> args) {
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " using command \"" + cmd +
+             "\" with arguments [ ");
+  for (string s : args)
+    DEBUGPRINT(s + " ");
+  DEBUGPRINT("]");
+  map<string, CmdRet (CmdMan::*)(vector<string>)>::iterator it =
+      execmap.find(cmd);
+  CmdRet retval;
+  Json::Value root;
+  root["command"] = cmd;
+  if (it == execmap.end()) {
+    retval.type = error;
+    root["command"] = "error";
+    root["error"] = string(__PRETTY_FUNCTION__) + " unknown command \"" + cmd +
+                    "\".\ntype help to list available commands.";
+    retval.msg = root;
+    return retval;
+  } else if (loginpossible || dologin || dosignup) {
+    DEBUGPRINT("execute does login");
+    DEBUGPRINT(string("comparison is ") +
+               std::to_string(cmd.compare("login") && cmd.compare("signup") &&
+                              cmd.compare("disconnect") &&
+                              cmd.compare("help")));
+    if (cmd.compare("login") && cmd.compare("signup") &&
+        cmd.compare("disconnect") && cmd.compare("help")) {
+      retval.type = error;
+      root["command"] = "error";
+      root["error"] =
+          string("Not logged in. Available commands are limited to ") +
+          "login" + ", " + "signup" + " and " + "disconnect" + "\n" +
+          "Use help for usage of these commands.";
+      retval.msg = root;
+      return retval;
+    }
+  }
+  return (this->*(execmap[cmd]))(args);
+}
+
+/* login and signup commands */
+CmdMan::CmdRet CmdMan::cmdLogin(vector<string> args) {
+  CmdRet retval;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  Json::Value root;
+
+  if (loginpossible) {
+    dologin = true;
+    loginpossible = false;
+    root["user"] = args[0];
+    root["pass"] = args[1];
+    root["login"] = true;
+    root["cancel"] = false;
+    retval.type = send;
+  } else {
+    root["command"] = "login";
+    root["error"] = "Login not possible, because you already requested a login "
+                    "or you are logged in";
+    root["accept"] = false;
+    retval.type = error;
+  }
+
+  retval.msg = root;
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdSignup(vector<string> args) {
+  CmdRet retval;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  Json::Value root;
+
+  if (loginpossible) {
+    dosignup = true;
+    loginpossible = false;
+    root["user"] = args[0];
+    root["pass"] = args[1];
+    root["login"] = true;
+    root["cancel"] = false;
+    retval.type = send;
+  } else {
+    root["command"] = "signup";
+    root["error"] = "Signup not possible, because you already requested a "
+                    "login or you are logged in";
+    root["accept"] = false;
+    retval.type = error;
+  }
+
+  retval.msg = root;
+  return retval;
+}
+
+/* internal commands */
+CmdMan::CmdRet CmdMan::cmdVersion(vector<string> args) {
+  CmdRet retval;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  Json::Value root;
+  root["version"] = protocolVersion;
+  retval.type = send;
+  retval.msg = root;
+
+  doversion = true;
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handle(Json::Value root) {
+  CmdRet retval;
+  Json::Value output;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  if (doversion)
+    root["command"] = "version";
+  else if (dosignup)
+    root["command"] = "signup";
+  else if (dologin)
+    root["command"] = "login";
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " using json\n" +
+             Json::writeString(wbuilder, root) + "\n");
+  string retmsg;
+  map<string, CmdRet (CmdMan::*)(Json::Value)>::iterator it =
+      handlemap.find(root["command"].asString());
+
+  if (it == handlemap.end()) {
+    retval.type = error;
+    output["command"] = "error";
+    output["error"] = string(__PRETTY_FUNCTION__) + " unknown command \"" +
+                      root["command"].asString() +
+                      "\".\nEnsure code is implemented.";
+    retval.msg = output;
+    return retval;
+  }
+  return (this->*(handlemap[root["command"].asString()]))(root);
+}
+
+CmdMan::CmdRet CmdMan::handleStatus(Json::Value root) {
+  CmdRet retval;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  retval.type = print;
+  retval.msg = root;
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleClose(Json::Value root) {
+  CmdRet retval;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  retval.type = close;
+  retval.msg = root;
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handlePut(Json::Value root) {
+  CmdRet retval;
+  Json::Value output;
+  output["command"] = "put";
+  output["file"] = fileman.getPutName();
+  output["accept"] = false;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+
+  if (!root["accept"].asBool()) {
+    retval.type = error;
+    output["error"] = "Server reports: " + root["error"].asString();
+    fileman.cancelPut();
+  } else if (root["file"].asString() != fileman.getPutName()) {
+    retval.type = error;
+    output["error"] = "Server reports filename " + root["file"].asString() +
+                      " but actual filename is " + fileman.getPutName();
+    fileman.cancelPut();
+  } else {
+    output["accept"] = true;
+    output["error"] = "";
+    retval.type = print | send;
+    retval.nextcommand = "putdata";
+  }
+  retval.msg = output;
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handlePutdata(Json::Value root) {
+  CmdRet retval;
+  Json::Value output;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  output["command"] = "putdata";
+  output["file"] = fileman.getPutName();
+  output["speed"] = 0.0f; // TODO
+  output["cancel"] = true;
+
+  if (root["received"].asInt() != fileman.getPutRemainingChunks()) {
+    // the number of remaining chunks received from the daemon does not equal
+    // the number stored at the client side
+    retval.type = error;
+    output["error"] = std::string("Server reports number of "
+                                  "remaining chunks as ") +
+                      std::to_string(root["received"].asInt()) +
+                      " but actual number is " +
+                      std::to_string(fileman.getPutRemainingChunks());
+    fileman.cancelPut();
+  } else if (root["cancel"].asBool()) {
+    retval.type = error;
+    output["error"] = "Server reports: " + root["error"].asString();
+    fileman.cancelPut();
+  } else if (root["file"].asString() != fileman.getPutName()) {
+    retval.type = error;
+    output["error"] = "Server reports filename " + root["file"].asString() +
+                      " but actual filename is " + fileman.getPutName();
+    fileman.cancelPut();
+  } else {
+    output["cancel"] = false;
+    output["error"] = "";
+    // sent successfully
+    if (!root["received"].asInt()) {
+      // everything sent
+      retval.type = print;
+      // TODO
+      //~ retval.msg = "succesfully uploaded file " + fileman.getPutName();
+      fileman.closePut();
+    } else {
+      retval.type = print | send;
+      retval.nextcommand = "putdata";
+    }
+  }
+  retval.msg = output;
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleGet(Json::Value root) {
+  CmdRet retval;
+  Json::Value output;
+  output["command"] = "get";
+  output["file"] = fileman.getGetName();
+  output["accept"] = false;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+
+  if (!root["accept"].asBool()) {
+    retval.type = error;
+    output["error"] = "Server reports: " + root["error"].asString();
+  } else if (root["file"].asString() != fileman.getGetName()) {
+    retval.type = error;
+    output["error"] = "Server reports filename " + root["file"].asString() +
+                      " but actual filename is " + fileman.getGetName();
+  } else {
+    fileman.setGetChunks(root["chunks"].asInt());
+    output["accept"] = true;
+    output["error"] = "";
+    retval.type = print | send;
+    retval.nextcommand = "getdata";
+  }
+  retval.msg = output;
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleGetdata(Json::Value root) {
+  CmdRet retval;
+  Json::Value output;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  output["command"] = "getdata";
+  output["file"] = fileman.getGetName();
+  output["speed"] = 0.0f; // TODO
+  output["cancel"] = true;
+
+  // the passed number of recieved chunks should equal the number of sent chunks
+  if (root["remaining"].asInt() != fileman.getGetRemainingChunks()) {
+    retval.type = error;
+    output["error"] =
+        std::string("Server reports number of remaining chunks as ") +
+        std::to_string(root["remaining"].asInt()) + " but actual number is " +
+        std::to_string(fileman.getGetRemainingChunks());
+    fileman.cancelGet();
+  } else if (root["cancel"].asBool()) {
+    retval.type = error;
+    output["error"] = "Server reports: " + root["error"].asString();
+    fileman.cancelGet();
+  } else if (root["file"].asString() != fileman.getGetName()) {
+    retval.type = error;
+    output["error"] = "Server reports filename " + root["file"].asString() +
+                      " but actual filename is " + fileman.getGetName();
+    fileman.cancelGet();
+  } else {
+    output["cancel"] = false;
+    output["error"] = "";
+    fileman.writeBase64(root["data"].asString());
+    // loaded successfully
+    if (fileman.getGetRemainingChunks() < 0) {
+      // everything sent
+      retval.type = print;
+      //~ retval.msg = "succesfully downloaded file " + fileman.getGetName();
+      fileman.closeGet();
+    } else {
+      retval.type = print | send;
+      retval.nextcommand = "getdata";
+    }
+  }
+  retval.msg = output;
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleList(Json::Value root) {
+  CmdRet retval;
+  Json::Value output; // LOCALOUTPUT
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+
+  output["command"] = "list";
+  output["names"] = "";
+
+  if (!root["accept"].asBool()) {
+    retval.type = error;
+    output["accept"] = false;
+    output["error"] = "Server reports: " + root["error"].asString();
+    fileman.cancelList();
+  } else {
+    fileman.setListChunks(root["chunks"].asInt());
+    retval.type = send;
+    output["accept"] = true;
+    retval.nextcommand = "listdata";
+  }
+
+  retval.msg = output;
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleListdata(Json::Value root) {
+  CmdRet retval;
+  Json::Value output, arr;
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+  vector<string> toadd;
+
+  output["command"] = "list";
+  output["names"] = "";
+  output["accept"] = false;
+  // the passed number of recieved chunks should equal the number of sent chunks
+  if (root["remaining"].asInt() != fileman.getListRemainingChunks()) {
+    retval.type = error;
+    output["error"] = std::string("Server reports number of "
+                                  "remaining chunks as ") +
+                      std::to_string(root["remaining"].asInt()) +
+                      " but actual number is " +
+                      std::to_string(fileman.getListRemainingChunks());
+    fileman.cancelList();
+  } else if (root["cancel"].asBool()) {
+    retval.type = error;
+    output["error"] = "Server reports: " + root["error"].asString();
+    fileman.cancelList();
+  } else {
+    output["accept"] = true;
+    for (Json::Value i : root["names"])
+      toadd.push_back(i.asString());
+    fileman.putListData(toadd);
+    // loaded successfully
+    if (!fileman.getListRemainingChunks()) {
+      // everything sent
+      retval.type = print;
+      for (string s : fileman.getListData())
+        arr.append(s);
+      output["names"] = arr;
+      fileman.closeList();
+    } else {
+      retval.type = print | send;
+      retval.nextcommand = "listdata";
+    }
+  }
+  retval.msg = output;
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleVersion(Json::Value root) {
+  CmdRet retval;
+  Json::Value output; // LOCALOUTPUT
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+
+  output["command"] = "version";
+  output["serverversion"] = root["version"].asString();
+  output["clientversion"] = protocolVersion;
+
+  if (!root["accept"].asBool()) {
+    retval.type = error;
+    output["accept"] = false;
+  } else {
+    retval.type = print | seton;
+    output["accept"] = true;
+    doversion = false;
+    loginpossible = true;
+  }
+  retval.msg = output;
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleLogin(Json::Value root) {
+  CmdRet retval;
+  Json::Value output; // LOCALOUTPUT
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+
+  output["command"] = "login";
+
+  if (!root["accept"].asBool()) {
+    retval.type = error;
+    output["error"] = root["error"].asString();
+    output["accept"] = false;
+    loginpossible = true;
+    dologin = false;
+  } else {
+    retval.type = print | seton;
+    output["error"] = "";
+    output["accept"] = true;
+    dologin = false;
+  }
+  retval.msg = output;
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleSignup(Json::Value root) {
+  CmdRet retval;
+  Json::Value output; // LOCALOUTPUT
+  DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+
+  output["command"] = "signup";
+
+  if (!root["accept"].asBool()) {
+    retval.type = error;
+    output["error"] = root["error"].asString();
+    output["accept"] = false;
+    loginpossible = true;
+    dosignup = false;
+  } else {
+    retval.type = print | seton;
+    output["error"] = "";
+    output["accept"] = true;
+    dosignup = false;
+  }
+  retval.msg = output;
+
+  return retval;
+}

+ 0 - 51
cli/src/commands.cpp

@@ -1,51 +0,0 @@
-#include "../include/commands.hpp"
-#include <cstdio>
-
-CMD commands[]{
-    {CMD_HELP, "help", "show help"},
-    {CMD_STATUS, "status", "query status of IP"},
-    {CMD_DISCONNECT, "disconnect", "disconnect from IP"},
-    {CMD_PUT, "put", "upload file to IP and add to queue"},
-    {CMD_GET, "get", "retrieve file from deamon"},
-    {CMD_LIST, "list", "list files that are downloadable from deamon"}
-
-    /* TODO
-    {CMD_REMOVE	, "remove", "remove file from IP and queue (stops xfer
-    if required)"},
-    {CMD_SETUP	, "setup", "configure server at IP"},
-    {CMD_LOG	, "log", "show log from IP"}
-    */
-};
-
-COMMANDID getCmdIdFromString(const char *str) {
-  COMMANDID ret = CMD_UNKNOWN;
-  char temp[COMMANDLEN + 1] = {0};
-  int len = COMMANDLEN;
-  if (std::strlen(str) > COMMANDLEN) {
-    return ret;
-  } else
-    len = std::strlen(str);
-  for (int i = 0; i < len; i++)
-    temp[i] = std::tolower(str[i]);
-
-  for (int i = 0; i < sizeofarr(commands); i++) {
-    if (!std::strncmp(temp, commands[i].name, COMMANDLEN))
-      ret = commands[i].cmd;
-  }
-  return ret;
-}
-
-const char *getCmdStringFromId(COMMANDID id) {
-  const char *ret = NULL;
-  for (int i = 0; i < sizeofarr(commands); i++) {
-    if (commands[i].cmd == id)
-      ret = commands[i].name;
-  }
-  return ret;
-}
-
-void printCmds(void) {
-  for (CMD command : commands) {
-    printf("%-12s - %s\n", command.name, command.desc);
-  }
-}

+ 167 - 0
cli/src/fileman.cpp

@@ -0,0 +1,167 @@
+#include "../include/fileman.h"
+#include "../include/base64.h"
+
+FileMan::FileMan() { islisting = false; }
+
+FileMan::~FileMan() {
+  cancelGet();
+  cancelPut();
+  cancelList();
+}
+
+bool FileMan::isGetting() { return getfile.is_open(); }
+
+bool FileMan::isPutting() { return putfile.is_open(); }
+
+bool FileMan::isListing() { return islisting; }
+
+bool FileMan::openPut(const std::string &path) {
+  putpath = path;
+  putname = pathToFilename(path);
+  putfile.open(path, std::ios::ate | std::ios::binary);
+  if (putfile.is_open()) {
+    size_t size = putfile.tellg();
+    putsize = size;
+    putchunks = size / max_read_len + ((size % max_read_len) ? 1 : 0);
+    putchunksRemaining = putchunks;
+    putfile.seekg(std::ios::beg);
+    return true;
+  }
+  return false;
+}
+
+bool FileMan::openGet(const std::string &path) {
+  getpath = path;
+  getname = pathToFilename(path);
+  getchunks = 0;
+  getchunksRemaining = 0;
+  getfile.open(path, std::ios::app | std::ios::binary);
+  if (getfile.tellp() != std::ios::beg) {
+    closeGet();
+    return false;
+  }
+  return true;
+}
+
+bool FileMan::openList() {
+  if (isListing()) {
+    return false;
+  }
+  listdata = std::vector<std::string>();
+  islisting = true;
+  return true;
+}
+
+void FileMan::closePut() { putfile.close(); }
+
+void FileMan::closeGet() { getfile.close(); }
+
+void FileMan::closeList() { islisting = false; }
+
+void FileMan::cancelPut() {
+  if (isPutting()) {
+    closePut();
+  }
+}
+
+void FileMan::cancelGet() {
+  if (isGetting()) {
+    closeGet();
+    std::remove(getname.c_str());
+  }
+}
+
+void FileMan::cancelList() {
+  if (isListing()) {
+    closeList();
+  }
+}
+
+void FileMan::writeGet(const std::vector<char> data) {
+  getfile.write(data.data(), data.size());
+  getchunksRemaining--;
+}
+
+void FileMan::writeBase64(std::string data) {
+  writeGet(base64::decodeVector(data));
+}
+
+void FileMan::putListData(std::vector<std::string> names) {
+  listdata.insert(listdata.end(), names.begin(), names.end());
+  listchunksRemaining--;
+}
+
+std::vector<std::string> FileMan::getListData() { return listdata; }
+
+int FileMan::getGetChunks() { return getchunks; }
+
+int FileMan::getGetRemainingChunks() { return getchunksRemaining; }
+
+void FileMan::setGetChunks(int chunks) {
+  getchunks = chunks;
+  getchunksRemaining = chunks - 1;
+}
+
+void FileMan::setListChunks(int chunks) {
+  listchunks = chunks;
+  getchunksRemaining = chunks - 1;
+}
+
+std::vector<char> FileMan::readPut() {
+  char buf[max_read_len];
+  std::vector<char> data;
+
+  std::streamoff read = this->putfile.tellg();
+  if (read + max_read_len > this->putsize) {
+    read = this->putsize % max_read_len;
+  } else {
+    read = max_read_len;
+  }
+
+  putfile.read(buf, read);
+  data.assign(buf, buf + read);
+  putchunksRemaining--;
+  return data;
+}
+
+std::string FileMan::readBase64() { return base64::encodeVector(readPut()); }
+
+std::string FileMan::getPutName() { return putname; }
+
+std::string FileMan::getGetName() { return getname; }
+
+int FileMan::getPutChunks() { return putchunks; }
+
+int FileMan::getPutRemainingChunks() { return putchunksRemaining; }
+
+int FileMan::getPutSize() { return putsize; }
+
+int FileMan::getListRemainingChunks() { return listchunksRemaining; }
+
+int FileMan::getListChunks() { return listchunks; }
+
+std::string FileMan::pathToFilename(std::string path) {
+
+  int lastFoundIndex = -1;
+
+  for (int currentIndex = path.find("/"); currentIndex != std::string::npos;
+       currentIndex = path.find("/", currentIndex + 1)) {
+
+    // check if the "/" was escaped
+    if (currentIndex > 0 && path[currentIndex - 1] == '\\')
+      continue;
+
+    /*  // check unnecessary, because error occurs when trying to open file
+       anyways
+
+        // check if the "/" is at the end of path name
+        if (currentIndex + 1 == path.length()) {
+          // ERROR: INVALID FILENAME, BECAUSE ENDS WITH "/"
+        }
+    */
+    // otherwise we found a valid "/"
+    lastFoundIndex = currentIndex;
+  }
+
+  return path.substr(lastFoundIndex + 1);
+}

+ 557 - 0
cli/src/ioman.cpp

@@ -0,0 +1,557 @@
+#include "../include/ioman.h"
+#include "../include/global.h"
+
+#include <boost/asio.hpp>
+#include <iostream>
+
+#include <boost/algorithm/string.hpp>
+#include <string>
+#include <vector>
+
+#include <readline/history.h>
+#include <readline/readline.h>
+
+#include <poll.h>
+
+using std::string;
+using std::vector;
+
+using boost::asio::buffer;
+using boost::asio::ip::address;
+using boost::asio::ip::tcp;
+
+/* this will be provided by main.cpp for the readline callback */
+extern IoMan *gIOMAN;
+
+void ioman_externalDebugPrint(string msg) {
+  gIOMAN->printMessage(msg, gIOMAN->OutMsgType::debug);
+}
+
+IoMan::IoMan(char *ipcstring) : cmdman(fileman, &ioman_externalDebugPrint) {
+  ipstring = std::string(ipcstring);
+  port = 1234;
+  tcpsock = new tcp::socket(ios);
+  connected = false;
+
+  /* to be put elsewhere */
+
+  /* setup json stuff */
+  Json::CharReaderBuilder rbuilder;
+  wbuilder.settings_["indentation"] = "";
+  reader = rbuilder.newCharReader();
+  runnetwork = false;
+  runinput = false;
+  runresponse = false;
+  versionstatus = off;
+  loginstatus = off;
+}
+
+IoMan::~IoMan() {
+  initcv.notify_all();
+  if (runnetwork) {
+    networkmutex.lock();
+    runnetwork = false;
+    networkmutex.unlock();
+    tnetwork.join();
+  }
+  if (runinput) {
+    inputmutex.lock();
+    runinput = false;
+    inputmutex.unlock();
+    localcv.notify_all();
+    tinput.join();
+  }
+  if (runresponse) {
+    responsemutex.lock();
+    runresponse = false;
+    responsemutex.unlock();
+    netcv.notify_all();
+    tresponse.join();
+  }
+
+  if (connected) {
+    disconnect();
+  }
+
+  delete tcpsock;
+  delete reader;
+}
+
+void IoMan::printMessage(string nouse, OutMsgType nouse2) {}
+
+vector<string> IoMan::tokenizeInput(string in) {
+  size_t prev, index, quot;
+  vector<string> args;
+  /* tokenize string into command and arguments vector*/
+  if ((index = in.find(" ")) == string::npos) {
+    // only command no args
+    args.push_back(in);
+  } else {
+    args.push_back(in.substr(0, index));
+    index++;
+
+    bool end_tokenizing = false;
+    while (!end_tokenizing) {
+      // find first char thats not a space
+      while (in[index] == ' ') {
+        index++;
+
+        // bounds check
+        if (index == in.size())
+          end_tokenizing = true;
+      }
+      if (end_tokenizing)
+        break;
+
+      in = in.substr(index);
+
+      if (in[0] == '\"') {
+        // quoted string
+        in = in.substr(1);
+        index = in.find("\"");
+        args.push_back(in.substr(0, index));
+        index++;
+
+        /*
+        tokens.push_back(in.substr(0, ++index));
+        */
+
+        // char after closing quote should be space while within bounds
+        if (index == in.size())
+          end_tokenizing = true;
+      } else {
+        // non-quoted string
+        index = in.find(" ");
+        if (index == string::npos) { // no spaces, last arg
+          args.push_back(in);
+          end_tokenizing = true;
+        } else {
+          args.push_back(in.substr(0, index));
+        }
+      }
+    }
+  }
+  return args;
+}
+
+bool IoMan::connect() {
+  tcp::endpoint *ep = NULL;
+  address addr;
+  Json::Value root;
+  root["command"] = "connect";
+  root["address"] = ipstring;
+  root["port"] = port;
+  addr = address::from_string(ipstring, errcode);
+  if (errcode) {
+    root["error"] = errcode.message();
+    connected = false;
+  } else {
+    // establish connection
+    printMessage("IoMan::connect() connecting to " + ipstring, debug);
+    ep = new tcp::endpoint(addr, port);
+    tcpsock->connect(*ep, errcode);
+    if (errcode) {
+      root["error"] = errcode.message();
+      connected = false;
+    } else {
+      connected = true;
+      root["error"] = "";
+    }
+    delete ep;
+  }
+  root["accept"] = connected;
+  printMessage(Json::writeString(wbuilder, root), normal);
+  return connected;
+}
+
+void IoMan::disconnect() {
+  printMessage("IoMan::disconnect()", debug);
+  tcpsock->close();
+  connected = false;
+}
+
+bool IoMan::init() {
+  CmdMan::CmdRet ret;
+  string work;
+  Json::Value root;
+  std::unique_lock<std::mutex> ulock;
+
+  printMessage("IoMan::Init() begin", debug);
+
+  if (!connect())
+    return false;
+
+  printMessage("IoMan::Init() versioncheck", debug);
+
+  localmutex.lock();
+  printMessage(string(__PRETTY_FUNCTION__) + string(" get localmutex"), debug);
+  localinput.push_back("version " + protocolVersion);
+  localmutex.unlock();
+  printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"),
+               debug);
+  localcv.notify_all();
+
+  printMessage("IoMan::Init() begin", debug);
+
+  runnetwork = true;
+  runinput = true;
+  runresponse = true;
+
+  tnetwork = std::thread(&IoMan::networkMain, this);
+  tinput = std::thread(&IoMan::inputMain, this);
+  tresponse = std::thread(&IoMan::responseMain, this);
+
+  ulock = std::unique_lock<std::mutex>(initmutex);
+  while (versionstatus == off) {
+    initcv.wait(ulock);
+  }
+  if (versionstatus == err) {
+    runnetwork = false;
+    runinput = false;
+    runresponse = false;
+    return false;
+  }
+  initmutex.unlock();
+  initcv.notify_all();
+
+  printWelcomeMessage();
+  return true;
+}
+
+/* loop to fetch data from the network, doing light preprocessing on it to be
+ * handled by responseMain */
+void IoMan::networkMain() {
+  vector<Json::Value> toput;
+  char *recvjson;
+  Json::Value root;
+  unsigned int jsonsize, readsize;
+
+  printMessage("IoMan::networkMain() begin", debug);
+  networkmutex.lock();
+  while (runnetwork) {
+    networkmutex.unlock();
+    /*
+    read from network until \n
+    try to parse json
+    - output error if not ok
+    store all ok json in local vector
+
+    get networkmutex
+    put all local jsons into network vector
+    release networkmutex
+    */
+
+    // read from network
+    readsize = boost::asio::read_until(*tcpsock, recvbuf, '\n', errcode);
+    printMessage(string(__PRETTY_FUNCTION__) + string(" asio::read() ok ") +
+                     std::to_string(readsize),
+                 debug);
+    // printMessage(string("have ") + std::to_string(toprocess.size()) +
+    // string(" commands"), debug);
+    if (errcode && errcode != boost::asio::error::eof) {
+      printMessage("IoMan::networkMain() couldnt read json data\n" +
+                       errcode.message(),
+                   error);
+      continue;
+    }
+    if (!readsize)
+      break;
+    recvjson = (char *)(boost::asio::buffer_cast<const char *>(recvbuf.data()));
+    recvjson[readsize] = 0;
+    while (strchr(recvjson, '\n')) {
+      // parse
+      jsonsize = strchr(recvjson, '\n') - recvjson + 1;
+
+      printMessage(string(__PRETTY_FUNCTION__) + string(" found jsondata ") +
+                       string(recvjson),
+                   debug);
+
+      if (!reader->parse(recvjson, recvjson + jsonsize, &root, &jsonerror)) {
+        printMessage("IoMan::networkMain() couldnt parse json data: " +
+                         jsonerror,
+                     error);
+        recvbuf.consume(jsonsize);
+        continue;
+      }
+      recvbuf.consume(jsonsize);
+      readsize -= jsonsize;
+
+      printMessage(
+          string(__PRETTY_FUNCTION__) + string(" remaining recvbuf ") +
+              string(boost::asio::buffer_cast<const char *>(recvbuf.data())),
+          debug);
+
+      for (int i = 0; i < jsonsize; i++)
+        recvjson++;
+      // store locally
+      toput.push_back(root);
+    }
+
+    if (toput.size()) {
+      // put into global vector
+      netmutex.lock();
+      printMessage(string(__PRETTY_FUNCTION__) + string(" get netmutex"),
+                   debug);
+      netinput.insert(netinput.end(), toput.begin(), toput.end());
+      netmutex.unlock();
+      printMessage(string(__PRETTY_FUNCTION__) + string(" release netmutex"),
+                   debug);
+    }
+    netcv.notify_all();
+
+    // clean up local stuff
+    toput = vector<Json::Value>();
+    recvbuf.consume(readsize);
+    networkmutex.lock();
+  }
+}
+
+/* loop to handle input from the user and responseMain, sending data via network
+ * if required */
+void IoMan::inputMain() {
+  vector<string> toprocess;
+  string command;
+  vector<string> args;
+  CmdMan::CmdRet cmdret;
+  std::unique_lock<std::mutex> ulock;
+
+  printMessage("IoMan::inputMain() begin", debug);
+  inputmutex.lock();
+  while (runinput) {
+    inputmutex.unlock();
+    /*
+    get inputmutex
+    read all input vector into local vector
+    release inputmutex
+
+    process inputs
+    send to server if required
+    */
+
+    // read into local vector
+    ulock = std::unique_lock<std::mutex>(localmutex);
+    while (!localinput.size() && runinput) {
+      localcv.wait(ulock);
+    }
+    printMessage(string(__PRETTY_FUNCTION__) + string(" has localmutex"),
+                 debug);
+    toprocess = vector<string>(localinput);
+    localinput = vector<string>();
+    localmutex.unlock();
+    printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"),
+                 debug);
+    localcv.notify_all();
+
+    if (!runinput)
+      return;
+
+    // printMessage(string("have ") + std::to_string(toprocess.size()) +
+    // string(" commands"), debug);
+
+    // process
+    for (string cmd : toprocess) {
+      args = tokenizeInput(cmd);
+      command = args.front();
+      args.erase(args.begin());
+      cmdret = cmdman.execute(command, args);
+
+      // determine wether to send something and do so if required
+      if (cmdret.type & CmdMan::rettype::print) {
+        printMessage(Json::writeString(wbuilder, cmdret.msg), normal);
+      }
+      if (cmdret.type & CmdMan::rettype::send) {
+        printMessage("IoMan::inputMain() sending json \"" +
+                         Json::writeString(wbuilder, cmdret.msg) + "\"",
+                     debug);
+        boost::asio::write(
+            *tcpsock, buffer(Json::writeString(wbuilder, cmdret.msg) + "\n"),
+            errcode);
+        if (errcode) {
+          printMessage("IoMan::inputMain() couldnt send json data\n" +
+                           errcode.message() + "\n",
+                       error);
+          continue;
+        }
+      }
+      if (cmdret.type & CmdMan::rettype::error) {
+        printMessage(Json::writeString(wbuilder, cmdret.msg), error);
+      }
+      if (cmdret.type & CmdMan::rettype::close) {
+        /* TODO i dunno */
+        mainmutex.lock();
+        runmain = false;
+        mainmutex.unlock();
+      }
+    }
+
+    // clean up local stuff
+    toprocess = vector<string>();
+    inputmutex.lock();
+  }
+}
+
+/* loop to handle responses that have been fetched by netMain and possibly add
+ * new commands to be handled by inputMain */
+void IoMan::responseMain() {
+  vector<Json::Value> toprocess;
+  vector<string> toput;
+  CmdMan::CmdRet cmdret;
+  std::unique_lock<std::mutex> ulock;
+
+  printMessage("IoMan::responseMain() begin", debug);
+  responsemutex.lock();
+  while (runresponse) {
+    responsemutex.unlock();
+    /*
+    get networkmutex
+    read all network vector into local vector
+    release networkmutex
+
+    process all jsons
+    process putdata
+    process getdata
+    process listdata
+
+    get inputmutex
+    place new commands into input vector
+    release inputmutex
+    */
+
+    // read into local vector
+    ulock = std::unique_lock<std::mutex>(netmutex);
+    while (!netinput.size() && runresponse) {
+      netcv.wait(ulock);
+    }
+    printMessage(string(__PRETTY_FUNCTION__) + string(" get netmutex"), debug);
+    toprocess = vector<Json::Value>(netinput);
+    netinput = vector<Json::Value>();
+    netmutex.unlock();
+    printMessage(string(__PRETTY_FUNCTION__) + string(" release netmutex"),
+                 debug);
+    netcv.notify_all();
+
+    if (!runresponse)
+      return;
+
+    // process jsons
+    for (Json::Value root : toprocess) {
+      cmdret = cmdman.handle(root);
+      if (cmdret.type & CmdMan::rettype::close) {
+        /* TODO i dunno */
+        mainmutex.lock();
+        runmain = false;
+        mainmutex.unlock();
+      }
+      if (cmdret.type & CmdMan::rettype::error) {
+        initmutex.lock();
+        if (versionstatus == off)
+          versionstatus = err;
+        else if (loginstatus == off)
+          loginstatus = err;
+        initmutex.unlock();
+        initcv.notify_all();
+        printMessage(Json::writeString(wbuilder, cmdret.msg), error);
+      }
+      if (cmdret.type & CmdMan::rettype::seton) {
+        initmutex.lock();
+        if (versionstatus == off)
+          versionstatus = on;
+        else
+          loginstatus = on;
+        initmutex.unlock();
+        initcv.notify_all();
+      }
+      if (cmdret.type & CmdMan::rettype::print) {
+        printMessage(Json::writeString(wbuilder, cmdret.msg), normal);
+      }
+      if (cmdret.type & CmdMan::rettype::send) {
+        if (cmdret.nextcommand.size()) {
+          toput.push_back(cmdret.nextcommand);
+        }
+      }
+    }
+
+    if (toput.size()) {
+      // put new commands into global vector
+      localmutex.lock();
+      printMessage(string(__PRETTY_FUNCTION__) + string(" get localmutex"),
+                   debug);
+      localinput.insert(localinput.end(), toput.begin(), toput.end());
+      localmutex.unlock();
+      printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"),
+                   debug);
+    }
+    localcv.notify_all();
+
+    // clean up local stuff
+    toprocess = vector<Json::Value>();
+    toput = vector<string>();
+    responsemutex.lock();
+  }
+}
+
+/* this is the handler that readlines alternative interface will use to process
+ * user input */
+void ioman_readlineHandler(char *line) {
+  vector<string> tokens;
+  if (!line) {
+    printf("\nNULLBURGER\n");
+    gIOMAN->mainmutex.lock();
+    gIOMAN->runmain = false;
+    gIOMAN->mainmutex.unlock();
+  } else {
+    // split input line into tokens
+    boost::algorithm::split(tokens, std::string(line),
+                            boost::algorithm::is_any_of(" "),
+                            boost::algorithm::token_compress_on);
+    if (strlen(line) && tokens.size()) {
+      add_history(line);
+
+      gIOMAN->localmutex.lock();
+      gIOMAN->printMessage(string(__PRETTY_FUNCTION__) +
+                               string(" get localmutex"),
+                           gIOMAN->debug);
+      gIOMAN->localinput.push_back(line);
+      gIOMAN->localmutex.unlock();
+      gIOMAN->printMessage(string(__PRETTY_FUNCTION__) +
+                               string(" release localmutex"),
+                           gIOMAN->debug);
+      gIOMAN->localcv.notify_all();
+    }
+    free(line);
+  }
+}
+
+/* main user input loop */
+void IoMan::run() {
+  printMessage("IoMan::run() begin", debug);
+  struct pollfd inPipeStatus;
+  inPipeStatus.fd = STDIN_FILENO;
+  inPipeStatus.events = POLLIN;
+
+  runmain = true;
+
+  // Install readline handler
+  rl_callback_handler_install(getCmdPrompt().c_str(),
+                              (rl_vcpfunc_t *)&ioman_readlineHandler);
+
+  mainmutex.lock();
+  while (runmain) {
+    mainmutex.unlock();
+
+    poll(&inPipeStatus, 1, 100);
+
+    if (inPipeStatus.revents & POLLIN) {
+      rl_callback_read_char();
+    }
+    if (!connected)
+      break;
+    if (loginstatus == err)
+      break;
+    mainmutex.lock();
+  }
+  mainmutex.unlock();
+
+  // Remove the handler
+  rl_callback_handler_remove();
+}

+ 0 - 157
cli/src/iomanager.cpp

@@ -1,157 +0,0 @@
-#include "../include/iomanager.hpp"
-
-#include <iostream>
-
-using boost::asio::buffer;
-
-IoManager::IoManager(char *ipcstring) {
-  ipstring = std::string(ipcstring);
-  port = 1234;
-  tcpsock = new tcp::socket(ios);
-  wbuilder.settings_["indentation"] = "";
-  // const std::string output = Json::writeString(wbuilder, root);
-  reader = rbuilder.newCharReader();
-}
-
-bool IoManager::sendJson(Json::Value root) {
-  boost::asio::write(*tcpsock, buffer(Json::writeString(wbuilder, root)),
-                     errcode);
-  if (errcode) {
-    std::cerr << "couldnt send json data" << std::endl
-              << errcode.message() << std::endl;
-    return false;
-  }
-  return true;
-}
-
-bool IoManager::receiveJson(boost::asio::streambuf &recvbuf) {
-  // use transfer_at_least(1) to avoid deadlock with transfer_all()
-  boost::asio::read(*tcpsock, recvbuf, boost::asio::transfer_at_least(1),
-                    errcode);
-  if (errcode && errcode != boost::asio::error::eof) {
-    std::cerr << "couldnt recieve json data" << std::endl
-              << errcode.message() << std::endl;
-    return false;
-  }
-  return true;
-}
-
-bool IoManager::parseJson(Json::Value *root, boost::asio::streambuf &recvbuf) {
-  const char *recvjson;
-  std::string jsonerror;
-  recvjson = boost::asio::buffer_cast<const char *>(recvbuf.data());
-  if (!reader->parse(recvjson, recvjson + recvbuf.size(), root, &jsonerror)) {
-    std::cerr << "couldnt parse json data" << std::endl
-              << jsonerror << std::endl;
-    return false;
-  }
-  recvbuf.consume(recvbuf.size());
-  return true;
-}
-
-bool IoManager::connect() {
-  boost::asio::streambuf recvbuf;
-  tcp::endpoint *ep;
-  Json::Value root, checkok;
-  bool bcheckok;
-
-  ep = new tcp::endpoint(boost::asio::ip::address::from_string(ipstring), 1234);
-
-  // establish connection
-  std::cerr << "connecting to " << ipstring << std::endl;
-  tcpsock->connect(*ep, errcode);
-  if (errcode) {
-    std::cerr << "couldnt connect to " << ipstring << std::endl
-              << errcode.message() << std::endl;
-    return false;
-  }
-
-  // send version check
-  root["version"] = VERSION;
-
-  std::cerr << root << std::endl;
-
-  if (!sendJson(root))
-    return false;
-  // receive answer
-  if (!receiveJson(recvbuf))
-    return false;
-  if (!parseJson(&root, recvbuf))
-    return false;
-
-  // dump for GUI
-  std::cout << root << std::endl;
-
-  // check if version check was ok
-  checkok = root["accept"];
-
-  if (!checkok.asBool()) {
-    std::cerr << "version check failed. client version is " << VERSION
-              << std::endl
-              << "server reports version " << root["version"] << std::endl;
-    return false;
-  }
-
-  printf("check ok\n\n\n");
-  fflush(stdout);
-
-  /* */
-  // TODO remove hardcoded login
-  root = Json::Value();
-  // send version check
-  // TODO make client version global
-  root["user"] = "user";
-  root["pass"] = "pass";
-
-  std::cerr << root << std::endl;
-
-  // send login
-  if (!sendJson(root))
-    return false;
-  // receive answer to login
-  if (!receiveJson(recvbuf))
-    return false;
-  if (!parseJson(&root, recvbuf))
-    return false;
-
-  // dump for GUI
-  std::cout << root << std::endl;
-  /* */
-
-  // clean up
-  delete ep;
-
-  return true;
-}
-
-IoManager::~IoManager() {
-
-  /* */
-
-  boost::asio::streambuf recvbuf;
-  Json::Value root, checkok;
-  const char *recvjson;
-  std::string jsonerror;
-
-  // TODO remove hardcoded login
-  root = Json::Value();
-  // send version check
-  // TODO make client version global
-  root["command"] = "close";
-
-  std::cerr << root << std::endl;
-
-  // send disconnect
-  sendJson(root);
-  // recieve answer to disconnect
-  receiveJson(recvbuf);
-  parseJson(&root, recvbuf);
-
-  // dump to GUI
-  std::cout << root << std::endl;
-  /* */
-
-  tcpsock->close();
-  delete tcpsock;
-  delete reader;
-}

+ 38 - 0
cli/src/machineioman.cpp

@@ -0,0 +1,38 @@
+#include "../include/machineioman.h"
+
+#include <boost/algorithm/string.hpp>
+#include <fstream>
+#include <iostream>
+#include <limits>
+#include <vector>
+
+#include <readline/readline.h>
+
+using boost::asio::buffer;
+
+void MachineIoMan::printMessage(std::string msg, OutMsgType type) {
+  switch (type) {
+  case normal: {
+    std::cout << msg << std::endl;
+    break;
+  }
+  case error: {
+    std::cout << msg << std::endl;
+    break;
+  }
+  case debug: {
+    //~ std::cerr << msg << std::endl;
+    break;
+  }
+  }
+}
+
+void MachineIoMan::printWelcomeMessage() {
+  //~ std::cout << "please enter user and password" << std::endl;
+}
+
+std::string MachineIoMan::getCmdPrompt() { return ""; }
+
+// currently not in use:
+//~ std::string MachineIoMan::getUserPrompt() { return ""; }
+//~ std::string MachineIoMan::getPassPrompt() { return ""; }

+ 0 - 274
cli/src/machineiomanager.cpp

@@ -1,274 +0,0 @@
-#include "../include/machineiomanager.hpp"
-#include "../include/base64.hpp"
-#include "../include/commands.hpp"
-
-#include <boost/algorithm/string.hpp>
-#include <fstream>
-#include <iostream>
-#include <limits>
-#include <vector>
-
-#include <readline/readline.h>
-
-using boost::asio::buffer;
-
-void MachineIoManager::run() {
-  std::cerr << "MachineIoManager::run() says hello!" << std::endl;
-
-  boost::asio::streambuf recvbuf;
-
-  Json::Value root, recv;
-  const char *recvjson;
-
-  bool keep_reading = true;
-  COMMANDID cmd;
-  char *line = NULL;
-
-  std::vector<std::string> tokens, pathtemp;
-
-  std::fstream fs;
-
-  // .open(path, mode; mode std::fstream::in, out, binary, trunc(ate))
-  // .read(char* dest, int size)
-  // .write(char* src, int size)
-  // .eof()
-  // .seekg(offset, ios_base::beg, cur, end)
-  // .tellg()
-  // .close()
-
-  while (keep_reading) {
-    // clean up
-    free(line);
-    root = Json::Value();
-    recv = Json::Value();
-    recvbuf.consume(recvbuf.size());
-
-    // read an input line
-    line = readline("");
-
-    // if no input continue
-    if (strlen(line) == 0) {
-      continue;
-    }
-
-    boost::algorithm::split(tokens, std::string(line),
-                            boost::algorithm::is_any_of(" "),
-                            boost::algorithm::token_compress_on);
-
-    std::cerr << "tokens are: [ ";
-    for (int i = 0; i < tokens.size(); i++) {
-      std::cerr << tokens[i] << " ";
-    }
-    std::cerr << "]" << std::endl;
-
-    if (tokens.size() < 1) {
-      continue;
-    }
-
-    cmd = getCmdIdFromString(tokens[0].c_str());
-
-    switch (cmd) {
-    case CMD_DISCONNECT: {
-      std::cerr << "disconnecting\n";
-      keep_reading = false;
-      break;
-    }
-    case CMD_STATUS: {
-      root["command"] = "status";
-
-      // send request
-      sendJson(root);
-      // receive answer
-      receiveJson(recvbuf);
-      //
-      parseJson(&recv, recvbuf);
-
-      // dump received data to stdout for gui to handle
-      std::cout << recv;
-
-      break;
-    }
-    case CMD_PUT: {
-      int size, chunks, haveread;
-      char rbuf[BLOCKSIZE];
-      if (tokens.size() < 2) {
-        std::cerr << "no files specified" << std::endl;
-        break;
-      }
-      fs.open(tokens[1], std::fstream::in | std::fstream::binary);
-      if (!fs.is_open()) {
-        std::cerr << "couldnt open file " << tokens[1] << std::endl;
-        break;
-      }
-      fs.seekg(0, fs.end);
-      size = fs.tellg();
-      fs.seekg(0, fs.beg);
-      chunks = size / BLOCKSIZE + ((size % BLOCKSIZE) ? 1 : 0);
-      std::cerr << "size is " << size << " chunks are " << chunks << std::endl;
-
-      if (!size) {
-        std::cerr << "not sending empty file" << std::endl;
-        break;
-      }
-
-      boost::algorithm::split(pathtemp, tokens[1],
-                              boost::algorithm::is_any_of("/"),
-                              boost::algorithm::token_compress_on);
-
-      root["command"] = "put";
-      root["file"] = pathtemp.back();
-      root["size"] = size;
-
-      std::cerr << root;
-
-      if (!sendJson(root) | !receiveJson(recvbuf) |
-          !parseJson(&recv, recvbuf)) {
-        // error occured, stop working
-        break;
-      }
-      // dump to stdout for GUI
-      std::cout << recv;
-
-      // check correct command once asychronous
-      if (recv["accept"].asBool()) {
-        // request ok, start sending...
-        root = Json::Value();
-        root["command"] = "put";
-        root["cancel"] = false;
-
-        std::cerr << root;
-
-        while (chunks) {
-          haveread = fs.readsome(rbuf, BLOCKSIZE);
-          chunks--;
-          root["data"] = base64::encodeVector(
-              std::vector<char>(rbuf, rbuf + haveread * sizeof(char)));
-          root["remaining"] = chunks;
-          if (!sendJson(root) | !receiveJson(recvbuf) |
-              !parseJson(&recv, recvbuf)) {
-            // error occured, stop working
-            break;
-          }
-          if (recv["cancel"].asBool()) {
-            // transfer cancelled by server
-            break;
-          }
-          std::cout << root;
-        }
-        if (chunks)
-          std::cerr << "not all data sent, remain chunks " << chunks
-                    << std::endl;
-      }
-      break;
-    }
-    case CMD_GET: {
-      int chunks, rec = 0;
-      std::vector<char> wtmp;
-      char wbuf[BLOCKSIZE];
-      if (tokens.size() < 2) {
-        std::cerr << "no files specified" << std::endl;
-        break;
-      }
-      fs.open(tokens[1],
-              std::fstream::out | std::fstream::binary | std::fstream::trunc);
-      if (!fs.is_open()) {
-        std::cerr << "couldnt open file " << tokens[1] << std::endl;
-        break;
-      }
-
-      root["command"] = "get";
-      root["file"] = tokens[1];
-
-      if (!sendJson(root) | !receiveJson(recvbuf) |
-          !parseJson(&recv, recvbuf)) {
-        // error occured, stop working
-        break;
-      }
-
-      // dump received data for GUI
-      std::cout << recv << std::endl;
-
-      if (recv["accept"].asBool()) {
-        // for ACK
-        root["command"] = "get";
-        root["cancel"] = false;
-        // request ok, start receiving
-        chunks = std::numeric_limits<int>::max();
-        std::cerr << "survive" << std::endl;
-        while (chunks) {
-          std::cerr << "survive" << std::endl;
-          if (!receiveJson(recvbuf) | !parseJson(&recv, recvbuf))
-            break;
-          std::cerr << "survive" << std::endl;
-          // dump received data for GUI
-          std::cout << recv << std::endl;
-          wtmp = base64::decodeVector(recv["data"].asString());
-          fs.write(wtmp.data(), wtmp.size());
-          chunks = recv["remaining"].asInt();
-          root["received"] = rec;
-          if (!sendJson(root)) {
-            // couldnt ACK
-            break;
-          }
-          rec++;
-        }
-      }
-      if (recv["cancel"].asBool())
-        std::cerr << "transfer was cancelled" << std::endl;
-      if (recv["remaining"].asInt())
-        std::cerr << "not all data received, remain chunks "
-                  << recv["remaining"].asInt();
-      break;
-    }
-    case CMD_LIST: {
-      int chunks, rec = 0;
-      root["command"] = "list";
-      if (!sendJson(root) | !receiveJson(recvbuf) |
-          !parseJson(&recv, recvbuf)) {
-        // error occured, stop working
-        break;
-      }
-      // dump received data for GUI
-      std::cout << recv << std::endl;
-
-      if (recv["accept"].asBool()) {
-        // ACK?
-        root["command"] = "list";
-        root["cancel"] = false;
-        chunks = std::numeric_limits<int>::max();
-        while (chunks) {
-          if (!receiveJson(recvbuf) | !parseJson(&recv, recvbuf))
-            break;
-          // dump received data for GUI
-          std::cout << recv << std::endl;
-          /*
-          Json::Value arraything = recv["names"];
-          if(arraything.isArray())
-          for(Json::Value d : recv["names"])
-          */
-          chunks = recv["remaining"].asInt();
-          root["received"] = rec;
-          if (!sendJson(root)) {
-            // couldnt ACK
-            break;
-          }
-          rec++;
-        }
-      }
-      break;
-    }
-    default: {
-      std::cerr << "unknown command " << line << "\n";
-      break;
-    }
-    }
-    std::printf("\n");
-
-    // if a command used fs, close it now
-    if (fs.is_open())
-      fs.close();
-  }
-  free(line);
-
-  return;
-}

+ 11 - 17
cli/src/main.cpp

@@ -1,14 +1,15 @@
-#include "../include/commands.hpp"
-#include "../include/machineiomanager.hpp"
-#include "../include/useriomanager.hpp"
+#include "../include/machineioman.h"
+#include "../include/userioman.h"
 
-#include <boost/asio.hpp>
 #include <boost/program_options.hpp>
 #include <iostream>
 #include <string>
 
 namespace bpo = boost::program_options;
 
+/* this is provided for the readline callback in IoMan */
+IoMan *gIOMAN = NULL;
+
 void show_help(char *exec) {
   std::printf("ccats command line interface\n");
   std::printf("usage: %s IP [Options]\n", exec);
@@ -26,7 +27,7 @@ int main(int argc, char **argv) {
   bpo::variables_map vm;
   unsigned int machine = 0;
   const char *file = NULL;
-  IoManager *ioman;
+  IoMan *ioman;
 
   if (argc < 2 || !std::strncmp(argv[1], "--help", 6)) {
     show_help(argv[0]);
@@ -34,13 +35,6 @@ int main(int argc, char **argv) {
     return 1;
   }
 
-  if (!isdigit(argv[1][0])) {
-    std::printf("invalid ip\n");
-    show_help(argv[0]);
-    std::cout << desc;
-    return 1;
-  }
-
   try {
     store(parse_command_line(argc - 1, argv + 1, desc), vm);
     notify(vm);
@@ -65,15 +59,15 @@ int main(int argc, char **argv) {
               file ? file : "");
 
   if (machine) {
-    ioman = new MachineIoManager(argv[1]);
+    ioman = new MachineIoMan(argv[1]);
   } else {
-    ioman = new UserIoManager(argv[1]);
+    ioman = new UserIoMan(argv[1]);
   }
-
-  // ‘MachineIoManager::MachineIoManager(char*&)’
-  if (ioman->connect()) {
+  gIOMAN = ioman;
+  if (ioman->init()) {
     ioman->run();
   }
   delete ioman;
+  gIOMAN = NULL;
   std::printf("done\n");
 }

+ 182 - 0
cli/src/userioman.cpp

@@ -0,0 +1,182 @@
+#include "../include/userioman.h"
+
+#include <boost/algorithm/string.hpp>
+#include <fstream>
+#include <iostream>
+#include <vector>
+
+#include <readline/history.h>
+#include <readline/readline.h>
+
+using boost::asio::buffer;
+
+UserIoMan::UserIoMan(char *ipcstring) : IoMan(ipcstring) {
+
+  /* setup json stuff */
+  Json::CharReaderBuilder rbuilder;
+  wbuilder.settings_["indentation"] = "";
+  reader = rbuilder.newCharReader();
+
+  /* initialize print command map */
+  printmap["error"] = &UserIoMan::printError;
+  printmap["connect"] = &UserIoMan::printConnect;
+  printmap["help"] = &UserIoMan::printHelp;
+  printmap["status"] = &UserIoMan::printStatus;
+  printmap["disconnect"] = &UserIoMan::printDisconnect;
+  printmap["put"] = &UserIoMan::printPut;
+  printmap["get"] = &UserIoMan::printGet;
+  printmap["list"] = &UserIoMan::printList;
+  printmap["version"] = &UserIoMan::printVersion;
+  printmap["login"] = &UserIoMan::printLogin;
+  printmap["signup"] = &UserIoMan::printSignup;
+  printmap["putdata"] = &UserIoMan::printPutdata;
+  printmap["getdata"] = &UserIoMan::printGetdata;
+}
+
+UserIoMan::~UserIoMan() { delete reader; }
+
+void UserIoMan::printMessage(std::string msg, OutMsgType type) {
+  Json::Value root;
+  msgmutex.lock();
+  switch (type) {
+  case normal:
+  case error: {
+    // this should never happen outside of development
+    if (!reader->parse(msg.c_str(), msg.c_str() + msg.size(), &root,
+                       &jsonerror)) {
+      printMessage(string(__PRETTY_FUNCTION__) +
+                       " couldnt parse json data: " + jsonerror,
+                   debug);
+    } else {
+      printJson(root);
+    }
+    break;
+  }
+  //~ case error: {
+  //~ std::cout << msg << std::endl;
+  //~ break;
+  //~ }
+  case debug: {
+    std::cerr << msg << std::endl;
+    break;
+  }
+  }
+  rl_redisplay();
+  msgmutex.unlock();
+}
+
+void UserIoMan::printWelcomeMessage() {
+  std::cout << "please login by entering \"login <username> <password>\" \nor "
+               "sign up and log in with \"signup <username> <password>\""
+            << std::endl;
+}
+
+std::string UserIoMan::getCmdPrompt() { return "ccats> "; }
+
+// currently not in use:
+//~ std::string UserIoMan::getUserPrompt() { return "User: "; }
+//~ std::string UserIoMan::getPassPrompt() { return "Pass: "; }
+
+void UserIoMan::printJson(Json::Value root) {
+  map<string, void (UserIoMan::*)(Json::Value)>::iterator it =
+      printmap.find(root["command"].asString());
+  if (it == printmap.end()) {
+    // this should never happen outside of development
+    printMessage(string(__PRETTY_FUNCTION__) + " unknown command \"" +
+                     root["command"].asString() +
+                     "\".\nensure code is implemented.",
+                 debug);
+    return;
+  }
+  (this->*(printmap[root["command"].asString()]))(root);
+}
+
+void UserIoMan::printError(Json::Value root) {
+  std::cout << "Error: " << root["error"].asString() << std::endl;
+}
+
+void UserIoMan::printConnect(Json::Value root) {
+  if (!root["accept"].asBool()) {
+    std::cout << "Couldnt connect to " << root["address"].asString() << ":"
+              << root["port"].asUInt() << std::endl
+              << "Reason: " << root["error"].asString() << std::endl;
+  }
+}
+
+void UserIoMan::printHelp(Json::Value root) {
+  std::cout << "Available commands are: " << std::endl;
+  for (Json::Value i : root["names"])
+    std::cout << i.asString() << std::endl;
+}
+
+void UserIoMan::printStatus(Json::Value root) {
+  std::cout << "Server reports status: " << root["response"].asString()
+            << std::endl;
+}
+
+void UserIoMan::printDisconnect(Json::Value root) {
+  if (!root["accept"].asBool()) {
+    std::cout << "Disconnect failed." << std::endl;
+  } else {
+    std::cout << "Disconnect successful." << std::endl;
+  }
+}
+
+void UserIoMan::printPut(Json::Value root) {
+  if (!root["accept"].asBool()) {
+    std::cout << "Upload request for file " << root["file"].asString()
+              << " failed: " << root["error"].asString() << std::endl;
+  } else
+    std::cout << "Begin uploading file " << root["file"].asString()
+              << std::endl;
+}
+
+void UserIoMan::printGet(Json::Value root) {
+  if (!root["accept"].asBool()) {
+    std::cout << "Download request for file " << root["file"].asString()
+              << " failed: " << root["error"].asString() << std::endl;
+  } else
+    std::cout << "Begin downloading file " << root["file"].asString()
+              << std::endl;
+}
+
+void UserIoMan::printList(Json::Value root) {
+  if (!root["accept"].asBool()) {
+    std::cout << "Listing files failed: " << root["error"].asString()
+              << std::endl;
+  } else {
+    std::cout << "Listing files stored on server: " << std::endl;
+    for (Json::Value i : root["names"])
+      std::cout << i.asString() << std::endl;
+    std::cout << "End of list." << std::endl;
+  }
+}
+
+void UserIoMan::printVersion(Json::Value root) {
+  if (!root["accept"].asBool()) {
+    std::cout << "Version check failed. Server reports "
+              << root["serverversion"].asString() << " but client is "
+              << root["clientversion"].asString() << std::endl;
+  } else
+    std::cout << "Version check ok." << std::endl;
+}
+
+void UserIoMan::printLogin(Json::Value root) {
+  if (!root["accept"].asBool()) {
+    std::cout << "Login failed: " << root["error"].asString() << std::endl;
+  } else
+    std::cout << "Login ok." << std::endl;
+}
+
+void UserIoMan::printSignup(Json::Value root) {
+  if (!root["accept"].asBool()) {
+    std::cout << "Signup failed: " << root["error"].asString() << std::endl;
+  } else
+    std::cout << "Signup ok. You are now logged in." << std::endl;
+}
+
+void UserIoMan::printPutdata(Json::Value root) {}
+
+void UserIoMan::printGetdata(Json::Value root) {}
+
+void UserIoMan::printListdata(Json::Value root) {}

+ 0 - 401
cli/src/useriomanager.cpp

@@ -1,401 +0,0 @@
-#include "../include/useriomanager.hpp"
-#include "../include/base64.hpp"
-#include "../include/commands.hpp"
-
-#include <boost/algorithm/string.hpp>
-#include <fstream>
-#include <iostream>
-#include <vector>
-
-#include <readline/history.h>
-#include <readline/readline.h>
-
-#define BLOCKSIZE 8
-
-using boost::asio::buffer;
-
-void UserIoManager::run() {
-  std::cout << "UserIoManager::run() says hello!" << std::endl;
-
-  bool keep_reading = true;
-  COMMANDID cmd;
-
-  char *line = NULL;
-  std::vector<std::string> tokens;
-
-  while (keep_reading) {
-    free(line);
-
-    // read an input line
-    line = readline("ccats> ");
-
-    // if no input, do not add to history, and go to reading next line
-    if (strlen(line) == 0) {
-      continue;
-    }
-
-    // split input line into tokens
-    boost::algorithm::split(tokens, std::string(line),
-                            boost::algorithm::is_any_of(" "),
-                            boost::algorithm::token_compress_on);
-
-    // if input contains only spaces, do not add to history, and go to reading
-    // next line
-    if (tokens.size() < 1) {
-      continue;
-    }
-
-    // add the line to history
-    add_history(line);
-
-    // check for passed command
-    cmd = getCmdIdFromString(tokens[0].c_str());
-
-    switch (cmd) {
-    case CMD_STATUS: {
-
-      boost::asio::streambuf recvbuf;
-      Json::Value root, checkok;
-      const char *recvjson;
-
-      // ask for status
-      root["command"] = "status";
-      boost::asio::write(*tcpsock, buffer(Json::writeString(wbuilder, root)),
-                         errcode);
-      if (errcode) {
-        std::cerr << "couldnt send status query to server " << ipstring
-                  << std::endl
-                  << errcode.message() << std::endl;
-        keep_reading = false;
-        continue;
-      }
-
-      // recieve answer to status query
-      boost::asio::read(*tcpsock, recvbuf, boost::asio::transfer_at_least(1),
-                        errcode);
-      if (errcode && errcode != boost::asio::error::eof) {
-        std::cerr << "couldnt recieve status from " << ipstring << std::endl
-                  << errcode.message() << std::endl;
-        keep_reading = false;
-        continue;
-      }
-
-      // parse json
-      recvjson = boost::asio::buffer_cast<const char *>(recvbuf.data());
-      if (!reader->parse(recvjson, recvjson + recvbuf.size(), &root,
-                         &jsonerror)) {
-        std::cerr << "couldnt parse recieved json" << std::endl
-                  << jsonerror << std::endl;
-        keep_reading = false;
-        continue;
-      }
-
-      // remove processed data from recvbuf
-      recvbuf.consume(recvbuf.size());
-
-      // check if received answer to right query
-      checkok = root["command"];
-
-      if (checkok.asString().compare("status") != 0) {
-        std::cerr << "command check failed. client sent command \"status\""
-                  << std::endl
-                  << "server replied to command \"" << checkok << "\""
-                  << std::endl;
-        keep_reading = false;
-        continue;
-      } else {
-        std::cout << "server replied with status: " << root["response"]
-                  << std::endl;
-      }
-
-      break;
-    }
-    case CMD_PUT: {
-
-      if (tokens.size() < 2) {
-        std::cerr << "no file specified" << std::endl;
-        continue;
-      }
-
-      boost::asio::streambuf recvbuf;
-      Json::Value root, ackRecv;
-
-      std::fstream fs;
-      fs.open(tokens[1].c_str(), std::fstream::in | std::fstream::binary);
-
-      if (!fs.is_open()) {
-        std::cerr << "couldnt open file " << tokens[1] << std::endl;
-        break;
-      }
-      // determine file size
-      fs.seekg(0, fs.end);
-      int filelen = fs.tellg();
-      fs.seekg(0, fs.beg);
-
-      // determine file name
-      std::vector<std::string> path;
-      boost::algorithm::split(path, tokens[1], boost::algorithm::is_any_of("/"),
-                              boost::algorithm::token_compress_on);
-      std::string filename = path.back();
-
-      // send first query to server
-      root["command"] = "put";
-      root["file"] = filename;
-      root["size"] = filelen;
-
-      std::cout << root << std::endl;
-
-      if (!sendJson(root)) {
-        keep_reading = false;
-        continue;
-      }
-      std::cout << "sent request to server " << ipstring << std::endl;
-
-      if (!receiveJson(recvbuf)) {
-        keep_reading = false;
-        continue;
-      }
-      if (!parseJson(&root, recvbuf)) {
-        std::cerr << "received invalid JSON from server" << std::endl;
-        keep_reading = false;
-        continue;
-      }
-
-      std::cout << root << std::endl;
-
-      if (!root["accept"].asBool()) {
-        std::cerr << "server did not accept request" << std::endl;
-        keep_reading = false;
-        continue;
-      }
-
-      std::cout << "request successful, beginning to send file to " << ipstring
-                << std::endl;
-
-      // determine number of chunks
-      int size_remainder = filelen % BLOCKSIZE;
-      int chunks = filelen / BLOCKSIZE + ((size_remainder > 0) ? 1 : 0);
-
-      char readbuf[BLOCKSIZE];
-
-      // send file
-      root = Json::Value();
-      root["command"] = "put";
-      root["cancel"] = false;
-
-      while (chunks > 0) {
-        chunks--;
-        if (!chunks &
-            (size_remainder !=
-             0)) { // size_remainder is the size of the last chunk if it's not 0
-          fs.read(readbuf, size_remainder);
-          root["data"] = base64::encodeVector(std::vector<char>(
-              readbuf, readbuf + size_remainder * sizeof(char)));
-        } else { // not last chunk or last chunk has full length
-          fs.read(readbuf, BLOCKSIZE);
-          root["data"] = base64::encodeVector(
-              std::vector<char>(readbuf, readbuf + BLOCKSIZE * sizeof(char)));
-        }
-        root["remaining"] = chunks;
-
-        std::cout << root << std::endl;
-
-        if (!sendJson(root)) {
-          std::cerr << "couldn't send file, " << chunks + 1
-                    << " chunks not sent" << std::endl;
-          keep_reading = false;
-          continue;
-        }
-
-        if (!receiveJson(recvbuf)) {
-          continue;
-        }
-        if (!parseJson(&ackRecv, recvbuf)) {
-          std::cerr << "received invalid JSON from server" << std::endl;
-          continue;
-        }
-        // TODO: check for cancel answer
-      }
-
-      fs.close();
-      std::cout << "successfully sent file to " << ipstring << std::endl;
-
-      break;
-    }
-    case CMD_GET: {
-
-      if (tokens.size() < 2) {
-        std::cerr << "no file specified" << std::endl;
-        continue;
-      }
-
-      boost::asio::streambuf recvbuf;
-      Json::Value root, recvAck;
-
-      // send first query to server
-      root["command"] = "get";
-      root["file"] = tokens[1];
-
-      std::cout << root << std::endl;
-
-      if (!sendJson(root)) {
-        keep_reading = false;
-        continue;
-      }
-      std::cout << "sent request to server " << ipstring << std::endl;
-
-      if (!receiveJson(recvbuf)) {
-        keep_reading = false;
-        continue;
-      }
-      if (!parseJson(&root, recvbuf)) {
-        std::cerr << "received invalid JSON from server" << std::endl;
-        continue;
-      }
-
-      std::cout << root << std::endl;
-
-      if (!root["accept"].asBool()) {
-        std::cerr << "server did not accept request" << std::endl;
-        keep_reading = false;
-        continue;
-      }
-
-      std::cout << "request successful, beginning to receive file from "
-                << ipstring << std::endl;
-
-      // open file stream
-      std::fstream fs;
-      fs.open(tokens[1].c_str(),
-              std::fstream::out | std::fstream::trunc | std::fstream::binary);
-      std::vector<char> writebuf;
-
-      // number of remaining chunks, dummy value to entry loop
-      int remaining = std::numeric_limits<int>::max();
-      int recvCount = 0;
-
-      std::cout << "foobar" << std::endl;
-
-      while (remaining > 0) {
-
-        std::cout << "foobar0" << std::endl;
-
-        if (!receiveJson(recvbuf)) {
-          keep_reading = false;
-
-          std::cout << "fail1" << std::endl;
-
-          continue;
-        }
-
-        std::cout << "foobar1" << std::endl;
-
-        if (!parseJson(&root, recvbuf)) {
-          std::cerr << "received invalid JSON from server" << std::endl;
-          keep_reading = false;
-          continue;
-        }
-        std::cout << "foobar2" << std::endl;
-        std::cout << root << std::endl;
-
-        if (root["cancel"].asBool()) {
-          std::cerr << "server cancelled recieving file" << std::endl;
-          keep_reading = false;
-          continue;
-        }
-
-        writebuf = base64::decodeVector(root["data"].asString());
-        fs.write(writebuf.data(), writebuf.size());
-        remaining = root["remaining"].asInt();
-
-        std::cout << "remaining == " << remaining << std::endl;
-
-        recvAck["received"] = recvCount++;
-
-        if (!sendJson(recvAck)) {
-          continue;
-        }
-      }
-
-      fs.close();
-      break;
-    }
-    case CMD_LIST: {
-      boost::asio::streambuf recvbuf;
-      Json::Value root, recv;
-      //       const char *recvjson;
-
-      int chunks, rec = 0;
-      root["command"] = "list";
-      if (!sendJson(root)) {
-        std::cerr << "couldn't send json" << std::endl;
-        continue;
-      }
-      if (!receiveJson(recvbuf)) {
-        std::cerr << "couldn't receive json" << std::endl;
-        continue;
-      }
-      if (!parseJson(&recv, recvbuf)) {
-        std::cerr << "couldn't parse json" << std::endl;
-        continue;
-      }
-
-      if (recv["accept"].asBool()) {
-        // ACK?
-        root["command"] = "list";
-        root["cancel"] = false;
-        chunks = std::numeric_limits<int>::max();
-        while (chunks) {
-          if (!receiveJson(recvbuf)) {
-            std::cerr << "couldn't receive json" << std::endl;
-            break;
-          }
-          if (!parseJson(&recv, recvbuf)) {
-            std::cerr << "couldn't parse json" << std::endl;
-            break;
-          }
-          // dump received data
-          if (recv["names"].isArray()) {
-            for (Json::Value name : recv["names"]) {
-              std::cout << name.asString();
-            }
-          } else {
-            std::cerr << "should have recieved an array of files, but did not "
-                         "receive an array"
-                      << std::endl;
-          }
-          chunks = recv["remaining"].asInt();
-          root["received"] = rec;
-          if (!sendJson(root)) {
-            // couldnt ACK
-            break;
-          }
-          rec++;
-        }
-      } else {
-        std::cerr << "server refused listing" << std::endl;
-      }
-      break;
-    }
-    case CMD_DISCONNECT: {
-      std::printf("disconnecting\n");
-      keep_reading = false;
-      break;
-    }
-    default: {
-      std::printf("unknown command %s\n", line);
-    }
-    case CMD_HELP: {
-      std::printf("\n");
-      std::printf("AVAILABLE COMMANDS are:\n");
-      printCmds();
-      break;
-    }
-    }
-    std::printf("\n");
-  }
-  free(line);
-  rl_clear_history();
-
-  return;
-}

+ 0 - 1
daemon/CMakeLists.txt

@@ -1,7 +1,6 @@
 cmake_minimum_required(VERSION 2.8)
 
 set(CMAKE_CXX_STANDARD 11)
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
 
 project(ccats)
 

+ 17 - 0
daemon/Daemon-Config-Reference.md

@@ -0,0 +1,17 @@
+# Daemon configuration
+
+The daemon is configurable by config.txt.
+This file must be in the same directory as the binary.
+
+### Configuration Values
+`port` : The port where the server listens. Must be a valid port.
+`interface` : The sniffer interface you want to use.
+`userdatabase` : The file where userdata is stored in format: user;password
+
+### Example for config.txt
+```
+port=1234
+interface=lo
+userdatabase=user Storage.txt
+filedirectory=./files/
+```

+ 34 - 0
daemon/include/Config.h

@@ -0,0 +1,34 @@
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include <fstream>
+#include <map>
+#include <sstream>
+#include <vector>
+
+/**
+ * Namespace which loads and holds configuration data
+ */
+namespace Config {
+
+/**
+ * Load data from file into storage
+ *
+ * @param filename the name of the file which holds the data
+ */
+bool init(const std::string &filename);
+
+/**
+ * Load data from file into storage
+ *
+ * @param key get value from storage by key
+ */
+std::string getValue(const std::string &key);
+
+/**
+ * Map which holds the configuration data
+ */
+extern std::map<std::string, std::string> storage;
+}; // namespace Config
+
+#endif

+ 8 - 1
daemon/include/FileManager.h

@@ -11,7 +11,7 @@
  */
 class FileManager {
 private:
-  const std::string fileDirectory = "./files";
+  const std::string fileDirectory;
 
   /**
    * file stream for get command
@@ -39,6 +39,13 @@ private:
    */
   std::string getBaseFileName;
 
+  /*
+   * size of the get file
+   *
+   * Used to determine how much to read and encode as base64
+   */
+  std::streamoff getFileSize;
+
   /**
    * list vector for list command
    */

+ 1 - 1
daemon/include/JsonCommander.h

@@ -19,7 +19,7 @@ public:
    * closeAndSend - send the json answer and close the socket
    * close        - close the socket
    */
-  enum Action { send, closeAndSend, close };
+  enum Action { close, send, closeAndSend };
 
   /**
    * Response for commands

+ 69 - 0
daemon/include/UserManager.h

@@ -0,0 +1,69 @@
+#ifndef USER_MANAGER_H
+#define USER_MANAGER_H
+
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+/**
+ * @Class UserManager
+ *
+ * Handle differnt users with passwords and store them
+ */
+class UserManager {
+public:
+  /**
+   * Makes shure that there is a user storage.
+   * If not. It creates a new one
+   */
+  static void init(const std::string &file);
+
+  /**
+   * Checks if a given user with password is valid
+   *
+   * @param user the name of the users
+   * @param pw password in plain text
+   */
+  static bool isAllowed(const std::string &user, const std::string &pw);
+
+  /**
+   * Add a new user and write to storage.
+   *
+   * @param user the name of the users
+   * @param pw password in plain text
+   */
+  static void addUser(const std::string &name, const std::string &pw);
+
+  /**
+   * Delete a new user and delete from storage.
+   *
+   * @param user the name of the users
+   * @param pw password in plain text
+   */
+  static void deleteUser(const std::string &name, const std::string &pw);
+
+private:
+  /**
+   * Name of the file which holds the user data
+   */
+  static std::string filename;
+
+  /**
+   * Read data from file and create map.
+   *
+   * @param user_map pointer to the map
+   */
+  static void readFromFile(std::map<std::string, std::string> &user_map);
+
+  /**
+   * Write data to afile
+   *
+   * @param user_map pointer to the map
+   */
+  static void writeToFile(std::map<std::string, std::string> &user_map);
+};
+
+#endif

+ 1 - 4
daemon/src/CMakeLists.txt

@@ -1,11 +1,8 @@
 cmake_minimum_required(VERSION 2.8)
 
-set(CMAKE_CXX_STANDARD 11)
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
 
-project(ccats)
-
-add_executable(ccats src/main.cpp src/Sniffer.cpp src/Server.cpp src/base64.cpp src/JsonCommander.cpp src/FileManager.cpp)
+add_executable(ccats src/main.cpp src/Sniffer.cpp src/Server.cpp src/base64.cpp src/JsonCommander.cpp src/FileManager.cpp src/UserManager.cpp src/Config.cpp)
 
 # use pkg-config to fix building on debian unstable
 find_package(PkgConfig REQUIRED)

+ 36 - 0
daemon/src/Config.cpp

@@ -0,0 +1,36 @@
+#include "../include/Config.h"
+
+namespace Config {
+std::map<std::string, std::string> storage;
+};
+
+bool Config::init(const std::string &filename) {
+  std::ifstream ifile(filename);
+  std::string line;
+
+  if (ifile.is_open()) {
+    while (getline(ifile, line)) {
+      std::stringstream ss(line);
+      std::string segment;
+      std::vector<std::string> v;
+      while (getline(ss, segment, '=')) {
+        v.push_back(segment);
+      }
+      if (v.size() != 2) {
+        return false;
+      }
+      storage.insert(std::pair<std::string, std::string>(v.at(0), v.at(1)));
+    }
+    return true;
+  }
+  return false;
+}
+
+std::string Config::getValue(const std::string &key) {
+  auto it = storage.find(key);
+  if (it != storage.end()) {
+    return it->second;
+  }
+
+  return "";
+}

+ 25 - 14
daemon/src/FileManager.cpp

@@ -3,7 +3,9 @@
 #include <boost/filesystem.hpp>
 #include <boost/range/iterator_range.hpp>
 
-FileManager::FileManager() {}
+#include "../include/Config.h"
+
+FileManager::FileManager() : fileDirectory(Config::getValue("filedirectory")) {}
 
 FileManager::~FileManager() {
   cancelPut();
@@ -19,15 +21,17 @@ bool FileManager::openPutFile(const std::string &filename) {
   this->putFileName = this->fileDirectory;
   this->putFileName.append(filename);
 
-  // open file and test if it already exists
-  this->putFile.open(this->putFileName, std::ios::app | std::ios::binary);
-  if (this->putFile.tellp() != std::ios::beg) {
-    // file already exists
+  std::ifstream ifile(this->putFileName);
+  if (ifile.is_open()) {
+    // file alread exists
+    ifile.close();
     closePutFile();
     return false;
-  } else {
-    return true;
   }
+
+  // open file and test if it already exists
+  this->putFile.open(this->putFileName, std::ios::app | std::ios::binary);
+  return true;
 }
 
 bool FileManager::openGetFile(const std::string &filename, int &chunks) {
@@ -41,8 +45,9 @@ bool FileManager::openGetFile(const std::string &filename, int &chunks) {
     // file does not exist or cannot be opened
     return false;
   } else {
-    size_t size = this->getFile.tellg();
-    chunks = size / max_data_length + (size % max_data_length == 0 ? 0 : 1);
+    this->getFileSize = this->getFile.tellg();
+    chunks = this->getFileSize / max_data_length +
+             (this->getFileSize % max_data_length == 0 ? 0 : 1);
 
     this->getFile.seekg(std::ios::beg);
     return true;
@@ -69,16 +74,22 @@ std::string FileManager::getGetBaseFileName() { return this->getBaseFileName; }
 std::string FileManager::getPutBaseFileName() { return this->putBaseFileName; }
 
 void FileManager::writePut(const std::vector<char> &data) {
-  std::ostreambuf_iterator<char> output_iterator(this->putFile);
-  std::copy(data.begin(), data.end(), output_iterator);
+  this->putFile.write(data.data(), data.size());
 }
 
 std::vector<char> FileManager::readGet() {
-  char fileBuffer[max_data_length];
-  int read = this->getFile.readsome(fileBuffer, max_data_length);
+  // store position before read
+  std::streamoff read = this->getFile.tellg();
+  if (read + max_data_length > this->getFileSize) {
+    read = this->getFileSize % max_data_length;
+  } else {
+    read = max_data_length;
+  }
 
   std::vector<char> data;
-  data.assign(fileBuffer, fileBuffer + read);
+  data.resize(read);
+
+  this->getFile.read(data.data(), read);
 
   return data;
 }

+ 7 - 3
daemon/src/JsonCommander.cpp

@@ -1,4 +1,5 @@
 #include "../include/JsonCommander.h"
+#include "../include/UserManager.h"
 #include "../include/base64.h"
 
 JsonCommander::JsonCommander(FileManager &fileManager)
@@ -390,10 +391,13 @@ JsonCommander::Response JsonCommander::checkLogin(const Json::Value &message) {
     response.action = closeAndSend;
     response.json["accept"] = false;
     response.json["error"] = "invalid login request";
+  } else if (message["cancel"].asBool()) {
+    response.action = close;
+
   } else if (message["login"].asBool() &&
-             message["user"].asString().compare("user") == 0 &&
-             message["pass"].asString().compare("pass") == 0) {
-    // TODO replace with real credentials check
+             UserManager::isAllowed(message["user"].asString(),
+                                    message["pass"].asString())) {
+    // credential check
     response.action = send;
     response.json["accept"] = true;
     response.json["error"] = "";

+ 4 - 1
daemon/src/Server.cpp

@@ -1,4 +1,6 @@
 #include "../include/Server.h"
+#include "../include/Config.h"
+#include "../include/base64.h"
 
 #include <iostream>
 
@@ -171,7 +173,8 @@ void Server::start_accept() {
 }
 
 Server::Server(io_service &io_service)
-    : acceptor_(io_service, tcp::endpoint(tcp::v4(), 1234)) {
+    : acceptor_(io_service,
+                tcp::endpoint(tcp::v4(), std::stoi(Config::getValue("port")))) {
   start_accept();
 }
 

+ 83 - 0
daemon/src/UserManager.cpp

@@ -0,0 +1,83 @@
+#include "../include/UserManager.h"
+
+// TODO passwords are stored and checked in plain text
+// TODO read userStorage file location from config
+
+// initialize static filename to empty string
+std::string UserManager::filename = "";
+
+void UserManager::init(const std::string &file) {
+  filename = file;
+  std::ifstream ifile(filename);
+  if (!ifile.is_open()) {
+    // create new file if userStorage does not exist
+    std::ofstream ofile;
+    ofile.open(filename);
+    ofile << "user;pass\n";
+    ofile.close();
+    std::cout << "Created \"" << filename << "\" and added the default user"
+              << std::endl;
+  }
+  ifile.close();
+}
+
+bool UserManager::isAllowed(const std::string &name, const std::string &pw) {
+  std::map<std::string, std::string> user_map;
+  readFromFile(user_map);
+  auto it = user_map.find(name);
+  // check if user exists and pw is equal
+  if (it != user_map.end() && (it->second.compare(pw) == 0)) {
+    return true;
+  }
+  return false;
+}
+
+void UserManager::addUser(const std::string &name, const std::string &pw) {
+  std::map<std::string, std::string> user_map;
+  readFromFile(user_map);
+  auto it = user_map.find(name);
+  // if user exists, do nothing
+  if (it != user_map.end()) {
+    return;
+  }
+  user_map.insert(std::pair<std::string, std::string>(name, pw));
+  writeToFile(user_map);
+}
+
+void UserManager::deleteUser(const std::string &name, const std::string &pw) {
+  // TODO check pw before delete
+  std::map<std::string, std::string> user_map;
+  readFromFile(user_map);
+  auto it = user_map.find(name);
+  if (it == user_map.end()) {
+    return;
+  }
+  user_map.erase(it);
+  writeToFile(user_map);
+}
+
+// read content from file into given map
+void UserManager::readFromFile(std::map<std::string, std::string> &user_map) {
+  std::ifstream ifile(filename);
+  std::string line;
+  while (getline(ifile, line)) {
+    std::stringstream ss(line);
+    std::string segment;
+    std::vector<std::string> v;
+    while (std::getline(ss, segment, ';')) {
+      v.push_back(segment);
+    }
+    user_map.insert(std::pair<std::string, std::string>(v.at(0), v.at(1)));
+  }
+  ifile.close();
+}
+
+// write content from map to file
+void UserManager::writeToFile(std::map<std::string, std::string> &user_map) {
+  std::ofstream file;
+  file.open(filename);
+  for (auto const &x : user_map) {
+    file << x.first << ";" << x.second << std::endl;
+  }
+  file.close();
+}

+ 35 - 26
daemon/src/base64.cpp

@@ -1,40 +1,49 @@
 #include "../include/base64.h"
 
-#include <boost/algorithm/string.hpp>
-#include <boost/archive/iterators/base64_from_binary.hpp>
-#include <boost/archive/iterators/binary_from_base64.hpp>
-#include <boost/archive/iterators/transform_width.hpp>
+#include <boost/beast.hpp>
 
 std::string base64::decode(const std::string &val) {
-  using namespace boost::archive::iterators;
-  using It =
-      transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>;
-  return boost::algorithm::trim_right_copy_if(
-      std::string(It(std::begin(val)), It(std::end(val))),
-      [](char c) { return c == '\0'; });
+  std::string ret;
+  ret.resize(boost::beast::detail::base64::decoded_size(val.size()));
+
+  std::size_t read = boost::beast::detail::base64::decode(
+                         &ret.front(), val.c_str(), val.size())
+                         .first;
+
+  ret.resize(read);
+  return ret;
 }
 
 std::vector<char> base64::decodeVector(const std::string &val) {
-  using namespace boost::archive::iterators;
-  using It =
-      transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>;
-  return boost::algorithm::trim_right_copy_if(
-      std::vector<char>(It(std::begin(val)), It(std::end(val))),
-      [](char c) { return c == '\0'; });
+  std::vector<char> ret;
+  ret.resize(boost::beast::detail::base64::decoded_size(val.size()));
+
+  std::size_t read =
+      boost::beast::detail::base64::decode(ret.data(), val.c_str(), val.size())
+          .first;
+
+  ret.resize(read);
+  return ret;
 }
 
 std::string base64::encode(const std::string &val) {
-  using namespace boost::archive::iterators;
-  using It =
-      base64_from_binary<transform_width<std::string::const_iterator, 6, 8>>;
-  auto tmp = std::string(It(std::begin(val)), It(std::end(val)));
-  return tmp.append((3 - val.size() % 3) % 3, '=');
+  std::string ret;
+  ret.resize(boost::beast::detail::base64::encoded_size(val.size()));
+
+  std::size_t read = boost::beast::detail::base64::encode(
+      &ret.front(), val.data(), val.size());
+
+  ret.resize(read);
+  return ret;
 }
 
 std::string base64::encodeVector(const std::vector<char> &val) {
-  using namespace boost::archive::iterators;
-  using It = base64_from_binary<
-      transform_width<std::vector<char>::const_iterator, 6, 8>>;
-  auto tmp = std::string(It(std::begin(val)), It(std::end(val)));
-  return tmp.append((3 - val.size() % 3) % 3, '=');
+  std::string ret;
+  ret.resize(boost::beast::detail::base64::encoded_size(val.size()));
+
+  std::size_t read = boost::beast::detail::base64::encode(
+      &ret.front(), val.data(), val.size());
+
+  ret.resize(read);
+  return ret;
 }

+ 12 - 4
daemon/src/main.cpp

@@ -1,18 +1,26 @@
 #include <iostream>
 #include <thread>
 
+#include "../include/Config.h"
 #include "../include/Server.h"
 #include "../include/Sniffer.h"
+#include "../include/UserManager.h"
 
 using namespace std;
 
 int main(int argc, char *argv[]) {
-  if (argc < 2) {
-    cout << "Usage: " << argv[0] << " <interface>" << endl << endl;
-    return 0;
+  // load config int namespace
+  if (!Config::init("config.txt")) {
+    cerr << "configuration could not be loaded properly" << endl;
+    exit(EXIT_FAILURE);
   }
 
-  const string interface = argv[1];
+  const string interface = Config::getValue("interface");
+
+  // check if userStorage is add specified location
+  // if not create one
+  UserManager::init(Config::getValue("userdatabase"));
+
   Sniffer *sniffer = new Sniffer(interface);
   thread snifferThread(&Sniffer::startSniffing, sniffer);
 

+ 2 - 5
daemon/test/CMakeLists.txt

@@ -1,9 +1,6 @@
 cmake_minimum_required(VERSION 2.8)
 
-set(CMAKE_CXX_STANDARD 11)
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
-
-project(ccats)
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/test)
 
 # use pkg-config to fix building on debian unstable
 find_package(PkgConfig REQUIRED)
@@ -23,7 +20,7 @@ find_package(GTest REQUIRED)
 include_directories(${Boost_INCLUDE_DIR} ${JSONCPP_INCLUDEDIR} ${GMOCK_INCLUDE_DIR} ${GTEST_INCLUDE_DIR})
 
 # Add test cpp file
-add_executable(jsonCommanderTest test/JsonCommanderTest.cpp src/JsonCommander.cpp src/FileManager.cpp src/base64.cpp)
+add_executable(jsonCommanderTest test/JsonCommanderTest.cpp src/JsonCommander.cpp src/FileManager.cpp src/base64.cpp src/UserManager.cpp test/ConfigMock.cpp)
 target_link_libraries(jsonCommanderTest ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} ${JSONCPP_LIBRARIES} ${GMOCK_LIBRARIES} ${GTEST_LIBRARY} ${GTEST_MAIN_LIBRARY})
 
 add_test(

+ 19 - 0
daemon/test/ConfigMock.cpp

@@ -0,0 +1,19 @@
+#include "../include/Config.h"
+
+namespace Config {
+  std::map<std::string, std::string> storage;
+};
+
+bool Config::init(const std::string &filename) {
+  return true;
+}
+
+std::string Config::getValue(const std::string &key) {
+  auto it = storage.find(key);
+  if (it != storage.end()) {
+    return it->second;
+  }
+
+  return "";
+}
+

+ 1 - 0
gui/src/FooterForm.ui.qml

@@ -63,6 +63,7 @@ Page {
                 TextArea.flickable: TextArea {
                     selectByMouse: true
                     id: footerLog
+                    readOnly: true
                     wrapMode: TextArea.Wrap
                     text: qsTr("")
                     font.pointSize: 15

+ 26 - 18
gui/src/IpPopupForm.ui.qml → gui/src/IpPopup.ui.qml

@@ -12,12 +12,25 @@ Popup {
     focus: true
     closePolicy: Popup.NoAutoClose
 
+    Connections {
+        target: _qmlHandler
+        onIpPopupClose: {
+            popup.close()
+        }
+        onIpPopupOpen: {
+          popup.open();
+        }
+        onIpPopupSetStatus: {
+            ipPopupStatusText.text = status
+        }
+    }
+
     ColumnLayout {
         anchors.fill: parent
 
         Text {
             Layout.alignment: Qt.AlignCenter
-            id: popupText
+            id: ipPopupText
             color: "#ffffff"
             text: qsTr("Enter the IP to connect:")
             horizontalAlignment: Text.AlignHCenter
@@ -27,23 +40,23 @@ Popup {
 
         TextField {
             Layout.alignment: Qt.AlignCenter
-            id: popupIpInput
+            id: ipPopupIpInput
             selectByMouse: true
             focus: true
             text: qsTr("")
-            placeholderText: "Enter IP"
+            placeholderText: "IP-Address"
             horizontalAlignment: Text.AlignHCenter
             validator: RegExpValidator {
                 regExp: /^(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))$/
             }
             // @disable-check M222
-            Keys.onReturnPressed: popupConnectButton.activate()
+            Keys.onReturnPressed: ipPopupConnectButton.activate()
             // @disable-check M222
-            Keys.onEnterPressed: popupConnectButton.activate()
+            Keys.onEnterPressed: ipPopupConnectButton.activate()
         }
 
         Text {
-            id: popupStatusText
+            id: ipPopupStatusText
             color: "#df3f3f"
             text: qsTr("")
             horizontalAlignment: Text.AlignHCenter
@@ -54,31 +67,26 @@ Popup {
 
         Button {
             Layout.alignment: Qt.AlignCenter
-            id: popupConnectButton
+            id: ipPopupConnectButton
             text: qsTr("Connect")
-            enabled: popupIpInput.acceptableInput
+            rightPadding: 8
+            padding: 12
+            enabled: ipPopupIpInput.acceptableInput
             font.pointSize: 16
             // @disable-check M223
             onClicked: {
                 // @disable-check M222
-                popupConnectButton.activate()
+                ipPopupConnectButton.activate()
             }
 
             // @disable-check M222
             function activate() {
                 // @disable-check M223
-                if (popupIpInput.acceptableInput) {
-                    // @disable-check M222
-                    _qmlHandler.onIpPopupEnterIp(popupIpInput.text)
+                if (ipPopupIpInput.acceptableInput) {
                     // @disable-check M222
-                    popup.close()
+                    _qmlHandler.onIpPopupConnectButton(ipPopupIpInput.text)
                 }
             }
         }
     }
 }
-
-/*##^## Designer {
-    D{i:0;autoSize:true;height:480;width:640}
-}
- ##^##*/

+ 91 - 0
gui/src/LoginForm.ui.qml

@@ -0,0 +1,91 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.3
+
+Page {
+    width: 400
+    height: 400
+
+    Connections {
+        target: _qmlHandler
+        onLoginSetStatus: {
+            loginStatusText.text = status
+        }
+    }
+
+    ColumnLayout {
+        anchors.fill: parent
+
+        Text {
+            Layout.alignment: Qt.AlignCenter
+            id: loginTitle
+            color: "#ffffff"
+            text: qsTr("Login")
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            font.pixelSize: 20
+        }
+
+        TextField {
+            Layout.alignment: Qt.AlignCenter
+            id: loginUsernameInput
+            selectByMouse: true
+            focus: true
+            text: qsTr("")
+            placeholderText: "Username"
+            horizontalAlignment: Text.AlignHCenter
+            // @disable-check M222
+            Keys.onReturnPressed: loginLoginButton.activate()
+            // @disable-check M222
+            Keys.onEnterPressed: loginLoginButton.activate()
+        }
+
+        TextField {
+            Layout.alignment: Qt.AlignCenter
+            id: loginPasswordInput
+            selectByMouse: true
+            focus: true
+            text: qsTr("")
+            placeholderText: "Password"
+            horizontalAlignment: Text.AlignHCenter
+            // @disable-check M222
+            Keys.onReturnPressed: loginLoginButton.activate()
+            // @disable-check M222
+            Keys.onEnterPressed: loginLoginButton.activate()
+            echoMode: TextInput.Password
+        }
+
+        Text {
+            id: loginStatusText
+            color: "#df3f3f"
+            text: qsTr("")
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            Layout.alignment: Qt.AlignCenter
+            font.pixelSize: 20
+        }
+
+        Button {
+            Layout.alignment: Qt.AlignCenter
+            id: loginLoginButton
+            text: qsTr("Login")
+            enabled: (loginUsernameInput.text != ""
+                      && loginPasswordInput.text != "")
+            font.pointSize: 16
+            // @disable-check M223
+            onClicked: {
+                // @disable-check M222
+                loginLoginButton.activate()
+            }
+
+            // @disable-check M222
+            function activate() {
+                // @disable-check M223
+                if (loginLoginButton.enabled) {
+                    // @disable-check M222
+                    _qmlHandler.onLoginLoginButton(loginUsernameInput.text, loginPasswordInput.text)
+                }
+            }
+        }
+    }
+}

+ 61 - 0
gui/src/LoginSignupPopup.ui.qml

@@ -0,0 +1,61 @@
+import QtQuick 2.4
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.3
+
+Popup {
+    id: popup
+    height: 450
+    dim: true
+    clip: false
+    width: 400
+    modal: true
+    focus: true
+    closePolicy: Popup.NoAutoClose
+
+    Connections {
+        target: _qmlHandler
+        onLoginSignupPopupClose: {
+            popup.close()
+        }
+        onLoginSignupPopupOpen: {
+          popup.open()
+        }
+    }
+
+    Page {
+        anchors.fill: parent
+
+        header: TabBar {
+            id: header
+            currentIndex: swipeView.currentIndex
+            contentHeight: 50
+
+            TabButton {
+                text: qsTr("Login")
+            }
+
+            TabButton {
+                text: qsTr("Signup")
+            }
+        }
+
+        SwipeView {
+            id: swipeView
+            anchors.fill: parent
+            currentIndex: header.currentIndex
+            clip: true
+
+            LoginForm {
+
+            }
+
+            SignupForm {
+
+            }
+        }
+    }
+
+    Component.onCompleted: {
+      swipeView.interactive = false
+    }
+}

+ 1 - 0
gui/src/MessagesForm.ui.qml

@@ -27,6 +27,7 @@ Page {
 
             TextArea.flickable: TextArea {
                 id: messagesLog
+                readOnly: true
                 selectByMouse: true
                 wrapMode: TextArea.Wrap
                 font.pointSize: 15

+ 45 - 1
gui/src/ReceivingForm.ui.qml

@@ -8,9 +8,40 @@ Page {
 
     font.capitalization: Font.MixedCase
 
+    Connections {
+        target: _qmlHandler
+        onReceivingListFile: {
+            receivingFileList.append(fileName)
+            receivingFlickable.contentY = receivingFileList.height - receivingFlickable.height
+        }
+
+        onReceivingClearFileList: {
+            receivingFileList.text = ""
+        }
+    }
+
     ColumnLayout {
         anchors.fill: parent
 
+        Flickable {
+            id: receivingFlickable
+            flickableDirection: Flickable.VerticalFlick
+            Layout.preferredHeight: 400
+            Layout.preferredWidth: parent.width
+
+            TextArea.flickable: TextArea {
+                selectByMouse: true
+                readOnly: true
+                id: receivingFileList
+                wrapMode: TextArea.Wrap
+                text: qsTr("")
+                font.pointSize: 15
+            }
+
+            ScrollBar.vertical: ScrollBar {
+            }
+        }
+
         RowLayout {
             Layout.preferredWidth: parent.width
             Layout.preferredHeight: 70
@@ -18,13 +49,26 @@ Page {
             TextField {
                 id: receivingFileNameField
                 selectByMouse: true
-                Layout.preferredWidth: 1060
+                Layout.preferredWidth: 880
                 Layout.preferredHeight: parent.height
                 placeholderText: "Enter File Name to download..."
                 text: qsTr("")
                 font.pixelSize: 20
             }
 
+            Button {
+                id: receivingListFilesButton
+                Layout.preferredWidth: 180
+                Layout.preferredHeight: parent.height
+                text: qsTr("List Files")
+
+                // @disable-check M223
+                onClicked: {
+                    // @disable-check M223
+                    _qmlHandler.onReceivingListFilesButton()
+                }
+            }
+
             Button {
                 id: receivingGetFileButton
                 Layout.preferredWidth: 180

+ 111 - 0
gui/src/SignupForm.ui.qml

@@ -0,0 +1,111 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.3
+
+Page {
+  width: 400
+  height: 400
+
+  Connections {
+      target: _qmlHandler
+      onSignupSetStatus: {
+          signupStatusText.text = status
+      }
+  }
+
+  ColumnLayout {
+      anchors.fill: parent
+
+      Text {
+          Layout.alignment: Qt.AlignCenter
+          id: signupTitle
+          color: "#ffffff"
+          text: qsTr("Signup")
+          horizontalAlignment: Text.AlignHCenter
+          verticalAlignment: Text.AlignVCenter
+          font.pixelSize: 20
+      }
+
+      TextField {
+          Layout.alignment: Qt.AlignCenter
+          id: signupUsernameInput
+          selectByMouse: true
+          focus: true
+          text: qsTr("")
+          placeholderText: "Username"
+          horizontalAlignment: Text.AlignHCenter
+          // @disable-check M222
+          Keys.onReturnPressed: signupRegisterButton.activate()
+          // @disable-check M222
+          Keys.onEnterPressed: signupRegisterButton.activate()
+      }
+
+      TextField {
+          Layout.alignment: Qt.AlignCenter
+          id: signupPasswordOneInput
+          selectByMouse: true
+          focus: true
+          text: qsTr("")
+          placeholderText: "Password"
+          horizontalAlignment: Text.AlignHCenter
+          // @disable-check M222
+          Keys.onReturnPressed: signupRegisterButton.activate()
+          // @disable-check M222
+          Keys.onEnterPressed: signupRegisterButton.activate()
+          echoMode: TextInput.Password
+
+          onTextEdited: signupStatusText.text = ""
+      }
+
+      TextField {
+          Layout.alignment: Qt.AlignCenter
+          id: signupPasswordTwoInput
+          selectByMouse: true
+          focus: true
+          text: qsTr("")
+          placeholderText: "Repeat Passw."
+          horizontalAlignment: Text.AlignHCenter
+          // @disable-check M222
+          Keys.onReturnPressed: signupRegisterButton.activate()
+          // @disable-check M222
+          Keys.onEnterPressed: signupRegisterButton.activate()
+          echoMode: TextInput.Password
+
+          onTextEdited: signupStatusText.text = ""
+      }
+
+      Text {
+          id: signupStatusText
+          color: "#df3f3f"
+          text: qsTr("")
+          horizontalAlignment: Text.AlignHCenter
+          verticalAlignment: Text.AlignVCenter
+          Layout.alignment: Qt.AlignCenter
+          font.pixelSize: 20
+      }
+
+      Button {
+          Layout.alignment: Qt.AlignCenter
+          id: signupRegisterButton
+          text: qsTr("Register")
+          enabled: (signupUsernameInput.text != ""
+                    && signupPasswordOneInput.text != ""
+                    && signupPasswordTwoInput.text != "")
+          font.pointSize: 16
+          // @disable-check M223
+          onClicked: {
+              // @disable-check M222
+              signupRegisterButton.activate()
+          }
+
+          // @disable-check M222
+          function activate() {
+              // @disable-check M223
+              if (signupRegisterButton.enabled) {
+                  // @disable-check M222
+                  _qmlHandler.onSignupRegisterButton(signupUsernameInput.text, signupPasswordOneInput.text, signupPasswordTwoInput.text);
+              }
+          }
+      }
+   }
+}

+ 0 - 97
gui/src/SwitchIpPopupForm.ui.qml

@@ -1,97 +0,0 @@
-import QtQuick 2.4
-import QtQuick.Controls 2.3
-import QtQuick.Layouts 1.3
-
-Popup {
-    id: popup
-    height: 200
-    dim: true
-    clip: false
-    width: 400
-    modal: true
-    focus: true
-    closePolicy: Popup.NoAutoClose
-
-    ColumnLayout {
-        anchors.fill: parent
-
-        Text {
-            Layout.alignment: Qt.AlignCenter
-            id: switchText
-            color: "#ffffff"
-            text: qsTr("Enter the IP to connect:")
-            horizontalAlignment: Text.AlignHCenter
-            verticalAlignment: Text.AlignVCenter
-            font.pixelSize: 20
-        }
-
-        TextField {
-            Layout.alignment: Qt.AlignCenter
-            id: switchIpInput
-            selectByMouse: true
-            text: qsTr("")
-            placeholderText: "Enter IP"
-            horizontalAlignment: Text.AlignHCenter
-            validator: RegExpValidator {
-                regExp: /^(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))$/
-            }
-
-            // @disable-check M222
-            Keys.onReturnPressed: switchConnectButton.activate()
-            // @disable-check M222
-            Keys.onEnterPressed: switchConnectButton.activate()
-        }
-
-        Text {
-            id: switchStatusText
-            color: "#df3f3f"
-            text: qsTr("")
-            horizontalAlignment: Text.AlignHCenter
-            verticalAlignment: Text.AlignVCenter
-            Layout.alignment: Qt.AlignCenter
-            font.pixelSize: 20
-        }
-
-        RowLayout {
-            Layout.alignment: Qt.AlignCenter
-
-            Button {
-                Layout.alignment: Qt.AlignLeft
-                id: switchConnectButton
-                text: qsTr("Connect")
-                rightPadding: 8
-                padding: 12
-                enabled: switchIpInput.acceptableInput
-                font.pointSize: 16
-                // @disable-check M223
-                onClicked: {
-                    // @disable-check M222
-                    switchConnectButton.activate()
-                }
-
-                // @disable-check M222
-                function activate() {
-                    // @disable-check M223
-                    if (switchIpInput.acceptableInput) {
-                        // @disable-check M222
-                        _qmlHandler.onSwitchPopupEnterIp(switchIpInput.text)
-                        // @disable-check M222
-                        popup.close()
-                    }
-                }
-            }
-
-            Button {
-                Layout.alignment: Qt.AlignRight
-                id: switchCancelButton
-                text: qsTr("Cancel")
-                font.pointSize: 16
-                // @disable-check M223
-                onClicked: {
-                    // @disable-check M222
-                    popup.close()
-                }
-            }
-        }
-    }
-}

+ 8 - 0
gui/src/main.cpp

@@ -29,14 +29,22 @@ int main(int argc, char *argv[]) {
 
   QMLHandler qmlHandler;
 
+  // Set the context for the window, so that the qml files can be connected to
+  // the qmlHandler
   engine.rootContext()->setContextProperty("_qmlHandler", &qmlHandler);
 
+  // Load the main window
   QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
 
   QObject *object = component.create();
 
+  // Run the main window and save the return of the application in an integer
+  // ret This is blocking, which means it will block the main program until the
+  // window is closed
   int ret = app.exec();
 
+  // If we land here, the window has been closed. Properly disconnect from the
+  // server now
   qmlHandler.onExit();
 
   return ret;

+ 6 - 5
gui/src/main.qml

@@ -71,20 +71,21 @@ ApplicationWindow {
 
     }
 
-    IpPopupForm {
-        id: ipDialog
+    LoginSignupPopup {
+        id: loginSignupPopup
         x: Math.round((parent.width - width) / 2)
         y: Math.round((parent.height - height) / 2)
     }
 
-    SwitchIpPopupForm {
-        id: switchDialog
+    IpPopup {
+        id: ipPopup
         x: Math.round((parent.width - width) / 2)
         y: Math.round((parent.height - height) / 2)
+        onClosed: loginSignupPopup.open()
     }
 
     Component.onCompleted: {
       swipeView.interactive = false
-      ipDialog.open()
+      ipPopup.open()
     }
 }

+ 4 - 2
gui/src/qml.qrc

@@ -6,9 +6,11 @@
         <file>SettingsForm.ui.qml</file>
         <file>MessagesForm.ui.qml</file>
         <file>qtquickcontrols2.conf</file>
-        <file>IpPopupForm.ui.qml</file>
+        <file>LoginSignupPopup.ui.qml</file>
         <file>HelpForm.ui.qml</file>
         <file>FooterForm.ui.qml</file>
-        <file>SwitchIpPopupForm.ui.qml</file>
+        <file>IpPopup.ui.qml</file>
+        <file>LoginForm.ui.qml</file>
+        <file>SignupForm.ui.qml</file>
     </qresource>
 </RCC>

+ 191 - 44
gui/src/qmlhandler.cpp

@@ -20,54 +20,203 @@ using namespace std;
 
 int inpipefd[2];
 int outpipefd[2];
-char buf[1024];
+char buf[1025];
 QUrl sendFileUrl = QUrl("");
+QString _IP = "";
+bool _CLI_RUNNING = false;
 
 bool programActive = true;
 
 QMLHandler::QMLHandler(QObject *parent) : QObject(parent) {}
 
+void QMLHandler::closeCLI() {
+  close(outpipefd[1]);
+  close(inpipefd[0]);
+}
+
+void QMLHandler::reopenCLI(QString ip) {
+  if (_CLI_RUNNING) {
+    closeCLI();
+  }
+
+  _IP = ip;
+  pid_t pid = 0;
+
+  pipe(inpipefd);
+  pipe(outpipefd);
+  pid = fork();
+  if (pid == 0) {
+    // Child
+    dup2(outpipefd[0], STDIN_FILENO);
+    dup2(inpipefd[1], STDOUT_FILENO);
+    // dup2(inpipefd[1], STDERR_FILENO);
+
+    // ask kernel to deliver SIGTERM in case the parent dies
+    prctl(PR_SET_PDEATHSIG, SIGTERM);
+
+    // Set the path to the CLI - pass argument h (help) for now
+    // TODO: Change hardcoded path
+    execl("../../cli/build/ccats-cli", "ccats-cli", ip.toUtf8().constData(),
+          "--machine", (char *)NULL);
+
+    _CLI_RUNNING = true;
+
+    exit(1);
+  }
+
+  close(outpipefd[0]);
+  close(inpipefd[1]);
+
+  std::thread(&QMLHandler::readPipeLoop, this).detach();
+}
+
+std::vector<std::string> tokenizeByNewlines(std::string in) {
+  vector<string> res;
+  size_t index;
+  for (index = in.find("\n"); index != std::string::npos;
+       index = in.find("\n")) {
+    if (index != 0)
+      res.push_back(in.substr(0, index));
+    in = in.substr(index + 1);
+  }
+  if (in.length() > 0)
+    res.push_back(in);
+  return res;
+}
+
 void QMLHandler::onExit() {
   write(outpipefd[1], "disconnect\n", strlen("disconnect\n"));
 }
 
+// This method gets a string and tries to read it as Json
+// If it fails to do so, return and do nothing, else handle the content
 void QMLHandler::handleJSON(string buffer) {
   Json::Value root;
   Json::CharReaderBuilder builder;
   Json::CharReader *reader = builder.newCharReader();
   string jsonError;
 
+  // Try to parse the string as Json and store the result of the pasring in a
+  // boolean
   bool parsingSuccessful = reader->parse(
       buffer.c_str(), buffer.c_str() + buffer.size(), &root, &jsonError);
 
+  // If the string is not correct Json, return
   if (!parsingSuccessful) {
     return;
   }
+
   const Json::Value command = root["command"];
   string cmd = command.asString();
+  qInfo() << QString::fromStdString("Received command " + cmd);
 
-  if (cmd == "status") {
+  if (!cmd.compare("status")) {
     emit footerSetStatus(root["response"].asString().c_str());
   }
 
-  else if (cmd == "close") {
+  else if (!cmd.compare("close")) {
     programActive = false;
   }
+
+  else if (!cmd.compare("list")) {
+    if (root["accept"] == true) {
+      emit receivingClearFileList();
+
+      // Get the array of file Names
+      auto fileNames = root["names"];
+      for (int i = 0; i < fileNames.size(); i++) {
+        emit receivingListFile(
+            QString::fromStdString(fileNames[i].asString().c_str()));
+      }
+    } else {
+      emit log(root["error"].asString().c_str());
+    }
+  }
+
+  else if (!cmd.compare("connect")) {
+    if (root["accept"].asBool()) {
+    } else {
+      emit ipPopupSetStatus(root["error"].asString().c_str());
+      reopenCLI(_IP);
+    }
+  }
+
+  else if (!cmd.compare("version")) {
+    if (root["accept"] == true) {
+      emit ipPopupClose();
+      emit loginSignupPopupOpen();
+    } else {
+      QString errorMessage = QString::fromStdString(string(
+          "Version mismatch: \nClient: " + root["clientversion"].asString() +
+          "\nServer: " + root["serverversion"].asString()));
+      emit ipPopupSetStatus(errorMessage);
+      reopenCLI(_IP);
+    }
+  }
+
+  else if (!cmd.compare("login")) {
+    if (root["accept"] == true) {
+      emit loginSignupPopupClose();
+    } else {
+      emit loginSetStatus(root["error"].asString().c_str());
+      reopenCLI(_IP);
+    }
+  }
+
+  else if (!cmd.compare("signup")) {
+    if (root["accept"] == true) {
+      emit loginSignupPopupClose();
+    } else {
+      emit loginSetStatus(root["error"].asString().c_str());
+      reopenCLI(_IP);
+    }
+  }
+
+  else if (!cmd.compare("put")) {
+    if (root["accept"] == false) {
+      QString errorMessage = QString::fromStdString(
+          string("Error when uploading file " + root["file"].asString() +
+                 ":\n" + root["error"].asString()));
+      emit log(errorMessage);
+    }
+  }
+
+  else if (!cmd.compare("putdata")) {
+    // TODO: Show speed and handle Error
+  }
+
+  else if (!cmd.compare("get")) {
+    if (root["accept"] == false) {
+      QString errorMessage = QString::fromStdString(
+          string("Error when downloading file " + root["file"].asString() +
+                 ":\n" + root["error"].asString()));
+      emit log(errorMessage);
+    }
+  }
+
+  else if (!cmd.compare("getdata")) {
+    // TODO: Show speed and handle Error
+  }
 }
 
+// This method is a loop which runs in a seperate thread.
+// If will read the Input Pipe, which containts the string that get printed
+// on stdout by the CLI/Server, and calls handleJSON with the read content
+// one it reads a newline.
 void QMLHandler::readPipeLoop() {
   unsigned int readOffset = 0;
   unsigned int pollCount = 0;
   struct pollfd inPipeStatus;
   inPipeStatus.fd = inpipefd[0];
   inPipeStatus.events = POLLIN;
+  std::vector<std::string> inputs;
 
   while (programActive) {
+    inputs = std::vector<std::string>();
     poll(&inPipeStatus, 1, 100);
 
     if (inPipeStatus.revents & POLLIN) {
       readOffset += read(inpipefd[0], buf + readOffset, 1);
-
       pollCount = 0;
 
     } else {
@@ -75,19 +224,29 @@ void QMLHandler::readPipeLoop() {
     }
 
     if (pollCount > 9 && buf[0]) {
-      buf[1023] = 0;
+      buf[1024] = 0;
       buf[strlen(buf)] = 0;
-
-      string cleanBuffer = buf + strcspn(buf, "\n") + 1;
-      string receivedData = cleanBuffer.substr(0, cleanBuffer.size() - 1);
-
-      emit log(QString::fromStdString(receivedData));
-      qInfo() << QString::fromStdString(receivedData);
-      handleJSON(receivedData);
+      inputs = tokenizeByNewlines(string(buf));
+      for (string s : inputs) {
+        //~ string cleanBuffer = buf + strcspn(buf, "\n") + 1;
+        //~ string receivedData = cleanBuffer.substr(0, cleanBuffer.size() - 1);
+
+        emit log(QString::fromStdString(s));
+        qInfo() << QString::fromStdString(s);
+        handleJSON(s);
+      }
       memset(buf, 0, 1024);
       pollCount = 0;
       readOffset = 0;
     }
+
+    // Fixme
+    if (readOffset >= 1024) {
+      qInfo() << "Fixme: QMLHandler::readPipeLoop() readOffset too high!!!";
+      readOffset = 0;
+      pollCount = 0;
+      memset(buf, 0, 1025);
+    }
   }
 }
 
@@ -115,6 +274,10 @@ void QMLHandler::onSendingClearSelectionButton() {
 }
 
 // Receiving
+void QMLHandler::onReceivingListFilesButton() {
+  write(outpipefd[1], "list\n", strlen("list\n"));
+}
+
 void QMLHandler::onReceivingGetFileButton(QString fileName) {
   QString command = "get " + fileName + "\n";
   write(outpipefd[1], command.toUtf8().constData(),
@@ -130,41 +293,25 @@ void QMLHandler::onSettingsSwitchServerButton() {
 }
 
 // Ip Popup
-void QMLHandler::onIpPopupEnterIp(QString ip) {
-  pid_t pid = 0;
-
-  pipe(inpipefd);
-  pipe(outpipefd);
-  pid = fork();
-  if (pid == 0) {
-    // Child
-    dup2(outpipefd[0], STDIN_FILENO);
-    dup2(inpipefd[1], STDOUT_FILENO);
-    // dup2(inpipefd[1], STDERR_FILENO);
-
-    // ask kernel to deliver SIGTERM in case the parent dies
-    prctl(PR_SET_PDEATHSIG, SIGTERM);
+void QMLHandler::onIpPopupConnectButton(QString ip) { reopenCLI(ip); }
 
-    // Set the path to the CLI - pass argument h (help) for now
-    // TODO: Change hardcoded path
-    execl("../../cli/build/ccats-cli", "ccats-cli", ip.toUtf8().constData(),
-          "--machine", (char *)NULL);
-
-    exit(1);
-  }
-
-  // TODO: Not hardcoded
-  emit footerSetStatus("Connected to " + ip);
-
-  close(outpipefd[0]);
-  close(inpipefd[1]);
-
-  std::thread(&QMLHandler::readPipeLoop, this).detach();
+// Login
+void QMLHandler::onLoginLoginButton(QString username, QString password) {
+  QString command = "login " + username + " " + password + "\n";
+  write(outpipefd[1], command.toUtf8().constData(),
+        strlen(command.toUtf8().constData()));
 }
 
-// Switch Popup
-void QMLHandler::onSwitchPopupEnterIp(QString ip) {
-  qInfo() << "Switching to " << ip;
+// Signup
+void QMLHandler::onSignupRegisterButton(QString username, QString passwordOne,
+                                        QString passwordTwo) {
+  if (QString::compare(passwordOne, passwordTwo, Qt::CaseSensitive)) {
+    emit signupSetStatus("Passwords don't match");
+    return;
+  }
+  QString command = "signup " + username + " " + passwordOne + "\n";
+  write(outpipefd[1], command.toUtf8().constData(),
+        strlen(command.toUtf8().constData()));
 }
 
 // Footer

+ 23 - 4
gui/src/qmlhandler.h

@@ -13,6 +13,8 @@ class QMLHandler : public QObject {
 private:
   void handleJSON(std::string buffer);
   void readPipeLoop();
+  void reopenCLI(QString ip);
+  void closeCLI();
 
 public:
   explicit QMLHandler(QObject *parent = 0);
@@ -27,6 +29,8 @@ signals:
   void sendingDisableSendButton();
 
   // Receiving
+  void receivingClearFileList();
+  void receivingListFile(QString fileName);
 
   // Messages
   void message(QString msg);
@@ -35,8 +39,19 @@ signals:
   void settingsOpenSwitchServerPopup();
 
   // Ip Popup
+  void ipPopupSetStatus(QString status);
+  void ipPopupClose();
+  void ipPopupOpen();
 
-  // Switch Popup
+  // Login Signup Popup
+  void loginSignupPopupClose();
+  void loginSignupPopupOpen();
+
+  // Login
+  void loginSetStatus(QString status);
+
+  // Signup
+  void signupSetStatus(QString status);
 
   // Footer
   void log(QString logText);
@@ -51,6 +66,7 @@ public slots:
   void onSendingClearSelectionButton();
 
   // Receiving
+  void onReceivingListFilesButton();
   void onReceivingGetFileButton(QString fileName);
 
   // Messages
@@ -60,10 +76,13 @@ public slots:
   void onSettingsSwitchServerButton();
 
   // Ip Popup
-  void onIpPopupEnterIp(QString ip);
+  void onIpPopupConnectButton(QString ip);
+
+  // Login
+  void onLoginLoginButton(QString username, QString password);
 
-  // Switch Popup
-  void onSwitchPopupEnterIp(QString ip);
+  // Signup
+  void onSignupRegisterButton(QString username, QString passwordOne, QString passwordTwo);
 
   // Footer
   void onFooterGetStatusButton();