client.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. #include <arpa/inet.h>
  2. #include <math.h>
  3. #include <netdb.h>
  4. #include <string.h>
  5. #include <sys/socket.h>
  6. #include <sys/types.h>
  7. #include <unistd.h>
  8. #include <QByteArray>
  9. #include <QDebug>
  10. #include <algorithm>
  11. #include <cstring>
  12. #include <functional>
  13. #include <future>
  14. #include <include/client/client.hpp>
  15. #include <iostream>
  16. #include <vector>
  17. #define FLATBUFFERS_DEBUG_VERIFICATION_FAILURE
  18. #include <headers/kmessage_codec.hpp>
  19. #include <headers/instatask_generated.h>
  20. #include <headers/generictask_generated.h>
  21. using namespace KData;
  22. using namespace IGData;
  23. using namespace GenericData;
  24. static const int MAX_PACKET_SIZE = 4096;
  25. static const int HEADER_SIZE = 4;
  26. flatbuffers::FlatBufferBuilder builder(1024);
  27. namespace Task {
  28. /**
  29. * @brief getTaskFileInfo
  30. * @param [in] {std::vector<SentFile>} files The files to produce an information string from
  31. * @return std::string A string with the following format denoting each file:
  32. * `1580057341filename|image::`
  33. */
  34. std::string getTaskFileInfo(std::vector<SentFile> files) {
  35. std::string info{};
  36. for (const auto& f : files) {
  37. info += std::to_string(f.timestamp);
  38. info += f.name.toUtf8().constData();
  39. info += "|";
  40. if (f.type == Scheduler::FileType::VIDEO) {
  41. info += "video";
  42. } else {
  43. info += "image";
  44. }
  45. info += ":";
  46. }
  47. qDebug() << "File Info: " << info.c_str();
  48. return info;
  49. }
  50. flatbuffers::Offset<GenericTask> createGenericTask(
  51. Scheduler::Task* task, uint32_t app_mask, std::vector<SentFile> sent_files) {
  52. return CreateGenericTask(
  53. builder,
  54. Scheduler::GENERIC_TASK_ID, // ID
  55. builder.CreateString(getTaskFileInfo(sent_files)),
  56. builder.CreateString(std::string{std::get<QString>(
  57. task->getTaskArgumentValue("datetime")).toUtf8().constData()
  58. }),
  59. builder.CreateString(std::string{std::get<QString>(
  60. task->getTaskArgumentValue("description")).toUtf8().constData()
  61. }),
  62. std::get<bool>(
  63. task->getTaskArgumentValue("is_video")
  64. ),
  65. app_mask,
  66. builder.CreateString(std::string{std::get<QString>(
  67. task->getTaskArgumentValue("header")).toUtf8().constData()
  68. }),
  69. builder.CreateString(std::string{std::get<QString>(
  70. task->getTaskArgumentValue("user")).toUtf8().constData()
  71. }),
  72. std::get<int>(
  73. task->getTaskArgumentValue("recurring")
  74. ),
  75. std::get<bool>(
  76. task->getTaskArgumentValue("notify")
  77. )
  78. );
  79. }
  80. flatbuffers::Offset<IGTask> createIGTask(
  81. Scheduler::Task* task, uint32_t app_mask, std::vector<SentFile> sent_files) {
  82. return CreateIGTask(
  83. builder,
  84. Scheduler::INSTAGRAM_TASK_ID, // ID
  85. builder.CreateString(getTaskFileInfo(sent_files)),
  86. builder.CreateString(std::string{std::get<QString>(
  87. task->getTaskArgumentValue("datetime")).toUtf8().constData()}),
  88. builder.CreateString(std::string{std::get<QString>(
  89. task->getTaskArgumentValue("description")).toUtf8().constData()}),
  90. builder.CreateString(std::string{std::get<QString>(
  91. task->getTaskArgumentValue("hashtags_string")).toUtf8().constData()}),
  92. builder.CreateString(std::string{std::get<QString>(
  93. task->getTaskArgumentValue("requested_by_string")).toUtf8().constData()}),
  94. builder.CreateString(std::string{std::get<QString>(
  95. task->getTaskArgumentValue("requested_by_phrase")).toUtf8().constData()}),
  96. builder.CreateString(std::string{std::get<QString>(
  97. task->getTaskArgumentValue("promote_share")).toUtf8().constData()}),
  98. builder.CreateString(std::string{std::get<QString>(
  99. task->getTaskArgumentValue("link_in_bio")).toUtf8().constData()}),
  100. std::get<bool>(
  101. task->getTaskArgumentValue("is_video")),
  102. app_mask,
  103. builder.CreateString(std::string{std::get<QString>(
  104. task->getTaskArgumentValue("header")).toUtf8().constData()}),
  105. builder.CreateString(std::string{std::get<QString>(
  106. task->getTaskArgumentValue("user")).toUtf8().constData()}));
  107. }
  108. }
  109. /**
  110. * @param [in] {std::function<void()>} cb A non-returning function to be called without parameter
  111. * @returns {MessageHandler} A message loop handler
  112. */
  113. Client::MessageHandler Client::createMessageHandler(std::function<void()> cb) {
  114. return MessageHandler(cb);
  115. }
  116. /**
  117. * @brief Client::Client
  118. * @constructor
  119. * @param [in] {QWidget*} parent
  120. * @param [in] {int} count
  121. * @param [in] {char**} arguments
  122. */
  123. Client::Client(QWidget *parent,
  124. int count,
  125. char** arguments)
  126. : QDialog(parent),
  127. argc(count),
  128. argv(arguments),
  129. m_client_socket_fd(-1),
  130. executing(false),
  131. m_outbound_task(nullptr),
  132. m_commands({}){
  133. // Register metadata type for passing data over slots/signals
  134. qRegisterMetaType<QVector<QString>>("QVector<QString>");
  135. }
  136. /**
  137. * @brief Client::~Client
  138. * @destructor
  139. */
  140. Client::~Client() {
  141. closeConnection();
  142. }
  143. /**
  144. * @brief Client::handleMessages
  145. */
  146. void Client::handleMessages() {
  147. uint8_t receive_buffer[MAX_PACKET_SIZE];
  148. for (;;) {
  149. memset(receive_buffer, 0, MAX_PACKET_SIZE);
  150. ssize_t bytes_received = 0;
  151. bytes_received = recv(m_client_socket_fd, receive_buffer, MAX_PACKET_SIZE, 0);
  152. if (bytes_received < 1) { // Finish message loop
  153. break;
  154. }
  155. size_t end_idx = findNullIndex(receive_buffer);
  156. std::string data_string{receive_buffer, receive_buffer + end_idx};
  157. if (isPong(data_string.c_str())) {
  158. qDebug() << "Server returned pong";
  159. continue;
  160. }
  161. StringVec s_v{};
  162. if (isNewSession(data_string.c_str())) { // Session Start
  163. m_commands = getArgMap(data_string.c_str());
  164. for (const auto& [k, v] : m_commands) { // Receive available commands
  165. s_v.push_back(v.data());
  166. }
  167. emit Client::messageReceived(COMMANDS_UPDATE_TYPE, "New Session", s_v); // Update UI
  168. } else if (serverWaitingForFile(data_string.c_str())) { // Server expects a file
  169. processFileQueue();
  170. } else if (isEvent(data_string.c_str())) { // Receiving event
  171. handleEvent(data_string);
  172. }
  173. std::string formatted_json = getJsonString(data_string);
  174. emit Client::messageReceived(
  175. MESSAGE_UPDATE_TYPE,
  176. QString::fromUtf8(formatted_json.data(), formatted_json.size()), {}
  177. );
  178. }
  179. memset(receive_buffer, 0, 2048);
  180. ::close(m_client_socket_fd);
  181. }
  182. void Client::handleEvent(std::string data) {
  183. QString event = getEvent(data.c_str());
  184. QVector<QString> args = getArgs(data.c_str());
  185. emit Client::messageReceived(EVENT_UPDATE_TYPE, event, args); // Update UI (event)
  186. if (isUploadCompleteEvent(event.toUtf8().constData())) { // Upload complete
  187. if (!args.isEmpty()) {
  188. sent_files.at(sent_files.size() - 1).timestamp =
  189. std::stoi(args.at(0).toUtf8().constData()); // mark file with server-generated timestamp
  190. if (outgoing_files.isEmpty()) { // Task files are all sent
  191. sendTaskEncoded(m_outbound_task); // Send remaining task data to complete scheduling
  192. file_was_sent = false;
  193. } else { // Begin file upload operation. Task will be sent after all outgoing files are sent.
  194. sendEncoded(
  195. createOperation("FileUpload", {"Subsequent file"})
  196. );
  197. }
  198. }
  199. }
  200. }
  201. /**
  202. * @brief Client::processFileQueue
  203. */
  204. void Client::processFileQueue() {
  205. Scheduler::KFileData outgoing_file = outgoing_files.dequeue();
  206. sendFileEncoded(outgoing_file.bytes);
  207. sent_files.push_back(SentFile{
  208. .name = outgoing_file.name,
  209. .type = outgoing_file.type
  210. });
  211. }
  212. /**
  213. * @brief Client::start
  214. */
  215. void Client::start() {
  216. if (m_client_socket_fd == -1) {
  217. m_client_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
  218. if (m_client_socket_fd != -1) {
  219. sockaddr_in server_socket;
  220. char* end;
  221. server_socket.sin_family = AF_INET;
  222. auto port_value = strtol(argv[2], &end, 10);
  223. if (port_value < 0 || end == argv[2]) {
  224. return;
  225. }
  226. int socket_option = 1;
  227. // Free up the port to begin listening again
  228. setsockopt(
  229. m_client_socket_fd, SOL_SOCKET, SO_REUSEADDR, &socket_option,
  230. sizeof(socket_option)
  231. );
  232. server_socket.sin_port = htons(port_value);
  233. inet_pton(AF_INET, argv[1], &server_socket.sin_addr.s_addr);
  234. if (::connect(m_client_socket_fd, reinterpret_cast<sockaddr*>(&server_socket),
  235. sizeof(server_socket)) != -1) {
  236. std::string start_operation_string = createOperation("start", {});
  237. // Send operation as an encoded message
  238. sendEncoded(start_operation_string);
  239. // Delegate message handling to its own thread
  240. std::function<void()> message_send_fn = [this]() {
  241. this->handleMessages();
  242. };
  243. MessageHandler message_handler = createMessageHandler(message_send_fn);
  244. // Handle received messages on separate thread
  245. std::thread (message_handler).detach();
  246. } else {
  247. qDebug() << errno;
  248. ::close(m_client_socket_fd);
  249. }
  250. } else {
  251. qDebug() << "Failed to create new connection";
  252. }
  253. } else {
  254. qDebug() << "Connection already in progress";
  255. }
  256. }
  257. /**
  258. * @brief Client::sendMessage
  259. * @param [in] {const QString&} The message to send
  260. */
  261. void Client::sendMessage(const QString& s) {
  262. if (m_client_socket_fd != -1) {
  263. std::string json_string {"{\"type\":\"custom\", \"message\": \""};
  264. json_string += s.toUtf8().data();
  265. json_string += "\", \"args\":\"placeholder\"}";
  266. // Send custom message as an encoded message
  267. sendEncoded(json_string);
  268. } else {
  269. qDebug() << "You must first open a connection";
  270. }
  271. }
  272. /**
  273. * @brief Client::sendEncoded
  274. * @param [in] {std::string message} The message to send
  275. */
  276. void Client::sendEncoded(std::string message) {
  277. std::vector<uint8_t> fb_byte_vector{message.begin(), message.end()};
  278. auto byte_vector = builder.CreateVector(fb_byte_vector);
  279. auto k_message = CreateMessage(builder, 69, byte_vector);
  280. builder.Finish(k_message);
  281. uint8_t* encoded_message_buffer = builder.GetBufferPointer();
  282. uint32_t size = builder.GetSize();
  283. uint8_t send_buffer[MAX_PACKET_SIZE];
  284. memset(send_buffer, 0, MAX_PACKET_SIZE);
  285. send_buffer[0] = (size & 0xFF) >> 24;
  286. send_buffer[1] = (size & 0xFF) >> 16;
  287. send_buffer[2] = (size & 0xFF) >> 8;
  288. send_buffer[3] = (size & 0xFF);
  289. send_buffer[4] = (TaskCode::GENMSGBYTE & 0xFF);
  290. std::memcpy(send_buffer + 5, encoded_message_buffer, size);
  291. qDebug() << "Sending encoded message";
  292. std::string message_to_send{};
  293. for (unsigned int i = 0; i < (size + 5); i++) {
  294. message_to_send += (char)*(send_buffer + i);
  295. }
  296. qDebug() << "Encoded message size: " << (size + 5);
  297. // Send start operation
  298. ::send(m_client_socket_fd, send_buffer, size + 5, 0);
  299. builder.Clear();
  300. }
  301. /**
  302. * @brief Client::sendTaskEncoded
  303. * @param [in] {Scheduler::Task*} task The task arguments
  304. */
  305. void Client::sendTaskEncoded(Scheduler::Task* task) {
  306. if (task->getType() == Scheduler::TaskType::INSTAGRAM) {
  307. flatbuffers::Offset<IGTask> ig_task =
  308. Task::createIGTask(task, getSelectedApp(), sent_files);
  309. builder.Finish(ig_task);
  310. } else {
  311. flatbuffers::Offset<GenericTask> generic_task =
  312. Task::createGenericTask(task, getSelectedApp(), sent_files);
  313. builder.Finish(generic_task);
  314. }
  315. uint8_t* encoded_message_buffer = builder.GetBufferPointer();
  316. uint32_t size = builder.GetSize();
  317. uint8_t send_buffer[MAX_PACKET_SIZE];
  318. memset(send_buffer, 0, MAX_PACKET_SIZE);
  319. send_buffer[0] = (size >> 24) & 0xFF;
  320. send_buffer[1] = (size >> 16) & 0xFF;
  321. send_buffer[2] = (size >> 8) & 0xFF;
  322. send_buffer[3] = size & 0xFF;
  323. send_buffer[4] = (task->getTaskCode() & 0xFF);
  324. std::memcpy(send_buffer + 5, encoded_message_buffer, size);
  325. qDebug() << "Ready to send task";
  326. // Send start operation
  327. ::send(m_client_socket_fd, send_buffer, size + 5, 0);
  328. // Cleanup and process queue
  329. builder.Clear();
  330. sent_files.clear();
  331. m_outbound_task = nullptr;
  332. if (!m_task_queue.isEmpty()) {
  333. m_outbound_task = m_task_queue.dequeue();
  334. // TODO work from here
  335. if (m_outbound_task->hasFiles() && !outgoing_files.empty()) {
  336. qDebug() << "There are still outgoing files left over from last "
  337. "task which were never sent. They are being deleted";
  338. outgoing_files.clear();
  339. }
  340. sendFiles(m_outbound_task);
  341. }
  342. }
  343. /**
  344. * @brief Client::sendPackets
  345. * @param [in] {uint8_t*} data A pointer to a buffer of bytes
  346. * @param [in] {int} size The size of the buffer to be packetized and sent
  347. */
  348. void Client::sendPackets(uint8_t* data, int size) {
  349. uint32_t total_size = static_cast<uint32_t>(size + HEADER_SIZE);
  350. uint32_t total_packets = static_cast<uint32_t>(ceil(
  351. static_cast<double>(
  352. static_cast<double>(total_size) / static_cast<double>(MAX_PACKET_SIZE)) // total size / packet
  353. )
  354. );
  355. uint32_t idx = 0;
  356. for (; idx < total_packets; idx++) {
  357. bool is_first_packet = (idx == 0);
  358. bool is_last_packet = (idx == (total_packets - 1));
  359. if (is_first_packet) {
  360. uint32_t first_packet_size =
  361. std::min(size + HEADER_SIZE, MAX_PACKET_SIZE);
  362. uint8_t packet[first_packet_size];
  363. packet[0] = (total_size >> 24) & 0xFF;
  364. packet[1] = (total_size >> 16) & 0xFF;
  365. packet[2] = (total_size >> 8) & 0xFF;
  366. packet[3] = (total_size) & 0xFF;
  367. std::memcpy(packet + HEADER_SIZE, data, first_packet_size - HEADER_SIZE);
  368. /**
  369. * SEND PACKET !!!
  370. */
  371. ::send(m_client_socket_fd, packet, first_packet_size, 0);
  372. if (is_last_packet) {
  373. break;
  374. }
  375. continue;
  376. }
  377. int offset = (idx * MAX_PACKET_SIZE) - HEADER_SIZE;
  378. uint32_t packet_size = std::min(size - offset, MAX_PACKET_SIZE);
  379. uint8_t packet[packet_size];
  380. std::memcpy(packet, data + offset, packet_size);
  381. // Send packet
  382. ::send(m_client_socket_fd, packet, packet_size, 0);
  383. if (is_last_packet) {
  384. qDebug() << "Last packet of file sent";
  385. file_was_sent = true;
  386. }
  387. }
  388. }
  389. void Client::ping() {
  390. if (m_client_socket_fd != -1) { // if we have active connection
  391. if (outgoing_files.size() == 0 || file_was_sent) {
  392. /* 1st condition: we aren't sending file packets
  393. 2nd condition: we were sending packets, but the file is complete and we want
  394. to ping in case the server is unresponsive */
  395. uint8_t send_buffer[5];
  396. memset(send_buffer, 0, 5);
  397. send_buffer[4] = (TaskCode::PINGBYTE & 0xFF);
  398. qDebug() << "Pinging server";
  399. ::send(m_client_socket_fd, send_buffer, 5, 0);
  400. }
  401. }
  402. }
  403. /**
  404. * @brief Client::sendFileEncoded
  405. * @param [in] {QByteArray} bytes An array of bytes to send
  406. */
  407. void Client::sendFileEncoded(QByteArray bytes) {
  408. sendPackets(reinterpret_cast<uint8_t*>(bytes.data()), bytes.size());
  409. }
  410. /**
  411. * @brief Client::closeConnection
  412. */
  413. void Client::closeConnection() {
  414. if (m_client_socket_fd != -1) {
  415. std::string stop_operation_string = createOperation("stop", {});
  416. // Send operation as an encoded message
  417. sendEncoded(stop_operation_string);
  418. // Clean up socket file descriptor
  419. ::shutdown(m_client_socket_fd, SHUT_RDWR);
  420. ::close(m_client_socket_fd);
  421. m_client_socket_fd = -1;
  422. return;
  423. }
  424. qDebug() << "There is no active connection to close";
  425. }
  426. /**
  427. * @brief Client::setSelectedApp
  428. * @param [in] TYPE SHOULD CHANGE app_names
  429. */
  430. void Client::setSelectedApp(std::vector<QString> app_names) {
  431. selected_commands.clear();
  432. for (const auto& name : app_names) {
  433. qDebug() << "Matching mask to " << name;
  434. for (const auto& command : m_commands) {
  435. if (command.second.c_str() == name.toUtf8()) {
  436. selected_commands.push_back(command.first);
  437. }
  438. }
  439. }
  440. }
  441. /**
  442. * @brief Client::getSelectedApp
  443. * @returns {int} The mask representing the selected application
  444. */
  445. int Client::getSelectedApp() {
  446. if (selected_commands.size() == 1) {
  447. return selected_commands.at(0);
  448. } else {
  449. QMessageBox::warning(this, tr("App Selection Error"), tr("Unable to retrieve app selection"));
  450. }
  451. return -1;
  452. }
  453. /**
  454. * @brief Client::getAppName
  455. * @param [in] {int} mask The mask representing the application
  456. * @returns {QString} The application name
  457. */
  458. QString Client::getAppName(int mask) {
  459. auto app = m_commands.find(mask);
  460. if (app != m_commands.end()) {
  461. return QString{app->second.c_str()};
  462. }
  463. return QString{""};
  464. }
  465. /**
  466. * @brief Client::execute
  467. */
  468. void Client::execute() {
  469. if (!selected_commands.empty()) {
  470. executing = true;
  471. for (const auto& command : selected_commands) {
  472. auto app_name = getAppName(command);
  473. auto message = app_name + " pending";
  474. auto request_id =
  475. QUuid::createUuid().toString(QUuid::StringFormat::WithoutBraces);
  476. emit Client::messageReceived(
  477. PROCESS_REQUEST_TYPE, message, { QString{command}, app_name, request_id }
  478. );
  479. std::string execute_operation =
  480. createOperation(
  481. "Execute",
  482. {std::to_string(command), std::string(request_id.toUtf8().constData())}
  483. );
  484. sendEncoded(execute_operation);
  485. }
  486. }
  487. }
  488. /**
  489. * @brief Client::scheduleTask
  490. * @param [in] {std::vector<std::string>} task_args The task arguments
  491. * @param [in] {bool} file_pending A boolean indicating whether there are
  492. * files being sent for this task
  493. */
  494. void Client::scheduleTask(Scheduler::Task* task) {
  495. if (m_outbound_task == nullptr) {
  496. m_outbound_task = std::move(task);
  497. if (m_outbound_task->hasFiles()) {
  498. sendFiles(m_outbound_task);
  499. } else {
  500. qDebug() << "Requesting a task to be scheduled";
  501. sendTaskEncoded(m_outbound_task);
  502. }
  503. } else {
  504. m_task_queue.enqueue(task);
  505. }
  506. }
  507. /**
  508. * @brief Client::sendFiles
  509. * @param [in] {QVector<const QByteArray} files The files to be sent
  510. */
  511. void Client::sendFiles(Scheduler::Task* task) {
  512. if (outgoing_files.isEmpty()) {
  513. file_was_sent = false;
  514. for (const auto& file : task->getFiles()) {
  515. outgoing_files.enqueue(std::move(file));
  516. }
  517. std::string send_file_operation = createOperation("FileUpload", {});
  518. sendEncoded(send_file_operation);
  519. } else {
  520. m_task_queue.enqueue(task);
  521. qDebug() << "Still attempting to send a different file";
  522. }
  523. }