• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2015 Google Inc.  All rights reserved.
4 //
5 // Use of this source code is governed by a BSD-style
6 // license that can be found in the LICENSE file or at
7 // https://developers.google.com/open-source/licenses/bsd
8 #endregion
9 
10 using Google.Protobuf.Reflection;
11 using System.Buffers;
12 using System.Collections;
13 using System;
14 using System.IO;
15 using System.Linq;
16 using System.Security;
17 
18 namespace Google.Protobuf
19 {
20     /// <summary>
21     /// Extension methods on <see cref="IMessage"/> and <see cref="IMessage{T}"/>.
22     /// </summary>
23     public static class MessageExtensions
24     {
25         /// <summary>
26         /// Merges data from the given byte array into an existing message.
27         /// </summary>
28         /// <param name="message">The message to merge the data into.</param>
29         /// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param>
MergeFrom(this IMessage message, byte[] data)30         public static void MergeFrom(this IMessage message, byte[] data) =>
31             MergeFrom(message, data, false, null);
32 
33         /// <summary>
34         /// Merges data from the given byte array slice into an existing message.
35         /// </summary>
36         /// <param name="message">The message to merge the data into.</param>
37         /// <param name="data">The data containing the slice to merge, which must be protobuf-encoded binary data.</param>
38         /// <param name="offset">The offset of the slice to merge.</param>
39         /// <param name="length">The length of the slice to merge.</param>
MergeFrom(this IMessage message, byte[] data, int offset, int length)40         public static void MergeFrom(this IMessage message, byte[] data, int offset, int length) =>
41             MergeFrom(message, data, offset, length, false, null);
42 
43         /// <summary>
44         /// Merges data from the given byte string into an existing message.
45         /// </summary>
46         /// <param name="message">The message to merge the data into.</param>
47         /// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param>
MergeFrom(this IMessage message, ByteString data)48         public static void MergeFrom(this IMessage message, ByteString data) =>
49             MergeFrom(message, data, false, null);
50 
51         /// <summary>
52         /// Merges data from the given stream into an existing message.
53         /// </summary>
54         /// <param name="message">The message to merge the data into.</param>
55         /// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param>
MergeFrom(this IMessage message, Stream input)56         public static void MergeFrom(this IMessage message, Stream input) =>
57             MergeFrom(message, input, false, null);
58 
59         /// <summary>
60         /// Merges data from the given span into an existing message.
61         /// </summary>
62         /// <param name="message">The message to merge the data into.</param>
63         /// <param name="span">Span containing the data to merge, which must be protobuf-encoded binary data.</param>
64         [SecuritySafeCritical]
MergeFrom(this IMessage message, ReadOnlySpan<byte> span)65         public static void MergeFrom(this IMessage message, ReadOnlySpan<byte> span) =>
66             MergeFrom(message, span, false, null);
67 
68         /// <summary>
69         /// Merges data from the given sequence into an existing message.
70         /// </summary>
71         /// <param name="message">The message to merge the data into.</param>
72         /// <param name="sequence">Sequence from the specified data to merge, which must be protobuf-encoded binary data.</param>
73         [SecuritySafeCritical]
MergeFrom(this IMessage message, ReadOnlySequence<byte> sequence)74         public static void MergeFrom(this IMessage message, ReadOnlySequence<byte> sequence) =>
75             MergeFrom(message, sequence, false, null);
76 
77         /// <summary>
78         /// Merges length-delimited data from the given stream into an existing message.
79         /// </summary>
80         /// <remarks>
81         /// The stream is expected to contain a length and then the data. Only the amount of data
82         /// specified by the length will be consumed.
83         /// </remarks>
84         /// <param name="message">The message to merge the data into.</param>
85         /// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param>
MergeDelimitedFrom(this IMessage message, Stream input)86         public static void MergeDelimitedFrom(this IMessage message, Stream input) =>
87             MergeDelimitedFrom(message, input, false, null);
88 
89         /// <summary>
90         /// Converts the given message into a byte array in protobuf encoding.
91         /// </summary>
92         /// <param name="message">The message to convert.</param>
93         /// <returns>The message data as a byte array.</returns>
ToByteArray(this IMessage message)94         public static byte[] ToByteArray(this IMessage message)
95         {
96             ProtoPreconditions.CheckNotNull(message, nameof(message));
97             byte[] result = new byte[message.CalculateSize()];
98             CodedOutputStream output = new CodedOutputStream(result);
99             message.WriteTo(output);
100             output.CheckNoSpaceLeft();
101             return result;
102         }
103 
104         /// <summary>
105         /// Writes the given message data to the given stream in protobuf encoding.
106         /// </summary>
107         /// <param name="message">The message to write to the stream.</param>
108         /// <param name="output">The stream to write to.</param>
WriteTo(this IMessage message, Stream output)109         public static void WriteTo(this IMessage message, Stream output)
110         {
111             ProtoPreconditions.CheckNotNull(message, nameof(message));
112             ProtoPreconditions.CheckNotNull(output, nameof(output));
113             CodedOutputStream codedOutput = new CodedOutputStream(output);
114             message.WriteTo(codedOutput);
115             codedOutput.Flush();
116         }
117 
118         /// <summary>
119         /// Writes the length and then data of the given message to a stream.
120         /// </summary>
121         /// <param name="message">The message to write.</param>
122         /// <param name="output">The output stream to write to.</param>
WriteDelimitedTo(this IMessage message, Stream output)123         public static void WriteDelimitedTo(this IMessage message, Stream output)
124         {
125             ProtoPreconditions.CheckNotNull(message, nameof(message));
126             ProtoPreconditions.CheckNotNull(output, nameof(output));
127             CodedOutputStream codedOutput = new CodedOutputStream(output);
128             codedOutput.WriteLength(message.CalculateSize());
129             message.WriteTo(codedOutput);
130             codedOutput.Flush();
131         }
132 
133         /// <summary>
134         /// Converts the given message into a byte string in protobuf encoding.
135         /// </summary>
136         /// <param name="message">The message to convert.</param>
137         /// <returns>The message data as a byte string.</returns>
ToByteString(this IMessage message)138         public static ByteString ToByteString(this IMessage message)
139         {
140             ProtoPreconditions.CheckNotNull(message, nameof(message));
141             return ByteString.AttachBytes(message.ToByteArray());
142         }
143 
144         /// <summary>
145         /// Writes the given message data to the given buffer writer in protobuf encoding.
146         /// </summary>
147         /// <param name="message">The message to write to the stream.</param>
148         /// <param name="output">The stream to write to.</param>
149         [SecuritySafeCritical]
WriteTo(this IMessage message, IBufferWriter<byte> output)150         public static void WriteTo(this IMessage message, IBufferWriter<byte> output)
151         {
152             ProtoPreconditions.CheckNotNull(message, nameof(message));
153             ProtoPreconditions.CheckNotNull(output, nameof(output));
154 
155             WriteContext.Initialize(output, out WriteContext ctx);
156             WritingPrimitivesMessages.WriteRawMessage(ref ctx, message);
157             ctx.Flush();
158         }
159 
160         /// <summary>
161         /// Writes the given message data to the given span in protobuf encoding.
162         /// The size of the destination span needs to fit the serialized size
163         /// of the message exactly, otherwise an exception is thrown.
164         /// </summary>
165         /// <param name="message">The message to write to the stream.</param>
166         /// <param name="output">The span to write to. Size must match size of the message exactly.</param>
167         [SecuritySafeCritical]
WriteTo(this IMessage message, Span<byte> output)168         public static void WriteTo(this IMessage message, Span<byte> output)
169         {
170             ProtoPreconditions.CheckNotNull(message, nameof(message));
171 
172             WriteContext.Initialize(ref output, out WriteContext ctx);
173             WritingPrimitivesMessages.WriteRawMessage(ref ctx, message);
174             ctx.CheckNoSpaceLeft();
175         }
176 
177         /// <summary>
178         /// Checks if all required fields in a message have values set. For proto3 messages, this returns true.
179         /// </summary>
IsInitialized(this IMessage message)180         public static bool IsInitialized(this IMessage message)
181         {
182             if (message.Descriptor.File.Edition == Edition.Proto3)
183             {
184                 return true;
185             }
186 
187             if (!message.Descriptor.IsExtensionsInitialized(message))
188             {
189                 return false;
190             }
191 
192             return message.Descriptor
193                 .Fields
194                 .InDeclarationOrder()
195                 .All(f =>
196                 {
197                     if (f.IsMap)
198                     {
199                         var valueField = f.MessageType.Fields[2];
200                         if (valueField.FieldType == FieldType.Message)
201                         {
202                             var map = (IDictionary)f.Accessor.GetValue(message);
203                             return map.Values.Cast<IMessage>().All(IsInitialized);
204                         }
205                         else
206                         {
207                             return true;
208                         }
209                     }
210                     else if (f.IsRepeated && f.FieldType == FieldType.Message || f.FieldType == FieldType.Group)
211                     {
212                         var enumerable = (IEnumerable)f.Accessor.GetValue(message);
213                         return enumerable.Cast<IMessage>().All(IsInitialized);
214                     }
215                     else if (f.FieldType == FieldType.Message || f.FieldType == FieldType.Group)
216                     {
217                         if (f.Accessor.HasValue(message))
218                         {
219                             return ((IMessage)f.Accessor.GetValue(message)).IsInitialized();
220                         }
221                         else
222                         {
223                             return !f.IsRequired;
224                         }
225                     }
226                     else if (f.IsRequired)
227                     {
228                         return f.Accessor.HasValue(message);
229                     }
230                     else
231                     {
232                         return true;
233                     }
234                 });
235         }
236 
237         // Implementations allowing unknown fields to be discarded.
MergeFrom(this IMessage message, byte[] data, bool discardUnknownFields, ExtensionRegistry registry)238         internal static void MergeFrom(this IMessage message, byte[] data, bool discardUnknownFields, ExtensionRegistry registry)
239         {
240             ProtoPreconditions.CheckNotNull(message, nameof(message));
241             ProtoPreconditions.CheckNotNull(data, nameof(data));
242             CodedInputStream input = new CodedInputStream(data)
243             {
244                 DiscardUnknownFields = discardUnknownFields,
245                 ExtensionRegistry = registry
246             };
247             message.MergeFrom(input);
248             input.CheckReadEndOfStreamTag();
249         }
250 
MergeFrom(this IMessage message, byte[] data, int offset, int length, bool discardUnknownFields, ExtensionRegistry registry)251         internal static void MergeFrom(this IMessage message, byte[] data, int offset, int length, bool discardUnknownFields, ExtensionRegistry registry)
252         {
253             ProtoPreconditions.CheckNotNull(message, nameof(message));
254             ProtoPreconditions.CheckNotNull(data, nameof(data));
255             CodedInputStream input = new CodedInputStream(data, offset, length)
256             {
257                 DiscardUnknownFields = discardUnknownFields,
258                 ExtensionRegistry = registry
259             };
260             message.MergeFrom(input);
261             input.CheckReadEndOfStreamTag();
262         }
263 
MergeFrom(this IMessage message, ByteString data, bool discardUnknownFields, ExtensionRegistry registry)264         internal static void MergeFrom(this IMessage message, ByteString data, bool discardUnknownFields, ExtensionRegistry registry)
265         {
266             ProtoPreconditions.CheckNotNull(message, nameof(message));
267             ProtoPreconditions.CheckNotNull(data, nameof(data));
268             CodedInputStream input = data.CreateCodedInput();
269             input.DiscardUnknownFields = discardUnknownFields;
270             input.ExtensionRegistry = registry;
271             message.MergeFrom(input);
272             input.CheckReadEndOfStreamTag();
273         }
274 
MergeFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)275         internal static void MergeFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)
276         {
277             ProtoPreconditions.CheckNotNull(message, nameof(message));
278             ProtoPreconditions.CheckNotNull(input, nameof(input));
279             CodedInputStream codedInput = new CodedInputStream(input)
280             {
281                 DiscardUnknownFields = discardUnknownFields,
282                 ExtensionRegistry = registry
283             };
284             message.MergeFrom(codedInput);
285             codedInput.CheckReadEndOfStreamTag();
286         }
287 
288         [SecuritySafeCritical]
MergeFrom(this IMessage message, ReadOnlySequence<byte> data, bool discardUnknownFields, ExtensionRegistry registry)289         internal static void MergeFrom(this IMessage message, ReadOnlySequence<byte> data, bool discardUnknownFields, ExtensionRegistry registry)
290         {
291             ParseContext.Initialize(data, out ParseContext ctx);
292             ctx.DiscardUnknownFields = discardUnknownFields;
293             ctx.ExtensionRegistry = registry;
294             ParsingPrimitivesMessages.ReadRawMessage(ref ctx, message);
295             ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref ctx.state);
296         }
297 
298         [SecuritySafeCritical]
MergeFrom(this IMessage message, ReadOnlySpan<byte> data, bool discardUnknownFields, ExtensionRegistry registry)299         internal static void MergeFrom(this IMessage message, ReadOnlySpan<byte> data, bool discardUnknownFields, ExtensionRegistry registry)
300         {
301             ParseContext.Initialize(data, out ParseContext ctx);
302             ctx.DiscardUnknownFields = discardUnknownFields;
303             ctx.ExtensionRegistry = registry;
304             ParsingPrimitivesMessages.ReadRawMessage(ref ctx, message);
305             ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref ctx.state);
306         }
307 
MergeDelimitedFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)308         internal static void MergeDelimitedFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)
309         {
310             ProtoPreconditions.CheckNotNull(message, nameof(message));
311             ProtoPreconditions.CheckNotNull(input, nameof(input));
312             int size = (int) CodedInputStream.ReadRawVarint32(input);
313             Stream limitedStream = new LimitedInputStream(input, size);
314             MergeFrom(message, limitedStream, discardUnknownFields, registry);
315         }
316     }
317 }
318