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 System; 11 using System.Collections.Generic; 12 using System.Diagnostics; 13 using System.Linq; 14 using System.Security; 15 16 namespace Google.Protobuf 17 { 18 /// <summary> 19 /// Used to keep track of fields which were seen when parsing a protocol message 20 /// but whose field numbers or types are unrecognized. This most frequently 21 /// occurs when new fields are added to a message type and then messages containing 22 /// those fields are read by old software that was built before the new types were 23 /// added. 24 /// 25 /// Most users will never need to use this class directly. 26 /// </summary> 27 [DebuggerDisplay("Count = {fields.Count}")] 28 [DebuggerTypeProxy(typeof(UnknownFieldSetDebugView))] 29 public sealed partial class UnknownFieldSet 30 { 31 private readonly IDictionary<int, UnknownField> fields = new Dictionary<int, UnknownField>(); 32 33 /// <summary> 34 /// Creates a new UnknownFieldSet. 35 /// </summary> UnknownFieldSet()36 internal UnknownFieldSet() 37 { 38 } 39 40 /// <summary> 41 /// Checks whether or not the given field number is present in the set. 42 /// </summary> HasField(int field)43 internal bool HasField(int field) 44 { 45 return fields.ContainsKey(field); 46 } 47 48 /// <summary> 49 /// Serializes the set and writes it to <paramref name="output"/>. 50 /// </summary> WriteTo(CodedOutputStream output)51 public void WriteTo(CodedOutputStream output) 52 { 53 WriteContext.Initialize(output, out WriteContext ctx); 54 try 55 { 56 WriteTo(ref ctx); 57 } 58 finally 59 { 60 ctx.CopyStateTo(output); 61 } 62 } 63 64 /// <summary> 65 /// Serializes the set and writes it to <paramref name="ctx"/>. 66 /// </summary> 67 [SecuritySafeCritical] WriteTo(ref WriteContext ctx)68 public void WriteTo(ref WriteContext ctx) 69 { 70 foreach (KeyValuePair<int, UnknownField> entry in fields) 71 { 72 entry.Value.WriteTo(entry.Key, ref ctx); 73 } 74 } 75 76 /// <summary> 77 /// Gets the number of bytes required to encode this set. 78 /// </summary> CalculateSize()79 public int CalculateSize() 80 { 81 int result = 0; 82 foreach (KeyValuePair<int, UnknownField> entry in fields) 83 { 84 result += entry.Value.GetSerializedSize(entry.Key); 85 } 86 return result; 87 } 88 89 /// <summary> 90 /// Checks if two unknown field sets are equal. 91 /// </summary> Equals(object other)92 public override bool Equals(object other) 93 { 94 if (ReferenceEquals(this, other)) 95 { 96 return true; 97 } 98 UnknownFieldSet otherSet = other as UnknownFieldSet; 99 IDictionary<int, UnknownField> otherFields = otherSet.fields; 100 if (fields.Count != otherFields.Count) 101 { 102 return false; 103 } 104 foreach (KeyValuePair<int, UnknownField> leftEntry in fields) 105 { 106 if (!otherFields.TryGetValue(leftEntry.Key, out UnknownField rightValue)) 107 { 108 return false; 109 } 110 if (!leftEntry.Value.Equals(rightValue)) 111 { 112 return false; 113 } 114 } 115 return true; 116 } 117 118 /// <summary> 119 /// Gets the unknown field set's hash code. 120 /// </summary> GetHashCode()121 public override int GetHashCode() 122 { 123 int ret = 1; 124 foreach (KeyValuePair<int, UnknownField> field in fields) 125 { 126 // Use ^ here to make the field order irrelevant. 127 int hash = field.Key.GetHashCode() ^ field.Value.GetHashCode(); 128 ret ^= hash; 129 } 130 return ret; 131 } 132 133 // Optimization: We keep around the last field that was 134 // modified so that we can efficiently add to it multiple times in a 135 // row (important when parsing an unknown repeated field). 136 private int lastFieldNumber; 137 private UnknownField lastField; 138 GetOrAddField(int number)139 private UnknownField GetOrAddField(int number) 140 { 141 if (lastField != null && number == lastFieldNumber) 142 { 143 return lastField; 144 } 145 if (number == 0) 146 { 147 return null; 148 } 149 150 if (fields.TryGetValue(number, out UnknownField existing)) 151 { 152 return existing; 153 } 154 lastField = new UnknownField(); 155 AddOrReplaceField(number, lastField); 156 lastFieldNumber = number; 157 return lastField; 158 } 159 160 /// <summary> 161 /// Adds a field to the set. If a field with the same number already exists, it 162 /// is replaced. 163 /// </summary> AddOrReplaceField(int number, UnknownField field)164 internal UnknownFieldSet AddOrReplaceField(int number, UnknownField field) 165 { 166 if (number == 0) 167 { 168 throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number."); 169 } 170 fields[number] = field; 171 return this; 172 } 173 174 /// <summary> 175 /// Parse a single field from <paramref name="ctx"/> and merge it 176 /// into this set. 177 /// </summary> 178 /// <param name="ctx">The parse context from which to read the field</param> 179 /// <returns>false if the tag is an "end group" tag, true otherwise</returns> MergeFieldFrom(ref ParseContext ctx)180 private bool MergeFieldFrom(ref ParseContext ctx) 181 { 182 uint tag = ctx.LastTag; 183 int number = WireFormat.GetTagFieldNumber(tag); 184 switch (WireFormat.GetTagWireType(tag)) 185 { 186 case WireFormat.WireType.Varint: 187 { 188 ulong uint64 = ctx.ReadUInt64(); 189 GetOrAddField(number).AddVarint(uint64); 190 return true; 191 } 192 case WireFormat.WireType.Fixed32: 193 { 194 uint uint32 = ctx.ReadFixed32(); 195 GetOrAddField(number).AddFixed32(uint32); 196 return true; 197 } 198 case WireFormat.WireType.Fixed64: 199 { 200 ulong uint64 = ctx.ReadFixed64(); 201 GetOrAddField(number).AddFixed64(uint64); 202 return true; 203 } 204 case WireFormat.WireType.LengthDelimited: 205 { 206 ByteString bytes = ctx.ReadBytes(); 207 GetOrAddField(number).AddLengthDelimited(bytes); 208 return true; 209 } 210 case WireFormat.WireType.StartGroup: 211 { 212 UnknownFieldSet set = new UnknownFieldSet(); 213 ParsingPrimitivesMessages.ReadGroup(ref ctx, number, set); 214 GetOrAddField(number).AddGroup(set); 215 return true; 216 } 217 case WireFormat.WireType.EndGroup: 218 { 219 return false; 220 } 221 default: 222 throw InvalidProtocolBufferException.InvalidWireType(); 223 } 224 } 225 MergeGroupFrom(ref ParseContext ctx)226 internal void MergeGroupFrom(ref ParseContext ctx) 227 { 228 while (true) 229 { 230 uint tag = ctx.ReadTag(); 231 if (tag == 0) 232 { 233 break; 234 } 235 if (!MergeFieldFrom(ref ctx)) 236 { 237 break; 238 } 239 } 240 } 241 242 /// <summary> 243 /// Create a new UnknownFieldSet if unknownFields is null. 244 /// Parse a single field from <paramref name="input"/> and merge it 245 /// into unknownFields. If <paramref name="input"/> is configured to discard unknown fields, 246 /// <paramref name="unknownFields"/> will be returned as-is and the field will be skipped. 247 /// </summary> 248 /// <param name="unknownFields">The UnknownFieldSet which need to be merged</param> 249 /// <param name="input">The coded input stream containing the field</param> 250 /// <returns>The merged UnknownFieldSet</returns> MergeFieldFrom(UnknownFieldSet unknownFields, CodedInputStream input)251 public static UnknownFieldSet MergeFieldFrom(UnknownFieldSet unknownFields, 252 CodedInputStream input) 253 { 254 ParseContext.Initialize(input, out ParseContext ctx); 255 try 256 { 257 return MergeFieldFrom(unknownFields, ref ctx); 258 } 259 finally 260 { 261 ctx.CopyStateTo(input); 262 } 263 } 264 265 /// <summary> 266 /// Create a new UnknownFieldSet if unknownFields is null. 267 /// Parse a single field from <paramref name="ctx"/> and merge it 268 /// into unknownFields. If <paramref name="ctx"/> is configured to discard unknown fields, 269 /// <paramref name="unknownFields"/> will be returned as-is and the field will be skipped. 270 /// </summary> 271 /// <param name="unknownFields">The UnknownFieldSet which need to be merged</param> 272 /// <param name="ctx">The parse context from which to read the field</param> 273 /// <returns>The merged UnknownFieldSet</returns> 274 [SecuritySafeCritical] MergeFieldFrom(UnknownFieldSet unknownFields, ref ParseContext ctx)275 public static UnknownFieldSet MergeFieldFrom(UnknownFieldSet unknownFields, 276 ref ParseContext ctx) 277 { 278 if (ctx.DiscardUnknownFields) 279 { 280 ParsingPrimitivesMessages.SkipLastField(ref ctx.buffer, ref ctx.state); 281 return unknownFields; 282 } 283 if (unknownFields == null) 284 { 285 unknownFields = new UnknownFieldSet(); 286 } 287 if (!unknownFields.MergeFieldFrom(ref ctx)) 288 { 289 throw new InvalidProtocolBufferException("Merge an unknown field of end-group tag, indicating that the corresponding start-group was missing."); // match the old code-gen 290 } 291 return unknownFields; 292 } 293 294 /// <summary> 295 /// Merges the fields from <paramref name="other"/> into this set. 296 /// If a field number exists in both sets, the values in <paramref name="other"/> 297 /// will be appended to the values in this set. 298 /// </summary> MergeFrom(UnknownFieldSet other)299 private UnknownFieldSet MergeFrom(UnknownFieldSet other) 300 { 301 if (other != null) 302 { 303 foreach (KeyValuePair<int, UnknownField> entry in other.fields) 304 { 305 MergeField(entry.Key, entry.Value); 306 } 307 } 308 return this; 309 } 310 311 /// <summary> 312 /// Created a new UnknownFieldSet to <paramref name="unknownFields"/> if 313 /// needed and merges the fields from <paramref name="other"/> into the first set. 314 /// If a field number exists in both sets, the values in <paramref name="other"/> 315 /// will be appended to the values in this set. 316 /// </summary> MergeFrom(UnknownFieldSet unknownFields, UnknownFieldSet other)317 public static UnknownFieldSet MergeFrom(UnknownFieldSet unknownFields, 318 UnknownFieldSet other) 319 { 320 if (other == null) 321 { 322 return unknownFields; 323 } 324 if (unknownFields == null) 325 { 326 unknownFields = new UnknownFieldSet(); 327 } 328 unknownFields.MergeFrom(other); 329 return unknownFields; 330 } 331 332 333 /// <summary> 334 /// Adds a field to the unknown field set. If a field with the same 335 /// number already exists, the two are merged. 336 /// </summary> MergeField(int number, UnknownField field)337 private UnknownFieldSet MergeField(int number, UnknownField field) 338 { 339 if (number == 0) 340 { 341 throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number."); 342 } 343 if (HasField(number)) 344 { 345 GetOrAddField(number).MergeFrom(field); 346 } 347 else 348 { 349 AddOrReplaceField(number, field); 350 } 351 return this; 352 } 353 354 /// <summary> 355 /// Clone an unknown field set from <paramref name="other"/>. 356 /// </summary> Clone(UnknownFieldSet other)357 public static UnknownFieldSet Clone(UnknownFieldSet other) 358 { 359 if (other == null) 360 { 361 return null; 362 } 363 UnknownFieldSet unknownFields = new UnknownFieldSet(); 364 unknownFields.MergeFrom(other); 365 return unknownFields; 366 } 367 368 private sealed class UnknownFieldSetDebugView 369 { 370 private readonly UnknownFieldSet set; 371 UnknownFieldSetDebugView(UnknownFieldSet set)372 public UnknownFieldSetDebugView(UnknownFieldSet set) 373 { 374 this.set = set; 375 } 376 377 [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] 378 public KeyValuePair<int, UnknownField>[] Items => set.fields.ToArray(); 379 } 380 } 381 } 382 383