diff --git a/src/server/encryption_sequencer.cpp b/src/server/encryption_sequencer.cpp index 27d4f97..17ebc81 100644 --- a/src/server/encryption_sequencer.cpp +++ b/src/server/encryption_sequencer.cpp @@ -124,6 +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. + */ try { // Decompress and split plaintext into level and value bytes auto [level_bytes, value_bytes] = DecompressAndSplit( @@ -140,29 +149,47 @@ 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) + // Set the encryption type to per-value + encryption_metadata_[ENCRYPTION_MODE] = ENCRYPTION_PER_VALUE; + encryption_metadata_[DBPS_VERSION_KEY] = DBPS_VERSION; + return true; + } + // 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 + const bool is_compression_supported = (compression_ == CompressionCodec::UNCOMPRESSED || + compression_ == CompressionCodec::SNAPPY); + + // Format: Only PLAIN is supported + const bool is_format_supported = (format_ == Format::PLAIN); + + // Page type: All are supported (DATA_PAGE_V1, DATA_PAGE_V2, DICTIONARY_PAGE) + const bool is_page_supported = true; + + // Datatype: All datatypes are supported. + const bool is_datatype_supported = true; + + 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; + } + 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. + // 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. 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/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; diff --git a/src/server/parquet_utils.cpp b/src/server/parquet_utils.cpp index 665f63f..15bebad 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,15 @@ 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"); + } + 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 +200,15 @@ 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"); + } + 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) { 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) {