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.Collections;
35 using System.IO;
36 using System.Linq;
37 
38 namespace Google.Protobuf
39 {
40     /// <summary>
41     /// Extension methods on <see cref="IMessage"/> and <see cref="IMessage{T}"/>.
42     /// </summary>
43     public static class MessageExtensions
44     {
45         /// <summary>
46         /// Merges data from the given byte array into an existing message.
47         /// </summary>
48         /// <param name="message">The message to merge the data into.</param>
49         /// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param>
MergeFrom(this IMessage message, byte[] data)50         public static void MergeFrom(this IMessage message, byte[] data) =>
51             MergeFrom(message, data, false, null);
52 
53         /// <summary>
54         /// Merges data from the given byte array slice into an existing message.
55         /// </summary>
56         /// <param name="message">The message to merge the data into.</param>
57         /// <param name="data">The data containing the slice to merge, which must be protobuf-encoded binary data.</param>
58         /// <param name="offset">The offset of the slice to merge.</param>
59         /// <param name="length">The length of the slice to merge.</param>
MergeFrom(this IMessage message, byte[] data, int offset, int length)60         public static void MergeFrom(this IMessage message, byte[] data, int offset, int length) =>
61             MergeFrom(message, data, offset, length, false, null);
62 
63         /// <summary>
64         /// Merges data from the given byte string into an existing message.
65         /// </summary>
66         /// <param name="message">The message to merge the data into.</param>
67         /// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param>
MergeFrom(this IMessage message, ByteString data)68         public static void MergeFrom(this IMessage message, ByteString data) =>
69             MergeFrom(message, data, false, null);
70 
71         /// <summary>
72         /// Merges data from the given stream into an existing message.
73         /// </summary>
74         /// <param name="message">The message to merge the data into.</param>
75         /// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param>
MergeFrom(this IMessage message, Stream input)76         public static void MergeFrom(this IMessage message, Stream input) =>
77             MergeFrom(message, input, false, null);
78 
79         /// <summary>
80         /// Merges length-delimited data from the given stream into an existing message.
81         /// </summary>
82         /// <remarks>
83         /// The stream is expected to contain a length and then the data. Only the amount of data
84         /// specified by the length will be consumed.
85         /// </remarks>
86         /// <param name="message">The message to merge the data into.</param>
87         /// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param>
MergeDelimitedFrom(this IMessage message, Stream input)88         public static void MergeDelimitedFrom(this IMessage message, Stream input) =>
89             MergeDelimitedFrom(message, input, false, null);
90 
91         /// <summary>
92         /// Converts the given message into a byte array in protobuf encoding.
93         /// </summary>
94         /// <param name="message">The message to convert.</param>
95         /// <returns>The message data as a byte array.</returns>
ToByteArray(this IMessage message)96         public static byte[] ToByteArray(this IMessage message)
97         {
98             ProtoPreconditions.CheckNotNull(message, "message");
99             byte[] result = new byte[message.CalculateSize()];
100             CodedOutputStream output = new CodedOutputStream(result);
101             message.WriteTo(output);
102             output.CheckNoSpaceLeft();
103             return result;
104         }
105 
106         /// <summary>
107         /// Writes the given message data to the given stream in protobuf encoding.
108         /// </summary>
109         /// <param name="message">The message to write to the stream.</param>
110         /// <param name="output">The stream to write to.</param>
WriteTo(this IMessage message, Stream output)111         public static void WriteTo(this IMessage message, Stream output)
112         {
113             ProtoPreconditions.CheckNotNull(message, "message");
114             ProtoPreconditions.CheckNotNull(output, "output");
115             CodedOutputStream codedOutput = new CodedOutputStream(output);
116             message.WriteTo(codedOutput);
117             codedOutput.Flush();
118         }
119 
120         /// <summary>
121         /// Writes the length and then data of the given message to a stream.
122         /// </summary>
123         /// <param name="message">The message to write.</param>
124         /// <param name="output">The output stream to write to.</param>
WriteDelimitedTo(this IMessage message, Stream output)125         public static void WriteDelimitedTo(this IMessage message, Stream output)
126         {
127             ProtoPreconditions.CheckNotNull(message, "message");
128             ProtoPreconditions.CheckNotNull(output, "output");
129             CodedOutputStream codedOutput = new CodedOutputStream(output);
130             codedOutput.WriteRawVarint32((uint)message.CalculateSize());
131             message.WriteTo(codedOutput);
132             codedOutput.Flush();
133         }
134 
135         /// <summary>
136         /// Converts the given message into a byte string in protobuf encoding.
137         /// </summary>
138         /// <param name="message">The message to convert.</param>
139         /// <returns>The message data as a byte string.</returns>
ToByteString(this IMessage message)140         public static ByteString ToByteString(this IMessage message)
141         {
142             ProtoPreconditions.CheckNotNull(message, "message");
143             return ByteString.AttachBytes(message.ToByteArray());
144         }
145 
146         /// <summary>
147         /// Checks if all required fields in a message have values set. For proto3 messages, this returns true
148         /// </summary>
IsInitialized(this IMessage message)149         public static bool IsInitialized(this IMessage message)
150         {
151             if (message.Descriptor.File.Proto.Syntax != "proto2")
152             {
153                 return true;
154             }
155 
156             return message.Descriptor
157                 .Fields
158                 .InDeclarationOrder()
159                 .All(f =>
160                 {
161                     if (f.IsMap)
162                     {
163                         var map = (IDictionary)f.Accessor.GetValue(message);
164                         return map.Values.OfType<IMessage>().All(IsInitialized);
165                     }
166                     else if (f.IsRepeated && f.FieldType == FieldType.Message || f.FieldType == FieldType.Group)
167                     {
168                         var enumerable = (IEnumerable)f.Accessor.GetValue(message);
169                         return enumerable.Cast<IMessage>().All(IsInitialized);
170                     }
171                     else if (f.FieldType == FieldType.Message || f.FieldType == FieldType.Group)
172                     {
173                         if (f.Accessor.HasValue(message))
174                         {
175                             return ((IMessage)f.Accessor.GetValue(message)).IsInitialized();
176                         }
177                         else
178                         {
179                             return !f.IsRequired;
180                         }
181                     }
182                     else if (f.IsRequired)
183                     {
184                         return f.Accessor.HasValue(message);
185                     }
186                     else
187                     {
188                         return true;
189                     }
190                 });
191         }
192 
193         // Implementations allowing unknown fields to be discarded.
MergeFrom(this IMessage message, byte[] data, bool discardUnknownFields, ExtensionRegistry registry)194         internal static void MergeFrom(this IMessage message, byte[] data, bool discardUnknownFields, ExtensionRegistry registry)
195         {
196             ProtoPreconditions.CheckNotNull(message, "message");
197             ProtoPreconditions.CheckNotNull(data, "data");
198             CodedInputStream input = new CodedInputStream(data);
199             input.DiscardUnknownFields = discardUnknownFields;
200             input.ExtensionRegistry = registry;
201             message.MergeFrom(input);
202             input.CheckReadEndOfStreamTag();
203         }
204 
MergeFrom(this IMessage message, byte[] data, int offset, int length, bool discardUnknownFields, ExtensionRegistry registry)205         internal static void MergeFrom(this IMessage message, byte[] data, int offset, int length, bool discardUnknownFields, ExtensionRegistry registry)
206         {
207             ProtoPreconditions.CheckNotNull(message, "message");
208             ProtoPreconditions.CheckNotNull(data, "data");
209             CodedInputStream input = new CodedInputStream(data, offset, length);
210             input.DiscardUnknownFields = discardUnknownFields;
211             input.ExtensionRegistry = registry;
212             message.MergeFrom(input);
213             input.CheckReadEndOfStreamTag();
214         }
215 
MergeFrom(this IMessage message, ByteString data, bool discardUnknownFields, ExtensionRegistry registry)216         internal static void MergeFrom(this IMessage message, ByteString data, bool discardUnknownFields, ExtensionRegistry registry)
217         {
218             ProtoPreconditions.CheckNotNull(message, "message");
219             ProtoPreconditions.CheckNotNull(data, "data");
220             CodedInputStream input = data.CreateCodedInput();
221             input.DiscardUnknownFields = discardUnknownFields;
222             input.ExtensionRegistry = registry;
223             message.MergeFrom(input);
224             input.CheckReadEndOfStreamTag();
225         }
226 
MergeFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)227         internal static void MergeFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)
228         {
229             ProtoPreconditions.CheckNotNull(message, "message");
230             ProtoPreconditions.CheckNotNull(input, "input");
231             CodedInputStream codedInput = new CodedInputStream(input);
232             codedInput.DiscardUnknownFields = discardUnknownFields;
233             codedInput.ExtensionRegistry = registry;
234             message.MergeFrom(codedInput);
235             codedInput.CheckReadEndOfStreamTag();
236         }
237 
MergeDelimitedFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)238         internal static void MergeDelimitedFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)
239         {
240             ProtoPreconditions.CheckNotNull(message, "message");
241             ProtoPreconditions.CheckNotNull(input, "input");
242             int size = (int) CodedInputStream.ReadRawVarint32(input);
243             Stream limitedStream = new LimitedInputStream(input, size);
244             MergeFrom(message, limitedStream, discardUnknownFields, registry);
245         }
246     }
247 }
248