using System; using System.Text; namespace ZB.MOM.WW.MxGateway.Worker.MxAccess; /// /// Derives a deterministic synthetic 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. /// /// /// The 128-bit value is computed with a fixed FNV-1a hash over the UTF-8 /// bytes of the reference, deliberately not via /// System.Security.Cryptography. On .NET Framework 4.8 /// MD5.Create() returns the non-validated /// MD5CryptoServiceProvider, 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. /// 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; /// /// Produces a stable for the given alarm reference. /// The same reference always maps to the same GUID; distinct references /// map to distinct GUIDs with overwhelming probability. /// /// /// The fully-qualified alarm reference (for example /// "Galaxy!Area.Tag.HiHi"). Treated as UTF-8 bytes. /// /// A deterministic, non-empty GUID derived from the reference. /// /// Thrown when is . /// 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)); } } }