• 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 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