using System.Text.RegularExpressions; namespace SPTarkov.Server.Core.Models.Common; public readonly partial struct MongoId : IEquatable { private readonly string _stringId; public MongoId(string id) { if (string.IsNullOrWhiteSpace(id) || id.Length != 24) { // TODO: Items.json root item has an empty parentId property Console.WriteLine($"Critical MongoId error: Incorrect length. id: {id}"); } if (!IsValidMongoId(id)) { Console.WriteLine( $"Critical MongoId error: Incorrect format. Must be a hexadecimal [a-f0-9] of 24 characters. id: {id}" ); } _stringId = string.Intern(id); } public MongoId() { _stringId = Generate(); } /// /// Create a 24 character MongoId /// /// 24 character objectId private static string Generate() { // Allocate a span directly onto the stack, will dispose whenever we finished running // Span is recommended to work with stackalloc + we use stackalloc here because we don't do anything with this afterwards Span objectId = stackalloc byte[12]; // Time stamp (4 bytes) var timestamp = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); // Convert to big-endian objectId[0] = (byte)(timestamp >> 24); objectId[1] = (byte)(timestamp >> 16); objectId[2] = (byte)(timestamp >> 8); objectId[3] = (byte)timestamp; // Random value (5 bytes) var rand = new Random(); rand.NextBytes(objectId.Slice(4, 5)); // Incrementing counter (3 bytes) // 24-bit counter var counter = rand.Next(0, 16777215); objectId[9] = (byte)(counter >> 16); objectId[10] = (byte)(counter >> 8); objectId[11] = (byte)counter; return Convert.ToHexStringLower(objectId); } public override string ToString() { return _stringId ?? string.Empty; } public bool Equals(MongoId? other) { if (other is null) { return other == this; } return other.ToString().Equals(ToString(), StringComparison.InvariantCultureIgnoreCase); } public bool Equals(string? other) { if (other is null) { return other == this; } return other.Equals(ToString(), StringComparison.InvariantCultureIgnoreCase); } public static bool IsValidMongoId(string stringToCheck) { return MongoIdRegex().IsMatch(stringToCheck); } public static implicit operator string(MongoId mongoId) { return mongoId.ToString(); } public static implicit operator MongoId(string mongoId) { return new MongoId(mongoId); } public bool Equals(MongoId other) { return _stringId == other._stringId; } public override bool Equals(object? obj) { return obj is MongoId other && Equals(other); } public static bool operator ==(MongoId left, MongoId right) { return left.Equals(right); } public static bool operator !=(MongoId left, MongoId right) { return !left.Equals(right); } public override int GetHashCode() { return _stringId.GetHashCode(); } public MongoId Empty() { return new MongoId("000000000000000000000000"); } [GeneratedRegex("^[a-fA-F0-9]{24}$")] private static partial Regex MongoIdRegex(); }