Skip to content
Open
Show file tree
Hide file tree
Changes from 92 commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
c54618a
Add JsonProcessor override via RequestOptions property bag
adamnova Sep 12, 2025
23749e0
Add diagnostics context and stream processor tests
adamnova Sep 12, 2025
22e40d7
Refactor diagnostics scopes in decryption logic
adamnova Sep 15, 2025
d6b3416
Optimize stream serialization and diagnostics in encryption
adamnova Sep 15, 2025
6e5884c
Refactor test encryptor setup and improve MDE fallback
adamnova Sep 16, 2025
585e66c
Unify JSON processor diagnostics and add concurrency tests
adamnova Sep 23, 2025
27d2593
Enforce object root for streaming encryption
adamnova Sep 23, 2025
8b84c66
Enforce object root for streaming encryption
adamnova Sep 24, 2025
78f0a94
Add comprehensive diagnostics scope tests for encryption
adamnova Sep 24, 2025
0812ac5
Refactor MDE encryption processor with adapter pattern
adamnova Sep 24, 2025
9850d4b
Refactor diagnostics scopes and add adapter tests
adamnova Sep 25, 2025
752905f
[Encryption Stream] Fix provided-output decrypt tests: ensure raw enc…
adamnova Sep 25, 2025
c09f71f
Add tests for Newtonsoft override and change feed assertions
adamnova Sep 25, 2025
9f8318b
Update diagnostics scope count assertions in tests
adamnova Sep 26, 2025
459c21e
Fixed tests
adamnova Sep 26, 2025
7b2170c
Update EncryptionBenchmark.cs
adamnova Sep 26, 2025
5aa3de0
Cleanup
adamnova Sep 26, 2025
4dcd1c8
Add experimental JSON processor configuration support
adamnova Sep 26, 2025
c4f6365
Update RequestOptionsOverrideHelper.cs
adamnova Sep 26, 2025
f11fbed
Cleanup conditional compilation
adamnova Sep 26, 2025
ec9b77e
Merge branch 'master' into feature/encryptionprocessor-stream-switch
adamnova Sep 26, 2025
3e7ce8a
Revert
adamnova Sep 26, 2025
c35ffcf
Cleanup
adamnova Sep 26, 2025
db67e7c
Cleanup
adamnova Sep 26, 2025
9496752
Changes
adamnova Sep 26, 2025
7f191e9
Improved naming
adamnova Sep 26, 2025
410f6ca
Refactor RequestOptions extension method usage
adamnova Sep 26, 2025
eb54301
Refactor decryption logic for streamlined processor selection
adamnova Sep 30, 2025
4f6c466
Cleanup preview flag
adamnova Sep 30, 2025
849f58e
Cleanup
adamnova Sep 30, 2025
33dc1e9
Cleanup
adamnova Sep 30, 2025
6ac8394
Cleanup
adamnova Sep 30, 2025
d5ea198
Reverted some changes and added documentation
adamnova Sep 30, 2025
c1cfb95
Refactor argument null checks to use ArgumentValidation
adamnova Sep 30, 2025
98a5310
Standardization
adamnova Oct 1, 2025
75d2684
Merge branch 'master' into feature/encryptionprocessor-stream-switch
adamnova Oct 6, 2025
1dbba14
Refactor encryption processor to use adapter pattern
adamnova Oct 8, 2025
565392c
Update conditional compilation for DecryptAsync methods
adamnova Oct 8, 2025
2bbf6e0
Update baseline tests and API contracts for Cosmos SDK
adamnova Oct 9, 2025
d76440d
Update baseline test for trace writer activities
adamnova Oct 9, 2025
3a4b05b
Delegate decryption to MdeEncryptionProcessor
adamnova Oct 10, 2025
aaba649
Revert "Delegate decryption to MdeEncryptionProcessor"
adamnova Oct 10, 2025
1bb97dd
Add support for legacy decryption in EncryptionProcessor
adamnova Oct 10, 2025
a070536
Refactor legacy decryption path and update tests
adamnova Oct 10, 2025
0d8ad47
Handle exceptions during legacy encryption detection
adamnova Oct 10, 2025
43e7804
Update API contract files and baseline test data
adamnova Oct 10, 2025
8e13f88
Merge branch 'master' into feature/encryptionprocessor-stream-switch
adamnova Oct 13, 2025
754997c
Remove ENCRYPTION_CUSTOM_PREVIEW conditional compilation
adamnova Oct 13, 2025
1c1f79c
Add NET8_0_OR_GREATER conditional compilation
adamnova Oct 13, 2025
e099ff0
Add experimental API and update trace baseline
adamnova Oct 13, 2025
965f859
Use stream mode in encryption feed iterators
MartinSarkany Oct 13, 2025
e244861
Add test for unsupported JsonProcessor exception
adamnova Oct 14, 2025
a18b8f6
Merge branch 'master' into feature/encryptionprocessor-stream-switch
adamnova Oct 14, 2025
132b6e2
Merge branch 'master' into feature/encryptionprocessor-stream-switch
adamnova Oct 15, 2025
55b103b
Remove compression options from test encryption options
adamnova Oct 15, 2025
03ec259
Merge branch 'master' into feature/encryption-feed-iterator-rework
MartinSarkany Oct 15, 2025
1421b3e
Merge branch 'master' into feature/encryptionprocessor-stream-switch
kirankumarkolli Oct 15, 2025
037c360
Merge branch 'master' into feature/encryption-feed-iterator-rework
MartinSarkany Oct 15, 2025
63fa6bc
Add tests
MartinSarkany Oct 15, 2025
576a29d
Allow null request options
MartinSarkany Oct 15, 2025
bc7e3ef
Rename StreamAdapter and related extensions for clarity
adamnova Oct 21, 2025
a486d40
Refactor null checks to use ArgumentValidation.ThrowIfNull
adamnova Oct 21, 2025
bea9d94
Merge remote-tracking branch 'adamnova/feature/encryptionprocessor-st…
MartinSarkany Oct 21, 2025
d68a1aa
Merge branch 'master' into feature/encryption-feed-iterator-rework
MartinSarkany Nov 5, 2025
de3fd65
Merge branch 'master' into feature/encryption-feed-iterator-rework
MartinSarkany Nov 6, 2025
32f1ae8
Some updates and fixes
MartinSarkany Nov 6, 2025
07d3251
Remove remarks with internal property
MartinSarkany Nov 6, 2025
c293521
Minor improvements
MartinSarkany Nov 6, 2025
a432219
Remove leftover file
MartinSarkany Nov 6, 2025
4a86df4
Refactor JSON array splitting
MartinSarkany Nov 6, 2025
7ae1eb3
Remove unused package reference
MartinSarkany Nov 6, 2025
6eb8dc0
Add container extension method
MartinSarkany Nov 7, 2025
bc0b54b
Remove RecyclableMemoryStream
MartinSarkany Nov 7, 2025
2fd635c
Remove unused ENCRYPTION_CUSTOM_PREVIEW
MartinSarkany Nov 10, 2025
6e2b9c9
Merge branch 'master' into feature/encryption-feed-iterator-rework
MartinSarkany Nov 10, 2025
8400ca8
Reintroduce RecyclableMemoryStream
MartinSarkany Nov 11, 2025
e7d15b2
Merge branch 'master' into feature/encryption-feed-iterator-rework
MartinSarkany Nov 17, 2025
73be399
Make EncryptionProcessor.EncryptAsync() private
MartinSarkany Nov 17, 2025
ff3c26f
Merge branch 'master' into feature/encryption-feed-iterator-rework
MartinSarkany Mar 12, 2026
fa9a18a
Merge branch 'master' into feature/encryption-feed-iterator-rework
MartinSarkany Mar 16, 2026
be04a6d
PR review improvements
MartinSarkany Mar 16, 2026
6ebb38a
Minor improvement
MartinSarkany Mar 17, 2026
d25a323
Merge branch 'master' into feature/encryption-feed-iterator-rework
MartinSarkany Apr 21, 2026
c2e1243
Merge branch 'master' into feature/encryption-feed-iterator-rework
kundadebdatta Apr 22, 2026
fbb0edb
Merge branch 'main' into feature/encryption-feed-iterator-rework
MartinSarkany May 4, 2026
0779f51
Replace RecyclableMemoryStream with ArrayPool-backed streams
MartinSarkany May 4, 2026
b669b32
Minor improvements
MartinSarkany May 5, 2026
a1f2640
Remove unused code
MartinSarkany May 6, 2026
29b6923
Revert unnecessary changes
MartinSarkany May 6, 2026
56bdc45
Merge branch 'main' into feature/encryption-feed-iterator-rework
MartinSarkany May 6, 2026
64303e5
Minor improvements
MartinSarkany May 7, 2026
1528c33
Merge branch 'main' into feature/encryption-feed-iterator-rework
kundadebdatta May 10, 2026
338871d
[Internal] DTS: Adds retries in DTS when isRetriable is true and on t…
Meghana-Palaparthi May 12, 2026
333a746
Simplify GetJsonProcessor() calls in EncryptionContainer
MartinSarkany May 13, 2026
151d94b
Merge branch 'main' into feature/encryption-feed-iterator-rework
MartinSarkany May 28, 2026
618d601
Revert accidental changes
MartinSarkany May 28, 2026
f8f5db5
Merge branch 'main' into feature/encryption-feed-iterator-rework
adamnova May 29, 2026
d306148
Code review improvements
MartinSarkany May 29, 2026
e09e0bd
Merge branch 'main' into feature/encryption-feed-iterator-rework
kundadebdatta Jun 3, 2026
6e0d6d5
[Client encryption] Fixes orphan PooledMemoryStream leak when feed sp…
adamnova Jun 3, 2026
38e6e38
[Client encryption] Fixes DEK-id regression, best-effort dispose casc…
adamnova Jun 3, 2026
1bd80cd
[Internal] Client Encryption: Adds intent comment on duplicate-Docume…
adamnova Jun 3, 2026
5cecb2b
[Internal] Client Encryption: Adds MDE stream-decrypt path coverage test
adamnova Jun 4, 2026
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
Expand Up @@ -303,6 +303,9 @@ public override void Write(byte[] buffer, int offset, int count)

#if NET8_0_OR_GREATER
public override void Write(ReadOnlySpan<byte> buffer)
#else
public void Write(ReadOnlySpan<byte> buffer)
#endif
{
this.EnsureNotDisposed();

Expand Down Expand Up @@ -332,7 +335,6 @@ public override void Write(ReadOnlySpan<byte> buffer)
this.length = this.position;
}
}
#endif

public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

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

/// <summary>
Expand Down Expand Up @@ -71,13 +72,23 @@ namespace Microsoft.Azure.Cosmos.Encryption.Custom
/// ]]>
/// </code>
/// </example>
public abstract class DecryptableItem
public abstract class DecryptableItem : IAsyncDisposable
{
/// <summary>
/// Decrypts and deserializes the content.
/// </summary>
/// <typeparam name="T">The type of item to be returned.</typeparam>
/// <returns>The requested item and the decryption related context.</returns>
public abstract Task<(T, DecryptionContext)> GetItemAsync<T>();

/// <summary>
/// Disposes any resources held by the decryptable item.
/// Default implementation does nothing. Override in derived classes that hold disposable resources.
/// </summary>
/// <returns>A ValueTask representing the asynchronous dispose operation.</returns>
public virtual ValueTask DisposeAsync()
{
return default;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ internal sealed class EncryptionContainer : Container
{
private readonly Container container;

internal JsonProcessor DefaultJsonProcessor { get; private set; } = JsonProcessor.Newtonsoft;

public CosmosSerializer CosmosSerializer { get; }

public Encryptor Encryptor { get; }
Expand Down Expand Up @@ -631,7 +633,10 @@ public override FeedIterator<T> GetItemQueryIterator<T>(
queryDefinition,
continuationToken,
requestOptions),
this.ResponseFactory);
this.ResponseFactory,
this.Encryptor,
this.CosmosSerializer,
requestOptions?.GetJsonProcessor(this.DefaultJsonProcessor) ?? this.DefaultJsonProcessor);
}

public override FeedIterator<T> GetItemQueryIterator<T>(
Expand All @@ -644,7 +649,10 @@ public override FeedIterator<T> GetItemQueryIterator<T>(
queryText,
continuationToken,
requestOptions),
this.ResponseFactory);
this.ResponseFactory,
this.Encryptor,
this.CosmosSerializer,
requestOptions?.GetJsonProcessor(this.DefaultJsonProcessor) ?? this.DefaultJsonProcessor);
}

public override Task<ContainerResponse> ReadContainerAsync(
Expand Down Expand Up @@ -724,7 +732,7 @@ public override FeedIterator GetItemQueryStreamIterator(
continuationToken,
requestOptions),
this.Encryptor,
this.CosmosSerializer);
requestOptions?.GetJsonProcessor(this.DefaultJsonProcessor) ?? this.DefaultJsonProcessor);
}

public override FeedIterator GetItemQueryStreamIterator(
Expand All @@ -738,7 +746,7 @@ public override FeedIterator GetItemQueryStreamIterator(
continuationToken,
requestOptions),
this.Encryptor,
this.CosmosSerializer);
requestOptions?.GetJsonProcessor(this.DefaultJsonProcessor) ?? this.DefaultJsonProcessor);
}

public override Task<ThroughputResponse> ReplaceThroughputAsync(
Expand Down Expand Up @@ -771,7 +779,7 @@ public override FeedIterator GetItemQueryStreamIterator(
continuationToken,
requestOptions),
this.Encryptor,
this.CosmosSerializer);
requestOptions?.GetJsonProcessor(this.DefaultJsonProcessor) ?? this.DefaultJsonProcessor);
}

public override FeedIterator<T> GetItemQueryIterator<T>(
Expand All @@ -786,7 +794,10 @@ public override FeedIterator<T> GetItemQueryIterator<T>(
queryDefinition,
continuationToken,
requestOptions),
this.ResponseFactory);
this.ResponseFactory,
this.Encryptor,
this.CosmosSerializer,
requestOptions?.GetJsonProcessor(this.DefaultJsonProcessor) ?? this.DefaultJsonProcessor);
}

public override ChangeFeedEstimator GetChangeFeedEstimator(
Expand All @@ -807,7 +818,7 @@ public override FeedIterator GetChangeFeedStreamIterator(
changeFeedMode,
changeFeedRequestOptions),
this.Encryptor,
this.CosmosSerializer);
changeFeedRequestOptions?.GetJsonProcessor(this.DefaultJsonProcessor) ?? this.DefaultJsonProcessor);
}

public override FeedIterator<T> GetChangeFeedIterator<T>(
Expand All @@ -820,7 +831,10 @@ public override FeedIterator<T> GetChangeFeedIterator<T>(
changeFeedStartFrom,
changeFeedMode,
changeFeedRequestOptions),
this.ResponseFactory);
this.ResponseFactory,
this.Encryptor,
this.CosmosSerializer,
changeFeedRequestOptions?.GetJsonProcessor(this.DefaultJsonProcessor) ?? this.DefaultJsonProcessor);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the last ?? this.DefaultJsonProcessor should not be needed, the extension method already returns the fallback processor if no override is found in the request options

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}

public override Task<ItemResponse<T>> PatchItemAsync<T>(
Expand Down Expand Up @@ -917,6 +931,7 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder(
Stream decryptedChanges = await EncryptionProcessor.DeserializeAndDecryptResponseAsync(
changes,
this.Encryptor,
this.DefaultJsonProcessor,
cancellationToken);

// Call the original passed in delegate
Expand All @@ -939,6 +954,7 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManu
Stream decryptedChanges = await EncryptionProcessor.DeserializeAndDecryptResponseAsync(
changes,
this.Encryptor,
this.DefaultJsonProcessor,
cancellationToken);

// Call the original passed in delegate
Expand Down Expand Up @@ -1008,6 +1024,13 @@ public override Task<bool> IsFeedRangePartOfAsync(
}
#endif

#if NET8_0_OR_GREATER
public void UseStreamingJsonProcessingByDefault()
{
this.DefaultJsonProcessor = JsonProcessor.Stream;
}
#endif

#if PREVIEW && SDKPROJECTREF
public override Task<SemanticRerankResult> SemanticRerankAsync(
string rerankContext,
Expand Down Expand Up @@ -1036,6 +1059,7 @@ private async Task<ResponseMessage> ReadManyItemsHelperAsync(
Stream decryptedContent = await EncryptionProcessor.DeserializeAndDecryptResponseAsync(
responseMessage.Content,
this.Encryptor,
readManyRequestOptions?.GetJsonProcessor(this.DefaultJsonProcessor) ?? this.DefaultJsonProcessor,
cancellationToken);

return new DecryptedResponseMessage(responseMessage, decryptedContent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ public static Container WithEncryptor(
encryptor);
}

#if NET8_0_OR_GREATER
/// <summary>
/// Configures the specified <see cref="Container"/> to use streaming JSON processing by default.
/// </summary>
/// <param name="container">The <see cref="Container"/> instance to configure. Must be an <see cref="EncryptionContainer"/>.</param>
/// <returns>The configured <see cref="EncryptionContainer"/> instance.</returns>
/// <exception cref="NotSupportedException">Thrown if <paramref name="container"/> is not an <see cref="EncryptionContainer"/>.</exception>
public static Container UseStreamingJsonProcessingByDefault(this Container container)
{
if (container is not EncryptionContainer encryptionContainer)
{
throw new NotSupportedException($"{nameof(UseStreamingJsonProcessingByDefault)} is only supported with {nameof(EncryptionContainer)}.");
}

encryptionContainer.UseStreamingJsonProcessingByDefault();

return encryptionContainer;
}
#endif

/// <summary>
/// This method gets the FeedIterator from LINQ IQueryable to execute query asynchronously.
/// This will create the fresh new FeedIterator when called which will support decryption.
Expand Down Expand Up @@ -57,7 +77,10 @@ public static FeedIterator<T> ToEncryptionFeedIterator<T>(

return new EncryptionFeedIterator<T>(
(EncryptionFeedIterator)encryptionContainer.ToEncryptionStreamIterator(query),
encryptionContainer.ResponseFactory);
encryptionContainer.ResponseFactory,
encryptionContainer.Encryptor,
encryptionContainer.CosmosSerializer,
encryptionContainer.DefaultJsonProcessor);
}

/// <summary>
Expand Down Expand Up @@ -90,7 +113,7 @@ public static FeedIterator ToEncryptionStreamIterator<T>(
return new EncryptionFeedIterator(
query.ToStreamIterator(),
encryptionContainer.Encryptor,
encryptionContainer.CosmosSerializer);
encryptionContainer.DefaultJsonProcessor);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,32 @@

namespace Microsoft.Azure.Cosmos.Encryption.Custom
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;

internal sealed class EncryptionFeedIterator : FeedIterator
{
private readonly FeedIterator feedIterator;
private readonly Encryptor encryptor;
private readonly CosmosSerializer cosmosSerializer;
private readonly JsonProcessor jsonProcessor;

public EncryptionFeedIterator(
FeedIterator feedIterator,
Encryptor encryptor,
CosmosSerializer cosmosSerializer)
JsonProcessor jsonProcessor)
{
this.feedIterator = feedIterator ?? throw new System.ArgumentNullException(nameof(feedIterator));
this.encryptor = encryptor ?? throw new System.ArgumentNullException(nameof(encryptor));
this.jsonProcessor = jsonProcessor;
}

public EncryptionFeedIterator(
FeedIterator feedIterator,
Encryptor encryptor,
RequestOptions requestOptions)
: this(feedIterator, encryptor, requestOptions?.GetJsonProcessor() ?? JsonProcessor.Newtonsoft)
{
this.feedIterator = feedIterator;
this.encryptor = encryptor;
this.cosmosSerializer = cosmosSerializer;
}

public override bool HasMoreResults => this.feedIterator.HasMoreResults;
Expand All @@ -41,6 +46,7 @@ public override async Task<ResponseMessage> ReadNextAsync(CancellationToken canc
Stream decryptedContent = await EncryptionProcessor.DeserializeAndDecryptResponseAsync(
responseMessage.Content,
this.encryptor,
this.jsonProcessor,
cancellationToken);

return new DecryptedResponseMessage(responseMessage, decryptedContent);
Expand All @@ -50,49 +56,9 @@ public override async Task<ResponseMessage> ReadNextAsync(CancellationToken canc
}
}

public async Task<(ResponseMessage, List<T>)> ReadNextWithoutDecryptionAsync<T>(CancellationToken cancellationToken = default)
{
CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(options: null);
using (diagnosticsContext.CreateScope("FeedIterator.ReadNextWithoutDecryption"))
{
ResponseMessage responseMessage = await this.feedIterator.ReadNextAsync(cancellationToken);
List<T> decryptableContent = null;

if (responseMessage.IsSuccessStatusCode && responseMessage.Content != null)
{
decryptableContent = this.ConvertResponseToDecryptableItems<T>(
responseMessage.Content);

return (responseMessage, decryptableContent);
}

return (responseMessage, decryptableContent);
}
}

private List<T> ConvertResponseToDecryptableItems<T>(
Stream content)
internal Task<ResponseMessage> ReadNextRawResponseAsync(CancellationToken cancellationToken = default)
{
JObject contentJObj = EncryptionProcessor.BaseSerializer.FromStream<JObject>(content);

if (contentJObj.SelectToken(Constants.DocumentsResourcePropertyName) is not JArray documents)
{
throw new InvalidOperationException("Feed Response body contract was violated. Feed Response did not have an array of Documents.");
}

List<T> decryptableItems = new (documents.Count);

foreach (JToken value in documents)
{
DecryptableItemCore item = new (
value,
this.encryptor,
this.cosmosSerializer);

decryptableItems.Add((T)(object)item);
}

return decryptableItems;
return this.feedIterator.ReadNextAsync(cancellationToken);
}
}
}
Loading