xref: /aosp_15_r20/external/cronet/third_party/protobuf/csharp/src/Google.Protobuf/MessageExtensions.cs (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2015 Google Inc.  All rights reserved.
4 // https://developers.google.com/protocol-buffers/
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are
8 // met:
9 //
10 //     * Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 //     * Redistributions in binary form must reproduce the above
13 // copyright notice, this list of conditions and the following disclaimer
14 // in the documentation and/or other materials provided with the
15 // distribution.
16 //     * Neither the name of Google Inc. nor the names of its
17 // contributors may be used to endorse or promote products derived from
18 // this software without specific prior written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #endregion
32 
33 using Google.Protobuf.Reflection;
34 using System.Buffers;
35 using System.Collections;
36 using System;
37 using System.IO;
38 using System.Linq;
39 using System.Security;
40 
41 namespace Google.Protobuf
42 {
43     /// <summary>
44     /// Extension methods on <see cref="IMessage"/> and <see cref="IMessage{T}"/>.
45     /// </summary>
46     public static class MessageExtensions
47     {
48         /// <summary>
49         /// Merges data from the given byte array into an existing message.
50         /// </summary>
51         /// <param name="message">The message to merge the data into.</param>
52         /// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param>
MergeFrom(this IMessage message, byte[] data)53         public static void MergeFrom(this IMessage message, byte[] data) =>
54             MergeFrom(message, data, false, null);
55 
56         /// <summary>
57         /// Merges data from the given byte array slice into an existing message.
58         /// </summary>
59         /// <param name="message">The message to merge the data into.</param>
60         /// <param name="data">The data containing the slice to merge, which must be protobuf-encoded binary data.</param>
61         /// <param name="offset">The offset of the slice to merge.</param>
62         /// <param name="length">The length of the slice to merge.</param>
MergeFrom(this IMessage message, byte[] data, int offset, int length)63         public static void MergeFrom(this IMessage message, byte[] data, int offset, int length) =>
64             MergeFrom(message, data, offset, length, false, null);
65 
66         /// <summary>
67         /// Merges data from the given byte string into an existing message.
68         /// </summary>
69         /// <param name="message">The message to merge the data into.</param>
70         /// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param>
MergeFrom(this IMessage message, ByteString data)71         public static void MergeFrom(this IMessage message, ByteString data) =>
72             MergeFrom(message, data, false, null);
73 
74         /// <summary>
75         /// Merges data from the given stream into an existing message.
76         /// </summary>
77         /// <param name="message">The message to merge the data into.</param>
78         /// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param>
MergeFrom(this IMessage message, Stream input)79         public static void MergeFrom(this IMessage message, Stream input) =>
80             MergeFrom(message, input, false, null);
81 
82         /// <summary>
83         /// Merges data from the given span into an existing message.
84         /// </summary>
85         /// <param name="message">The message to merge the data into.</param>
86         /// <param name="span">Span containing the data to merge, which must be protobuf-encoded binary data.</param>
87         [SecuritySafeCritical]
MergeFrom(this IMessage message, ReadOnlySpan<byte> span)88         public static void MergeFrom(this IMessage message, ReadOnlySpan<byte> span) =>
89             MergeFrom(message, span, false, null);
90 
91         /// <summary>
92         /// Merges length-delimited data from the given stream into an existing message.
93         /// </summary>
94         /// <remarks>
95         /// The stream is expected to contain a length and then the data. Only the amount of data
96         /// specified by the length will be consumed.
97         /// </remarks>
98         /// <param name="message">The message to merge the data into.</param>
99         /// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param>
MergeDelimitedFrom(this IMessage message, Stream input)100         public static void MergeDelimitedFrom(this IMessage message, Stream input) =>
101             MergeDelimitedFrom(message, input, false, null);
102 
103         /// <summary>
104         /// Converts the given message into a byte array in protobuf encoding.
105         /// </summary>
106         /// <param name="message">The message to convert.</param>
107         /// <returns>The message data as a byte array.</returns>
ToByteArray(this IMessage message)108         public static byte[] ToByteArray(this IMessage message)
109         {
110             ProtoPreconditions.CheckNotNull(message, "message");
111             byte[] result = new byte[message.CalculateSize()];
112             CodedOutputStream output = new CodedOutputStream(result);
113             message.WriteTo(output);
114             output.CheckNoSpaceLeft();
115             return result;
116         }
117 
118         /// <summary>
119         /// Writes the given message data to the given stream in protobuf encoding.
120         /// </summary>
121         /// <param name="message">The message to write to the stream.</param>
122         /// <param name="output">The stream to write to.</param>
WriteTo(this IMessage message, Stream output)123         public static void WriteTo(this IMessage message, Stream output)
124         {
125             ProtoPreconditions.CheckNotNull(message, "message");
126             ProtoPreconditions.CheckNotNull(output, "output");
127             CodedOutputStream codedOutput = new CodedOutputStream(output);
128             message.WriteTo(codedOutput);
129             codedOutput.Flush();
130         }
131 
132         /// <summary>
133         /// Writes the length and then data of the given message to a stream.
134         /// </summary>
135         /// <param name="message">The message to write.</param>
136         /// <param name="output">The output stream to write to.</param>
WriteDelimitedTo(this IMessage message, Stream output)137         public static void WriteDelimitedTo(this IMessage message, Stream output)
138         {
139             ProtoPreconditions.CheckNotNull(message, "message");
140             ProtoPreconditions.CheckNotNull(output, "output");
141             CodedOutputStream codedOutput = new CodedOutputStream(output);
142             codedOutput.WriteLength(message.CalculateSize());
143             message.WriteTo(codedOutput);
144             codedOutput.Flush();
145         }
146 
147         /// <summary>
148         /// Converts the given message into a byte string in protobuf encoding.
149         /// </summary>
150         /// <param name="message">The message to convert.</param>
151         /// <returns>The message data as a byte string.</returns>
ToByteString(this IMessage message)152         public static ByteString ToByteString(this IMessage message)
153         {
154             ProtoPreconditions.CheckNotNull(message, "message");
155             return ByteString.AttachBytes(message.ToByteArray());
156         }
157 
158         /// <summary>
159         /// Writes the given message data to the given buffer writer in protobuf encoding.
160         /// </summary>
161         /// <param name="message">The message to write to the stream.</param>
162         /// <param name="output">The stream to write to.</param>
163         [SecuritySafeCritical]
WriteTo(this IMessage message, IBufferWriter<byte> output)164         public static void WriteTo(this IMessage message, IBufferWriter<byte> output)
165         {
166             ProtoPreconditions.CheckNotNull(message, nameof(message));
167             ProtoPreconditions.CheckNotNull(output, nameof(output));
168 
169             WriteContext.Initialize(output, out WriteContext ctx);
170             WritingPrimitivesMessages.WriteRawMessage(ref ctx, message);
171             ctx.Flush();
172         }
173 
174         /// <summary>
175         /// Writes the given message data to the given span in protobuf encoding.
176         /// The size of the destination span needs to fit the serialized size
177         /// of the message exactly, otherwise an exception is thrown.
178         /// </summary>
179         /// <param name="message">The message to write to the stream.</param>
180         /// <param name="output">The span to write to. Size must match size of the message exactly.</param>
181         [SecuritySafeCritical]
WriteTo(this IMessage message, Span<byte> output)182         public static void WriteTo(this IMessage message, Span<byte> output)
183         {
184             ProtoPreconditions.CheckNotNull(message, nameof(message));
185 
186             WriteContext.Initialize(ref output, out WriteContext ctx);
187             WritingPrimitivesMessages.WriteRawMessage(ref ctx, message);
188             ctx.CheckNoSpaceLeft();
189         }
190 
191         /// <summary>
192         /// Checks if all required fields in a message have values set. For proto3 messages, this returns true
193         /// </summary>
IsInitialized(this IMessage message)194         public static bool IsInitialized(this IMessage message)
195         {
196             if (message.Descriptor.File.Syntax == Syntax.Proto3)
197             {
198                 return true;
199             }
200 
201             if (!message.Descriptor.IsExtensionsInitialized(message))
202             {
203                 return false;
204             }
205 
206             return message.Descriptor
207                 .Fields
208                 .InDeclarationOrder()
209                 .All(f =>
210                 {
211                     if (f.IsMap)
212                     {
213                         var valueField = f.MessageType.Fields[2];
214                         if (valueField.FieldType == FieldType.Message)
215                         {
216                             var map = (IDictionary)f.Accessor.GetValue(message);
217                             return map.Values.Cast<IMessage>().All(IsInitialized);
218                         }
219                         else
220                         {
221                             return true;
222                         }
223                     }
224                     else if (f.IsRepeated && f.FieldType == FieldType.Message || f.FieldType == FieldType.Group)
225                     {
226                         var enumerable = (IEnumerable)f.Accessor.GetValue(message);
227                         return enumerable.Cast<IMessage>().All(IsInitialized);
228                     }
229                     else if (f.FieldType == FieldType.Message || f.FieldType == FieldType.Group)
230                     {
231                         if (f.Accessor.HasValue(message))
232                         {
233                             return ((IMessage)f.Accessor.GetValue(message)).IsInitialized();
234                         }
235                         else
236                         {
237                             return !f.IsRequired;
238                         }
239                     }
240                     else if (f.IsRequired)
241                     {
242                         return f.Accessor.HasValue(message);
243                     }
244                     else
245                     {
246                         return true;
247                     }
248                 });
249         }
250 
251         // Implementations allowing unknown fields to be discarded.
MergeFrom(this IMessage message, byte[] data, bool discardUnknownFields, ExtensionRegistry registry)252         internal static void MergeFrom(this IMessage message, byte[] data, bool discardUnknownFields, ExtensionRegistry registry)
253         {
254             ProtoPreconditions.CheckNotNull(message, "message");
255             ProtoPreconditions.CheckNotNull(data, "data");
256             CodedInputStream input = new CodedInputStream(data);
257             input.DiscardUnknownFields = discardUnknownFields;
258             input.ExtensionRegistry = registry;
259             message.MergeFrom(input);
260             input.CheckReadEndOfStreamTag();
261         }
262 
MergeFrom(this IMessage message, byte[] data, int offset, int length, bool discardUnknownFields, ExtensionRegistry registry)263         internal static void MergeFrom(this IMessage message, byte[] data, int offset, int length, bool discardUnknownFields, ExtensionRegistry registry)
264         {
265             ProtoPreconditions.CheckNotNull(message, "message");
266             ProtoPreconditions.CheckNotNull(data, "data");
267             CodedInputStream input = new CodedInputStream(data, offset, length);
268             input.DiscardUnknownFields = discardUnknownFields;
269             input.ExtensionRegistry = registry;
270             message.MergeFrom(input);
271             input.CheckReadEndOfStreamTag();
272         }
273 
MergeFrom(this IMessage message, ByteString data, bool discardUnknownFields, ExtensionRegistry registry)274         internal static void MergeFrom(this IMessage message, ByteString data, bool discardUnknownFields, ExtensionRegistry registry)
275         {
276             ProtoPreconditions.CheckNotNull(message, "message");
277             ProtoPreconditions.CheckNotNull(data, "data");
278             CodedInputStream input = data.CreateCodedInput();
279             input.DiscardUnknownFields = discardUnknownFields;
280             input.ExtensionRegistry = registry;
281             message.MergeFrom(input);
282             input.CheckReadEndOfStreamTag();
283         }
284 
MergeFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)285         internal static void MergeFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)
286         {
287             ProtoPreconditions.CheckNotNull(message, "message");
288             ProtoPreconditions.CheckNotNull(input, "input");
289             CodedInputStream codedInput = new CodedInputStream(input);
290             codedInput.DiscardUnknownFields = discardUnknownFields;
291             codedInput.ExtensionRegistry = registry;
292             message.MergeFrom(codedInput);
293             codedInput.CheckReadEndOfStreamTag();
294         }
295 
296         [SecuritySafeCritical]
MergeFrom(this IMessage message, ReadOnlySequence<byte> data, bool discardUnknownFields, ExtensionRegistry registry)297         internal static void MergeFrom(this IMessage message, ReadOnlySequence<byte> data, bool discardUnknownFields, ExtensionRegistry registry)
298         {
299             ParseContext.Initialize(data, out ParseContext ctx);
300             ctx.DiscardUnknownFields = discardUnknownFields;
301             ctx.ExtensionRegistry = registry;
302             ParsingPrimitivesMessages.ReadRawMessage(ref ctx, message);
303             ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref ctx.state);
304         }
305 
306         [SecuritySafeCritical]
MergeFrom(this IMessage message, ReadOnlySpan<byte> data, bool discardUnknownFields, ExtensionRegistry registry)307         internal static void MergeFrom(this IMessage message, ReadOnlySpan<byte> data, bool discardUnknownFields, ExtensionRegistry registry)
308         {
309             ParseContext.Initialize(data, out ParseContext ctx);
310             ctx.DiscardUnknownFields = discardUnknownFields;
311             ctx.ExtensionRegistry = registry;
312             ParsingPrimitivesMessages.ReadRawMessage(ref ctx, message);
313             ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref ctx.state);
314         }
315 
MergeDelimitedFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)316         internal static void MergeDelimitedFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)
317         {
318             ProtoPreconditions.CheckNotNull(message, "message");
319             ProtoPreconditions.CheckNotNull(input, "input");
320             int size = (int) CodedInputStream.ReadRawVarint32(input);
321             Stream limitedStream = new LimitedInputStream(input, size);
322             MergeFrom(message, limitedStream, discardUnknownFields, registry);
323         }
324     }
325 }
326