Skip to content

Commit 641dfcd

Browse files
authored
Merge pull request #185 from protegrity/av_sequncer_decryption_023
Implement CompressionAndJoin to complete the Decryption sequence
2 parents de3c1fa + 8e0d1fd commit 641dfcd

4 files changed

Lines changed: 204 additions & 10 deletions

File tree

src/server/encryption_sequencer.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,15 +208,13 @@ bool DataBatchEncryptionSequencer::ConvertAndDecrypt(const std::vector<uint8_t>&
208208
auto [encrypted_level_bytes, encrypted_value_bytes] = SplitWithLengthPrefix(decompressed_encrypted_bytes);
209209
auto level_bytes = encryptor_->DecryptBlock(encrypted_level_bytes);
210210
auto typed_list = encryptor_->DecryptValueList(encrypted_value_bytes);
211-
212-
213-
throw DBPSUnsupportedException("Per-value decryption implementation is not complete.");
214211

215212
// Convert the decrypted typed list back to value bytes
216213
auto value_bytes = GetTypedListAsValueBytes(typed_list, datatype_, datatype_length_, format_);
217214

218215
// Join the decrypted level and value bytes, then compress to get plaintext
219-
decrypted_result_ = CompressAndJoin(level_bytes, value_bytes);
216+
decrypted_result_ = CompressAndJoin(
217+
level_bytes, value_bytes, compression_, encoding_attributes_converted_);
220218
}
221219

222220
// Per-block encryption

src/server/parquet_utils.cpp

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,41 @@ LevelAndValueBytes DecompressAndSplit(
277277

278278
std::vector<uint8_t> CompressAndJoin(
279279
const std::vector<uint8_t>& level_bytes,
280-
const std::vector<uint8_t>& value_bytes) {
281-
throw DBPSUnsupportedException("CompressAndJoin not implemented");
280+
const std::vector<uint8_t>& value_bytes,
281+
CompressionCodec::type compression,
282+
const AttributesMap& encoding_attributes) {
283+
284+
// Get the page type from the encoding attributes.
285+
const auto& page_type = std::get<std::string>(encoding_attributes.at("page_type"));
286+
287+
// Check that the calculated level bytes size == the size of the actual level bytes.
288+
int expected_level_bytes = CalculateLevelBytesLength(level_bytes, encoding_attributes);
289+
if (static_cast<size_t>(expected_level_bytes) != level_bytes.size()) {
290+
throw InvalidInputException("Level bytes size does not match encoding attributes");
291+
}
292+
293+
if (page_type == "DATA_PAGE_V1") {
294+
auto joined = Join(level_bytes, value_bytes);
295+
return Compress(joined, compression);
296+
}
297+
298+
if (page_type == "DATA_PAGE_V2") {
299+
bool page_v2_is_compressed =
300+
std::get<bool>(encoding_attributes.at("page_v2_is_compressed"));
301+
if (page_v2_is_compressed) {
302+
auto compressed_values = Compress(value_bytes, compression);
303+
return Join(level_bytes, compressed_values);
304+
} else {
305+
return Join(level_bytes, value_bytes);
306+
}
307+
}
308+
309+
// DICTIONARY_PAGE has no level bytes.
310+
if (page_type == "DICTIONARY_PAGE") {
311+
return Compress(value_bytes, compression);
312+
}
313+
314+
throw InvalidInputException("Unexpected page type: " + page_type);
282315
}
283316

284317
TypedListValues ParseValueBytesIntoTypedList(

src/server/parquet_utils.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,14 @@ LevelAndValueBytes DecompressAndSplit(
7777
const AttributesMap& encoding_attributes);
7878

7979
/**
80-
* Merges level and value bytes and compresses them into plaintext.
81-
* Handles different page types (DATA_PAGE_V1, DATA_PAGE_V2, DICTIONARY_PAGE) appropriately.
82-
* Returns the joined and compressed plaintext.
80+
* Reverse of DecompressAndSplit: joins level/value bytes and applies compression
81+
* based on page type and encoding attributes.
8382
*/
8483
std::vector<uint8_t> CompressAndJoin(
8584
const std::vector<uint8_t>& level_bytes,
86-
const std::vector<uint8_t>& value_bytes);
85+
const std::vector<uint8_t>& value_bytes,
86+
CompressionCodec::type compression,
87+
const AttributesMap& encoding_attributes);
8788

8889
/**
8990
* Parse the value bytes into a typed list based on the data type and format.

src/server/parquet_utils_test.cpp

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,13 @@ TEST(ParquetUtils, SliceValueBytesIntoRawBytes_FixedSizeMisaligned) {
274274
InvalidInputException);
275275
}
276276

277+
TEST(ParquetUtils, SliceValueBytesIntoRawBytes_UnsupportedFormat) {
278+
std::vector<uint8_t> bytes = {0x01,0x00,0x00,0x00};
279+
EXPECT_THROW(
280+
SliceValueBytesIntoRawBytes(bytes, Type::INT32, std::nullopt, Format::RLE),
281+
DBPSUnsupportedException);
282+
}
283+
277284
TEST(ParquetUtils, CombineRawBytesIntoValueBytes_INT32) {
278285
std::vector<RawValueBytes> elems = {
279286
{0x04,0x03,0x02,0x01},
@@ -309,6 +316,15 @@ TEST(ParquetUtils, CombineRawBytesIntoValueBytes_FIXED_LEN_BYTE_ARRAY_SizeMismat
309316
InvalidInputException);
310317
}
311318

319+
TEST(ParquetUtils, CombineRawBytesIntoValueBytes_UnsupportedFormat) {
320+
std::vector<RawValueBytes> elems = {
321+
{0x04,0x03,0x02,0x01}
322+
};
323+
EXPECT_THROW(
324+
CombineRawBytesIntoValueBytes(elems, Type::INT32, std::nullopt, Format::RLE),
325+
DBPSUnsupportedException);
326+
}
327+
312328
TEST(ParquetUtils, SliceAndCombine_RoundTrip_INT64) {
313329
// Two int64 values: little-endian bytes
314330
std::vector<uint8_t> bytes = {
@@ -402,3 +418,149 @@ TEST(ParquetUtils, DecompressAndSplit_DataPageV2_UnsupportedCompression) {
402418
DecompressAndSplit(plaintext, CompressionCodec::LZO, attribs),
403419
DBPSUnsupportedException);
404420
}
421+
422+
TEST(ParquetUtils, CompressAndJoin_DataPageV1_Compressed) {
423+
AttributesMap attribs = {
424+
{"page_type", std::string("DATA_PAGE_V1")},
425+
{"data_page_num_values", int32_t(2)},
426+
{"data_page_max_repetition_level", int32_t(0)},
427+
{"data_page_max_definition_level", int32_t(1)},
428+
{"page_v1_repetition_level_encoding", std::string("RLE")},
429+
{"page_v1_definition_level_encoding", std::string("RLE")}
430+
};
431+
432+
std::vector<uint8_t> level_bytes;
433+
append_u32_le(level_bytes, 2); // RLE block length
434+
level_bytes.insert(level_bytes.end(), {0x0A, 0x0B});
435+
std::vector<uint8_t> value_bytes = {0x21, 0x22, 0x23, 0x24};
436+
437+
auto joined = CompressAndJoin(level_bytes, value_bytes, CompressionCodec::SNAPPY, attribs);
438+
auto expected = Compress(Join(level_bytes, value_bytes), CompressionCodec::SNAPPY);
439+
EXPECT_EQ(joined, expected);
440+
441+
auto decomposed = DecompressAndSplit(joined, CompressionCodec::SNAPPY, attribs);
442+
EXPECT_EQ(decomposed.level_bytes, level_bytes);
443+
EXPECT_EQ(decomposed.value_bytes, value_bytes);
444+
}
445+
446+
TEST(ParquetUtils, CompressAndJoin_DataPageV2_Uncompressed) {
447+
AttributesMap attribs = {
448+
{"page_type", std::string("DATA_PAGE_V2")},
449+
{"data_page_num_values", int32_t(3)},
450+
{"data_page_max_definition_level", int32_t(1)},
451+
{"data_page_max_repetition_level", int32_t(0)},
452+
{"page_v2_definition_levels_byte_length", int32_t(2)},
453+
{"page_v2_repetition_levels_byte_length", int32_t(1)},
454+
{"page_v2_num_nulls", int32_t(0)},
455+
{"page_v2_is_compressed", false}
456+
};
457+
458+
std::vector<uint8_t> level_bytes = {0x10, 0x11, 0x12}; // 2+1
459+
std::vector<uint8_t> value_bytes = {0x21, 0x22, 0x23, 0x24};
460+
461+
auto joined = CompressAndJoin(level_bytes, value_bytes, CompressionCodec::UNCOMPRESSED, attribs);
462+
std::vector<uint8_t> expected = Join(level_bytes, value_bytes);
463+
EXPECT_EQ(joined, expected);
464+
465+
auto decomposed = DecompressAndSplit(joined, CompressionCodec::UNCOMPRESSED, attribs);
466+
EXPECT_EQ(decomposed.level_bytes, level_bytes);
467+
EXPECT_EQ(decomposed.value_bytes, value_bytes);
468+
}
469+
470+
TEST(ParquetUtils, CompressAndJoin_DataPageV2_Compressed_RoundTrip) {
471+
AttributesMap attribs = {
472+
{"page_type", std::string("DATA_PAGE_V2")},
473+
{"data_page_num_values", int32_t(4)},
474+
{"data_page_max_definition_level", int32_t(1)},
475+
{"data_page_max_repetition_level", int32_t(0)},
476+
{"page_v2_definition_levels_byte_length", int32_t(2)},
477+
{"page_v2_repetition_levels_byte_length", int32_t(1)},
478+
{"page_v2_num_nulls", int32_t(0)},
479+
{"page_v2_is_compressed", true}
480+
};
481+
482+
std::vector<uint8_t> level_bytes = {0x10, 0x11, 0x12}; // len matches 2+1
483+
std::vector<uint8_t> value_bytes = {0x21, 0x22, 0x23, 0x24, 0x25};
484+
485+
auto joined = CompressAndJoin(level_bytes, value_bytes, CompressionCodec::SNAPPY, attribs);
486+
auto expected_compressed = Compress(value_bytes, CompressionCodec::SNAPPY);
487+
auto expected_joined = Join(level_bytes, expected_compressed);
488+
EXPECT_EQ(joined, expected_joined);
489+
490+
auto decomposed = DecompressAndSplit(joined, CompressionCodec::SNAPPY, attribs);
491+
492+
EXPECT_EQ(decomposed.level_bytes, level_bytes);
493+
EXPECT_EQ(decomposed.value_bytes, value_bytes);
494+
}
495+
496+
TEST(ParquetUtils, CompressAndJoin_DataPageV2_LevelLengthMismatch) {
497+
AttributesMap attribs = {
498+
{"page_type", std::string("DATA_PAGE_V2")},
499+
{"data_page_num_values", int32_t(2)},
500+
{"data_page_max_definition_level", int32_t(1)},
501+
{"data_page_max_repetition_level", int32_t(0)},
502+
{"page_v2_definition_levels_byte_length", int32_t(2)},
503+
{"page_v2_repetition_levels_byte_length", int32_t(1)},
504+
{"page_v2_num_nulls", int32_t(0)},
505+
{"page_v2_is_compressed", true}
506+
};
507+
508+
std::vector<uint8_t> level_bytes = {0x10, 0x11}; // expected 3 bytes -> mismatch
509+
std::vector<uint8_t> value_bytes = {0x21, 0x22, 0x23};
510+
511+
EXPECT_THROW(
512+
CompressAndJoin(level_bytes, value_bytes, CompressionCodec::SNAPPY, attribs),
513+
InvalidInputException);
514+
}
515+
516+
TEST(ParquetUtils, CompressAndJoin_DictionaryPage) {
517+
AttributesMap attribs = {
518+
{"page_type", std::string("DICTIONARY_PAGE")}
519+
};
520+
521+
std::vector<uint8_t> level_bytes; // must be empty
522+
std::vector<uint8_t> value_bytes = {0x31, 0x32, 0x33};
523+
524+
auto joined = CompressAndJoin(level_bytes, value_bytes, CompressionCodec::UNCOMPRESSED, attribs);
525+
EXPECT_EQ(joined, value_bytes);
526+
}
527+
528+
TEST(ParquetUtils, CompressAndJoin_UnsupportedCompression) {
529+
AttributesMap attribs = {
530+
{"page_type", std::string("DATA_PAGE_V1")},
531+
{"data_page_num_values", int32_t(1)},
532+
{"data_page_max_repetition_level", int32_t(0)},
533+
{"data_page_max_definition_level", int32_t(0)},
534+
{"page_v1_repetition_level_encoding", std::string("RLE")},
535+
{"page_v1_definition_level_encoding", std::string("RLE")}
536+
};
537+
538+
std::vector<uint8_t> level_bytes;
539+
std::vector<uint8_t> value_bytes = {0x40};
540+
541+
EXPECT_THROW(
542+
CompressAndJoin(level_bytes, value_bytes, CompressionCodec::LZO, attribs),
543+
DBPSUnsupportedException);
544+
}
545+
546+
TEST(ParquetUtils, CompressAndJoin_UnsupportedEncoding) {
547+
AttributesMap attribs = {
548+
{"page_type", std::string("DATA_PAGE_V1")},
549+
{"data_page_num_values", int32_t(1)},
550+
{"data_page_max_repetition_level", int32_t(1)}, // triggers repetition level parsing
551+
{"data_page_max_definition_level", int32_t(0)},
552+
{"page_v1_repetition_level_encoding", std::string("BIT_PACKED")}, // unsupported
553+
{"page_v1_definition_level_encoding", std::string("RLE")}
554+
};
555+
556+
// Build minimal level bytes that would be valid if encoding were RLE: len + payload
557+
std::vector<uint8_t> level_bytes;
558+
append_u32_le(level_bytes, 1);
559+
level_bytes.push_back(0x00);
560+
561+
std::vector<uint8_t> value_bytes = {0x01, 0x02};
562+
563+
EXPECT_THROW(
564+
CompressAndJoin(level_bytes, value_bytes, CompressionCodec::UNCOMPRESSED, attribs),
565+
InvalidInputException);
566+
}

0 commit comments

Comments
 (0)