diff --git a/Core/Utils/HashUtil.cs b/Core/Utils/HashUtil.cs index 23de8a29..993d228c 100644 --- a/Core/Utils/HashUtil.cs +++ b/Core/Utils/HashUtil.cs @@ -9,14 +9,40 @@ namespace Core.Utils; public class HashUtil { private readonly Regex MongoIdRegex = new Regex("^[a-fA-F0-9]{24}$"); + + private readonly RandomUtil _randomUtil; + + public HashUtil(RandomUtil randomUtil) + { + _randomUtil = randomUtil; + } /// - /// Create a 24 character id using the sha256 algorithm + current timestamp + /// Create a 24 character MongoId /// - /// 24 character hash + /// 24 character objectId public string Generate() { - throw new NotImplementedException(); + var objectId = new byte[12]; + + // Time stamp (4 bytes) + var timestamp = BitConverter.GetBytes((int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()); + // Convert to big-endian + Array.Reverse(timestamp); + Array.Copy(timestamp, 0, objectId, 0, 4); + + // Random value (5 bytes) + var randomValue = new byte[5]; + _randomUtil.Random.NextBytes(randomValue); + Array.Copy(randomValue, 0, objectId, 4, 5); + + // Incrementing counter (3 bytes) + // 24-bit counter + var counter = BitConverter.GetBytes(_randomUtil.GetInt(0, 16777215)); + Array.Reverse(counter); + Array.Copy(counter, 0, objectId, 9, 3); + + return Convert.ToHexStringLower(objectId); } /// diff --git a/Core/Utils/RandomUtil.cs b/Core/Utils/RandomUtil.cs index 5da25cd3..7e5993d8 100644 --- a/Core/Utils/RandomUtil.cs +++ b/Core/Utils/RandomUtil.cs @@ -7,7 +7,7 @@ namespace Core.Utils; [Injectable(InjectionType.Singleton)] public class RandomUtil { - private readonly Random _random = new(); + public readonly Random Random = new(); /// /// The IEEE-754 standard for double-precision floating-point numbers limits the number of digits (including both @@ -30,7 +30,7 @@ public class RandomUtil } // maxVal is exclusive of the passed value, so add 1 - return max > min ? _random.Next(min, max + 1) : min; + return max > min ? Random.Next(min, max + 1) : min; } /// @@ -41,7 +41,7 @@ public class RandomUtil /// A random integer between 1 and max - 1, or 1 if max is less than or equal to 1. public int GetIntEx(int max) { - return max > 2 ? _random.Next(1, max - 1) : 1; + return max > 2 ? Random.Next(1, max - 1) : 1; } /// @@ -214,13 +214,13 @@ public class RandomUtil // Return a random integer from 0 to low if high is not provided if (high is null) { - return _random.Next(0, low); + return Random.Next(0, low); } // Return low directly when low and high are equal return low == high ? low - : _random.Next(low, (int)high); + : Random.Next(low, (int)high); } /// diff --git a/UnitTests/Tests/Utils/HashUtilTests.cs b/UnitTests/Tests/Utils/HashUtilTests.cs index c9a06405..d6f91006 100644 --- a/UnitTests/Tests/Utils/HashUtilTests.cs +++ b/UnitTests/Tests/Utils/HashUtilTests.cs @@ -5,7 +5,26 @@ namespace UnitTests.Tests.Utils; [TestClass] public class HashUtilTests { - private readonly HashUtil _hashUtil = new(); + private readonly HashUtil _hashUtil = new(new RandomUtil()); + + [TestMethod] + public void GenerateTest() + { + // Generate 100 MongoId's + for (var i = 0; i < 100; i++) + { + // Invalid mongoId character + var result = _hashUtil.Generate(); + + // Invalid mongoId length + var test = _hashUtil.IsValidMongoId(result); + + Assert.AreEqual( + true, + test, + $"IsValidMongoId() `{result}` is not a valid MongoId."); + } + } [TestMethod] public void IsValidMongoIdTest()