-
Notifications
You must be signed in to change notification settings - Fork 630
Use ReadOnlyMemory<byte> for binary data to eliminate UTF-16 transcoding #1070
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
Merged
+400
−103
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
7dca602
Initial plan
Copilot 4287694
Update protocol types to use ReadOnlyMemory<byte> for binary data
Copilot 8e6fcf0
Fix test files to work with new binary data representation
Copilot e405dfc
Add cache invalidation when Blob/Data properties are set
Copilot ebe3eef
Apply performance optimizations using MemoryMarshal.TryGetArray
Copilot 1d76c11
Use GetBytes overload with offset and count parameters
Copilot 39213fb
Use Base64.DecodeFromUtf8 to avoid string intermediate during decoding
Copilot a4ba4a9
Merge branch 'main' into copilot/fix-binary-data-encoding
ericstj b4e4eaf
Fix merge conflict with DebuggerDisplay
ericstj 61f0dfa
Cleanup refactoring
ericstj 5de847c
Remove string from DataContent conversion
ericstj dc01608
Add factory methods to data backed types to efficiently initialize fr…
ericstj 2fc35d9
Update some tests to use factory methods
ericstj 14fedca
Fix test assertions in McpServerToolTests
ericstj b3eacff
Merge branch 'main' of https://github.com/modelcontextprotocol/csharp…
ericstj 0a3015f
Address code review feedback: rename Data to DecodedData, use span-ba…
Copilot e4f2816
Address code review: remove zero-copy claims, rename FromImage/FromAu…
Copilot d72030e
Apply feedback
ericstj d90737f
Remove ifdef and optimize an encoding method
ericstj 5860b78
Address feedback
ericstj 05e906c
Fix up doc comments
ericstj a9ad676
Merge branch 'main' of https://github.com/modelcontextprotocol/csharp…
ericstj 51acfd5
Fix a couple new tests
ericstj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| using System.Buffers; | ||
| using System.Buffers.Text; | ||
| using System.Diagnostics; | ||
| using System.Text; | ||
|
|
||
| namespace ModelContextProtocol; | ||
|
|
||
| /// <summary>Provides helper methods for encoding operations.</summary> | ||
| internal static class EncodingUtilities | ||
| { | ||
| /// <summary> | ||
| /// Converts UTF-16 characters to UTF-8 bytes without intermediate string allocations. | ||
| /// </summary> | ||
| /// <param name="utf16">The UTF-16 character span to convert.</param> | ||
| /// <returns>A byte array containing the UTF-8 encoded bytes.</returns> | ||
| public static byte[] GetUtf8Bytes(ReadOnlySpan<char> utf16) | ||
| { | ||
| byte[] bytes = new byte[Encoding.UTF8.GetByteCount(utf16)]; | ||
| Encoding.UTF8.GetBytes(utf16, bytes); | ||
| return bytes; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Encodes binary data to base64-encoded UTF-8 bytes. | ||
| /// </summary> | ||
| /// <param name="data">The binary data to encode.</param> | ||
| /// <returns>A ReadOnlyMemory containing the base64-encoded UTF-8 bytes.</returns> | ||
| public static ReadOnlyMemory<byte> EncodeToBase64Utf8(ReadOnlyMemory<byte> data) | ||
| { | ||
| int maxLength = Base64.GetMaxEncodedToUtf8Length(data.Length); | ||
| byte[] buffer = new byte[maxLength]; | ||
| OperationStatus status = Base64.EncodeToUtf8(data.Span, buffer, out _, out int bytesWritten); | ||
| Debug.Assert(status == OperationStatus.Done, "Base64 encoding should succeed for valid input data"); | ||
| Debug.Assert(bytesWritten == buffer.Length, "Base64 encoding should always produce the same length as the max length"); | ||
| return buffer.AsMemory(0, bytesWritten); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Decodes base64-encoded UTF-8 bytes to binary data. | ||
| /// </summary> | ||
| /// <param name="base64Data">The base64-encoded UTF-8 bytes to decode.</param> | ||
| /// <returns>A ReadOnlyMemory containing the decoded binary data.</returns> | ||
| /// <exception cref="FormatException">The input is not valid base64 data.</exception> | ||
| public static ReadOnlyMemory<byte> DecodeFromBase64Utf8(ReadOnlyMemory<byte> base64Data) | ||
| { | ||
| int maxLength = Base64.GetMaxDecodedFromUtf8Length(base64Data.Length); | ||
| byte[] buffer = new byte[maxLength]; | ||
| if (Base64.DecodeFromUtf8(base64Data.Span, buffer, out _, out int bytesWritten) == OperationStatus.Done) | ||
| { | ||
| // Base64 decoding may produce fewer bytes than the max length, due to whitespace anywhere in the string or padding. | ||
| Debug.Assert(bytesWritten <= buffer.Length, "Base64 decoding should never produce more bytes than the max length"); | ||
| return buffer.AsMemory(0, bytesWritten); | ||
| } | ||
| else | ||
| { | ||
| throw new FormatException("Invalid base64 data"); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| #if !NET | ||
|
|
||
| namespace System.Text; | ||
|
|
||
| internal static class EncodingExtensions | ||
| { | ||
| /// <summary> | ||
| /// Gets the number of bytes required to encode the specified characters. | ||
| /// </summary> | ||
| public static int GetByteCount(this Encoding encoding, ReadOnlySpan<char> chars) | ||
| { | ||
| if (chars.IsEmpty) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| unsafe | ||
| { | ||
| fixed (char* charsPtr = chars) | ||
| { | ||
| return encoding.GetByteCount(charsPtr, chars.Length); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Encodes the specified characters into the specified byte span. | ||
| /// </summary> | ||
| public static int GetBytes(this Encoding encoding, ReadOnlySpan<char> chars, Span<byte> bytes) | ||
| { | ||
| if (chars.IsEmpty) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| unsafe | ||
| { | ||
| fixed (char* charsPtr = chars) | ||
| fixed (byte* bytesPtr = bytes) | ||
| { | ||
| return encoding.GetBytes(charsPtr, chars.Length, bytesPtr, bytes.Length); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.