peteroupc / CBOR

A C# implementation of Concise Binary Object Representation (RFC 8949).
The Unlicense
206 stars 29 forks source link

CBORObject.Read & ToObject, CBOR byte string (type 2) and target types #66

Closed finnkam closed 1 year ago

finnkam commented 1 year ago

Hi Peter,

Still a happy user of PeterO.Cbor (v4.5.2). Thanks for your efforts. :)

In relation to the application I am developing I have stumbled on something subtle with our use of deserializing bytes with PeterO.Cbor. Specifically the lib is rather picky when deserializing CBOR byte string (type 2) to a CLR byte array/list type.

Here is an NUnit test to demonstrate:

        [Test]
        [TestCaseSource(nameof(BytesAndTargetTypes))]
        public void SerializeBytesAndDeserialize(object bytes, Type targetType)
        {
            // Arrange
            using var stream = new MemoryStream();

            // Act
            CBORObject.FromObject(bytes).WriteTo(stream);
            stream.Position = 0;
            var deserializedBytes = CBORObject.Read(stream).ToObject(targetType);

            // Assert
            Assert.That(deserializedBytes, Is.EqualTo(bytes));
        }

        private static readonly object[] BytesAndTargetTypes =
        {
            // FromObject byte[] - yields CBOR byte string type 2
            new object[] { new [] { (byte)65 }, typeof(byte[]) },
            new object[] { new [] { (byte)65 }, typeof(Collection<byte>) },
            new object[] { new [] { (byte)65 }, typeof(List<byte>) },
            new object[] { new [] { (byte)65 }, typeof(ICollection<byte>) },
            new object[] { new [] { (byte)65 }, typeof(IList<byte>) },
            new object[] { new [] { (byte)65 }, typeof(IReadOnlyCollection<byte>) },
            new object[] { new [] { (byte)65 }, typeof(IReadOnlyList<byte>) },

            // FromObject List<byte> - yields CBOR array type 4
            new object[] { new List<byte> { 65 }, typeof(byte[]) },
            new object[] { new List<byte> { 65 }, typeof(Collection<byte>) },
            new object[] { new List<byte> { 65 }, typeof(List<byte>) },
            new object[] { new List<byte> { 65 }, typeof(ICollection<byte>) },
            new object[] { new List<byte> { 65 }, typeof(IList<byte>) },
            new object[] { new List<byte> { 65 }, typeof(IReadOnlyCollection<byte>) },
            new object[] { new List<byte> { 65 }, typeof(IReadOnlyList<byte>) },
        };

When I run this test I get: image

Deserializing CBOR byte string works only when the target type is byte[] - not any of the [I][ReadOnly]Collection/List types work. And, deserializing CBOR array (of bytes) can be done to any of the target types; byte[] or [I][ReadOnly]Collection/List.

Is this how deserializing CBOR byte string is meant to work?

peteroupc commented 1 year ago

I note that there are two possible ways to serialize from non-array byte lists to CBOR:

And this library chooses to serialize as a CBOR array of integers to avoid ambiguities in round-tripping between CBOR and plain-old-data types that use non-array byte lists.

I agree that deserializing from byte string to non-array list types is useful to support, but it is a design question—

finnkam commented 1 year ago

Hi Peter, Thanks for your response. After thinking more about it and reading your response I think it makes good sense to treat CBOR byte strings and CBOR array of integers/bytes as different things. Closing issue. Thanks again. :)