Files
voice-cat/clients/apple/VoiceCat.iOS/IosNetworkPathMonitor.cs
T

57 lines
2.0 KiB
C#
Raw Normal View History

using CoreFoundation;
using Network;
using VoiceCat.Core;
namespace VoiceCat.iOS;
// Watches which interface actually carries traffic so a handover can be acted on the instant it
// happens instead of after the control keepalive proves the old path dead.
//
// The signal must be narrow. Roaming between access points, a lift ride, a minute of bad
// cellular: those keep the same interface and the same source address, so TCP survives them and
// a reconnect would be pure damage. Only the set of satisfied interfaces changing — Wi-Fi to
// cellular, one physical link to another — strands the existing sockets on a source address that
// no longer exists, and that is the only thing reported here.
internal sealed class IosNetworkPathMonitor : IDisposable
{
internal static IosNetworkPathMonitor Shared { get; } = new();
private readonly DispatchQueue queue = new("voicecat.path");
private readonly object gate = new();
private readonly ControlPathWatcher watcher = new();
private NWPathMonitor? monitor;
private bool started;
// Raised on the monitor queue when the carrying interface changed and the new path is usable.
internal event Action<string>? InterfaceChanged;
internal void Start()
{
lock (gate)
{
if (started) return;
started = true;
monitor = new NWPathMonitor();
monitor.SetQueue(queue);
monitor.SnapshotHandler = OnPath;
monitor.Start();
}
}
private void OnPath(NWPath path)
{
List<string> interfaces = [];
if (path.Status == NWPathStatus.Satisfied)
path.EnumerateInterfaces(item => { interfaces.Add($"{item.InterfaceType}:{item.Name}"); return true; });
if (watcher.Observe(path.Status == NWPathStatus.Satisfied, interfaces))
InterfaceChanged?.Invoke(string.Join(",", interfaces));
}
public void Dispose()
{
lock (gate)
{
monitor?.Cancel(); monitor?.Dispose(); monitor = null; started = false; watcher.Reset();
}
}
}