From 472035b56d93041b386ae70a9e9c52f862a9e097 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 1 Oct 2026 10:22:12 +0200 Subject: [PATCH 1/3] [Network] Correct Ethernet channel state callback type Use the Ethernet channel state enum in the public callback signature so the block target matches the native trampoline. Add macOS coverage that starts a channel on a wired interface and checks that a native state callback delivers an Ethernet channel state when the custom-protocol entitlement is available. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/Network/NWEthernetChannel.cs | 18 +++++ .../Network/NWEthernetChannelTest.cs | 70 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 tests/monotouch-test/Network/NWEthernetChannelTest.cs diff --git a/src/Network/NWEthernetChannel.cs b/src/Network/NWEthernetChannel.cs index b99c63b04be7..671b019fe8b5 100644 --- a/src/Network/NWEthernetChannel.cs +++ b/src/Network/NWEthernetChannel.cs @@ -166,8 +166,26 @@ static void TrampolineStateChangesHandler (IntPtr block, NWEthernetChannelState } } +#if !XAMCORE_5_0 [BindingImpl (BindingImplOptions.Optimizable)] + [EditorBrowsable (EditorBrowsableState.Never)] + [Obsolete ("Use the other overload.")] + [OverloadResolutionPriority (-1)] public void SetStateChangesHandler (Action handler) + { + if (handler is null) { + unsafe { + nw_ethernet_channel_set_state_changed_handler (GetCheckedHandle (), null); + } + return; + } + + SetStateChangesHandler ((NWEthernetChannelState state, NWError? error) => handler ((NWBrowserState) state, error)); + } +#endif + + [BindingImpl (BindingImplOptions.Optimizable)] + public void SetStateChangesHandler (Action handler) { unsafe { if (handler is null) { diff --git a/tests/monotouch-test/Network/NWEthernetChannelTest.cs b/tests/monotouch-test/Network/NWEthernetChannelTest.cs new file mode 100644 index 000000000000..6596a1849880 --- /dev/null +++ b/tests/monotouch-test/Network/NWEthernetChannelTest.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#if MONOMAC +using System.Threading; + +using Network; + +namespace MonoTouchFixtures.Network { + [TestFixture] + [Preserve (AllMembers = true)] + public class NWEthernetChannelTest { + [TestCase (false)] + [TestCase (true)] + public void StateChangesHandler (bool useLegacyHandler) + { + TestRuntime.AssertXcodeVersion (11, 0); + TestRuntime.IgnoreInCI ("This test requires a wired interface and the custom-protocol entitlement."); + + using var monitor = new NWPathMonitor (); + using var pathUpdated = new AutoResetEvent (false); + NWPath? path = null; + monitor.SetQueue (DispatchQueue.DefaultGlobalQueue); + monitor.SnapshotHandler = currentPath => { + path = currentPath; + pathUpdated.Set (); + }; + monitor.Start (); + Assert.That (pathUpdated.WaitOne (30000), Is.True, "Path update"); + monitor.Cancel (); + Assert.That (path, Is.Not.Null, "Current path"); + + NWInterface? ethernetInterface = null; + path.EnumerateInterfaces (networkInterface => { + if (networkInterface.InterfaceType == NWInterfaceType.Wired) + ethernetInterface = networkInterface; + return true; + }); + if (ethernetInterface is null) + Assert.Ignore ("This test requires a wired interface."); + + using var channel = new NWEthernetChannel (0x88b5, ethernetInterface); + using var stateChanged = new AutoResetEvent (false); + NWEthernetChannelState state = NWEthernetChannelState.Invalid; + channel.SetQueue (DispatchQueue.DefaultGlobalQueue); + if (useLegacyHandler) { + Action legacyHandler = (currentState, error) => { + state = (NWEthernetChannelState) currentState; + stateChanged.Set (); + }; + channel.SetStateChangesHandler (legacyHandler); + } else { + channel.SetStateChangesHandler ((currentState, error) => { + state = currentState; + stateChanged.Set (); + }); + } + channel.Start (); + try { + Assert.That (stateChanged.WaitOne (30000), Is.True, "State callback"); + Assert.That (state, Is.AnyOf ( + NWEthernetChannelState.Waiting, NWEthernetChannelState.Preparing, NWEthernetChannelState.Ready, + NWEthernetChannelState.Failed, NWEthernetChannelState.Cancelled), "Ethernet channel state"); + } finally { + channel.Cancel (); + } + } + } +} +#endif From 454629187916bf9543dd3ab54235f900f9e9e122 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 14 Oct 2026 17:37:53 +0200 Subject: [PATCH 2/3] [Network] Document the Ethernet channel state handler Describe when the correctly typed state-change callback runs, the Ethernet state and optional error it receives, and how passing null clears the handler. Keep the deprecated compatibility overload and all behavior unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/Network/NWEthernetChannel.cs | 4 +++- tests/cecil-tests/Documentation.KnownFailures.txt | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Network/NWEthernetChannel.cs b/src/Network/NWEthernetChannel.cs index 671b019fe8b5..a097b8d7460d 100644 --- a/src/Network/NWEthernetChannel.cs +++ b/src/Network/NWEthernetChannel.cs @@ -184,8 +184,10 @@ public void SetStateChangesHandler (Action handler) } #endif + /// Sets the callback invoked when the Ethernet channel state changes. + /// The callback receives the new and an associated , if any. Pass to remove the callback. [BindingImpl (BindingImplOptions.Optimizable)] - public void SetStateChangesHandler (Action handler) + public void SetStateChangesHandler (Action? handler) { unsafe { if (handler is null) { diff --git a/tests/cecil-tests/Documentation.KnownFailures.txt b/tests/cecil-tests/Documentation.KnownFailures.txt index 21849ae1724a..79aa2efdb44b 100644 --- a/tests/cecil-tests/Documentation.KnownFailures.txt +++ b/tests/cecil-tests/Documentation.KnownFailures.txt @@ -22644,7 +22644,6 @@ M:Network.NWEthernetChannel.Cancel M:Network.NWEthernetChannel.Send(System.ReadOnlySpan{System.Byte},System.UInt16,System.String,System.Action{Network.NWError}) M:Network.NWEthernetChannel.SetQueue(CoreFoundation.DispatchQueue) M:Network.NWEthernetChannel.SetReceiveHandler(Network.NWEthernetChannelReceiveDelegate) -M:Network.NWEthernetChannel.SetStateChangesHandler(System.Action{Network.NWBrowserState,Network.NWError}) M:Network.NWEthernetChannel.Start M:Network.NWFramer.CreateMessage M:Network.NWFramer.CreateOptions``1(Network.NWProtocolDefinition) From 5c694a91d3ac9a7275b31170f10d0d6eeed73d20 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 14 Oct 2026 17:47:17 +0200 Subject: [PATCH 3/3] [Network] Make Ethernet callback test teardown safe Use task completion sources for path and state callbacks so asynchronous notifications cannot signal disposed events during teardown. Capture only the first callback result and cancel the path monitor even when its wait fails. Include the component-model namespace required by the legacy overload visibility attribute. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/Network/NWEthernetChannel.cs | 1 + .../Network/NWEthernetChannelTest.cs | 29 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Network/NWEthernetChannel.cs b/src/Network/NWEthernetChannel.cs index a097b8d7460d..f88cc1fb7617 100644 --- a/src/Network/NWEthernetChannel.cs +++ b/src/Network/NWEthernetChannel.cs @@ -10,6 +10,7 @@ #nullable enable #if MONOMAC +using System.ComponentModel; using System.Text; using System.Runtime.CompilerServices; using CoreFoundation; diff --git a/tests/monotouch-test/Network/NWEthernetChannelTest.cs b/tests/monotouch-test/Network/NWEthernetChannelTest.cs index 6596a1849880..790d3e7576ea 100644 --- a/tests/monotouch-test/Network/NWEthernetChannelTest.cs +++ b/tests/monotouch-test/Network/NWEthernetChannelTest.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. #if MONOMAC -using System.Threading; +using System.Threading.Tasks; using Network; @@ -18,16 +18,18 @@ public void StateChangesHandler (bool useLegacyHandler) TestRuntime.IgnoreInCI ("This test requires a wired interface and the custom-protocol entitlement."); using var monitor = new NWPathMonitor (); - using var pathUpdated = new AutoResetEvent (false); - NWPath? path = null; + var pathUpdated = new TaskCompletionSource (); monitor.SetQueue (DispatchQueue.DefaultGlobalQueue); monitor.SnapshotHandler = currentPath => { - path = currentPath; - pathUpdated.Set (); + pathUpdated.TrySetResult (currentPath); }; monitor.Start (); - Assert.That (pathUpdated.WaitOne (30000), Is.True, "Path update"); - monitor.Cancel (); + try { + Assert.That (pathUpdated.Task.Wait (30000), Is.True, "Path update"); + } finally { + monitor.Cancel (); + } + var path = pathUpdated.Task.Result; Assert.That (path, Is.Not.Null, "Current path"); NWInterface? ethernetInterface = null; @@ -40,25 +42,22 @@ public void StateChangesHandler (bool useLegacyHandler) Assert.Ignore ("This test requires a wired interface."); using var channel = new NWEthernetChannel (0x88b5, ethernetInterface); - using var stateChanged = new AutoResetEvent (false); - NWEthernetChannelState state = NWEthernetChannelState.Invalid; + var stateChanged = new TaskCompletionSource (); channel.SetQueue (DispatchQueue.DefaultGlobalQueue); if (useLegacyHandler) { Action legacyHandler = (currentState, error) => { - state = (NWEthernetChannelState) currentState; - stateChanged.Set (); + stateChanged.TrySetResult ((NWEthernetChannelState) currentState); }; channel.SetStateChangesHandler (legacyHandler); } else { channel.SetStateChangesHandler ((currentState, error) => { - state = currentState; - stateChanged.Set (); + stateChanged.TrySetResult (currentState); }); } channel.Start (); try { - Assert.That (stateChanged.WaitOne (30000), Is.True, "State callback"); - Assert.That (state, Is.AnyOf ( + Assert.That (stateChanged.Task.Wait (30000), Is.True, "State callback"); + Assert.That (stateChanged.Task.Result, Is.AnyOf ( NWEthernetChannelState.Waiting, NWEthernetChannelState.Preparing, NWEthernetChannelState.Ready, NWEthernetChannelState.Failed, NWEthernetChannelState.Cancelled), "Ethernet channel state"); } finally {