using Common.Logging; using Common.Messaging; using CommunityToolkit.Mvvm.Messaging; using MultiTerm.Protocols.Helpers; using MultiTerm.Protocols.Model; using MultiTerm.Protocols.Network; using MultiTerm.Protocols.Types; using System.Net; using System.Net.Sockets; namespace MultiTerm.Protocols.Udp; public class UdpProtocol : CommunicationProtocol { public override Types.ProtocolType ProtocolType => Types.ProtocolType.Udp; public override string InstanceIdentifier { get; protected set; } = string.Empty; public override string LongInstanceIdentifier { get; protected set; } = string.Empty; private INetworkProtocolSettings? settings; private UdpClient? udpClient; private const int ReadTimeoutMs = 100; // milliseconds until the read operation timeouts private const int WriteTimeoutMs = 100; // milliseconds until the write operation timeouts public UdpProtocol(ILogger logger, IMessenger messenger) : base(logger, messenger) { } protected override bool InternalConnect(IProtocolSettings settings) { // check if settings are of correct type if (settings is not INetworkProtocolSettings networkSettings) { this.settings = null; throw new ArgumentException($"Cannot connect due to wrong type of Protocol Settings. " + $"Check parameter {nameof(settings)}' of '{nameof(InternalConnect)}()' in {nameof(UdpProtocol)}."); } // store locally this.settings = networkSettings; // update identifiers this.InstanceIdentifier = NetworkProtocolHelpers.GetLimitedLengthHostname(this.settings); this.LongInstanceIdentifier = this.settings.Hostname; // check if client is null if(this.udpClient != null) { this.udpClient = null; this.logger.LogWarn($"UDP client was not null when {nameof(InternalConnect)} was called: " + $"{nameof(udpClient)} isnull={this.udpClient == null}", nameof(UdpProtocol)); } /* create udp client */ // try opening udp socket try { // opens an udp client on this pc and binds it to the port provided this.udpClient = new UdpClient(this.settings.Port); // define default remote host and port this.udpClient.Connect(this.settings.Hostname!, this.settings.Port); } catch (Exception ex) { this.logger.LogException(ex, $"'{nameof(InternalConnect)}()' Opening UDP socket failed:", nameof(UdpProtocol)); // rollback this.InternalDisconnect(); return false; } // set static settings this.udpClient.Client.ReceiveTimeout = ReadTimeoutMs; this.udpClient.Client.SendTimeout = WriteTimeoutMs; return true; } protected override void InternalDisconnect() { // close udp client if(this.udpClient != null) { this.udpClient?.Close(); this.udpClient?.Dispose(); this.udpClient = null; } // reset settings this.settings = null; // send message that indicates that no protocol is connected anymore this.messenger.Send(new UdpConnectedMessage(null)); } protected override void InternalRead(CancellationToken ct) { while (ct.IsCancellationRequested == false) { // try receive message IPEndPoint? remoteEndPoint = null; byte[] receivedBytes = Array.Empty(); try { // will throw ObjectDisposedException if null receivedBytes = this.udpClient!.Receive(ref remoteEndPoint); } catch (OperationCanceledException) // intentionally cancelled => just break { break; } catch (ObjectDisposedException objex) { this.logger.LogException(objex, $"ObjectDisposedException while sending data in {nameof(InternalRead)}", nameof(UdpProtocol)); this.OnUnintentionallyDisconnected(); break; // break loop } catch (SocketException sockEx) { // timeout = normal use case if (sockEx.SocketErrorCode != SocketError.TimedOut) { this.logger.LogException(sockEx, $"SocketException while reading data in {nameof(InternalRead)}", nameof(UdpProtocol)); this.messenger.Send(new StoppedReadingUIMessage(this, sockEx.SocketErrorCode.ToString())); break; // break loop } } catch (Exception ex) { this.logger.LogException(ex, $"Exception while reading data in {nameof(InternalRead)}", nameof(UdpProtocol)); this.messenger.Send(new StoppedReadingUIMessage(this, ex.Message)); break; // break loop } // any data received? if (receivedBytes.Length > 0) { foreach (byte b in receivedBytes) { // report received data this.OnReceivedData(new ExtendedByte(b)); } // after data was processed, send update with latest connected endpoint if(remoteEndPoint != null) { this.messenger.Send(new UdpConnectedMessage(remoteEndPoint.Address)); } } } } protected override bool InternalSendBytes(byte[] bytes) { // check for empty bytes array if (bytes == null || bytes.Length == 0) { this.logger.LogWarn($"'{nameof(InternalSendBytes)}()' got null or empty bytes array.", nameof(UdpProtocol)); return false; } // try sending the data try { // will throw ObjectDisposedException if null this.udpClient!.Send(bytes, bytes.Length); } catch (ObjectDisposedException objex) { this.logger.LogException(objex, $"ObjectDisposedException while sending data in {nameof(InternalSendBytes)}", nameof(UdpProtocol)); this.OnUnintentionallyDisconnected(); return false; } catch (Exception ex) { this.logger.LogException(ex, $"Exception while sending data in {nameof(InternalSendBytes)}", nameof(UdpProtocol)); return false; } foreach (byte b in bytes) { // report sent data this.OnSentData(new ExtendedByte(b)); } return true; } }