Skip to content

Read/Write Big/LittleEndian support for Guid #87993

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
114 changes: 81 additions & 33 deletions src/libraries/System.Private.CoreLib/src/System/Guid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,35 +53,43 @@ public Guid(ReadOnlySpan<byte> b)
{
if (b.Length != 16)
{
ThrowArgumentException();
ThrowGuidArrayCtorArgumentException();
}

if (BitConverter.IsLittleEndian)
this = MemoryMarshal.Read<Guid>(b);

if (!BitConverter.IsLittleEndian)
{
this = MemoryMarshal.Read<Guid>(b);
return;
_a = BinaryPrimitives.ReverseEndianness(_a);
_b = BinaryPrimitives.ReverseEndianness(_b);
_c = BinaryPrimitives.ReverseEndianness(_c);
}
}

// slower path for BigEndian:
_k = b[15]; // hoist bounds checks
_a = BinaryPrimitives.ReadInt32LittleEndian(b);
_b = BinaryPrimitives.ReadInt16LittleEndian(b.Slice(4));
_c = BinaryPrimitives.ReadInt16LittleEndian(b.Slice(6));
_d = b[8];
_e = b[9];
_f = b[10];
_g = b[11];
_h = b[12];
_i = b[13];
_j = b[14];
public Guid(ReadOnlySpan<byte> b, bool bigEndian)
{
if (b.Length != 16)
{
ThrowGuidArrayCtorArgumentException();
}

[StackTraceHidden]
static void ThrowArgumentException()
this = MemoryMarshal.Read<Guid>(b);

if (BitConverter.IsLittleEndian == bigEndian)
{
throw new ArgumentException(SR.Format(SR.Arg_GuidArrayCtor, "16"), nameof(b));
_a = BinaryPrimitives.ReverseEndianness(_a);
_b = BinaryPrimitives.ReverseEndianness(_b);
_c = BinaryPrimitives.ReverseEndianness(_c);
}
}

[DoesNotReturn]
[StackTraceHidden]
private static void ThrowGuidArrayCtorArgumentException()
{
throw new ArgumentException(SR.Format(SR.Arg_GuidArrayCtor, "16"), "b");
}

[CLSCompliant(false)]
public Guid(uint a, ushort b, ushort c, byte d, byte e, byte f, byte g, byte h, byte i, byte j, byte k)
{
Expand Down Expand Up @@ -843,6 +851,10 @@ private static bool IsHexPrefix(ReadOnlySpan<char> str, int i) =>
str[i] == '0' &&
(str[i + 1] | 0x20) == 'x';

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe ReadOnlySpan<byte> AsBytes(in Guid source) =>
new ReadOnlySpan<byte>(Unsafe.AsPointer(ref Unsafe.AsRef(in source)), sizeof(Guid));

// Returns an unsigned byte array containing the GUID.
public byte[] ToByteArray()
{
Expand All @@ -853,34 +865,70 @@ public byte[] ToByteArray()
}
else
{
TryWriteBytes(g);
// slower path for BigEndian
Guid guid = new Guid(AsBytes(this), false);
MemoryMarshal.TryWrite(g, ref Unsafe.AsRef(in guid));
}
return g;
}


// Returns an unsigned byte array containing the GUID.
public byte[] ToByteArray(bool bigEndian)
{
var g = new byte[16];
if (BitConverter.IsLittleEndian != bigEndian)
{
MemoryMarshal.TryWrite(g, ref Unsafe.AsRef(in this));
}
else
{
// slower path for Reverse
Guid guid = new Guid(AsBytes(this), bigEndian);
MemoryMarshal.TryWrite(g, ref Unsafe.AsRef(in guid));
}
return g;
}

// Returns whether bytes are successfully written to given span.
public bool TryWriteBytes(Span<byte> destination)
{
if (destination.Length < 16)
return false;

if (BitConverter.IsLittleEndian)
{
return MemoryMarshal.TryWrite(destination, ref Unsafe.AsRef(in this));
MemoryMarshal.TryWrite(destination, ref Unsafe.AsRef(in this));
}
else
{
// slower path for BigEndian
Guid guid = new Guid(AsBytes(this), false);
MemoryMarshal.TryWrite(destination, ref Unsafe.AsRef(in guid));
}
return true;
}

// slower path for BigEndian
// Returns whether bytes are successfully written to given span.
public bool TryWriteBytes(Span<byte> destination, bool bigEndian, out int bytesWritten)
{
if (destination.Length < 16)
{
bytesWritten = 0;
return false;
}

destination[15] = _k; // hoist bounds checks
BinaryPrimitives.WriteInt32LittleEndian(destination, _a);
BinaryPrimitives.WriteInt16LittleEndian(destination.Slice(4), _b);
BinaryPrimitives.WriteInt16LittleEndian(destination.Slice(6), _c);
destination[8] = _d;
destination[9] = _e;
destination[10] = _f;
destination[11] = _g;
destination[12] = _h;
destination[13] = _i;
destination[14] = _j;
if (BitConverter.IsLittleEndian != bigEndian)
{
MemoryMarshal.TryWrite(destination, ref Unsafe.AsRef(in this));
}
else
{
// slower path for Reverse
Guid guid = new Guid(AsBytes(this), bigEndian);
MemoryMarshal.TryWrite(destination, ref Unsafe.AsRef(in guid));
}
bytesWritten = 16;
return true;
}

Expand Down
4 changes: 4 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2769,6 +2769,7 @@ public enum GCNotificationStatus
public Guid(int a, short b, short c, byte d, byte e, byte f, byte g, byte h, byte i, byte j, byte k) { throw null; }
public Guid(int a, short b, short c, byte[] d) { throw null; }
public Guid(System.ReadOnlySpan<byte> b) { throw null; }
public Guid(System.ReadOnlySpan<byte> b, bool bigEndian) { throw null; }
public Guid(string g) { throw null; }
[System.CLSCompliantAttribute(false)]
public Guid(uint a, ushort b, ushort c, byte d, byte e, byte f, byte g, byte h, byte i, byte j, byte k) { throw null; }
Expand All @@ -2793,6 +2794,7 @@ public enum GCNotificationStatus
bool System.ISpanFormattable.TryFormat(System.Span<char> destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
bool System.IUtf8SpanFormattable.TryFormat(System.Span<byte> utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
public byte[] ToByteArray() { throw null; }
public byte[] ToByteArray(bool bigEndian) { throw null; }
public override string ToString() { throw null; }
public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] string? format) { throw null; }
public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] string? format, System.IFormatProvider? provider) { throw null; }
Expand All @@ -2805,6 +2807,8 @@ public enum GCNotificationStatus
public static bool TryParseExact(System.ReadOnlySpan<char> input, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] System.ReadOnlySpan<char> format, out System.Guid result) { throw null; }
public static bool TryParseExact([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? input, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true), System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] string? format, out System.Guid result) { throw null; }
public bool TryWriteBytes(System.Span<byte> destination) { throw null; }
public bool TryWriteBytes(Span<byte> destination, bool bigEndian, out int bytesWritten) { throw null; }

}
public readonly partial struct Half : System.IComparable, System.IComparable<System.Half>, System.IEquatable<System.Half>, System.IFormattable, System.IParsable<System.Half>, System.ISpanFormattable, System.ISpanParsable<System.Half>, System.Numerics.IAdditionOperators<System.Half, System.Half, System.Half>, System.Numerics.IAdditiveIdentity<System.Half, System.Half>, System.Numerics.IBinaryFloatingPointIeee754<System.Half>, System.Numerics.IBinaryNumber<System.Half>, System.Numerics.IBitwiseOperators<System.Half, System.Half, System.Half>, System.Numerics.IComparisonOperators<System.Half, System.Half, bool>, System.Numerics.IDecrementOperators<System.Half>, System.Numerics.IDivisionOperators<System.Half, System.Half, System.Half>, System.Numerics.IEqualityOperators<System.Half, System.Half, bool>, System.Numerics.IExponentialFunctions<System.Half>, System.Numerics.IFloatingPoint<System.Half>, System.Numerics.IFloatingPointConstants<System.Half>, System.Numerics.IFloatingPointIeee754<System.Half>, System.Numerics.IHyperbolicFunctions<System.Half>, System.Numerics.IIncrementOperators<System.Half>, System.Numerics.ILogarithmicFunctions<System.Half>, System.Numerics.IMinMaxValue<System.Half>, System.Numerics.IModulusOperators<System.Half, System.Half, System.Half>, System.Numerics.IMultiplicativeIdentity<System.Half, System.Half>, System.Numerics.IMultiplyOperators<System.Half, System.Half, System.Half>, System.Numerics.INumber<System.Half>, System.Numerics.INumberBase<System.Half>, System.Numerics.IPowerFunctions<System.Half>, System.Numerics.IRootFunctions<System.Half>, System.Numerics.ISignedNumber<System.Half>, System.Numerics.ISubtractionOperators<System.Half, System.Half, System.Half>, System.Numerics.ITrigonometricFunctions<System.Half>, System.Numerics.IUnaryNegationOperators<System.Half, System.Half>, System.Numerics.IUnaryPlusOperators<System.Half, System.Half>, System.IUtf8SpanFormattable
{
Expand Down
35 changes: 35 additions & 0 deletions src/libraries/System.Runtime/tests/System/GuidTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ public static void Ctor_ByteArray(byte[] b, Guid expected)
Assert.Equal(expected, new Guid(b));
}

public static IEnumerable<object[]> Ctor_ByteArray_BigEndian_TestData()
{
yield return new object[] { new byte[16], true, Guid.Empty };
yield return new object[] { new byte[16], false, Guid.Empty };
yield return new object[] { new byte[] { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }, true, new Guid("11223344-5566-7788-9900-aabbccddeeff") };
yield return new object[] { new byte[] { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }, false, new Guid("44332211-6655-8877-9900-aabbccddeeff") };
yield return new object[] { new byte[] { 0x44, 0x33, 0x22, 0x11, 0x66, 0x55, 0x88, 0x77, 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }, true, new Guid("44332211-6655-8877-9900-aabbccddeeff") };
yield return new object[] { new byte[] { 0x44, 0x33, 0x22, 0x11, 0x66, 0x55, 0x88, 0x77, 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }, false, new Guid("11223344-5566-7788-9900-aabbccddeeff") };
yield return new object[] { s_testGuid.ToByteArray(true), true, s_testGuid };
yield return new object[] { s_testGuid.ToByteArray(), false, s_testGuid };
}

[Theory]
[MemberData(nameof(Ctor_ByteArray_BigEndian_TestData))]
public static void Ctor_ByteArray_BigEndian(byte[] b, bool bigEndian, Guid expected)
{
Assert.Equal(expected, new Guid(b, bigEndian));
}

[Fact]
public static void Ctor_NullByteArray_ThrowsArgumentNullException()
{
Expand Down Expand Up @@ -375,6 +394,8 @@ public static void EqualsTest(Guid guid1, object obj, bool expected)
public static void ToByteArray()
{
Assert.Equal(new byte[] { 0xd5, 0x10, 0xa1, 0xa8, 0x49, 0xfc, 0xc5, 0x43, 0xbf, 0x46, 0x80, 0x2d, 0xb8, 0xf8, 0x43, 0xff }, s_testGuid.ToByteArray());
Assert.Equal(new byte[] { 0xd5, 0x10, 0xa1, 0xa8, 0x49, 0xfc, 0xc5, 0x43, 0xbf, 0x46, 0x80, 0x2d, 0xb8, 0xf8, 0x43, 0xff }, s_testGuid.ToByteArray(false));
Assert.Equal(new byte[] { 0xa8, 0xa1, 0x10, 0xd5, 0xfc, 0x49, 0x43, 0xc5, 0xbf, 0x46, 0x80, 0x2d, 0xb8, 0xf8, 0x43, 0xff }, s_testGuid.ToByteArray(true));
}

public static IEnumerable<object[]> ToString_TestData()
Expand Down Expand Up @@ -834,12 +855,26 @@ public static void TryWriteBytes_ValidLength_ReturnsTrue(byte[] b, Guid guid)
Assert.Equal(b, bytes);
}

[Theory]
[MemberData(nameof(Ctor_ByteArray_BigEndian_TestData))]
public static void TryWriteBytes_BigEndian_ValidLength_ReturnsTrue(byte[] b, bool bigEndian, Guid guid)
{
var bytes = new byte[16];
Assert.True(guid.TryWriteBytes(new Span<byte>(bytes), bigEndian, out int bytesWritten));
Assert.Equal(b, bytes);
Assert.Equal(16, bytesWritten);
}

[Theory]
[InlineData(0)]
[InlineData(15)]
public static void TryWriteBytes_LengthTooShort_ReturnsFalse(int length)
{
Assert.False(s_testGuid.TryWriteBytes(new Span<byte>(new byte[length])));
Assert.False(s_testGuid.TryWriteBytes(new Span<byte>(new byte[length]), true, out int bytesWritten));
Assert.Equal(0, bytesWritten);
Assert.False(s_testGuid.TryWriteBytes(new Span<byte>(new byte[length]), false, out bytesWritten));
Assert.Equal(0, bytesWritten);
}

[Theory]
Expand Down