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::view // NOLINT(modernize-concat-nested-namespaces)
38{
39//! @brief Type-length-value scheme.
40namespace tlv
41{
42//! @brief Invalid shared memory id.
43constexpr int invalidShmId = -1;
44//! @brief Default information size.
45constexpr std::uint16_t defInfoSize = 256;
46
47//! @brief Enumerate the types in TLV.
48enum TLVType : int
49{
50 //! @brief Header.
51 header = 0x3B9ACA07,
52 //! @brief Stop.
53 stop = 0,
54 //! @brief Depend.
55 depend = 1,
56 //! @brief Execute.
57 execute = 2,
58 //! @brief Journal.
59 journal = 3,
60 //! @brief Monitor.
61 monitor = 4,
62 //! @brief Profile.
63 profile = 5
64};
65
66//! @brief Value in TLV.
67struct TLVValue
68{
69 //! @brief Flag for stopping the connection.
70 bool stopTag{false};
71 //! @brief Information about the runtime library.
72 char libInfo[defInfoSize]{'\0'};
73 //! @brief Shared memory id of the bash outputs.
74 int bashShmId{invalidShmId};
75 //! @brief Shared memory id of the log contents.
76 int logShmId{invalidShmId};
77 //! @brief Shared memory id of the status reports.
78 int statusShmId{invalidShmId};
79 //! @brief Information about the current configuration.
80 char configInfo[defInfoSize * 2]{'\0'};
81};
82
83//! @brief TLV value serialization.
84//! @tparam Data - type of target payload
85//! @param pkt - encoding packet that was filled type
86//! @param val - value of TLV to encode
87//! @param pl - target payload that has been included in the value of TLV
88//! @return summary offset of length-value
89template <typename Data>
90static int serialize(data::Packet& pkt, const TLVValue& val, const Data TLVValue::* const pl)
91{
92 if (!pl)
93 {
94 return 0;
95 }
96
97 constexpr int length = sizeof(Data);
98 pkt.write<int>(data: length);
99 pkt.write<Data>(val.*pl);
100 return static_cast<int>(sizeof(int) + length);
101}
102
103//! @brief TLV value serialization.
104//! @tparam Size - size of target payload
105//! @param pkt - encoding packet that was filled type
106//! @param val - value of TLV to encode
107//! @param pl - target payload that has been included in the value of TLV
108//! @return summary offset of length-value
109template <std::size_t Size>
110static int serialize(data::Packet& pkt, const TLVValue& val, const char (TLVValue::* const pl)[Size])
111{
112 if (!pl)
113 {
114 return 0;
115 }
116
117 const int length = ::strnlen(string: val.*pl, maxlen: Size);
118 pkt.write<int>(data: length);
119 pkt.write(val.*pl, length);
120 return static_cast<int>(sizeof(int) + length);
121}
122
123//! @brief TLV value deserialization.
124//! @tparam Data - type of target payload
125//! @param pkt - decoding packet that was filled type
126//! @param val - value of TLV to decode
127//! @param pl - target payload that has been included in the value of TLV
128//! @return summary offset of length-value
129template <typename Data>
130static int deserialize(data::Packet& pkt, TLVValue& val, Data TLVValue::* const pl)
131{
132 if (!pl)
133 {
134 return 0;
135 }
136
137 int length = 0;
138 pkt.read<int>(data: &length);
139 pkt.read<Data>(&(val.*pl));
140 return static_cast<int>(sizeof(int) + length);
141}
142
143//! @brief TLV value deserialization.
144//! @tparam Size - size of target payload
145//! @param pkt - decoding packet that was filled type
146//! @param val - value of TLV to decode
147//! @param pl - target payload that has been included in the value of TLV
148//! @return summary offset of length-value
149template <std::size_t Size>
150static int deserialize(data::Packet& pkt, TLVValue& val, char (TLVValue::* const pl)[Size])
151{
152 if (!pl)
153 {
154 return 0;
155 }
156
157 int length = 0;
158 pkt.read<int>(data: &length);
159 pkt.read(val.*pl, std::min<std::size_t>(a: length, b: Size));
160 return static_cast<int>(sizeof(int) + length);
161}
162
163//! @brief Enumerate the error codes for TLV error handling.
164enum class ErrCode : std::uint8_t
165{
166 //! @brief No error.
167 noError = 0,
168 //! @brief Null buffer.
169 nullBuffer,
170 //! @brief Insufficient length.
171 insufficientLength,
172 //! @brief Bad header.
173 badHeader,
174 //! @brief Unknown type.
175 unknownType,
176 //! @brief Fail serialize.
177 failSerialize,
178 //! @brief Fail deserialize.
179 failDeserialize,
180};
181
182//! @brief Error category for TLV error handling.
183class ErrCategory : public std::error_category
184{
185public:
186 //! @brief Get the name of the error category.
187 //! @return name of the error category
188 [[nodiscard]] const char* name() const noexcept override { return "TLV error category"; }
189 //! @brief Get the message of the error code value.
190 //! @param value - error code value
191 //! @return message of the error code value
192 [[nodiscard]] std::string message(const int value) const override
193 {
194 switch (static_cast<ErrCode>(value))
195 {
196 case ErrCode::noError:
197 return "no TLV error";
198 case ErrCode::nullBuffer:
199 return "null TLV buffer";
200 case ErrCode::insufficientLength:
201 return "insufficient TLV-length";
202 case ErrCode::badHeader:
203 return "invalid TLV header";
204 case ErrCode::unknownType:
205 return "unknown TLV-type";
206 case ErrCode::failSerialize:
207 return "TLV-value serialization failure";
208 case ErrCode::failDeserialize:
209 return "TLV-value deserialization failure";
210 default:
211 break;
212 }
213 return "unknown TLV error";
214 }
215};
216
217//! @brief Make error code from the custom error code for TLV error handling.
218//! @param errCode - custom error code
219//! @return error code
220[[maybe_unused]] static std::error_code make_error_code(const ErrCode errCode) // NOLINT(readability-identifier-naming)
221{
222 static const ErrCategory errCategory{};
223 return std::error_code{static_cast<int>(errCode), errCategory};
224}
225} // namespace tlv
226} // namespace application::view
227
228template <>
229struct std::is_error_code_enum<application::view::tlv::ErrCode> : public std::true_type
230{
231};
232
233namespace application::view
234{
235namespace tlv
236{
237//! @brief Encode the TLV packet.
238//! @param buf - TLV packet buffer
239//! @param len - buffer length
240//! @param val - value of TLV to encode
241//! @return error code
242static std::error_code encodeTLV(char* const buf, std::size_t& len, const TLVValue& val)
243{
244 if (!buf)
245 {
246 return ErrCode::nullBuffer;
247 }
248
249 data::Packet enc(buf, len);
250 int sum = 0;
251 if (!enc.write<int>(data: TLVType::header) || !enc.write<int>(data: sum))
252 {
253 return ErrCode::insufficientLength;
254 }
255
256 const auto serializer = [&enc, &val, &sum](const auto pl) -> std::error_code
257 {
258 if (const int res = serialize(enc, val, pl); res > 0)
259 {
260 sum += sizeof(int) + res;
261 return ErrCode::noError;
262 }
263 return ErrCode::failSerialize;
264 };
265 enc.write<int>(data: TLVType::stop);
266 if (const auto ec = serializer(&TLVValue::stopTag); ec)
267 {
268 return ec;
269 }
270 enc.write<int>(data: TLVType::depend);
271 if (const auto ec = serializer(&TLVValue::libInfo); ec)
272 {
273 return ec;
274 }
275 enc.write<int>(data: TLVType::execute);
276 if (const auto ec = serializer(&TLVValue::bashShmId); ec)
277 {
278 return ec;
279 }
280 enc.write<int>(data: TLVType::journal);
281 if (const auto ec = serializer(&TLVValue::logShmId); ec)
282 {
283 return ec;
284 }
285 enc.write<int>(data: TLVType::monitor);
286 if (const auto ec = serializer(&TLVValue::statusShmId); ec)
287 {
288 return ec;
289 }
290 enc.write<int>(data: TLVType::profile);
291 if (const auto ec = serializer(&TLVValue::configInfo); ec)
292 {
293 return ec;
294 }
295
296 auto temp = static_cast<int>(::htonl(hostlong: sum));
297 std::memcpy(dest: buf + sizeof(int), src: &temp, n: sizeof(temp));
298 len = sizeof(int) + sizeof(int) + sum;
299 return ErrCode::noError;
300}
301
302//! @brief Decode the TLV packet.
303//! @param buf - TLV packet buffer
304//! @param len - buffer length
305//! @param val - value of TLV to decode
306//! @return error code
307static std::error_code decodeTLV(char* const buf, const std::size_t len, TLVValue& val)
308{
309 if (!buf || (len == 0))
310 {
311 return ErrCode::nullBuffer;
312 }
313
314 data::Packet dec(buf, len);
315 int type = 0;
316 int sum = 0;
317 if (!dec.read<int>(data: &type) || !dec.read<int>(data: &sum))
318 {
319 return ErrCode::insufficientLength;
320 }
321 if (type != TLVType::header)
322 {
323 return ErrCode::badHeader;
324 }
325
326 const auto deserializer = [&dec, &val, &sum](const auto pl) -> std::error_code
327 {
328 if (const int res = deserialize(dec, val, pl); res > 0)
329 {
330 sum -= sizeof(int) + res;
331 return ErrCode::noError;
332 }
333 return ErrCode::failDeserialize;
334 };
335 while (sum > 0)
336 {
337 dec.read<int>(data: &type);
338 std::error_code ec = ErrCode::noError;
339 switch (type)
340 {
341 case TLVType::stop:
342 ec = deserializer(&TLVValue::stopTag);
343 break;
344 case TLVType::depend:
345 ec = deserializer(&TLVValue::libInfo);
346 break;
347 case TLVType::execute:
348 ec = deserializer(&TLVValue::bashShmId);
349 break;
350 case TLVType::journal:
351 ec = deserializer(&TLVValue::logShmId);
352 break;
353 case TLVType::monitor:
354 ec = deserializer(&TLVValue::statusShmId);
355 break;
356 case TLVType::profile:
357 ec = deserializer(&TLVValue::configInfo);
358 break;
359 default:
360 ec = ErrCode::unknownType;
361 break;
362 }
363 if (ec)
364 {
365 return ec;
366 }
367 }
368 return ErrCode::noError;
369}
370} // namespace tlv
371
372View::View() : FSM(State::initial)
373{
374 if (!configure::detail::activateHelper()) [[unlikely]]
375 {
376 throw std::logic_error{"The " + name + " is disabled."};
377 }
378}
379
380std::shared_ptr<View> View::getInstance()
381{
382 static const std::shared_ptr<View> viewer(::new View{});
383 return viewer;
384}
385
386// NOLINTBEGIN(cppcoreguidelines-avoid-goto)
387void View::service()
388{
389retry:
390 try
391 {
392 processEvent(event: Relaunch{});
393
394 assert(currentState() == State::initial);
395 processEvent(event: CreateServer{});
396
397 assert(currentState() == State::active);
398 awaitNotification2Proceed();
399 processEvent(event: GoViewing{});
400
401 assert(currentState() == State::established);
402 notificationLoop();
403 if (inResetting.load())
404 {
405 goto retry;
406 }
407 processEvent(event: DestroyServer{});
408
409 assert(currentState() == State::active);
410 processEvent(event: NoViewing{});
411
412 assert(currentState() == State::inactive);
413 }
414 catch (const std::exception& err)
415 {
416 LOG_ERR << "Suspend the " << name << " during " << static_cast<State>(currentState()) << " state. "
417 << err.what();
418
419 processEvent(event: Standby{});
420 if (awaitNotification2Retry())
421 {
422 goto retry;
423 }
424 }
425}
426// NOLINTEND(cppcoreguidelines-avoid-goto)
427
428void View::Access::startup() const
429try
430{
431 waitOr(state: State::active, handling: []() { throw std::runtime_error{"The " + View::name + " did not setup successfully ..."}; });
432 notifyVia(action: [this]() { inst->isOngoing.store(i: true); });
433 waitOr(
434 state: State::established,
435 handling: []() { throw std::runtime_error{"The " + View::name + " did not start successfully ..."}; });
436}
437catch (const std::exception& err)
438{
439 LOG_ERR << err.what();
440}
441
442void View::Access::shutdown() const
443try
444{
445 notifyVia(action: [this]() { inst->isOngoing.store(i: false); });
446 waitOr(state: State::inactive, handling: []() { throw std::runtime_error{"The " + View::name + " did not stop successfully ..."}; });
447}
448catch (const std::exception& err)
449{
450 LOG_ERR << err.what();
451}
452
453void View::Access::reload() const
454try
455{
456 notifyVia(action: [this]() { inst->inResetting.store(i: true); });
457 countdownIf(
458 condition: [this]() { return inst->inResetting.load(); },
459 handling: [this]()
460 {
461 throw std::runtime_error{
462 "The " + View::name + " did not reset properly in " + std::to_string(val: inst->timeoutPeriod) + " ms ..."};
463 });
464}
465catch (const std::exception& err)
466{
467 LOG_ERR << err.what();
468}
469
470bool View::Access::onParsing(char* const bytes, const std::size_t size) const
471{
472 data::decryptMessage(buffer: bytes, length: size);
473 tlv::TLVValue value{};
474 if (const auto ec = tlv::decodeTLV(buf: bytes, len: size, val&: value); ec)
475 {
476 throw std::runtime_error{
477 "Invalid message content \"" + data::toHexString(buffer: bytes, length: size) + "\" (" + ec.message() + ")."};
478 }
479
480 if (const std::size_t actLibLen = ::strnlen(string: value.libInfo, maxlen: sizeof(value.libInfo));
481 (actLibLen != 0) && (actLibLen < sizeof(value.libInfo)))
482 {
483 std::cout << value.libInfo << std::endl;
484 }
485 if (value.bashShmId != tlv::invalidShmId)
486 {
487 printSharedMemory(shmId: value.bashShmId);
488 }
489 if (value.logShmId != tlv::invalidShmId)
490 {
491 printSharedMemory(shmId: value.logShmId, withoutPaging: !inst->isInServingState(state: State::established));
492 }
493 if (value.statusShmId != tlv::invalidShmId)
494 {
495 printSharedMemory(shmId: value.statusShmId);
496 }
497 if (const std::size_t actConfigLen = ::strnlen(string: value.configInfo, maxlen: sizeof(value.configInfo));
498 (actConfigLen != 0) && (actConfigLen < sizeof(value.configInfo)))
499 {
500 using utility::json::JSON;
501 std::cout << JSON::load(fmt: value.configInfo) << std::endl;
502 }
503 return !value.stopTag;
504}
505
506void View::Access::waitOr(const State state, const std::function<void()>& handling) const
507{
508 do
509 {
510 if (inst->isInServingState(state: State::idle) && handling)
511 {
512 handling();
513 }
514 std::this_thread::yield();
515 }
516 while (!inst->isInServingState(state));
517}
518
519void View::Access::notifyVia(const std::function<void()>& action) const
520{
521 std::unique_lock<std::mutex> daemonLock(inst->daemonMtx);
522 if (action)
523 {
524 action();
525 }
526 daemonLock.unlock();
527 inst->daemonCond.notify_one();
528}
529
530void View::Access::countdownIf(const std::function<bool()>& condition, const std::function<void()>& handling) const
531{
532 for (const utility::time::Stopwatch timing{}; timing.elapsedTime() <= inst->timeoutPeriod;)
533 {
534 if (!condition || !condition())
535 {
536 return;
537 }
538 std::this_thread::yield();
539 }
540 if (handling)
541 {
542 handling();
543 }
544}
545
546void View::Sync::waitTaskDone() const
547{
548 std::unique_lock<std::mutex> outputLock(inst->outputMtx);
549 inst->outputCompleted.store(i: false);
550
551 const auto maxWaitTime = std::chrono::milliseconds{inst->timeoutPeriod};
552 utility::time::Timer expiryTimer(
553 inst->isInServingState(state: State::established) ? []() {} : []() { Sync().notifyTaskDone(); });
554 expiryTimer.start(interval: maxWaitTime);
555 inst->outputCond.wait(lock&: outputLock, p: [this]() { return inst->outputCompleted.load(); });
556 expiryTimer.stop();
557}
558
559void View::Sync::notifyTaskDone() const
560{
561 std::unique_lock<std::mutex> outputLock(inst->outputMtx);
562 inst->outputCompleted.store(i: true);
563 outputLock.unlock();
564 inst->outputCond.notify_one();
565}
566
567//! @brief Build the TLV packet of the response message to get library information.
568//! @param buffer - TLV packet buffer
569//! @return buffer length
570template <>
571std::size_t View::buildCustomTLVPacket<View::OptDepend>(const Args& /*args*/, Buffer& buffer)
572{
573 tlv::TLVValue val{};
574 std::string extLibraries{};
575#if defined(__GLIBC__) && defined(__GLIBC_MINOR__)
576 extLibraries += "GNU C Library " MACRO_STRINGIFY(__GLIBC__) "." MACRO_STRINGIFY(__GLIBC_MINOR__) "\n";
577#else
578#error Could not find the GNU C Library version.
579#endif
580#if defined(_GLIBCXX_RELEASE) && defined(__GLIBCXX__)
581 extLibraries +=
582 "GNU C++ Standard Library " MACRO_STRINGIFY(_GLIBCXX_RELEASE) " (" MACRO_STRINGIFY(__GLIBCXX__) ")\n";
583#else
584#error Could not find the GNU C++ Standard Library version.
585#endif
586#if defined(__GNU_MP_VERSION) && defined(__GNU_MP_VERSION_MINOR) && defined(__GNU_MP_VERSION_PATCHLEVEL)
587 extLibraries += "GNU MP Library " MACRO_STRINGIFY(__GNU_MP_VERSION) "." MACRO_STRINGIFY(
588 __GNU_MP_VERSION_MINOR) "." MACRO_STRINGIFY(__GNU_MP_VERSION_PATCHLEVEL) "\n";
589#else
590#pragma message("Could not find the GNU MP Library version.")
591#endif
592#if defined(MPFR_VERSION_STRING)
593 extLibraries += "GNU MPFR Library " MPFR_VERSION_STRING "\n";
594#else
595#error Could not find the GNU MPFR Library version.
596#endif
597#if defined(GSL_VERSION)
598 extLibraries += "GNU Scientific Library " GSL_VERSION " (CBLAS)\n";
599#else
600#error Could not find the GNU Scientific Library (CBLAS) version.
601#endif
602#if defined(RL_VERSION_MAJOR) && defined(RL_VERSION_MINOR)
603 extLibraries +=
604 "GNU Readline Library " MACRO_STRINGIFY(RL_VERSION_MAJOR) "." MACRO_STRINGIFY(RL_VERSION_MINOR) "\n";
605#else
606#error Could not find the GNU Readline Library version.
607#endif
608#if defined(LZ4_VERSION_STRING)
609 extLibraries += "LZ4 Library " LZ4_VERSION_STRING "\n";
610#else
611#error Could not find the LZ4 Library version.
612#endif
613#if defined(NCURSES_VERSION)
614 extLibraries += "Ncurses Library " NCURSES_VERSION "\n";
615#else
616#pragma message("Could not find the Ncurses Library version.")
617#endif
618#if defined(OPENSSL_VERSION_STR)
619 extLibraries += "OpenSSL Library " OPENSSL_VERSION_STR "";
620#else
621#error Could not find the OpenSSL Library version.
622#endif
623 std::strncpy(dest: val.libInfo, src: extLibraries.c_str(), n: sizeof(val.libInfo) - 1);
624 val.libInfo[sizeof(val.libInfo) - 1] = '\0';
625 char* const buf = buffer.data();
626 std::size_t len = buffer.size();
627 if (const auto ec = tlv::encodeTLV(buf, len, val); ec)
628 {
629 throw std::runtime_error{
630 "Failed to build packet for the " + std::string{OptDepend::name} + " option (" + ec.message() + ")."};
631 }
632 data::encryptMessage(buffer: buf, length: len);
633 return len;
634}
635
636//! @brief Build the TLV packet of the response message to get bash outputs.
637//! @param args - container of arguments
638//! @param buffer - TLV packet buffer
639//! @return buffer length
640template <>
641std::size_t View::buildCustomTLVPacket<View::OptExecute>(const Args& args, Buffer& buffer)
642{
643 const auto cmd = std::accumulate(
644 first: args.cbegin(),
645 last: args.cend(),
646 init: std::string{},
647 binary_op: [](const auto& acc, const auto& arg) { return acc.empty() ? arg : (acc + ' ' + arg); });
648 if (((cmd.find_first_not_of(c: '\'') == 0) || (cmd.find_last_not_of(c: '\'') == (cmd.length() - 1)))
649 && ((cmd.find_first_not_of(c: '"') == 0) || (cmd.find_last_not_of(c: '"') == (cmd.length() - 1))))
650 {
651 throw std::runtime_error{
652 "Please enter the \"" + std::string{OptExecute::name} + "\" and append with 'CMD' (include quotes)."};
653 }
654
655 char* const buf = buffer.data();
656 std::size_t len = buffer.size();
657 const int shmId = fillSharedMemory(contents: utility::io::executeCommand(command: "/bin/bash -c " + cmd));
658 if (const auto ec = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.bashShmId = shmId}); ec)
659 {
660 throw std::runtime_error{
661 "Failed to build packet for the " + std::string{OptExecute::name} + " option (" + ec.message() + ")."};
662 }
663 data::encryptMessage(buffer: buf, length: len);
664 return len;
665}
666
667//! @brief Build the TLV packet of the response message to get log contents.
668//! @param buffer - TLV packet buffer
669//! @return buffer length
670template <>
671std::size_t View::buildCustomTLVPacket<View::OptJournal>(const Args& /*args*/, Buffer& buffer)
672{
673 char* const buf = buffer.data();
674 std::size_t len = buffer.size();
675 const int shmId = fillSharedMemory(contents: logContentsPreview());
676 if (const auto ec = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.logShmId = shmId}); ec)
677 {
678 throw std::runtime_error{
679 "Failed to build packet for the " + std::string{OptJournal::name} + " option (" + ec.message() + ")."};
680 }
681 data::encryptMessage(buffer: buf, length: len);
682 return len;
683}
684
685//! @brief Build the TLV packet of the response message to get status reports.
686//! @param args - container of arguments
687//! @param buffer - TLV packet buffer
688//! @return buffer length
689template <>
690std::size_t View::buildCustomTLVPacket<View::OptMonitor>(const Args& args, Buffer& buffer)
691{
692 if (!args.empty())
693 {
694 if (const auto& input = args.front(); (input.length() != 1) || (std::isdigit(input.front()) == 0))
695 {
696 throw std::runtime_error{
697 "Please enter the \"" + std::string{OptMonitor::name} + "\" and append with or without NUM (0 to 9)."};
698 }
699 }
700
701 char* const buf = buffer.data();
702 std::size_t len = buffer.size();
703 const int shmId = fillSharedMemory(contents: statusReportsPreview(frame: args.empty() ? 0 : std::stoul(str: args.front())));
704 if (const auto ec = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.statusShmId = shmId}); ec)
705 {
706 throw std::runtime_error{
707 "Failed to build packet for the " + std::string{OptMonitor::name} + " option (" + ec.message() + ")."};
708 }
709 data::encryptMessage(buffer: buf, length: len);
710 return len;
711}
712
713//! @brief Build the TLV packet of the response message to get current configuration.
714//! @param buffer - TLV packet buffer
715//! @return buffer length
716template <>
717std::size_t View::buildCustomTLVPacket<View::OptProfile>(const Args& /*args*/, Buffer& buffer)
718{
719 tlv::TLVValue val{};
720 std::strncpy(
721 dest: val.configInfo,
722 src: (static_cast<const utility::json::JSON&>(configure::retrieveDataRepo())).asUnescapedString().c_str(),
723 n: sizeof(val.configInfo) - 1);
724 val.configInfo[sizeof(val.configInfo) - 1] = '\0';
725 char* const buf = buffer.data();
726 std::size_t len = buffer.size();
727 if (const auto ec = tlv::encodeTLV(buf, len, val); ec)
728 {
729 throw std::runtime_error{
730 "Failed to build packet for the " + std::string{OptProfile::name} + " option (" + ec.message() + ")."};
731 }
732 data::encryptMessage(buffer: buf, length: len);
733 return len;
734}
735
736std::size_t View::buildResponse(const std::string& reqPlaintext, Buffer& respBuffer)
737{
738 return utility::common::patternMatch(
739 var: extractOption(reqPlaintext),
740 cases: [&respBuffer](const OptDepend& opt) { return buildCustomTLVPacket<OptDepend>(opt.args, buffer&: respBuffer); },
741 cases: [&respBuffer](const OptExecute& opt) { return buildCustomTLVPacket<OptExecute>(args: opt.args, buffer&: respBuffer); },
742 cases: [&respBuffer](const OptJournal& opt) { return buildCustomTLVPacket<OptJournal>(opt.args, buffer&: respBuffer); },
743 cases: [&respBuffer](const OptMonitor& opt) { return buildCustomTLVPacket<OptMonitor>(args: opt.args, buffer&: respBuffer); },
744 cases: [&respBuffer](const OptProfile& opt) { return buildCustomTLVPacket<OptProfile>(opt.args, buffer&: respBuffer); },
745 cases: [](const auto& opt)
746 {
747 if (const auto* origPtr = std::addressof(opt); dynamic_cast<const OptBase*>(origPtr))
748 {
749 throw std::runtime_error{
750 "The option is unprocessed due to unregistered or potential registration failures (typeid: "
751 + std::string{typeid(opt).name()} + ")."};
752 }
753 return 0UL;
754 });
755}
756
757View::OptionType View::extractOption(const std::string& reqPlaintext)
758{
759 auto args = splitString(str: reqPlaintext);
760 const auto optName = args.empty() ? std::string{} : args.at(n: 0);
761 switch (utility::common::bkdrHash(str: optName.c_str()))
762 {
763 using utility::common::operator""_bkdrHash;
764 case operator""_bkdrHash(str: OptDepend::name):
765 return OptDepend{};
766 case operator""_bkdrHash(str: OptExecute::name):
767 args.erase(position: args.cbegin());
768 return OptExecute{std::move(args)};
769 case operator""_bkdrHash(str: OptJournal::name):
770 return OptJournal{};
771 case operator""_bkdrHash(str: OptMonitor::name):
772 args.erase(position: args.cbegin());
773 return OptMonitor{std::move(args)};
774 case operator""_bkdrHash(str: OptProfile::name):
775 return OptProfile{};
776 default:
777 break;
778 }
779 return {};
780}
781
782std::vector<std::string> View::splitString(const std::string& str)
783{
784 std::vector<std::string> split{};
785 std::istringstream transfer(str);
786 std::string token{};
787 while (transfer >> token)
788 {
789 split.emplace_back(args&: token);
790 }
791 return split;
792}
793
794std::size_t View::buildAckTLVPacket(Buffer& buffer)
795{
796 char* const buf = buffer.data();
797 std::size_t len = buffer.size();
798 std::ignore = tlv::encodeTLV(buf, len, val: tlv::TLVValue{});
799 data::encryptMessage(buffer: buf, length: len);
800 return len;
801}
802
803std::size_t View::buildFinTLVPacket(Buffer& buffer)
804{
805 char* const buf = buffer.data();
806 std::size_t len = buffer.size();
807 std::ignore = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.stopTag = true});
808 data::encryptMessage(buffer: buf, length: len);
809 return len;
810}
811
812// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
813int View::fillSharedMemory(const std::string_view contents)
814{
815 const int shmId = ::shmget(
816 key: 0, size: sizeof(ShrMemBlock), IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
817 if (shmId == -1)
818 {
819 throw std::runtime_error{"Failed to create shared memory (" + std::to_string(val: shmId) + ")."};
820 }
821 void* const shm = ::shmat(shmid: shmId, shmaddr: nullptr, shmflg: 0);
822 if (!shm)
823 {
824 throw std::runtime_error{"Failed to attach shared memory (" + std::to_string(val: shmId) + ")."};
825 }
826
827 auto* const shrMem = reinterpret_cast<ShrMemBlock*>(shm);
828 for (shrMem->signal.store(i: false);;)
829 {
830 if (shrMem->signal.load())
831 {
832 std::this_thread::yield();
833 continue;
834 }
835
836 std::vector<char> processed(contents.data(), contents.data() + contents.length());
837 data::compressData(cache&: processed);
838 if (processed.size() > sizeof(shrMem->data))
839 {
840 shrMem->signal.store(i: true);
841 break;
842 }
843 data::encryptMessage(buffer: processed.data(), length: processed.size());
844 std::memset(s: shrMem->data, c: 0, n: sizeof(shrMem->data));
845 std::memcpy(dest: shrMem->data, src: processed.data(), n: processed.size() * sizeof(char));
846 shrMem->size = processed.size();
847
848 shrMem->signal.store(i: true);
849 break;
850 }
851 ::shmdt(shmaddr: shm);
852 return shmId;
853}
854
855void View::fetchSharedMemory(const int shmId, std::string& contents)
856{
857 void* const shm = ::shmat(shmid: shmId, shmaddr: nullptr, shmflg: 0);
858 if (!shm)
859 {
860 throw std::runtime_error{"Failed to attach shared memory (" + std::to_string(val: shmId) + ")."};
861 }
862
863 auto* const shrMem = reinterpret_cast<ShrMemBlock*>(shm);
864 for (shrMem->signal.store(i: true);;)
865 {
866 if (!shrMem->signal.load())
867 {
868 std::this_thread::yield();
869 continue;
870 }
871
872 if (shrMem->size > sizeof(shrMem->data))
873 {
874 shrMem->signal.store(i: false);
875 break;
876 }
877 std::vector<char> processed(shrMem->size);
878 shrMem->size = 0;
879 std::memcpy(dest: processed.data(), src: shrMem->data, n: processed.size() * sizeof(char));
880 std::memset(s: shrMem->data, c: 0, n: sizeof(shrMem->data));
881 data::decryptMessage(buffer: processed.data(), length: processed.size());
882 data::decompressData(cache&: processed);
883 contents = std::string{processed.data(), processed.data() + processed.size()};
884
885 shrMem->signal.store(i: false);
886 break;
887 }
888 ::shmdt(shmaddr: shm);
889 ::shmctl(shmid: shmId, IPC_RMID, buf: nullptr);
890}
891// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
892
893void View::printSharedMemory(const int shmId, const bool withoutPaging)
894{
895 std::string output{};
896 fetchSharedMemory(shmId, contents&: output);
897 if (!withoutPaging)
898 {
899 segmentedOutput(cache: output);
900 return;
901 }
902
903 std::istringstream transfer(output.c_str());
904 std::string line{};
905 while (std::getline(is&: transfer, str&: line))
906 {
907 std::cout << line << '\n';
908 }
909 std::cout << "\033[0m" << std::flush;
910}
911
912void View::segmentedOutput(const std::string& cache)
913{
914 constexpr std::uint8_t terminalRows = 24;
915 constexpr std::string_view prompt = "--- Type <CR> for more, c to continue, n to show next page, q to quit ---: ";
916 constexpr std::string_view escapeClear = "\033[1A\033[2K\r";
917 constexpr std::string_view escapeMoveUp = "\n\033[1A\033[";
918 std::istringstream transfer(cache);
919 const std::size_t lineNum =
920 std::count(first: std::istreambuf_iterator<char>(transfer), last: std::istreambuf_iterator<char>{}, value: '\n');
921 transfer.seekg(std::ios::beg);
922
923 bool moreRows = false;
924 bool forcedCancel = false;
925 bool withoutPaging = (lineNum <= terminalRows);
926 std::string line{};
927 std::size_t counter = 0;
928 const auto handling = utility::common::wrapClosure(
929 closure: [&](const std::string& input)
930 {
931 std::cout << escapeClear << std::flush;
932 if (input.empty())
933 {
934 moreRows = true;
935 counter = 0;
936 return true;
937 }
938
939 moreRows = false;
940 switch (utility::common::bkdrHash(str: input.c_str()))
941 {
942 using utility::common::operator""_bkdrHash;
943 case "c"_bkdrHash:
944 withoutPaging = true;
945 break;
946 case "n"_bkdrHash:
947 counter = 0;
948 break;
949 case "q"_bkdrHash:
950 forcedCancel = true;
951 break;
952 default:
953 std::cout << prompt << std::flush;
954 return false;
955 }
956 return true;
957 });
958 while (std::getline(is&: transfer, str&: line) && !forcedCancel)
959 {
960 std::cout << line << '\n';
961 ++counter;
962 if (!withoutPaging && (moreRows || (counter == terminalRows)))
963 {
964 std::cout << prompt << escapeMoveUp << prompt.length() << 'C' << std::flush;
965 utility::io::waitForUserInput(operation: handling);
966 }
967 }
968
969 std::cout << "\033[0m" << std::flush;
970 if (lineNum > terminalRows)
971 {
972 std::cout << std::endl;
973 }
974}
975
976std::string View::logContentsPreview()
977{
978 std::ostringstream transfer{};
979 log::Log::Access().onPreviewing(
980 peeking: [&transfer](const std::string& filePath)
981 {
982 constexpr std::uint16_t lineLimit = 24 * 100;
983 auto logRows = utility::io::readFileLines(filename: filePath, lock: false, reverse: true, limit: lineLimit);
984 std::ranges::for_each(logRows, [](auto& line) { log::changeToLogStyle(line); });
985 std::ranges::copy(logRows, std::ostream_iterator<std::string>(transfer, "\n"));
986 });
987 return std::move(transfer).str();
988}
989
990std::string View::statusReportsPreview(const std::uint16_t frame)
991{
992 if ((frame > 0)
993 && (::system( // NOLINT(cert-env33-c, concurrency-mt-unsafe)
994 command: "command -v eu-stack >/dev/null 2>&1 "
995 "&& grep -qx '0' /proc/sys/kernel/yama/ptrace_scope >/dev/null 2>&1")
996 != EXIT_SUCCESS))
997 {
998 throw std::runtime_error{"Please confirm that the eu-stack program has been installed and "
999 "that the classic ptrace permissions have been set."};
1000 }
1001
1002 const ::pid_t pid = ::getpid();
1003 constexpr std::uint16_t totalLen = 1024;
1004 std::array<char, totalLen> queryStmt{};
1005 std::snprintf(
1006 s: queryStmt.data(), maxlen: queryStmt.size(), format: "ps -T -p %d | awk 'NR>1 {split($0, a, \" \"); print a[2]}'", pid);
1007
1008 constexpr const char* const focusField = "Name|State|Tgid|Pid|PPid|TracerPid|Uid|Gid|VmSize|VmRSS|CoreDumping|"
1009 "Threads|SigQ|voluntary_ctxt_switches|nonvoluntary_ctxt_switches";
1010 const auto queryResult = utility::io::executeCommand(command: queryStmt.data());
1011 std::vector<std::string> statements{};
1012 std::size_t pos = 0;
1013 std::size_t prev = 0;
1014 while ((pos = queryResult.find(c: '\n', pos: prev)) != std::string::npos)
1015 {
1016 const int tid = std::stoi(str: queryResult.substr(pos: prev, n: pos - prev + 1));
1017 std::array<char, totalLen> execStmt{};
1018 if (const int usedLen = std::snprintf(
1019 s: execStmt.data(),
1020 maxlen: execStmt.size(),
1021 format: "/bin/bash -c "
1022 "\"if [[ -f /proc/%d/task/%d/status ]]; then cat /proc/%d/task/%d/status | grep -E '^(%s):'",
1023 pid,
1024 tid,
1025 pid,
1026 tid,
1027 focusField);
1028 frame == 0)
1029 {
1030 std::strncpy(dest: execStmt.data() + usedLen, src: "; fi\"", n: execStmt.size() - usedLen);
1031 execStmt[totalLen - 1] = '\0';
1032 }
1033 else
1034 {
1035 std::snprintf(
1036 s: execStmt.data() + usedLen,
1037 maxlen: execStmt.size() - usedLen,
1038 format: " && echo 'Stack:' "
1039 "&& (timeout --preserve-status -s SIGINT 1 stdbuf -o0 eu-stack -1v -n %u -p %d 2>&1 | grep '#' "
1040 "|| exit 0); fi\"",
1041 frame,
1042 tid);
1043 }
1044 statements.emplace_back(args: execStmt.data());
1045 prev += pos - prev + 1;
1046 }
1047
1048 auto reports = std::accumulate(
1049 first: statements.cbegin(),
1050 last: statements.cend(),
1051 init: std::string{},
1052 binary_op: [](const auto& acc, const auto& stmt)
1053 { return acc.empty() ? utility::io::executeCommand(command: stmt) : (acc + '\n' + utility::io::executeCommand(command: stmt)); });
1054 while (!reports.empty() && (reports.back() == '\n'))
1055 {
1056 reports.pop_back();
1057 }
1058 return reports;
1059}
1060
1061//! @brief Renew the TCP server.
1062template <>
1063void View::renewServer<utility::socket::TCPServer>()
1064{
1065 permSessServer = std::make_shared<utility::socket::TCPServer>();
1066 permSessServer->subscribeConnection(
1067 callback: [](const std::shared_ptr<utility::socket::TCPSocket> client) // NOLINT(performance-unnecessary-value-param)
1068 {
1069 const std::weak_ptr weakSock = client;
1070 client->subscribeMessage(
1071 callback: [weakSock](const std::string_view message)
1072 {
1073 auto currSock = weakSock.lock();
1074 if (!currSock || message.empty())
1075 {
1076 return;
1077 }
1078
1079 Buffer respBuffer{};
1080 try
1081 {
1082 const auto reqPlaintext = utility::common::base64Decode(data: message);
1083 currSock->send(
1084 bytes: respBuffer.data(),
1085 size: (reqPlaintext != exitSymbol) ? buildResponse(reqPlaintext, respBuffer)
1086 : buildFinTLVPacket(buffer&: respBuffer));
1087 }
1088 catch (const std::exception& err)
1089 {
1090 LOG_WRN << err.what();
1091 currSock->send(bytes: respBuffer.data(), size: buildAckTLVPacket(buffer&: respBuffer));
1092 }
1093 });
1094 });
1095}
1096
1097//! @brief Renew the UDP server.
1098template <>
1099void View::renewServer<utility::socket::UDPServer>()
1100{
1101 tempSessServer = std::make_shared<utility::socket::UDPServer>();
1102 const std::weak_ptr weakSock = tempSessServer;
1103 tempSessServer->subscribeMessage(
1104 callback: [weakSock](const std::string_view message, const std::string& ip, const std::uint16_t port)
1105 {
1106 auto currSock = weakSock.lock();
1107 if (!currSock || message.empty())
1108 {
1109 return;
1110 }
1111
1112 Buffer respBuffer{};
1113 try
1114 {
1115 const auto reqPlaintext = utility::common::base64Decode(data: message);
1116 currSock->sendTo(
1117 bytes: respBuffer.data(),
1118 size: (reqPlaintext != exitSymbol) ? buildResponse(reqPlaintext, respBuffer)
1119 : buildFinTLVPacket(buffer&: respBuffer),
1120 ip,
1121 port);
1122 }
1123 catch (const std::exception& err)
1124 {
1125 LOG_WRN << err.what();
1126 currSock->sendTo(bytes: respBuffer.data(), size: buildAckTLVPacket(buffer&: respBuffer), ip, port);
1127 }
1128 });
1129}
1130
1131bool View::isInServingState(const State state) const
1132{
1133 return (currentState() == state) && !inResetting.load();
1134}
1135
1136void View::createViewServer()
1137{
1138 renewServer<utility::socket::TCPServer>();
1139 renewServer<utility::socket::UDPServer>();
1140}
1141
1142void View::destroyViewServer()
1143{
1144 if (permSessServer)
1145 {
1146 permSessServer->close();
1147 permSessServer->join();
1148 }
1149 permSessServer.reset();
1150
1151 if (tempSessServer)
1152 {
1153 tempSessServer->close();
1154 tempSessServer->join();
1155 }
1156 tempSessServer.reset();
1157}
1158
1159void View::startViewing()
1160{
1161 if (permSessServer)
1162 {
1163 permSessServer->bind(port: tcpPort);
1164 permSessServer->listen();
1165 permSessServer->accept();
1166 }
1167
1168 if (tempSessServer)
1169 {
1170 tempSessServer->bind(port: udpPort);
1171 tempSessServer->receiveFrom();
1172 }
1173}
1174
1175void View::stopViewing()
1176{
1177 const std::scoped_lock locks(daemonMtx, outputMtx);
1178 isOngoing.store(i: false);
1179 inResetting.store(i: false);
1180 outputCompleted.store(i: true);
1181}
1182
1183void View::doToggle()
1184{
1185 utility::benchmark::escape(p: this);
1186}
1187
1188void View::doRollback()
1189{
1190 const std::scoped_lock locks(daemonMtx, outputMtx);
1191 isOngoing.store(i: false);
1192
1193 destroyViewServer();
1194
1195 inResetting.store(i: false);
1196 outputCompleted.store(i: true);
1197}
1198
1199void View::notificationLoop()
1200{
1201 while (isOngoing.load())
1202 {
1203 std::unique_lock<std::mutex> daemonLock(daemonMtx);
1204 daemonCond.wait(lock&: daemonLock, p: [this]() { return !isOngoing.load() || inResetting.load(); });
1205 if (inResetting.load())
1206 {
1207 break;
1208 }
1209
1210 if (MACRO_IMPLIES(permSessServer, permSessServer->stopRequested())
1211 || MACRO_IMPLIES(tempSessServer, tempSessServer->stopRequested()))
1212 {
1213 throw std::runtime_error{"Found that the server did not work as expected."};
1214 }
1215 }
1216}
1217
1218void View::awaitNotification2Proceed()
1219{
1220 std::unique_lock<std::mutex> daemonLock(daemonMtx);
1221 daemonCond.wait(lock&: daemonLock, p: [this]() { return isOngoing.load(); });
1222}
1223
1224bool View::awaitNotification2Retry()
1225{
1226 std::unique_lock<std::mutex> daemonLock(daemonMtx);
1227 daemonCond.wait(lock&: daemonLock);
1228 return inResetting.load();
1229}
1230
1231//! @brief The operator (<<) overloading of the State enum.
1232//! @param os - output stream object
1233//! @param state - current state
1234//! @return reference of the output stream object
1235std::ostream& operator<<(std::ostream& os, const View::State state)
1236{
1237 using enum View::State;
1238 switch (state)
1239 {
1240 case initial:
1241 os << "INITIAL";
1242 break;
1243 case active:
1244 os << "ACTIVE";
1245 break;
1246 case established:
1247 os << "ESTABLISHED";
1248 break;
1249 case inactive:
1250 os << "INACTIVE";
1251 break;
1252 case idle:
1253 os << "IDLE";
1254 break;
1255 default:
1256 os << "UNKNOWN (" << static_cast<std::underlying_type_t<View::State>>(state) << ')';
1257 break;
1258 }
1259 return os;
1260}
1261} // namespace application::view
1262