| #region Copyright notice and license |
| // Protocol Buffers - Google's data interchange format |
| // Copyright 2015 Google Inc. All rights reserved. |
| // |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file or at |
| // https://developers.google.com/open-source/licenses/bsd |
| #endregion |
| |
| using System; |
| using System.Collections.Generic; |
| using System.Diagnostics; |
| using System.Linq; |
| using System.Security; |
| |
| namespace Google.Protobuf |
| { |
| /// <summary> |
| /// Used to keep track of fields which were seen when parsing a protocol message |
| /// but whose field numbers or types are unrecognized. This most frequently |
| /// occurs when new fields are added to a message type and then messages containing |
| /// those fields are read by old software that was built before the new types were |
| /// added. |
| /// |
| /// Most users will never need to use this class directly. |
| /// </summary> |
| [DebuggerDisplay("Count = {fields.Count}")] |
| [DebuggerTypeProxy(typeof(UnknownFieldSetDebugView))] |
| public sealed partial class UnknownFieldSet |
| { |
| private readonly IDictionary<int, UnknownField> fields = new Dictionary<int, UnknownField>(); |
| |
| /// <summary> |
| /// Creates a new UnknownFieldSet. |
| /// </summary> |
| internal UnknownFieldSet() |
| { |
| } |
| |
| /// <summary> |
| /// Checks whether or not the given field number is present in the set. |
| /// </summary> |
| internal bool HasField(int field) |
| { |
| return fields.ContainsKey(field); |
| } |
| |
| /// <summary> |
| /// Serializes the set and writes it to <paramref name="output"/>. |
| /// </summary> |
| public void WriteTo(CodedOutputStream output) |
| { |
| WriteContext.Initialize(output, out WriteContext ctx); |
| try |
| { |
| WriteTo(ref ctx); |
| } |
| finally |
| { |
| ctx.CopyStateTo(output); |
| } |
| } |
| |
| /// <summary> |
| /// Serializes the set and writes it to <paramref name="ctx"/>. |
| /// </summary> |
| [SecuritySafeCritical] |
| public void WriteTo(ref WriteContext ctx) |
| { |
| foreach (KeyValuePair<int, UnknownField> entry in fields) |
| { |
| entry.Value.WriteTo(entry.Key, ref ctx); |
| } |
| } |
| |
| /// <summary> |
| /// Gets the number of bytes required to encode this set. |
| /// </summary> |
| public int CalculateSize() |
| { |
| int result = 0; |
| foreach (KeyValuePair<int, UnknownField> entry in fields) |
| { |
| result += entry.Value.GetSerializedSize(entry.Key); |
| } |
| return result; |
| } |
| |
| /// <summary> |
| /// Checks if two unknown field sets are equal. |
| /// </summary> |
| public override bool Equals(object other) |
| { |
| if (ReferenceEquals(this, other)) |
| { |
| return true; |
| } |
| UnknownFieldSet otherSet = other as UnknownFieldSet; |
| IDictionary<int, UnknownField> otherFields = otherSet.fields; |
| if (fields.Count != otherFields.Count) |
| { |
| return false; |
| } |
| foreach (KeyValuePair<int, UnknownField> leftEntry in fields) |
| { |
| if (!otherFields.TryGetValue(leftEntry.Key, out UnknownField rightValue)) |
| { |
| return false; |
| } |
| if (!leftEntry.Value.Equals(rightValue)) |
| { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /// <summary> |
| /// Gets the unknown field set's hash code. |
| /// </summary> |
| public override int GetHashCode() |
| { |
| int ret = 1; |
| foreach (KeyValuePair<int, UnknownField> field in fields) |
| { |
| // Use ^ here to make the field order irrelevant. |
| int hash = field.Key.GetHashCode() ^ field.Value.GetHashCode(); |
| ret ^= hash; |
| } |
| return ret; |
| } |
| |
| // Optimization: We keep around the last field that was |
| // modified so that we can efficiently add to it multiple times in a |
| // row (important when parsing an unknown repeated field). |
| private int lastFieldNumber; |
| private UnknownField lastField; |
| |
| private UnknownField GetOrAddField(int number) |
| { |
| if (lastField != null && number == lastFieldNumber) |
| { |
| return lastField; |
| } |
| if (number == 0) |
| { |
| return null; |
| } |
| |
| if (fields.TryGetValue(number, out UnknownField existing)) |
| { |
| return existing; |
| } |
| lastField = new UnknownField(); |
| AddOrReplaceField(number, lastField); |
| lastFieldNumber = number; |
| return lastField; |
| } |
| |
| /// <summary> |
| /// Adds a field to the set. If a field with the same number already exists, it |
| /// is replaced. |
| /// </summary> |
| internal UnknownFieldSet AddOrReplaceField(int number, UnknownField field) |
| { |
| if (number == 0) |
| { |
| throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number."); |
| } |
| fields[number] = field; |
| return this; |
| } |
| |
| /// <summary> |
| /// Parse a single field from <paramref name="ctx"/> and merge it |
| /// into this set. |
| /// </summary> |
| /// <param name="ctx">The parse context from which to read the field</param> |
| /// <returns>false if the tag is an "end group" tag, true otherwise</returns> |
| private bool MergeFieldFrom(ref ParseContext ctx) |
| { |
| uint tag = ctx.LastTag; |
| int number = WireFormat.GetTagFieldNumber(tag); |
| switch (WireFormat.GetTagWireType(tag)) |
| { |
| case WireFormat.WireType.Varint: |
| { |
| ulong uint64 = ctx.ReadUInt64(); |
| GetOrAddField(number).AddVarint(uint64); |
| return true; |
| } |
| case WireFormat.WireType.Fixed32: |
| { |
| uint uint32 = ctx.ReadFixed32(); |
| GetOrAddField(number).AddFixed32(uint32); |
| return true; |
| } |
| case WireFormat.WireType.Fixed64: |
| { |
| ulong uint64 = ctx.ReadFixed64(); |
| GetOrAddField(number).AddFixed64(uint64); |
| return true; |
| } |
| case WireFormat.WireType.LengthDelimited: |
| { |
| ByteString bytes = ctx.ReadBytes(); |
| GetOrAddField(number).AddLengthDelimited(bytes); |
| return true; |
| } |
| case WireFormat.WireType.StartGroup: |
| { |
| UnknownFieldSet set = new UnknownFieldSet(); |
| ParsingPrimitivesMessages.ReadGroup(ref ctx, number, set); |
| GetOrAddField(number).AddGroup(set); |
| return true; |
| } |
| case WireFormat.WireType.EndGroup: |
| { |
| return false; |
| } |
| default: |
| throw InvalidProtocolBufferException.InvalidWireType(); |
| } |
| } |
| |
| internal void MergeGroupFrom(ref ParseContext ctx) |
| { |
| while (true) |
| { |
| uint tag = ctx.ReadTag(); |
| if (tag == 0) |
| { |
| break; |
| } |
| if (!MergeFieldFrom(ref ctx)) |
| { |
| break; |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Create a new UnknownFieldSet if unknownFields is null. |
| /// Parse a single field from <paramref name="input"/> and merge it |
| /// into unknownFields. If <paramref name="input"/> is configured to discard unknown fields, |
| /// <paramref name="unknownFields"/> will be returned as-is and the field will be skipped. |
| /// </summary> |
| /// <param name="unknownFields">The UnknownFieldSet which need to be merged</param> |
| /// <param name="input">The coded input stream containing the field</param> |
| /// <returns>The merged UnknownFieldSet</returns> |
| public static UnknownFieldSet MergeFieldFrom(UnknownFieldSet unknownFields, |
| CodedInputStream input) |
| { |
| ParseContext.Initialize(input, out ParseContext ctx); |
| try |
| { |
| return MergeFieldFrom(unknownFields, ref ctx); |
| } |
| finally |
| { |
| ctx.CopyStateTo(input); |
| } |
| } |
| |
| /// <summary> |
| /// Create a new UnknownFieldSet if unknownFields is null. |
| /// Parse a single field from <paramref name="ctx"/> and merge it |
| /// into unknownFields. If <paramref name="ctx"/> is configured to discard unknown fields, |
| /// <paramref name="unknownFields"/> will be returned as-is and the field will be skipped. |
| /// </summary> |
| /// <param name="unknownFields">The UnknownFieldSet which need to be merged</param> |
| /// <param name="ctx">The parse context from which to read the field</param> |
| /// <returns>The merged UnknownFieldSet</returns> |
| [SecuritySafeCritical] |
| public static UnknownFieldSet MergeFieldFrom(UnknownFieldSet unknownFields, |
| ref ParseContext ctx) |
| { |
| if (ctx.DiscardUnknownFields) |
| { |
| ParsingPrimitivesMessages.SkipLastField(ref ctx.buffer, ref ctx.state); |
| return unknownFields; |
| } |
| if (unknownFields == null) |
| { |
| unknownFields = new UnknownFieldSet(); |
| } |
| if (!unknownFields.MergeFieldFrom(ref ctx)) |
| { |
| throw new InvalidProtocolBufferException("Merge an unknown field of end-group tag, indicating that the corresponding start-group was missing."); // match the old code-gen |
| } |
| return unknownFields; |
| } |
| |
| /// <summary> |
| /// Merges the fields from <paramref name="other"/> into this set. |
| /// If a field number exists in both sets, the values in <paramref name="other"/> |
| /// will be appended to the values in this set. |
| /// </summary> |
| private UnknownFieldSet MergeFrom(UnknownFieldSet other) |
| { |
| if (other != null) |
| { |
| foreach (KeyValuePair<int, UnknownField> entry in other.fields) |
| { |
| MergeField(entry.Key, entry.Value); |
| } |
| } |
| return this; |
| } |
| |
| /// <summary> |
| /// Created a new UnknownFieldSet to <paramref name="unknownFields"/> if |
| /// needed and merges the fields from <paramref name="other"/> into the first set. |
| /// If a field number exists in both sets, the values in <paramref name="other"/> |
| /// will be appended to the values in this set. |
| /// </summary> |
| public static UnknownFieldSet MergeFrom(UnknownFieldSet unknownFields, |
| UnknownFieldSet other) |
| { |
| if (other == null) |
| { |
| return unknownFields; |
| } |
| if (unknownFields == null) |
| { |
| unknownFields = new UnknownFieldSet(); |
| } |
| unknownFields.MergeFrom(other); |
| return unknownFields; |
| } |
| |
| |
| /// <summary> |
| /// Adds a field to the unknown field set. If a field with the same |
| /// number already exists, the two are merged. |
| /// </summary> |
| private UnknownFieldSet MergeField(int number, UnknownField field) |
| { |
| if (number == 0) |
| { |
| throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number."); |
| } |
| if (HasField(number)) |
| { |
| GetOrAddField(number).MergeFrom(field); |
| } |
| else |
| { |
| AddOrReplaceField(number, field); |
| } |
| return this; |
| } |
| |
| /// <summary> |
| /// Clone an unknown field set from <paramref name="other"/>. |
| /// </summary> |
| public static UnknownFieldSet Clone(UnknownFieldSet other) |
| { |
| if (other == null) |
| { |
| return null; |
| } |
| UnknownFieldSet unknownFields = new UnknownFieldSet(); |
| unknownFields.MergeFrom(other); |
| return unknownFields; |
| } |
| |
| private sealed class UnknownFieldSetDebugView |
| { |
| private readonly UnknownFieldSet set; |
| |
| public UnknownFieldSetDebugView(UnknownFieldSet set) |
| { |
| this.set = set; |
| } |
| |
| [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] |
| public KeyValuePair<int, UnknownField>[] Items => set.fields.ToArray(); |
| } |
| } |
| } |
| |