ioman.cpp 17 KB

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