ioman.cpp 14 KB

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