diff --git a/app/src/commonMain/kotlin/com/aerotoss/discovery/TcpDiscovery.kt b/app/src/commonMain/kotlin/com/aerotoss/discovery/TcpDiscovery.kt index a770627..5c9034e 100644 --- a/app/src/commonMain/kotlin/com/aerotoss/discovery/TcpDiscovery.kt +++ b/app/src/commonMain/kotlin/com/aerotoss/discovery/TcpDiscovery.kt @@ -23,6 +23,7 @@ class TcpDiscovery : DiscoveryManager { override val devices: Flow> = _devices.asStateFlow() private var serverSocket: ServerSocket? = null + private var udpSocket: DatagramSocket? = null private var serverThread: Thread? = null private var discoveryThread: Thread? = null private val deviceId = generateDeviceId() @@ -33,41 +34,34 @@ class TcpDiscovery : DiscoveryManager { override fun startDiscovery(servicePort: Int) { if (serverThread != null) return - // Start TCP server for incoming discovery requests + // TCP server for incoming discovery requests serverThread = Thread { try { val ss = ServerSocket(TCP_PORT, 50, InetAddress.getByName("0.0.0.0")) serverSocket = ss - println("AeroToss-TCP: Server listening on port $TCP_PORT") + println("AeroToss: TCP server on port $TCP_PORT, service on $servicePort") while (!Thread.currentThread().isInterrupted) { try { val client = ss.accept() - Thread { - handleDiscoveryRequest(client, servicePort) - }.start() + Thread { handleDiscoveryRequest(client, servicePort) }.start() } catch (_: Exception) { if (Thread.currentThread().isInterrupted) break } } ss.close() } catch (e: Exception) { - if (!Thread.currentThread().isInterrupted) { - println("AeroToss-TCP: Server error: ${e.message}") - } + if (!Thread.currentThread().isInterrupted) println("AeroToss: Server error: ${e.message}") } - }.apply { - isDaemon = true - name = "aerotoss-tcp-server" - start() - } + }.apply { isDaemon = true; name = "aerotoss-server"; start() } - // Start UDP broadcast sender + // UDP broadcast: send AND receive on same socket discoveryThread = Thread { try { - val socket = DatagramSocket() + val socket = DatagramSocket(UDP_PORT, InetAddress.getByName("0.0.0.0")) socket.broadcast = true socket.soTimeout = 2000 + udpSocket = socket var lastBroadcast = System.currentTimeMillis() @@ -78,7 +72,6 @@ class TcpDiscovery : DiscoveryManager { lastBroadcast = now } - // Listen for broadcasts from other devices val buf = ByteArray(1024) try { val packet = DatagramPacket(buf, buf.size) @@ -88,15 +81,9 @@ class TcpDiscovery : DiscoveryManager { } socket.close() } catch (e: Exception) { - if (!Thread.currentThread().isInterrupted) { - println("AeroToss-TCP: Broadcast error: ${e.message}") - } + if (!Thread.currentThread().isInterrupted) println("AeroToss: UDP error: ${e.message}") } - }.apply { - isDaemon = true - name = "aerotoss-tcp-broadcast" - start() - } + }.apply { isDaemon = true; name = "aerotoss-broadcast"; start() } } private fun handleDiscoveryRequest(client: Socket, servicePort: Int) { @@ -123,30 +110,11 @@ class TcpDiscovery : DiscoveryManager { } catch (_: Exception) {} } - private fun findNetworkAddress(): String { - return try { - NetworkInterface.getNetworkInterfaces()?.toList() - ?.filter { it.isUp && !it.isLoopback } - ?.flatMap { it.inetAddresses.toList() } - ?.filterIsInstance() - ?.firstOrNull { isPrivate(it) } - ?.hostAddress ?: "127.0.0.1" - } catch (_: Exception) { "127.0.0.1" } - } - - private fun isPrivate(addr: java.net.InetAddress): Boolean { - val bytes = addr.address - return (bytes[0].toInt() and 0xFF == 192 && bytes[1].toInt() and 0xFF == 168) || - (bytes[0].toInt() and 0xFF == 10) || - (bytes[0].toInt() and 0xFF == 172 && (bytes[1].toInt() and 0xFF in 16..31)) - } - private fun sendBroadcast(socket: DatagramSocket, servicePort: Int) { try { val message = "AEROTOSS:$TCP_PORT:$servicePort:$deviceId:$deviceName" val data = message.toByteArray(Charsets.UTF_8) - val broadcastAddr = InetAddress.getByName("255.255.255.255") - val packet = DatagramPacket(data, data.size, broadcastAddr, UDP_PORT) + val packet = DatagramPacket(data, data.size, InetAddress.getByName("255.255.255.255"), UDP_PORT) socket.send(packet) } catch (_: Exception) {} } @@ -167,14 +135,13 @@ class TcpDiscovery : DiscoveryManager { if (remoteId == deviceId) return if (remoteServicePort == myPort) return - // Connect to remote device to get full info val hostAddress = packet.address.hostAddress ?: return val device = queryDevice(hostAddress, remoteTcpPort, remoteId, remoteName, remoteServicePort) if (device != null) { discoveredDevices[device.id] = device _devices.value = discoveredDevices.values.toList() - println("AeroToss-TCP: Found ${device.name} at ${device.hostAddress}:${device.port}") + println("AeroToss: Found ${device.name} at ${device.hostAddress}:${device.port}") } } catch (_: Exception) {} } @@ -204,7 +171,6 @@ class TcpDiscovery : DiscoveryManager { ) } } catch (_: Exception) { - // Fallback to broadcast info Device( id = fallbackId, name = fallbackName, @@ -215,13 +181,33 @@ class TcpDiscovery : DiscoveryManager { } } + private fun findNetworkAddress(): String { + return try { + NetworkInterface.getNetworkInterfaces()?.toList() + ?.filter { it.isUp && !it.isLoopback } + ?.flatMap { it.inetAddresses.toList() } + ?.filterIsInstance() + ?.firstOrNull { isPrivate(it) } + ?.hostAddress ?: "127.0.0.1" + } catch (_: Exception) { "127.0.0.1" } + } + + private fun isPrivate(addr: java.net.InetAddress): Boolean { + val bytes = addr.address + return (bytes[0].toInt() and 0xFF == 192 && bytes[1].toInt() and 0xFF == 168) || + (bytes[0].toInt() and 0xFF == 10) || + (bytes[0].toInt() and 0xFF == 172 && (bytes[1].toInt() and 0xFF in 16..31)) + } + override fun stopDiscovery() { serverThread?.interrupt() serverThread = null discoveryThread?.interrupt() discoveryThread = null try { serverSocket?.close() } catch (_: Exception) {} + try { udpSocket?.close() } catch (_: Exception) {} serverSocket = null + udpSocket = null discoveredDevices.clear() _devices.value = emptyList() }