Skip to content

Commit beecb3e

Browse files
authored
Merge pull request #169 from protegrity/av_snappy_04
Refactor to make separation of decoding / compression / encryption more clear
2 parents 9bf39b6 + 118eb44 commit beecb3e

8 files changed

Lines changed: 589 additions & 341 deletions

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ target_link_libraries(dbps_common_lib PUBLIC tcb_span)
109109
# Server components library
110110
add_library(dbps_server_lib STATIC
111111
src/server/encryption_sequencer.cpp
112+
src/server/decoding_utils.cpp
112113
)
113114
target_link_libraries(dbps_server_lib PUBLIC dbps_common_lib)
114115
target_include_directories(dbps_server_lib PUBLIC
@@ -222,6 +223,7 @@ if(BUILD_TESTS)
222223
# Decoding utils tests
223224
add_executable(decoding_utils_test src/server/decoding_utils_test.cpp)
224225
target_link_libraries(decoding_utils_test
226+
dbps_server_lib
225227
dbps_common_lib
226228
gtest_main
227229
)

src/server/compression_utils.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
#pragma once
19+
20+
#include <vector>
21+
#include <cstdint>
22+
#include "enums.h"
23+
#include "enum_utils.h"
24+
#include "exceptions.h"
25+
26+
using namespace dbps::external;
27+
using namespace dbps::enum_utils;
28+
29+
/**
30+
* Compress bytes using the compression codec.
31+
*
32+
* @param bytes The bytes to compress
33+
* @param compression The compression codec to use
34+
* @return Compressed bytes, or original bytes if UNCOMPRESSED
35+
* @throws DBPSUnsupportedException if the compression codec is not supported
36+
*/
37+
inline std::vector<uint8_t> Compress(const std::vector<uint8_t>& bytes, CompressionCodec::type compression) {
38+
if (compression == CompressionCodec::UNCOMPRESSED) {
39+
return bytes;
40+
}
41+
// Note for future implementations: If compression fails because of invalid or corrupt input,
42+
// then throw an InvalidInputException.
43+
throw DBPSUnsupportedException(
44+
"Unsupported compression codec: " + std::string(to_string(compression)));
45+
}
46+
47+
/**
48+
* Decompress bytes using the compression codec.
49+
*
50+
* @param bytes The bytes to decompress
51+
* @param compression The compression codec that was used to compress the bytes
52+
* @return Decompressed bytes, or original bytes if UNCOMPRESSED
53+
* @throws DBPSUnsupportedException if the compression codec is not supported
54+
*/
55+
inline std::vector<uint8_t> Decompress(const std::vector<uint8_t>& bytes, CompressionCodec::type compression) {
56+
if (compression == CompressionCodec::UNCOMPRESSED) {
57+
return bytes;
58+
}
59+
// Note for future implementations: If decompression fails because of invalid or corrupt input,
60+
// then throw an InvalidInputException.
61+
throw DBPSUnsupportedException(
62+
"Unsupported compression codec: " + std::string(to_string(compression)));
63+
}
64+

src/server/decoding_utils.cpp

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
#include "decoding_utils.h"
19+
#include "enum_utils.h"
20+
#include <iostream>
21+
#include <cstring>
22+
#include <sstream>
23+
#include <iomanip>
24+
#include <array>
25+
26+
using namespace dbps::external;
27+
using namespace dbps::enum_utils;
28+
29+
int CalculateLevelBytesLength(const std::vector<uint8_t>& raw,
30+
const std::map<std::string, std::variant<int32_t, bool, std::string>>& encoding_attribs) {
31+
32+
// Helper function to skip V1 RLE level data in raw bytes
33+
// Returns number of bytes consumed: [4-byte len] + [level bytes indicated by `len`]
34+
auto SkipV1RLELevel = [&raw](size_t& offset) -> int {
35+
if (offset + 4 > raw.size()) {
36+
throw InvalidInputException(
37+
"Invalid RLE level data: offset + 4 exceeds data size (offset=" +
38+
std::to_string(offset) + ", size=" + std::to_string(raw.size()) + ")");
39+
}
40+
uint32_t len = *reinterpret_cast<const uint32_t*>(raw.data() + offset);
41+
if (offset + 4 + len > raw.size()) {
42+
throw InvalidInputException(
43+
"Invalid RLE level data: length field overflows (offset=" +
44+
std::to_string(offset) + ", len=" + std::to_string(len) + ", size=" +
45+
std::to_string(raw.size()) + ")");
46+
}
47+
offset += 4 + len;
48+
return 4 + len;
49+
};
50+
51+
// Get page_type from the converted attributes
52+
const std::string& page_type = std::get<std::string>(encoding_attribs.at("page_type"));
53+
int total_level_bytes = 0;
54+
55+
if (page_type == "DATA_PAGE_V2") {
56+
// For DATA_PAGE_V2: sum of definition and repetition level byte lengths
57+
int32_t def_level_length = std::get<int32_t>(encoding_attribs.at("page_v2_definition_levels_byte_length"));
58+
int32_t rep_level_length = std::get<int32_t>(encoding_attribs.at("page_v2_repetition_levels_byte_length"));
59+
total_level_bytes = def_level_length + rep_level_length;
60+
std::cout << "CalculateLevelBytesLength DATA_PAGE_V2: total_level_bytes=" << total_level_bytes << std::endl;
61+
62+
} else if (page_type == "DATA_PAGE_V1") {
63+
// Check that encoding types are RLE (instead of BIT_PACKED which is deprecated)
64+
const std::string& rep_encoding = std::get<std::string>(encoding_attribs.at("page_v1_repetition_level_encoding"));
65+
const std::string& def_encoding = std::get<std::string>(encoding_attribs.at("page_v1_definition_level_encoding"));
66+
if (rep_encoding != "RLE" || def_encoding != "RLE") {
67+
throw InvalidInputException(
68+
"Invalid encoding for DATA_PAGE_V1: repetition_level_encoding=" + rep_encoding +
69+
", definition_level_encoding=" + def_encoding + " (only RLE is expected)");
70+
}
71+
72+
// if max_rep_level > 0, there are repetition levels bytes. Same for definition levels.
73+
int32_t max_rep_level = std::get<int32_t>(encoding_attribs.at("data_page_max_repetition_level"));
74+
int32_t max_def_level = std::get<int32_t>(encoding_attribs.at("data_page_max_definition_level"));
75+
size_t offset = 0;
76+
if (max_rep_level > 0) {
77+
int bytes_skipped = SkipV1RLELevel(offset);
78+
total_level_bytes += bytes_skipped;
79+
std::cout << "CalculateLevelBytesLength DATA_PAGE_V1: repetition level bytes skipped=" << bytes_skipped << std::endl;
80+
}
81+
if (max_def_level > 0) {
82+
int bytes_skipped = SkipV1RLELevel(offset);
83+
total_level_bytes += bytes_skipped;
84+
std::cout << "CalculateLevelBytesLength DATA_PAGE_V1: definition level bytes skipped=" << bytes_skipped << std::endl;
85+
}
86+
87+
} else if (page_type == "DICTIONARY_PAGE") {
88+
// DICTIONARY_PAGE has no level bytes
89+
total_level_bytes = 0;
90+
91+
} else {
92+
// Unknown page type
93+
throw DBPSUnsupportedException("Unsupported page type: " + page_type);
94+
}
95+
96+
// Validate that the total level bytes before returning.
97+
if (total_level_bytes < 0) {
98+
throw InvalidInputException(
99+
"Invalid level bytes calculation: negative total_level_bytes=" + std::to_string(total_level_bytes));
100+
}
101+
if (total_level_bytes > static_cast<int>(raw.size())) {
102+
throw InvalidInputException(
103+
"Invalid level bytes calculation: total_level_bytes=" + std::to_string(total_level_bytes) +
104+
" exceeds data size=" + std::to_string(raw.size()));
105+
}
106+
return total_level_bytes;
107+
}
108+
109+
LevelAndValueBytes Split(const std::vector<uint8_t>& bytes, int index) {
110+
LevelAndValueBytes result;
111+
112+
if (index < 0 || index > static_cast<int>(bytes.size())) {
113+
throw InvalidInputException("Invalid index for splitting bytes: " + std::to_string(index));
114+
}
115+
result.level_bytes = std::vector<uint8_t>(bytes.begin(), bytes.begin() + index);
116+
result.value_bytes = std::vector<uint8_t>(bytes.begin() + index, bytes.end());
117+
118+
return result;
119+
}
120+
121+
template<typename T>
122+
std::vector<T> DecodeFixedSizeType(const uint8_t* raw_data, size_t raw_size, const char* name) {
123+
if ((raw_size % sizeof(T)) != 0) {
124+
throw InvalidInputException(std::string("Invalid data size for ") + name + " decoding");
125+
}
126+
std::vector<T> result;
127+
const T* v = reinterpret_cast<const T*>(raw_data);
128+
size_t count = raw_size / sizeof(T);
129+
result.assign(v, v + count);
130+
return result;
131+
}
132+
133+
TypedListValues ParseValueBytesIntoTypedList(
134+
const std::vector<uint8_t>& bytes,
135+
Type::type datatype,
136+
const std::optional<int>& datatype_length,
137+
Format::type format) {
138+
if (format != Format::PLAIN) {
139+
throw DBPSUnsupportedException("Unsupported format: " + std::string(to_string(format)));
140+
}
141+
switch (datatype) {
142+
case Type::INT32: {
143+
return DecodeFixedSizeType<int32_t>(bytes.data(), bytes.size(), "INT32");
144+
}
145+
case Type::INT64: {
146+
return DecodeFixedSizeType<int64_t>(bytes.data(), bytes.size(), "INT64");
147+
}
148+
case Type::FLOAT: {
149+
return DecodeFixedSizeType<float>(bytes.data(), bytes.size(), "FLOAT");
150+
}
151+
case Type::DOUBLE: {
152+
return DecodeFixedSizeType<double>(bytes.data(), bytes.size(), "DOUBLE");
153+
}
154+
case Type::INT96: {
155+
if ((bytes.size() % 12) != 0) {
156+
throw InvalidInputException("Invalid data size for INT96 decoding");
157+
}
158+
std::vector<std::array<uint32_t, 3>> result;
159+
const uint8_t* p = bytes.data();
160+
const uint8_t* last_byte = bytes.data() + bytes.size();
161+
while (p + 12 <= last_byte) {
162+
std::array<uint32_t, 3> value;
163+
memcpy(&value[0], p + 0, 4);
164+
memcpy(&value[1], p + 4, 4);
165+
memcpy(&value[2], p + 8, 4);
166+
result.push_back(value);
167+
p += 12;
168+
}
169+
return result;
170+
}
171+
case Type::BYTE_ARRAY: {
172+
std::vector<std::string> result;
173+
const uint8_t* p = bytes.data();
174+
const uint8_t* last_byte = bytes.data() + bytes.size();
175+
while (p + 4 <= last_byte) {
176+
uint32_t len;
177+
memcpy(&len, p, sizeof(len));
178+
p += 4;
179+
if (p + len > last_byte) {
180+
throw InvalidInputException(
181+
"Invalid BYTE_ARRAY encoding: length exceeds data bounds");
182+
}
183+
const char* s = reinterpret_cast<const char*>(p);
184+
result.emplace_back(s, len);
185+
p += len;
186+
}
187+
if (p != last_byte) {
188+
throw InvalidInputException(
189+
"Invalid BYTE_ARRAY encoding: unexpected trailing bytes");
190+
}
191+
return result;
192+
}
193+
case Type::FIXED_LEN_BYTE_ARRAY: {
194+
if (!datatype_length.has_value() || datatype_length.value() <= 0) {
195+
throw InvalidInputException(
196+
"FIXED_LEN_BYTE_ARRAY requires positive datatype_length");
197+
}
198+
int fixed_length = datatype_length.value();
199+
if ((bytes.size() % fixed_length) != 0) {
200+
throw InvalidInputException(
201+
"Invalid data size for FIXED_LEN_BYTE_ARRAY decoding");
202+
}
203+
std::vector<std::string> result;
204+
size_t element_count = bytes.size() / fixed_length;
205+
for (size_t i = 0; i < element_count; ++i) {
206+
const char* element_start = reinterpret_cast<const char*>(
207+
bytes.data() + i * fixed_length);
208+
result.emplace_back(element_start, fixed_length);
209+
}
210+
return result;
211+
}
212+
case Type::UNDEFINED: {
213+
std::vector<uint8_t> result;
214+
const char* s = reinterpret_cast<const char*>(bytes.data());
215+
result.assign(s, s + bytes.size());
216+
return result;
217+
}
218+
default: {
219+
throw DBPSUnsupportedException(
220+
"Unsupported datatype: " + std::string(to_string(datatype)));
221+
}
222+
}
223+
}
224+
225+
template<typename T>
226+
const char* GetTypeName() {
227+
if constexpr (std::is_same_v<T, std::vector<int32_t>>) return "INT32";
228+
else if constexpr (std::is_same_v<T, std::vector<int64_t>>) return "INT64";
229+
else if constexpr (std::is_same_v<T, std::vector<float>>) return "FLOAT";
230+
else if constexpr (std::is_same_v<T, std::vector<double>>) return "DOUBLE";
231+
else if constexpr (std::is_same_v<T, std::vector<std::array<uint32_t, 3>>>) return "INT96";
232+
else if constexpr (std::is_same_v<T, std::vector<std::string>>)
233+
return "string (BYTE_ARRAY/FIXED_LEN_BYTE_ARRAY)";
234+
else if constexpr (std::is_same_v<T, std::vector<uint8_t>>) return "UNDEFINED (raw bytes)";
235+
else if constexpr (std::is_same_v<T, std::monostate>) return "empty/error";
236+
else return "unknown";
237+
}
238+
239+
std::string PrintTypedList(const TypedListValues& list) {
240+
std::ostringstream out;
241+
242+
std::visit([&out](auto&& values) {
243+
using T = std::decay_t<decltype(values)>;
244+
245+
if constexpr (std::is_same_v<T, std::monostate>) {
246+
out << "Empty/error state\n";
247+
}
248+
else if constexpr (std::is_same_v<T, std::vector<std::array<uint32_t, 3>>>) {
249+
// Special case for INT96 - [lo, mid, hi] format
250+
out << "Decoded INT96 values ([lo, mid, hi] 32-bit words):\n";
251+
for (size_t i = 0; i < values.size(); ++i) {
252+
out << " [" << i << "] [" << values[i][0] << ", "
253+
<< values[i][1] << ", " << values[i][2] << "]\n";
254+
}
255+
}
256+
else if constexpr (std::is_same_v<T, std::vector<uint8_t>>) {
257+
// Special case for UNDEFINED - raw bytes as hex
258+
out << "Decoded UNDEFINED type (raw bytes):\n";
259+
out << " Hex: ";
260+
for (size_t i = 0; i < values.size(); ++i) {
261+
out << std::hex << std::setw(2) << std::setfill('0')
262+
<< static_cast<int>(values[i]);
263+
if (i < values.size() - 1) out << " ";
264+
}
265+
out << std::dec << "\n"; // Reset to decimal
266+
267+
// Also show as string if printable
268+
out << " String: \"";
269+
for (uint8_t byte : values) {
270+
if (byte >= 32 && byte < 127) {
271+
out << static_cast<char>(byte);
272+
} else {
273+
out << ".";
274+
}
275+
}
276+
out << "\"\n";
277+
}
278+
else if constexpr (std::is_same_v<T, std::vector<std::string>>) {
279+
// String values with quotes and the length of the string.
280+
out << "Decoded " << GetTypeName<T>() << " values:\n";
281+
for (size_t i = 0; i < values.size(); ++i) {
282+
out << " [" << i << "] \"" << values[i] << "\" (length: " << values[i].size() << ")\n";
283+
}
284+
}
285+
else {
286+
// Generic case for numeric types (int32, int64, float, double)
287+
out << "Decoded " << GetTypeName<T>() << " values:\n";
288+
for (size_t i = 0; i < values.size(); ++i) {
289+
out << " [" << i << "] " << values[i] << "\n";
290+
}
291+
}
292+
}, list);
293+
294+
return out.str();
295+
}

0 commit comments

Comments
 (0)