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-2025 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 // _PRECOMPILED_HEADER
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
54// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
55//! @brief Encrypt the message with AES-128-CFB-128.
56//! @param buffer - message buffer
57//! @param length - buffer length
58void encryptMessage(char* const buffer, const std::size_t length)
59{
60 if (!buffer || (length == 0))
61 {
62 return;
63 }
64
65 auto* const ctx = ::EVP_CIPHER_CTX_new();
66 do
67 {
68 if (constexpr std::array<unsigned char, 16> key =
69 {0x37, 0x47, 0x10, 0x33, 0x6F, 0x18, 0xC8, 0x9A, 0x4B, 0xC1, 0x2B, 0x97, 0x92, 0x19, 0x25, 0x6D},
70 iv = {0x9F, 0x7B, 0x0E, 0x68, 0x2D, 0x2F, 0x4E, 0x7F, 0x1A, 0xFA, 0x61, 0xD3, 0xC6, 0x18, 0xF4, 0xC1};
71 ::EVP_EncryptInit_ex(ctx, cipher: ::EVP_aes_128_cfb128(), impl: nullptr, key: key.data(), iv: iv.data()) == 0)
72 {
73 break;
74 }
75
76 int outLen = 0;
77 if (::EVP_EncryptUpdate(
78 ctx,
79 out: reinterpret_cast<unsigned char*>(buffer),
80 outl: &outLen,
81 in: reinterpret_cast<unsigned char*>(buffer),
82 inl: length)
83 == 0)
84 {
85 break;
86 }
87
88 if (int tempLen = 0;
89 ::EVP_EncryptFinal_ex(ctx, out: reinterpret_cast<unsigned char*>(buffer) + outLen, outl: &tempLen) == 0)
90 {
91 break;
92 }
93 }
94 while (false);
95 ::EVP_CIPHER_CTX_free(c: ctx);
96}
97
98//! @brief Decrypt the message with AES-128-CFB-128.
99//! @param buffer - message buffer
100//! @param length - buffer length
101void decryptMessage(char* const buffer, const std::size_t length)
102{
103 if (!buffer || (length == 0))
104 {
105 return;
106 }
107
108 auto* const ctx = ::EVP_CIPHER_CTX_new();
109 do
110 {
111 if (constexpr std::array<unsigned char, 16> key =
112 {0x37, 0x47, 0x10, 0x33, 0x6F, 0x18, 0xC8, 0x9A, 0x4B, 0xC1, 0x2B, 0x97, 0x92, 0x19, 0x25, 0x6D},
113 iv = {0x9F, 0x7B, 0x0E, 0x68, 0x2D, 0x2F, 0x4E, 0x7F, 0x1A, 0xFA, 0x61, 0xD3, 0xC6, 0x18, 0xF4, 0xC1};
114 ::EVP_DecryptInit_ex(ctx, cipher: ::EVP_aes_128_cfb128(), impl: nullptr, key: key.data(), iv: iv.data()) == 0)
115 {
116 break;
117 }
118
119 int outLen = 0;
120 if (::EVP_DecryptUpdate(
121 ctx,
122 out: reinterpret_cast<unsigned char*>(buffer),
123 outl: &outLen,
124 in: reinterpret_cast<unsigned char*>(buffer),
125 inl: length)
126 == 0)
127 {
128 break;
129 }
130
131 if (int tempLen = 0;
132 ::EVP_DecryptFinal_ex(ctx, outm: reinterpret_cast<unsigned char*>(buffer) + outLen, outl: &tempLen) == 0)
133 {
134 break;
135 }
136 }
137 while (false);
138 ::EVP_CIPHER_CTX_free(c: ctx);
139}
140// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
141
142//! @brief Compress the data with LZ4.
143//! @param cache - data cache
144void compressData(std::vector<char>& cache)
145{
146 if (cache.empty())
147 {
148 return;
149 }
150
151 const int compressedCap = ::LZ4_compressBound(inputSize: cache.size());
152 std::vector<char> compressed(compressedCap);
153 const int compressedSize = ::LZ4_compress_default(src: cache.data(), dst: compressed.data(), srcSize: cache.size(), dstCapacity: compressedCap);
154 if (compressedSize < 0)
155 {
156 throw std::runtime_error{"Failed to compress data, return " + std::to_string(val: compressedSize) + '.'};
157 }
158 compressed.resize(new_size: compressedSize);
159 compressed.shrink_to_fit();
160 cache = std::move(compressed);
161}
162
163//! @brief Decompress the data with LZ4.
164//! @param cache - data cache
165void decompressData(std::vector<char>& cache)
166{
167 if (cache.empty())
168 {
169 return;
170 }
171
172 constexpr int decompressedCap = 65536 * 10 * 10;
173 std::vector<char> decompressed(decompressedCap);
174 const int decompressedSize =
175 ::LZ4_decompress_safe(src: cache.data(), dst: decompressed.data(), compressedSize: cache.size(), dstCapacity: decompressedCap);
176 if (decompressedSize < 0)
177 {
178 throw std::runtime_error{"Failed to decompress data, return " + std::to_string(val: decompressedSize) + '.'};
179 }
180 decompressed.resize(new_size: decompressedSize);
181 decompressed.shrink_to_fit();
182 cache = std::move(decompressed);
183}
184
185//! @brief Convert a byte buffer to a space-separated hexadecimal string.
186//! @param buffer - byte buffer
187//! @param length - buffer length
188//! @return hexadecimal string representation of the buffer
189std::string toHexString(const char* const buffer, const std::size_t length)
190{
191 if (!buffer || (length == 0))
192 {
193 return {};
194 }
195
196 std::ostringstream body{};
197 for (std::size_t i = 0; i < length; ++i)
198 {
199 body << "0x" << std::setfill('0') << std::setw(2) << std::hex
200 << static_cast<int>(static_cast<unsigned char>(buffer[i]));
201 if ((i + 1) != length)
202 {
203 body << ' ';
204 }
205 }
206 return body.str();
207}
208} // namespace application::data
209