91 lines
3.8 KiB
C#
91 lines
3.8 KiB
C#
using System;
|
|
using System.Text;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Worker.MxAccess;
|
|
|
|
/// <summary>
|
|
/// Derives a deterministic synthetic <see cref="Guid"/> from an alarm
|
|
/// reference for the subtag-provider fallback path, which has no native
|
|
/// MxAccess alarm GUID. Hashing the reference yields a stable identity so
|
|
/// repeated transitions for the same alarm reference correlate downstream
|
|
/// (acknowledge, snapshot, OPC UA mapping) without an alarmmgr-supplied GUID.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The 128-bit value is computed with a fixed FNV-1a hash over the UTF-8
|
|
/// bytes of the reference, deliberately <strong>not</strong> via
|
|
/// <c>System.Security.Cryptography</c>. On .NET Framework 4.8
|
|
/// <c>MD5.Create()</c> returns the non-validated
|
|
/// <c>MD5CryptoServiceProvider</c>, whose constructor throws under the
|
|
/// Windows FIPS-compliance policy ("not part of the Windows Platform FIPS
|
|
/// validated cryptographic algorithms"). Because this derivation needs only
|
|
/// determinism and distinctness — never cryptographic strength — a plain
|
|
/// non-crypto hash avoids the FIPS gate entirely, so the subtag fallback
|
|
/// keeps working on regulated (FIPS-enabled) hosts exactly when it is needed.
|
|
/// </remarks>
|
|
internal static class SyntheticAlarmGuid
|
|
{
|
|
// 64-bit FNV-1a constants (RFC-style; widely used reference values).
|
|
private const ulong FnvOffsetBasis = 14695981039346656037UL;
|
|
private const ulong FnvPrime = 1099511628211UL;
|
|
|
|
// A second independent seed for the high 8 bytes so the full 128-bit value
|
|
// is well-distributed across distinct references rather than two correlated
|
|
// halves of the same single-pass hash.
|
|
private const ulong FnvSecondSeed = 1469598103934665603UL;
|
|
|
|
/// <summary>
|
|
/// Produces a stable <see cref="Guid"/> for the given alarm reference.
|
|
/// The same reference always maps to the same GUID; distinct references
|
|
/// map to distinct GUIDs with overwhelming probability.
|
|
/// </summary>
|
|
/// <param name="reference">
|
|
/// The fully-qualified alarm reference (for example
|
|
/// <c>"Galaxy!Area.Tag.HiHi"</c>). Treated as UTF-8 bytes.
|
|
/// </param>
|
|
/// <returns>A deterministic, non-empty GUID derived from the reference.</returns>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// Thrown when <paramref name="reference"/> is <see langword="null"/>.
|
|
/// </exception>
|
|
public static Guid ForReference(string reference)
|
|
{
|
|
if (reference is null) throw new ArgumentNullException(nameof(reference));
|
|
|
|
byte[] bytes = Encoding.UTF8.GetBytes(reference);
|
|
|
|
// Two independent FNV-1a passes fill the low and high 64 bits of the
|
|
// 128-bit value. The second pass mixes the running length into its seed
|
|
// so single-character differences and re-orderings still diverge in both
|
|
// halves, avoiding correlated-half collisions a single pass would risk.
|
|
ulong low = FnvOffsetBasis;
|
|
ulong high = FnvSecondSeed;
|
|
for (int i = 0; i < bytes.Length; i++)
|
|
{
|
|
byte b = bytes[i];
|
|
|
|
low ^= b;
|
|
low *= FnvPrime;
|
|
|
|
high ^= unchecked(b + (ulong)i);
|
|
high *= FnvPrime;
|
|
}
|
|
|
|
// Fold the length in so the empty string and other short inputs are not
|
|
// degenerate (an all-zero / Guid.Empty result is undesirable downstream).
|
|
low ^= (ulong)bytes.Length;
|
|
low *= FnvPrime;
|
|
|
|
byte[] guidBytes = new byte[16];
|
|
WriteUInt64(guidBytes, 0, low);
|
|
WriteUInt64(guidBytes, 8, high);
|
|
return new Guid(guidBytes);
|
|
}
|
|
|
|
private static void WriteUInt64(byte[] buffer, int offset, ulong value)
|
|
{
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
buffer[offset + i] = (byte)(value >> (i * 8));
|
|
}
|
|
}
|
|
}
|