1//! @file data.cpp
2//! @author ryftchen
3//! @brief The definitions (data) in the application module.
4//! @version 0.1.0
5//! @copyright Copyright (c) 2022-2026 ryftchen. All rights reserved.
6
7#include "data.hpp"
8
9#ifndef _PRECOMPILED_HEADER
10#include <openssl/evp.h>
11#include <lz4.h>
12#include <array>
13#include <cstring>
14#include <iomanip>
15#else
16#include "application/pch/precompiled_header.hpp"
17#endif
18
19namespace application::data
20{
21Packet::Packet(char* const pktBuf, const std::size_t pktLen) :
22 head{pktBuf}, tail{head + pktLen}, writer{head}, reader{head}
23{
24 if (!pktBuf)
25 {
26 throw std::runtime_error{"The attempt to create the data packet failed."};
27 }
28}
29
30bool Packet::write(const void* const dst, const std::size_t offset)
31{
32 if (!dst || (offset == 0))
33 {
34 return false;
35 }
36
37 std::memcpy(dest: writer, src: dst, n: offset);
38 writer += offset;
39 return writer < tail;
40}
41
42bool Packet::read(void* const dst, const std::size_t offset)
43{
44 if (!dst || (offset == 0))
45 {
46 return false;
47 }
48
49 std::memcpy(dest: dst, src: reader, n: offset);
50 reader += offset;
51 return reader < tail;
52}
53
54namespace tlv
55{
56//! @brief Enumerate the types in TLV.
57enum TLVType : int
58{
59 //! @brief Header.
60 header = 0x3B9ACA07,
61 //! @brief Stop.
62 stop = 0,
63 //! @brief Depend.
64 depend = 1,
65 //! @brief Execute.
66 execute = 2,
67 //! @brief Journal.
68 journal = 3,
69 //! @brief Monitor.
70 monitor = 4,
71 //! @brief Profile.
72 profile = 5
73};
74
75//! @brief TLV value serialization.
76//! @tparam Data - type of target payload
77//! @param pkt - encoding packet that was filled type
78//! @param val - value of TLV to encode
79//! @param pl - target payload that has been included in the value of TLV
80//! @return summary offset of length-value
81template <typename Data>
82static int serialize(data::Packet& pkt, const TLVValue& val, const Data TLVValue::* const pl)
83{
84 if (!pl)
85 {
86 return 0;
87 }
88
89 constexpr int length = sizeof(Data);
90 pkt.write<int>(data: length);
91 pkt.write<Data>(val.*pl);
92 return static_cast<int>(sizeof(int) + length);
93}
94
95//! @brief TLV value serialization.
96//! @tparam Size - size of target payload
97//! @param pkt - encoding packet that was filled type
98//! @param val - value of TLV to encode
99//! @param pl - target payload that has been included in the value of TLV
100//! @return summary offset of length-value
101template <std::size_t Size>
102static int serialize(data::Packet& pkt, const TLVValue& val, const char (TLVValue::* const pl)[Size])
103{
104 if (!pl)
105 {
106 return 0;
107 }
108
109 const int length = ::strnlen(string: val.*pl, maxlen: Size);
110 pkt.write<int>(data: length);
111 pkt.write(val.*pl, length);
112 return static_cast<int>(sizeof(int) + length);
113}
114
115//! @brief TLV value deserialization.
116//! @tparam Data - type of target payload
117//! @param pkt - decoding packet that was filled type
118//! @param val - value of TLV to decode
119//! @param pl - target payload that has been included in the value of TLV
120//! @return summary offset of length-value
121template <typename Data>
122static int deserialize(data::Packet& pkt, TLVValue& val, Data TLVValue::* const pl)
123{
124 if (!pl)
125 {
126 return 0;
127 }
128
129 int length = 0;
130 pkt.read<int>(data: &length);
131 pkt.read<Data>(&(val.*pl));
132 return static_cast<int>(sizeof(int) + length);
133}
134
135//! @brief TLV value deserialization.
136//! @tparam Size - size of target payload
137//! @param pkt - decoding packet that was filled type
138//! @param val - value of TLV to decode
139//! @param pl - target payload that has been included in the value of TLV
140//! @return summary offset of length-value
141template <std::size_t Size>
142static int deserialize(data::Packet& pkt, TLVValue& val, char (TLVValue::* const pl)[Size])
143{
144 if (!pl)
145 {
146 return 0;
147 }
148
149 int length = 0;
150 pkt.read<int>(data: &length);
151 pkt.read(val.*pl, std::min<std::size_t>(a: length, b: Size));
152 return static_cast<int>(sizeof(int) + length);
153}
154
155//! @brief Enumerate the error codes for TLV error handling.
156enum class ErrCode : std::uint8_t
157{
158 //! @brief No error.
159 noError = 0,
160 //! @brief Null buffer.
161 nullBuffer,
162 //! @brief Insufficient length.
163 insufficientLength,
164 //! @brief Bad header.
165 badHeader,
166 //! @brief Unknown type.
167 unknownType,
168 //! @brief Fail serialize.
169 failSerialize,
170 //! @brief Fail deserialize.
171 failDeserialize,
172};
173
174//! @brief Error category for TLV error handling.
175class ErrCategory : public std::error_category
176{
177public:
178 //! @brief Get the name of the error category.
179 //! @return name of the error category
180 [[nodiscard]] const char* name() const noexcept override { return "TLV error category"; }
181 //! @brief Get the message of the error code value.
182 //! @param value - error code value
183 //! @return message of the error code value
184 [[nodiscard]] std::string message(const int value) const override
185 {
186 switch (static_cast<ErrCode>(value))
187 {
188 case ErrCode::noError:
189 return "no TLV error";
190 case ErrCode::nullBuffer:
191 return "null TLV buffer";
192 case ErrCode::insufficientLength:
193 return "insufficient TLV-length";
194 case ErrCode::badHeader:
195 return "invalid TLV header";
196 case ErrCode::unknownType:
197 return "unknown TLV-type";
198 case ErrCode::failSerialize:
199 return "TLV-value serialization failure";
200 case ErrCode::failDeserialize:
201 return "TLV-value deserialization failure";
202 default:
203 break;
204 }
205 return "unknown TLV error";
206 }
207};
208
209//! @brief Make error code from the custom error code for TLV error handling.
210//! @param errCode - custom error code
211//! @return error code
212[[maybe_unused]] static std::error_code make_error_code(const ErrCode errCode) // NOLINT(readability-identifier-naming)
213{
214 static const ErrCategory errCategory{};
215 return std::error_code{static_cast<int>(errCode), errCategory};
216}
217} // namespace tlv
218} // namespace application::data
219
220template <>
221struct std::is_error_code_enum<application::data::tlv::ErrCode> : public std::true_type
222{
223};
224
225namespace application::data
226{
227namespace tlv
228{
229//! @brief Encode the TLV packet.
230//! @param buf - TLV packet buffer
231//! @param len - buffer length
232//! @param val - value of TLV to encode
233//! @return error code
234std::error_code encodeTLV(char* const buf, std::size_t& len, const TLVValue& val)
235{
236 if (!buf)
237 {
238 return ErrCode::nullBuffer;
239 }
240
241 data::Packet enc(buf, len);
242 int sum = 0;
243 if (!enc.write<int>(data: TLVType::header) || !enc.write<int>(data: sum))
244 {
245 return ErrCode::insufficientLength;
246 }
247
248 const auto serializer = [&enc, &val, &sum](const auto pl) -> std::error_code
249 {
250 if (const int res = serialize(enc, val, pl); res > 0)
251 {
252 sum += sizeof(int) + res;
253 return ErrCode::noError;
254 }
255 return ErrCode::failSerialize;
256 };
257 enc.write<int>(data: TLVType::stop);
258 if (const auto ec = serializer(&TLVValue::stopTag); ec)
259 {
260 return ec;
261 }
262 enc.write<int>(data: TLVType::depend);
263 if (const auto ec = serializer(&TLVValue::libInfo); ec)
264 {
265 return ec;
266 }
267 enc.write<int>(data: TLVType::execute);
268 if (const auto ec = serializer(&TLVValue::bashShmId); ec)
269 {
270 return ec;
271 }
272 enc.write<int>(data: TLVType::journal);
273 if (const auto ec = serializer(&TLVValue::logShmId); ec)
274 {
275 return ec;
276 }
277 enc.write<int>(data: TLVType::monitor);
278 if (const auto ec = serializer(&TLVValue::statusShmId); ec)
279 {
280 return ec;
281 }
282 enc.write<int>(data: TLVType::profile);
283 if (const auto ec = serializer(&TLVValue::configInfo); ec)
284 {
285 return ec;
286 }
287
288 auto temp = static_cast<int>(::htonl(hostlong: sum));
289 std::memcpy(dest: buf + sizeof(int), src: &temp, n: sizeof(temp));
290 len = sizeof(int) + sizeof(int) + sum;
291 return ErrCode::noError;
292}
293
294//! @brief Decode the TLV packet.
295//! @param buf - TLV packet buffer
296//! @param len - buffer length
297//! @param val - value of TLV to decode
298//! @return error code
299std::error_code decodeTLV(char* const buf, const std::size_t len, TLVValue& val)
300{
301 if (!buf || (len == 0))
302 {
303 return ErrCode::nullBuffer;
304 }
305
306 data::Packet dec(buf, len);
307 int type = 0;
308 int sum = 0;
309 if (!dec.read<int>(data: &type) || !dec.read<int>(data: &sum))
310 {
311 return ErrCode::insufficientLength;
312 }
313 if (type != TLVType::header)
314 {
315 return ErrCode::badHeader;
316 }
317
318 const auto deserializer = [&dec, &val, &sum](const auto pl) -> std::error_code
319 {
320 if (const int res = deserialize(dec, val, pl); res > 0)
321 {
322 sum -= sizeof(int) + res;
323 return ErrCode::noError;
324 }
325 return ErrCode::failDeserialize;
326 };
327 while (sum > 0)
328 {
329 dec.read<int>(data: &type);
330 std::error_code ec = ErrCode::noError;
331 switch (type)
332 {
333 case TLVType::stop:
334 ec = deserializer(&TLVValue::stopTag);
335 break;
336 case TLVType::depend:
337 ec = deserializer(&TLVValue::libInfo);
338 break;
339 case TLVType::execute:
340 ec = deserializer(&TLVValue::bashShmId);
341 break;
342 case TLVType::journal:
343 ec = deserializer(&TLVValue::logShmId);
344 break;
345 case TLVType::monitor:
346 ec = deserializer(&TLVValue::statusShmId);
347 break;
348 case TLVType::profile:
349 ec = deserializer(&TLVValue::configInfo);
350 break;
351 default:
352 ec = ErrCode::unknownType;
353 break;
354 }
355 if (ec)
356 {
357 return ec;
358 }
359 }
360 return ErrCode::noError;
361}
362} // namespace tlv
363
364// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
365//! @brief Encrypt the message with AES-128-CFB-128.
366//! @param buffer - message buffer
367//! @param length - buffer length
368void encryptMessage(char* const buffer, const std::size_t length)
369{
370 if (!buffer || (length == 0))
371 {
372 return;
373 }
374
375 auto* const ctx = ::EVP_CIPHER_CTX_new();
376 do
377 {
378 if (constexpr std::array<unsigned char, 16> key =
379 {0x37, 0x47, 0x10, 0x33, 0x6F, 0x18, 0xC8, 0x9A, 0x4B, 0xC1, 0x2B, 0x97, 0x92, 0x19, 0x25, 0x6D},
380 iv = {0x9F, 0x7B, 0x0E, 0x68, 0x2D, 0x2F, 0x4E, 0x7F, 0x1A, 0xFA, 0x61, 0xD3, 0xC6, 0x18, 0xF4, 0xC1};
381 ::EVP_EncryptInit_ex(ctx, cipher: ::EVP_aes_128_cfb128(), impl: nullptr, key: key.data(), iv: iv.data()) == 0)
382 {
383 break;
384 }
385
386 int outLen = 0;
387 if (::EVP_EncryptUpdate(
388 ctx,
389 out: reinterpret_cast<unsigned char*>(buffer),
390 outl: &outLen,
391 in: reinterpret_cast<unsigned char*>(buffer),
392 inl: static_cast<int>(length))
393 == 0)
394 {
395 break;
396 }
397
398 if (int tempLen = 0;
399 ::EVP_EncryptFinal_ex(ctx, out: reinterpret_cast<unsigned char*>(buffer) + outLen, outl: &tempLen) == 0)
400 {
401 break;
402 }
403 }
404 while (false);
405 ::EVP_CIPHER_CTX_free(c: ctx);
406}
407
408//! @brief Decrypt the message with AES-128-CFB-128.
409//! @param buffer - message buffer
410//! @param length - buffer length
411void decryptMessage(char* const buffer, const std::size_t length)
412{
413 if (!buffer || (length == 0))
414 {
415 return;
416 }
417
418 auto* const ctx = ::EVP_CIPHER_CTX_new();
419 do
420 {
421 if (constexpr std::array<unsigned char, 16> key =
422 {0x37, 0x47, 0x10, 0x33, 0x6F, 0x18, 0xC8, 0x9A, 0x4B, 0xC1, 0x2B, 0x97, 0x92, 0x19, 0x25, 0x6D},
423 iv = {0x9F, 0x7B, 0x0E, 0x68, 0x2D, 0x2F, 0x4E, 0x7F, 0x1A, 0xFA, 0x61, 0xD3, 0xC6, 0x18, 0xF4, 0xC1};
424 ::EVP_DecryptInit_ex(ctx, cipher: ::EVP_aes_128_cfb128(), impl: nullptr, key: key.data(), iv: iv.data()) == 0)
425 {
426 break;
427 }
428
429 int outLen = 0;
430 if (::EVP_DecryptUpdate(
431 ctx,
432 out: reinterpret_cast<unsigned char*>(buffer),
433 outl: &outLen,
434 in: reinterpret_cast<unsigned char*>(buffer),
435 inl: static_cast<int>(length))
436 == 0)
437 {
438 break;
439 }
440
441 if (int tempLen = 0;
442 ::EVP_DecryptFinal_ex(ctx, outm: reinterpret_cast<unsigned char*>(buffer) + outLen, outl: &tempLen) == 0)
443 {
444 break;
445 }
446 }
447 while (false);
448 ::EVP_CIPHER_CTX_free(c: ctx);
449}
450// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
451
452//! @brief Compress the data with LZ4.
453//! @param cache - data cache
454void compressData(std::vector<char>& cache)
455{
456 if (cache.empty())
457 {
458 return;
459 }
460
461 const int compressedCap = ::LZ4_compressBound(inputSize: static_cast<int>(cache.size()));
462 std::vector<char> compressed(compressedCap);
463 const int compressedSize =
464 ::LZ4_compress_default(src: cache.data(), dst: compressed.data(), srcSize: static_cast<int>(cache.size()), dstCapacity: compressedCap);
465 if (compressedSize < 0)
466 {
467 throw std::runtime_error{"Failed to compress data, return " + std::to_string(val: compressedSize) + '.'};
468 }
469 compressed.resize(new_size: compressedSize);
470 compressed.shrink_to_fit();
471 cache = std::move(compressed);
472}
473
474//! @brief Decompress the data with LZ4.
475//! @param cache - data cache
476void decompressData(std::vector<char>& cache)
477{
478 if (cache.empty())
479 {
480 return;
481 }
482
483 constexpr int decompressedCap = 65536 * 10 * 10;
484 std::vector<char> decompressed(decompressedCap);
485 const int decompressedSize =
486 ::LZ4_decompress_safe(src: cache.data(), dst: decompressed.data(), compressedSize: static_cast<int>(cache.size()), dstCapacity: decompressedCap);
487 if (decompressedSize < 0)
488 {
489 throw std::runtime_error{"Failed to decompress data, return " + std::to_string(val: decompressedSize) + '.'};
490 }
491 decompressed.resize(new_size: decompressedSize);
492 decompressed.shrink_to_fit();
493 cache = std::move(decompressed);
494}
495
496//! @brief Convert a byte buffer to a space-separated hexadecimal string.
497//! @param buffer - byte buffer
498//! @param length - buffer length
499//! @return hexadecimal string representation of the buffer
500std::string toHexString(const char* const buffer, const std::size_t length)
501{
502 if (!buffer || (length == 0))
503 {
504 return {};
505 }
506
507 std::ostringstream body{};
508 for (std::size_t i = 0; i < length; ++i)
509 {
510 body << "0x" << std::setfill('0') << std::setw(2) << std::hex
511 << static_cast<int>(static_cast<unsigned char>(buffer[i]));
512 if ((i + 1) != length)
513 {
514 body << ' ';
515 }
516 }
517 return body.str();
518}
519} // namespace application::data
520