Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Client encryption]: Adds support for Brotli compression #4769

Merged
merged 71 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
d6f89ea
Add baseline benchmarks for `Microsoft.Azure.Cosmos.Encryption.Custom`
juraj-blazek Sep 12, 2024
8928b88
Cleanup
juraj-blazek Sep 12, 2024
9dc7d54
Use set of static test data for benchmarks
juraj-blazek Sep 12, 2024
368dc25
Merge branch 'master' into users/juraj-blazek/encryption-benchmark-ba…
juraj-blazek Sep 12, 2024
38a198a
Add non-allocating APIs to encryptors
juraj-blazek Sep 16, 2024
e0bb8bf
WIP
juraj-blazek Sep 16, 2024
9c3c276
Revert solution update
juraj-blazek Sep 16, 2024
c37a092
Merge branch 'users/juraj-blazek/encryption-benchmark-baseline' of ht…
juraj-blazek Sep 16, 2024
f4db1c6
Merge branch 'master' into users/juraj-blazek/encryption-benchmark-ba…
juraj-blazek Sep 16, 2024
91714f9
Merge branch 'master' into users/juraj-blazek/encryption-benchmark-ba…
juraj-blazek Sep 17, 2024
2058f8b
Merge remote-tracking branch 'origin/master' into users/juraj-blazek/…
juraj-blazek Sep 17, 2024
f33538c
Add implementation, fix tests
juraj-blazek Sep 17, 2024
6923fd2
Switch to randomized encryption for benchmarks
juraj-blazek Sep 17, 2024
4ea8690
Merge branch 'master' into users/juraj-blazek/encryption-benchmark-ba…
kr-santosh Sep 18, 2024
03ef682
Some more array pooling
juraj-blazek Sep 17, 2024
1044a89
Streaming deserialization
juraj-blazek Sep 19, 2024
f1db54d
Merge remote-tracking branch 'origin/master' into users/juraj-blazek/…
juraj-blazek Sep 19, 2024
05bfc50
Cleanup
juraj-blazek Sep 20, 2024
5629f74
Update MDE and rerun benchmarks
juraj-blazek Sep 20, 2024
495d2c4
Add non-allocating APIs to encryptors
juraj-blazek Sep 16, 2024
b3f74b1
Merge branch 'master' into users/juraj-blazek/non-allocating-encryptor
JanHyka Sep 30, 2024
14bce37
~ drop repeated DEK calls
JanHyka Sep 30, 2024
4ff1601
! typo
JanHyka Sep 30, 2024
d8a345c
~ update benchmark
JanHyka Sep 30, 2024
03c06e0
~ fix tests
JanHyka Sep 30, 2024
a1025c4
Merge branch 'Azure:master' into users/juraj-blazek/non-allocating-en…
JanHyka Oct 1, 2024
3bf77c8
~ cleanup
JanHyka Oct 1, 2024
ceaa8b5
+ refresh benchmark
JanHyka Oct 1, 2024
611b3ac
+ unit test
JanHyka Oct 1, 2024
9ca89d4
~ merge predecessor PR
JanHyka Oct 1, 2024
8a78fe8
~ merge fixes and initial cleanup
JanHyka Oct 1, 2024
8ed2135
~ write directly to output document instead of copying
JanHyka Oct 1, 2024
bbe9845
! tests
JanHyka Oct 1, 2024
a107f62
~ retrieve DataEncryptionKey only once per document
JanHyka Oct 1, 2024
a1ad02b
! fix tests
JanHyka Oct 1, 2024
4f2f072
~ update Aes algorithm to reuse GetEncryptedByteCount
JanHyka Oct 2, 2024
cbbeee2
~ refactor EncryptionProcessor
JanHyka Oct 4, 2024
b6c851c
! names
JanHyka Oct 4, 2024
72ccae7
~ less static
JanHyka Oct 4, 2024
8ea5879
Merge branch 'master' into users/juraj-blazek/encryption-array-pooling
JanHyka Oct 6, 2024
8c60e69
Merge branch 'master' into users/juraj-blazek/encryption-array-pooling
JanHyka Oct 6, 2024
5554aa0
~ merge fixes
JanHyka Oct 6, 2024
28620ed
~ cleanup
JanHyka Oct 6, 2024
eb059c8
~ unwanted changes
JanHyka Oct 6, 2024
cc2eab5
- unused method
JanHyka Oct 6, 2024
c9ba300
~ updates (PR)
JanHyka Oct 6, 2024
9f9cbca
~ add stable vs preview release duplicity
JanHyka Oct 7, 2024
c347e71
Merge branch 'users/juraj-blazek/encryption-array-pooling' into users…
JanHyka Oct 7, 2024
64172b8
~ cleanup and parent branch merge
JanHyka Oct 7, 2024
ff583f9
Merge branch 'master' into users/jan-hyka/refactor-encryption-processor
JanHyka Oct 7, 2024
0ba8c34
Merge branch 'master' into users/jan-hyka/refactor-encryption-processor
JanHyka Oct 7, 2024
326b1be
~ master merges
JanHyka Oct 7, 2024
c520e16
- duplicate
JanHyka Oct 7, 2024
5c40821
~ cleanup
JanHyka Oct 7, 2024
6dad4cd
+ initial commit
JanHyka Oct 8, 2024
99c7a75
+ Add .NET8.0 target for Cosmos.Encryption.Custom
JanHyka Oct 8, 2024
31c20e7
- remove implicit IsPreview from csproj
JanHyka Oct 8, 2024
306f655
Merge branch 'users/jan-hyka/add-net8-target' into users/jan-hyka/brotli
JanHyka Oct 8, 2024
0a627bd
~ fixes
JanHyka Oct 8, 2024
78da8bb
+ unit tests
JanHyka Oct 8, 2024
17f79e7
Merge branch 'master' into users/jan-hyka/brotli
JanHyka Oct 10, 2024
ff08a1f
~ post merge fixes
JanHyka Oct 10, 2024
d7a5be4
Merge branch 'master' into users/jan-hyka/brotli
JanHyka Oct 10, 2024
a058cb1
~ fix tests for version difference between stable and preview mode
JanHyka Oct 10, 2024
ae8049b
Merge branch 'master' into users/jan-hyka/brotli
JanHyka Oct 11, 2024
8630cd4
~ cleanup
JanHyka Oct 11, 2024
7dd48bb
Merge branch 'users/jan-hyka/brotli' of https://github.com/juraj-blaz…
JanHyka Oct 11, 2024
450df8b
~ refactor as pre PR
JanHyka Oct 11, 2024
b061d1a
Merge branch 'master' into users/jan-hyka/brotli
kirankumarkolli Oct 11, 2024
96041ab
! fix breaking change
JanHyka Oct 11, 2024
e9c7cf1
Merge branch 'users/jan-hyka/brotli' of https://github.com/juraj-blaz…
JanHyka Oct 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------

namespace Microsoft.Azure.Cosmos.Encryption.Custom
{
using System.IO.Compression;

/// <summary>
/// Options for payload compression
/// </summary>
public class CompressionOptions
{
/// <summary>
/// Supported compression algorithms
/// </summary>
/// <remarks>Compression is only supported with .NET8.0+.</remarks>
public enum CompressionAlgorithm
{
/// <summary>
/// No compression
/// </summary>
None = 0,
#if NET8_0_OR_GREATER

/// <summary>
/// Brotli compression
/// </summary>
Brotli = 1,
#endif
}

/// <summary>
/// Gets or sets compression algorithm.
/// </summary>
public CompressionAlgorithm Algorithm { get; set; } = CompressionAlgorithm.None;

/// <summary>
/// Gets or sets compression level.
/// </summary>
public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Fastest;

/// <summary>
/// Gets or sets minimal property size for compression.
/// </summary>
public int MinimalCompressedLength { get; set; } = 128;
}
}
2 changes: 2 additions & 0 deletions Microsoft.Azure.Cosmos.Encryption.Custom/src/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ internal static class Constants
public const string EncryptionDekId = "_en";
public const string EncryptionFormatVersion = "_ef";
public const string EncryptedPaths = "_ep";
public const string CompressionAlgorithm = "_ce";
public const string CompressedEncryptedPaths = "_cp";
public const int DekPropertiesDefaultTTLInMinutes = 120;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static class CosmosEncryptionAlgorithm
/// MDE(Microsoft.Data.Encryption) Randomized AEAD_AES_256_CBC_HMAC_SHA256 Algorithm.
/// As described <see href="http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05">here</see>.
/// </summary>
public const string MdeAeadAes256CbcHmac256Randomized = "MdeAeadAes256CbcHmac256Randomized";
public const string MdeAeadAes256CbcHmac256Randomized = @"MdeAeadAes256CbcHmac256Randomized";

/// <summary>
/// Verify if the Encryption Algorithm is supported by Cosmos.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public sealed class EncryptionOptions
/// </remarks>
public string EncryptionAlgorithm { get; set; }

/// <summary>
/// Gets or sets payload compression mode
/// </summary>
public CompressionOptions CompressionOptions { get; set; } = new CompressionOptions();

/// <summary>
/// Gets or sets list of JSON paths to encrypt on the payload.
/// Only top level paths are supported.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------

namespace Microsoft.Azure.Cosmos.Encryption.Custom
{
using System;

internal static class EncryptionOptionsExtensions
{
internal static void Validate(this EncryptionOptions options)
{
if (string.IsNullOrWhiteSpace(options.DataEncryptionKeyId))
{
#pragma warning disable CA2208 // Instantiate argument exceptions correctly
throw new ArgumentNullException(nameof(options.DataEncryptionKeyId));
#pragma warning restore CA2208 // Instantiate argument exceptions correctly
}

if (string.IsNullOrWhiteSpace(options.EncryptionAlgorithm))
{
#pragma warning disable CA2208 // Instantiate argument exceptions correctly
throw new ArgumentNullException(nameof(options.EncryptionAlgorithm));
#pragma warning restore CA2208 // Instantiate argument exceptions correctly
}

if (options.PathsToEncrypt == null)
{
#pragma warning disable CA2208 // Instantiate argument exceptions correctly
throw new ArgumentNullException(nameof(options.PathsToEncrypt));
#pragma warning restore CA2208 // Instantiate argument exceptions correctly
}

options.CompressionOptions?.Validate();
}

internal static void Validate(this CompressionOptions options)
{
if (options.MinimalCompressedLength < 0)
{
#pragma warning disable CA2208 // Instantiate argument exceptions correctly
throw new ArgumentOutOfRangeException(nameof(options.MinimalCompressedLength));
#pragma warning restore CA2208 // Instantiate argument exceptions correctly
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,22 +206,7 @@ private static void ValidateInputForEncrypt(
}
#endif

#pragma warning disable CA2208 // Instantiate argument exceptions correctly
if (string.IsNullOrWhiteSpace(encryptionOptions.DataEncryptionKeyId))
{
throw new ArgumentNullException(nameof(encryptionOptions.DataEncryptionKeyId));
}

if (string.IsNullOrWhiteSpace(encryptionOptions.EncryptionAlgorithm))
{
throw new ArgumentNullException(nameof(encryptionOptions.EncryptionAlgorithm));
}

if (encryptionOptions.PathsToEncrypt == null)
{
throw new ArgumentNullException(nameof(encryptionOptions.PathsToEncrypt));
}
#pragma warning restore CA2208 // Instantiate argument exceptions correctly
encryptionOptions.Validate();
}

private static JObject RetrieveItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,28 @@ internal class EncryptionProperties
[JsonProperty(PropertyName = Constants.EncryptedPaths)]
public IEnumerable<string> EncryptedPaths { get; }

[JsonProperty(PropertyName = Constants.CompressionAlgorithm)]
public CompressionOptions.CompressionAlgorithm CompressionAlgorithm { get; }

[JsonProperty(PropertyName = Constants.CompressedEncryptedPaths)]
public IDictionary<string, int> CompressedEncryptedPaths { get; }

public EncryptionProperties(
int encryptionFormatVersion,
string encryptionAlgorithm,
string dataEncryptionKeyId,
byte[] encryptedData,
IEnumerable<string> encryptedPaths)
IEnumerable<string> encryptedPaths,
CompressionOptions.CompressionAlgorithm compressionAlgorithm = CompressionOptions.CompressionAlgorithm.None,
IDictionary<string, int> compressedEncryptedPaths = null)
{
this.EncryptionFormatVersion = encryptionFormatVersion;
this.EncryptionAlgorithm = encryptionAlgorithm;
this.DataEncryptionKeyId = dataEncryptionKeyId;
this.EncryptedData = encryptedData;
this.EncryptedPaths = encryptedPaths;
this.CompressionAlgorithm = compressionAlgorithm;
this.CompressedEncryptedPaths = compressedEncryptedPaths;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------

namespace Microsoft.Azure.Cosmos.Encryption.Custom.Transformation
{
#if NET8_0_OR_GREATER

using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO.Compression;

internal class BrotliCompressor : IDisposable
{
private const int DefaultWindow = 22;
kirankumarkolli marked this conversation as resolved.
Show resolved Hide resolved

internal static int GetQualityFromCompressionLevel(CompressionLevel compressionLevel)
{
return compressionLevel switch
{
CompressionLevel.NoCompression => 0,
CompressionLevel.Fastest => 1,
CompressionLevel.Optimal => 4,
CompressionLevel.SmallestSize => 11,
kirankumarkolli marked this conversation as resolved.
Show resolved Hide resolved
_ => throw new ArgumentException("Unsupported compression level", nameof(compressionLevel))
};
}

internal static int GetMaxCompressedSize(int inputSize)
{
return BrotliEncoder.GetMaxCompressedLength(inputSize);
}

private readonly BrotliDecoder decoder;
private readonly BrotliEncoder encoder;
private bool disposedValue;

public BrotliCompressor()
{
}

public BrotliCompressor(CompressionLevel compressionLevel)
{
this.encoder = new BrotliEncoder(BrotliCompressor.GetQualityFromCompressionLevel(compressionLevel), DefaultWindow);
}

public virtual int Compress(Dictionary<string, int> compressedPaths, string path, byte[] bytes, int length, byte[] outputBytes)
{
OperationStatus status = this.encoder.Compress(bytes.AsSpan(0, length), outputBytes, out int bytesConsumed, out int bytesWritten, true);

ThrowIfFailure(status, length, bytesConsumed);

compressedPaths[path] = length;

return bytesWritten;
}

public virtual int Decompress(byte[] inputBytes, int length, byte[] outputBytes)
{
OperationStatus status = this.decoder.Decompress(inputBytes.AsSpan(0, length), outputBytes, out int bytesConsumed, out int bytesWritten);

ThrowIfFailure(status, length, bytesConsumed);

return bytesWritten;
}

private static void ThrowIfFailure(OperationStatus status, int expectedLength, int processedLength)
{
if (status != OperationStatus.Done)
{
throw new InvalidOperationException($"Brotli compressor failed : {status}");
}

if (expectedLength != processedLength)
{
throw new InvalidOperationException($"Expected to process {expectedLength} but only processed {processedLength}");
}
}

protected virtual void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
}

// TODO: free unmanaged resources (unmanaged objects) and override finalizer
this.encoder.Dispose();

this.disposedValue = true;
}
}

~BrotliCompressor()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
this.Dispose(disposing: false);
}

public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
this.Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
#endif
}
Loading
Loading