Skip to content
Open
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
58 changes: 52 additions & 6 deletions src/MongoDB.Bson/BsonUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@ public static byte[] ParseHexString(string s)
return bytes;
}

/// <summary>
/// Parses a hex char span into its equivalent byte array.
/// </summary>
/// <param name="s">The hex char span to parse.</param>
/// <param name="bytes">The result span to fill with the byte equivalent of the hex string.</param>
public static void ParseHexString(ReadOnlySpan<char> s, Span<byte> bytes)
{
int expectedLength = GetByteLength(s.Length);
if (bytes.Length != expectedLength)
{
throw new FormatException($"Target should be {expectedLength} bytes long");
}
if (!TryParseHexString(s, bytes))
{
throw new FormatException("String should contain only hexadecimal digits.");
}
}

/// <summary>
/// Converts from number of milliseconds since Unix epoch to DateTime.
/// </summary>
Expand Down Expand Up @@ -212,14 +230,43 @@ public static DateTime ToUniversalTime(DateTime dateTime)
/// <returns>True if the hex string was successfully parsed.</returns>
public static bool TryParseHexString(string s, out byte[] bytes)
{
bytes = null;

if (s == null)
{
bytes = null;
return false;
}

var buffer = new byte[(s.Length + 1) / 2];
var buffer = new byte[GetByteLength(s.Length)];
if (TryParseHexString(s.AsSpan(), buffer))
{
bytes = buffer;
return true;
}
else
{
bytes = null;
return false;
}
}

/// <summary>
/// Calculate the result byte length for the hex string length
/// </summary>
/// <param name="hexStringLength">The length of the hex string</param>
/// <returns>The required length to convert the hex string to bytes</returns>
internal static int GetByteLength(int hexStringLength)
=> (hexStringLength + 1) / 2;

/// <summary>
/// Tries to parse a hex char span to a byte span.
/// </summary>
/// <param name="s">The hex chars.</param>
/// <param name="bytes">A byte span.</param>
/// <returns>True if the hex char span was successfully parsed.</returns>
public static bool TryParseHexString(ReadOnlySpan<char> s, Span<byte> bytes)
{
if (bytes.Length != GetByteLength(s.Length))
return false;

var i = 0;
var j = 0;
Expand All @@ -232,7 +279,7 @@ public static bool TryParseHexString(string s, out byte[] bytes)
{
return false;
}
buffer[j++] = (byte)y;
bytes[j++] = (byte)y;
}

while (i < s.Length)
Expand All @@ -246,10 +293,9 @@ public static bool TryParseHexString(string s, out byte[] bytes)
{
return false;
}
buffer[j++] = (byte)((x << 4) | y);
bytes[j++] = (byte)((x << 4) | y);
}

bytes = buffer;
return true;
}

Expand Down
28 changes: 22 additions & 6 deletions src/MongoDB.Bson/ObjectModel/ObjectId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ public ObjectId(string value)
{
throw new ArgumentNullException(nameof(value));
}

var bytes = BsonUtils.ParseHexString(value);
Span<byte> bytes = stackalloc byte[12];
BsonUtils.ParseHexString(value.AsSpan(), bytes);
FromBytesSpan(bytes, out _a, out _b, out _c);
}

Expand Down Expand Up @@ -255,11 +255,27 @@ public static ObjectId Parse(string s)
/// <returns>True if the string was parsed successfully.</returns>
public static bool TryParse(string s, out ObjectId objectId)
{
// don't throw ArgumentNullException if s is null
if (s != null && s.Length == 24)
if (s == null)
{
// don't throw ArgumentNullException if s is null
objectId = default(ObjectId);
return false;
}
return TryParse(s.AsSpan(), out objectId);
}

/// <summary>
/// Tries to parse a span of chars and create a new ObjectId.
/// </summary>
/// <param name="s">The span value.</param>
/// <param name="objectId">The new ObjectId.</param>
/// <returns>True if the string was parsed successfully.</returns>
public static bool TryParse(ReadOnlySpan<char> s, out ObjectId objectId)
{
if (s.Length == 24)
{
byte[] bytes;
if (BsonUtils.TryParseHexString(s, out bytes))
Span<byte> bytes = stackalloc byte[12];
if (BsonUtils.TryParseHexString(s, bytes))
{
objectId = new ObjectId(bytes);
return true;
Expand Down
2 changes: 1 addition & 1 deletion tests/MongoDB.Bson.Tests/ObjectModel/ObjectIdTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ public void TestTryParse()
Assert.False(ObjectId.TryParse("102030405060708090a0b0c", out objectId1)); // too short
Assert.False(ObjectId.TryParse("x102030405060708090a0b0c", out objectId1)); // invalid character
Assert.False(ObjectId.TryParse("00102030405060708090a0b0c", out objectId1)); // too long
Assert.False(ObjectId.TryParse(null, out objectId1)); // should return false not throw ArgumentNullException
Assert.False(ObjectId.TryParse(default(string), out objectId1)); // should return false not throw ArgumentNullException
}

[Fact]
Expand Down