using Google.Protobuf.WellKnownTypes; using ZB.MOM.WW.MxGateway.Contracts.Proto; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime; /// /// Translates boxed CLR values from WriteRequest.Value into gateway-side /// instances. Inverse of . /// Handles the seven Galaxy data types — Boolean, Int32, Int64, Float32, Float64, /// String, DateTime — and their array variants. Null + unsupported types throw /// so the IWritable caller can fail the write with a /// clear status code rather than silently mis-typing the wire payload. /// internal static class MxValueEncoder { /// Encodes a CLR value as an MxValue for transmission to the gateway. /// The value to encode, or null for a null MxValue. /// An MxValue instance representing the encoded value. /// Thrown if the value type is not supported. public static MxValue Encode(object? value) { if (value is null) return new MxValue { IsNull = true }; switch (value) { case bool b: return new MxValue { BoolValue = b }; case sbyte i8: return new MxValue { Int32Value = i8 }; case short i16: return new MxValue { Int32Value = i16 }; case int i32: return new MxValue { Int32Value = i32 }; case byte u8: return new MxValue { Int32Value = u8 }; case ushort u16: return new MxValue { Int32Value = u16 }; case uint u32 when u32 <= int.MaxValue: return new MxValue { Int32Value = (int)u32 }; case long i64: return new MxValue { Int64Value = i64 }; case ulong u64 when u64 <= long.MaxValue: return new MxValue { Int64Value = (long)u64 }; case float f32: return new MxValue { FloatValue = f32 }; case double f64: return new MxValue { DoubleValue = f64 }; case string s: return new MxValue { StringValue = s }; case DateTime dt: return new MxValue { TimestampValue = Timestamp.FromDateTime(EnsureUtc(dt)) }; case DateTimeOffset dto: return new MxValue { TimestampValue = Timestamp.FromDateTimeOffset(dto) }; case bool[] arr: return EncodeArray(arr, (mx, vs) => mx.BoolValues = ToBoolArray(vs)); // Exact-type guards: CLR array covariance makes `uint[] is int[]` and `ulong[] is long[]` true, so // without them an unsigned array would be encoded by REINTERPRETING its bits (uint.MaxValue → -1) — // unlike the range-checked scalar arms above. An unsigned array is refused instead (Gitea #610 review). case int[] arr when arr.GetType() == typeof(int[]): return EncodeArray(arr, (mx, vs) => mx.Int32Values = ToInt32Array(vs)); case long[] arr when arr.GetType() == typeof(long[]): return EncodeArray(arr, (mx, vs) => mx.Int64Values = ToInt64Array(vs)); case float[] arr: return EncodeArray(arr, (mx, vs) => mx.FloatValues = ToFloatArray(vs)); case double[] arr: return EncodeArray(arr, (mx, vs) => mx.DoubleValues = ToDoubleArray(vs)); case string[] arr: return EncodeArray(arr, (mx, vs) => mx.StringValues = ToStringArray(vs)); case DateTime[] arr: return EncodeArray(arr, (mx, vs) => mx.TimestampValues = ToTimestampArray(vs)); default: throw new ArgumentException( $"Cannot encode value of type {value.GetType()} as MxValue. Supported: " + "bool, int / long (and their unsigned variants), float, double, string, DateTime, " + "and their 1-D array variants.", nameof(value)); } } private static MxValue EncodeArray(T[] values, Action populate) { var array = new MxArray(); populate(array, values); array.Dimensions.Add((uint)values.Length); return new MxValue { ArrayValue = array }; } private static BoolArray ToBoolArray(bool[] vs) { var a = new BoolArray(); a.Values.AddRange(vs); return a; } private static Int32Array ToInt32Array(int[] vs) { var a = new Int32Array(); a.Values.AddRange(vs); return a; } private static Int64Array ToInt64Array(long[] vs) { var a = new Int64Array(); a.Values.AddRange(vs); return a; } private static FloatArray ToFloatArray(float[] vs) { var a = new FloatArray(); a.Values.AddRange(vs); return a; } private static DoubleArray ToDoubleArray(double[] vs) { var a = new DoubleArray(); a.Values.AddRange(vs); return a; } private static StringArray ToStringArray(string[] vs) { var a = new StringArray(); a.Values.AddRange(vs); return a; } private static TimestampArray ToTimestampArray(DateTime[] vs) { var a = new TimestampArray(); foreach (var dt in vs) a.Values.Add(Timestamp.FromDateTime(EnsureUtc(dt))); return a; } /// /// requires UTC. Convert non-UTC inputs /// explicitly so a caller passing local time gets predictable wire bytes. /// private static DateTime EnsureUtc(DateTime dt) => dt.Kind switch { DateTimeKind.Utc => dt, DateTimeKind.Local => dt.ToUniversalTime(), _ => DateTime.SpecifyKind(dt, DateTimeKind.Utc), }; }