From 76e3ffd9f0ff32090f36d1ca6ea1491e71024b27 Mon Sep 17 00:00:00 2001 From: Jensaarai Date: Fri, 9 Apr 2021 19:36:32 -0700 Subject: [PATCH] C#: Add ParseFrom/MergeFrom ReadOnlySpan Address #7885 --- .../CodedInputStreamTest.cs | 33 +++++++++++++++++++ .../src/Google.Protobuf/MessageExtensions.cs | 19 +++++++++++ csharp/src/Google.Protobuf/MessageParser.cs | 26 +++++++++++++++ csharp/src/Google.Protobuf/ParseContext.cs | 23 +++++++++++++ 4 files changed, 101 insertions(+) diff --git a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs index 0ad286f378..4335b88548 100644 --- a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs +++ b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs @@ -926,6 +926,39 @@ namespace Google.Protobuf } } + [Test] + public void TestParseFromSpan() + { + var testMessage = new TestAllTypes + { + RepeatedBool = { true, false }, + SingleDouble = 42.0d, + SingleString = "testing" + }; + + ReadOnlySpan messagesBytes = testMessage.ToByteArray().AsSpan(); + TestAllTypes parsedMessage = TestAllTypes.Parser.ParseFrom(messagesBytes); + + Assert.AreEqual(testMessage, parsedMessage); + } + + [Test] + public void TestMergeFromSpan() + { + var testMessage = new TestAllTypes + { + RepeatedBool = { true, false }, + SingleDouble = 42.0d, + SingleString = "testing" + }; + + ReadOnlySpan messagesBytes = testMessage.ToByteArray().AsSpan(); + var mergedMessage = new TestAllTypes(); + mergedMessage.MergeFrom(messagesBytes); + + Assert.AreEqual(testMessage, mergedMessage); + } + /// A serialized big message private static byte[] GenerateBigSerializedMessage() { diff --git a/csharp/src/Google.Protobuf/MessageExtensions.cs b/csharp/src/Google.Protobuf/MessageExtensions.cs index 36a9df7286..c4b3f82343 100644 --- a/csharp/src/Google.Protobuf/MessageExtensions.cs +++ b/csharp/src/Google.Protobuf/MessageExtensions.cs @@ -79,6 +79,15 @@ namespace Google.Protobuf public static void MergeFrom(this IMessage message, Stream input) => MergeFrom(message, input, false, null); + /// + /// Merges data from the given span into an existing message. + /// + /// The message to merge the data into. + /// Span containing the data to merge, which must be protobuf-encoded binary data. + [SecuritySafeCritical] + public static void MergeFrom(this IMessage message, ReadOnlySpan span) => + MergeFrom(message, span, false, null); + /// /// Merges length-delimited data from the given stream into an existing message. /// @@ -294,6 +303,16 @@ namespace Google.Protobuf ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref ctx.state); } + [SecuritySafeCritical] + internal static void MergeFrom(this IMessage message, ReadOnlySpan data, bool discardUnknownFields, ExtensionRegistry registry) + { + ParseContext.Initialize(data, out ParseContext ctx); + ctx.DiscardUnknownFields = discardUnknownFields; + ctx.ExtensionRegistry = registry; + ParsingPrimitivesMessages.ReadRawMessage(ref ctx, message); + ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref ctx.state); + } + internal static void MergeDelimitedFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry) { ProtoPreconditions.CheckNotNull(message, "message"); diff --git a/csharp/src/Google.Protobuf/MessageParser.cs b/csharp/src/Google.Protobuf/MessageParser.cs index f8b26c2348..30a25a8698 100644 --- a/csharp/src/Google.Protobuf/MessageParser.cs +++ b/csharp/src/Google.Protobuf/MessageParser.cs @@ -128,6 +128,19 @@ namespace Google.Protobuf return message; } + /// + /// Parses a message from the given span. + /// + /// The data to parse. + /// The parsed message. + [SecuritySafeCritical] + public IMessage ParseFrom(ReadOnlySpan data) + { + IMessage message = factory(); + message.MergeFrom(data, DiscardUnknownFields, Extensions); + return message; + } + /// /// Parses a length-delimited message from the given stream. /// @@ -315,6 +328,19 @@ namespace Google.Protobuf return message; } + /// + /// Parses a message from the given span. + /// + /// The data to parse. + /// The parsed message. + [SecuritySafeCritical] + public new T ParseFrom(ReadOnlySpan data) + { + T message = factory(); + message.MergeFrom(data, DiscardUnknownFields, Extensions); + return message; + } + /// /// Parses a length-delimited message from the given stream. /// diff --git a/csharp/src/Google.Protobuf/ParseContext.cs b/csharp/src/Google.Protobuf/ParseContext.cs index bf46236565..4d185d9fd6 100644 --- a/csharp/src/Google.Protobuf/ParseContext.cs +++ b/csharp/src/Google.Protobuf/ParseContext.cs @@ -104,6 +104,29 @@ namespace Google.Protobuf ctx.state.ExtensionRegistry = null; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Initialize(ReadOnlySpan input, out ParseContext ctx) + { + Initialize(input, DefaultRecursionLimit, out ctx); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Initialize(ReadOnlySpan input, int recursionLimit, out ParseContext ctx) + { + ctx.buffer = input; + ctx.state = default; + ctx.state.lastTag = 0; + ctx.state.recursionDepth = 0; + ctx.state.sizeLimit = DefaultSizeLimit; + ctx.state.recursionLimit = recursionLimit; + ctx.state.currentLimit = int.MaxValue; + ctx.state.bufferPos = 0; + ctx.state.bufferSize = input.Length; + + ctx.state.DiscardUnknownFields = false; + ctx.state.ExtensionRegistry = null; + } + /// /// Returns the last tag read, or 0 if no tags have been read or we've read beyond /// the end of the input.