diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/ArrayPoolManager.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/ArrayPoolManager.cs new file mode 100644 index 0000000000..3546b7155b --- /dev/null +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/ArrayPoolManager.cs @@ -0,0 +1,54 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Encryption.Custom +{ + using System; + using System.Buffers; + using System.Collections.Generic; + +#pragma warning disable SA1402 // File may only contain a single type + internal class ArrayPoolManager : IDisposable +#pragma warning restore SA1402 // File may only contain a single type + { + private List rentedBuffers = new (); + private bool disposedValue; + + public T[] Rent(int minimumLength) + { + T[] buffer = ArrayPool.Shared.Rent(minimumLength); + this.rentedBuffers.Add(buffer); + return buffer; + } + + protected virtual void Dispose(bool disposing) + { + if (!this.disposedValue) + { + if (disposing) + { + foreach (T[] buffer in this.rentedBuffers) + { + ArrayPool.Shared.Return(buffer, clearArray: true); + } + + this.rentedBuffers = null; + } + + this.disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } + + internal class ArrayPoolManager : ArrayPoolManager + { + } +} diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionProcessor.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionProcessor.cs index 95a9b65ecc..1c7f692fe2 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionProcessor.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionProcessor.cs @@ -21,10 +21,11 @@ namespace Microsoft.Azure.Cosmos.Encryption.Custom /// internal static class EncryptionProcessor { - private static readonly SqlSerializerFactory SqlSerializerFactory = new (); - // UTF-8 encoding. private static readonly SqlVarCharSerializer SqlVarCharSerializer = new (size: -1, codePageCharacterEncoding: 65001); + private static readonly SqlBitSerializer SqlBoolSerializer = new (); + private static readonly SqlFloatSerializer SqlDoubleSerializer = new (); + private static readonly SqlBigIntSerializer SqlLongSerializer = new (); private static readonly JsonSerializerSettings JsonSerializerSettings = new () { @@ -75,18 +76,22 @@ public static async Task EncryptAsync( } } - JObject itemJObj = EncryptionProcessor.BaseSerializer.FromStream(input); + JObject itemJObj = BaseSerializer.FromStream(input); List pathsEncrypted = new (encryptionOptions.PathsToEncrypt.Count()); EncryptionProperties encryptionProperties = null; byte[] plainText = null; byte[] cipherText = null; TypeMarker typeMarker; + using ArrayPoolManager arrayPoolManager = new (); + #pragma warning disable CS0618 // Type or member is obsolete switch (encryptionOptions.EncryptionAlgorithm) { case CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized: + DataEncryptionKey encryptionKey = await encryptor.GetEncryptionKeyAsync(encryptionOptions.DataEncryptionKeyId, encryptionOptions.EncryptionAlgorithm, cancellationToken); + foreach (string pathToEncrypt in encryptionOptions.PathsToEncrypt) { string propertyName = pathToEncrypt.Substring(1); @@ -100,19 +105,23 @@ public static async Task EncryptAsync( continue; } - (typeMarker, plainText) = EncryptionProcessor.Serialize(propertyValue); + (typeMarker, plainText, int plainTextLength) = EncryptionProcessor.Serialize(propertyValue, arrayPoolManager); - DataEncryptionKey encryptionKey = await encryptor.GetEncryptionKeyAsync(encryptionOptions.DataEncryptionKeyId, encryptionOptions.EncryptionAlgorithm); + if (plainText == null) + { + continue; + } - int cipherTextLength = encryptionKey.GetEncryptByteCount(plainText.Length); + int cipherTextLength = encryptionKey.GetEncryptByteCount(plainTextLength); byte[] cipherTextWithTypeMarker = new byte[cipherTextLength + 1]; + cipherTextWithTypeMarker[0] = (byte)typeMarker; int encryptedBytesCount = encryptionKey.EncryptData( plainText, plainTextOffset: 0, - plainTextLength: plainText.Length, + plainTextLength, cipherTextWithTypeMarker, outputOffset: 1); @@ -126,11 +135,11 @@ public static async Task EncryptAsync( } encryptionProperties = new EncryptionProperties( - encryptionFormatVersion: 3, - encryptionOptions.EncryptionAlgorithm, - encryptionOptions.DataEncryptionKeyId, - encryptedData: null, - pathsEncrypted); + encryptionFormatVersion: 3, + encryptionOptions.EncryptionAlgorithm, + encryptionOptions.DataEncryptionKeyId, + encryptedData: null, + pathsEncrypted); break; case CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized: @@ -149,7 +158,7 @@ public static async Task EncryptAsync( itemJObj.Remove(propertyName); } - MemoryStream memoryStream = EncryptionProcessor.BaseSerializer.ToStream(toEncryptJObj); + MemoryStream memoryStream = BaseSerializer.ToStream(toEncryptJObj); Debug.Assert(memoryStream != null); Debug.Assert(memoryStream.TryGetBuffer(out _)); plainText = memoryStream.ToArray(); @@ -166,11 +175,11 @@ public static async Task EncryptAsync( } encryptionProperties = new EncryptionProperties( - encryptionFormatVersion: 2, - encryptionOptions.EncryptionAlgorithm, - encryptionOptions.DataEncryptionKeyId, - encryptedData: cipherText, - encryptionOptions.PathsToEncrypt); + encryptionFormatVersion: 2, + encryptionOptions.EncryptionAlgorithm, + encryptionOptions.DataEncryptionKeyId, + encryptedData: cipherText, + encryptionOptions.PathsToEncrypt); break; default: @@ -180,7 +189,7 @@ public static async Task EncryptAsync( itemJObj.Add(Constants.EncryptedInfo, JObject.FromObject(encryptionProperties)); input.Dispose(); - return EncryptionProcessor.BaseSerializer.ToStream(itemJObj); + return BaseSerializer.ToStream(itemJObj); } /// @@ -233,7 +242,7 @@ public static async Task EncryptAsync( #pragma warning restore CS0618 // Type or member is obsolete input.Dispose(); - return (EncryptionProcessor.BaseSerializer.ToStream(itemJObj), decryptionContext); + return (BaseSerializer.ToStream(itemJObj), decryptionContext); } public static async Task<(JObject, DecryptionContext)> DecryptAsync( @@ -283,7 +292,18 @@ private static async Task MdeEncAlgoDecryptObjectAsync( CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { - JObject plainTextJObj = new (); + _ = diagnosticsContext; + + if (encryptionProperties.EncryptionFormatVersion != 3) + { + throw new NotSupportedException($"Unknown encryption format version: {encryptionProperties.EncryptionFormatVersion}. Please upgrade your SDK to the latest version."); + } + + using ArrayPoolManager arrayPoolManager = new (); + + DataEncryptionKey encryptionKey = await encryptor.GetEncryptionKeyAsync(encryptionProperties.DataEncryptionKeyId, encryptionProperties.EncryptionAlgorithm, cancellationToken); + + List pathsDecrypted = new (encryptionProperties.EncryptedPaths.Count()); foreach (string path in encryptionProperties.EncryptedPaths) { string propertyName = path.Substring(1); @@ -293,34 +313,29 @@ private static async Task MdeEncAlgoDecryptObjectAsync( } byte[] cipherTextWithTypeMarker = propertyValue.ToObject(); - if (cipherTextWithTypeMarker == null) { continue; } - byte[] cipherText = new byte[cipherTextWithTypeMarker.Length - 1]; - Buffer.BlockCopy(cipherTextWithTypeMarker, 1, cipherText, 0, cipherTextWithTypeMarker.Length - 1); + int plainTextLength = encryptionKey.GetDecryptByteCount(cipherTextWithTypeMarker.Length - 1); - byte[] plainText = await EncryptionProcessor.MdeEncAlgoDecryptPropertyAsync( - encryptionProperties, - cipherText, - encryptor, - diagnosticsContext, - cancellationToken); + byte[] plainText = arrayPoolManager.Rent(plainTextLength); + + int decryptedCount = EncryptionProcessor.MdeEncAlgoDecryptProperty( + encryptionKey, + cipherTextWithTypeMarker, + cipherTextOffset: 1, + cipherTextWithTypeMarker.Length - 1, + plainText); EncryptionProcessor.DeserializeAndAddProperty( (TypeMarker)cipherTextWithTypeMarker[0], - plainText, - plainTextJObj, + plainText.AsSpan(0, decryptedCount), + document, propertyName); - } - List pathsDecrypted = new (); - foreach (JProperty property in plainTextJObj.Properties()) - { - document[property.Name] = property.Value; - pathsDecrypted.Add("/" + property.Name); + pathsDecrypted.Add(path); } DecryptionContext decryptionContext = EncryptionProcessor.CreateDecryptionContext( @@ -345,26 +360,26 @@ private static DecryptionContext CreateDecryptionContext( return decryptionContext; } - private static async Task MdeEncAlgoDecryptPropertyAsync( - EncryptionProperties encryptionProperties, + private static int MdeEncAlgoDecryptProperty( + DataEncryptionKey encryptionKey, byte[] cipherText, - Encryptor encryptor, - CosmosDiagnosticsContext diagnosticsContext, - CancellationToken cancellationToken) + int cipherTextOffset, + int cipherTextLength, + byte[] buffer) { - _ = diagnosticsContext; + int decryptedCount = encryptionKey.DecryptData( + cipherText, + cipherTextOffset, + cipherTextLength, + buffer, + outputOffset: 0); - if (encryptionProperties.EncryptionFormatVersion != 3) + if (decryptedCount < 0) { - throw new NotSupportedException($"Unknown encryption format version: {encryptionProperties.EncryptionFormatVersion}. Please upgrade your SDK to the latest version."); + throw new InvalidOperationException($"{nameof(Encryptor)} returned null plainText from {nameof(DecryptAsync)}."); } - byte[] plainText = await encryptor.DecryptAsync( - cipherText, - encryptionProperties.DataEncryptionKeyId, - encryptionProperties.EncryptionAlgorithm, - cancellationToken) ?? throw new InvalidOperationException($"{nameof(Encryptor)} returned null plainText from {nameof(DecryptAsync)}."); - return plainText; + return decryptedCount; } private static async Task LegacyEncAlgoDecryptContentAsync( @@ -482,63 +497,101 @@ private static JObject RetrieveEncryptionProperties( return encryptionPropertiesJObj; } - private static (TypeMarker, byte[]) Serialize(JToken propertyValue) + private static (TypeMarker typeMarker, byte[] serializedBytes, int serializedBytesCount) Serialize(JToken propertyValue, ArrayPoolManager arrayPoolManager) { + byte[] buffer; + int length; switch (propertyValue.Type) { case JTokenType.Undefined: Debug.Assert(false, "Undefined value cannot be in the JSON"); - return (default, null); + return (default, null, -1); case JTokenType.Null: Debug.Assert(false, "Null type should have been handled by caller"); - return (TypeMarker.Null, null); + return (TypeMarker.Null, null, -1); case JTokenType.Boolean: - return (TypeMarker.Boolean, SqlSerializerFactory.GetDefaultSerializer().Serialize(propertyValue.ToObject())); + (buffer, length) = SerializeFixed(SqlBoolSerializer); + return (TypeMarker.Boolean, buffer, length); case JTokenType.Float: - return (TypeMarker.Double, SqlSerializerFactory.GetDefaultSerializer().Serialize(propertyValue.ToObject())); + (buffer, length) = SerializeFixed(SqlDoubleSerializer); + return (TypeMarker.Double, buffer, length); case JTokenType.Integer: - return (TypeMarker.Long, SqlSerializerFactory.GetDefaultSerializer().Serialize(propertyValue.ToObject())); + (buffer, length) = SerializeFixed(SqlLongSerializer); + return (TypeMarker.Long, buffer, length); case JTokenType.String: - return (TypeMarker.String, SqlVarCharSerializer.Serialize(propertyValue.ToObject())); + (buffer, length) = SerializeString(propertyValue.ToObject()); + return (TypeMarker.String, buffer, length); case JTokenType.Array: - return (TypeMarker.Array, SqlVarCharSerializer.Serialize(propertyValue.ToString())); + (buffer, length) = SerializeString(propertyValue.ToString()); + return (TypeMarker.Array, buffer, length); case JTokenType.Object: - return (TypeMarker.Object, SqlVarCharSerializer.Serialize(propertyValue.ToString())); + (buffer, length) = SerializeString(propertyValue.ToString()); + return (TypeMarker.Object, buffer, length); default: throw new InvalidOperationException($" Invalid or Unsupported Data Type Passed : {propertyValue.Type}"); } + + (byte[], int) SerializeFixed(IFixedSizeSerializer serializer) + { + byte[] buffer = arrayPoolManager.Rent(serializer.GetSerializedMaxByteCount()); + int length = serializer.Serialize(propertyValue.ToObject(), buffer); + return (buffer, length); + } + + (byte[], int) SerializeString(string value) + { + byte[] buffer = arrayPoolManager.Rent(SqlVarCharSerializer.GetSerializedMaxByteCount(value.Length)); + int length = SqlVarCharSerializer.Serialize(value, buffer); + return (buffer, length); + } } private static void DeserializeAndAddProperty( TypeMarker typeMarker, - byte[] serializedBytes, + ReadOnlySpan serializedBytes, JObject jObject, string key) { switch (typeMarker) { case TypeMarker.Boolean: - jObject.Add(key, SqlSerializerFactory.GetDefaultSerializer().Deserialize(serializedBytes)); + jObject[key] = SqlBoolSerializer.Deserialize(serializedBytes); break; case TypeMarker.Double: - jObject.Add(key, SqlSerializerFactory.GetDefaultSerializer().Deserialize(serializedBytes)); + jObject[key] = SqlDoubleSerializer.Deserialize(serializedBytes); break; case TypeMarker.Long: - jObject.Add(key, SqlSerializerFactory.GetDefaultSerializer().Deserialize(serializedBytes)); + jObject[key] = SqlLongSerializer.Deserialize(serializedBytes); break; case TypeMarker.String: - jObject.Add(key, SqlVarCharSerializer.Deserialize(serializedBytes)); + jObject[key] = SqlVarCharSerializer.Deserialize(serializedBytes); break; case TypeMarker.Array: - jObject.Add(key, JsonConvert.DeserializeObject(SqlVarCharSerializer.Deserialize(serializedBytes), JsonSerializerSettings)); + DeserializeAndAddProperty(serializedBytes); break; case TypeMarker.Object: - jObject.Add(key, JsonConvert.DeserializeObject(SqlVarCharSerializer.Deserialize(serializedBytes), JsonSerializerSettings)); + DeserializeAndAddProperty(serializedBytes); break; default: - Debug.Fail(string.Format("Unexpected type marker {0}", typeMarker)); + Debug.Fail($"Unexpected type marker {typeMarker}"); break; } + + void DeserializeAndAddProperty(ReadOnlySpan serializedBytes) + where T : JToken + { + using ArrayPoolManager manager = new (); + + char[] buffer = manager.Rent(SqlVarCharSerializer.GetDeserializedMaxLength(serializedBytes.Length)); + int length = SqlVarCharSerializer.Deserialize(serializedBytes, buffer.AsSpan()); + + JsonSerializer serializer = JsonSerializer.Create(JsonSerializerSettings); + + using MemoryTextReader memoryTextReader = new (new Memory(buffer, 0, length)); + using JsonTextReader reader = new (memoryTextReader); + + jObject[key] = serializer.Deserialize(reader); + } } private enum TypeMarker : byte @@ -557,7 +610,7 @@ internal static async Task DeserializeAndDecryptResponseAsync( Encryptor encryptor, CancellationToken cancellationToken) { - JObject contentJObj = EncryptionProcessor.BaseSerializer.FromStream(content); + JObject contentJObj = BaseSerializer.FromStream(content); if (contentJObj.SelectToken(Constants.DocumentsResourcePropertyName) is not JArray documents) { @@ -587,4 +640,4 @@ await EncryptionProcessor.DecryptAsync( return EncryptionProcessor.BaseSerializer.ToStream(contentJObj); } } -} +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Encryptor.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Encryptor.cs index 635a6383e9..27c152d491 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Encryptor.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Encryptor.cs @@ -13,6 +13,15 @@ namespace Microsoft.Azure.Cosmos.Encryption.Custom /// public abstract class Encryptor { + /// + /// Retrieve Data Encryption Key. + /// + /// Identifier of the data encryption key. + /// Identifier of the encryption algorithm. + /// Token for cancellation. + /// Data Encryption Key + public abstract Task GetEncryptionKeyAsync(string dataEncryptionKeyId, string encryptionAlgorithm, CancellationToken cancellationToken = default); + /// /// Encrypts the plainText using the key and algorithm provided. /// @@ -27,15 +36,6 @@ public abstract Task EncryptAsync( string encryptionAlgorithm, CancellationToken cancellationToken = default); - /// - /// Retrieve Data Encryption Key. - /// - /// Identifier of the data encryption key. - /// Identifier of the encryption algorithm. - /// Token for cancellation. - /// Data Encryption Key - public abstract Task GetEncryptionKeyAsync(string dataEncryptionKeyId, string encryptionAlgorithm, CancellationToken cancellationToken = default); - /// /// Decrypts the cipherText using the key and algorithm provided. /// diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/MemoryTextReader.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/MemoryTextReader.cs new file mode 100644 index 0000000000..9326fbbf0e --- /dev/null +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/MemoryTextReader.cs @@ -0,0 +1,161 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Encryption.Custom +{ + using System; + using System.Diagnostics.Contracts; + using System.IO; + + /// + /// Adjusted implementation of .Net StringReader reading from a Memory instead of a string. + /// + internal class MemoryTextReader : TextReader + { + private Memory chars; + private int length; + private int pos; + private bool closed; + + public MemoryTextReader(Memory chars) + { + this.chars = chars; + this.length = chars.Length; + } + + public override void Close() + { + this.Dispose(true); + } + + protected override void Dispose(bool disposing) + { + this.chars = null; + this.pos = 0; + this.length = 0; + this.closed = true; + base.Dispose(disposing); + } + + [Pure] + public override int Peek() + { + if (this.closed) + { + throw new InvalidOperationException("Reader is closed"); + } + + if (this.pos == this.length) + { + return -1; + } + + return this.chars.Span[this.pos]; + } + + public override int Read() + { + if (this.closed) + { + throw new InvalidOperationException("Reader is closed"); + } + + if (this.pos == this.length) + { + return -1; + } + + return this.chars.Span[this.pos++]; + } + + public override int Read(char[] buffer, int index, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (buffer.Length - index < count) + { + throw new ArgumentOutOfRangeException(); + } + + if (this.closed) + { + throw new InvalidOperationException("Reader is closed"); + } + + int n = this.length - this.pos; + if (n > 0) + { + if (n > count) + { + n = count; + } + + this.chars.Span.Slice(this.pos, n).CopyTo(buffer.AsSpan(index, n)); + this.pos += n; + } + + return n; + } + + public override string ReadToEnd() + { + if (this.closed) + { + throw new InvalidOperationException("Reader is closed"); + } + + this.pos = this.length; + return new string(this.chars.Slice(this.pos, this.length - this.pos).ToArray()); + } + + public override string ReadLine() + { + if (this.closed) + { + throw new InvalidOperationException("Reader is closed"); + } + + int i = this.pos; + while (i < this.length) + { + char ch = this.chars.Span[i]; + if (ch == '\r' || ch == '\n') + { + string result = new (this.chars.Slice(this.pos, i - this.pos).ToArray()); + this.pos = i + 1; + if (ch == '\r' && this.pos < this.length && this.chars.Span[this.pos] == '\n') + { + this.pos++; + } + + return result; + } + + i++; + } + + if (i > this.pos) + { + string result = new (this.chars.Slice(this.pos, i - this.pos).ToArray()); + this.pos = i; + return result; + } + + return null; + } + } +} diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Microsoft.Azure.Cosmos.Encryption.Custom.csproj b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Microsoft.Azure.Cosmos.Encryption.Custom.csproj index 64cbde6902..8769abdb89 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Microsoft.Azure.Cosmos.Encryption.Custom.csproj +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Microsoft.Azure.Cosmos.Encryption.Custom.csproj @@ -37,7 +37,7 @@ - + diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/MdeCustomEncryptionTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/MdeCustomEncryptionTests.cs index 29f4458c59..661b876907 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/MdeCustomEncryptionTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/MdeCustomEncryptionTests.cs @@ -370,7 +370,7 @@ public async Task ValidateCachingOfProtectedDataEncryptionKey() await CreateItemAsync(encryptionContainer, dekId, TestDoc.PathsToEncrypt); testEncryptionKeyStoreProvider.UnWrapKeyCallsCount.TryGetValue(masterKeyUri1.ToString(), out unwrapcount); - Assert.AreEqual(32, unwrapcount); + Assert.AreEqual(4, unwrapcount); // 2 hours default testEncryptionKeyStoreProvider = new TestEncryptionKeyStoreProvider(); diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests/Readme.md b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests/Readme.md index 64a686244c..ced6fc95ed 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests/Readme.md +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests/Readme.md @@ -9,11 +9,11 @@ Job=MediumRun Toolchain=InProcessEmitToolchain IterationCount=15 LaunchCount=2 WarmupCount=10 ``` -| Method | DocumentSizeInKb | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|-------- |----------------- |------------:|-----------:|-----------:|---------:|---------:|--------:|-----------:| -| **Encrypt** | **1** | **37.15 μs** | **0.683 μs** | **1.002 μs** | **3.8452** | **0.9766** | **-** | **47.28 KB** | -| Decrypt | 1 | 45.29 μs | 0.757 μs | 1.062 μs | 4.3945 | 1.0986 | - | 54.17 KB | -| **Encrypt** | **10** | **111.83 μs** | **1.252 μs** | **1.874 μs** | **15.1367** | **3.0518** | **-** | **186.07 KB** | -| Decrypt | 10 | 151.46 μs | 2.259 μs | 3.311 μs | 19.5313 | 2.1973 | - | 239.94 KB | -| **Encrypt** | **100** | **1,567.24 μs** | **153.944 μs** | **230.416 μs** | **152.3438** | **109.3750** | **76.1719** | **1773.95 KB** | -| Decrypt | 100 | 2,088.77 μs | 232.084 μs | 347.372 μs | 160.1563 | 113.2813 | 76.1719 | 2042.61 KB | +| Method | DocumentSizeInKb | Mean | Error | StdDev | Median | Gen0 | Gen1 | Gen2 | Allocated | +|-------- |----------------- |------------:|-----------:|-----------:|------------:|---------:|---------:|---------:|-----------:| +| **Encrypt** | **1** | **28.40 μs** | **0.428 μs** | **0.640 μs** | **28.40 μs** | **3.3569** | **0.8240** | **-** | **41.15 KB** | +| Decrypt | 1 | 33.19 μs | 0.532 μs | 0.779 μs | 33.54 μs | 3.2349 | 0.7935 | - | 39.7 KB | +| **Encrypt** | **10** | **105.95 μs** | **2.230 μs** | **3.337 μs** | **106.49 μs** | **13.7939** | **0.6104** | **-** | **169.78 KB** | +| Decrypt | 10 | 113.47 μs | 1.716 μs | 2.569 μs | 111.81 μs | 12.5732 | 1.2207 | - | 154.62 KB | +| **Encrypt** | **100** | **1,486.58 μs** | **389.596 μs** | **583.129 μs** | **1,487.32 μs** | **216.7969** | **177.7344** | **142.5781** | **1655.2 KB** | +| Decrypt | 100 | 1,404.48 μs | 137.824 μs | 206.288 μs | 1,409.23 μs | 144.5313 | 107.4219 | 87.8906 | 1248.31 KB | diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/MdeEncryptionProcessorTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/MdeEncryptionProcessorTests.cs index 1dac0ef6fd..69a9c7afb0 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/MdeEncryptionProcessorTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/MdeEncryptionProcessorTests.cs @@ -41,18 +41,22 @@ public static void ClassInitialize(TestContext testContext) .Returns((int plainTextLength) => plainTextLength); DekMock.Setup(m => m.EncryptData(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((byte[] plainText, int plainTextOffset, int plainTextLength, byte[] output, int outputOffset) => TestCommon.EncryptData(plainText, plainTextOffset, plainTextLength, output, outputOffset)); + DekMock.Setup(m => m.DecryptData(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((byte[] plainText, int plainTextOffset, int plainTextLength, byte[] output, int outputOffset) => TestCommon.DecryptData(plainText, plainTextOffset, plainTextLength, output, outputOffset)); + DekMock.Setup(m => m.GetDecryptByteCount(It.IsAny())) + .Returns((int cipherTextLength) => cipherTextLength); - MdeEncryptionProcessorTests.mockEncryptor = new Mock(); - MdeEncryptionProcessorTests.mockEncryptor.Setup(m => m.GetEncryptionKeyAsync(It.IsAny(), It.IsAny(), It.IsAny())) + mockEncryptor = new Mock(); + mockEncryptor.Setup(m => m.GetEncryptionKeyAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((string dekId, string algorithm, CancellationToken token) => dekId == MdeEncryptionProcessorTests.dekId ? DekMock.Object : throw new InvalidOperationException("DEK not found.")); - MdeEncryptionProcessorTests.mockEncryptor.Setup(m => m.EncryptAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + mockEncryptor.Setup(m => m.EncryptAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((byte[] plainText, string dekId, string algo, CancellationToken t) => dekId == MdeEncryptionProcessorTests.dekId ? TestCommon.EncryptData(plainText) : throw new InvalidOperationException("DEK not found.")); - MdeEncryptionProcessorTests.mockEncryptor.Setup(m => m.DecryptAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + mockEncryptor.Setup(m => m.DecryptAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((byte[] cipherText, string dekId, string algo, CancellationToken t) => dekId == MdeEncryptionProcessorTests.dekId ? TestCommon.DecryptData(cipherText) : throw new InvalidOperationException("Null DEK was returned.")); }