From d6f8e4c11fabe34ac3f0634a8ee1b1a2f706023a Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Mon, 8 Dec 2025 09:32:36 -0600 Subject: [PATCH 1/7] - On Encryption Sequencer, declare explicitly the conditions to allow fallback to per-block encryption. - Cleanup use of DBPSUnsupportedException across util functions. --- src/server/compression_utils.cpp | 1 + src/server/encryption_sequencer.cpp | 35 +++++++++++++++++++++---- src/server/encryptors/basic_encryptor.h | 1 - src/server/parquet_utils.cpp | 30 ++++++++++++++++----- 4 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/server/compression_utils.cpp b/src/server/compression_utils.cpp index 0c6b90a..7b8ba29 100644 --- a/src/server/compression_utils.cpp +++ b/src/server/compression_utils.cpp @@ -23,6 +23,7 @@ using namespace dbps::enum_utils; namespace dbps::compression { +// TODO(Issue #188): Add support for other compressions. std::vector Compress(const std::vector& bytes, CompressionCodec::type compression) { if (compression == CompressionCodec::UNCOMPRESSED) { return bytes; diff --git a/src/server/encryption_sequencer.cpp b/src/server/encryption_sequencer.cpp index 27d4f97..10278f2 100644 --- a/src/server/encryption_sequencer.cpp +++ b/src/server/encryption_sequencer.cpp @@ -140,24 +140,49 @@ bool DataBatchEncryptionSequencer::ConvertAndEncrypt(const std::vector& // Compress the joined encrypted bytes encrypted_result_ = Compress(joined_encrypted_bytes, encrypted_compression_); - } catch (const DBPSUnsupportedException& e) { - // If any stage is as of yet unsupported, default to whole payload (per-block) encryption - // (as opposed to per-value) + } + // If the sequence was interrupted by a DBPSUnsupportedException, allow fallback to per-block encryption + // but only for explicitly unsupported conditions. + // Any conditions that are already supported should not fallback. In those cases, the exception is re-thrown. + catch (const DBPSUnsupportedException& e) { + + // Compression: Only UNCOMPRESSED and SNAPPY are supported + // TODO(Issue #188): Add support for other compressions. + const bool compression_supported = (compression_ == CompressionCodec::UNCOMPRESSED || + compression_ == CompressionCodec::SNAPPY); + + // Format: Only PLAIN is supported + // TODO(Issue #187): Add support for other encodings. + const bool format_supported = (format_ == Format::PLAIN); + + // Page type: All are supported (DATA_PAGE_V1, DATA_PAGE_V2, DICTIONARY_PAGE) + const bool page_supported = true; + + // Datatype: All datatypes are supported. + const bool datatype_supported = true; + + if (compression_supported && format_supported && page_supported && datatype_supported) { + // All conditions are supported, therefore an DBPSUnsupportedException exception should not have happened. + // Re-throw the exception. + throw; + } + + // Fallback: Use per-block encryption for unsupported combinations. encrypted_result_ = encryptor_->EncryptBlock(plaintext); if (encrypted_result_.empty()) { error_stage_ = "encryption"; error_message_ = "Failed to encrypt data"; return false; } - // If the sequence was interrupted by a DBPSUnsupportedException at any point, use per block encryption. - // Set the encryption type to per block. encryption_metadata_[ENCRYPTION_MODE] = ENCRYPTION_PER_BLOCK; encryption_metadata_[DBPS_VERSION_KEY] = DBPS_VERSION; return true; + } catch (const InvalidInputException& e) { // Throw the exception so it can be caught by the caller. throw; } + // If the sequencer got here, it means the encryption of the values in the typed list finished successfully. // Set the encryption type to per value encryption_metadata_[ENCRYPTION_MODE] = ENCRYPTION_PER_VALUE; diff --git a/src/server/encryptors/basic_encryptor.h b/src/server/encryptors/basic_encryptor.h index d70e8e7..ce6a7d9 100644 --- a/src/server/encryptors/basic_encryptor.h +++ b/src/server/encryptors/basic_encryptor.h @@ -28,7 +28,6 @@ * * This implementation provides: * - Block encryption/decryption using XOR with key_id hash (same as current encryption_sequencer) - * - Value encryption/decryption currently throws DBPSUnsupportedException (same as current encryption_sequencer) * * This is a simple, default encryption implementation that can be replaced with more * sophisticated encryption providers (e.g., Protegrity) in the future. diff --git a/src/server/parquet_utils.cpp b/src/server/parquet_utils.cpp index 665f63f..9f937e7 100644 --- a/src/server/parquet_utils.cpp +++ b/src/server/parquet_utils.cpp @@ -92,8 +92,8 @@ int CalculateLevelBytesLength(const std::vector& raw, total_level_bytes = 0; } else { - // Unknown page type - throw DBPSUnsupportedException("Unsupported page type: " + page_type); + // Invalid page type + throw InvalidInputException("Invalid page type: " + page_type); } // Validate that the total level bytes before returning. @@ -130,10 +130,10 @@ inline static size_t GetFixedElemSizeOrThrow(Type::type datatype, const std::opt case Type::UNDEFINED: return 1; case Type::BYTE_ARRAY: - throw DBPSUnsupportedException("BYTE_ARRAY is variable-length; not fixed-size"); + throw InvalidInputException("BYTE_ARRAY is variable-length; not fixed-size"); default: - throw DBPSUnsupportedException( - "Unsupported datatype: " + std::string(to_string(datatype))); + throw InvalidInputException( + "Invalid datatype. Only fixed-size types are supported: " + std::string(to_string(datatype))); } } @@ -142,8 +142,16 @@ std::vector SliceValueBytesIntoRawBytes( Type::type datatype, const std::optional& datatype_length, Format::type format) { + + // RLE_DICTIONARY is not supported for per-value operations since the values themselves are not present in the data, + // only references to them. + if (format == Format::RLE_DICTIONARY) { + throw DBPSUnsupportedException("Unsupported format: RLE_DICTIONARY is not supported for per-value operations"); + } + + // TODO(Issue #187): Add support for other encodings. if (format != Format::PLAIN) { - throw DBPSUnsupportedException("Unsupported format: " + std::string(to_string(format))); + throw DBPSUnsupportedException("On SliceValueBytesIntoRawBytes, unsupported format: " + std::string(to_string(format))); } // Variable-length BYTE_ARRAY: parse [4-byte len][bytes...] elements in order. @@ -193,8 +201,16 @@ std::vector CombineRawBytesIntoValueBytes( Type::type datatype, const std::optional& datatype_length, Format::type format) { + + // RLE_DICTIONARY is not supported for per-value operations since the values themselves are not present in the data, + // only references to them. + if (format == Format::RLE_DICTIONARY) { + throw DBPSUnsupportedException("Unsupported format: RLE_DICTIONARY is not supported for per-value operations"); + } + + // TODO(Issue #187): Add support for other encodings. if (format != Format::PLAIN) { - throw DBPSUnsupportedException("Unsupported format: " + std::string(to_string(format))); + throw DBPSUnsupportedException("On CombineRawBytesIntoValueBytes, unsupported format: " + std::string(to_string(format))); } if (datatype == Type::BYTE_ARRAY) { From 3e267f6851f2c06e8d3fa3928e569d2e1e662595 Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Mon, 8 Dec 2025 09:46:11 -0600 Subject: [PATCH 2/7] - Update to a unittest. --- src/server/parquet_utils_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/parquet_utils_test.cpp b/src/server/parquet_utils_test.cpp index 7559f97..b628473 100644 --- a/src/server/parquet_utils_test.cpp +++ b/src/server/parquet_utils_test.cpp @@ -120,7 +120,7 @@ TEST(ParquetUtils, CalculateLevelBytesLength_UnknownPageType) { AttributesMap attribs = { {"page_type", std::string("UNKNOWN_PAGE_TYPE")} }; - EXPECT_THROW(CalculateLevelBytesLength(raw, attribs), DBPSUnsupportedException); + EXPECT_THROW(CalculateLevelBytesLength(raw, attribs), InvalidInputException); } TEST(ParquetUtils, CalculateLevelBytesLength_InvalidTotalSize) { From 9ef7fc616a17f558a3dcf461f7335fa6f483d843 Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Mon, 8 Dec 2025 09:47:57 -0600 Subject: [PATCH 3/7] - Updating variable names for clarity. --- src/server/encryption_sequencer.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/server/encryption_sequencer.cpp b/src/server/encryption_sequencer.cpp index 10278f2..bb9cb08 100644 --- a/src/server/encryption_sequencer.cpp +++ b/src/server/encryption_sequencer.cpp @@ -148,20 +148,20 @@ bool DataBatchEncryptionSequencer::ConvertAndEncrypt(const std::vector& // Compression: Only UNCOMPRESSED and SNAPPY are supported // TODO(Issue #188): Add support for other compressions. - const bool compression_supported = (compression_ == CompressionCodec::UNCOMPRESSED || - compression_ == CompressionCodec::SNAPPY); + const bool is_compression_supported = (compression_ == CompressionCodec::UNCOMPRESSED || + compression_ == CompressionCodec::SNAPPY); // Format: Only PLAIN is supported // TODO(Issue #187): Add support for other encodings. - const bool format_supported = (format_ == Format::PLAIN); + const bool is_format_supported = (format_ == Format::PLAIN); // Page type: All are supported (DATA_PAGE_V1, DATA_PAGE_V2, DICTIONARY_PAGE) - const bool page_supported = true; + const bool is_page_supported = true; // Datatype: All datatypes are supported. - const bool datatype_supported = true; + const bool is_datatype_supported = true; - if (compression_supported && format_supported && page_supported && datatype_supported) { + if (is_compression_supported && is_format_supported && is_page_supported && is_datatype_supported) { // All conditions are supported, therefore an DBPSUnsupportedException exception should not have happened. // Re-throw the exception. throw; From ce799ae6a82339814c69bae946b1f1117044680a Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Mon, 8 Dec 2025 10:06:13 -0600 Subject: [PATCH 4/7] - Remove DBPSUnsupportedException from unneeded method signatures. --- src/server/encryptors/dbps_encryptor.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/server/encryptors/dbps_encryptor.h b/src/server/encryptors/dbps_encryptor.h index 0fe27b2..2b30fd1 100644 --- a/src/server/encryptors/dbps_encryptor.h +++ b/src/server/encryptors/dbps_encryptor.h @@ -70,7 +70,6 @@ class DBPS_EXPORT DBPSEncryptor { * @param data The plaintext data to encrypt as a vector of bytes * @return The encrypted data as a vector of bytes * @throws InvalidInputException if the input data is invalid or empty - * @throws DBPSUnsupportedException if the encryption operation is not supported */ virtual std::vector EncryptBlock(const std::vector& data) = 0; @@ -80,7 +79,6 @@ class DBPS_EXPORT DBPSEncryptor { * @param data The ciphertext data to decrypt as a vector of bytes * @return The decrypted data as a vector of bytes * @throws InvalidInputException if the input data is invalid, empty, or corrupted - * @throws DBPSUnsupportedException if the decryption operation is not supported */ virtual std::vector DecryptBlock(const std::vector& data) = 0; @@ -95,7 +93,6 @@ class DBPS_EXPORT DBPSEncryptor { * @param typed_list The typed list of values to encrypt (variant type supporting multiple data types) * @return The encrypted data as a vector of bytes containing the encrypted typed list values * @throws InvalidInputException if the input data is invalid or empty - * @throws DBPSUnsupportedException if the encryption operation is not supported */ virtual std::vector EncryptValueList( const TypedListValues& typed_list) = 0; @@ -108,7 +105,6 @@ class DBPS_EXPORT DBPSEncryptor { * @param encrypted_bytes The encrypted data as a vector of bytes containing only the encrypted typed list * @return The decrypted TypedListValues * @throws InvalidInputException if the input data is invalid, empty, or corrupted - * @throws DBPSUnsupportedException if the decryption operation is not supported */ virtual TypedListValues DecryptValueList( const std::vector& encrypted_bytes) = 0; From 2ee0c460513ddd8e8812f1693daae60d0b2d9345 Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Mon, 8 Dec 2025 11:15:23 -0600 Subject: [PATCH 5/7] - Updating TODO comments. --- src/server/compression_utils.cpp | 1 - src/server/encryption_sequencer.cpp | 2 -- src/server/parquet_utils.cpp | 2 -- 3 files changed, 5 deletions(-) diff --git a/src/server/compression_utils.cpp b/src/server/compression_utils.cpp index 7b8ba29..0c6b90a 100644 --- a/src/server/compression_utils.cpp +++ b/src/server/compression_utils.cpp @@ -23,7 +23,6 @@ using namespace dbps::enum_utils; namespace dbps::compression { -// TODO(Issue #188): Add support for other compressions. std::vector Compress(const std::vector& bytes, CompressionCodec::type compression) { if (compression == CompressionCodec::UNCOMPRESSED) { return bytes; diff --git a/src/server/encryption_sequencer.cpp b/src/server/encryption_sequencer.cpp index bb9cb08..c6fd4ab 100644 --- a/src/server/encryption_sequencer.cpp +++ b/src/server/encryption_sequencer.cpp @@ -147,12 +147,10 @@ bool DataBatchEncryptionSequencer::ConvertAndEncrypt(const std::vector& catch (const DBPSUnsupportedException& e) { // Compression: Only UNCOMPRESSED and SNAPPY are supported - // TODO(Issue #188): Add support for other compressions. const bool is_compression_supported = (compression_ == CompressionCodec::UNCOMPRESSED || compression_ == CompressionCodec::SNAPPY); // Format: Only PLAIN is supported - // TODO(Issue #187): Add support for other encodings. const bool is_format_supported = (format_ == Format::PLAIN); // Page type: All are supported (DATA_PAGE_V1, DATA_PAGE_V2, DICTIONARY_PAGE) diff --git a/src/server/parquet_utils.cpp b/src/server/parquet_utils.cpp index 9f937e7..15bebad 100644 --- a/src/server/parquet_utils.cpp +++ b/src/server/parquet_utils.cpp @@ -149,7 +149,6 @@ std::vector SliceValueBytesIntoRawBytes( throw DBPSUnsupportedException("Unsupported format: RLE_DICTIONARY is not supported for per-value operations"); } - // TODO(Issue #187): Add support for other encodings. if (format != Format::PLAIN) { throw DBPSUnsupportedException("On SliceValueBytesIntoRawBytes, unsupported format: " + std::string(to_string(format))); } @@ -208,7 +207,6 @@ std::vector CombineRawBytesIntoValueBytes( throw DBPSUnsupportedException("Unsupported format: RLE_DICTIONARY is not supported for per-value operations"); } - // TODO(Issue #187): Add support for other encodings. if (format != Format::PLAIN) { throw DBPSUnsupportedException("On CombineRawBytesIntoValueBytes, unsupported format: " + std::string(to_string(format))); } From 331f8de6d3aa4f05f247957e269835f8e0e5f762 Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Mon, 8 Dec 2025 14:18:12 -0600 Subject: [PATCH 6/7] - Added a more complete comment on the try-catch for the per-block fallback. - Moved setting per-value metadata closer to the rest of the code. --- src/server/encryption_sequencer.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/server/encryption_sequencer.cpp b/src/server/encryption_sequencer.cpp index c6fd4ab..12dccae 100644 --- a/src/server/encryption_sequencer.cpp +++ b/src/server/encryption_sequencer.cpp @@ -124,6 +124,13 @@ bool DataBatchEncryptionSequencer::ConvertAndEncrypt(const std::vector& return false; } + // Note on try-catch block: + // - When fully done, ConvertAndEncrypt will support per-value encryption for all cases. + // - This try-catch block is transitional to allow features to be developed incrementally until all features are + // complete: Compressions, Encodings, Page types, Datatypes. + // - During development if a feature is not yet supported, UnsupportedExceptions are caught and the fallback to + // per-block encryption is used. + // - Once per-value encryption for all cases is complete, the try-catch block and the call to EncryptBlock must be removed. try { // Decompress and split plaintext into level and value bytes auto [level_bytes, value_bytes] = DecompressAndSplit( @@ -140,10 +147,12 @@ bool DataBatchEncryptionSequencer::ConvertAndEncrypt(const std::vector& // Compress the joined encrypted bytes encrypted_result_ = Compress(joined_encrypted_bytes, encrypted_compression_); + // Set the encryption type to per-value + encryption_metadata_[ENCRYPTION_MODE] = ENCRYPTION_PER_VALUE; + encryption_metadata_[DBPS_VERSION_KEY] = DBPS_VERSION; + return true; } - // If the sequence was interrupted by a DBPSUnsupportedException, allow fallback to per-block encryption - // but only for explicitly unsupported conditions. - // Any conditions that are already supported should not fallback. In those cases, the exception is re-thrown. + // Allow fallback to per-block encryption, only for explicitly unsupported conditions. See note above. catch (const DBPSUnsupportedException& e) { // Compression: Only UNCOMPRESSED and SNAPPY are supported @@ -165,7 +174,6 @@ bool DataBatchEncryptionSequencer::ConvertAndEncrypt(const std::vector& throw; } - // Fallback: Use per-block encryption for unsupported combinations. encrypted_result_ = encryptor_->EncryptBlock(plaintext); if (encrypted_result_.empty()) { error_stage_ = "encryption"; @@ -177,15 +185,9 @@ bool DataBatchEncryptionSequencer::ConvertAndEncrypt(const std::vector& return true; } catch (const InvalidInputException& e) { - // Throw the exception so it can be caught by the caller. + // InvalidInputException is treated as any other exception and is re-thrown to be handled by the caller. throw; } - - // If the sequencer got here, it means the encryption of the values in the typed list finished successfully. - // Set the encryption type to per value - encryption_metadata_[ENCRYPTION_MODE] = ENCRYPTION_PER_VALUE; - encryption_metadata_[DBPS_VERSION_KEY] = DBPS_VERSION; - return true; } // TODO: Rename this method so it captures better the flow of decompress/format and encrypt/decrypt operations. From a7b99f0f78da29a9970a337539c5c87239ccb7c9 Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Mon, 8 Dec 2025 14:30:41 -0600 Subject: [PATCH 7/7] - Updated spacing and formatting. --- src/server/encryption_sequencer.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/server/encryption_sequencer.cpp b/src/server/encryption_sequencer.cpp index 12dccae..17ebc81 100644 --- a/src/server/encryption_sequencer.cpp +++ b/src/server/encryption_sequencer.cpp @@ -124,13 +124,15 @@ bool DataBatchEncryptionSequencer::ConvertAndEncrypt(const std::vector& return false; } - // Note on try-catch block: - // - When fully done, ConvertAndEncrypt will support per-value encryption for all cases. - // - This try-catch block is transitional to allow features to be developed incrementally until all features are - // complete: Compressions, Encodings, Page types, Datatypes. - // - During development if a feature is not yet supported, UnsupportedExceptions are caught and the fallback to - // per-block encryption is used. - // - Once per-value encryption for all cases is complete, the try-catch block and the call to EncryptBlock must be removed. + /* + * Note on try-catch block: + * - When fully done, ConvertAndEncrypt will support per-value encryption for all cases. + * - This try-catch block is transitional to allow features to be developed incrementally until all features are + * complete: Compressions, Encodings, Page types, Datatypes. + * - During development if a feature is not yet supported, UnsupportedExceptions are caught and the fallback to + * per-block encryption is used. + * - Once per-value encryption for all cases is complete, the try-catch block and the call to EncryptBlock must be removed. + */ try { // Decompress and split plaintext into level and value bytes auto [level_bytes, value_bytes] = DecompressAndSplit(