-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Create NsdManager proxy service for android with stub JVM * Adjust code style * Remove code sensing
- Loading branch information
Showing
8 changed files
with
301 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
plugins { | ||
id("io.ashdavies.default") | ||
} | ||
|
||
android { | ||
namespace = "io.ashdavies.nsd" | ||
} | ||
|
||
kotlin { | ||
commonMain.dependencies { | ||
implementation(projects.platformSupport) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest /> |
152 changes: 152 additions & 0 deletions
152
nsd-manager/src/androidMain/kotlin/io/ashdavies/nsd/NsdManager.android.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package io.ashdavies.nsd | ||
|
||
import android.os.Build | ||
import androidx.annotation.RequiresApi | ||
import kotlinx.coroutines.CoroutineDispatcher | ||
import kotlinx.coroutines.asExecutor | ||
import kotlinx.coroutines.cancel | ||
import kotlinx.coroutines.channels.awaitClose | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.callbackFlow | ||
import kotlinx.coroutines.flow.channelFlow | ||
import kotlinx.coroutines.flow.flow | ||
import kotlinx.coroutines.suspendCancellableCoroutine | ||
import kotlinx.coroutines.withContext | ||
import java.net.Inet4Address | ||
import java.net.Inet6Address | ||
import kotlin.coroutines.resumeWithException | ||
import android.net.nsd.NsdManager as AndroidNsdManager | ||
import android.net.nsd.NsdServiceInfo as AndroidNsdServiceInfo | ||
|
||
public actual typealias NsdManager = AndroidNsdManager | ||
|
||
public actual typealias NsdServiceInfo = AndroidNsdServiceInfo | ||
|
||
public actual fun NsdServiceInfo.getHostAddressOrNull( | ||
type: NsdHostAddress.Type, | ||
): NsdHostAddress? { | ||
val hostAddressList = when { | ||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> hostAddresses | ||
else -> { | ||
@Suppress("DEPRECATION") | ||
listOf(host) | ||
} | ||
} | ||
|
||
return hostAddressList | ||
.firstOrNull { | ||
when (type) { | ||
NsdHostAddress.Type.IPv4 -> it is Inet4Address | ||
NsdHostAddress.Type.IPv6 -> it is Inet6Address | ||
} | ||
} | ||
?.hostAddress | ||
?.let { NsdHostAddress(it, type) } | ||
} | ||
|
||
public actual fun NsdManager.discoverServices( | ||
serviceType: String, | ||
protocolType: Int, | ||
): Flow<List<NsdServiceInfo>> = channelFlow { | ||
val servicesFound = mutableListOf<NsdServiceInfo>() | ||
val listener = object : AndroidNsdManager.DiscoveryListener { | ||
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) { | ||
cancel("Service start discovery failed ($errorCode)") | ||
} | ||
|
||
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) { | ||
cancel("Service stop discovery failed ($errorCode)") | ||
} | ||
|
||
override fun onDiscoveryStarted(serviceType: String) = Unit | ||
|
||
override fun onDiscoveryStopped(serviceType: String) { | ||
channel.close() | ||
} | ||
|
||
override fun onServiceFound(serviceInfo: NsdServiceInfo) { | ||
servicesFound += serviceInfo | ||
trySend(servicesFound) | ||
} | ||
|
||
override fun onServiceLost(serviceInfo: NsdServiceInfo) { | ||
servicesFound -= serviceInfo | ||
trySend(servicesFound) | ||
} | ||
} | ||
|
||
discoverServices( | ||
/* serviceType = */ serviceType, | ||
/* protocolType = */ protocolType, | ||
/* listener = */ listener, | ||
) | ||
|
||
awaitClose { | ||
stopServiceDiscovery(listener) | ||
} | ||
} | ||
|
||
public actual fun NsdManager.resolveService( | ||
serviceInfo: NsdServiceInfo, | ||
coroutineDispatcher: CoroutineDispatcher, | ||
): Flow<NsdServiceInfo> { | ||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { | ||
return resolveServiceApi34(serviceInfo, coroutineDispatcher) | ||
} | ||
|
||
return flow { | ||
withContext(coroutineDispatcher) { | ||
emit(resolveServiceApi16(serviceInfo)) | ||
} | ||
} | ||
} | ||
|
||
private suspend fun NsdManager.resolveServiceApi16( | ||
serviceInfo: NsdServiceInfo, | ||
): NsdServiceInfo = suspendCancellableCoroutine { continuation -> | ||
val listener = object : AndroidNsdManager.ResolveListener { | ||
override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) { | ||
continuation.resumeWithException(IllegalStateException("Service resolution failed with an error ($errorCode)")) | ||
} | ||
|
||
override fun onServiceResolved(serviceInfo: NsdServiceInfo) { | ||
continuation.resumeWith(Result.success(serviceInfo)) | ||
} | ||
} | ||
|
||
@Suppress("DEPRECATION") | ||
resolveService( | ||
/* serviceInfo = */ serviceInfo, | ||
/* listener = */ listener, | ||
) | ||
} | ||
|
||
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) | ||
private fun NsdManager.resolveServiceApi34( | ||
serviceInfo: NsdServiceInfo, | ||
coroutineDispatcher: CoroutineDispatcher, | ||
): Flow<NsdServiceInfo> = callbackFlow { | ||
val callback = object : AndroidNsdManager.ServiceInfoCallback { | ||
override fun onServiceInfoCallbackRegistrationFailed(errorCode: Int) { | ||
cancel("Service info callback registration failed ($errorCode)") | ||
} | ||
|
||
override fun onServiceUpdated(serviceInfo: NsdServiceInfo) { | ||
trySend(serviceInfo) | ||
} | ||
|
||
override fun onServiceLost() = Unit | ||
|
||
override fun onServiceInfoCallbackUnregistered() { | ||
channel.close() | ||
} | ||
} | ||
|
||
registerServiceInfoCallback( | ||
/* serviceInfo = */ serviceInfo, | ||
/* executor = */ coroutineDispatcher.asExecutor(), | ||
/* listener = */ callback, | ||
) | ||
|
||
awaitClose { unregisterServiceInfoCallback(callback) } | ||
} |
62 changes: 62 additions & 0 deletions
62
nsd-manager/src/commonMain/kotlin/io/ashdavies/nsd/NsdAgent.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package io.ashdavies.nsd | ||
|
||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.channelFlow | ||
import kotlinx.coroutines.flow.flatMapLatest | ||
import kotlinx.coroutines.flow.flatMapMerge | ||
import kotlinx.coroutines.flow.flowOf | ||
|
||
private const val HTTP_TCP = "_http._tcp" | ||
private const val PROTOCOL_DNS_SD = 1 | ||
|
||
public fun interface NsdAgent { | ||
public fun resolve(serviceName: String): Flow<NsdState> | ||
} | ||
|
||
public sealed interface NsdState { | ||
|
||
public data object Discovering : NsdState | ||
|
||
public data class Discovered( | ||
val services: List<String>, | ||
) : NsdState | ||
|
||
public data class Resolved( | ||
val serviceName: String, | ||
val hostAddress: String, | ||
val port: Int, | ||
) : NsdState | ||
} | ||
|
||
public fun NsdAgent(manager: NsdManager): NsdAgent = NsdAgent { serviceName -> | ||
channelFlow { | ||
send(NsdState.Discovering) | ||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
manager | ||
.discoverServices(HTTP_TCP, PROTOCOL_DNS_SD) | ||
.flatMapLatest { serviceList -> | ||
val serviceInfo = serviceList.filter { it.getServiceName() == serviceName } | ||
val serviceNames = serviceInfo.map { it.getServiceName() } | ||
|
||
send(NsdState.Discovered(serviceNames)) | ||
flowOf(*serviceInfo.toTypedArray()) | ||
} | ||
.flatMapMerge { serviceInfo -> | ||
manager.resolveService(serviceInfo) | ||
} | ||
.collect { serviceInfo -> | ||
val hostAddress = serviceInfo.getHostAddressOrNull() | ||
if (hostAddress != null) { | ||
val resolved = NsdState.Resolved( | ||
serviceName = serviceInfo.getServiceName(), | ||
hostAddress = hostAddress.value, | ||
port = serviceInfo.getPort(), | ||
) | ||
|
||
send(resolved) | ||
} | ||
} | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
nsd-manager/src/commonMain/kotlin/io/ashdavies/nsd/NsdManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package io.ashdavies.nsd | ||
|
||
import kotlinx.coroutines.CoroutineDispatcher | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.flow.Flow | ||
|
||
public data class NsdHostAddress( | ||
public val value: String, | ||
public val type: Type, | ||
) { | ||
public enum class Type { | ||
IPv4, | ||
IPv6, | ||
} | ||
} | ||
|
||
public expect class NsdManager | ||
|
||
public expect class NsdServiceInfo { | ||
public fun getServiceName(): String | ||
public fun getPort(): Int | ||
} | ||
|
||
public expect fun NsdServiceInfo.getHostAddressOrNull( | ||
type: NsdHostAddress.Type = NsdHostAddress.Type.IPv4, | ||
): NsdHostAddress? | ||
|
||
public expect fun NsdManager.discoverServices( | ||
serviceType: String, | ||
protocolType: Int, | ||
): Flow<List<NsdServiceInfo>> | ||
|
||
public expect fun NsdManager.resolveService( | ||
serviceInfo: NsdServiceInfo, | ||
coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO, | ||
): Flow<NsdServiceInfo> |
34 changes: 34 additions & 0 deletions
34
nsd-manager/src/jvmMain/kotlin/io/ashdavies/nsd/NsdManager.jvm.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package io.ashdavies.nsd | ||
|
||
import kotlinx.coroutines.CoroutineDispatcher | ||
import kotlinx.coroutines.flow.Flow | ||
|
||
public actual class NsdManager | ||
|
||
public actual class NsdServiceInfo { | ||
public actual fun getServiceName(): String { | ||
unsupportedOperation() | ||
} | ||
|
||
public actual fun getPort(): Int { | ||
unsupportedOperation() | ||
} | ||
} | ||
|
||
public actual fun NsdServiceInfo.getHostAddressOrNull( | ||
type: NsdHostAddress.Type, | ||
): NsdHostAddress? = unsupportedOperation() | ||
|
||
public actual fun NsdManager.discoverServices( | ||
serviceType: String, | ||
protocolType: Int, | ||
): Flow<List<NsdServiceInfo>> = unsupportedOperation() | ||
|
||
public actual fun NsdManager.resolveService( | ||
serviceInfo: NsdServiceInfo, | ||
coroutineDispatcher: CoroutineDispatcher, | ||
): Flow<NsdServiceInfo> = unsupportedOperation() | ||
|
||
private fun unsupportedOperation(): Nothing { | ||
error("UnsupportedOperation") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters