Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,16 @@ System.Formats.Cbor.CborWriter</PackageDescription>
<Compile Include="System\Formats\Cbor\CborInitialByte.cs" />
<Compile Include="System\Formats\Cbor\CborTag.cs" />
<Compile Include="System\Formats\Cbor\CborContentException.cs" />
<Compile Include="System\Formats\Cbor\CborHelpers.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
<Compile Include="System\Formats\Cbor\CborHelpers.netcoreapp.cs" />
<Compile Include="System\Formats\Cbor\HalfHelpers.netcoreapp.cs" />
<Compile Include="System\Formats\Cbor\Reader\CborReader.Simple.netcoreapp.cs" />
<Compile Include="System\Formats\Cbor\Writer\CborWriter.Simple.netcoreapp.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
<Compile Include="System\Formats\Cbor\CborHelpers.netstandard.cs" />
<Compile Include="System\Formats\Cbor\HalfHelpers.netstandard.cs" />
<Compile Include="System\Formats\Cbor\Writer\CborWriter.Simple.netstandard.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
Expand All @@ -11,12 +12,20 @@

namespace System.Formats.Cbor
{
internal static partial class CborHelpers
internal static class CborHelpers
Copy link
Copy Markdown
Member

@jkotas jkotas Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not mean to add a bunch of ifdefs to the common file.

I meant to:

  • Delete CborHelpers.netcoreapp.cs
  • Use the netcoreapp APIs directly throughout the code
  • Add extensions to CborHelpers.netstandard.cs to polyfill the netcoreapp APIs

Copy link
Copy Markdown
Member Author

@EgorBo EgorBo Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I did have that in mind, in my workflow I just ask AI to address the feedback and then change it. It sometimes proactively pushes it before I change it, but I assumed it's fine since the PR is in draft 🙂 Still, I appreciate the early feedback!

{
#if NET
public static readonly DateTimeOffset UnixEpoch = DateTimeOffset.UnixEpoch;
#else
private const long UnixEpochTicks = 719162L /*Number of days from 1/1/0001 to 12/31/1969*/ * 10000 * 1000 * 60 * 60 * 24; /* Ticks per day.*/

public static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(UnixEpochTicks, TimeSpan.Zero);
#endif

#if NET
public static BigInteger CreateBigIntegerFromUnsignedBigEndianBytes(byte[] bytes)
=> new BigInteger(bytes, isUnsigned: true, isBigEndian: true);
#else
public static BigInteger CreateBigIntegerFromUnsignedBigEndianBytes(byte[] bigEndianBytes)
{
if (bigEndianBytes.Length == 0)
Expand Down Expand Up @@ -44,7 +53,12 @@ public static BigInteger CreateBigIntegerFromUnsignedBigEndianBytes(byte[] bigEn

return new BigInteger(temp);
}
#endif

#if NET
public static byte[] CreateUnsignedBigEndianBytesFromBigInteger(BigInteger value)
=> value.ToByteArray(isUnsigned: true, isBigEndian: true);
#else
public static byte[] CreateUnsignedBigEndianBytesFromBigInteger(BigInteger value)
{
byte[] littleEndianBytes = value.ToByteArray();
Expand Down Expand Up @@ -74,12 +88,22 @@ public static byte[] CreateUnsignedBigEndianBytesFromBigInteger(BigInteger value

return start == 0 ? littleEndianBytes : bytesAsSpan.Slice(start).ToArray();
}
#endif

#if NET
public static void GetBitsFromDecimal(decimal d, Span<int> destination)
=> decimal.GetBits(d, destination);
#else
public static void GetBitsFromDecimal(decimal d, Span<int> destination)
{
decimal.GetBits(d).CopyTo(destination);
}
#endif

#if NET
public static string BuildStringFromIndefiniteLengthTextString<TState>(int length, TState state, SpanAction<char, TState> action)
=> string.Create(length, state, action);
#else
public delegate void SpanAction<T, in TArg>(Span<T> span, TArg arg);

public static string BuildStringFromIndefiniteLengthTextString<TState>(int length, TState state, SpanAction<char, TState> action)
Expand All @@ -88,87 +112,71 @@ public static string BuildStringFromIndefiniteLengthTextString<TState>(int lengt
action(arr, state);
return new string(arr);
}
#endif

#if NET
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Half ReadHalfBigEndian(ReadOnlySpan<byte> source)
=> BinaryPrimitives.ReadHalfBigEndian(source);
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ReadHalfBigEndian(ReadOnlySpan<byte> source)
Comment thread
jkotas marked this conversation as resolved.
{
ushort value = BitConverter.IsLittleEndian ?
BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read<ushort>(source)) :
MemoryMarshal.Read<ushort>(source);

return value;
return BinaryPrimitives.ReadUInt16BigEndian(source);
}
Comment thread
EgorBo marked this conversation as resolved.
#endif
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteHalfBigEndian(Span<byte> destination, ushort value)
#if !NET
internal static class BinaryPrimitivesPolyfills
{
extension(BinaryPrimitives)
{
if (BitConverter.IsLittleEndian)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double ReadDoubleBigEndian(ReadOnlySpan<byte> source)
{
ushort tmp = BinaryPrimitives.ReverseEndianness(value);
MemoryMarshal.Write(destination, ref tmp);
return BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64BigEndian(source));
}
else
{
MemoryMarshal.Write(destination, ref value);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ReadSingleBigEndian(ReadOnlySpan<byte> source)
{
return BitConverter.IsLittleEndian ?
Int32BitsToSingle(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read<int>(source))) :
MemoryMarshal.Read<float>(source);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteSingleBigEndian(Span<byte> destination, float value)
{
if (BitConverter.IsLittleEndian)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteDoubleBigEndian(Span<byte> destination, double value)
{
int tmp = BinaryPrimitives.ReverseEndianness(SingleToInt32Bits(value));
MemoryMarshal.Write(destination, ref tmp);
BinaryPrimitives.WriteInt64BigEndian(destination, BitConverter.DoubleToInt64Bits(value));
}
else
{
MemoryMarshal.Write(destination, ref value);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double ReadDoubleBigEndian(ReadOnlySpan<byte> source)
{
return BitConverter.IsLittleEndian ?
BitConverter.Int64BitsToDouble(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read<long>(source))) :
MemoryMarshal.Read<double>(source);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteDoubleBigEndian(Span<byte> destination, double value)
{
if (BitConverter.IsLittleEndian)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe float ReadSingleBigEndian(ReadOnlySpan<byte> source)
{
long tmp = BinaryPrimitives.ReverseEndianness(BitConverter.DoubleToInt64Bits(value));
MemoryMarshal.Write(destination, ref tmp);
int intValue = BinaryPrimitives.ReadInt32BigEndian(source);
return *((float*)&intValue);
}
else

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void WriteSingleBigEndian(Span<byte> destination, float value)
{
MemoryMarshal.Write(destination, ref value);
BinaryPrimitives.WriteInt32BigEndian(destination, *((int*)&value));
}
Comment thread
EgorBo marked this conversation as resolved.
}
}

internal static uint SingleToUInt32Bits(float value)
=> (uint)SingleToInt32Bits(value);
internal static class BitConverterPolyfills
{
extension(BitConverter)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int SingleToInt32Bits(float value) => *((int*)&value);

internal static unsafe int SingleToInt32Bits(float value)
=> *((int*)&value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe float Int32BitsToSingle(int value) => *((float*)&value);

internal static float UInt32BitsToSingle(uint value)
=> Int32BitsToSingle((int)value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint SingleToUInt32Bits(float value) => unchecked((uint)BitConverter.SingleToInt32Bits(value));

internal static unsafe float Int32BitsToSingle(int value)
=> *((float*)&value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float UInt32BitsToSingle(uint value) => BitConverter.Int32BitsToSingle((int)value);
}
}
#endif

internal static class StackExtensions
{
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -53,7 +53,7 @@ public static float HalfToFloat(ushort value)
{
if (sig == 0)
{
return CborHelpers.UInt32BitsToSingle(sign ? FloatSignMask : 0); // Positive / Negative zero
return BitConverter.UInt32BitsToSingle(sign ? FloatSignMask : 0); // Positive / Negative zero
}
(exp, sig) = NormSubnormalF16Sig(sig);
exp -= 1;
Expand All @@ -62,7 +62,7 @@ public static float HalfToFloat(ushort value)
return CreateSingle(sign, (byte)(exp + 0x70), sig << 13);

static float CreateSingle(bool sign, byte exp, uint sig)
=> CborHelpers.Int32BitsToSingle((int)(((sign ? 1U : 0U) << FloatSignShift) + ((uint)exp << FloatExponentShift) + sig));
=> BitConverter.Int32BitsToSingle((int)(((sign ? 1U : 0U) << FloatSignShift) + ((uint)exp << FloatExponentShift) + sig));
}
Comment on lines 56 to 66
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BitConverter.UInt32BitsToSingle, BitConverter.Int32BitsToSingle, and BitConverter.SingleToUInt32Bits are not available on the netstandard2.0 / .NET Framework TFMs that this file targets, so these replacements will fail to compile without a robust polyfill. Please keep using the existing internal conversion helpers (previously on CborHelpers) or implement local bit-conversion helpers for these TFMs.

Copilot uses AI. Check for mistakes.

public static bool HalfIsNaN(ushort value)
Expand Down Expand Up @@ -119,7 +119,7 @@ private static float CreateSingleNaN(bool sign, ulong significand)
uint signInt = (sign ? 1U : 0U) << FloatSignShift;
uint sigInt = (uint)(significand >> 41);

return CborHelpers.UInt32BitsToSingle(signInt | NaNBits | sigInt);
return BitConverter.UInt32BitsToSingle(signInt | NaNBits | sigInt);
}
#endregion

Expand All @@ -128,7 +128,7 @@ public static ushort FloatToHalf(float value)
{
const int SingleMaxExponent = 0xFF;

uint floatInt = CborHelpers.SingleToUInt32Bits(value);
uint floatInt = BitConverter.SingleToUInt32Bits(value);
bool sign = (floatInt & FloatSignMask) >> FloatSignShift != 0;
int exp = (int)(floatInt & FloatExponentMask) >> FloatExponentShift;
uint sig = floatInt & FloatSignificandMask;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public float ReadSingle()

case CborAdditionalInfo.Additional32BitData:
EnsureReadCapacity(buffer, 1 + sizeof(float));
result = CborHelpers.ReadSingleBigEndian(buffer.Slice(1));
result = BinaryPrimitives.ReadSingleBigEndian(buffer.Slice(1));
AdvanceBuffer(1 + sizeof(float));
AdvanceDataItemCounters();
return result;
Expand Down Expand Up @@ -77,14 +77,14 @@ public double ReadDouble()

case CborAdditionalInfo.Additional32BitData:
EnsureReadCapacity(buffer, 1 + sizeof(float));
result = CborHelpers.ReadSingleBigEndian(buffer.Slice(1));
result = BinaryPrimitives.ReadSingleBigEndian(buffer.Slice(1));
AdvanceBuffer(1 + sizeof(float));
AdvanceDataItemCounters();
return result;

case CborAdditionalInfo.Additional64BitData:
EnsureReadCapacity(buffer, 1 + sizeof(double));
result = CborHelpers.ReadDoubleBigEndian(buffer.Slice(1));
result = BinaryPrimitives.ReadDoubleBigEndian(buffer.Slice(1));
AdvanceBuffer(1 + sizeof(double));
AdvanceDataItemCounters();
return result;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers.Binary;
using System.Runtime.CompilerServices;

namespace System.Formats.Cbor
Expand Down Expand Up @@ -63,7 +64,7 @@ private void WriteSingleCore(float value)
{
EnsureWriteCapacity(1 + sizeof(float));
WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional32BitData));
CborHelpers.WriteSingleBigEndian(_buffer.AsSpan(_offset), value);
BinaryPrimitives.WriteSingleBigEndian(_buffer.AsSpan(_offset), value);
_offset += sizeof(float);
AdvanceDataItemCounters();
}
Expand All @@ -72,7 +73,7 @@ private void WriteDoubleCore(double value)
{
EnsureWriteCapacity(1 + sizeof(double));
WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional64BitData));
CborHelpers.WriteDoubleBigEndian(_buffer.AsSpan(_offset), value);
BinaryPrimitives.WriteDoubleBigEndian(_buffer.AsSpan(_offset), value);
_offset += sizeof(double);
AdvanceDataItemCounters();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers.Binary;
Expand Down Expand Up @@ -27,7 +27,7 @@ private void WriteHalf(ushort value)
}
else
{
CborHelpers.WriteHalfBigEndian(_buffer.AsSpan(_offset), value);
BinaryPrimitives.WriteUInt16BigEndian(_buffer.AsSpan(_offset), value);
}
_offset += sizeof(ushort);
AdvanceDataItemCounters();
Expand All @@ -37,7 +37,7 @@ private void WriteHalf(ushort value)
internal static bool TryConvertSingleToHalf(float value, out ushort result)
{
result = HalfHelpers.FloatToHalf(value);
return float.IsNaN(value) || CborHelpers.SingleToInt32Bits(HalfHelpers.HalfToFloat(result)) == CborHelpers.SingleToInt32Bits(value);
return float.IsNaN(value) || BitConverter.SingleToInt32Bits(HalfHelpers.HalfToFloat(result)) == BitConverter.SingleToInt32Bits(value);
}
Comment on lines 39 to 41
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On non-.NETCoreApp targets (netstandard2.0 / .NET Framework), BitConverter.SingleToInt32Bits is not available (the test helper for netstandard uses an unsafe bitcast for this reason). This change will break compilation unless you provide a valid polyfill; consider restoring the previous internal helper (e.g., CborHelpers.SingleToInt32Bits) or comparing bit patterns via BinaryPrimitives-based conversions instead.

Copilot uses AI. Check for mistakes.
}
}
Loading
Loading