Skip to content
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

Read/Write Big/LittleEndian support for Guid #87993

Merged
Merged
Show file tree
Hide file tree
Changes from 3 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
93 changes: 48 additions & 45 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,42 @@ public Guid(ReadOnlySpan<byte> b)
{
if (b.Length != 16)
{
ThrowArgumentException();
ThrowGuidArrayCtorArgumentException();
}

if (BitConverter.IsLittleEndian)
this = Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(b));
AlexRadch marked this conversation as resolved.
Show resolved Hide resolved

if (!BitConverter.IsLittleEndian)
{
_a = BinaryPrimitives.ReverseEndianness(_a);
_b = BinaryPrimitives.ReverseEndianness(_b);
_c = BinaryPrimitives.ReverseEndianness(_c);
}
}

public Guid(ReadOnlySpan<byte> b, bool bigEndian)
{
if (b.Length != 16)
{
this = MemoryMarshal.Read<Guid>(b);
return;
ThrowGuidArrayCtorArgumentException();
}

// 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];
this = Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(b));

[StackTraceHidden]
static void ThrowArgumentException()
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);
}
}

[StackTraceHidden]
private static void ThrowGuidArrayCtorArgumentException()
AlexRadch marked this conversation as resolved.
Show resolved Hide resolved
{
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 @@ -844,43 +851,39 @@ private static bool IsHexPrefix(ReadOnlySpan<char> str, int i) =>
(str[i + 1] | 0x20) == 'x';

// Returns an unsigned byte array containing the GUID.
public byte[] ToByteArray()
public byte[] ToByteArray() => ToByteArray(false);

// Returns an unsigned byte array containing the GUID.
public byte[] ToByteArray(bool bigEndian)
{
var g = new byte[16];
if (BitConverter.IsLittleEndian)
{
MemoryMarshal.TryWrite(g, ref Unsafe.AsRef(in this));
}
else
{
TryWriteBytes(g);
}
TryWriteBytes(g, bigEndian, out _);
return g;
}

// Returns whether bytes are successfully written to given span.
public bool TryWriteBytes(Span<byte> destination)
public bool TryWriteBytes(Span<byte> destination) => TryWriteBytes(destination, false, out _);
AlexRadch marked this conversation as resolved.
Show resolved Hide resolved

// Returns whether bytes are successfully written to given span.
public bool TryWriteBytes(Span<byte> destination, bool bigEndian, out int bytesWritten)
AlexRadch marked this conversation as resolved.
Show resolved Hide resolved
{
if (BitConverter.IsLittleEndian)
if (destination.Length < 16)
{
return MemoryMarshal.TryWrite(destination, ref Unsafe.AsRef(in this));
bytesWritten = 0;
return false;
}

// slower path for BigEndian
if (destination.Length < 16)
return false;
ref Guid guid = ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(destination));
guid = this;

if (BitConverter.IsLittleEndian == bigEndian)
{
Unsafe.AsRef(in guid._a) = BinaryPrimitives.ReverseEndianness(guid._a);
AlexRadch marked this conversation as resolved.
Show resolved Hide resolved
Unsafe.AsRef(in guid._b) = BinaryPrimitives.ReverseEndianness(guid._b);
Unsafe.AsRef(in guid._c) = BinaryPrimitives.ReverseEndianness(guid._c);
}

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;
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