blob: d0963d257dc69a87a31be32eccf07b21932b73c1 [file] [log] [blame] [edit]
#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();
}
}
}