Skip to content

Conversation

@cptkoolbeenz
Copy link
Member

@cptkoolbeenz cptkoolbeenz commented Jul 21, 2025

Summary

  • Fixes UDP device discovery not working with multiple WiFi adapters that had matching/conflicting subnets but on different networks
  • Resolves TCP connection timeouts when multiple NICs have routes to same IP
  • Adds backward compatibility for firmware that responds to port 30303

Problem

  1. UDP discovery broadcasts were being sent but the device was not responding in AP mode - that has been fixed on the firmware level
  2. When multiple network interfaces (on separate networks) had routes to 192.168.1.1 (common for routers and DAQiFi AP mode), Windows would route TCP connections to the wrong interface (or whichever had priority in Windows settings).

Solution

  1. Created DaqifiDeviceFinderMultiInterface to broadcast from ALL network interfaces
  2. Created DaqifiDeviceFinderBackwardCompatible that:
    • Listens on port 30303 for existing firmware responses
    • Tracks which local interface discovered each device
  3. Modified DaqifiStreamingDevice to bind TCP connections to the correct local interface

Test plan

  • Test UDP discovery with device in AP mode
  • Test UDP discovery with device in STA mode
  • Verify device discovery works from WiFi adapters
  • Confirm TCP connections succeed when router and device share same IP
  • Test with multiple DAQiFi devices on different interfaces
  • Verify backward compatibility with older firmware versions

Breaking changes

None - maintains full backward compatibility

🤖 Generated with Claude Code

cptkoolbeenz and others added 2 commits July 20, 2025 19:13
The WiFi module restarts when ApplyNetworkLan is called, which disables
the WiFi. This fix adds logic to re-enable WiFi after the module has
restarted, but only when in StreamToApp mode.

Changes:
- Add using System.Threading for Thread.Sleep
- Ensure SD is disabled and WiFi enabled before configuration
- Add 2-second delay after ApplyNetworkLan for module restart
- Re-enable WiFi after restart ONLY if in StreamToApp mode
- Respect hardware limitation that SD and WiFi share same SPI bus

This fixes issue #169 where WiFi remains disabled after applying
network configuration changes, while preventing conflicts with SD
card logging mode.

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
…ironments

- Add backward-compatible UDP discovery that handles firmware responding to port 30303
- Create multi-interface UDP broadcaster to send discovery from all network adapters
- Track which local interface discovered each device
- Bind TCP connections to the correct local interface to prevent routing issues
- Fix connection timeouts when multiple NICs have routes to same IP (192.168.1.1)

This resolves device discovery failures when using WiFi adapters and connection
timeouts when both router and DAQiFi device use the same IP on different interfaces.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@cptkoolbeenz cptkoolbeenz requested a review from a team as a code owner July 21, 2025 21:15
@codacy-production
Copy link

codacy-production bot commented Jul 21, 2025

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
Report missing for ca0108a1 0.00%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (ca0108a) Report Missing Report Missing Report Missing
Head commit (b86877d) 5543 309 5.57%

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#173) 898 0 0.00%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

See your quality gate settings    Change summary preferences

Footnotes

  1. Codacy didn't receive coverage data for the commit, or there was an error processing the received data. Check your integration for errors and validate that your coverage setup is correct.

@cptkoolbeenz
Copy link
Member Author

/review

@qodo-merge-pro
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Port binding behavior

The legacy UDP receiver binds to the same broadcast port used for sending. Confirm this does not conflict with per-interface sockets and that ReuseAddress plus OS semantics reliably allow concurrent binds across NICs, especially on Windows.

// Create receiver on port 30303 for firmware that responds to this port by design
try
{
    _legacyReceiver = new UdpClient(_broadcastPort);
    _legacyReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
    AppLogger.Information($"Receiver listening on port {_broadcastPort} for firmware responses");
}
catch (Exception ex)
{
    AppLogger.Warning($"Could not create receiver on port {_broadcastPort}: {ex.Message}");
    // Continue without it - direct responses will still work
Duplicate device suppression

Device de-dup uses MAC+IP but does not account for device reappearance or IP change; there is no removal path or TTL. Validate that this won’t prevent rediscovery after network changes or power cycles within the same run.

// Prevent duplicate device notifications
lock (_deviceLock)
{
    var deviceKey = $"{device.MacAddress}_{device.IpAddress}";
    if (_discoveredDevices.Add(deviceKey))
    {
        NotifyDeviceFound(this, device);
    }
TCP bind robustness

Binding to a specific local interface is attempted when present, but there’s no validation that the bound NIC has a route to the target IP or that the local address matches the destination subnet; consider pre-checks to avoid misleading partial connects.

{
    try
    {
        var localEndpoint = new IPEndPoint(IPAddress.Parse(LocalInterfaceAddress), 0);
        Client = new TcpClient(localEndpoint);
        AppLogger.Information($"Binding TCP connection to local interface {LocalInterfaceAddress} for device at {IpAddress}");
    }
    catch (Exception ex)
    {
        AppLogger.Warning($"Failed to bind to local interface {LocalInterfaceAddress}: {ex.Message}. Using default.");
        Client = new TcpClient();
    }

@cptkoolbeenz
Copy link
Member Author

/improve

@qodo-merge-pro
Copy link
Contributor

qodo-merge-pro bot commented Aug 24, 2025

PR Code Suggestions ✨

Latest suggestions up to 70bf53f

CategorySuggestion                                                                                                                                    Impact
Possible issue
Ignore self and validate payload

Guard against processing your own broadcast by checking if
remoteEndPoint.Address equals the local interface address, and return early.
Also validate that the buffer contains a full delimited protobuf message to
avoid parser exceptions on partial or non-protobuf UDP traffic.

Daqifi.Desktop/Device/WiFiDevice/DaqifiDeviceFinder.cs [384-403]

 private void ProcessDiscoveryResponse(byte[] receivedBytes, IPEndPoint remoteEndPoint, string source, IPAddress localInterface)
 {
     var receivedText = Encoding.ASCII.GetString(receivedBytes);
-    
+
+    // Ignore self-generated packets
+    if (localInterface != null && remoteEndPoint.Address.Equals(localInterface))
+        return;
+
     AppLogger.Information($"Received response on {source} from {remoteEndPoint.Address}:{remoteEndPoint.Port}");
 
-    if (IsValidDiscoveryMessage(receivedText))
+    if (!IsValidDiscoveryMessage(receivedText))
+        return;
+
+    try
     {
-        try
-        {
-            var stream = new MemoryStream(receivedBytes);
-            var message = DaqifiOutMessage.Parser.ParseDelimitedFrom(stream);
-            var device = GetDeviceFromProtobufMessage(message);
-            device.IpAddress = remoteEndPoint.Address.ToString();
-            
-            // Store the local interface that received this response
-            if (localInterface != null)
-            {
-                device.LocalInterfaceAddress = localInterface.ToString();
-            }
+        using var stream = new MemoryStream(receivedBytes);
+        // Basic sanity check: ensure there is at least 1 byte length prefix or minimal payload
+        if (receivedBytes.Length == 0) return;
 
+        var message = DaqifiOutMessage.Parser.ParseDelimitedFrom(stream);
+        var device = GetDeviceFromProtobufMessage(message);
+        device.IpAddress = remoteEndPoint.Address.ToString();
+
+        if (localInterface != null)
+            device.LocalInterfaceAddress = localInterface.ToString();
+

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential issue where the application might process its own broadcast packets, and proposes a valid check to prevent this, improving the robustness of the discovery mechanism.

Medium
Avoid overbroad UDP binding conflicts

Binding the legacy receiver to IPAddress.Any can capture responses intended for
other apps and may conflict with multiple NIC use. Bind explicitly to
IPAddress.Broadcast or better, join a per-interface socket bound to each local
IP to receive directed replies on that port. At minimum, use new
IPEndPoint(IPAddress.Any, 0) for senders and keep legacy receiver strictly for
port 30303 responses while ensuring you don't process your own broadcasts by
checking source port/content.

Daqifi.Desktop/Device/WiFiDevice/DaqifiDeviceFinder.cs [66-79]

 _legacyReceiver = new UdpClient(AddressFamily.InterNetwork);
 _legacyReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
-            
-// For Windows, explicitly allow multiple processes to bind to same port
 if (Environment.OSVersion.Platform == PlatformID.Win32NT)
 {
-    try 
-    { 
-        _legacyReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, false);
-    } 
-    catch { /* ExclusiveAddressUse might not be available on all systems */ }
+    try { _legacyReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, false); }
+    catch { }
 }
-            
+// Bind only to IPv4 any on the specific legacy port to avoid capturing other traffic;
+// also ensure we ignore messages originating from ourselves in processing.
 _legacyReceiver.Client.Bind(new IPEndPoint(IPAddress.Any, _broadcastPort));
  • Apply / Chat
Suggestion importance[1-10]: 2

__

Why: The suggestion's improved_code is functionally identical to the existing_code, only adding a comment, which provides minimal value.

Low
General
Harden TCP connect and timeouts

BeginConnect can throw immediately for DNS/invalid IP and the wait handle may be
already signaled; ensure you handle synchronous completion and socket disposal
consistently. Also set LingerState and receive/send timeouts to avoid hanging
sockets on failure paths.

Daqifi.Desktop/Device/WiFiDevice/DaqifiStreamingDevice.cs [64-97]

-var result = Client.BeginConnect(IpAddress, Port, null, null);
+IAsyncResult result = null;
 bool connectionSuccessful = false;
-        
 try
 {
+    result = Client.BeginConnect(IpAddress, Port, null, null);
     var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5));
-
     if (!success)
     {
         AppLogger.Error($"Timeout connecting to DAQiFi Device at {IpAddress}:{Port} from local interface {LocalInterfaceAddress ?? "default"}");
         try { Client.Close(); } catch { }
         return false;
     }
-            
-    // Complete the connection
-    try
-    {
-        Client.EndConnect(result);
-        // Set NoDelay for lower latency
-        try { Client.NoDelay = true; } catch { }
-        connectionSuccessful = true;
-    }
-    catch (Exception ex)
-    {
-        AppLogger.Error(ex, $"Failed to complete connection to {IpAddress}:{Port}");
-        try { Client.Close(); } catch { }
-        return false;
-    }
+
+    Client.EndConnect(result);
+    try { Client.NoDelay = true; } catch { }
+    try { Client.LingerState = new LingerOption(false, 0); } catch { }
+    try { Client.ReceiveTimeout = 5000; Client.SendTimeout = 5000; } catch { }
+    connectionSuccessful = true;
+}
+catch (Exception ex)
+{
+    AppLogger.Error(ex, $"Error connecting to {IpAddress}:{Port}");
+    try { Client.Close(); } catch { }
+    return false;
 }
 finally
 {
-    // Always dispose the wait handle to prevent resource leaks
-    try { result.AsyncWaitHandle?.Close(); } catch { }
+    try { result?.AsyncWaitHandle?.Close(); } catch { }
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion proposes adding socket options like LingerState and timeouts, which are good practices for hardening network connections and preventing resource leaks, thus improving the connection logic's reliability.

Low
  • More

Previous suggestions

✅ Suggestions up to commit f48448f
CategorySuggestion                                                                                                                                    Impact
Incremental [*]
Fix legacy UDP bind robustness
Suggestion Impact:The commit changed UDP client creation to use AddressFamily.InterNetwork, set ReuseAddress before binding, attempted to disable ExclusiveAddressUse (with platform guard), and then explicitly bound the socket, matching the suggestion’s intent.

code diff:

-                // Create UDP client directly with the port to ensure proper binding
-                _legacyReceiver = new UdpClient(new IPEndPoint(IPAddress.Any, _broadcastPort));
+                // Create UDP client and set socket options before binding for maximum compatibility
+                _legacyReceiver = new UdpClient(AddressFamily.InterNetwork);
                 _legacyReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+                
+                // For Windows, explicitly allow multiple processes to bind to same port
+                if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+                {
+                    try 
+                    { 
+                        _legacyReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, false);
+                    } 
+                    catch { /* ExclusiveAddressUse might not be available on all systems */ }
+                }
+                
+                _legacyReceiver.Client.Bind(new IPEndPoint(IPAddress.Any, _broadcastPort));

Set the socket reuse option before binding and use exclusive address use off to
avoid bind failures on multi-NIC systems; also bind explicitly to IPv4 by
creating the socket with AddressFamily.InterNetwork before wrapping in
UdpClient.

Daqifi.Desktop/Device/WiFiDevice/DaqifiDeviceFinder.cs [66-68]

-_legacyReceiver = new UdpClient(new IPEndPoint(IPAddress.Any, _broadcastPort));
-_legacyReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+var legacySocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+legacySocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+try { legacySocket.ExclusiveAddressUse = false; } catch { }
+legacySocket.Bind(new IPEndPoint(IPAddress.Any, _broadcastPort));
+_legacyReceiver = new UdpClient { Client = legacySocket };
 AppLogger.Information($"Receiver listening on 0.0.0.0:{_broadcastPort} for firmware responses");

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: This suggestion correctly identifies that setting socket options before binding is more robust and improves reliability, which is a valuable enhancement for network programming.

Medium
Suggestions up to commit 311b056
CategorySuggestion                                                                                                                                    Impact
Incremental [*]
Use correct legacy UDP port

Bind the legacy UDP receiver to the actual legacy response port (e.g., 30303)
rather than _broadcastPort, to ensure old firmware responses are received even
when discovery broadcasts use a different port.

Daqifi.Desktop/Device/WiFiDevice/DaqifiDeviceFinder.cs [62-74]

 try
 {
-    var legacyEndpoint = new IPEndPoint(IPAddress.Any, _broadcastPort);
+    const int legacyPort = 30303;
+    var legacyEndpoint = new IPEndPoint(IPAddress.Any, legacyPort);
     _legacyReceiver = new UdpClient(AddressFamily.InterNetwork);
     _legacyReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
     _legacyReceiver.Client.Bind(legacyEndpoint);
     AppLogger.Information($"Receiver listening on {legacyEndpoint} for firmware responses");
 }
 catch (Exception ex)
 {
-    AppLogger.Warning($"Could not create receiver on port {_broadcastPort}: {ex.Message}");
+    AppLogger.Warning($"Could not create receiver on legacy port 30303: {ex.Message}");
     // Continue without it - direct responses will still work
 }
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a bug where the legacy receiver listens on the wrong port, which would break discovery for older firmware, a key feature of this PR.

High
Possible issue
Dispose stream and validate device

Use a using block or explicitly dispose the MemoryStream to avoid leaking
unmanaged buffers on frequent discovery responses. Also guard
GetDeviceFromProtobufMessage with a null-check and validate required fields
before deduping to prevent null reference and incorrect keys.

Daqifi.Desktop/Device/WiFiDevice/DaqifiDeviceFinder.cs [376-408]

 AppLogger.Information($"Received response on {source} from {remoteEndPoint.Address}:{remoteEndPoint.Port}");
 
 if (IsValidDiscoveryMessage(receivedText))
 {
     try
     {
-        var stream = new MemoryStream(receivedBytes);
-        var message = DaqifiOutMessage.Parser.ParseDelimitedFrom(stream);
-        var device = GetDeviceFromProtobufMessage(message);
-        device.IpAddress = remoteEndPoint.Address.ToString();
-        
-        // Store the local interface that received this response
-        if (localInterface != null)
+        using (var stream = new MemoryStream(receivedBytes))
         {
-            device.LocalInterfaceAddress = localInterface.ToString();
-        }
-        
-        // Prevent duplicate device notifications
-        lock (_deviceLock)
-        {
-            var deviceKey = $"{device.MacAddress}_{device.IpAddress}";
-            if (_discoveredDevices.Add(deviceKey))
+            var message = DaqifiOutMessage.Parser.ParseDelimitedFrom(stream);
+            var device = GetDeviceFromProtobufMessage(message);
+            if (device == null)
             {
-                NotifyDeviceFound(this, device);
+                AppLogger.Warning("Parsed discovery response produced null device; skipping.");
+                return;
+            }
+
+            device.IpAddress = remoteEndPoint.Address.ToString();
+
+            if (localInterface != null)
+            {
+                device.LocalInterfaceAddress = localInterface.ToString();
+            }
+
+            lock (_deviceLock)
+            {
+                var mac = device.MacAddress ?? string.Empty;
+                var ip = device.IpAddress ?? string.Empty;
+                if (mac.Length == 0 || ip.Length == 0)
+                {
+                    AppLogger.Warning("Device missing MAC or IP; skipping duplicate check and notification.");
+                    return;
+                }
+
+                var deviceKey = $"{mac}_{ip}";
+                if (_discoveredDevices.Add(deviceKey))
+                {
+                    NotifyDeviceFound(this, device);
+                }
             }
         }
     }
     catch (Exception ex)
     {
         AppLogger.Error(ex, "Error parsing discovery response");
     }
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a missing using statement for MemoryStream and adds important null checks, which improves resource management and prevents potential runtime exceptions.

Medium
Cancel hung connects and validate inputs

Add a cancellation of the pending connect when the wait times out to avoid
lingering socket attempts. Also validate IpAddress and Port upfront and set
ReceiveTimeout/SendTimeout on the client to fail fast in multi-NIC routing
issues.

Daqifi.Desktop/Device/WiFiDevice/DaqifiStreamingDevice.cs [64-97]

 var result = Client.BeginConnect(IpAddress, Port, null, null);
 bool connectionSuccessful = false;
 
 try
 {
+    if (string.IsNullOrWhiteSpace(IpAddress) || Port <= 0)
+    {
+        AppLogger.Error("Invalid IP address or port for connection.");
+        try { Client.Close(); } catch { }
+        return false;
+    }
+
     var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5));
 
     if (!success)
     {
+        try { Client.Close(); } catch { }
         AppLogger.Error($"Timeout connecting to DAQiFi Device at {IpAddress}:{Port} from local interface {LocalInterfaceAddress ?? "default"}");
-        try { Client.Close(); } catch { }
         return false;
     }
-    
-    // Complete the connection
+
     try
     {
         Client.EndConnect(result);
-        // Set NoDelay for lower latency
-        try { Client.NoDelay = true; } catch { }
+        try
+        {
+            Client.NoDelay = true;
+            Client.ReceiveTimeout = 5000;
+            Client.SendTimeout = 5000;
+        }
+        catch { }
         connectionSuccessful = true;
     }
     catch (Exception ex)
     {
         AppLogger.Error(ex, $"Failed to complete connection to {IpAddress}:{Port}");
         try { Client.Close(); } catch { }
         return false;
     }
 }
 finally
 {
-    // Always dispose the wait handle to prevent resource leaks
     try { result.AsyncWaitHandle?.Close(); } catch { }
 }
Suggestion importance[1-10]: 6

__

Why: This suggestion enhances connection robustness by adding upfront validation for IpAddress and Port and setting client timeouts, which are good practices for network programming.

Low
✅ Suggestions up to commit bcb6549
CategorySuggestion                                                                                                                                    Impact
Incremental [*]
Harden receive loop re-arming
Suggestion Impact:The commit wraps EndReceive/processing in try/catch, checks for non-empty payloads, and moves BeginReceive into a finally block (with additional guards) for both the main and legacy receivers, ensuring the receive loop is re-armed even after exceptions.

code diff:

+        byte[] receivedBytes = null;
+        IPEndPoint remoteIpEndPoint = null;
+        
         try
         {
-            var remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
-            var receivedBytes = broadcaster.Client.EndReceive(res, ref remoteIpEndPoint);
-            
-            ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, broadcaster.InterfaceName, broadcaster.InterfaceAddress);
-            
-            // Continue receiving on this broadcaster
-            broadcaster.Client.BeginReceive(HandleFinderMessageReceived, broadcaster);
+            remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
+            receivedBytes = broadcaster.Client.EndReceive(res, ref remoteIpEndPoint);
+            
+            if (receivedBytes != null && receivedBytes.Length > 0)
+            {
+                ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, broadcaster.InterfaceName, broadcaster.InterfaceAddress);
+            }
         }
         catch (ObjectDisposedException)
         {
             // Expected when stopping
+            return;
+        }
+        catch (SocketException sockEx)
+        {
+            AppLogger.Warning($"Socket error on {broadcaster?.InterfaceName}: {sockEx.Message}");
         }
         catch (Exception ex)
         {
             AppLogger.Error(ex, $"Problem receiving on {broadcaster?.InterfaceName}: {ex.Message}");
+        }
+        finally
+        {
+            // Re-arm the receive only if we're still running and the client is valid
+            if (Running && broadcaster?.Client != null)
+            {
+                try 
+                { 
+                    broadcaster.Client.BeginReceive(HandleFinderMessageReceived, broadcaster); 
+                } 
+                catch (ObjectDisposedException) 
+                { 
+                    // Socket was closed during shutdown
+                }
+                catch (Exception ex)
+                {
+                    AppLogger.Warning($"Could not re-arm receive for {broadcaster.InterfaceName}: {ex.Message}");
+                }
+            }
         }
     }
     
     private void HandleLegacyMessageReceived(IAsyncResult res)
     {
+        byte[] receivedBytes = null;
+        IPEndPoint remoteIpEndPoint = null;
+        
         try
         {
-            var remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
-            var receivedBytes = _legacyReceiver.EndReceive(res, ref remoteIpEndPoint);
-            
-            // For legacy receiver, we don't know the specific interface
-            ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, "Legacy Port 30303", null);
-            
-            // Continue receiving
-            _legacyReceiver.BeginReceive(HandleLegacyMessageReceived, null);
+            remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
+            receivedBytes = _legacyReceiver.EndReceive(res, ref remoteIpEndPoint);
+            
+            if (receivedBytes != null && receivedBytes.Length > 0)
+            {
+                // For legacy receiver, we don't know the specific interface
+                ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, "Legacy Port 30303", null);
+            }
         }
         catch (ObjectDisposedException)
         {
             // Expected when stopping
+            return;
+        }
+        catch (SocketException sockEx)
+        {
+            AppLogger.Warning($"Socket error on legacy port: {sockEx.Message}");
         }
         catch (Exception ex)
         {
             AppLogger.Error(ex, $"Problem receiving on legacy port: {ex.Message}");
+        }
+        finally
+        {
+            // Re-arm the receive only if we're still running
+            if (Running && _legacyReceiver != null)
+            {
+                try 
+                { 
+                    _legacyReceiver.BeginReceive(HandleLegacyMessageReceived, null); 
+                } 
+                catch (ObjectDisposedException) 
+                { 
+                    // Socket was closed during shutdown
+                }
+                catch (Exception ex)
+                {
+                    AppLogger.Warning($"Could not re-arm legacy receive: {ex.Message}");
+                }
+            }
         }

Validate that received data is non-empty before processing and re-arm the
receive in a finally block to avoid losing the receive loop on transient parse
errors or exceptions

Daqifi.Desktop/Device/WiFiDevice/DaqifiDeviceFinder.cs [275-281]

 var remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
-var receivedBytes = broadcaster.Client.EndReceive(res, ref remoteIpEndPoint);
+byte[] receivedBytes = null;
+try
+{
+    receivedBytes = broadcaster.Client.EndReceive(res, ref remoteIpEndPoint);
+    if (receivedBytes != null && receivedBytes.Length > 0)
+    {
+        ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, broadcaster.InterfaceName, broadcaster.InterfaceAddress);
+    }
+    else
+    {
+        AppLogger.Warning($"Received empty UDP packet on {broadcaster.InterfaceName}");
+    }
+}
+catch (ObjectDisposedException)
+{
+    // Expected when stopping
+    return;
+}
+catch (Exception ex)
+{
+    AppLogger.Error(ex, $"Problem receiving on {broadcaster?.InterfaceName}: {ex.Message}");
+}
+finally
+{
+    if (broadcaster?.Client != null)
+    {
+        try { broadcaster.Client.BeginReceive(HandleFinderMessageReceived, broadcaster); } catch { }
+    }
+}
 
-ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, broadcaster.InterfaceName, broadcaster.InterfaceAddress);
-
-// Continue receiving on this broadcaster
-broadcaster.Client.BeginReceive(HandleFinderMessageReceived, broadcaster);
-

[Suggestion processed]

Suggestion importance[1-10]: 8

__

Why: This suggestion correctly points out a critical flaw where an exception in ProcessDiscoveryResponse would stop the receive loop for a given interface; moving BeginReceive to a finally block ensures the listener is always re-armed, significantly improving the finder's reliability.

Medium
Fix UDP receiver binding
Suggestion Impact:The commit creates an IPv4 UdpClient, binds it to IPAddress.Any with the specified port, updates the log to include the endpoint, and adds guarded re-arming of BeginReceive in finally blocks handling ObjectDisposed and null checks.

code diff:

-                _legacyReceiver = new UdpClient(_broadcastPort);
+                var legacyEndpoint = new IPEndPoint(IPAddress.Any, _broadcastPort);
+                _legacyReceiver = new UdpClient(AddressFamily.InterNetwork);
                 _legacyReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
-                AppLogger.Information($"Receiver listening on port {_broadcastPort} for firmware responses");
+                _legacyReceiver.Client.Bind(legacyEndpoint);
+                AppLogger.Information($"Receiver listening on {legacyEndpoint} for firmware responses");
             }
             catch (Exception ex)
             {
@@ -270,46 +272,100 @@
         var broadcaster = res.AsyncState as InterfaceBroadcaster;
         if (broadcaster == null) return;
         
+        byte[] receivedBytes = null;
+        IPEndPoint remoteIpEndPoint = null;
+        
         try
         {
-            var remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
-            var receivedBytes = broadcaster.Client.EndReceive(res, ref remoteIpEndPoint);
-            
-            ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, broadcaster.InterfaceName, broadcaster.InterfaceAddress);
-            
-            // Continue receiving on this broadcaster
-            broadcaster.Client.BeginReceive(HandleFinderMessageReceived, broadcaster);
+            remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
+            receivedBytes = broadcaster.Client.EndReceive(res, ref remoteIpEndPoint);
+            
+            if (receivedBytes != null && receivedBytes.Length > 0)
+            {
+                ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, broadcaster.InterfaceName, broadcaster.InterfaceAddress);
+            }
         }
         catch (ObjectDisposedException)
         {
             // Expected when stopping
+            return;
+        }
+        catch (SocketException sockEx)
+        {
+            AppLogger.Warning($"Socket error on {broadcaster?.InterfaceName}: {sockEx.Message}");
         }
         catch (Exception ex)
         {
             AppLogger.Error(ex, $"Problem receiving on {broadcaster?.InterfaceName}: {ex.Message}");
+        }
+        finally
+        {
+            // Re-arm the receive only if we're still running and the client is valid
+            if (Running && broadcaster?.Client != null)
+            {
+                try 
+                { 
+                    broadcaster.Client.BeginReceive(HandleFinderMessageReceived, broadcaster); 
+                } 
+                catch (ObjectDisposedException) 
+                { 
+                    // Socket was closed during shutdown
+                }
+                catch (Exception ex)
+                {
+                    AppLogger.Warning($"Could not re-arm receive for {broadcaster.InterfaceName}: {ex.Message}");
+                }
+            }
         }
     }
     
     private void HandleLegacyMessageReceived(IAsyncResult res)
     {
+        byte[] receivedBytes = null;
+        IPEndPoint remoteIpEndPoint = null;
+        
         try
         {
-            var remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
-            var receivedBytes = _legacyReceiver.EndReceive(res, ref remoteIpEndPoint);
-            
-            // For legacy receiver, we don't know the specific interface
-            ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, "Legacy Port 30303", null);
-            
-            // Continue receiving
-            _legacyReceiver.BeginReceive(HandleLegacyMessageReceived, null);
+            remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
+            receivedBytes = _legacyReceiver.EndReceive(res, ref remoteIpEndPoint);
+            
+            if (receivedBytes != null && receivedBytes.Length > 0)
+            {
+                // For legacy receiver, we don't know the specific interface
+                ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, "Legacy Port 30303", null);
+            }
         }
         catch (ObjectDisposedException)
         {
             // Expected when stopping
+            return;
+        }
+        catch (SocketException sockEx)
+        {
+            AppLogger.Warning($"Socket error on legacy port: {sockEx.Message}");
         }
         catch (Exception ex)
         {
             AppLogger.Error(ex, $"Problem receiving on legacy port: {ex.Message}");
+        }
+        finally
+        {
+            // Re-arm the receive only if we're still running
+            if (Running && _legacyReceiver != null)
+            {
+                try 
+                { 
+                    _legacyReceiver.BeginReceive(HandleLegacyMessageReceived, null); 
+                } 
+                catch (ObjectDisposedException) 
+                { 
+                    // Socket was closed during shutdown
+                }
+                catch (Exception ex)
+                {
+                    AppLogger.Warning($"Could not re-arm legacy receive: {ex.Message}");
+                }
+            }
         }

Bind the legacy UDP receiver explicitly to IPv4 (and optionally to Any) and set
ReuseAddress before binding to prevent AddressFamily/port conflicts on multi-NIC
systems; also guard BeginReceive calls when the socket is null or disposed

Daqifi.Desktop/Device/WiFiDevice/DaqifiDeviceFinder.cs [64-66]

-_legacyReceiver = new UdpClient(_broadcastPort);
+var legacyEndpoint = new IPEndPoint(IPAddress.Any, _broadcastPort);
+_legacyReceiver = new UdpClient(AddressFamily.InterNetwork);
 _legacyReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
-AppLogger.Information($"Receiver listening on port {_broadcastPort} for firmware responses");
+_legacyReceiver.Client.Bind(legacyEndpoint);
+AppLogger.Information($"Receiver listening on {legacyEndpoint} for firmware responses");

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that explicitly binding the UdpClient to an IPv4 endpoint (IPAddress.Any) improves robustness, preventing potential address family conflicts on multi-NIC or IPv6-enabled systems.

Medium
Prevent handle leaks and set NoDelay
Suggestion Impact:The commit moved the WaitOne and EndConnect into a try/finally, disposed result.AsyncWaitHandle in finally, set Client.NoDelay after EndConnect, and partially guarded resource cleanup. This aligns with the suggestion’s intent to prevent handle leaks and set NoDelay.

code diff:

             var result = Client.BeginConnect(IpAddress, Port, null, null);
-            var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5));
+            bool connectionSuccessful = false;
+            
+            try
+            {
+                var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5));
 
-            if (!success)
+                if (!success)
+                {
+                    AppLogger.Error($"Timeout connecting to DAQiFi Device at {IpAddress}:{Port} from local interface {LocalInterfaceAddress ?? "default"}");
+                    try { Client.Close(); } catch { }
+                    return false;
+                }
+                
+                // Complete the connection
+                try
+                {
+                    Client.EndConnect(result);
+                    // Set NoDelay for lower latency
+                    try { Client.NoDelay = true; } catch { }
+                    connectionSuccessful = true;
+                }
+                catch (Exception ex)
+                {
+                    AppLogger.Error(ex, $"Failed to complete connection to {IpAddress}:{Port}");
+                    try { Client.Close(); } catch { }
+                    return false;
+                }
+            }
+            finally
             {
-                AppLogger.Error($"Timeout connecting to DAQiFi Device at {IpAddress}:{Port} from local interface {LocalInterfaceAddress ?? "default"}");
-                try { Client.Close(); } catch { }
-                return false;
+                // Always dispose the wait handle to prevent resource leaks
+                try { result.AsyncWaitHandle?.Close(); } catch { }

Ensure the async handle is always closed/disposed to avoid WaitHandle leaks and
set NoDelay to reduce latency; also guard against null Client after fallback
paths

Daqifi.Desktop/Device/WiFiDevice/DaqifiStreamingDevice.cs [64-84]

 var result = Client.BeginConnect(IpAddress, Port, null, null);
-var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5));
-
-if (!success)
-{
-    AppLogger.Error($"Timeout connecting to DAQiFi Device at {IpAddress}:{Port} from local interface {LocalInterfaceAddress ?? "default"}");
-    try { Client.Close(); } catch { }
-    return false;
-}
-
-// Complete the connection
 try
 {
+    var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5));
+    if (!success)
+    {
+        AppLogger.Error($"Timeout connecting to DAQiFi Device at {IpAddress}:{Port} from local interface {LocalInterfaceAddress ?? "default"}");
+        try { Client?.Close(); } catch { }
+        return false;
+    }
     Client.EndConnect(result);
+    try { Client.NoDelay = true; } catch { }
 }
 catch (Exception ex)
 {
     AppLogger.Error(ex, $"Failed to complete connection to {IpAddress}:{Port}");
-    try { Client.Close(); } catch { }
+    try { Client?.Close(); } catch { }
     return false;
 }
+finally
+{
+    try { result.AsyncWaitHandle.Close(); } catch { }
+}

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a resource leak by not closing the AsyncWaitHandle and proposes a finally block to fix it, which is a valid improvement for resource management.

Low
Possible issue
Handle socket race conditions
Suggestion Impact:The commit enhanced the receive callbacks with additional exception handling (including SocketException), early returns on ObjectDisposedException, and conditional re-arming of BeginReceive only when still running and client exists. It also wrapped EndReceive in try/catch and moved re-arming into finally with guards, aligning with the suggestion’s intent to make the code resilient to race conditions. While it didn’t add explicit IsBound checks, the implemented guards and flow changes reflect the suggested robustness improvements.

code diff:

@@ -270,46 +272,100 @@
         var broadcaster = res.AsyncState as InterfaceBroadcaster;
         if (broadcaster == null) return;
         
+        byte[] receivedBytes = null;
+        IPEndPoint remoteIpEndPoint = null;
+        
         try
         {
-            var remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
-            var receivedBytes = broadcaster.Client.EndReceive(res, ref remoteIpEndPoint);
-            
-            ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, broadcaster.InterfaceName, broadcaster.InterfaceAddress);
-            
-            // Continue receiving on this broadcaster
-            broadcaster.Client.BeginReceive(HandleFinderMessageReceived, broadcaster);
+            remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
+            receivedBytes = broadcaster.Client.EndReceive(res, ref remoteIpEndPoint);
+            
+            if (receivedBytes != null && receivedBytes.Length > 0)
+            {
+                ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, broadcaster.InterfaceName, broadcaster.InterfaceAddress);
+            }
         }
         catch (ObjectDisposedException)
         {
             // Expected when stopping
+            return;
+        }
+        catch (SocketException sockEx)
+        {
+            AppLogger.Warning($"Socket error on {broadcaster?.InterfaceName}: {sockEx.Message}");
         }
         catch (Exception ex)
         {
             AppLogger.Error(ex, $"Problem receiving on {broadcaster?.InterfaceName}: {ex.Message}");
+        }
+        finally
+        {
+            // Re-arm the receive only if we're still running and the client is valid
+            if (Running && broadcaster?.Client != null)
+            {
+                try 
+                { 
+                    broadcaster.Client.BeginReceive(HandleFinderMessageReceived, broadcaster); 
+                } 
+                catch (ObjectDisposedException) 
+                { 
+                    // Socket was closed during shutdown
+                }
+                catch (Exception ex)
+                {
+                    AppLogger.Warning($"Could not re-arm receive for {broadcaster.InterfaceName}: {ex.Message}");
+                }
+            }
         }
     }
     
     private void HandleLegacyMessageReceived(IAsyncResult res)
     {
+        byte[] receivedBytes = null;
+        IPEndPoint remoteIpEndPoint = null;
+        
         try
         {
-            var remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
-            var receivedBytes = _legacyReceiver.EndReceive(res, ref remoteIpEndPoint);
-            
-            // For legacy receiver, we don't know the specific interface
-            ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, "Legacy Port 30303", null);
-            
-            // Continue receiving
-            _legacyReceiver.BeginReceive(HandleLegacyMessageReceived, null);
+            remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
+            receivedBytes = _legacyReceiver.EndReceive(res, ref remoteIpEndPoint);
+            
+            if (receivedBytes != null && receivedBytes.Length > 0)
+            {
+                // For legacy receiver, we don't know the specific interface
+                ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, "Legacy Port 30303", null);
+            }
         }
         catch (ObjectDisposedException)
         {
             // Expected when stopping
+            return;
+        }
+        catch (SocketException sockEx)
+        {
+            AppLogger.Warning($"Socket error on legacy port: {sockEx.Message}");
         }
         catch (Exception ex)
         {
             AppLogger.Error(ex, $"Problem receiving on legacy port: {ex.Message}");
+        }
+        finally
+        {
+            // Re-arm the receive only if we're still running
+            if (Running && _legacyReceiver != null)
+            {
+                try 
+                { 
+                    _legacyReceiver.BeginReceive(HandleLegacyMessageReceived, null); 
+                } 
+                catch (ObjectDisposedException) 
+                { 
+                    // Socket was closed during shutdown
+                }
+                catch (Exception ex)
+                {
+                    AppLogger.Warning($"Could not re-arm legacy receive: {ex.Message}");
+                }
+            }
         }

Guard BeginReceive/EndReceive with null/IsBound checks and catch potential
SocketException to avoid crashes when the socket closes between the callbacks
(race during Stop). Re-arm receive only when the client is still valid.

Daqifi.Desktop/Device/WiFiDevice/DaqifiDeviceFinder.cs [273-282]

-broadcaster.Client.BeginReceive(HandleFinderMessageReceived, broadcaster);
-...
-var receivedBytes = broadcaster.Client.EndReceive(res, ref remoteIpEndPoint);
+if (broadcaster?.Client?.Client != null && broadcaster.Client.Client.IsBound)
+{
+    try
+    {
+        var receivedBytes = broadcaster.Client.EndReceive(res, ref remoteIpEndPoint);
+        ProcessDiscoveryResponse(receivedBytes, remoteIpEndPoint, broadcaster.InterfaceName, broadcaster.InterfaceAddress);
+    }
+    catch (ObjectDisposedException) { return; }
+    catch (SocketException) { return; }
+    catch (Exception ex)
+    {
+        AppLogger.Error(ex, $"Problem receiving on {broadcaster.InterfaceName}: {ex.Message}");
+    }
 
+    if (broadcaster?.Client?.Client != null && broadcaster.Client.Client.IsBound)
+    {
+        try { broadcaster.Client.BeginReceive(HandleFinderMessageReceived, broadcaster); } catch { }
+    }
+}
+
Suggestion importance[1-10]: 6

__

Why: The suggestion enhances robustness by adding checks and more comprehensive exception handling to the async receive callback, making it more resilient to race conditions during shutdown, although the main exception was already handled.

Low
✅ Suggestions up to commit 300eee2
CategorySuggestion                                                                                                                                    Impact
Possible issue
Properly finalize async TCP connect
Suggestion Impact:The commit added a call to Client.EndConnect(result) after the async wait, wrapped in try/catch, and closes the client on timeout or exception, aligning with the suggestion to finalize the async connect and free resources on failure.

code diff:

+            var result = Client.BeginConnect(IpAddress, Port, null, null);
+            var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5));
+
+            if (!success)
+            {
+                AppLogger.Error($"Timeout connecting to DAQiFi Device at {IpAddress}:{Port} from local interface {LocalInterfaceAddress ?? "default"}");
+                try { Client.Close(); } catch { }
+                return false;
+            }
+            
+            // Complete the connection
+            try
+            {
+                Client.EndConnect(result);
+            }
+            catch (Exception ex)
+            {
+                AppLogger.Error(ex, $"Failed to complete connection to {IpAddress}:{Port}");
+                try { Client.Close(); } catch { }
+                return false;
+            }

Always call EndConnect regardless of timeout outcome to avoid socket leaks and
ensure proper exception surfacing. On timeout, close the client to free
resources before returning failure.

Daqifi.Desktop/Device/WiFiDevice/DaqifiStreamingDevice.cs [64-71]

-var result = Client.BeginConnect(IpAddress, Port, null, null);
-var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5));
+var asyncResult = Client.BeginConnect(IpAddress, Port, null, null);
+var success = asyncResult.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5));
 
-if (!success)
+try
 {
-    AppLogger.Error($"Timeout connecting to DAQiFi Device at {IpAddress}:{Port}");
+    Client.EndConnect(asyncResult);
+}
+catch (Exception ex)
+{
+    AppLogger.Error(ex, $"Failed to connect to DAQiFi Device at {IpAddress}:{Port}");
+    Client.Close();
     return false;
 }
 
+if (!success || !Client.Connected)
+{
+    AppLogger.Error($"Timeout connecting to DAQiFi Device at {IpAddress}:{Port}");
+    Client.Close();
+    return false;
+}
+

[Suggestion processed]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a critical issue where EndConnect is not called, which can lead to resource leaks; the proposed fix properly handles the connection finalization.

Medium
Fix legacy UDP bind robustness

Set socket options like ReuseAddress/ExclusiveAddressUse before binding to
ensure they take effect on all platforms and avoid bind failures in
multi-process scenarios. Also disable multicast loopback and explicitly bind to
IPv4 Any to prevent v6-only binds on dual-stack systems.

Daqifi.Desktop/Device/WiFiDevice/DaqifiDeviceFinderBackwardCompatible.cs [64-66]

-_legacyReceiver = new UdpClient(_broadcastPort);
+_legacyReceiver = new UdpClient(AddressFamily.InterNetwork);
 _legacyReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+_legacyReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, false);
+_legacyReceiver.ExclusiveAddressUse = false;
+_legacyReceiver.Client.Bind(new IPEndPoint(IPAddress.Any, _broadcastPort));
 AppLogger.Information($"Receiver listening on port {_broadcastPort} for firmware responses");
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that setting socket options before binding is more robust and improves cross-platform compatibility, making the network code more reliable.

Low
General
Avoid receive restart after stop

Guard the async receive restart with a Running check to avoid re-arming
callbacks during shutdown. This prevents ObjectDisposedException races and stray
callbacks after Stop() closes sockets.

Daqifi.Desktop/Device/WiFiDevice/DaqifiDeviceFinderBackwardCompatible.cs [268]

-broadcaster.Client.BeginReceive(HandleFinderMessageReceived, broadcaster);
+if (Running)
+{
+    broadcaster.Client.BeginReceive(HandleFinderMessageReceived, broadcaster);
+}
Suggestion importance[1-10]: 5

__

Why: This suggestion prevents a race condition during shutdown by adding a Running check, which is cleaner than relying on catching an ObjectDisposedException for control flow.

Low

cptkoolbeenz and others added 4 commits August 24, 2025 22:06
…ssion

- Restore multi-interface UDP discovery that was accidentally reverted in merge
- Simplify architecture by consolidating 3 device finder classes into 1
- Improve robustness with better error handling and connection management
- Add proper TCP connection cleanup on timeout
- Fix thread safety with collection copying during broadcast
- Enhanced logging for debugging multi-NIC scenarios

The merge with main had reverted the device finder from the multi-interface
version back to single-interface, causing discovery to only work on one NIC.
This commit restores full multi-NIC support with backward compatibility for
both old firmware (port 30303) and new firmware (source port response).

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Fix receive loop re-arming: Move BeginReceive to finally block to ensure
  the receive loop continues even if ProcessDiscoveryResponse throws
- Improve UDP receiver binding: Explicitly bind to IPv4 Any endpoint for
  better cross-platform compatibility
- Add AsyncWaitHandle disposal: Properly dispose wait handles to prevent
  resource leaks in TCP connection
- Add NoDelay setting: Enable TCP NoDelay for lower latency
- Enhanced error handling: Add SocketException handling and null checks

These changes address the valid suggestions from the Qodo code review
to make the multi-NIC discovery implementation more robust.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Fix UDP receiver to properly bind to port 30303 by passing endpoint
  directly to UdpClient constructor
- Add 5-minute TTL for discovered devices to handle IP changes and
  allow rediscovery after network changes
- Clean up expired device entries automatically during discovery
- Update timestamps for existing devices to keep them alive

This addresses remaining Qodo review comments about port binding and
device deduplication without TTL.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Set socket options BEFORE binding for maximum compatibility
- Create UdpClient with AddressFamily first, then set options, then bind
- Add Windows-specific ExclusiveAddressUse=false for better multi-process support
- This ensures the UDP receiver works reliably across different platforms

This addresses the final Qodo suggestion about legacy UDP bind robustness.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@github-actions
Copy link

📊 Code Coverage Report

Summary

Summary
Generated on: 8/25/2025 - 4:27:07 AM
Coverage date: 8/25/2025 - 4:26:59 AM - 8/25/2025 - 4:27:03 AM
Parser: MultiReport (5x Cobertura)
Assemblies: 5
Classes: 117
Files: 149
Line coverage: 8.4% (484 of 5710)
Covered lines: 484
Uncovered lines: 5226
Coverable lines: 5710
Total lines: 17302
Branch coverage: 8.3% (167 of 2008)
Covered branches: 167
Total branches: 2008
Method coverage: Feature is only available for sponsors

Coverage

DAQiFi - 6.5%
Name Line Branch
DAQiFi 6.5% 6.9%
Daqifi.Desktop.App 3% 0%
Daqifi.Desktop.Channel.AbstractChannel 28.5% 30%
Daqifi.Desktop.Channel.AnalogChannel 88.4%
Daqifi.Desktop.Channel.Channel 11.5% 0%
Daqifi.Desktop.Channel.ChannelColorManager 100% 100%
Daqifi.Desktop.Channel.DataSample 90.4%
Daqifi.Desktop.Channel.DigitalChannel 0%
Daqifi.Desktop.Commands.CompositeCommand 0% 0%
Daqifi.Desktop.Commands.HostCommands 0%
Daqifi.Desktop.Commands.WeakEventHandlerManager 0% 0%
Daqifi.Desktop.Configuration.FirewallConfiguration 76.4% 87.5%
Daqifi.Desktop.Configuration.WindowsFirewallWrapper 0%
Daqifi.Desktop.ConnectionManager 0% 0%
Daqifi.Desktop.Converters.BoolToActiveStatusConverter 0% 0%
Daqifi.Desktop.Converters.BoolToConnectionStatusConverter 0% 0%
Daqifi.Desktop.Converters.BoolToStatusColorConverter 0% 0%
Daqifi.Desktop.Converters.ConnectionTypeToColorConverter 0% 0%
Daqifi.Desktop.Converters.ConnectionTypeToUsbConverter 0% 0%
Daqifi.Desktop.Converters.InvertedBoolToVisibilityConverter 0% 0%
Daqifi.Desktop.Converters.ListToStringConverter 0% 0%
Daqifi.Desktop.Converters.NotNullToVisibilityConverter 0% 0%
Daqifi.Desktop.Converters.OxyColorToBrushConverter 0% 0%
Daqifi.Desktop.Device.AbstractStreamingDevice 2.5% 0%
Daqifi.Desktop.Device.DeviceMessage 0%
Daqifi.Desktop.Device.HidDevice.HidDeviceFinder 0% 0%
Daqifi.Desktop.Device.HidDevice.HidFirmwareDevice 0%
Daqifi.Desktop.Device.NativeMethods 0%
Daqifi.Desktop.Device.SerialDevice.SerialDeviceFinder 0% 0%
Daqifi.Desktop.Device.SerialDevice.SerialDeviceHelper 0% 0%
Daqifi.Desktop.Device.SerialDevice.SerialStreamingDevice 0% 0%
Daqifi.Desktop.Device.SerialDevice.UsbDevice 0% 0%
Daqifi.Desktop.Device.WiFiDevice.DaqifiDeviceFinder 0% 0%
Daqifi.Desktop.Device.WiFiDevice.DaqifiStreamingDevice 0% 0%
Daqifi.Desktop.DialogService.DialogService 0% 0%
Daqifi.Desktop.DialogService.ServiceLocator 0% 0%
Daqifi.Desktop.Exporter.OptimizedLoggingSessionExporter 29.6% 32.9%
Daqifi.Desktop.Exporter.SampleData 0%
Daqifi.Desktop.Helpers.BooleanConverter`1 0% 0%
Daqifi.Desktop.Helpers.BooleanToInverseBoolConverter 0% 0%
Daqifi.Desktop.Helpers.BooleanToVisibilityConverter 0%
Daqifi.Desktop.Helpers.EnumDescriptionConverter 100% 100%
Daqifi.Desktop.Helpers.IntToVisibilityConverter 0% 0%
Daqifi.Desktop.Helpers.MyMultiValueConverter 0%
Daqifi.Desktop.Helpers.OrdinalStringComparer 0% 0%
Daqifi.Desktop.Helpers.VersionHelper 100% 87%
Daqifi.Desktop.Logger.DatabaseLogger 0% 0%
Daqifi.Desktop.Logger.LoggedSeriesLegendItem 0% 0%
Daqifi.Desktop.Logger.LoggingContext 0%
Daqifi.Desktop.Logger.LoggingManager 0% 0%
Daqifi.Desktop.Logger.LoggingSession 26.6% 0%
Daqifi.Desktop.Logger.PlotLogger 0% 0%
Daqifi.Desktop.Logger.SummaryLogger 0% 0%
Daqifi.Desktop.Loggers.FirmwareUpdatationManager 5.8% 0%
Daqifi.Desktop.MainWindow 0% 0%
Daqifi.Desktop.Migrations.InitialSQLiteMigration 0%
Daqifi.Desktop.Migrations.LoggingContextModelSnapshot 0%
Daqifi.Desktop.Models.AddProfileModel 0%
Daqifi.Desktop.Models.DaqifiSettings 86.3% 100%
Daqifi.Desktop.Models.DebugDataCollection 0% 0%
Daqifi.Desktop.Models.DebugDataModel 0% 0%
Daqifi.Desktop.Models.Notifications 0%
Daqifi.Desktop.Models.SdCardFile 0%
Daqifi.Desktop.Services.WindowsPrincipalAdminChecker 0%
Daqifi.Desktop.Services.WpfMessageBoxService 0%
Daqifi.Desktop.UpdateVersion.VersionNotification 0% 0%
Daqifi.Desktop.View.AddChannelDialog 0% 0%
Daqifi.Desktop.View.AddProfileConfirmationDialog 0% 0%
Daqifi.Desktop.View.AddprofileDialog 0% 0%
Daqifi.Desktop.View.ConnectionDialog 0% 0%
Daqifi.Desktop.View.DebugWindow 0% 0%
Daqifi.Desktop.View.DeviceLogsView 0% 0%
Daqifi.Desktop.View.ErrorDialog 0% 0%
Daqifi.Desktop.View.ExportDialog 0% 0%
Daqifi.Desktop.View.FirmwareDialog 0% 0%
Daqifi.Desktop.View.Flyouts.ChannelsFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.DevicesFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.FirmwareFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.LiveGraphFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.LoggedSessionFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.NotificationsFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.SummaryFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.UpdateProfileFlyout 0% 0%
Daqifi.Desktop.View.SelectColorDialog 0% 0%
Daqifi.Desktop.View.SettingsDialog 0% 0%
Daqifi.Desktop.View.SuccessDialog 0% 0%
Daqifi.Desktop.ViewModels.AddChannelDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.AddProfileConfirmationDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.AddProfileDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.ConnectionDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.DaqifiViewModel 0% 0%
Daqifi.Desktop.ViewModels.DeviceLogsViewModel 0% 0%
Daqifi.Desktop.ViewModels.DeviceSettingsViewModel 0% 0%
Daqifi.Desktop.ViewModels.ErrorDialogViewModel 0%
Daqifi.Desktop.ViewModels.ExportDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.FirmwareDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.SelectColorDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.SettingsViewModel 0%
Daqifi.Desktop.ViewModels.SuccessDialogViewModel 0%
Daqifi.Desktop.WindowViewModelMapping.IWindowViewModelMappingsContract 0%
Daqifi.Desktop.WindowViewModelMapping.WindowViewModelMappings 0%
Daqifi.Desktop.Bootloader - 20%
Name Line Branch
Daqifi.Desktop.Bootloader 20% 15.7%
Daqifi.Desktop.Bootloader.Crc16 100% 100%
Daqifi.Desktop.Bootloader.Exceptions.FirmwareUpdateException 0%
Daqifi.Desktop.Bootloader.FirmwareDownloader 82% 66.6%
Daqifi.Desktop.Bootloader.Pic32Bootloader 0% 0%
Daqifi.Desktop.Bootloader.Pic32BootloaderMessageConsumer 0% 0%
Daqifi.Desktop.Bootloader.Pic32BootloaderMessageProducer 80.9% 100%
Daqifi.Desktop.Bootloader.WifiFirmwareDownloader 0% 0%
Daqifi.Desktop.Bootloader.WifiModuleUpdater 0% 0%
Daqifi.Desktop.Common - 62.9%
Name Line Branch
Daqifi.Desktop.Common 62.9% 38.8%
Daqifi.Desktop.Common.Loggers.AppLogger 63.7% 38.8%
Daqifi.Desktop.Common.Loggers.NoOpLogger 50%
Daqifi.Desktop.DataModel - 0%
Name Line Branch
Daqifi.Desktop.DataModel 0% ****
Daqifi.Desktop.DataModel.Device.DeviceInfo 0%
Daqifi.Desktop.IO - 24.1%
Name Line Branch
Daqifi.Desktop.IO 24.1% 18.9%
Daqifi.Desktop.IO.Messages.Consumers.AbstractMessageConsumer 0% 0%
Daqifi.Desktop.IO.Messages.Consumers.MessageConsumer 0% 0%
Daqifi.Desktop.IO.Messages.Consumers.TextMessageConsumer 0% 0%
Daqifi.Desktop.IO.Messages.Decoders.ProtobufDecoder 100% 75%
Daqifi.Desktop.IO.Messages.MessageEventArgs`1 0%
Daqifi.Desktop.IO.Messages.Producers.MessageProducer 81% 80%

Coverage report generated by ReportGeneratorView full report in build artifacts

@cptkoolbeenz
Copy link
Member Author

Fixed feature regression from merge(s). Tested on multiple NICs.

@cptkoolbeenz cptkoolbeenz requested a review from tylerkron August 25, 2025 04:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants