diff --git a/CONTEXT.md b/CONTEXT.md index 75b482c..3af5d86 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -10,6 +10,8 @@ A requested change of media, playback state, position, or quality that may still **Cast playback transition**: A playback transition that transfers playback between this Mac and a Cast receiver. Connection alone does not transfer playback; the destination must acknowledge the handoff, and interruption must preserve the latest playback intent and acknowledged position. +Discovery supplies receiver addresses to both Network.framework and the native media relay's POSIX resolver. IPv4 addresses must omit interface scope suffixes; IPv6 addresses retain their scopes. + **Playback intent**: The user's latest requested playing or paused state, which can differ from the state playback has acknowledged. diff --git a/Checks/cast-session.swift b/Checks/cast-session.swift index 4c1bcce..ea12b4c 100644 --- a/Checks/cast-session.swift +++ b/Checks/cast-session.swift @@ -412,6 +412,16 @@ struct CastSessionCheck { let fallbackID = discovery.devices.first { $0.name == prefix + "-fallback" }!.id let device = ownDevices()[0] require(device.port == second.port?.rawValue, "Discovery preserves the advertised Cast port") + for device in discovery.devices { + guard case .ipv4 = NWEndpoint.Host(device.host) else { continue } + var hints = addrinfo() + hints.ai_family = AF_INET + hints.ai_socktype = SOCK_DGRAM + var addresses: UnsafeMutablePointer? + let result = getaddrinfo(device.host, "8009", &hints, &addresses) + if let addresses { freeaddrinfo(addresses) } + require(result == 0, "Discovered IPv4 receiver resolves through the native relay's POSIX API") + } let connection = NWConnection(host: NWEndpoint.Host(device.host), port: NWEndpoint.Port(rawValue: device.port)!, using: .tcp) defer { connection.cancel() } diff --git a/Sources/App/CastSession.swift b/Sources/App/CastSession.swift index 359572f..8e0ff27 100644 --- a/Sources/App/CastSession.swift +++ b/Sources/App/CastSession.swift @@ -115,9 +115,17 @@ final class CastDiscovery: ObservableObject { let value = String(data: data, encoding: .utf8), !value.isEmpty else { return nil } return value } + // Network.framework scopes IPv4 to an interface; POSIX getaddrinfo + // rejects that suffix. IPv6 scope identifiers must remain intact. + let address: String + if case .ipv4 = host { + address = String(host.debugDescription.prefix { $0 != "%" }) + } else { + address = host.debugDescription + } resolved[endpoint] = CastDevice(id: text("id") ?? domain + type + name, name: text("fn") ?? name, - host: host.debugDescription, port: port.rawValue) + host: address, port: port.rawValue) } services[endpoint]?.timeout?.cancel() services[endpoint]?.timeout = nil