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

CSHARP-2758 : Add Span<T> support to ObjectId #385

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
100 changes: 83 additions & 17 deletions src/MongoDB.Bson/BsonUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ public static string GetFriendlyTypeName(Type type)
return sb.ToString();
}

/// <summary>
/// Get's the hex string's equivalent byte array size
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static int GetHexStringBinaryLength(string s)
{
return (s.Length + 1) / 2;
}

/// <summary>
/// Parses a hex string into its equivalent byte array.
/// </summary>
Expand All @@ -72,6 +82,35 @@ public static byte[] ParseHexString(string s)
return bytes;
}

/// <summary>
/// Parses a hex string into its equivalent span of bytes
/// </summary>
/// <param name="s">The hex string to parse.</param>
/// <param name="span">The byte sequivalent of the hex string.</param>
public static void ParseHexString(string s, Span<byte> span)
{
if (s == null)
{
throw new ArgumentException(nameof(s));
}

if (span == null)
{
throw new ArgumentException(nameof(span));
}

var length = GetHexStringBinaryLength(s);
if (span.Length != length)
{
throw new ArgumentException(nameof(span), $"Span does not match the length of the equivalent binary size of the hex string (expected : {length})");
}

if (!TryParseHexString(s, span))
{
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 @@ -116,23 +155,31 @@ public static char ToHexChar(int value)
/// <param name="bytes">The byte array.</param>
/// <returns>A hex string.</returns>
public static string ToHexString(byte[] bytes)
{
return ToHexString(bytes.AsSpan());
}

/// <summary>
/// Converts a span of bytes to a hex string.
/// </summary>
/// <param name="bytes">The span of bytes.</param>
/// <returns>A hex string.</returns>
public static string ToHexString(ReadOnlySpan<byte> bytes)
{
if (bytes == null)
{
throw new ArgumentNullException("bytes");
throw new ArgumentException(nameof(bytes));
}

var length = bytes.Length;
var c = new char[length * 2];

Span<char> c = stackalloc char[length * 2];
for (int i = 0, j = 0; i < length; i++)
{
var b = bytes[i];
c[j++] = ToHexChar(b >> 4);
c[j++] = ToHexChar(b & 0x0f);
}

return new string(c);
return c.ToString();
}

/// <summary>
Expand Down Expand Up @@ -189,22 +236,17 @@ public static DateTime ToUniversalTime(DateTime dateTime)
}

/// <summary>
/// Tries to parse a hex string to a byte array.
/// Tries to parse a hex string to a pre-allocated span of bytes
/// </summary>
/// <param name="s">The hex string.</param>
/// <param name="bytes">A byte array.</param>
/// <returns>True if the hex string was successfully parsed.</returns>
public static bool TryParseHexString(string s, out byte[] bytes)
/// <param name="buffer">A pre-allocated span of bytes</param>
/// <returns>True if the hex string was successfully parsed, and the allocated span of bytes has the exact length</returns>
public static bool TryParseHexString(string s, Span<byte> buffer)
{
bytes = null;

if (s == null)
{
if (s == null || buffer.Length != GetHexStringBinaryLength(s)) {
return false;
}

var buffer = new byte[(s.Length + 1) / 2];

var i = 0;
var j = 0;

Expand Down Expand Up @@ -232,11 +274,35 @@ public static bool TryParseHexString(string s, out byte[] bytes)
}
buffer[j++] = (byte)((x << 4) | y);
}

bytes = buffer;
return true;
}

/// <summary>
/// Tries to parse a hex string to a byte array.
/// </summary>
/// <param name="s">The hex string.</param>
/// <param name="bytes">A byte array.</param>
/// <returns>True if the hex string was successfully parsed.</returns>
public static bool TryParseHexString(string s, out byte[] bytes)
{
bytes = null;

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

var buffer = new byte[GetHexStringBinaryLength(s)];
var result = TryParseHexString(s, buffer);

if (result)
{
bytes = buffer;
}

return result;
}

// private static methods
private static bool TryParseHexChar(char c, out int value)
{
Expand Down
1 change: 1 addition & 0 deletions src/MongoDB.Bson/MongoDB.Bson.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.2" PrivateAssets="All" />
<PackageReference Include="System.Memory" Version="4.5.3" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.5'">
Expand Down
141 changes: 106 additions & 35 deletions src/MongoDB.Bson/ObjectModel/ObjectId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,24 @@ public ObjectId(byte[] bytes)
throw new ArgumentException("Byte array must be 12 bytes long", "bytes");
}

FromByteArray(bytes, 0, out _a, out _b, out _c);
FromByteSpan(bytes, 0, out _a, out _b, out _c);
}

/// <summary>
/// Initializes a new instance of the ObjectId class.
/// </summary>
/// <param name="span">The span containing bytes.</param>
public ObjectId(ReadOnlySpan<byte> span)
{
if (span == null)
{
throw new ArgumentException(nameof(span));
}
if (span.Length != 12)
{
throw new ArgumentException("Span must be 12 bytes long", nameof(span));
}
FromByteSpan(span, 0, out _a, out _b, out _c);
}

/// <summary>
Expand All @@ -66,7 +83,7 @@ public ObjectId(byte[] bytes)
/// <param name="index">The index into the byte array where the ObjectId starts.</param>
internal ObjectId(byte[] bytes, int index)
{
FromByteArray(bytes, index, out _a, out _b, out _c);
FromByteSpan(bytes, index, out _a, out _b, out _c);
}

/// <summary>
Expand Down Expand Up @@ -115,8 +132,9 @@ public ObjectId(string value)
throw new ArgumentNullException("value");
}

var bytes = BsonUtils.ParseHexString(value);
FromByteArray(bytes, 0, out _a, out _b, out _c);
Span<byte> span = stackalloc byte[BsonUtils.GetHexStringBinaryLength(value)];
BsonUtils.ParseHexString(value, span);
FromByteSpan(span, 0, out _a, out _b, out _c);
}

// public static properties
Expand Down Expand Up @@ -276,6 +294,21 @@ public static ObjectId GenerateNewId(int timestamp)
/// <param name="increment">The increment.</param>
/// <returns>A byte array.</returns>
public static byte[] Pack(int timestamp, int machine, short pid, int increment)
{
var bytes = new byte[12];
Pack(bytes, timestamp, machine, pid, increment);
return bytes;
}

/// <summary>
/// Packs the components of an ObjectId and write into a span of bytes
/// </summary>
/// <param name="span">The span to be writen into.</param>
/// <param name="timestamp">The timestamp.</param>
/// <param name="machine">The machine hash.</param>
/// <param name="pid">The PID.</param>
/// <param name="increment">The increment.</param>
public static void Pack(Span<byte> span, int timestamp, int machine, short pid, int increment)
{
if ((machine & 0xff000000) != 0)
{
Expand All @@ -285,21 +318,23 @@ public static byte[] Pack(int timestamp, int machine, short pid, int increment)
{
throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
}
if (span.Length < 12)
{
throw new ArgumentException(nameof(span), "Span length must equal or longer then 12");
}

byte[] bytes = new byte[12];
bytes[0] = (byte)(timestamp >> 24);
bytes[1] = (byte)(timestamp >> 16);
bytes[2] = (byte)(timestamp >> 8);
bytes[3] = (byte)(timestamp);
bytes[4] = (byte)(machine >> 16);
bytes[5] = (byte)(machine >> 8);
bytes[6] = (byte)(machine);
bytes[7] = (byte)(pid >> 8);
bytes[8] = (byte)(pid);
bytes[9] = (byte)(increment >> 16);
bytes[10] = (byte)(increment >> 8);
bytes[11] = (byte)(increment);
return bytes;
span[0] = (byte)(timestamp >> 24);
span[1] = (byte)(timestamp >> 16);
span[2] = (byte)(timestamp >> 8);
span[3] = (byte)(timestamp);
span[4] = (byte)(machine >> 16);
span[5] = (byte)(machine >> 8);
span[6] = (byte)(machine);
span[7] = (byte)(pid >> 8);
span[8] = (byte)(pid);
span[9] = (byte)(increment >> 16);
span[10] = (byte)(increment >> 8);
span[11] = (byte)(increment);
}

/// <summary>
Expand Down Expand Up @@ -337,8 +372,8 @@ public static bool TryParse(string s, out ObjectId objectId)
// don't throw ArgumentNullException if s is null
if (s != null && s.Length == 24)
{
byte[] bytes;
if (BsonUtils.TryParseHexString(s, out bytes))
Span<byte> bytes = stackalloc byte[BsonUtils.GetHexStringBinaryLength(s)];
if (BsonUtils.TryParseHexString(s, bytes))
{
objectId = new ObjectId(bytes);
return true;
Expand Down Expand Up @@ -368,6 +403,28 @@ public static void Unpack(byte[] bytes, out int timestamp, out int machine, out
throw new ArgumentOutOfRangeException("bytes", "Byte array must be 12 bytes long.");
}

Unpack(bytes.AsSpan(), out timestamp, out machine, out pid, out increment);
}

/// <summary>
/// Unpacks a span into the components of an ObjectId.
/// </summary>
/// <param name="bytes">A span containing bytes.</param>
/// <param name="timestamp">The timestamp.</param>
/// <param name="machine">The machine hash.</param>
/// <param name="pid">The PID.</param>
/// <param name="increment">The increment.</param>
public static void Unpack(ReadOnlySpan<byte> bytes, out int timestamp, out int machine, out short pid, out int increment)
{
if (bytes == null)
{
throw new ArgumentNullException(nameof(bytes));
}
if (bytes.Length != 12)
{
throw new ArgumentOutOfRangeException(nameof(bytes), "Span must be 12 bytes long.");
}

timestamp = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
machine = (bytes[4] << 16) + (bytes[5] << 8) + bytes[6];
pid = (short)((bytes[7] << 8) + bytes[8]);
Expand Down Expand Up @@ -429,7 +486,7 @@ private static int GetTimestampFromDateTime(DateTime timestamp)
return (int)secondsSinceEpoch;
}

private static void FromByteArray(byte[] bytes, int offset, out int a, out int b, out int c)
private static void FromByteSpan(ReadOnlySpan<byte> bytes, int offset, out int a, out int b, out int c)
{
a = (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3];
b = (bytes[offset + 4] << 24) | (bytes[offset + 5] << 16) | (bytes[offset + 6] << 8) | bytes[offset + 7];
Expand Down Expand Up @@ -521,18 +578,32 @@ public void ToByteArray(byte[] destination, int offset)
throw new ArgumentException("Not enough room in destination buffer.", "offset");
}

destination[offset + 0] = (byte)(_a >> 24);
destination[offset + 1] = (byte)(_a >> 16);
destination[offset + 2] = (byte)(_a >> 8);
destination[offset + 3] = (byte)(_a);
destination[offset + 4] = (byte)(_b >> 24);
destination[offset + 5] = (byte)(_b >> 16);
destination[offset + 6] = (byte)(_b >> 8);
destination[offset + 7] = (byte)(_b);
destination[offset + 8] = (byte)(_c >> 24);
destination[offset + 9] = (byte)(_c >> 16);
destination[offset + 10] = (byte)(_c >> 8);
destination[offset + 11] = (byte)(_c);
ToSpan(destination.AsSpan(offset));
}

/// <summary>
/// Writes the binary form of ObjectId into a span of bytes
/// </summary>
/// <param name="destination">The destination.</param>
public void ToSpan(Span<byte> destination)
{
if (destination.Length < 12)
{
throw new ArgumentException(nameof(destination), "Not enough room in destination span");
}

destination[0] = (byte)(_a >> 24);
destination[1] = (byte)(_a >> 16);
destination[2] = (byte)(_a >> 8);
destination[3] = (byte)(_a);
destination[4] = (byte)(_b >> 24);
destination[5] = (byte)(_b >> 16);
destination[6] = (byte)(_b >> 8);
destination[7] = (byte)(_b);
destination[8] = (byte)(_c >> 24);
destination[9] = (byte)(_c >> 16);
destination[10] = (byte)(_c >> 8);
destination[11] = (byte)(_c);
}

/// <summary>
Expand All @@ -541,7 +612,7 @@ public void ToByteArray(byte[] destination, int offset)
/// <returns>A string representation of the value.</returns>
public override string ToString()
{
var c = new char[24];
Span<char> c = stackalloc char[24];
c[0] = BsonUtils.ToHexChar((_a >> 28) & 0x0f);
c[1] = BsonUtils.ToHexChar((_a >> 24) & 0x0f);
c[2] = BsonUtils.ToHexChar((_a >> 20) & 0x0f);
Expand All @@ -566,7 +637,7 @@ public override string ToString()
c[21] = BsonUtils.ToHexChar((_c >> 8) & 0x0f);
c[22] = BsonUtils.ToHexChar((_c >> 4) & 0x0f);
c[23] = BsonUtils.ToHexChar(_c & 0x0f);
return new string(c);
return c.ToString();
}

// explicit IConvertible implementation
Expand Down