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