using System.Security.Cryptography;
using System.Text;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian;
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common;
///
/// The deterministic alarm-event id and numeric severity the SQL historian backends store, so the store-and-forward
/// drain's at-least-once redelivery (and both pair nodes writing the same event) converge on one row.
///
public static class AlarmEventIds
{
///
/// Lowercase-hex SHA-256 over length-prefixed UTF-8 fields (little-endian int32 byte length, -1 for null):
/// EquipmentPath, AlarmId, EventKind, TimestampUtc.Ticks (decimal), User, Comment, Message. Length-prefixing keeps
/// field boundaries unambiguous ("a|b"+"c" ≠ "a"+"b|c").
///
/// The event.
/// 64 hex characters.
public static string ComputeEventId(AlarmHistorianEvent e)
{
using var ms = new MemoryStream();
Field(ms, e.EquipmentPath);
Field(ms, e.AlarmId);
Field(ms, e.EventKind);
Field(ms, e.TimestampUtc.Ticks.ToString(System.Globalization.CultureInfo.InvariantCulture));
Field(ms, e.User);
Field(ms, e.Comment);
Field(ms, e.Message);
return Convert.ToHexStringLower(SHA256.HashData(ms.ToArray()));
}
/// The numeric Part 9 priority stored for a severity (same values the gateway path sends).
/// Alarm severity.
/// 200 / 500 / 700 / 900; 500 for anything else.
public static int ToNumericSeverity(AlarmSeverity s) => s switch
{
AlarmSeverity.Low => 200,
AlarmSeverity.Medium => 500,
AlarmSeverity.High => 700,
AlarmSeverity.Critical => 900,
_ => 500,
};
private static void Field(MemoryStream ms, string? value)
{
Span len = stackalloc byte[4];
if (value is null)
{
System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian(len, -1);
ms.Write(len);
return;
}
var bytes = Encoding.UTF8.GetBytes(value);
System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian(len, bytes.Length);
ms.Write(len);
ms.Write(bytes);
}
}