ioman.cpp 15 KB

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