File: System\Runtime\Serialization\BinaryFormat\MemberTypeInfo.cs
Web Access
Project: src\src\libraries\System.Runtime.Serialization.BinaryFormat\src\System.Runtime.Serialization.BinaryFormat.csproj (System.Runtime.Serialization.BinaryFormat)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection.Metadata;
using System.Runtime.Serialization.BinaryFormat.Utils;
 
namespace System.Runtime.Serialization.BinaryFormat;
 
/// <summary>
/// Member type info.
/// </summary>
/// <remarks>
/// MemberTypeInfo structures are described in <see href="https://learn.microsoft.com/openspecs/windows_protocols/ms-nrbf/aa509b5a-620a-4592-a5d8-7e9613e0a03e">[MS-NRBF] 2.3.1.2</see>.
/// </remarks>
internal readonly struct MemberTypeInfo
{
    internal MemberTypeInfo(IReadOnlyList<(BinaryType BinaryType, object? AdditionalInfo)> infos) => _infos = infos;
 
    private readonly IReadOnlyList<(BinaryType BinaryType, object? AdditionalInfo)> _infos;
 
    internal IReadOnlyList<(BinaryType BinaryType, object? AdditionalInfo)> Infos => _infos;
 
    internal static MemberTypeInfo Decode(BinaryReader reader, int count, PayloadOptions options, RecordMap recordMap)
    {
        List<(BinaryType BinaryType, object? AdditionalInfo)> info = [];
 
        // [MS-NRBF] 2.3.1.2
        // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/aa509b5a-620a-4592-a5d8-7e9613e0a03e
 
        // All of the BinaryTypeEnumeration values come before all of the AdditionalInfo values.
        // There's not necessarily a 1:1 mapping; some enum values don't have associated AdditionalInfo.
        for (int i = 0; i < count; i++)
        {
            info.Add((reader.ReadBinaryType(), null));
        }
 
        // Check for more clarifying information
        for (int i = 0; i < info.Count; i++)
        {
            BinaryType type = info[i].BinaryType;
            switch (type)
            {
                case BinaryType.Primitive:
                case BinaryType.PrimitiveArray:
                    info[i] = (type, reader.ReadPrimitiveType());
                    break;
                case BinaryType.SystemClass:
                    info[i] = (type, reader.ReadString().ParseSystemRecordTypeName(options));
                    break;
                case BinaryType.Class:
                    info[i] = (type, ClassTypeInfo.Decode(reader, options, recordMap));
                    break;
                default:
                    // Other types have no additional data.
                    Debug.Assert(type is BinaryType.String or BinaryType.ObjectArray or BinaryType.StringArray or BinaryType.Object);
                    break;
            }
        }
 
        return new MemberTypeInfo(info);
    }
 
    internal (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetNextAllowedRecordType(int currentValuesCount)
    {
        (BinaryType binaryType, object? additionalInfo) = Infos[currentValuesCount];
 
        // Every array can be either an array itself, a null or a reference (to an array)
        const AllowedRecordTypes StringArray = AllowedRecordTypes.ArraySingleString
            | AllowedRecordTypes.ObjectNull | AllowedRecordTypes.MemberReference;
        const AllowedRecordTypes PrimitiveArray = AllowedRecordTypes.ArraySinglePrimitive
            | AllowedRecordTypes.ObjectNull | AllowedRecordTypes.MemberReference;
        const AllowedRecordTypes ObjectArray = AllowedRecordTypes.ArraySingleObject
            | AllowedRecordTypes.ObjectNull | AllowedRecordTypes.MemberReference;
 
        // Every string can be a string, a null or a reference (to a string)
        const AllowedRecordTypes Strings = AllowedRecordTypes.BinaryObjectString
            | AllowedRecordTypes.ObjectNull | AllowedRecordTypes.MemberReference;
 
        // Every class can be a null or a reference and a ClassWithId
        const AllowedRecordTypes Classes = AllowedRecordTypes.ClassWithId
            | AllowedRecordTypes.ObjectNull | AllowedRecordTypes.MemberReference
            | AllowedRecordTypes.MemberPrimitiveTyped
            | AllowedRecordTypes.BinaryLibrary; // Classes may be preceded with a library record (System too!)
        // but System Classes can be expressed only by System records
        const AllowedRecordTypes SystemClass = Classes | AllowedRecordTypes.SystemClassWithMembersAndTypes;
        const AllowedRecordTypes NonSystemClass = Classes |  AllowedRecordTypes.ClassWithMembersAndTypes;
 
        return binaryType switch
        {
            BinaryType.Primitive => (default, (PrimitiveType)additionalInfo!),
            BinaryType.String => (Strings, default),
            BinaryType.Object => (AllowedRecordTypes.AnyObject, default),
            BinaryType.StringArray => (StringArray, default),
            BinaryType.PrimitiveArray => (PrimitiveArray, default),
            BinaryType.Class => (NonSystemClass, default),
            BinaryType.SystemClass => (SystemClass, default),
            _ => (ObjectArray, default)
        };
    }
 
    internal bool IsElementType(Type typeElement)
    {
        (BinaryType binaryType, object? additionalInfo) = Infos[0];
 
        switch (binaryType)
        {
            case BinaryType.String:
                return typeElement == typeof(string);
            case BinaryType.StringArray:
                return typeElement == typeof(string[]);
            case BinaryType.Object:
                return typeElement == typeof(object);
            case BinaryType.ObjectArray:
                return typeElement == typeof(object[]);
            case BinaryType.Primitive:
            case BinaryType.PrimitiveArray:
                if (binaryType is BinaryType.PrimitiveArray)
                {
                    if (!typeElement.IsArray)
                    {
                        return false;
                    }
 
                    typeElement = typeElement.GetElementType()!;
                }
 
                return ((PrimitiveType)additionalInfo!) switch
                {
                    PrimitiveType.Boolean => typeElement == typeof(bool),
                    PrimitiveType.Byte => typeElement == typeof(byte),
                    PrimitiveType.Char => typeElement == typeof(char),
                    PrimitiveType.Decimal => typeElement == typeof(decimal),
                    PrimitiveType.Double => typeElement == typeof(double),
                    PrimitiveType.Int16 => typeElement == typeof(short),
                    PrimitiveType.Int32 => typeElement == typeof(int),
                    PrimitiveType.Int64 => typeElement == typeof(long),
                    PrimitiveType.SByte => typeElement == typeof(sbyte),
                    PrimitiveType.Single => typeElement == typeof(float),
                    PrimitiveType.TimeSpan => typeElement == typeof(TimeSpan),
                    PrimitiveType.DateTime => typeElement == typeof(DateTime),
                    PrimitiveType.UInt16 => typeElement == typeof(ushort),
                    PrimitiveType.UInt32 => typeElement == typeof(uint),
                    PrimitiveType.UInt64 => typeElement == typeof(ulong),
                    _ => false
                };
            case BinaryType.SystemClass:
                if (typeElement.Assembly != typeof(object).Assembly)
                {
                    return false;
                }
 
                TypeName typeName = (TypeName)additionalInfo!;
                string fullSystemClassName = typeElement.GetTypeFullNameIncludingTypeForwards();
                return typeName.FullName == fullSystemClassName;
            default:
                Debug.Assert(binaryType is BinaryType.Class, "The parsers should reject other inputs");
 
                ClassTypeInfo typeInfo = (ClassTypeInfo)additionalInfo!;
                string fullClassName = typeElement.GetTypeFullNameIncludingTypeForwards();
                if (typeInfo.TypeName.FullName != fullClassName)
                {
                    return false;
                }
 
                string assemblyName = typeElement.GetAssemblyNameIncludingTypeForwards();
                return assemblyName == typeInfo.TypeName.AssemblyName!.FullName;
        }
    }
 
    internal bool ShouldBeRepresentedAsArrayOfClassRecords()
    {
        // This library tries to minimize the number of concepts the users need to learn to use it.
        // Since SZArrays are most common, it provides an ArrayRecord<T> abstraction.
        // Every other array (jagged, multi-dimensional etc) is represented using ArrayRecord.
        // The goal of this method is to determine whether given array can be represented as ArrayRecord<ClassRecord>.
 
        (BinaryType binaryType, object? additionalInfo) = Infos[0];
 
        if (binaryType == BinaryType.Class)
        {
            // An array of arrays can not be represented as ArrayRecord<ClassRecord>.
            return !((ClassTypeInfo)additionalInfo!).TypeName.IsArray;
        }
        else if (binaryType == BinaryType.SystemClass)
        {
            TypeName typeName = (TypeName)additionalInfo!;
 
            // An array of arrays can not be represented as ArrayRecord<ClassRecord>.
            if (typeName.IsArray)
            {
                return false;
            }
 
            if (!typeName.IsConstructedGenericType)
            {
                return true;
            }
 
            // Can't use ArrayRecord<ClassRecord> for Nullable<T>[]
            // as it consists of MemberPrimitiveTypedRecord and NullsRecord
            return typeName.GetGenericTypeDefinition().FullName != typeof(Nullable<>).FullName;
        }
 
        return false;
    }
 
    internal TypeName GetElementTypeName()
    {
        (BinaryType binaryType, object? additionalInfo) = Infos[0];
 
        switch (binaryType)
        {
            case BinaryType.String:
                return TypeName.Parse(typeof(string).FullName.AsSpan()).WithCoreLibAssemblyName();
            case BinaryType.StringArray:
                return TypeName.Parse(typeof(string[]).FullName.AsSpan()).WithCoreLibAssemblyName();
            case BinaryType.Object:
                return TypeName.Parse(typeof(object).FullName.AsSpan()).WithCoreLibAssemblyName();
            case BinaryType.ObjectArray:
                return TypeName.Parse(typeof(object[]).FullName.AsSpan()).WithCoreLibAssemblyName();
            case BinaryType.Primitive:
            case BinaryType.PrimitiveArray:
                string? name = ((PrimitiveType)additionalInfo!) switch
                {
                    PrimitiveType.Boolean => typeof(bool).FullName,
                    PrimitiveType.Byte => typeof(byte).FullName,
                    PrimitiveType.Char => typeof(char).FullName,
                    PrimitiveType.Decimal => typeof(decimal).FullName,
                    PrimitiveType.Double => typeof(double).FullName,
                    PrimitiveType.Int16 => typeof(short).FullName,
                    PrimitiveType.Int32 => typeof(int).FullName,
                    PrimitiveType.Int64 => typeof(long).FullName,
                    PrimitiveType.SByte => typeof(sbyte).FullName,
                    PrimitiveType.Single => typeof(float).FullName,
                    PrimitiveType.TimeSpan => typeof(TimeSpan).FullName,
                    PrimitiveType.DateTime => typeof(DateTime).FullName,
                    PrimitiveType.UInt16 => typeof(ushort).FullName,
                    PrimitiveType.UInt32 => typeof(uint).FullName,
                    _ => typeof(ulong).FullName,
                };
 
                return binaryType is BinaryType.PrimitiveArray
                    ? TypeName.Parse($"{name}[], {TypeNameExtensions.CoreLibAssemblyName}".AsSpan())
                    : TypeName.Parse(name.AsSpan()).WithCoreLibAssemblyName();
 
            case BinaryType.SystemClass:
                return (TypeName)additionalInfo!;
            default:
                Debug.Assert(binaryType is BinaryType.Class, "The parsers should reject other inputs");
                return ((ClassTypeInfo)additionalInfo!).TypeName;
        }
    }
}