1//! @file view.cpp
2//! @author ryftchen
3//! @brief The definitions (view) in the application module.
4//! @version 0.1.0
5//! @copyright Copyright (c) 2022-2026 ryftchen. All rights reserved.
6
7#include "view.hpp"
8#include "data.hpp"
9#include "log.hpp"
10
11#ifndef _PRECOMPILED_HEADER
12#if __has_include(<gsl/gsl_version.h>)
13#include <gsl/gsl_version.h>
14#endif
15#include <openssl/evp.h>
16#include <readline/readline.h>
17#include <sys/shm.h>
18#include <sys/stat.h>
19#if __has_include(<gmp.h>)
20#include <gmp.h>
21#endif
22#include <lz4.h>
23#include <mpfr.h>
24#if __has_include(<ncurses.h>)
25#include <ncurses.h>
26#endif
27#include <cassert>
28#include <iterator>
29#include <numeric>
30#else
31#include "application/pch/precompiled_header.hpp"
32#endif
33
34#include "utility/include/benchmark.hpp"
35#include "utility/include/time.hpp"
36
37namespace application
38{
39namespace view
40{
41//! @brief Alias for the access controller.
42using AccessController = configure::Controller<View>;
43
44//! @brief Anonymous namespace.
45inline namespace
46{
47//! @brief The internal symbol for exiting.
48constexpr std::string_view exitSymbol = MACRO_STRINGIFY(stop);
49} // namespace
50
51//! @brief Alias for the type-length-value scheme.
52namespace tlv = data::tlv;
53
54View::View() : FSM(State::initial)
55{
56 if (!configure::detail::activateHelper()) [[unlikely]]
57 {
58 throw std::logic_error{"The " + name + " is disabled."};
59 }
60}
61
62std::shared_ptr<View> View::getInstance()
63{
64 static const std::shared_ptr<View> viewer(::new View{});
65 return viewer;
66}
67
68// NOLINTBEGIN(cppcoreguidelines-avoid-goto)
69void View::service()
70{
71retry:
72 try
73 {
74 processEvent(event: Relaunch{});
75
76 assert(currentState() == State::initial);
77 processEvent(event: CreateServer{});
78
79 assert(currentState() == State::active);
80 awaitNotification2Proceed();
81 processEvent(event: GoViewing{});
82
83 assert(currentState() == State::established);
84 notificationLoop();
85 if (inResetting.load())
86 {
87 goto retry;
88 }
89 processEvent(event: DestroyServer{});
90
91 assert(currentState() == State::active);
92 processEvent(event: NoViewing{});
93
94 assert(currentState() == State::inactive);
95 }
96 catch (const std::exception& err)
97 {
98 LOG_ERR << "Suspend the " << name << " during " << static_cast<State>(currentState()) << " state. "
99 << err.what();
100
101 processEvent(event: Standby{});
102 if (awaitNotification2Retry())
103 {
104 goto retry;
105 }
106 }
107}
108// NOLINTEND(cppcoreguidelines-avoid-goto)
109
110//! @brief Build the TLV packet of the response message to get library information.
111//! @param buffer - TLV packet buffer
112//! @return buffer length
113template <>
114std::size_t View::buildCustomTLVPacket<View::OptDepend>(const Args& /*args*/, Buffer& buffer)
115{
116 tlv::TLVValue val{};
117 std::string extLibraries{};
118#if defined(__GLIBC__) && defined(__GLIBC_MINOR__)
119 extLibraries += "GNU C Library " MACRO_STRINGIFY(__GLIBC__) "." MACRO_STRINGIFY(__GLIBC_MINOR__) "\n";
120#else
121#error Could not find the GNU C Library version.
122#endif
123#if defined(_GLIBCXX_RELEASE) && defined(__GLIBCXX__)
124 extLibraries +=
125 "GNU C++ Standard Library " MACRO_STRINGIFY(_GLIBCXX_RELEASE) " (" MACRO_STRINGIFY(__GLIBCXX__) ")\n";
126#else
127#error Could not find the GNU C++ Standard Library version.
128#endif
129#if defined(__GNU_MP_VERSION) && defined(__GNU_MP_VERSION_MINOR) && defined(__GNU_MP_VERSION_PATCHLEVEL)
130 extLibraries += "GNU MP Library " MACRO_STRINGIFY(__GNU_MP_VERSION) "." MACRO_STRINGIFY(
131 __GNU_MP_VERSION_MINOR) "." MACRO_STRINGIFY(__GNU_MP_VERSION_PATCHLEVEL) "\n";
132#else
133#pragma message("Could not find the GNU MP Library version.")
134#endif
135#if defined(MPFR_VERSION_STRING)
136 extLibraries += "GNU MPFR Library " MPFR_VERSION_STRING "\n";
137#else
138#error Could not find the GNU MPFR Library version.
139#endif
140#if defined(GSL_VERSION)
141 extLibraries += "GNU Scientific Library " GSL_VERSION " (CBLAS)\n";
142#else
143#error Could not find the GNU Scientific Library (CBLAS) version.
144#endif
145#if defined(RL_VERSION_MAJOR) && defined(RL_VERSION_MINOR)
146 extLibraries +=
147 "GNU Readline Library " MACRO_STRINGIFY(RL_VERSION_MAJOR) "." MACRO_STRINGIFY(RL_VERSION_MINOR) "\n";
148#else
149#error Could not find the GNU Readline Library version.
150#endif
151#if defined(LZ4_VERSION_STRING)
152 extLibraries += "LZ4 Library " LZ4_VERSION_STRING "\n";
153#else
154#error Could not find the LZ4 Library version.
155#endif
156#if defined(NCURSES_VERSION)
157 extLibraries += "Ncurses Library " NCURSES_VERSION "\n";
158#else
159#pragma message("Could not find the Ncurses Library version.")
160#endif
161#if defined(OPENSSL_VERSION_STR)
162 extLibraries += "OpenSSL Library " OPENSSL_VERSION_STR "";
163#else
164#error Could not find the OpenSSL Library version.
165#endif
166 std::strncpy(dest: val.libInfo, src: extLibraries.c_str(), n: sizeof(val.libInfo) - 1);
167 val.libInfo[sizeof(val.libInfo) - 1] = '\0';
168 char* const buf = buffer.data();
169 std::size_t len = buffer.size();
170 if (const auto ec = tlv::encodeTLV(buf, len, val); ec)
171 {
172 throw std::runtime_error{
173 "Failed to build packet for the " + std::string{OptDepend::name} + " option (" + ec.message() + ")."};
174 }
175 data::encryptMessage(buffer: buf, length: len);
176 return len;
177}
178
179//! @brief Build the TLV packet of the response message to get bash outputs.
180//! @param args - container of arguments
181//! @param buffer - TLV packet buffer
182//! @return buffer length
183template <>
184std::size_t View::buildCustomTLVPacket<View::OptExecute>(const Args& args, Buffer& buffer)
185{
186 const auto cmd = std::accumulate(
187 first: args.cbegin(),
188 last: args.cend(),
189 init: std::string{},
190 binary_op: [](const auto& acc, const auto& arg) { return acc.empty() ? arg : (acc + ' ' + arg); });
191 if (((cmd.find_first_not_of(c: '\'') == 0) || (cmd.find_last_not_of(c: '\'') == (cmd.length() - 1)))
192 && ((cmd.find_first_not_of(c: '"') == 0) || (cmd.find_last_not_of(c: '"') == (cmd.length() - 1))))
193 {
194 throw std::runtime_error{
195 "Please enter the \"" + std::string{OptExecute::name} + "\" and append with 'CMD' (include quotes)."};
196 }
197
198 char* const buf = buffer.data();
199 std::size_t len = buffer.size();
200 const int shmId = fillSharedMemory(contents: utility::io::executeCommand(command: "/bin/bash -c " + cmd));
201 if (const auto ec = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.bashShmId = shmId}); ec)
202 {
203 throw std::runtime_error{
204 "Failed to build packet for the " + std::string{OptExecute::name} + " option (" + ec.message() + ")."};
205 }
206 data::encryptMessage(buffer: buf, length: len);
207 return len;
208}
209
210//! @brief Build the TLV packet of the response message to get log contents.
211//! @param buffer - TLV packet buffer
212//! @return buffer length
213template <>
214std::size_t View::buildCustomTLVPacket<View::OptJournal>(const Args& /*args*/, Buffer& buffer)
215{
216 char* const buf = buffer.data();
217 std::size_t len = buffer.size();
218 const int shmId = fillSharedMemory(contents: logContextPreview());
219 if (const auto ec = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.logShmId = shmId}); ec)
220 {
221 throw std::runtime_error{
222 "Failed to build packet for the " + std::string{OptJournal::name} + " option (" + ec.message() + ")."};
223 }
224 data::encryptMessage(buffer: buf, length: len);
225 return len;
226}
227
228//! @brief Build the TLV packet of the response message to get status reports.
229//! @param args - container of arguments
230//! @param buffer - TLV packet buffer
231//! @return buffer length
232template <>
233std::size_t View::buildCustomTLVPacket<View::OptMonitor>(const Args& args, Buffer& buffer)
234{
235 if (!args.empty())
236 {
237 if (const auto& input = args.front(); (input.length() != 1) || (std::isdigit(input.front()) == 0))
238 {
239 throw std::runtime_error{
240 "Please enter the \"" + std::string{OptMonitor::name} + "\" and append with or without NUM (0 to 9)."};
241 }
242 }
243
244 char* const buf = buffer.data();
245 std::size_t len = buffer.size();
246 const int shmId = fillSharedMemory(contents: statusReportPreview(frame: args.empty() ? 0 : std::stoul(str: args.front())));
247 if (const auto ec = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.statusShmId = shmId}); ec)
248 {
249 throw std::runtime_error{
250 "Failed to build packet for the " + std::string{OptMonitor::name} + " option (" + ec.message() + ")."};
251 }
252 data::encryptMessage(buffer: buf, length: len);
253 return len;
254}
255
256//! @brief Build the TLV packet of the response message to get current configuration.
257//! @param buffer - TLV packet buffer
258//! @return buffer length
259template <>
260std::size_t View::buildCustomTLVPacket<View::OptProfile>(const Args& /*args*/, Buffer& buffer)
261{
262 tlv::TLVValue val{};
263 std::strncpy(
264 dest: val.configInfo,
265 src: (static_cast<const utility::json::JSON&>(configure::retrieveDataRepo())).asUnescapedString().c_str(),
266 n: sizeof(val.configInfo) - 1);
267 val.configInfo[sizeof(val.configInfo) - 1] = '\0';
268 char* const buf = buffer.data();
269 std::size_t len = buffer.size();
270 if (const auto ec = tlv::encodeTLV(buf, len, val); ec)
271 {
272 throw std::runtime_error{
273 "Failed to build packet for the " + std::string{OptProfile::name} + " option (" + ec.message() + ")."};
274 }
275 data::encryptMessage(buffer: buf, length: len);
276 return len;
277}
278
279std::size_t View::buildResponse(const std::string& reqPlaintext, Buffer& respBuffer)
280{
281 return utility::common::patternMatch(
282 var: extractOption(reqPlaintext),
283 cases: [&respBuffer](const OptDepend& opt) { return buildCustomTLVPacket<OptDepend>(opt.args, buffer&: respBuffer); },
284 cases: [&respBuffer](const OptExecute& opt) { return buildCustomTLVPacket<OptExecute>(args: opt.args, buffer&: respBuffer); },
285 cases: [&respBuffer](const OptJournal& opt) { return buildCustomTLVPacket<OptJournal>(opt.args, buffer&: respBuffer); },
286 cases: [&respBuffer](const OptMonitor& opt) { return buildCustomTLVPacket<OptMonitor>(args: opt.args, buffer&: respBuffer); },
287 cases: [&respBuffer](const OptProfile& opt) { return buildCustomTLVPacket<OptProfile>(opt.args, buffer&: respBuffer); },
288 cases: [](const auto& opt)
289 {
290 if (const auto* origPtr = std::addressof(opt); dynamic_cast<const OptBase*>(origPtr))
291 {
292 throw std::runtime_error{
293 "The option is unprocessed due to unregistered or potential registration failures (typeid: "
294 + std::string{typeid(opt).name()} + ")."};
295 }
296 return 0UL;
297 });
298}
299
300View::OptionType View::extractOption(const std::string& reqPlaintext)
301{
302 auto args = splitString(str: reqPlaintext);
303 if (args.empty())
304 {
305 return {};
306 }
307
308 const auto& optName = args.front();
309 switch (utility::common::bkdrHash(str: optName.c_str()))
310 {
311 using utility::common::operator""_bkdrHash;
312 case operator""_bkdrHash(str: OptDepend::name):
313 return OptDepend{};
314 case operator""_bkdrHash(str: OptExecute::name):
315 args.erase(position: args.cbegin());
316 return OptExecute{std::move(args)};
317 case operator""_bkdrHash(str: OptJournal::name):
318 return OptJournal{};
319 case operator""_bkdrHash(str: OptMonitor::name):
320 args.erase(position: args.cbegin());
321 return OptMonitor{std::move(args)};
322 case operator""_bkdrHash(str: OptProfile::name):
323 return OptProfile{};
324 default:
325 break;
326 }
327 return {};
328}
329
330std::vector<std::string> View::splitString(const std::string& str)
331{
332 std::vector<std::string> split{};
333 std::istringstream transfer(str);
334 std::string token{};
335 while (transfer >> token)
336 {
337 split.emplace_back(args&: token);
338 }
339 return split;
340}
341
342std::size_t View::buildAckTLVPacket(Buffer& buffer)
343{
344 char* const buf = buffer.data();
345 std::size_t len = buffer.size();
346 std::ignore = tlv::encodeTLV(buf, len, val: tlv::TLVValue{});
347 data::encryptMessage(buffer: buf, length: len);
348 return len;
349}
350
351std::size_t View::buildFinTLVPacket(Buffer& buffer)
352{
353 char* const buf = buffer.data();
354 std::size_t len = buffer.size();
355 std::ignore = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.stopTag = true});
356 data::encryptMessage(buffer: buf, length: len);
357 return len;
358}
359
360// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
361int View::fillSharedMemory(const std::string_view contents)
362{
363 const int shmId = ::shmget(
364 key: 0, size: sizeof(ShrMemBlock), IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
365 if (shmId == -1)
366 {
367 throw std::runtime_error{"Failed to create shared memory (" + std::to_string(val: shmId) + ")."};
368 }
369 void* const shm = ::shmat(shmid: shmId, shmaddr: nullptr, shmflg: 0);
370 if (!shm)
371 {
372 throw std::runtime_error{"Failed to attach shared memory (" + std::to_string(val: shmId) + ")."};
373 }
374
375 auto* const shrMem = reinterpret_cast<ShrMemBlock*>(shm);
376 for (shrMem->signal.store(i: false);;)
377 {
378 if (shrMem->signal.load())
379 {
380 std::this_thread::yield();
381 continue;
382 }
383
384 std::vector<char> processed(contents.data(), contents.data() + contents.length());
385 data::compressData(cache&: processed);
386 if (processed.size() > sizeof(shrMem->data))
387 {
388 shrMem->signal.store(i: true);
389 break;
390 }
391 data::encryptMessage(buffer: processed.data(), length: processed.size());
392 std::memset(s: shrMem->data, c: 0, n: sizeof(shrMem->data));
393 std::memcpy(dest: shrMem->data, src: processed.data(), n: processed.size() * sizeof(char));
394 shrMem->size = processed.size();
395
396 shrMem->signal.store(i: true);
397 break;
398 }
399 ::shmdt(shmaddr: shm);
400 return shmId;
401}
402
403void View::fetchSharedMemory(const int shmId, std::string& contents)
404{
405 void* const shm = ::shmat(shmid: shmId, shmaddr: nullptr, shmflg: 0);
406 if (!shm)
407 {
408 throw std::runtime_error{"Failed to attach shared memory (" + std::to_string(val: shmId) + ")."};
409 }
410
411 auto* const shrMem = reinterpret_cast<ShrMemBlock*>(shm);
412 for (shrMem->signal.store(i: true);;)
413 {
414 if (!shrMem->signal.load())
415 {
416 std::this_thread::yield();
417 continue;
418 }
419
420 if (shrMem->size > sizeof(shrMem->data))
421 {
422 shrMem->signal.store(i: false);
423 break;
424 }
425 std::vector<char> processed(shrMem->size);
426 shrMem->size = 0;
427 std::memcpy(dest: processed.data(), src: shrMem->data, n: processed.size() * sizeof(char));
428 std::memset(s: shrMem->data, c: 0, n: sizeof(shrMem->data));
429 data::decryptMessage(buffer: processed.data(), length: processed.size());
430 data::decompressData(cache&: processed);
431 contents = std::string{processed.data(), processed.data() + processed.size()};
432
433 shrMem->signal.store(i: false);
434 break;
435 }
436 ::shmdt(shmaddr: shm);
437 ::shmctl(shmid: shmId, IPC_RMID, buf: nullptr);
438}
439// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
440
441void View::printSharedMemory(const int shmId, const bool withoutPaging)
442{
443 std::string output{};
444 fetchSharedMemory(shmId, contents&: output);
445 if (!withoutPaging)
446 {
447 segmentedOutput(cache: output);
448 return;
449 }
450
451 std::istringstream transfer(output.c_str());
452 std::string line{};
453 while (std::getline(is&: transfer, str&: line))
454 {
455 std::cout << line << '\n';
456 }
457 std::cout << "\033[0m" << std::flush;
458}
459
460void View::segmentedOutput(const std::string& cache)
461{
462 constexpr std::uint8_t terminalRows = 24;
463 constexpr std::string_view prompt = "--- Type <CR> for more, c to continue, n to show next page, q to quit ---: ";
464 constexpr std::string_view escapeClear = "\033[1A\033[2K\r";
465 constexpr std::string_view escapeMoveUp = "\n\033[1A\033[";
466 std::istringstream transfer(cache);
467 const std::size_t lineNum =
468 std::count(first: std::istreambuf_iterator<char>(transfer), last: std::istreambuf_iterator<char>{}, value: '\n');
469 transfer.seekg(std::ios::beg);
470
471 bool moreRows = false;
472 bool forcedCancel = false;
473 bool withoutPaging = (lineNum <= terminalRows);
474 std::string line{};
475 std::size_t counter = 0;
476 const auto handling = utility::common::wrapClosure(
477 closure: [&](const std::string& input)
478 {
479 std::cout << escapeClear << std::flush;
480 if (input.empty())
481 {
482 moreRows = true;
483 counter = 0;
484 return true;
485 }
486
487 moreRows = false;
488 switch (utility::common::bkdrHash(str: input.c_str()))
489 {
490 using utility::common::operator""_bkdrHash;
491 case "c"_bkdrHash:
492 withoutPaging = true;
493 break;
494 case "n"_bkdrHash:
495 counter = 0;
496 break;
497 case "q"_bkdrHash:
498 forcedCancel = true;
499 break;
500 default:
501 std::cout << prompt << std::flush;
502 return false;
503 }
504 return true;
505 });
506 while (std::getline(is&: transfer, str&: line) && !forcedCancel)
507 {
508 std::cout << line << '\n';
509 ++counter;
510 if (!withoutPaging && (moreRows || (counter == terminalRows)))
511 {
512 std::cout << prompt << escapeMoveUp << prompt.length() << 'C' << std::flush;
513 utility::io::waitForUserInput(operation: handling);
514 }
515 }
516
517 std::cout << "\033[0m" << std::flush;
518 if (lineNum > terminalRows)
519 {
520 std::cout << std::endl;
521 }
522}
523
524std::string View::logContextPreview()
525{
526 std::ostringstream transfer{};
527 log::intf::previewInContext(
528 peeking: [&transfer](const std::string& filePath)
529 {
530 constexpr std::uint16_t lineLimit = 24 * 100;
531 auto logRows = utility::io::readFileLines(filename: filePath, lock: false, reverse: true, limit: lineLimit);
532 std::ranges::for_each(logRows, [](auto& line) { log::changeToLogStyle(line); });
533 std::ranges::copy(logRows, std::ostream_iterator<std::string>(transfer, "\n"));
534 });
535 return std::move(transfer).str();
536}
537
538std::string View::statusReportPreview(const std::uint16_t frame)
539{
540 if ((frame > 0)
541 && (::system( // NOLINT(cert-env33-c, concurrency-mt-unsafe)
542 command: "command -v eu-stack >/dev/null 2>&1 "
543 "&& grep -qx '0' /proc/sys/kernel/yama/ptrace_scope >/dev/null 2>&1")
544 != EXIT_SUCCESS))
545 {
546 throw std::runtime_error{"Please confirm that the eu-stack program has been installed and "
547 "that the classic ptrace permissions have been set."};
548 }
549
550 const ::pid_t pid = ::getpid();
551 constexpr std::uint16_t totalLen = 1024;
552 std::array<char, totalLen> queryStmt{};
553 std::snprintf(
554 s: queryStmt.data(), maxlen: queryStmt.size(), format: "ps -T -p %d | awk 'NR>1 {split($0, a, \" \"); print a[2]}'", pid);
555
556 constexpr const char* const focusField = "Name|State|Tgid|Pid|PPid|TracerPid|Uid|Gid|VmSize|VmRSS|CoreDumping|"
557 "Threads|SigQ|voluntary_ctxt_switches|nonvoluntary_ctxt_switches";
558 const auto queryResult = utility::io::executeCommand(command: queryStmt.data());
559 std::vector<std::string> statements{};
560 std::size_t pos = 0;
561 std::size_t prev = 0;
562 while ((pos = queryResult.find(c: '\n', pos: prev)) != std::string::npos)
563 {
564 const int tid = std::stoi(str: queryResult.substr(pos: prev, n: pos - prev + 1));
565 std::array<char, totalLen> execStmt{};
566 if (const int usedLen = std::snprintf(
567 s: execStmt.data(),
568 maxlen: execStmt.size(),
569 format: "/bin/bash -c "
570 "\"if [[ -f /proc/%d/task/%d/status ]]; then cat /proc/%d/task/%d/status | grep -E '^(%s):'",
571 pid,
572 tid,
573 pid,
574 tid,
575 focusField);
576 frame == 0)
577 {
578 std::strncpy(dest: execStmt.data() + usedLen, src: "; fi\"", n: execStmt.size() - usedLen);
579 execStmt[totalLen - 1] = '\0';
580 }
581 else
582 {
583 std::snprintf(
584 s: execStmt.data() + usedLen,
585 maxlen: execStmt.size() - usedLen,
586 format: " && echo 'Stack:' "
587 "&& (timeout --preserve-status -s SIGINT 1 stdbuf -o0 eu-stack -1v -n %u -p %d 2>&1 | grep '#' "
588 "|| exit 0); fi\"",
589 frame,
590 tid);
591 }
592 statements.emplace_back(args: execStmt.data());
593 prev += pos - prev + 1;
594 }
595
596 auto reports = std::accumulate(
597 first: statements.cbegin(),
598 last: statements.cend(),
599 init: std::string{},
600 binary_op: [](const auto& acc, const auto& stmt)
601 { return acc.empty() ? utility::io::executeCommand(command: stmt) : (acc + '\n' + utility::io::executeCommand(command: stmt)); });
602 while (!reports.empty() && (reports.back() == '\n'))
603 {
604 reports.pop_back();
605 }
606 return reports;
607}
608
609//! @brief Renew the TCP server.
610template <>
611void View::renewServer<utility::socket::TCPServer>()
612{
613 permSessServer = std::make_shared<utility::socket::TCPServer>();
614 permSessServer->subscribeConnection(
615 callback: [](const std::shared_ptr<utility::socket::TCPSocket> client) // NOLINT(performance-unnecessary-value-param)
616 {
617 const std::weak_ptr weakSock = client;
618 client->subscribeMessage(
619 callback: [weakSock](const std::string_view message)
620 {
621 auto currSock = weakSock.lock();
622 if (!currSock || message.empty())
623 {
624 return;
625 }
626
627 Buffer respBuffer{};
628 try
629 {
630 const auto reqPlaintext = utility::common::base64Decode(data: message);
631 currSock->send(
632 bytes: respBuffer.data(),
633 size: (reqPlaintext != exitSymbol) ? buildResponse(reqPlaintext, respBuffer)
634 : buildFinTLVPacket(buffer&: respBuffer));
635 }
636 catch (const std::exception& err)
637 {
638 LOG_WRN << err.what();
639 currSock->send(bytes: respBuffer.data(), size: buildAckTLVPacket(buffer&: respBuffer));
640 }
641 });
642 });
643}
644
645//! @brief Renew the UDP server.
646template <>
647void View::renewServer<utility::socket::UDPServer>()
648{
649 tempSessServer = std::make_shared<utility::socket::UDPServer>();
650 const std::weak_ptr weakSock = tempSessServer;
651 tempSessServer->subscribeMessage(
652 callback: [weakSock](const std::string_view message, const std::string& ip, const std::uint16_t port)
653 {
654 auto currSock = weakSock.lock();
655 if (!currSock || message.empty())
656 {
657 return;
658 }
659
660 Buffer respBuffer{};
661 try
662 {
663 const auto reqPlaintext = utility::common::base64Decode(data: message);
664 currSock->sendTo(
665 bytes: respBuffer.data(),
666 size: (reqPlaintext != exitSymbol) ? buildResponse(reqPlaintext, respBuffer)
667 : buildFinTLVPacket(buffer&: respBuffer),
668 ip,
669 port);
670 }
671 catch (const std::exception& err)
672 {
673 LOG_WRN << err.what();
674 currSock->sendTo(bytes: respBuffer.data(), size: buildAckTLVPacket(buffer&: respBuffer), ip, port);
675 }
676 });
677}
678
679//! @brief Connect from the TCP client. Simplified interface for external use.
680//! @param client - TCP client to be connected
681template <>
682void View::onConnecting<utility::socket::TCPSocket>(std::shared_ptr<utility::socket::TCPSocket>& client)
683{
684 client->subscribeRawMessage(callback: [this, &client](char* const bytes, const std::size_t size)
685 { parseTLVMessage(client, bytes, size); });
686 client->connect(ip: tcpHost, port: tcpPort);
687}
688
689//! @brief Connect from the UDP client. Simplified interface for external use.
690//! @param client - UDP client to be connected
691template <>
692void View::onConnecting<utility::socket::UDPSocket>(std::shared_ptr<utility::socket::UDPSocket>& client)
693{
694 client->subscribeRawMessage(
695 callback: [this, &client](char* const bytes, const std::size_t size, const std::string& ip, const std::uint16_t port)
696 {
697 MACRO_IGNORE(ip, port);
698 parseTLVMessage(client, bytes, size);
699 });
700 client->receive();
701 client->connect(ip: udpHost, port: udpPort);
702}
703
704template <typename Sock>
705void View::onForwarding(std::shared_ptr<Sock>& client, const std::vector<std::string>& inputs)
706{
707 auto reqBuffer = utility::common::base64Encode(data: std::accumulate(
708 inputs.cbegin(),
709 inputs.cend(),
710 std::string{},
711 [](const auto& acc, const auto& token) { return acc.empty() ? token : (acc + ' ' + token); }));
712 client->send(std::move(reqBuffer));
713 waitTaskDone();
714}
715
716template <typename Sock>
717void View::parseTLVMessage(std::shared_ptr<Sock>& client, char* const bytes, const std::size_t size)
718try
719{
720 MACRO_DEFER([this]() { notifyTaskDone(); });
721 if (client->stopRequested() || !bytes || (size == 0))
722 {
723 return;
724 }
725
726 data::decryptMessage(buffer: bytes, length: size);
727 tlv::TLVValue value{};
728 if (const auto ec = tlv::decodeTLV(buf: bytes, len: size, val&: value); ec)
729 {
730 throw std::runtime_error{
731 "Invalid message content \"" + data::toHexString(buffer: bytes, length: size) + "\" (" + ec.message() + ")."};
732 }
733
734 if (const std::size_t actLibLen = ::strnlen(string: value.libInfo, maxlen: sizeof(value.libInfo));
735 (actLibLen != 0) && (actLibLen < sizeof(value.libInfo)))
736 {
737 std::cout << value.libInfo << std::endl;
738 }
739 if (value.bashShmId != tlv::invalidShmId)
740 {
741 printSharedMemory(shmId: value.bashShmId);
742 }
743 if (value.logShmId != tlv::invalidShmId)
744 {
745 printSharedMemory(shmId: value.logShmId, withoutPaging: !isInServingState(state: State::established));
746 }
747 if (value.statusShmId != tlv::invalidShmId)
748 {
749 printSharedMemory(shmId: value.statusShmId);
750 }
751 if (const std::size_t actConfigLen = ::strnlen(string: value.configInfo, maxlen: sizeof(value.configInfo));
752 (actConfigLen != 0) && (actConfigLen < sizeof(value.configInfo)))
753 {
754 using utility::json::JSON;
755 std::cout << JSON::load(fmt: value.configInfo) << std::endl;
756 }
757
758 if (value.stopTag)
759 {
760 client->requestStop();
761 }
762}
763catch (const std::exception& err)
764{
765 LOG_WRN << err.what();
766}
767
768void View::waitTaskDone()
769{
770 std::unique_lock<std::mutex> outputLock(outputMtx);
771 outputCompleted.store(i: false);
772
773 const auto maxWaitTime = std::chrono::milliseconds{timeoutPeriod};
774 utility::time::Timer expiryTimer(
775 isInServingState(state: State::established) ? std::function<void()>{[]() {}} : [this]() { notifyTaskDone(); });
776 expiryTimer.start(interval: maxWaitTime);
777 outputCond.wait(lock&: outputLock, p: [this]() { return outputCompleted.load(); });
778}
779
780void View::notifyTaskDone()
781{
782 std::unique_lock<std::mutex> outputLock(outputMtx);
783 outputCompleted.store(i: true);
784 outputLock.unlock();
785 outputCond.notify_one();
786}
787
788void View::syncWaitOr(const State state, const std::function<void()>& handling) const
789{
790 do
791 {
792 if (isInServingState(state: State::idle) && handling)
793 {
794 handling();
795 }
796 std::this_thread::yield();
797 }
798 while (!isInServingState(state));
799}
800
801void View::syncNotifyVia(const std::function<void()>& action)
802{
803 std::unique_lock<std::mutex> daemonLock(daemonMtx);
804 if (action)
805 {
806 action();
807 }
808 daemonLock.unlock();
809 daemonCond.notify_one();
810}
811
812void View::syncCountdownIf(const std::function<bool()>& condition, const std::function<void()>& handling) const
813{
814 if (!condition)
815 {
816 return;
817 }
818 for (const utility::time::Stopwatch timing{}; timing.elapsedTime() <= timeoutPeriod;)
819 {
820 if (!condition())
821 {
822 return;
823 }
824 std::this_thread::yield();
825 }
826 if (handling)
827 {
828 handling();
829 }
830}
831
832bool View::isInServingState(const State state) const
833{
834 return (currentState() == state) && !inResetting.load();
835}
836
837void View::createViewServer()
838{
839 renewServer<utility::socket::TCPServer>();
840 renewServer<utility::socket::UDPServer>();
841}
842
843void View::destroyViewServer()
844{
845 if (permSessServer)
846 {
847 permSessServer->close();
848 permSessServer->join();
849 }
850 permSessServer.reset();
851
852 if (tempSessServer)
853 {
854 tempSessServer->close();
855 tempSessServer->join();
856 }
857 tempSessServer.reset();
858}
859
860void View::startViewing()
861{
862 if (permSessServer)
863 {
864 permSessServer->bind(port: tcpPort);
865 permSessServer->listen();
866 permSessServer->accept();
867 }
868
869 if (tempSessServer)
870 {
871 tempSessServer->bind(port: udpPort);
872 tempSessServer->receiveFrom();
873 }
874}
875
876void View::stopViewing()
877{
878 const std::scoped_lock locks(daemonMtx, outputMtx);
879 isOngoing.store(i: false);
880 inResetting.store(i: false);
881 outputCompleted.store(i: true);
882}
883
884void View::doToggle()
885{
886 utility::benchmark::escape(p: this);
887 utility::benchmark::clobber();
888}
889
890void View::doRollback()
891{
892 const std::scoped_lock locks(daemonMtx, outputMtx);
893 isOngoing.store(i: false);
894
895 destroyViewServer();
896
897 inResetting.store(i: false);
898 outputCompleted.store(i: true);
899}
900
901void View::notificationLoop()
902{
903 while (isOngoing.load())
904 {
905 std::unique_lock<std::mutex> daemonLock(daemonMtx);
906 daemonCond.wait(lock&: daemonLock, p: [this]() { return !isOngoing.load() || inResetting.load(); });
907 if (inResetting.load())
908 {
909 break;
910 }
911
912 if (MACRO_IMPLIES(permSessServer, permSessServer->stopRequested())
913 || MACRO_IMPLIES(tempSessServer, tempSessServer->stopRequested()))
914 {
915 throw std::runtime_error{"Found that the server did not work as expected."};
916 }
917 }
918}
919
920void View::awaitNotification2Proceed()
921{
922 std::unique_lock<std::mutex> daemonLock(daemonMtx);
923 daemonCond.wait(lock&: daemonLock, p: [this]() { return isOngoing.load(); });
924}
925
926bool View::awaitNotification2Retry()
927{
928 std::unique_lock<std::mutex> daemonLock(daemonMtx);
929 daemonCond.wait(lock&: daemonLock);
930 return inResetting.load();
931}
932
933//! @brief The operator (<<) overloading of the State enum.
934//! @param os - output stream object
935//! @param state - current state
936//! @return reference of the output stream object
937std::ostream& operator<<(std::ostream& os, const View::State state)
938{
939 using enum View::State;
940 switch (state)
941 {
942 case initial:
943 os << "INITIAL";
944 break;
945 case active:
946 os << "ACTIVE";
947 break;
948 case established:
949 os << "ESTABLISHED";
950 break;
951 case inactive:
952 os << "INACTIVE";
953 break;
954 case idle:
955 os << "IDLE";
956 break;
957 default:
958 os << "UNKNOWN (" << static_cast<std::underlying_type_t<View::State>>(state) << ')';
959 break;
960 }
961 return os;
962}
963
964//! @brief Obtain the supported viewer options.
965//! @return supported viewer options
966auto obtainSupportedOptions() -> std::map<std::string, std::string>
967{
968 using OptDepend = View::OptDepend;
969 using OptExecute = View::OptExecute;
970 using OptJournal = View::OptJournal;
971 using OptMonitor = View::OptMonitor;
972 using OptProfile = View::OptProfile;
973 return std::map<std::string, std::string>{
974 {OptDepend::name, OptDepend::description},
975 {OptExecute::name, OptExecute::description},
976 {OptJournal::name, OptJournal::description},
977 {OptMonitor::name, OptMonitor::description},
978 {OptProfile::name, OptProfile::description}};
979}
980
981//! @brief Build the disconnect request message.
982//! @return disconnect request message
983std::string buildDisconnectRequest()
984{
985 return utility::common::base64Encode(data: exitSymbol);
986}
987
988//! @brief Latency in the millisecond range.
989void interactionLatency()
990{
991 constexpr auto latency = std::chrono::milliseconds{10};
992 std::this_thread::sleep_for(rtime: latency);
993}
994
995namespace intf
996{
997//! @brief Connect from the TCP client.
998//! @param client - TCP client to be connected
999template <>
1000void connectFromClient(std::shared_ptr<utility::socket::TCPSocket>& client)
1001{
1002 View::getInstance()->onConnecting(client);
1003}
1004
1005//! @brief Connect from the UDP client.
1006//! @param client - UDP client to be connected
1007template <>
1008void connectFromClient(std::shared_ptr<utility::socket::UDPSocket>& client)
1009{
1010 View::getInstance()->onConnecting(client);
1011}
1012
1013//! @brief Forward message by TCP client.
1014//! @param client - TCP client to be used for forwarding
1015//! @param inputs - input strings to be forwarded
1016template <>
1017void forwardByClient(std::shared_ptr<utility::socket::TCPSocket>& client, const std::vector<std::string>& inputs)
1018{
1019 View::getInstance()->onForwarding(client, inputs);
1020}
1021
1022//! @brief Forward message by UDP client.
1023//! @param client - UDP client to be used for forwarding
1024//! @param inputs - input strings to be forwarded
1025template <>
1026void forwardByClient(std::shared_ptr<utility::socket::UDPSocket>& client, const std::vector<std::string>& inputs)
1027{
1028 View::getInstance()->onForwarding(client, inputs);
1029}
1030} // namespace intf
1031} // namespace view
1032
1033//! @brief Wait for the viewer to start.
1034template <>
1035void view::AccessController::startup() const
1036try
1037{
1038 using View = view::View;
1039 using State = View::State;
1040 srv->syncWaitOr(
1041 state: State::active, handling: []() { throw std::runtime_error{"The " + View::name + " did not set up successfully ..."}; });
1042 srv->syncNotifyVia(action: [this]() { srv->isOngoing.store(i: true); });
1043 srv->syncWaitOr(
1044 state: State::established,
1045 handling: []() { throw std::runtime_error{"The " + View::name + " did not start successfully ..."}; });
1046}
1047catch (const std::exception& err)
1048{
1049 LOG_ERR << err.what();
1050}
1051
1052//! @brief Wait for the viewer to stop.
1053template <>
1054void view::AccessController::shutdown() const
1055try
1056{
1057 using View = view::View;
1058 using State = View::State;
1059 srv->syncNotifyVia(action: [this]() { srv->isOngoing.store(i: false); });
1060 srv->syncWaitOr(
1061 state: State::inactive, handling: []() { throw std::runtime_error{"The " + View::name + " did not stop successfully ..."}; });
1062}
1063catch (const std::exception& err)
1064{
1065 LOG_ERR << err.what();
1066}
1067
1068//! @brief Request to reset the viewer.
1069template <>
1070void view::AccessController::reload() const
1071try
1072{
1073 using View = view::View;
1074 srv->syncNotifyVia(action: [this]() { srv->inResetting.store(i: true); });
1075 srv->syncCountdownIf(
1076 condition: [this]() { return srv->inResetting.load(); },
1077 handling: [this]()
1078 {
1079 throw std::runtime_error{
1080 "The " + View::name + " did not reset properly in " + std::to_string(val: srv->timeoutPeriod) + " ms ..."};
1081 });
1082}
1083catch (const std::exception& err)
1084{
1085 LOG_ERR << err.what();
1086}
1087} // namespace application
1088