File: System\Formats\Cbor\CborConformanceLevel.cs
Web Access
Project: src\src\libraries\System.Formats.Cbor\src\System.Formats.Cbor.csproj (System.Formats.Cbor)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
 
namespace System.Formats.Cbor
{
    /// <summary>Defines supported conformance modes for encoding and decoding CBOR data.</summary>
    public enum CborConformanceMode
    {
        /// <summary>Ensures that the CBOR data is well-formed, as specified in RFC7049.</summary>
        Lax,
 
        /// <summary>
        /// <para>Ensures that the CBOR data adheres to strict mode, as specified in RFC7049 section 3.10.</para>
        /// <para>Extends lax conformance with the following requirements:</para>
        /// <list type="bullet">
        /// <item><description>Maps (major type 5) must not contain duplicate keys.</description></item>
        /// <item><description>Simple values (major type 7) must be encoded as small a possible and exclude the reserved values 24-31.</description></item>
        /// <item><description>UTF-8 string encodings must be valid.</description></item>
        /// </list>
        /// </summary>
        Strict,
 
        /// <summary>
        /// <para>Ensures that the CBOR data is canonical, as specified in RFC7049 section 3.9.</para>
        /// <para>Extends strict conformance with the following requirements:</para>
        /// <list type="bullet">
        /// <item><description>Integers must be encoded as small as possible.</description></item>
        /// <item><description>Maps (major type 5) must contain keys sorted by encoding.</description></item>
        /// <item><description>Indefinite-length items must be made into definite-length items.</description></item>
        /// </list>
        /// </summary>
        Canonical,
 
        /// <summary>
        /// <para>Ensures that the CBOR data is canonical, as specified by the CTAP v2.0 standard, section 6.</para>
        /// <para>Extends strict conformance with the following requirements:</para>
        /// <list type="bullet">
        /// <item><description>Maps (major type 5) must contain keys sorted by encoding.</description></item>
        /// <item><description>Indefinite-length items must be made into definite-length items.</description></item>
        /// <item><description>Integers must be encoded as small as possible.</description></item>
        /// <item><description>The representations of any floating-point values are not changed.</description></item>
        /// <item><description>CBOR tags (major type 6) are not permitted.</description></item>
        /// </list>
        /// </summary>
        Ctap2Canonical,
    }
 
    internal static class CborConformanceModeHelpers
    {
        private static readonly UTF8Encoding s_utf8EncodingLax    = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);
        private static readonly UTF8Encoding s_utf8EncodingStrict = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
 
        public static void Validate(CborConformanceMode conformanceMode)
        {
            if (conformanceMode < CborConformanceMode.Lax ||
                conformanceMode > CborConformanceMode.Ctap2Canonical)
            {
                throw new ArgumentOutOfRangeException(nameof(conformanceMode));
            }
        }
 
        public static bool RequiresCanonicalIntegerRepresentation(CborConformanceMode conformanceMode)
        {
            switch (conformanceMode)
            {
                case CborConformanceMode.Lax:
                case CborConformanceMode.Strict:
                    return false;
 
                case CborConformanceMode.Canonical:
                case CborConformanceMode.Ctap2Canonical:
                    return true;
 
                default:
                    throw new ArgumentOutOfRangeException(nameof(conformanceMode));
            };
        }
 
        public static bool RequiresPreservingFloatPrecision(CborConformanceMode conformanceMode)
        {
            switch (conformanceMode)
            {
                case CborConformanceMode.Lax:
                case CborConformanceMode.Strict:
                case CborConformanceMode.Canonical:
                    return false;
 
                case CborConformanceMode.Ctap2Canonical:
                    return true;
 
                default:
                    throw new ArgumentOutOfRangeException(nameof(conformanceMode));
            };
        }
 
        public static bool RequiresUtf8Validation(CborConformanceMode conformanceMode)
        {
            switch (conformanceMode)
            {
                case CborConformanceMode.Lax:
                    return false;
 
                case CborConformanceMode.Strict:
                case CborConformanceMode.Canonical:
                case CborConformanceMode.Ctap2Canonical:
                    return true;
 
                default:
                    throw new ArgumentOutOfRangeException(nameof(conformanceMode));
            };
        }
 
        public static Encoding GetUtf8Encoding(CborConformanceMode conformanceMode)
        {
            return conformanceMode == CborConformanceMode.Lax ? s_utf8EncodingLax : s_utf8EncodingStrict;
        }
 
        public static bool RequiresDefiniteLengthItems(CborConformanceMode conformanceMode)
        {
            switch (conformanceMode)
            {
                case CborConformanceMode.Lax:
                case CborConformanceMode.Strict:
                    return false;
 
                case CborConformanceMode.Canonical:
                case CborConformanceMode.Ctap2Canonical:
                    return true;
 
                default:
                    throw new ArgumentOutOfRangeException(nameof(conformanceMode));
            };
        }
 
        public static bool AllowsTags(CborConformanceMode conformanceMode)
        {
            switch (conformanceMode)
            {
                case CborConformanceMode.Lax:
                case CborConformanceMode.Strict:
                case CborConformanceMode.Canonical:
                    return true;
 
                case CborConformanceMode.Ctap2Canonical:
                    return false;
 
                default:
                    throw new ArgumentOutOfRangeException(nameof(conformanceMode));
            };
        }
 
        public static bool RequiresUniqueKeys(CborConformanceMode conformanceMode)
        {
            switch (conformanceMode)
            {
                case CborConformanceMode.Lax:
                    return false;
 
                case CborConformanceMode.Strict:
                case CborConformanceMode.Canonical:
                case CborConformanceMode.Ctap2Canonical:
                    return true;
 
                default:
                    throw new ArgumentOutOfRangeException(nameof(conformanceMode));
            };
        }
 
        public static bool RequiresSortedKeys(CborConformanceMode conformanceMode)
        {
            switch (conformanceMode)
            {
                case CborConformanceMode.Strict:
                case CborConformanceMode.Lax:
                    return false;
 
                case CborConformanceMode.Canonical:
                case CborConformanceMode.Ctap2Canonical:
                    return true;
 
                default:
                    throw new ArgumentOutOfRangeException(nameof(conformanceMode));
            };
        }
 
        public static bool RequireCanonicalSimpleValueEncodings(CborConformanceMode conformanceMode)
        {
            switch (conformanceMode)
            {
                case CborConformanceMode.Lax:
                    return false;
 
                case CborConformanceMode.Strict:
                case CborConformanceMode.Canonical:
                case CborConformanceMode.Ctap2Canonical:
                    return true;
 
                default:
                    throw new ArgumentOutOfRangeException(nameof(conformanceMode));
            }
        }
 
        public static int GetKeyEncodingHashCode(ReadOnlySpan<byte> encoding)
        {
            HashCode hash = default;
#if NET
            hash.AddBytes(encoding);
#else
            while (encoding.Length >= sizeof(int))
            {
                hash.Add(MemoryMarshal.Read<int>(encoding));
                encoding = encoding.Slice(sizeof(int));
            }
 
            foreach (byte b in encoding)
            {
                hash.Add(b);
            }
#endif
            return hash.ToHashCode();
        }
 
        public static bool AreEqualKeyEncodings(ReadOnlySpan<byte> left, ReadOnlySpan<byte> right)
        {
            return left.SequenceEqual(right);
        }
 
        public static int CompareKeyEncodings(ReadOnlySpan<byte> left, ReadOnlySpan<byte> right, CborConformanceMode mode)
        {
            Debug.Assert(!left.IsEmpty && !right.IsEmpty);
 
            switch (mode)
            {
                case CborConformanceMode.Canonical:
                    // Implements key sorting according to
                    // https://tools.ietf.org/html/rfc7049#section-3.9
 
                    if (left.Length != right.Length)
                    {
                        return left.Length - right.Length;
                    }
 
                    return left.SequenceCompareTo(right);
 
                case CborConformanceMode.Ctap2Canonical:
                    // Implements key sorting according to
                    // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#message-encoding
 
                    int leftMt = (int)new CborInitialByte(left[0]).MajorType;
                    int rightMt = (int)new CborInitialByte(right[0]).MajorType;
 
                    if (leftMt != rightMt)
                    {
                        return leftMt - rightMt;
                    }
 
                    if (left.Length != right.Length)
                    {
                        return left.Length - right.Length;
                    }
 
                    return left.SequenceCompareTo(right);
 
                default:
                    Debug.Fail("Invalid conformance mode used in encoding sort.");
                    throw new Exception();
            }
        }
    }
}