1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 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 using Google.Protobuf; 33 using Google.Protobuf.Reflection; 34 using System.Buffers; 35 using pb = global::Google.Protobuf; 36 using pbr = global::Google.Protobuf.Reflection; 37 using NUnit.Framework; 38 using System.IO; 39 using System; 40 using Google.Protobuf.Buffers; 41 42 namespace Google.Protobuf 43 { 44 public class LegacyGeneratedCodeTest 45 { 46 [Test] IntermixingOfNewAndLegacyGeneratedCodeWorksWithCodedInputStream()47 public void IntermixingOfNewAndLegacyGeneratedCodeWorksWithCodedInputStream() 48 { 49 var message = new ParseContextEnabledMessageB 50 { 51 A = new LegacyGeneratedCodeMessageA 52 { 53 Bb = new ParseContextEnabledMessageB { OptionalInt32 = 12345 } 54 }, 55 OptionalInt32 = 6789 56 }; 57 var data = message.ToByteArray(); 58 59 // when parsing started using CodedInputStream and a message with legacy generated code 60 // is encountered somewhere in the parse tree, we still need to be able to use its 61 // MergeFrom(CodedInputStream) method to parse correctly. 62 var codedInput = new CodedInputStream(data); 63 var parsed = new ParseContextEnabledMessageB(); 64 codedInput.ReadRawMessage(parsed); 65 Assert.IsTrue(codedInput.IsAtEnd); 66 67 Assert.AreEqual(12345, parsed.A.Bb.OptionalInt32); 68 Assert.AreEqual(6789, parsed.OptionalInt32); 69 } 70 71 [Test] LegacyGeneratedCodeThrowsWithReadOnlySequence()72 public void LegacyGeneratedCodeThrowsWithReadOnlySequence() 73 { 74 var message = new ParseContextEnabledMessageB 75 { 76 A = new LegacyGeneratedCodeMessageA 77 { 78 Bb = new ParseContextEnabledMessageB { OptionalInt32 = 12345 } 79 }, 80 OptionalInt32 = 6789 81 }; 82 var data = message.ToByteArray(); 83 84 // if parsing started using ReadOnlySequence and we don't have a CodedInputStream 85 // instance at hand, we cannot fall back to the legacy MergeFrom(CodedInputStream) 86 // method and parsing will fail. As a consequence, one can only use parsing 87 // from ReadOnlySequence if all the messages in the parsing tree have their generated 88 // code up to date. 89 var exception = Assert.Throws<InvalidProtocolBufferException>(() => 90 { 91 ParseContext.Initialize(new ReadOnlySequence<byte>(data), out ParseContext parseCtx); 92 var parsed = new ParseContextEnabledMessageB(); 93 ParsingPrimitivesMessages.ReadRawMessage(ref parseCtx, parsed); 94 }); 95 Assert.AreEqual($"Message {typeof(LegacyGeneratedCodeMessageA).Name} doesn't provide the generated method that enables ParseContext-based parsing. You might need to regenerate the generated protobuf code.", exception.Message); 96 } 97 98 [Test] IntermixingOfNewAndLegacyGeneratedCodeWorksWithCodedOutputStream()99 public void IntermixingOfNewAndLegacyGeneratedCodeWorksWithCodedOutputStream() 100 { 101 // when serialization started using CodedOutputStream and a message with legacy generated code 102 // is encountered somewhere in the parse tree, we still need to be able to use its 103 // WriteTo(CodedOutputStream) method to serialize correctly. 104 var ms = new MemoryStream(); 105 var codedOutput = new CodedOutputStream(ms); 106 var message = new ParseContextEnabledMessageB 107 { 108 A = new LegacyGeneratedCodeMessageA 109 { 110 Bb = new ParseContextEnabledMessageB { OptionalInt32 = 12345 } 111 }, 112 OptionalInt32 = 6789 113 }; 114 message.WriteTo(codedOutput); 115 codedOutput.Flush(); 116 117 var codedInput = new CodedInputStream(ms.ToArray()); 118 var parsed = new ParseContextEnabledMessageB(); 119 codedInput.ReadRawMessage(parsed); 120 Assert.IsTrue(codedInput.IsAtEnd); 121 122 Assert.AreEqual(12345, parsed.A.Bb.OptionalInt32); 123 Assert.AreEqual(6789, parsed.OptionalInt32); 124 } 125 126 [Test] LegacyGeneratedCodeThrowsWithIBufferWriter()127 public void LegacyGeneratedCodeThrowsWithIBufferWriter() 128 { 129 // if serialization started using IBufferWriter and we don't have a CodedOutputStream 130 // instance at hand, we cannot fall back to the legacy WriteTo(CodedOutputStream) 131 // method and serializatin will fail. As a consequence, one can only use serialization 132 // to IBufferWriter if all the messages in the parsing tree have their generated 133 // code up to date. 134 var message = new ParseContextEnabledMessageB 135 { 136 A = new LegacyGeneratedCodeMessageA 137 { 138 Bb = new ParseContextEnabledMessageB { OptionalInt32 = 12345 } 139 }, 140 OptionalInt32 = 6789 141 }; 142 var exception = Assert.Throws<InvalidProtocolBufferException>(() => 143 { 144 WriteContext.Initialize(new ArrayBufferWriter<byte>(), out WriteContext writeCtx); 145 ((IBufferMessage)message).InternalWriteTo(ref writeCtx); 146 }); 147 Assert.AreEqual($"Message {typeof(LegacyGeneratedCodeMessageA).Name} doesn't provide the generated method that enables WriteContext-based serialization. You might need to regenerate the generated protobuf code.", exception.Message); 148 } 149 150 // hand-modified version of a generated message that only provides the legacy 151 // MergeFrom(CodedInputStream) method and doesn't implement IBufferMessage. 152 private sealed partial class LegacyGeneratedCodeMessageA : pb::IMessage { 153 private pb::UnknownFieldSet _unknownFields; 154 155 pbr::MessageDescriptor pb::IMessage.Descriptor => throw new System.NotImplementedException(); 156 157 /// <summary>Field number for the "bb" field.</summary> 158 public const int BbFieldNumber = 1; 159 private ParseContextEnabledMessageB bb_; 160 public ParseContextEnabledMessageB Bb { 161 get { return bb_; } 162 set { 163 bb_ = value; 164 } 165 } 166 WriteTo(pb::CodedOutputStream output)167 public void WriteTo(pb::CodedOutputStream output) { 168 if (bb_ != null) { 169 output.WriteRawTag(10); 170 output.WriteMessage(Bb); 171 } 172 if (_unknownFields != null) { 173 _unknownFields.WriteTo(output); 174 } 175 } 176 CalculateSize()177 public int CalculateSize() { 178 int size = 0; 179 if (bb_ != null) { 180 size += 1 + pb::CodedOutputStream.ComputeMessageSize(Bb); 181 } 182 if (_unknownFields != null) { 183 size += _unknownFields.CalculateSize(); 184 } 185 return size; 186 } 187 MergeFrom(pb::CodedInputStream input)188 public void MergeFrom(pb::CodedInputStream input) { 189 uint tag; 190 while ((tag = input.ReadTag()) != 0) { 191 switch(tag) { 192 default: 193 _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); 194 break; 195 case 10: { 196 if (bb_ == null) { 197 Bb = new ParseContextEnabledMessageB(); 198 } 199 input.ReadMessage(Bb); 200 break; 201 } 202 } 203 } 204 } 205 } 206 207 // hand-modified version of a generated message that does provide 208 // the new InternalMergeFrom(ref ParseContext) method. 209 private sealed partial class ParseContextEnabledMessageB : pb::IBufferMessage { 210 private pb::UnknownFieldSet _unknownFields; 211 212 pbr::MessageDescriptor pb::IMessage.Descriptor => throw new System.NotImplementedException(); 213 214 /// <summary>Field number for the "a" field.</summary> 215 public const int AFieldNumber = 1; 216 private LegacyGeneratedCodeMessageA a_; 217 public LegacyGeneratedCodeMessageA A { 218 get { return a_; } 219 set { 220 a_ = value; 221 } 222 } 223 224 /// <summary>Field number for the "optional_int32" field.</summary> 225 public const int OptionalInt32FieldNumber = 2; 226 private int optionalInt32_; 227 public int OptionalInt32 { 228 get { return optionalInt32_; } 229 set { 230 optionalInt32_ = value; 231 } 232 } 233 WriteTo(pb::CodedOutputStream output)234 public void WriteTo(pb::CodedOutputStream output) { 235 output.WriteRawMessage(this); 236 } 237 IBufferMessage.InternalWriteTo(ref pb::WriteContext output)238 void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) 239 { 240 if (a_ != null) 241 { 242 output.WriteRawTag(10); 243 output.WriteMessage(A); 244 } 245 if (OptionalInt32 != 0) 246 { 247 output.WriteRawTag(16); 248 output.WriteInt32(OptionalInt32); 249 } 250 if (_unknownFields != null) 251 { 252 _unknownFields.WriteTo(ref output); 253 } 254 } 255 CalculateSize()256 public int CalculateSize() { 257 int size = 0; 258 if (a_ != null) { 259 size += 1 + pb::CodedOutputStream.ComputeMessageSize(A); 260 } 261 if (OptionalInt32 != 0) { 262 size += 1 + pb::CodedOutputStream.ComputeInt32Size(OptionalInt32); 263 } 264 if (_unknownFields != null) { 265 size += _unknownFields.CalculateSize(); 266 } 267 return size; 268 } MergeFrom(pb::CodedInputStream input)269 public void MergeFrom(pb::CodedInputStream input) { 270 input.ReadRawMessage(this); 271 } 272 IBufferMessage.InternalMergeFrom(ref pb::ParseContext input)273 void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { 274 uint tag; 275 while ((tag = input.ReadTag()) != 0) { 276 switch(tag) { 277 default: 278 _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); 279 break; 280 case 10: { 281 if (a_ == null) { 282 A = new LegacyGeneratedCodeMessageA(); 283 } 284 input.ReadMessage(A); 285 break; 286 } 287 case 16: { 288 OptionalInt32 = input.ReadInt32(); 289 break; 290 } 291 } 292 } 293 } 294 } 295 } 296 }