using System.IO.Hashing;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using SPTarkov.DI.Annotations;
namespace SPTarkov.Server.Core.Utils;
[Injectable(InjectionType.Singleton)]
public partial class HashUtil(RandomUtil _randomUtil)
{
///
/// Create a 24 character MongoId
///
/// 24 character objectId
public string Generate()
{
// Allocate a span directly onto the stack, will dispose whenever we finished running
// Span is recommended to work with stackalloc and we can 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)
_randomUtil.NextBytes(objectId.Slice(4, 5));
// Incrementing counter (3 bytes)
// 24-bit counter
var counter = _randomUtil.GetInt(0, 16777215);
objectId[9] = (byte) (counter >> 16);
objectId[10] = (byte) (counter >> 8);
objectId[11] = (byte) counter;
return Convert.ToHexStringLower(objectId);
}
///
/// is the passed in string a valid mongo id
///
/// String to check
/// True when string is a valid mongo id
public bool IsValidMongoId(string stringToCheck)
{
return MongoIdRegex().IsMatch(stringToCheck);
}
public uint GenerateCrc32ForData(string data)
{
return Crc32.HashToUInt32(new ArraySegment(Encoding.UTF8.GetBytes(data)));
}
///
/// Create a hash for the data parameter
///
/// algorithm to use to hash
/// data to be hashed
/// hash value
/// thrown if the provided algorithm is not implemented
/// >
public string GenerateHashForData(HashingAlgorithm algorithm, string data)
{
switch (algorithm)
{
case HashingAlgorithm.MD5:
var md5HashData = MD5.HashData(Encoding.UTF8.GetBytes(data));
return Convert.ToHexString(md5HashData).Replace("-", string.Empty);
case HashingAlgorithm.SHA1:
var sha1HashData = SHA1.HashData(Encoding.UTF8.GetBytes(data));
return Convert.ToHexString(sha1HashData).Replace("-", string.Empty);
}
throw new NotImplementedException($"Provided hash algorithm: {algorithm} is not supported.");
}
///
/// Create a hash for the data parameter asynchronously
///
/// algorithm to use to hash
/// data to be hashed
/// A task which contains the hash value
/// thrown if the provided algorithm is not implemented
/// >
public async Task GenerateHashForDataAsync(HashingAlgorithm algorithm, string data)
{
switch (algorithm)
{
case HashingAlgorithm.MD5:
{
await using var ms = new MemoryStream(Encoding.UTF8.GetBytes(data));
var md5HashData = await MD5.HashDataAsync(ms);
return Convert.ToHexString(md5HashData).Replace("-", string.Empty);
}
case HashingAlgorithm.SHA1:
{
await using var ms = new MemoryStream(Encoding.UTF8.GetBytes(data));
var sha1HashData = await SHA1.HashDataAsync(ms);
return Convert.ToHexString(sha1HashData).Replace("-", string.Empty);
}
}
throw new NotImplementedException($"Provided hash algorithm: {algorithm} is not supported.");
}
///
/// Generates an account ID for a profile
///
/// Generated account ID
public int GenerateAccountId()
{
const int min = 1000000;
const int max = 1999999;
return _randomUtil.Random.Next(min, max + 1);
}
[GeneratedRegex("^[a-fA-F0-9]{24}$")]
private static partial Regex MongoIdRegex();
}
public enum HashingAlgorithm
{
MD5,
SHA1
}