using System.Diagnostics.CodeAnalysis; using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes; using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes.Convert; using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes.Signatures; using InnovEnergy.Lib.Protocols.DBus.Protocol.Header; using InnovEnergy.Lib.Protocols.DBus.Transport; using InnovEnergy.Lib.Protocols.DBus.WireFormat; namespace InnovEnergy.Lib.Protocols.DBus.Protocol; using HeaderType = ValueTuple>; using HeaderFieldType = ValueTuple; [SuppressMessage("ReSharper", "UnusedMember.Global")] public readonly struct Message { public const Byte ProtocolVersion = 1; private static Signature HeaderSignature { get; } = Signature.FromType(); private HeaderType Header { get; } public Object? Payload { get; } public Boolean ParsingError { get; } public MessageType Type => Header.MessageType(); public UInt32 Serial => Header.Serial(); public Signature? Signature => Header.Signature(); public String? Interface => Header.Interface(); public String? Member => Header.Member(); public String? Destination => Header.Destination(); public String? Sender => Header.Sender(); public String? ErrorName => Header.ErrorName(); public UInt32? ReplySerial => Header.ReplySerial(); public UInt32? PayloadLength => Header.PayloadLength(); public ObjectPath? ObjectPath => Header.ObjectPath(); public Endian Endian => Header.Endian(); public Boolean ReplyExpected => Header.ReplyExpected(); public Boolean AutoStart => Header.AutoStart(); public Boolean AllowInteractiveAuthorization => Header.AllowInteractiveAuthorization(); public Boolean HasPayload => Payload is not null; public Boolean IsError => Type == MessageType.Error; public Message(HeaderType header, Object? payload) : this(header, payload, false) { } private Message(HeaderType header, Object? payload, Boolean parsingError) { Header = header; Payload = payload; ParsingError = parsingError; CheckProtocolVersion(); } private void CheckProtocolVersion() { if (Header.ProtocolVersion() != ProtocolVersion) throw new Exception($"Unsupported protocol version: expecting {ProtocolVersion}, got {Header.ProtocolVersion()}"); } public static Message Signal(ObjectPath? objectPath, String? member, String? @interface = null, Object? payload = null) { var signature = payload.GetSignature().AsRootSignature(); var fields = new List(); AddField(FieldCode.Path, objectPath); AddField(FieldCode.Interface, @interface); AddField(FieldCode.Member, member); if (!signature.IsEmpty) AddField(FieldCode.Signature, signature); var bodyLength = (UInt32)signature.MeasureSize(payload); var serial = SerialSource.Next(); HeaderType header = ( (Byte)Env.Endianness, (Byte)MessageType.Signal, (Byte)HeaderFlags.NoReplyExpected, ProtocolVersion, bodyLength, serial, fields ); return new Message(header, payload); void AddField(FieldCode fieldCode, Object? value) { if (value is not null) fields.Add(((Byte)fieldCode, value.Variant())); } } public static Message MethodCall(String? destination, ObjectPath? objectPath, String? member, String? @interface = null, Object? payload = null, Boolean replyExpected = true) { var signature = payload.GetSignature().AsRootSignature(); var fields = new List(); AddField(FieldCode.Path, objectPath); AddField(FieldCode.Destination, destination); AddField(FieldCode.Interface, @interface); AddField(FieldCode.Member, member); if (!signature.IsEmpty) AddField(FieldCode.Signature, signature); var flags = replyExpected ? HeaderFlags.None : HeaderFlags.NoReplyExpected; var bodyLength = (UInt32)signature.MeasureSize(payload); var serial = SerialSource.Next(); HeaderType header = ( (Byte)Env.Endianness, (Byte)MessageType.MethodCall, (Byte)flags, ProtocolVersion, bodyLength, serial, fields ); return new Message(header, payload); void AddField(FieldCode fieldCode, Object? value) { if (value is not null) fields.Add(((Byte)fieldCode, value.Variant())); } } internal static Message Read(DBusReader reader) { var header = ReadHeader(reader); var (payload, error) = ReadPayload(reader, header); return new Message(header, payload, error); } private static HeaderType ReadHeader(DBusReader reader) { return (HeaderType) HeaderSignature.Read(reader); } private static (Object? payload, Boolean parsingError) ReadPayload(DBusReader reader, HeaderType header) { var payloadLength = header.PayloadLength(); var signature = header.Signature(); if (payloadLength == 0 || signature is null) return (null, false); reader.AlignForComposite(); var raw = reader.ReadSegment((Int32)payloadLength); if (signature.IsEmpty) return (raw.ToArray(), true); // no signature, but data available. should probably not happen. var br = new DBusBufferReader(raw, reader.SwapEndian); // try // { return (signature.Read(br), false); // } // catch // { // Console.WriteLine("failed to parse payload"); // } //return (raw.ToArray(), true); // parsing error } internal void Write(DBusWriter writer) { HeaderSignature.Write(Header, writer); writer.AlignForComposite(); if (Payload is null) return; Signature!.Write(Payload, writer); // TODO: on debug reread written message with reader, Roundtrip // // var w = new DBusBufferWriter(); // HeaderSignature.Write(Header, w); // writer.AlignForComposite(); // // if (Payload is null) return; // Signature!.Write(Payload, w); // // Console.WriteLine(w.Data.ToHexView()); // // var r = new DBusBufferReader(w.Data, false); // var m = Message.Read(r); } }