ioman.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. #include "../include/ioman.h"
  2. #include "../include/global.h"
  3. #include <boost/asio.hpp>
  4. #include <iostream>
  5. #include <boost/algorithm/string.hpp>
  6. #include <string>
  7. #include <vector>
  8. #include <readline/history.h>
  9. #include <readline/readline.h>
  10. #include <poll.h>
  11. using std::string;
  12. using std::vector;
  13. using boost::asio::buffer;
  14. using boost::asio::ip::address;
  15. using boost::asio::ip::tcp;
  16. /* this will be provided by main.cpp for the readline callback */
  17. extern IoMan *gIOMAN;
  18. void ioman_externalDebugPrint(string msg) { gIOMAN->printMessage(msg, gIOMAN->OutMsgType::debug); }
  19. IoMan::IoMan(char *ipcstring, bool enablessl) : IoMan(ipcstring) {
  20. usessl = enablessl;
  21. if(usessl) {
  22. sslctx = new boost::asio::ssl::context(boost::asio::ssl::context::sslv23);
  23. sslctx->set_verify_mode(boost::asio::ssl::verify_peer);
  24. sslctx->set_options(boost::asio::ssl::context::no_sslv2);
  25. sslctx->load_verify_file("rootca.crt");
  26. sslsock = new boost::asio::ssl::stream<tcp::socket&>(*tcpsock, *sslctx);
  27. }
  28. }
  29. IoMan::IoMan(char *ipcstring) : cmdman(fileman, &ioman_externalDebugPrint) {
  30. ipstring = std::string(ipcstring);
  31. port = 1234;
  32. tcpsock = new tcp::socket(ios);
  33. connected = false;
  34. usessl = false;
  35. /* to be put elsewhere */
  36. /* setup json stuff */
  37. Json::CharReaderBuilder rbuilder;
  38. wbuilder.settings_["indentation"] = "";
  39. reader = rbuilder.newCharReader();
  40. runnetwork = false;
  41. runinput = false;
  42. runresponse = false;
  43. versionstatus = off;
  44. loginstatus = off;
  45. }
  46. IoMan::~IoMan() {
  47. initcv.notify_all();
  48. if (connected) {
  49. disconnect();
  50. }
  51. if (runnetwork) {
  52. networkmutex.lock();
  53. runnetwork = false;
  54. networkmutex.unlock();
  55. tnetwork.join();
  56. }
  57. if (runinput) {
  58. inputmutex.lock();
  59. runinput = false;
  60. inputmutex.unlock();
  61. localcv.notify_all();
  62. tinput.join();
  63. }
  64. if (runresponse) {
  65. responsemutex.lock();
  66. runresponse = false;
  67. responsemutex.unlock();
  68. netcv.notify_all();
  69. tresponse.join();
  70. }
  71. if(usessl) delete sslsock;
  72. delete tcpsock;
  73. delete sslctx;
  74. delete reader;
  75. }
  76. void IoMan::printMessage(string nouse, OutMsgType nouse2) {}
  77. vector<string> IoMan::tokenizeInput(string in) {
  78. size_t prev, index, quot;
  79. vector<string> args;
  80. /* tokenize string into command and arguments vector*/
  81. if ((index = in.find(" ")) == string::npos) {
  82. // only command no args
  83. args.push_back(in);
  84. } else {
  85. args.push_back(in.substr(0, index));
  86. index++;
  87. bool end_tokenizing = false;
  88. while (!end_tokenizing) {
  89. // find first char thats not a space
  90. while (in[index] == ' ') {
  91. index++;
  92. // bounds check
  93. if (index == in.size())
  94. end_tokenizing = true;
  95. }
  96. if (end_tokenizing)
  97. break;
  98. in = in.substr(index);
  99. if (in[0] == '\"') {
  100. // quoted string
  101. in = in.substr(1);
  102. index = in.find("\"");
  103. args.push_back(in.substr(0, index));
  104. index++;
  105. /*
  106. tokens.push_back(in.substr(0, ++index));
  107. */
  108. // char after closing quote should be space while within bounds
  109. if (index == in.size())
  110. end_tokenizing = true;
  111. } else {
  112. // non-quoted string
  113. index = in.find(" ");
  114. if (index == string::npos) { // no spaces, last arg
  115. args.push_back(in);
  116. end_tokenizing = true;
  117. } else {
  118. args.push_back(in.substr(0, index));
  119. }
  120. }
  121. }
  122. }
  123. return args;
  124. }
  125. bool IoMan::connect() {
  126. tcp::endpoint *ep = NULL;
  127. address addr;
  128. Json::Value root;
  129. root["command"] = "connect";
  130. root["address"] = ipstring;
  131. root["port"] = port;
  132. addr = address::from_string(ipstring, errcode);
  133. if (errcode) {
  134. root["error"] = errcode.message();
  135. connected = false;
  136. } else {
  137. // establish connection
  138. printMessage(string(__PRETTY_FUNCTION__) + string(" connecting to ") + ipstring, debug);
  139. ep = new tcp::endpoint(addr, port);
  140. tcpsock->connect(*ep, errcode);
  141. if (errcode) {
  142. root["error"] = errcode.message();
  143. connected = false;
  144. } else {
  145. connected = true;
  146. root["error"] = "";
  147. }
  148. delete ep;
  149. }
  150. if(usessl) {
  151. // try to do ssl handshake
  152. printMessage(string(__PRETTY_FUNCTION__) + string(" doing ssl handshake with ") + ipstring, debug);
  153. sslsock->handshake(boost::asio::ssl::stream_base::client, errcode);
  154. if (errcode) {
  155. root["error"] = errcode.message();
  156. connected = false;
  157. } else {
  158. connected = true;
  159. root["error"] = "";
  160. }
  161. }
  162. root["accept"] = connected;
  163. printMessage(Json::writeString(wbuilder, root), normal);
  164. return connected;
  165. }
  166. void IoMan::disconnect() {
  167. printMessage("IoMan::disconnect()", debug);
  168. tcpsock->close();
  169. connected = false;
  170. }
  171. bool IoMan::init() {
  172. CmdMan::CmdRet ret;
  173. string work;
  174. Json::Value root;
  175. std::unique_lock<std::mutex> ulock;
  176. printMessage("IoMan::Init() begin", debug);
  177. if (!connect())
  178. return false;
  179. printMessage("IoMan::Init() versioncheck", debug);
  180. localmutex.lock();
  181. printMessage(string(__PRETTY_FUNCTION__) + string(" get localmutex"), debug);
  182. localinput.push_back("version " + protocolVersion);
  183. localmutex.unlock();
  184. printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"), debug);
  185. localcv.notify_all();
  186. printMessage("IoMan::Init() begin", debug);
  187. runnetwork = true;
  188. runinput = true;
  189. runresponse = true;
  190. tnetwork = std::thread(&IoMan::networkMain, this);
  191. tinput = std::thread(&IoMan::inputMain, this);
  192. tresponse = std::thread(&IoMan::responseMain, this);
  193. ulock = std::unique_lock<std::mutex>(initmutex);
  194. while (versionstatus == off) {
  195. initcv.wait(ulock);
  196. }
  197. if (versionstatus == err) {
  198. runnetwork = false;
  199. runinput = false;
  200. runresponse = false;
  201. return false;
  202. }
  203. initmutex.unlock();
  204. initcv.notify_all();
  205. printWelcomeMessage();
  206. return true;
  207. }
  208. /* loop to fetch data from the network, doing light preprocessing on it to be
  209. * handled by responseMain */
  210. void IoMan::networkMain() {
  211. vector<Json::Value> toput;
  212. char *recvjson;
  213. Json::Value root;
  214. unsigned int jsonsize, readsize;
  215. printMessage("IoMan::networkMain() begin", debug);
  216. networkmutex.lock();
  217. while (runnetwork) {
  218. networkmutex.unlock();
  219. /*
  220. read from network until \n
  221. try to parse json
  222. - output error if not ok
  223. store all ok json in local vector
  224. get networkmutex
  225. put all local jsons into network vector
  226. release networkmutex
  227. */
  228. // read from network
  229. if(usessl) readsize = boost::asio::read_until(*sslsock, recvbuf, '\n', errcode);
  230. else readsize = boost::asio::read_until(*tcpsock, recvbuf, '\n', errcode);
  231. printMessage(string(__PRETTY_FUNCTION__) + string(" asio::read() ok ") + std::to_string(readsize), debug);
  232. // printMessage(string("have ") + std::to_string(toprocess.size()) +
  233. // string(" commands"), debug);
  234. if (errcode && errcode != boost::asio::error::eof) {
  235. printMessage("IoMan::networkMain() couldnt read json data\n" + errcode.message(), error);
  236. continue;
  237. }
  238. if (readsize < 1) {
  239. break;
  240. }
  241. recvjson = (char *)(boost::asio::buffer_cast<const char *>(recvbuf.data()));
  242. recvjson[readsize] = 0;
  243. while (strchr(recvjson, '\n')) {
  244. // parse
  245. jsonsize = strchr(recvjson, '\n') - recvjson + 1;
  246. printMessage(string(__PRETTY_FUNCTION__) + string(" found jsondata ") + string(recvjson), debug);
  247. if (!reader->parse(recvjson, recvjson + jsonsize, &root, &jsonerror)) {
  248. printMessage("IoMan::networkMain() couldnt parse json data: " + jsonerror, error);
  249. recvbuf.consume(jsonsize);
  250. recvjson += jsonsize;
  251. continue;
  252. }
  253. recvbuf.consume(jsonsize);
  254. readsize -= jsonsize;
  255. printMessage(string(__PRETTY_FUNCTION__) + string(" remaining recvbuf ") + string(boost::asio::buffer_cast<const char *>(recvbuf.data())), debug);
  256. recvjson += jsonsize;
  257. // store locally
  258. toput.push_back(root);
  259. }
  260. if (toput.size()) {
  261. // put into global vector
  262. netmutex.lock();
  263. printMessage(string(__PRETTY_FUNCTION__) + string(" get netmutex"), debug);
  264. netinput.insert(netinput.end(), toput.begin(), toput.end());
  265. netmutex.unlock();
  266. printMessage(string(__PRETTY_FUNCTION__) + string(" release netmutex"), debug);
  267. }
  268. netcv.notify_all();
  269. // clean up local stuff
  270. toput = vector<Json::Value>();
  271. recvbuf.consume(readsize);
  272. networkmutex.lock();
  273. }
  274. }
  275. /* loop to handle input from the user and responseMain, sending data via network
  276. * if required */
  277. void IoMan::inputMain() {
  278. vector<string> toprocess;
  279. string command;
  280. vector<string> args;
  281. CmdMan::CmdRet cmdret;
  282. std::unique_lock<std::mutex> ulock;
  283. printMessage("IoMan::inputMain() begin", debug);
  284. inputmutex.lock();
  285. while (runinput) {
  286. inputmutex.unlock();
  287. /*
  288. get inputmutex
  289. read all input vector into local vector
  290. release inputmutex
  291. process inputs
  292. send to server if required
  293. */
  294. // read into local vector
  295. ulock = std::unique_lock<std::mutex>(localmutex);
  296. while (!localinput.size() && runinput) {
  297. localcv.wait(ulock);
  298. }
  299. printMessage(string(__PRETTY_FUNCTION__) + string(" has localmutex"), debug);
  300. toprocess = vector<string>(localinput);
  301. localinput = vector<string>();
  302. localmutex.unlock();
  303. printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"), debug);
  304. localcv.notify_all();
  305. if (!runinput)
  306. return;
  307. // printMessage(string("have ") + std::to_string(toprocess.size()) +
  308. // string(" commands"), debug);
  309. // process
  310. for (string cmd : toprocess) {
  311. args = tokenizeInput(cmd);
  312. command = args.front();
  313. args.erase(args.begin());
  314. cmdret = cmdman.execute(command, args);
  315. // determine wether to send something and do so if required
  316. if (cmdret.type & CmdMan::rettype::print) {
  317. printMessage(Json::writeString(wbuilder, cmdret.msg), normal);
  318. }
  319. if (cmdret.type & CmdMan::rettype::send) {
  320. printMessage("IoMan::inputMain() sending json \"" + Json::writeString(wbuilder, cmdret.msg) + "\"", debug);
  321. if(usessl) boost::asio::write(*sslsock, buffer(Json::writeString(wbuilder, cmdret.msg) + "\n"), errcode);
  322. else boost::asio::write(*tcpsock, buffer(Json::writeString(wbuilder, cmdret.msg) + "\n"), errcode);
  323. if (errcode) {
  324. printMessage("IoMan::inputMain() couldnt send json data\n" + errcode.message() + "\n", error);
  325. continue;
  326. }
  327. }
  328. if (cmdret.type & CmdMan::rettype::error) {
  329. printMessage(Json::writeString(wbuilder, cmdret.msg), error);
  330. }
  331. if (cmdret.type & CmdMan::rettype::close) {
  332. /* TODO i dunno */
  333. mainmutex.lock();
  334. runmain = false;
  335. mainmutex.unlock();
  336. }
  337. }
  338. // clean up local stuff
  339. toprocess = vector<string>();
  340. inputmutex.lock();
  341. }
  342. }
  343. /* loop to handle responses that have been fetched by netMain and possibly add
  344. * new commands to be handled by inputMain */
  345. void IoMan::responseMain() {
  346. vector<Json::Value> toprocess;
  347. vector<string> toput;
  348. CmdMan::CmdRet cmdret;
  349. std::unique_lock<std::mutex> ulock;
  350. printMessage("IoMan::responseMain() begin", debug);
  351. responsemutex.lock();
  352. while (runresponse) {
  353. responsemutex.unlock();
  354. /*
  355. get networkmutex
  356. read all network vector into local vector
  357. release networkmutex
  358. process all jsons
  359. process putdata
  360. process getdata
  361. process listdata
  362. get inputmutex
  363. place new commands into input vector
  364. release inputmutex
  365. */
  366. // read into local vector
  367. ulock = std::unique_lock<std::mutex>(netmutex);
  368. while (!netinput.size() && runresponse) {
  369. netcv.wait(ulock);
  370. }
  371. printMessage(string(__PRETTY_FUNCTION__) + string(" get netmutex"), debug);
  372. toprocess = vector<Json::Value>(netinput);
  373. netinput = vector<Json::Value>();
  374. netmutex.unlock();
  375. printMessage(string(__PRETTY_FUNCTION__) + string(" release netmutex"), debug);
  376. netcv.notify_all();
  377. if (!runresponse)
  378. return;
  379. // process jsons
  380. for (Json::Value root : toprocess) {
  381. cmdret = cmdman.handle(root);
  382. if (cmdret.type & CmdMan::rettype::close) {
  383. /* TODO i dunno */
  384. mainmutex.lock();
  385. runmain = false;
  386. mainmutex.unlock();
  387. }
  388. if (cmdret.type & CmdMan::rettype::error) {
  389. initmutex.lock();
  390. if (versionstatus == off)
  391. versionstatus = err;
  392. else if (loginstatus == off)
  393. loginstatus = err;
  394. initmutex.unlock();
  395. initcv.notify_all();
  396. printMessage(Json::writeString(wbuilder, cmdret.msg), error);
  397. }
  398. if (cmdret.type & CmdMan::rettype::seton) {
  399. initmutex.lock();
  400. if (versionstatus == off)
  401. versionstatus = on;
  402. else
  403. loginstatus = on;
  404. initmutex.unlock();
  405. initcv.notify_all();
  406. }
  407. if (cmdret.type & CmdMan::rettype::print) {
  408. printMessage(Json::writeString(wbuilder, cmdret.msg), normal);
  409. }
  410. if (cmdret.type & CmdMan::rettype::send) {
  411. if (cmdret.nextcommand.size()) {
  412. toput.push_back(cmdret.nextcommand);
  413. }
  414. }
  415. }
  416. if (toput.size()) {
  417. // put new commands into global vector
  418. localmutex.lock();
  419. printMessage(string(__PRETTY_FUNCTION__) + string(" get localmutex"), debug);
  420. localinput.insert(localinput.end(), toput.begin(), toput.end());
  421. localmutex.unlock();
  422. printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"), debug);
  423. }
  424. localcv.notify_all();
  425. // clean up local stuff
  426. toprocess = vector<Json::Value>();
  427. toput = vector<string>();
  428. responsemutex.lock();
  429. }
  430. }
  431. /* this is the handler that readlines alternative interface will use to process
  432. * user input */
  433. void ioman_readlineHandler(char *line) {
  434. vector<string> tokens;
  435. if (!line) {
  436. printf("\nNULLBURGER\n");
  437. gIOMAN->mainmutex.lock();
  438. gIOMAN->runmain = false;
  439. gIOMAN->mainmutex.unlock();
  440. } else {
  441. // split input line into tokens
  442. boost::algorithm::split(tokens, std::string(line), boost::algorithm::is_any_of(" "), boost::algorithm::token_compress_on);
  443. if (strlen(line) && tokens.size()) {
  444. add_history(line);
  445. gIOMAN->localmutex.lock();
  446. gIOMAN->printMessage(string(__PRETTY_FUNCTION__) + string(" get localmutex"), gIOMAN->debug);
  447. gIOMAN->localinput.push_back(line);
  448. gIOMAN->localmutex.unlock();
  449. gIOMAN->printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"), gIOMAN->debug);
  450. gIOMAN->localcv.notify_all();
  451. }
  452. free(line);
  453. }
  454. }
  455. /* main user input loop */
  456. void IoMan::run() {
  457. printMessage("IoMan::run() begin", debug);
  458. struct pollfd inPipeStatus;
  459. inPipeStatus.fd = STDIN_FILENO;
  460. inPipeStatus.events = POLLIN;
  461. runmain = true;
  462. // Install readline handler
  463. rl_callback_handler_install(getCmdPrompt().c_str(), (rl_vcpfunc_t *)&ioman_readlineHandler);
  464. mainmutex.lock();
  465. while (runmain) {
  466. mainmutex.unlock();
  467. poll(&inPipeStatus, 1, 100);
  468. if (inPipeStatus.revents & POLLIN) {
  469. rl_callback_read_char();
  470. }
  471. if (!connected)
  472. break;
  473. if (loginstatus == err)
  474. break;
  475. mainmutex.lock();
  476. }
  477. mainmutex.unlock();
  478. // Remove the handler
  479. rl_callback_handler_remove();
  480. }