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-2025 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 // __has_include(<gsl/gsl_version.h>)
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 // __has_include(<gmp.h>)
22#include <lz4.h>
23#include <mpfr.h>
24#if __has_include(<ncurses.h>)
25#include <ncurses.h>
26#endif // __has_include(<ncurses.h>)
27#include <cassert>
28#include <iterator>
29#include <numeric>
30#else
31#include "application/pch/precompiled_header.hpp"
32#endif // _PRECOMPILED_HEADER
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 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 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 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 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 int temp = ::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(
432 state: State::active, handling: [this]() { throw std::runtime_error{"The " + inst->name + " did not setup successfully ..."}; });
433 notifyVia(action: [this]() { inst->isOngoing.store(i: true); });
434 waitOr(
435 state: State::established,
436 handling: [this]() { throw std::runtime_error{"The " + inst->name + " did not start successfully ..."}; });
437}
438catch (const std::exception& err)
439{
440 LOG_ERR << err.what();
441}
442
443void View::Access::shutdown() const
444try
445{
446 notifyVia(action: [this]() { inst->isOngoing.store(i: false); });
447 waitOr(
448 state: State::inactive,
449 handling: [this]() { throw std::runtime_error{"The " + inst->name + " did not stop successfully ..."}; });
450}
451catch (const std::exception& err)
452{
453 LOG_ERR << err.what();
454}
455
456void View::Access::reload() const
457try
458{
459 notifyVia(action: [this]() { inst->inResetting.store(i: true); });
460 countdownIf(
461 condition: [this]() { return inst->inResetting.load(); },
462 handling: [this]()
463 {
464 throw std::runtime_error{
465 "The " + inst->name + " did not reset properly in " + std::to_string(val: inst->timeoutPeriod) + " ms ..."};
466 });
467}
468catch (const std::exception& err)
469{
470 LOG_ERR << err.what();
471}
472
473bool View::Access::onParsing(char* const bytes, const std::size_t size) const
474{
475 data::decryptMessage(buffer: bytes, length: size);
476 tlv::TLVValue value{};
477 if (const auto ec = tlv::decodeTLV(buf: bytes, len: size, val&: value); ec)
478 {
479 throw std::runtime_error{
480 "Invalid message content \"" + data::toHexString(buffer: bytes, length: size) + "\" (" + ec.message() + ")."};
481 }
482
483 if (const std::size_t actLibLen = ::strnlen(string: value.libInfo, maxlen: sizeof(value.libInfo));
484 (actLibLen != 0) && (actLibLen < sizeof(value.libInfo)))
485 {
486 std::cout << value.libInfo << std::endl;
487 }
488 if (value.bashShmId != tlv::invalidShmId)
489 {
490 printSharedMemory(shmId: value.bashShmId);
491 }
492 if (value.logShmId != tlv::invalidShmId)
493 {
494 printSharedMemory(shmId: value.logShmId, withoutPaging: !inst->isInServingState(state: State::established));
495 }
496 if (value.statusShmId != tlv::invalidShmId)
497 {
498 printSharedMemory(shmId: value.statusShmId);
499 }
500 if (const std::size_t actConfigLen = ::strnlen(string: value.configInfo, maxlen: sizeof(value.configInfo));
501 (actConfigLen != 0) && (actConfigLen < sizeof(value.configInfo)))
502 {
503 using utility::json::JSON;
504 std::cout << JSON::load(fmt: value.configInfo) << std::endl;
505 }
506 return !value.stopTag;
507}
508
509void View::Access::waitOr(const State state, const std::function<void()>& handling) const
510{
511 do
512 {
513 if (inst->isInServingState(state: State::idle) && handling)
514 {
515 handling();
516 }
517 std::this_thread::yield();
518 }
519 while (!inst->isInServingState(state));
520}
521
522void View::Access::notifyVia(const std::function<void()>& action) const
523{
524 std::unique_lock<std::mutex> daemonLock(inst->daemonMtx);
525 if (action)
526 {
527 action();
528 }
529 daemonLock.unlock();
530 inst->daemonCond.notify_one();
531}
532
533void View::Access::countdownIf(const std::function<bool()>& condition, const std::function<void()>& handling) const
534{
535 for (const utility::time::Stopwatch timing{}; timing.elapsedTime() <= inst->timeoutPeriod;)
536 {
537 if (!condition || !condition())
538 {
539 return;
540 }
541 std::this_thread::yield();
542 }
543 if (handling)
544 {
545 handling();
546 }
547}
548
549void View::Sync::waitTaskDone() const
550{
551 std::unique_lock<std::mutex> outputLock(inst->outputMtx);
552 inst->outputCompleted.store(i: false);
553
554 const auto maxWaitTime = std::chrono::milliseconds{inst->timeoutPeriod};
555 utility::time::Timer expiryTimer(
556 inst->isInServingState(state: State::established) ? []() {} : []() { Sync().notifyTaskDone(); });
557 expiryTimer.start(interval: maxWaitTime);
558 inst->outputCond.wait(lock&: outputLock, p: [this]() { return inst->outputCompleted.load(); });
559 expiryTimer.stop();
560}
561
562void View::Sync::notifyTaskDone() const
563{
564 std::unique_lock<std::mutex> outputLock(inst->outputMtx);
565 inst->outputCompleted.store(i: true);
566 outputLock.unlock();
567 inst->outputCond.notify_one();
568}
569
570//! @brief Build the TLV packet of the response message to get library information.
571//! @param buffer - TLV packet buffer
572//! @return buffer length
573template <>
574std::size_t View::buildCustomTLVPacket<View::OptDepend>(const Args& /*args*/, Buffer& buffer)
575{
576 tlv::TLVValue val{};
577 std::string extLibraries{};
578#if defined(__GLIBC__) && defined(__GLIBC_MINOR__)
579 extLibraries += "GNU C Library " MACRO_STRINGIFY(__GLIBC__) "." MACRO_STRINGIFY(__GLIBC_MINOR__) "\n";
580#else
581#error Could not find the GNU C Library version.
582#endif // defined(__GLIBC__) && defined(__GLIBC_MINOR__)
583#if defined(_GLIBCXX_RELEASE) && defined(__GLIBCXX__)
584 extLibraries +=
585 "GNU C++ Standard Library " MACRO_STRINGIFY(_GLIBCXX_RELEASE) " (" MACRO_STRINGIFY(__GLIBCXX__) ")\n";
586#else
587#error Could not find the GNU C++ Standard Library version.
588#endif // defined(_GLIBCXX_RELEASE) && defined(__GLIBCXX__)
589#if defined(__GNU_MP_VERSION) && defined(__GNU_MP_VERSION_MINOR) && defined(__GNU_MP_VERSION_PATCHLEVEL)
590 extLibraries += "GNU MP Library " MACRO_STRINGIFY(__GNU_MP_VERSION) "." MACRO_STRINGIFY(
591 __GNU_MP_VERSION_MINOR) "." MACRO_STRINGIFY(__GNU_MP_VERSION_PATCHLEVEL) "\n";
592#else
593#error Could not find the GNU MP Library version.
594#endif // defined(__GNU_MP_VERSION) && defined(__GNU_MP_VERSION_MINOR) && defined(__GNU_MP_VERSION_PATCHLEVEL)
595#if defined(MPFR_VERSION_STRING)
596 extLibraries += "GNU MPFR Library " MPFR_VERSION_STRING "\n";
597#else
598#error Could not find the GNU MPFR Library version.
599#endif // defined(MPFR_VERSION_STRING)
600#if defined(GSL_VERSION)
601 extLibraries += "GNU Scientific Library " GSL_VERSION " (CBLAS)\n";
602#else
603#error Could not find the GNU Scientific Library (CBLAS) version.
604#endif // defined(GSL_VERSION)
605#if defined(RL_VERSION_MAJOR) && defined(RL_VERSION_MINOR)
606 extLibraries +=
607 "GNU Readline Library " MACRO_STRINGIFY(RL_VERSION_MAJOR) "." MACRO_STRINGIFY(RL_VERSION_MINOR) "\n";
608#else
609#error Could not find the GNU Readline Library version.
610#endif // defined(RL_VERSION_MAJOR) && defined(RL_VERSION_MINOR)
611#if defined(LZ4_VERSION_STRING)
612 extLibraries += "LZ4 Library " LZ4_VERSION_STRING "\n";
613#else
614#error Could not find the LZ4 Library version.
615#endif // defined(LZ4_VERSION_STRING)
616#if defined(NCURSES_VERSION)
617 extLibraries += "Ncurses Library " NCURSES_VERSION "\n";
618#else
619#error Could not find the Ncurses Library version.
620#endif // defined(NCURSES_VERSION)
621#if defined(OPENSSL_VERSION_STR)
622 extLibraries += "OpenSSL Library " OPENSSL_VERSION_STR "";
623#else
624#error Could not find the OpenSSL Library version.
625#endif // defined(OPENSSL_VERSION_STR)
626 std::strncpy(dest: val.libInfo, src: extLibraries.c_str(), n: sizeof(val.libInfo) - 1);
627 val.libInfo[sizeof(val.libInfo) - 1] = '\0';
628 char* const buf = buffer.data();
629 std::size_t len = buffer.size();
630 if (const auto ec = tlv::encodeTLV(buf, len, val); ec)
631 {
632 throw std::runtime_error{
633 "Failed to build packet for the " + std::string{OptDepend::name} + " option (" + ec.message() + ")."};
634 }
635 data::encryptMessage(buffer: buf, length: len);
636 return len;
637}
638
639//! @brief Build the TLV packet of the response message to get bash outputs.
640//! @param args - container of arguments
641//! @param buffer - TLV packet buffer
642//! @return buffer length
643template <>
644std::size_t View::buildCustomTLVPacket<View::OptExecute>(const Args& args, Buffer& buffer)
645{
646 const auto cmd = std::accumulate(
647 first: args.cbegin(),
648 last: args.cend(),
649 init: std::string{},
650 binary_op: [](const auto& acc, const auto& arg) { return acc.empty() ? arg : (acc + ' ' + arg); });
651 if (((cmd.find_first_not_of(c: '\'') == 0) || (cmd.find_last_not_of(c: '\'') == (cmd.length() - 1)))
652 && ((cmd.find_first_not_of(c: '"') == 0) || (cmd.find_last_not_of(c: '"') == (cmd.length() - 1))))
653 {
654 throw std::runtime_error{"Please enter the \"execute\" and append with 'CMD' (include quotes)."};
655 }
656
657 char* const buf = buffer.data();
658 std::size_t len = buffer.size();
659 const int shmId = fillSharedMemory(contents: utility::io::executeCommand(command: "/bin/bash -c " + cmd));
660 if (const auto ec = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.bashShmId = shmId}); ec)
661 {
662 throw std::runtime_error{
663 "Failed to build packet for the " + std::string{OptExecute::name} + " option (" + ec.message() + ")."};
664 }
665 data::encryptMessage(buffer: buf, length: len);
666 return len;
667}
668
669//! @brief Build the TLV packet of the response message to get log contents.
670//! @param buffer - TLV packet buffer
671//! @return buffer length
672template <>
673std::size_t View::buildCustomTLVPacket<View::OptJournal>(const Args& /*args*/, Buffer& buffer)
674{
675 char* const buf = buffer.data();
676 std::size_t len = buffer.size();
677 const int shmId = fillSharedMemory(contents: logContentsPreview());
678 if (const auto ec = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.logShmId = shmId}); ec)
679 {
680 throw std::runtime_error{
681 "Failed to build packet for the " + std::string{OptJournal::name} + " option (" + ec.message() + ")."};
682 }
683 data::encryptMessage(buffer: buf, length: len);
684 return len;
685}
686
687//! @brief Build the TLV packet of the response message to get status reports.
688//! @param args - container of arguments
689//! @param buffer - TLV packet buffer
690//! @return buffer length
691template <>
692std::size_t View::buildCustomTLVPacket<View::OptMonitor>(const Args& args, Buffer& buffer)
693{
694 if (!args.empty())
695 {
696 if (const auto& input = args.front(); (input.length() != 1) || !std::isdigit(input.front()))
697 {
698 throw std::runtime_error{"Please enter the \"monitor\" and append with or without NUM (0 to 9)."};
699 }
700 }
701
702 char* const buf = buffer.data();
703 std::size_t len = buffer.size();
704 const int shmId = fillSharedMemory(contents: statusReportsPreview(frame: args.empty() ? 0 : std::stoul(str: args.front())));
705 if (const auto ec = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.statusShmId = shmId}); ec)
706 {
707 throw std::runtime_error{
708 "Failed to build packet for the " + std::string{OptMonitor::name} + " option (" + ec.message() + ")."};
709 }
710 data::encryptMessage(buffer: buf, length: len);
711 return len;
712}
713
714//! @brief Build the TLV packet of the response message to get current configuration.
715//! @param buffer - TLV packet buffer
716//! @return buffer length
717template <>
718std::size_t View::buildCustomTLVPacket<View::OptProfile>(const Args& /*args*/, Buffer& buffer)
719{
720 tlv::TLVValue val{};
721 std::strncpy(
722 dest: val.configInfo,
723 src: (static_cast<const utility::json::JSON&>(configure::retrieveDataRepo())).asUnescapedString().c_str(),
724 n: sizeof(val.configInfo) - 1);
725 val.configInfo[sizeof(val.configInfo) - 1] = '\0';
726 char* const buf = buffer.data();
727 std::size_t len = buffer.size();
728 if (const auto ec = tlv::encodeTLV(buf, len, val); ec)
729 {
730 throw std::runtime_error{
731 "Failed to build packet for the " + std::string{OptProfile::name} + " option (" + ec.message() + ")."};
732 }
733 data::encryptMessage(buffer: buf, length: len);
734 return len;
735}
736
737std::size_t View::buildResponse(const std::string& reqPlaintext, Buffer& respBuffer)
738{
739 return std::visit(
740 visitor: utility::common::VisitorOverload{
741 [&respBuffer](const OptDepend& opt) { return buildCustomTLVPacket<OptDepend>(opt.args, buffer&: respBuffer); },
742 [&respBuffer](const OptExecute& opt) { return buildCustomTLVPacket<OptExecute>(args: opt.args, buffer&: respBuffer); },
743 [&respBuffer](const OptJournal& opt) { return buildCustomTLVPacket<OptJournal>(opt.args, buffer&: respBuffer); },
744 [&respBuffer](const OptMonitor& opt) { return buildCustomTLVPacket<OptMonitor>(args: opt.args, buffer&: respBuffer); },
745 [&respBuffer](const OptProfile& opt) { return buildCustomTLVPacket<OptProfile>(opt.args, buffer&: respBuffer); },
746 [](const auto& opt)
747 {
748 if (const auto* origPtr = std::addressof(opt); dynamic_cast<const OptBase*>(origPtr))
749 {
750 throw std::runtime_error{
751 "The option is unprocessed due to unregistered or potential registration failures (typeid: "
752 + std::string{typeid(opt).name()} + ")."};
753 }
754 return static_cast<std::size_t>(0);
755 }},
756 variants: extractOption(reqPlaintext));
757}
758
759View::OptionType View::extractOption(const std::string& reqPlaintext)
760{
761 auto args = splitString(str: reqPlaintext);
762 const auto optName = args.empty() ? std::string{} : args.at(n: 0);
763 switch (utility::common::bkdrHash(str: optName.c_str()))
764 {
765 using utility::common::operator""_bkdrHash;
766 case operator""_bkdrHash(str: OptDepend::name):
767 return OptDepend{};
768 case operator""_bkdrHash(str: OptExecute::name):
769 args.erase(position: args.cbegin());
770 return OptExecute{std::move(args)};
771 case operator""_bkdrHash(str: OptJournal::name):
772 return OptJournal{};
773 case operator""_bkdrHash(str: OptMonitor::name):
774 args.erase(position: args.cbegin());
775 return OptMonitor{std::move(args)};
776 case operator""_bkdrHash(str: OptProfile::name):
777 return OptProfile{};
778 default:
779 break;
780 }
781 return {};
782}
783
784std::vector<std::string> View::splitString(const std::string& str)
785{
786 std::vector<std::string> split{};
787 std::istringstream transfer(str);
788 std::string token{};
789 while (transfer >> token)
790 {
791 split.emplace_back(args&: token);
792 }
793 return split;
794}
795
796std::size_t View::buildAckTLVPacket(Buffer& buffer)
797{
798 char* const buf = buffer.data();
799 std::size_t len = buffer.size();
800 std::ignore = tlv::encodeTLV(buf, len, val: tlv::TLVValue{});
801 data::encryptMessage(buffer: buf, length: len);
802 return len;
803}
804
805std::size_t View::buildFinTLVPacket(Buffer& buffer)
806{
807 char* const buf = buffer.data();
808 std::size_t len = buffer.size();
809 std::ignore = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.stopTag = true});
810 data::encryptMessage(buffer: buf, length: len);
811 return len;
812}
813
814// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
815int View::fillSharedMemory(const std::string_view contents)
816{
817 const int shmId = ::shmget(
818 key: 0, size: sizeof(ShrMemBlock), IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
819 if (shmId == -1)
820 {
821 throw std::runtime_error{"Failed to create shared memory (" + std::to_string(val: shmId) + ")."};
822 }
823 void* const shm = ::shmat(shmid: shmId, shmaddr: nullptr, shmflg: 0);
824 if (!shm)
825 {
826 throw std::runtime_error{"Failed to attach shared memory (" + std::to_string(val: shmId) + ")."};
827 }
828
829 auto* const shrMem = reinterpret_cast<ShrMemBlock*>(shm);
830 for (shrMem->signal.store(i: false);;)
831 {
832 if (shrMem->signal.load())
833 {
834 std::this_thread::yield();
835 continue;
836 }
837
838 std::vector<char> processed(contents.data(), contents.data() + contents.length());
839 data::compressData(cache&: processed);
840 if (processed.size() > sizeof(shrMem->data))
841 {
842 shrMem->signal.store(i: true);
843 break;
844 }
845 data::encryptMessage(buffer: processed.data(), length: processed.size());
846 std::memset(s: shrMem->data, c: 0, n: sizeof(shrMem->data));
847 std::memcpy(dest: shrMem->data, src: processed.data(), n: processed.size() * sizeof(char));
848 shrMem->size = processed.size();
849
850 shrMem->signal.store(i: true);
851 break;
852 }
853 ::shmdt(shmaddr: shm);
854 return shmId;
855}
856
857void View::fetchSharedMemory(const int shmId, std::string& contents)
858{
859 void* const shm = ::shmat(shmid: shmId, shmaddr: nullptr, shmflg: 0);
860 if (!shm)
861 {
862 throw std::runtime_error{"Failed to attach shared memory (" + std::to_string(val: shmId) + ")."};
863 }
864
865 auto* const shrMem = reinterpret_cast<ShrMemBlock*>(shm);
866 for (shrMem->signal.store(i: true);;)
867 {
868 if (!shrMem->signal.load())
869 {
870 std::this_thread::yield();
871 continue;
872 }
873
874 if (shrMem->size > sizeof(shrMem->data))
875 {
876 shrMem->signal.store(i: false);
877 break;
878 }
879 std::vector<char> processed(shrMem->size);
880 shrMem->size = 0;
881 std::memcpy(dest: processed.data(), src: shrMem->data, n: processed.size() * sizeof(char));
882 std::memset(s: shrMem->data, c: 0, n: sizeof(shrMem->data));
883 data::decryptMessage(buffer: processed.data(), length: processed.size());
884 data::decompressData(cache&: processed);
885 contents = std::string{processed.data(), processed.data() + processed.size()};
886
887 shrMem->signal.store(i: false);
888 break;
889 }
890 ::shmdt(shmaddr: shm);
891 ::shmctl(shmid: shmId, IPC_RMID, buf: nullptr);
892}
893// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
894
895void View::printSharedMemory(const int shmId, const bool withoutPaging)
896{
897 std::string output{};
898 fetchSharedMemory(shmId, contents&: output);
899 if (!withoutPaging)
900 {
901 segmentedOutput(cache: output);
902 return;
903 }
904
905 std::istringstream transfer(output.c_str());
906 std::string line{};
907 while (std::getline(is&: transfer, str&: line))
908 {
909 std::cout << line << '\n';
910 }
911 std::cout << "\033[0m" << std::flush;
912}
913
914void View::segmentedOutput(const std::string& cache)
915{
916 constexpr std::uint8_t terminalRows = 24;
917 constexpr std::string_view prompt = "--- Type <CR> for more, c to continue, n to show next page, q to quit ---: ";
918 constexpr std::string_view escapeClear = "\x1b[1A\x1b[2K\r";
919 constexpr std::string_view escapeMoveUp = "\n\x1b[1A\x1b[";
920 std::istringstream transfer(cache);
921 const std::size_t lineNum =
922 std::count(first: std::istreambuf_iterator<char>(transfer), last: std::istreambuf_iterator<char>{}, value: '\n');
923 transfer.seekg(std::ios::beg);
924
925 bool moreRows = false;
926 bool forcedCancel = false;
927 bool withoutPaging = (lineNum <= terminalRows);
928 std::string line{};
929 std::size_t counter = 0;
930 const auto handling = utility::common::wrapClosure(
931 closure: [&](const std::string& input)
932 {
933 std::cout << escapeClear << std::flush;
934 if (input.empty())
935 {
936 moreRows = true;
937 counter = 0;
938 return true;
939 }
940
941 moreRows = false;
942 switch (utility::common::bkdrHash(str: input.c_str()))
943 {
944 using utility::common::operator""_bkdrHash;
945 case "c"_bkdrHash:
946 withoutPaging = true;
947 break;
948 case "n"_bkdrHash:
949 counter = 0;
950 break;
951 case "q"_bkdrHash:
952 forcedCancel = true;
953 break;
954 default:
955 std::cout << prompt << std::flush;
956 return false;
957 }
958 return true;
959 });
960 while (std::getline(is&: transfer, str&: line) && !forcedCancel)
961 {
962 std::cout << line << '\n';
963 ++counter;
964 if (!withoutPaging && (moreRows || (counter == terminalRows)))
965 {
966 std::cout << prompt << escapeMoveUp << prompt.length() << 'C' << std::flush;
967 utility::io::waitForUserInput(operation: handling);
968 }
969 }
970
971 std::cout << "\033[0m" << std::flush;
972 if (lineNum > terminalRows)
973 {
974 std::cout << std::endl;
975 }
976}
977
978std::string View::logContentsPreview()
979{
980 std::ostringstream transfer{};
981 log::Log::Access().onPreviewing(
982 peeking: [&transfer](const std::string& filePath)
983 {
984 constexpr std::uint16_t lineLimit = 24 * 100;
985 auto logRows = utility::io::readFileLines(filename: filePath, lock: false, reverse: true, limit: lineLimit);
986 std::for_each(first: logRows.begin(), last: logRows.end(), f: [](auto& line) { log::changeToLogStyle(line); });
987 std::copy(first: logRows.cbegin(), last: logRows.cend(), result: std::ostream_iterator<std::string>(transfer, "\n"));
988 });
989 return std::move(transfer).str();
990}
991
992std::string View::statusReportsPreview(const std::uint16_t frame)
993{
994 if ((frame > 0)
995 && (::system( // NOLINT(cert-env33-c, concurrency-mt-unsafe)
996 command: "which eu-stack >/dev/null 2>&1 "
997 "&& grep -qx '0' /proc/sys/kernel/yama/ptrace_scope >/dev/null 2>&1")
998 != EXIT_SUCCESS))
999 {
1000 throw std::runtime_error{"Please confirm that the eu-stack program has been installed and "
1001 "that the classic ptrace permissions have been set."};
1002 }
1003
1004 const ::pid_t pid = ::getpid();
1005 constexpr std::uint16_t totalLen = 1024;
1006 std::array<char, totalLen> queryStmt{};
1007 std::snprintf(
1008 s: queryStmt.data(), maxlen: queryStmt.size(), format: "ps -T -p %d | awk 'NR>1 {split($0, a, \" \"); print a[2]}'", pid);
1009
1010 constexpr const char* const focusField = "Name|State|Tgid|Pid|PPid|TracerPid|Uid|Gid|VmSize|VmRSS|CoreDumping|"
1011 "Threads|SigQ|voluntary_ctxt_switches|nonvoluntary_ctxt_switches";
1012 const auto queryResult = utility::io::executeCommand(command: queryStmt.data());
1013 std::vector<std::string> statements{};
1014 std::size_t pos = 0;
1015 std::size_t prev = 0;
1016 while ((pos = queryResult.find(c: '\n', pos: prev)) != std::string::npos)
1017 {
1018 const int tid = std::stoi(str: queryResult.substr(pos: prev, n: pos - prev + 1));
1019 std::array<char, totalLen> execStmt{};
1020 if (const int usedLen = std::snprintf(
1021 s: execStmt.data(),
1022 maxlen: execStmt.size(),
1023 format: "/bin/bash -c "
1024 "\"if [[ -f /proc/%d/task/%d/status ]]; then cat /proc/%d/task/%d/status | grep -E '^(%s):'",
1025 pid,
1026 tid,
1027 pid,
1028 tid,
1029 focusField);
1030 frame == 0)
1031 {
1032 std::strncpy(dest: execStmt.data() + usedLen, src: "; fi\"", n: execStmt.size() - usedLen);
1033 execStmt[totalLen - 1] = '\0';
1034 }
1035 else
1036 {
1037 std::snprintf(
1038 s: execStmt.data() + usedLen,
1039 maxlen: execStmt.size() - usedLen,
1040 format: " && echo 'Stack:' "
1041 "&& (timeout --preserve-status --signal=2 1 stdbuf -o0 eu-stack -1v -n %d -p %d 2>&1 | grep '#' "
1042 "|| exit 0); fi\"",
1043 frame,
1044 tid);
1045 }
1046 statements.emplace_back(args: execStmt.data());
1047 prev += pos - prev + 1;
1048 }
1049 return std::accumulate(
1050 first: statements.cbegin(),
1051 last: statements.cend(),
1052 init: std::string{},
1053 binary_op: [](const auto& acc, const auto& stmt)
1054 { return acc.empty() ? utility::io::executeCommand(command: stmt) : (acc + '\n' + utility::io::executeCommand(command: stmt)); });
1055}
1056
1057//! @brief Renew the TCP server.
1058template <>
1059void View::renewServer<utility::socket::TCPServer>()
1060{
1061 tcpServer = std::make_shared<utility::socket::TCPServer>();
1062 tcpServer->subscribeConnection(
1063 callback: [](const std::shared_ptr<utility::socket::TCPSocket> client) // NOLINT(performance-unnecessary-value-param)
1064 {
1065 const std::weak_ptr<utility::socket::TCPSocket> weakSock = client;
1066 client->subscribeMessage(
1067 callback: [weakSock](const std::string_view message)
1068 {
1069 if (message.empty())
1070 {
1071 return;
1072 }
1073 auto newSocket = weakSock.lock();
1074 if (!newSocket)
1075 {
1076 return;
1077 }
1078
1079 Buffer respBuffer{};
1080 try
1081 {
1082 const auto reqPlaintext = utility::common::base64Decode(data: message);
1083 newSocket->toSend(
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 newSocket->toSend(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 udpServer = std::make_shared<utility::socket::UDPServer>();
1102 udpServer->subscribeMessage(
1103 callback: [this](const std::string_view message, const std::string& ip, const std::uint16_t port)
1104 {
1105 if (message.empty())
1106 {
1107 return;
1108 }
1109
1110 Buffer respBuffer{};
1111 try
1112 {
1113 const auto reqPlaintext = utility::common::base64Decode(data: message);
1114 udpServer->toSendTo(
1115 bytes: respBuffer.data(),
1116 size: (reqPlaintext != exitSymbol) ? buildResponse(reqPlaintext, respBuffer)
1117 : buildFinTLVPacket(buffer&: respBuffer),
1118 ip,
1119 port);
1120 }
1121 catch (const std::exception& err)
1122 {
1123 LOG_WRN << err.what();
1124 udpServer->toSendTo(bytes: respBuffer.data(), size: buildAckTLVPacket(buffer&: respBuffer), ip, port);
1125 }
1126 });
1127}
1128
1129bool View::isInServingState(const State state) const
1130{
1131 return (currentState() == state) && !inResetting.load();
1132}
1133
1134void View::createViewServer()
1135{
1136 renewServer<utility::socket::TCPServer>();
1137 renewServer<utility::socket::UDPServer>();
1138}
1139
1140void View::destroyViewServer()
1141{
1142 if (tcpServer)
1143 {
1144 tcpServer->toClose();
1145 tcpServer->toJoin();
1146 }
1147 tcpServer.reset();
1148
1149 if (udpServer)
1150 {
1151 udpServer->toClose();
1152 udpServer->toJoin();
1153 }
1154 udpServer.reset();
1155}
1156
1157void View::startViewing()
1158{
1159 if (tcpServer)
1160 {
1161 tcpServer->toBind(port: tcpPort);
1162 tcpServer->toListen();
1163 tcpServer->toAccept();
1164 }
1165
1166 if (udpServer)
1167 {
1168 udpServer->toBind(port: udpPort);
1169 udpServer->toReceiveFrom();
1170 }
1171}
1172
1173void View::stopViewing()
1174{
1175 const std::scoped_lock locks(daemonMtx, outputMtx);
1176 isOngoing.store(i: false);
1177 inResetting.store(i: false);
1178 outputCompleted.store(i: true);
1179}
1180
1181void View::doToggle()
1182{
1183 utility::benchmark::escape(p: this);
1184}
1185
1186void View::doRollback()
1187{
1188 const std::scoped_lock locks(daemonMtx, outputMtx);
1189 isOngoing.store(i: false);
1190
1191 destroyViewServer();
1192
1193 inResetting.store(i: false);
1194 outputCompleted.store(i: true);
1195}
1196
1197void View::notificationLoop()
1198{
1199 while (isOngoing.load())
1200 {
1201 std::unique_lock<std::mutex> daemonLock(daemonMtx);
1202 daemonCond.wait(lock&: daemonLock, p: [this]() { return !isOngoing.load() || inResetting.load(); });
1203 if (inResetting.load())
1204 {
1205 break;
1206 }
1207 }
1208}
1209
1210void View::awaitNotification2Proceed()
1211{
1212 std::unique_lock<std::mutex> daemonLock(daemonMtx);
1213 daemonCond.wait(lock&: daemonLock, p: [this]() { return isOngoing.load(); });
1214}
1215
1216bool View::awaitNotification2Retry()
1217{
1218 std::unique_lock<std::mutex> daemonLock(daemonMtx);
1219 daemonCond.wait(lock&: daemonLock);
1220 return inResetting.load();
1221}
1222
1223//! @brief The operator (<<) overloading of the State enum.
1224//! @param os - output stream object
1225//! @param state - current state
1226//! @return reference of the output stream object
1227std::ostream& operator<<(std::ostream& os, const View::State state)
1228{
1229 using enum View::State;
1230 switch (state)
1231 {
1232 case initial:
1233 os << "INITIAL";
1234 break;
1235 case active:
1236 os << "ACTIVE";
1237 break;
1238 case established:
1239 os << "ESTABLISHED";
1240 break;
1241 case inactive:
1242 os << "INACTIVE";
1243 break;
1244 case idle:
1245 os << "IDLE";
1246 break;
1247 default:
1248 os << "UNKNOWN (" << static_cast<std::underlying_type_t<View::State>>(state) << ')';
1249 break;
1250 }
1251 return os;
1252}
1253} // namespace application::view
1254