ioman.cpp 15 KB

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