| 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 | |
| 37 | namespace application::view // NOLINT(modernize-concat-nested-namespaces) |
| 38 | { |
| 39 | //! @brief Type-length-value scheme. |
| 40 | namespace tlv |
| 41 | { |
| 42 | //! @brief Invalid shared memory id. |
| 43 | constexpr int invalidShmId = -1; |
| 44 | //! @brief Default information size. |
| 45 | constexpr std::uint16_t defInfoSize = 256; |
| 46 | |
| 47 | //! @brief Enumerate the types in TLV. |
| 48 | enum TLVType : int |
| 49 | { |
| 50 | //! @brief Header. |
| 51 | = 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. |
| 67 | struct 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 |
| 89 | template <typename Data> |
| 90 | static 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 |
| 109 | template <std::size_t Size> |
| 110 | static 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 |
| 129 | template <typename Data> |
| 130 | static 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 |
| 149 | template <std::size_t Size> |
| 150 | static 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. |
| 164 | enum 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 | , |
| 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. |
| 183 | class ErrCategory : public std::error_category |
| 184 | { |
| 185 | public: |
| 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 | |
| 228 | template <> |
| 229 | struct std::is_error_code_enum<application::view::tlv::ErrCode> : public std::true_type |
| 230 | { |
| 231 | }; |
| 232 | |
| 233 | namespace application::view |
| 234 | { |
| 235 | namespace 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 |
| 242 | static 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 |
| 307 | static 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 | |
| 372 | View::View() : FSM(State::initial) |
| 373 | { |
| 374 | if (!configure::detail::activateHelper()) [[unlikely]] |
| 375 | { |
| 376 | throw std::logic_error{"The " + name + " is disabled." }; |
| 377 | } |
| 378 | } |
| 379 | |
| 380 | std::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) |
| 387 | void View::service() |
| 388 | { |
| 389 | retry: |
| 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 | |
| 428 | void View::Access::startup() const |
| 429 | try |
| 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 | } |
| 438 | catch (const std::exception& err) |
| 439 | { |
| 440 | LOG_ERR << err.what(); |
| 441 | } |
| 442 | |
| 443 | void View::Access::shutdown() const |
| 444 | try |
| 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 | } |
| 451 | catch (const std::exception& err) |
| 452 | { |
| 453 | LOG_ERR << err.what(); |
| 454 | } |
| 455 | |
| 456 | void View::Access::reload() const |
| 457 | try |
| 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 | } |
| 468 | catch (const std::exception& err) |
| 469 | { |
| 470 | LOG_ERR << err.what(); |
| 471 | } |
| 472 | |
| 473 | bool 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 | |
| 509 | void 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 | |
| 522 | void 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 | |
| 533 | void 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 | |
| 549 | void 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 | |
| 562 | void 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 |
| 573 | template <> |
| 574 | std::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 |
| 643 | template <> |
| 644 | std::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 |
| 672 | template <> |
| 673 | std::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 |
| 691 | template <> |
| 692 | std::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 |
| 717 | template <> |
| 718 | std::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 | |
| 737 | std::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 | |
| 759 | View::OptionType View::(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 | |
| 784 | std::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 | |
| 796 | std::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 | |
| 805 | std::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) |
| 815 | int 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 | |
| 857 | void 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 | |
| 895 | void 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 | |
| 914 | void 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 | |
| 978 | std::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 | |
| 992 | std::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. |
| 1058 | template <> |
| 1059 | void 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. |
| 1098 | template <> |
| 1099 | void 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 | |
| 1129 | bool View::isInServingState(const State state) const |
| 1130 | { |
| 1131 | return (currentState() == state) && !inResetting.load(); |
| 1132 | } |
| 1133 | |
| 1134 | void View::createViewServer() |
| 1135 | { |
| 1136 | renewServer<utility::socket::TCPServer>(); |
| 1137 | renewServer<utility::socket::UDPServer>(); |
| 1138 | } |
| 1139 | |
| 1140 | void 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 | |
| 1157 | void 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 | |
| 1173 | void 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 | |
| 1181 | void View::doToggle() |
| 1182 | { |
| 1183 | utility::benchmark::escape(p: this); |
| 1184 | } |
| 1185 | |
| 1186 | void 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 | |
| 1197 | void 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 | |
| 1210 | void View::awaitNotification2Proceed() |
| 1211 | { |
| 1212 | std::unique_lock<std::mutex> daemonLock(daemonMtx); |
| 1213 | daemonCond.wait(lock&: daemonLock, p: [this]() { return isOngoing.load(); }); |
| 1214 | } |
| 1215 | |
| 1216 | bool 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 |
| 1227 | std::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 | |