diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index a4470a0..94d3ced 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -37,8 +37,8 @@ jobs: echo sonatypeUrl=${SONATYPE_URL} >> ~/.gradle/gradle.properties echo sonatypeUsername=${SONATYPE_USERNAME} >> ~/.gradle/gradle.properties cat ~/.gradle/gradle.properties - - name: Run lint checks on library module - run: ./gradlew :lighthouse:ktlintCheck + - name: Run lint checks on entire project + run: ./gradlew ktfmtCheck - name: Unit test the library module run: ./gradlew :lighthouse:test - name: Build release version of library diff --git a/build.gradle.kts b/build.gradle.kts index 8293f75..db5d7bc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,26 +1,10 @@ plugins { - alias(libs.plugins.ktlint) + alias(libs.plugins.ktfmt) } subprojects { - apply(plugin = "org.jlleitschuh.gradle.ktlint") - - configure { - version.set("0.50.0") - debug.set(false) - verbose.set(true) - android.set(true) - outputToConsole.set(true) - outputColorName.set("GREEN") - ignoreFailures.set(false) - enableExperimentalRules.set(false) - disabledRules.set(setOf("max-line-length")) - kotlinScriptAdditionalPaths { - include(fileTree("scripts/")) - } - filter { - exclude("**/generated/**") - include("**/kotlin/**") - } + apply(plugin = "com.ncorti.ktfmt.gradle") + ktfmt { + kotlinLangStyle() } -} +} \ No newline at end of file diff --git a/demo/src/main/java/com/ivanempire/lighthouse/demo/MainActivity.kt b/demo/src/main/java/com/ivanempire/lighthouse/demo/MainActivity.kt index 234f09e..2854ed9 100644 --- a/demo/src/main/java/com/ivanempire/lighthouse/demo/MainActivity.kt +++ b/demo/src/main/java/com/ivanempire/lighthouse/demo/MainActivity.kt @@ -37,30 +37,28 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) // Example implementation of a custom logging system - val customLogger = object : LighthouseLogger() { - override fun logStateMessage(tag: String, message: String) { - Log.d(tag, message) - } + val customLogger = + object : LighthouseLogger() { + override fun logStateMessage(tag: String, message: String) { + Log.d(tag, message) + } - override fun logStatusMessage(tag: String, message: String) { - Log.d(tag, message) - } + override fun logStatusMessage(tag: String, message: String) { + Log.d(tag, message) + } - override fun logPacketMessage(tag: String, message: String) { - Log.d(tag, message) - } + override fun logPacketMessage(tag: String, message: String) { + Log.d(tag, message) + } - override fun logErrorMessage(tag: String, message: String, ex: Throwable?) { - Log.e(tag, message, ex) + override fun logErrorMessage(tag: String, message: String, ex: Throwable?) { + Log.e(tag, message, ex) + } } - } // Setup the client - lighthouseClient = LighthouseClient - .Builder(this) - .setLogger(customLogger) - .setRetryCount(2) - .build() + lighthouseClient = + LighthouseClient.Builder(this).setLogger(customLogger).setRetryCount(2).build() // Skips the need for Dagger in a simple demo app val viewModelFactory = MainActivityViewModelFactory(lighthouseClient) @@ -71,9 +69,7 @@ class MainActivity : ComponentActivity() { Column(modifier = Modifier.padding(16.dp)) { LazyColumn( - modifier = Modifier - .weight(1f) - .fillMaxWidth(), + modifier = Modifier.weight(1f).fillMaxWidth(), ) { items( items = discoveredDeviceList.value, @@ -85,17 +81,26 @@ class MainActivity : ComponentActivity() { val isDiscoveryRunning = remember { mutableStateOf(false) } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { - Button(onClick = { - viewModel.stopDiscovery() - isDiscoveryRunning.value = false - }, enabled = !isDiscoveryRunning.value) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + Button( + onClick = { + viewModel.stopDiscovery() + isDiscoveryRunning.value = false + }, + enabled = !isDiscoveryRunning.value + ) { Text(text = "Stop discovery") } - Button(onClick = { - viewModel.startDiscovery() - isDiscoveryRunning.value = true - }, enabled = !isDiscoveryRunning.value) { + Button( + onClick = { + viewModel.startDiscovery() + isDiscoveryRunning.value = true + }, + enabled = !isDiscoveryRunning.value + ) { Text(text = "Start discovery") } } diff --git a/demo/src/main/java/com/ivanempire/lighthouse/demo/MainActivityViewModel.kt b/demo/src/main/java/com/ivanempire/lighthouse/demo/MainActivityViewModel.kt index 3edaa36..39edddd 100644 --- a/demo/src/main/java/com/ivanempire/lighthouse/demo/MainActivityViewModel.kt +++ b/demo/src/main/java/com/ivanempire/lighthouse/demo/MainActivityViewModel.kt @@ -21,14 +21,11 @@ class MainActivityViewModel( val discoveredDevices = backingDiscoveredDevices.asStateFlow() fun startDiscovery() { - discoveryJob = viewModelScope.launch { - lighthouseClient.discoverDevices().collect { - backingDiscoveredDevices.value = it + discoveryJob = + viewModelScope.launch { + lighthouseClient.discoverDevices().collect { backingDiscoveredDevices.value = it } } - } } - fun stopDiscovery() = runBlocking { - discoveryJob?.cancelAndJoin() - } + fun stopDiscovery() = runBlocking { discoveryJob?.cancelAndJoin() } } diff --git a/demo/src/main/java/com/ivanempire/lighthouse/demo/MainActivityViewModelFactory.kt b/demo/src/main/java/com/ivanempire/lighthouse/demo/MainActivityViewModelFactory.kt index 8c66d3e..25b7478 100644 --- a/demo/src/main/java/com/ivanempire/lighthouse/demo/MainActivityViewModelFactory.kt +++ b/demo/src/main/java/com/ivanempire/lighthouse/demo/MainActivityViewModelFactory.kt @@ -4,13 +4,15 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.ivanempire.lighthouse.LighthouseClient -class MainActivityViewModelFactory(private val lighthouseClient: LighthouseClient) : ViewModelProvider.Factory { +class MainActivityViewModelFactory(private val lighthouseClient: LighthouseClient) : + ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( modelClass: Class, ): T { return MainActivityViewModel( lighthouseClient, - ) as T + ) + as T } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 375ff31..6a7f0c7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,5 @@ [versions] +ktfmt = "0.17.0" gradle = "8.3.0" maven = "0.26.0" junit = "4.13.2" @@ -32,4 +33,4 @@ mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito-cor testing-junit = { module = "junit:junit", version.ref = "junit" } [plugins] -ktlint = "org.jlleitschuh.gradle.ktlint:12.1.0" \ No newline at end of file +ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmt" } \ No newline at end of file diff --git a/lighthouse/build.gradle.kts b/lighthouse/build.gradle.kts index 10e8b3d..39c5bcb 100644 --- a/lighthouse/build.gradle.kts +++ b/lighthouse/build.gradle.kts @@ -14,4 +14,4 @@ dependencies { testImplementation(libs.mockito.core) testImplementation(libs.testing.junit) testImplementation(libs.kotlinx.coroutines.test) -} +} \ No newline at end of file diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/Extensions.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/Extensions.kt index f255e29..15b2540 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/Extensions.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/Extensions.kt @@ -12,14 +12,20 @@ import com.ivanempire.lighthouse.models.packets.UniqueServiceName * * @param latestComponent The latest ALIVE or BYEBYE packet's parsed [UniqueServiceName] field */ -internal fun AbridgedMediaDevice.updateEmbeddedComponent(latestComponent: UniqueServiceName): AbridgedMediaDevice { +internal fun AbridgedMediaDevice.updateEmbeddedComponent( + latestComponent: UniqueServiceName +): AbridgedMediaDevice { return when (latestComponent) { is EmbeddedDevice -> { - val updatedDeviceList = this.deviceList.filterNot { it.deviceType == latestComponent.deviceType } + latestComponent + val updatedDeviceList = + this.deviceList.filterNot { it.deviceType == latestComponent.deviceType } + + latestComponent this.copy(deviceList = updatedDeviceList) } is EmbeddedService -> { - val updatedServiceList = this.serviceList.filterNot { it.serviceType == latestComponent.serviceType } + latestComponent + val updatedServiceList = + this.serviceList.filterNot { it.serviceType == latestComponent.serviceType } + + latestComponent this.copy(serviceList = updatedServiceList) } else -> this @@ -32,14 +38,19 @@ internal fun AbridgedMediaDevice.updateEmbeddedComponent(latestComponent: Unique * * @param latestComponent The latest BYEBYE packet's parsed [UniqueServiceName] field */ -internal fun AbridgedMediaDevice.removeEmbeddedComponent(latestComponent: UniqueServiceName): AbridgedMediaDevice { +internal fun AbridgedMediaDevice.removeEmbeddedComponent( + latestComponent: UniqueServiceName +): AbridgedMediaDevice { return when (latestComponent) { - is EmbeddedDevice -> this.copy( - deviceList = deviceList.filterNot { it.deviceType == latestComponent.deviceType }, - ) - is EmbeddedService -> this.copy( - serviceList = serviceList.filterNot { it.serviceType == latestComponent.serviceType }, - ) + is EmbeddedDevice -> + this.copy( + deviceList = deviceList.filterNot { it.deviceType == latestComponent.deviceType }, + ) + is EmbeddedService -> + this.copy( + serviceList = + serviceList.filterNot { it.serviceType == latestComponent.serviceType }, + ) else -> this } } diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/LighthouseClient.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/LighthouseClient.kt index cfc3051..fbb7b40 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/LighthouseClient.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/LighthouseClient.kt @@ -9,12 +9,10 @@ import com.ivanempire.lighthouse.models.Constants.DEFAULT_SEARCH_REQUEST import com.ivanempire.lighthouse.models.devices.AbridgedMediaDevice import com.ivanempire.lighthouse.models.search.SearchRequest import com.ivanempire.lighthouse.socket.RealSocketListener -import kotlinx.coroutines.flow.Flow import java.lang.IllegalStateException +import kotlinx.coroutines.flow.Flow -/** - * The main entrypoint for the Lighthouse library - */ +/** The main entrypoint for the Lighthouse library */ interface LighthouseClient { /** Builder class for the Lighthouse configuration */ @@ -24,7 +22,8 @@ interface LighthouseClient { private var logger: LighthouseLogger? = null - private val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + private val wifiManager = + context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager /** * Specify a retry count in the off-chance the network packet is not received by the @@ -33,9 +32,7 @@ interface LighthouseClient { * @param retryCount Number of times to retry sending an SSDP search packet, must be > 0 */ fun setRetryCount(retryCount: Int) = apply { - assert(retryCount > 0) { - IllegalStateException("Retry count must be greater than 0") - } + assert(retryCount > 0) { IllegalStateException("Retry count must be greater than 0") } this.retryCount += retryCount } @@ -44,18 +41,17 @@ interface LighthouseClient { * * @param logger Custom implementation of [LighthouseLogger] */ - fun setLogger(logger: LighthouseLogger) = apply { - this.logger = logger - } + fun setLogger(logger: LighthouseLogger) = apply { this.logger = logger } fun build(): LighthouseClient { val socketListener = RealSocketListener(wifiManager, retryCount, logger) - val discoveryManager = RealDiscoveryManager( - LighthouseState(logger), - socketListener, - logger, - ) + val discoveryManager = + RealDiscoveryManager( + LighthouseState(logger), + socketListener, + logger, + ) return RealLighthouseClient(discoveryManager, logger = logger) } } @@ -67,5 +63,7 @@ interface LighthouseClient { * @param searchRequest The [SearchRequest] to send to the multicast group to discover devices * @return Flow of lists of [AbridgedMediaDevice] that have been discovered on the network */ - suspend fun discoverDevices(searchRequest: SearchRequest = DEFAULT_SEARCH_REQUEST): Flow> + suspend fun discoverDevices( + searchRequest: SearchRequest = DEFAULT_SEARCH_REQUEST + ): Flow> } diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/LighthouseLogger.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/LighthouseLogger.kt index e46c45a..9a9f270 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/LighthouseLogger.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/LighthouseLogger.kt @@ -4,9 +4,7 @@ import com.ivanempire.lighthouse.core.LighthouseState import com.ivanempire.lighthouse.models.packets.MediaPacket import java.net.DatagramPacket -/** - * Starting point for consumers to implement their own logging systems. - */ +/** Starting point for consumers to implement their own logging systems. */ abstract class LighthouseLogger { /** diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/core/DiscoveryManager.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/core/DiscoveryManager.kt index 09f26b3..32004dd 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/core/DiscoveryManager.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/core/DiscoveryManager.kt @@ -19,8 +19,8 @@ internal interface DiscoveryManager { fun createNewDeviceFlow(searchRequest: SearchRequest): Flow> /** - * Creates a [Flow] of a list of [AbridgedMediaDevice] instances that have not received any - * SSDP packets in the last [AbridgedMediaDevice.cache] seconds. These will be removed from the + * Creates a [Flow] of a list of [AbridgedMediaDevice] instances that have not received any SSDP + * packets in the last [AbridgedMediaDevice.cache] seconds. These will be removed from the * library's device list when this flow is combined with [createNewDeviceFlow]. * * @return [Flow] of a list of stale [AbridgedMediaDevice] instances diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/core/LighthouseState.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/core/LighthouseState.kt index a0e0972..364722b 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/core/LighthouseState.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/core/LighthouseState.kt @@ -44,9 +44,9 @@ internal class LighthouseState(private val logger: LighthouseLogger? = null) { /** * Handles the latest parsed instance of an [AliveMediaPacket] and either creates a new * [AbridgedMediaDevice] to add to the existing list, or updates any embedded components that - * said packet may target. Since ALIVE packets follow the sending equation of 3+2d+k (where - * a root device has d embedded devices, and k embedded services) the follow-up root packets - * are ignored. + * said packet may target. Since ALIVE packets follow the sending equation of 3+2d+k (where a + * root device has d embedded devices, and k embedded services) the follow-up root packets are + * ignored. * * @param latestPacket Latest instance of an [AliveMediaPacket] * @return A new version of [deviceList] with updated information from ALIVE packet @@ -56,28 +56,31 @@ internal class LighthouseState(private val logger: LighthouseLogger? = null) { val targetIndex = updatedList.indexOfFirst { it.uuid == latestPacket.usn.uuid } val targetComponent = latestPacket.usn -// logger?.logStateMessage(TAG, "Parsing ALIVE packet targeting $targetComponent on device $targetDevice") + // logger?.logStateMessage(TAG, "Parsing ALIVE packet targeting $targetComponent on + // device $targetDevice") // Create a new device since we haven't seen it yet if (targetIndex != -1) { - val updatedDevice = updatedList[targetIndex] - .copy(latestTimestamp = System.currentTimeMillis()) - .updateEmbeddedComponent(targetComponent) + val updatedDevice = + updatedList[targetIndex] + .copy(latestTimestamp = System.currentTimeMillis()) + .updateEmbeddedComponent(targetComponent) updatedList[targetIndex] = updatedDevice } else { updatedList.add( AbridgedMediaDevice( - uuid = targetComponent.uuid, - host = latestPacket.host, - cache = latestPacket.cache, - bootId = latestPacket.bootId, - mediaDeviceServer = latestPacket.server, - configId = latestPacket.configId, - location = latestPacket.location, - searchPort = latestPacket.searchPort, - secureLocation = latestPacket.secureLocation, - latestTimestamp = System.currentTimeMillis(), - ).updateEmbeddedComponent(targetComponent), + uuid = targetComponent.uuid, + host = latestPacket.host, + cache = latestPacket.cache, + bootId = latestPacket.bootId, + mediaDeviceServer = latestPacket.server, + configId = latestPacket.configId, + location = latestPacket.location, + searchPort = latestPacket.searchPort, + secureLocation = latestPacket.secureLocation, + latestTimestamp = System.currentTimeMillis(), + ) + .updateEmbeddedComponent(targetComponent), ) } @@ -97,39 +100,46 @@ internal class LighthouseState(private val logger: LighthouseLogger? = null) { val updatedList = backingDeviceList.value.toMutableList() val targetIndex = updatedList.indexOfFirst { it.uuid == latestPacket.usn.uuid } - // logger?.logStateMessage(TAG, "Parsing UPDATE packet targeting $targetComponent on device ${updatedDeviceList.getOrNull(targetIndex)}") - - val updatedDevice = if (targetIndex == -1) { - // Edge-case 1: UPDATE packet came before ALIVE - build full device, but specify empty fields - AbridgedMediaDevice( - uuid = targetComponent.uuid, - host = latestPacket.host, - cache = NOT_AVAILABLE_CACHE, - bootId = latestPacket.bootId, - mediaDeviceServer = null, - configId = latestPacket.configId, - location = latestPacket.location, - searchPort = latestPacket.searchPort, - secureLocation = latestPacket.secureLocation, - latestTimestamp = System.currentTimeMillis(), - ).updateEmbeddedComponent(targetComponent) - } else { - // ALIVE came first, UPDATE for root should only update certain fields (cache and server are not affected) - val existingDevice = updatedList[targetIndex] - val baseUpdatedDevice = existingDevice.copy(latestTimestamp = System.currentTimeMillis()).apply { - extraHeaders.putAll(latestPacket.extraHeaders) - } - when (targetComponent) { - is RootDeviceInformation -> baseUpdatedDevice.copy( - bootId = latestPacket.bootId, - configId = latestPacket.configId, - searchPort = latestPacket.searchPort, - location = latestPacket.location, - secureLocation = latestPacket.secureLocation, - ) - else -> baseUpdatedDevice.updateEmbeddedComponent(targetComponent) + // logger?.logStateMessage(TAG, "Parsing UPDATE packet targeting $targetComponent on device + // ${updatedDeviceList.getOrNull(targetIndex)}") + + val updatedDevice = + if (targetIndex == -1) { + // Edge-case 1: UPDATE packet came before ALIVE - build full device, but specify + // empty fields + AbridgedMediaDevice( + uuid = targetComponent.uuid, + host = latestPacket.host, + cache = NOT_AVAILABLE_CACHE, + bootId = latestPacket.bootId, + mediaDeviceServer = null, + configId = latestPacket.configId, + location = latestPacket.location, + searchPort = latestPacket.searchPort, + secureLocation = latestPacket.secureLocation, + latestTimestamp = System.currentTimeMillis(), + ) + .updateEmbeddedComponent(targetComponent) + } else { + // ALIVE came first, UPDATE for root should only update certain fields (cache and + // server are not affected) + val existingDevice = updatedList[targetIndex] + val baseUpdatedDevice = + existingDevice.copy(latestTimestamp = System.currentTimeMillis()).apply { + extraHeaders.putAll(latestPacket.extraHeaders) + } + when (targetComponent) { + is RootDeviceInformation -> + baseUpdatedDevice.copy( + bootId = latestPacket.bootId, + configId = latestPacket.configId, + searchPort = latestPacket.searchPort, + location = latestPacket.location, + secureLocation = latestPacket.secureLocation, + ) + else -> baseUpdatedDevice.updateEmbeddedComponent(targetComponent) + } } - } if (targetIndex != -1) { updatedList[targetIndex] = updatedDevice @@ -158,7 +168,10 @@ internal class LighthouseState(private val logger: LighthouseLogger? = null) { val targetComponent = latestPacket.usn val targetDevice = updatedList[targetIndex] - logger?.logStateMessage(TAG, "Parsing BYEBYE packet targeting $targetComponent on device $targetDevice") + logger?.logStateMessage( + TAG, + "Parsing BYEBYE packet targeting $targetComponent on device $targetDevice" + ) when (latestPacket.usn) { is RootDeviceInformation -> updatedList.remove(targetDevice) diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/core/RealDiscoveryManager.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/core/RealDiscoveryManager.kt index 552bf2e..9afd7a9 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/core/RealDiscoveryManager.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/core/RealDiscoveryManager.kt @@ -20,8 +20,10 @@ import kotlinx.coroutines.isActive /** * Specific implementation of [DiscoveryManager] * - * @param lighthouseState A library-wide instance of [LighthouseState] to keep track of discovered devices - * @param multicastSocketListener An implementation of [SocketListener] to send/receive packets from the network + * @param lighthouseState A library-wide instance of [LighthouseState] to keep track of discovered + * devices + * @param multicastSocketListener An implementation of [SocketListener] to send/receive packets from + * the network */ internal class RealDiscoveryManager( private val lighthouseState: LighthouseState, @@ -30,8 +32,11 @@ internal class RealDiscoveryManager( ) : DiscoveryManager { @OptIn(ExperimentalCoroutinesApi::class) - override fun createNewDeviceFlow(searchRequest: SearchRequest): Flow> { - return multicastSocketListener.listenForPackets(searchRequest) + override fun createNewDeviceFlow( + searchRequest: SearchRequest + ): Flow> { + return multicastSocketListener + .listenForPackets(searchRequest) .mapNotNull { DatagramPacketTransformer(it, logger) } .mapNotNull { MediaPacketParser(it, logger) } .onEach { lighthouseState.parseMediaPacket(it) } @@ -41,11 +46,12 @@ internal class RealDiscoveryManager( override fun createStaleDeviceFlow(): Flow> { return flow { - while (currentCoroutineContext().isActive) { - delay(1000) - lighthouseState.parseStaleDevices() - emit(lighthouseState.deviceList.value) + while (currentCoroutineContext().isActive) { + delay(1000) + lighthouseState.parseStaleDevices() + emit(lighthouseState.deviceList.value) + } } - }.filter { it.isNotEmpty() } + .filter { it.isNotEmpty() } } } diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/core/RealLighthouseClient.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/core/RealLighthouseClient.kt index 632935b..b2b5c47 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/core/RealLighthouseClient.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/core/RealLighthouseClient.kt @@ -24,10 +24,14 @@ internal class RealLighthouseClient( private val discoveryMutex = Mutex() private var isDiscoveryRunning = false - override suspend fun discoverDevices(searchRequest: SearchRequest): Flow> { + override suspend fun discoverDevices( + searchRequest: SearchRequest + ): Flow> { discoveryMutex.withLock { if (isDiscoveryRunning) { - throw IllegalStateException("Discovery is already in progress - did you call discoverDevices() multiple times?") + throw IllegalStateException( + "Discovery is already in progress - did you call discoverDevices() multiple times?" + ) } isDiscoveryRunning = true } @@ -40,11 +44,7 @@ internal class RealLighthouseClient( return merge(foundDevicesFlow, lostDevicesFlow) .distinctUntilChanged() .flowOn(dispatcher) - .onCompletion { - discoveryMutex.withLock { - isDiscoveryRunning = false - } - } + .onCompletion { discoveryMutex.withLock { isDiscoveryRunning = false } } } private companion object { diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/Constants.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/Constants.kt index 0c51e17..e31dcba 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/Constants.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/Constants.kt @@ -26,11 +26,12 @@ object Constants { val NOT_AVAILABLE_LOCATION = URL("http://0.0.0.0/") val DEFAULT_MEDIA_HOST = MediaHost(InetAddress.getByName(DEFAULT_MULTICAST_ADDRESS), 1900) - val DEFAULT_SEARCH_REQUEST = MulticastSearchRequest( - hostname = DEFAULT_MEDIA_HOST, - mx = 1, - searchTarget = "ssdp:all", - osVersion = null, - productVersion = null, - ) + val DEFAULT_SEARCH_REQUEST = + MulticastSearchRequest( + hostname = DEFAULT_MEDIA_HOST, + mx = 1, + searchTarget = "ssdp:all", + osVersion = null, + productVersion = null, + ) } diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/devices/MediaDevice.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/devices/MediaDevice.kt index 85520ca..709c281 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/devices/MediaDevice.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/devices/MediaDevice.kt @@ -13,8 +13,8 @@ import java.util.UUID abstract class MediaDevice /** - * A specific version of a [MediaDevice] that is built from SSDP discovery information. This is - * what Lighthouse will send to users after it has discovered devices + * A specific version of a [MediaDevice] that is built from SSDP discovery information. This is what + * Lighthouse will send to users after it has discovered devices * * @param uuid The unique identifier of this device * @param location The URL which can be called to get the complete XML description of this device @@ -70,10 +70,11 @@ open class DetailedMediaDevice( ) : MediaDevice() /** - * The root media device, populated when an [AbridgedMediaDevice] calls the XML description - * endpoint to get complete information about itself. Contains all of the information obtained - * from the XML information in a parsed format. All fields are identical to ones described in the + * The root media device, populated when an [AbridgedMediaDevice] calls the XML description endpoint + * to get complete information about itself. Contains all of the information obtained from the XML + * information in a parsed format. All fields are identical to ones described in the * [DetailedEmbeddedMediaService], except for two. + * * @param deviceList The list of embedded devices found on this root device * @param presentationUrl The presentation URL of this root device */ @@ -91,15 +92,27 @@ data class RootMediaDevice( override val serviceList: List?, val deviceList: List?, val presentationUrl: URL?, -) : DetailedMediaDevice( - deviceType, friendlyName, manufacturer, manufacturerURL, modelDescription, modelName, modelNumber, modelUrl, serialNumber, udn, serviceList, -) +) : + DetailedMediaDevice( + deviceType, + friendlyName, + manufacturer, + manufacturerURL, + modelDescription, + modelName, + modelNumber, + modelUrl, + serialNumber, + udn, + serviceList, + ) /** * The embedded media device, populated when an [AbridgedMediaDevice] calls the XML description - * endpoint to get complete information about itself. Contains all of the information obtained - * from the XML information in a parsed format. All fields are identical to ones described in the + * endpoint to get complete information about itself. Contains all of the information obtained from + * the XML information in a parsed format. All fields are identical to ones described in the * [DetailedEmbeddedMediaService], except for one. + * * @param upc Universal product code */ data class DetailedEmbeddedMediaDevice( @@ -115,6 +128,17 @@ data class DetailedEmbeddedMediaDevice( override val udn: UUID, val upc: Int?, override val serviceList: List?, -) : DetailedMediaDevice( - deviceType, friendlyName, manufacturer, manufacturerURL, modelDescription, modelName, modelNumber, modelUrl, serialNumber, udn, serviceList, -) +) : + DetailedMediaDevice( + deviceType, + friendlyName, + manufacturer, + manufacturerURL, + modelDescription, + modelName, + modelNumber, + modelUrl, + serialNumber, + udn, + serviceList, + ) diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/MediaPacket.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/MediaPacket.kt index ae54ffa..79cfea8 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/MediaPacket.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/MediaPacket.kt @@ -10,7 +10,7 @@ import kotlin.collections.HashMap * * @param host Required - IANA reserved multicast address:port - typically 239.255.255.250:1900 * @param notificationType The SSDP packet's [NotificationType] parsed field value - * @param usn The SSDP packet's [UniqueServiceName] parsed field value + * @param usn The SSDP packet's [UniqueServiceName] parsed field value * @param configId The SSDP packet's configuration ID of the specific device * @param bootId The SSDP packet's boot ID of the specific device * @param extraHeaders Manufacturer-added headers that were parsed in the SSDP packet @@ -46,13 +46,14 @@ internal data class AliveMediaPacket( override val configId: Int, val searchPort: Int?, val secureLocation: URL?, -) : MediaPacket( - host, - notificationType, - usn, - bootId, - configId, -) +) : + MediaPacket( + host, + notificationType, + usn, + bootId, + configId, + ) /** * The model class representing a parsed ssdp:update packet @@ -74,13 +75,14 @@ internal data class UpdateMediaPacket( val nextBootId: Int, val searchPort: Int?, val secureLocation: URL?, -) : MediaPacket( - host, - notificationType, - usn, - bootId, - configId, -) +) : + MediaPacket( + host, + notificationType, + usn, + bootId, + configId, + ) /** * The model class representing a parsed ssdp:byebye packet @@ -94,13 +96,14 @@ internal data class ByeByeMediaPacket( override val usn: UniqueServiceName, override val bootId: Int, override val configId: Int, -) : MediaPacket( - host, - notificationType, - usn, - bootId, - configId, -) +) : + MediaPacket( + host, + notificationType, + usn, + bootId, + configId, + ) /** * The model class representing a parsed M-SEARCH response packet @@ -126,10 +129,11 @@ internal data class SearchResponseMediaPacket( val searchPort: Int, val secureLocation: URL, override val host: MediaHost = DEFAULT_MEDIA_HOST, -) : MediaPacket( - host, - notificationType, - usn, - bootId, - configId, -) +) : + MediaPacket( + host, + notificationType, + usn, + bootId, + configId, + ) diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/NotificationSubtype.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/NotificationSubtype.kt index 05745e2..9ed225b 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/NotificationSubtype.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/NotificationSubtype.kt @@ -19,9 +19,10 @@ internal enum class NotificationSubtype(val rawString: String) { null } else { values().firstOrNull { - it.rawString == rawValue.uppercase( - Locale.getDefault(), - ) + it.rawString == + rawValue.uppercase( + Locale.getDefault(), + ) } } } diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/NotificationType.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/NotificationType.kt index 6e2c266..1988e39 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/NotificationType.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/NotificationType.kt @@ -1,8 +1,8 @@ package com.ivanempire.lighthouse.models.packets /** - * Wrapper class for the NT field string. Because this and the USN field are almost identical, - * the latter is used for parsing. + * Wrapper class for the NT field string. Because this and the USN field are almost identical, the + * latter is used for parsing. * * @param rawString The raw string that was obtained from the [HeaderKeys.NOTIFICATION_TYPE] field */ diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/UniqueServiceName.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/UniqueServiceName.kt index cb47e82..7fa0be6 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/UniqueServiceName.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/packets/UniqueServiceName.kt @@ -6,17 +6,15 @@ import com.ivanempire.lighthouse.models.Constants.SERVICE_MARKER import com.ivanempire.lighthouse.models.Constants.UPNP_SCHEMA_MARKER import com.ivanempire.lighthouse.models.Constants.UUID_MARKER -/** - * Wrapper class around an SSDP packet's USN field. - */ +/** Wrapper class around an SSDP packet's USN field. */ interface UniqueServiceName { val uuid: String companion object { /** - * This parses the string value and figures out if the packet is intended to update - * the root device, an embedded device, or an embedded service. The - * decision is made based on key markers present in the raw string + * This parses the string value and figures out if the packet is intended to update the root + * device, an embedded device, or an embedded service. The decision is made based on key + * markers present in the raw string * * @param rawValue The value provided by the USN header */ diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/search/MulticastSearchRequest.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/search/MulticastSearchRequest.kt index 8ce40eb..2fb2602 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/search/MulticastSearchRequest.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/search/MulticastSearchRequest.kt @@ -34,12 +34,8 @@ data class MulticastSearchRequest( ) : SearchRequest { init { - require(mx in 1..5) { - "MX should be between 1 and 5 inclusive" - } - require(searchTarget.isNotEmpty()) { - "Search target (ST) should not be an empty string" - } + require(mx in 1..5) { "MX should be between 1 and 5 inclusive" } + require(searchTarget.isNotEmpty()) { "Search target (ST) should not be an empty string" } require(friendlyName.isNotEmpty()) { "Friendly name (CPFN.UPNP.ORG) should not be an empty string" } @@ -48,17 +44,42 @@ data class MulticastSearchRequest( override fun toString(): String { val builder = StringBuilder() builder.append(StartLine.SEARCH.rawString).append(NEWLINE_SEPARATOR) - builder.append(HeaderKeys.HOST).append("$FIELD_SEPARATOR ").append(hostname.toString()).append(NEWLINE_SEPARATOR) - .append(HeaderKeys.MAN).append("$FIELD_SEPARATOR ").append(DEFAULT_SEARCH_MAN).append(NEWLINE_SEPARATOR) - .append(HeaderKeys.MX).append("$FIELD_SEPARATOR ").append(mx).append(NEWLINE_SEPARATOR) - .append(HeaderKeys.SEARCH_TARGET).append("$FIELD_SEPARATOR ").append(searchTarget).append(NEWLINE_SEPARATOR) + builder + .append(HeaderKeys.HOST) + .append("$FIELD_SEPARATOR ") + .append(hostname.toString()) + .append(NEWLINE_SEPARATOR) + .append(HeaderKeys.MAN) + .append("$FIELD_SEPARATOR ") + .append(DEFAULT_SEARCH_MAN) + .append(NEWLINE_SEPARATOR) + .append(HeaderKeys.MX) + .append("$FIELD_SEPARATOR ") + .append(mx) + .append(NEWLINE_SEPARATOR) + .append(HeaderKeys.SEARCH_TARGET) + .append("$FIELD_SEPARATOR ") + .append(searchTarget) + .append(NEWLINE_SEPARATOR) if (!osVersion.isNullOrEmpty() && !productVersion.isNullOrEmpty()) { - builder.append(HeaderKeys.USER_AGENT).append("$FIELD_SEPARATOR ").append("$osVersion UPnP/2.0 $productVersion").append(NEWLINE_SEPARATOR) + builder + .append(HeaderKeys.USER_AGENT) + .append("$FIELD_SEPARATOR ") + .append("$osVersion UPnP/2.0 $productVersion") + .append(NEWLINE_SEPARATOR) } - builder.append(HeaderKeys.FRIENDLY_NAME).append("$FIELD_SEPARATOR ").append(friendlyName).append(NEWLINE_SEPARATOR) - builder.append(HeaderKeys.CONTROL_POINT_UUID).append("$FIELD_SEPARATOR ").append(uuid).append(NEWLINE_SEPARATOR) + builder + .append(HeaderKeys.FRIENDLY_NAME) + .append("$FIELD_SEPARATOR ") + .append(friendlyName) + .append(NEWLINE_SEPARATOR) + builder + .append(HeaderKeys.CONTROL_POINT_UUID) + .append("$FIELD_SEPARATOR ") + .append(uuid) + .append(NEWLINE_SEPARATOR) builder.append(NEWLINE_SEPARATOR) return builder.toString() diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/search/SearchRequest.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/search/SearchRequest.kt index a13b371..c260ad5 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/search/SearchRequest.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/search/SearchRequest.kt @@ -3,9 +3,7 @@ package com.ivanempire.lighthouse.models.search import java.net.DatagramPacket import java.net.InetAddress -/** - * All SSDP search requests conform to this interface for Lighthouse to use - */ +/** All SSDP search requests conform to this interface for Lighthouse to use */ interface SearchRequest { /** diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/search/UnicastSearchRequest.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/search/UnicastSearchRequest.kt index 0505b46..85218be 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/models/search/UnicastSearchRequest.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/models/search/UnicastSearchRequest.kt @@ -9,9 +9,9 @@ import com.ivanempire.lighthouse.models.packets.StartLine import java.lang.StringBuilder /** - * A unicast [SearchRequest] that will send the search data to a specific IP address on the - * network. This can be used to quickly confirm the existence of a device and get additional - * information about it like the UUID, XML endpoint, embedded components. + * A unicast [SearchRequest] that will send the search data to a specific IP address on the network. + * This can be used to quickly confirm the existence of a device and get additional information + * about it like the UUID, XML endpoint, embedded components. * * @param hostname Required - IANA reserved multicast address:port - typically 239.255.255.250:1900 * @param searchTarget Required - search target to use for the search request @@ -26,20 +26,32 @@ data class UnicastSearchRequest( ) : SearchRequest { init { - require(searchTarget.isNotEmpty()) { - "Search target (ST) should not be an empty string" - } + require(searchTarget.isNotEmpty()) { "Search target (ST) should not be an empty string" } } override fun toString(): String { val builder = StringBuilder() builder.append(StartLine.SEARCH.rawString).append(NEWLINE_SEPARATOR) - builder.append(HeaderKeys.HOST).append("$FIELD_SEPARATOR ").append(hostname.toString()).append(NEWLINE_SEPARATOR) - .append(HeaderKeys.MAN).append("$FIELD_SEPARATOR ").append(DEFAULT_SEARCH_MAN).append(NEWLINE_SEPARATOR) - .append(HeaderKeys.SEARCH_TARGET).append("$FIELD_SEPARATOR ").append(searchTarget).append(NEWLINE_SEPARATOR) + builder + .append(HeaderKeys.HOST) + .append("$FIELD_SEPARATOR ") + .append(hostname.toString()) + .append(NEWLINE_SEPARATOR) + .append(HeaderKeys.MAN) + .append("$FIELD_SEPARATOR ") + .append(DEFAULT_SEARCH_MAN) + .append(NEWLINE_SEPARATOR) + .append(HeaderKeys.SEARCH_TARGET) + .append("$FIELD_SEPARATOR ") + .append(searchTarget) + .append(NEWLINE_SEPARATOR) if (!osVersion.isNullOrEmpty() && !productVersion.isNullOrEmpty()) { - builder.append(HeaderKeys.USER_AGENT).append("$FIELD_SEPARATOR ").append("$osVersion UPnP/2.0 $productVersion").append(NEWLINE_SEPARATOR) + builder + .append(HeaderKeys.USER_AGENT) + .append("$FIELD_SEPARATOR ") + .append("$osVersion UPnP/2.0 $productVersion") + .append(NEWLINE_SEPARATOR) } builder.append(NEWLINE_SEPARATOR) diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/DatagramPacketTransformer.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/DatagramPacketTransformer.kt index 4dedd98..a30ae7b 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/DatagramPacketTransformer.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/DatagramPacketTransformer.kt @@ -16,7 +16,10 @@ import java.nio.charset.Charset internal class DatagramPacketTransformer { companion object { - operator fun invoke(datagramPacket: DatagramPacket, logger: LighthouseLogger? = null): HashMap? { + operator fun invoke( + datagramPacket: DatagramPacket, + logger: LighthouseLogger? = null + ): HashMap? { val cleanedDatagram = datagramPacket.cleanPacket() logger?.logPacketMessage(TAG, "Cleaned datagram packet and got: $cleanedDatagram") val packetFields = cleanedDatagram.split(NEWLINE_SEPARATOR) @@ -36,6 +39,7 @@ internal class DatagramPacketTransformer { return packetHeaders } + private const val TAG = "DatagramTransformer" } } diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/AliveMediaPacketParser.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/AliveMediaPacketParser.kt index 9cd43e5..63ec3f7 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/AliveMediaPacketParser.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/AliveMediaPacketParser.kt @@ -24,9 +24,7 @@ internal class AliveMediaPacketParser( parseCacheControl(headerSet.getAndRemove(HeaderKeys.CACHE_CONTROL)) } - private val location: URL by lazy { - parseUrl(headerSet.getAndRemove(HeaderKeys.LOCATION)) - } + private val location: URL by lazy { parseUrl(headerSet.getAndRemove(HeaderKeys.LOCATION)) } private val server: MediaDeviceServer by lazy { MediaDeviceServer.parseFromString(headerSet.getAndRemove(HeaderKeys.SERVER)) @@ -42,7 +40,8 @@ internal class AliveMediaPacketParser( private val bootId = headerSet.getAndRemove(HeaderKeys.BOOT_ID)?.toInt() ?: NOT_AVAILABLE_NUM - private val configId = headerSet.getAndRemove(HeaderKeys.CONFIG_ID)?.toInt() ?: NOT_AVAILABLE_NUM + private val configId = + headerSet.getAndRemove(HeaderKeys.CONFIG_ID)?.toInt() ?: NOT_AVAILABLE_NUM private val searchPort = headerSet.getAndRemove(HeaderKeys.SEARCH_PORT)?.toInt() diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/ByeByeMediaPacketParser.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/ByeByeMediaPacketParser.kt index b6f4bfc..dceea23 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/ByeByeMediaPacketParser.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/ByeByeMediaPacketParser.kt @@ -28,7 +28,8 @@ internal class ByeByeMediaPacketParser( private val bootId = headerSet.getAndRemove(HeaderKeys.BOOT_ID)?.toInt() ?: NOT_AVAILABLE_NUM - private val configId = headerSet.getAndRemove(HeaderKeys.CONFIG_ID)?.toInt() ?: NOT_AVAILABLE_NUM + private val configId = + headerSet.getAndRemove(HeaderKeys.CONFIG_ID)?.toInt() ?: NOT_AVAILABLE_NUM override fun parseMediaPacket(): MediaPacket { return ByeByeMediaPacket( diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/MediaPacketParser.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/MediaPacketParser.kt index 535b9e1..1a5ac5a 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/MediaPacketParser.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/MediaPacketParser.kt @@ -12,9 +12,9 @@ import java.net.URL /** * Each SSDP packet parser needs to implement this class. There are some common functions, like - * [parseCacheControl] and [parseUrl] which help extract shared information. This class takes in - * the valid header set and figures out which parser to invoke in accordance to the [NotificationSubtype] - * field. + * [parseCacheControl] and [parseUrl] which help extract shared information. This class takes in the + * valid header set and figures out which parser to invoke in accordance to the + * [NotificationSubtype] field. */ internal abstract class MediaPacketParser { @@ -24,7 +24,8 @@ internal abstract class MediaPacketParser { * Parses the XML description endpoint URL from the [HeaderKeys.LOCATION] packet field * * @param rawValue Raw string value of the XML endpoint obtained from the media packet - * @return Returns URL of the XML endpoint, or a loopback value if [MalformedURLException] is thrown + * @return Returns URL of the XML endpoint, or a loopback value if [MalformedURLException] is + * thrown */ internal fun parseUrl(rawValue: String?): URL { return try { @@ -61,18 +62,23 @@ internal abstract class MediaPacketParser { return SearchPacketParser(packetHeaders).parseMediaPacket() } else { // If this is not a search response packet, determine type by the NTS field - val notificationSubtype = NotificationSubtype.getByRawValue( - packetHeaders.getAndRemove(HeaderKeys.NOTIFICATION_SUBTYPE), - ) - val packetParser = when (notificationSubtype) { - NotificationSubtype.ALIVE -> AliveMediaPacketParser(packetHeaders) - NotificationSubtype.UPDATE -> UpdateMediaPacketParser(packetHeaders) - NotificationSubtype.BYEBYE -> ByeByeMediaPacketParser(packetHeaders) - else -> { - logger?.logErrorMessage(TAG, "Received an invalid NotificationSubtype: $notificationSubtype") - null + val notificationSubtype = + NotificationSubtype.getByRawValue( + packetHeaders.getAndRemove(HeaderKeys.NOTIFICATION_SUBTYPE), + ) + val packetParser = + when (notificationSubtype) { + NotificationSubtype.ALIVE -> AliveMediaPacketParser(packetHeaders) + NotificationSubtype.UPDATE -> UpdateMediaPacketParser(packetHeaders) + NotificationSubtype.BYEBYE -> ByeByeMediaPacketParser(packetHeaders) + else -> { + logger?.logErrorMessage( + TAG, + "Received an invalid NotificationSubtype: $notificationSubtype" + ) + null + } } - } val parsedPacket = packetParser?.parseMediaPacket() return if (parsedPacket != null) { @@ -85,6 +91,7 @@ internal abstract class MediaPacketParser { } } } + private const val TAG = "MediaPacketParser" } } diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/SearchPacketParser.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/SearchPacketParser.kt index d70a14b..88ec716 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/SearchPacketParser.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/SearchPacketParser.kt @@ -23,9 +23,7 @@ internal class SearchPacketParser( private val date = headerSet.getAndRemove(HeaderKeys.DATE) ?: NOT_AVAILABLE - private val location: URL by lazy { - parseUrl(headerSet.getAndRemove(HeaderKeys.LOCATION)) - } + private val location: URL by lazy { parseUrl(headerSet.getAndRemove(HeaderKeys.LOCATION)) } private val server: MediaDeviceServer by lazy { MediaDeviceServer.parseFromString(headerSet.getAndRemove(HeaderKeys.SERVER)) @@ -41,9 +39,11 @@ internal class SearchPacketParser( private val bootId = headerSet.getAndRemove(HeaderKeys.BOOT_ID)?.toInt() ?: NOT_AVAILABLE_NUM - private val configId = headerSet.getAndRemove(HeaderKeys.CONFIG_ID)?.toInt() ?: NOT_AVAILABLE_NUM + private val configId = + headerSet.getAndRemove(HeaderKeys.CONFIG_ID)?.toInt() ?: NOT_AVAILABLE_NUM - private val searchPort = headerSet.getAndRemove(HeaderKeys.SEARCH_PORT)?.toInt() ?: NOT_AVAILABLE_NUM + private val searchPort = + headerSet.getAndRemove(HeaderKeys.SEARCH_PORT)?.toInt() ?: NOT_AVAILABLE_NUM private val secureLocation: URL by lazy { parseUrl(headerSet.getAndRemove(HeaderKeys.SECURE_LOCATION)) diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/UpdateMediaPacketParser.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/UpdateMediaPacketParser.kt index 3a691c6..477e05a 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/UpdateMediaPacketParser.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/parsers/packets/UpdateMediaPacketParser.kt @@ -19,9 +19,7 @@ internal class UpdateMediaPacketParser( MediaHost.parseFromString(headerSet.getAndRemove(HeaderKeys.HOST)) } - private val location: URL by lazy { - parseUrl(headerSet.getAndRemove(HeaderKeys.LOCATION)) - } + private val location: URL by lazy { parseUrl(headerSet.getAndRemove(HeaderKeys.LOCATION)) } private val notificationType: NotificationType by lazy { NotificationType(headerSet.getAndRemove(HeaderKeys.NOTIFICATION_TYPE)) @@ -33,11 +31,14 @@ internal class UpdateMediaPacketParser( private val bootId = headerSet.getAndRemove(HeaderKeys.BOOT_ID)?.toInt() ?: NOT_AVAILABLE_NUM - private val configId = headerSet.getAndRemove(HeaderKeys.CONFIG_ID)?.toInt() ?: NOT_AVAILABLE_NUM + private val configId = + headerSet.getAndRemove(HeaderKeys.CONFIG_ID)?.toInt() ?: NOT_AVAILABLE_NUM - private val nextBootId = headerSet.getAndRemove(HeaderKeys.NEXT_BOOT_ID)?.toInt() ?: NOT_AVAILABLE_NUM + private val nextBootId = + headerSet.getAndRemove(HeaderKeys.NEXT_BOOT_ID)?.toInt() ?: NOT_AVAILABLE_NUM - private val searchPort = headerSet.getAndRemove(HeaderKeys.SEARCH_PORT)?.toInt() ?: NOT_AVAILABLE_NUM + private val searchPort = + headerSet.getAndRemove(HeaderKeys.SEARCH_PORT)?.toInt() ?: NOT_AVAILABLE_NUM private val secureLocation: URL by lazy { parseUrl(headerSet.getAndRemove(HeaderKeys.SECURE_LOCATION)) diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/socket/RealSocketListener.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/socket/RealSocketListener.kt index 381e3d5..a606954 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/socket/RealSocketListener.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/socket/RealSocketListener.kt @@ -5,15 +5,15 @@ import com.ivanempire.lighthouse.LighthouseLogger import com.ivanempire.lighthouse.models.Constants.DEFAULT_MULTICAST_ADDRESS import com.ivanempire.lighthouse.models.Constants.LIGHTHOUSE_CLIENT import com.ivanempire.lighthouse.models.search.SearchRequest +import java.net.DatagramPacket +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.MulticastSocket import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.isActive -import java.net.DatagramPacket -import java.net.InetAddress -import java.net.InetSocketAddress -import java.net.MulticastSocket /** Specific implementation of [SocketListener] */ internal class RealSocketListener( @@ -44,7 +44,11 @@ internal class RealSocketListener( multicastSocket.bind(InetSocketAddress(MULTICAST_PORT)) logger?.logStatusMessage(TAG, "MulticastSocket has been setup") } catch (ex: Exception) { - logger?.logErrorMessage(TAG, "Could finish setting up the multicast socket and group", ex) + logger?.logErrorMessage( + TAG, + "Could finish setting up the multicast socket and group", + ex + ) } return multicastSocket @@ -55,21 +59,21 @@ internal class RealSocketListener( val multicastSocket = setupSocket() return flow { - multicastSocket.use { - val datagramPacketRequest = searchRequest.toDatagramPacket(multicastGroup) - - repeat(retryCount) { - multicastSocket.send(datagramPacketRequest) - } - - while (currentCoroutineContext().isActive) { - val discoveryBuffer = ByteArray(MULTICAST_DATAGRAM_SIZE) - val discoveryDatagram = DatagramPacket(discoveryBuffer, discoveryBuffer.size) - it.receive(discoveryDatagram) - emit(discoveryDatagram) + multicastSocket.use { + val datagramPacketRequest = searchRequest.toDatagramPacket(multicastGroup) + + repeat(retryCount) { multicastSocket.send(datagramPacketRequest) } + + while (currentCoroutineContext().isActive) { + val discoveryBuffer = ByteArray(MULTICAST_DATAGRAM_SIZE) + val discoveryDatagram = + DatagramPacket(discoveryBuffer, discoveryBuffer.size) + it.receive(discoveryDatagram) + emit(discoveryDatagram) + } } } - }.onCompletion { teardownSocket(multicastSocket) } + .onCompletion { teardownSocket(multicastSocket) } } override fun teardownSocket(multicastSocket: MulticastSocket) { diff --git a/lighthouse/src/main/java/com/ivanempire/lighthouse/socket/SocketListener.kt b/lighthouse/src/main/java/com/ivanempire/lighthouse/socket/SocketListener.kt index 5ff3f12..fd9fea5 100644 --- a/lighthouse/src/main/java/com/ivanempire/lighthouse/socket/SocketListener.kt +++ b/lighthouse/src/main/java/com/ivanempire/lighthouse/socket/SocketListener.kt @@ -1,9 +1,9 @@ package com.ivanempire.lighthouse.socket import com.ivanempire.lighthouse.models.search.SearchRequest -import kotlinx.coroutines.flow.Flow import java.net.DatagramPacket import java.net.MulticastSocket +import kotlinx.coroutines.flow.Flow /** * All socket listeners should conform to this interface in order to set up their sockets, listen @@ -13,8 +13,8 @@ internal interface SocketListener { /** * Creates and returns an instance of a [MulticastSocket] in order to set everything up for - * network discovery. A new socket is created, the multicast lock is acquired, and the - * multicast group is joined + * network discovery. A new socket is created, the multicast lock is acquired, and the multicast + * group is joined * * @return An instance of [MulticastSocket] to use for sending and receiving SSDP packets */ @@ -31,8 +31,8 @@ internal interface SocketListener { /** * Tears down the [MulticastSocket] used during device discovery and releases all resources. - * Lighthouse will release the multicast lock, leave the multicast group, and make sure that - * the socket is properly closed. + * Lighthouse will release the multicast lock, leave the multicast group, and make sure that the + * socket is properly closed. * * @param multicastSocket The [MulticastSocket] used for device discovery */ diff --git a/lighthouse/src/test/java/com/ivanempire/lighthouse/core/RealDiscoveryManagerTest.kt b/lighthouse/src/test/java/com/ivanempire/lighthouse/core/RealDiscoveryManagerTest.kt index c43dd3b..90cce94 100644 --- a/lighthouse/src/test/java/com/ivanempire/lighthouse/core/RealDiscoveryManagerTest.kt +++ b/lighthouse/src/test/java/com/ivanempire/lighthouse/core/RealDiscoveryManagerTest.kt @@ -3,6 +3,7 @@ package com.ivanempire.lighthouse.core import com.ivanempire.lighthouse.models.devices.AbridgedMediaDevice import com.ivanempire.lighthouse.parsers.TestUtils.generateAlivePacket import com.ivanempire.lighthouse.socket.SocketListener +import java.util.UUID import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch @@ -11,7 +12,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.mockito.Mockito -import java.util.UUID @OptIn(ExperimentalCoroutinesApi::class) class RealDiscoveryManagerTest { @@ -23,11 +23,12 @@ class RealDiscoveryManagerTest { @Before fun setup() = runTest { - sut = RealDiscoveryManager( - lighthouseState, - mockedSocketListener, - null, - ) + sut = + RealDiscoveryManager( + lighthouseState, + mockedSocketListener, + null, + ) } @Test diff --git a/lighthouse/src/test/java/com/ivanempire/lighthouse/models/packets/UniqueServiceNameTest.kt b/lighthouse/src/test/java/com/ivanempire/lighthouse/models/packets/UniqueServiceNameTest.kt index 3b93c93..4cdd58b 100644 --- a/lighthouse/src/test/java/com/ivanempire/lighthouse/models/packets/UniqueServiceNameTest.kt +++ b/lighthouse/src/test/java/com/ivanempire/lighthouse/models/packets/UniqueServiceNameTest.kt @@ -26,13 +26,15 @@ class UniqueServiceNameTest { @Test fun `given embedded service USN correctly parses`() { - val usnStringService1 = "uuid:709e1cf4-920d-4aa7-8678-6dab1b6c68a9::urn:schemas-upnp-org:service:Dimming:1" + val usnStringService1 = + "uuid:709e1cf4-920d-4aa7-8678-6dab1b6c68a9::urn:schemas-upnp-org:service:Dimming:1" assertEquals( EmbeddedService("709e1cf4-920d-4aa7-8678-6dab1b6c68a9", "Dimming", "1"), UniqueServiceName(usnStringService1), ) - val usnStringService2 = "uuid:709e1cf4-920d-4aa7-8678-6dab1b6c68a9::urn:schemas-upnp-org:service:SwitchPower:1" + val usnStringService2 = + "uuid:709e1cf4-920d-4aa7-8678-6dab1b6c68a9::urn:schemas-upnp-org:service:SwitchPower:1" assertEquals( EmbeddedService("709e1cf4-920d-4aa7-8678-6dab1b6c68a9", "SwitchPower", "1"), UniqueServiceName(usnStringService2), @@ -41,7 +43,8 @@ class UniqueServiceNameTest { @Test fun `given embedded device USN correctly parses`() { - val usnStringDevice = "uuid:709e1cf4-920d-4aa7-8678-6dab1b6c68a9::urn:schemas-upnp-org:device:DimmableLight:1" + val usnStringDevice = + "uuid:709e1cf4-920d-4aa7-8678-6dab1b6c68a9::urn:schemas-upnp-org:device:DimmableLight:1" assertEquals( EmbeddedDevice("709e1cf4-920d-4aa7-8678-6dab1b6c68a9", "DimmableLight", "1"), UniqueServiceName(usnStringDevice), @@ -50,7 +53,8 @@ class UniqueServiceNameTest { @Test fun `given USN with domain correctly parses`() { - val usnString = "uuid:00000000-0000-0000-0200-00125A8A0960::urn:schemas-microsoft-com:device:presence:1" + val usnString = + "uuid:00000000-0000-0000-0200-00125A8A0960::urn:schemas-microsoft-com:device:presence:1" assertEquals( EmbeddedDevice( uuid = "00000000-0000-0000-0200-00125A8A0960", @@ -64,7 +68,8 @@ class UniqueServiceNameTest { @Test fun `given complex USN 1 correctly parses`() { - val usnString = "uuid:ebf5a0a0-1dd1-11b2-a90f-e0dbd1eb5ad2::urn:schemas-upnp-org:service:DiscoverFriendlies:1" + val usnString = + "uuid:ebf5a0a0-1dd1-11b2-a90f-e0dbd1eb5ad2::urn:schemas-upnp-org:service:DiscoverFriendlies:1" assertEquals( EmbeddedService( uuid = "ebf5a0a0-1dd1-11b2-a90f-e0dbd1eb5ad2", @@ -77,7 +82,8 @@ class UniqueServiceNameTest { @Test fun `given complex USN 2 correctly parses`() { - val usnString = "uuid:5f6085cb-5c18-40c6-a299-dbc05135b0c4::urn:samsung.com:service:ScreenSharingService:1" + val usnString = + "uuid:5f6085cb-5c18-40c6-a299-dbc05135b0c4::urn:samsung.com:service:ScreenSharingService:1" assertEquals( EmbeddedService( uuid = "5f6085cb-5c18-40c6-a299-dbc05135b0c4", @@ -91,7 +97,8 @@ class UniqueServiceNameTest { @Test fun `given USN for Amcrest camera correctly parses`() { - val usnString = "uuid:device_3_0-AMC066F0BC0A3747AF::urn:schemas-upnp-org:device:3.0-AMC066F0BC0A3747AF" + val usnString = + "uuid:device_3_0-AMC066F0BC0A3747AF::urn:schemas-upnp-org:device:3.0-AMC066F0BC0A3747AF" assertEquals( EmbeddedDevice("device_3_0-AMC066F0BC0A3747AF", "3.0-AMC066F0BC0A3747AF", ""), UniqueServiceName(usnString), diff --git a/lighthouse/src/test/java/com/ivanempire/lighthouse/models/search/MulticastSearchRequestTest.kt b/lighthouse/src/test/java/com/ivanempire/lighthouse/models/search/MulticastSearchRequestTest.kt index b82a6ab..f936d33 100644 --- a/lighthouse/src/test/java/com/ivanempire/lighthouse/models/search/MulticastSearchRequestTest.kt +++ b/lighthouse/src/test/java/com/ivanempire/lighthouse/models/search/MulticastSearchRequestTest.kt @@ -2,10 +2,10 @@ package com.ivanempire.lighthouse.models.search import com.ivanempire.lighthouse.models.packets.MediaHost import com.ivanempire.lighthouse.models.packets.StartLine -import org.junit.Assert.assertEquals -import org.junit.Test import java.net.InetAddress import java.util.UUID +import org.junit.Assert.assertEquals +import org.junit.Test /** Tests [MulticastSearchRequest] creation and conversion */ class MulticastSearchRequestTest { @@ -51,29 +51,33 @@ class MulticastSearchRequestTest { @Test fun `given valid values correctly creates string`() { - val baseSearchRequest = MulticastSearchRequest( - hostname = MediaHost(InetAddress.getByName("239.255.255.250"), 1900), - mx = 5, - searchTarget = "ssdp:all", - osVersion = null, - productVersion = null, - friendlyName = "LighthouseClient", - uuid = UUID.nameUUIDFromBytes("LighthouseClient".toByteArray()), - ) - val baseResultString = "${StartLine.SEARCH.rawString}\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nMX: 5\r\nST: ssdp:all\r\nCPFN.UPNP.ORG: LighthouseClient\r\nCPUUID.UPNP.ORG: 747f550a-8dec-33a1-8470-e314bf440695\r\n\r\n" + val baseSearchRequest = + MulticastSearchRequest( + hostname = MediaHost(InetAddress.getByName("239.255.255.250"), 1900), + mx = 5, + searchTarget = "ssdp:all", + osVersion = null, + productVersion = null, + friendlyName = "LighthouseClient", + uuid = UUID.nameUUIDFromBytes("LighthouseClient".toByteArray()), + ) + val baseResultString = + "${StartLine.SEARCH.rawString}\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nMX: 5\r\nST: ssdp:all\r\nCPFN.UPNP.ORG: LighthouseClient\r\nCPUUID.UPNP.ORG: 747f550a-8dec-33a1-8470-e314bf440695\r\n\r\n" assertEquals(baseResultString, baseSearchRequest.toString()) - val completeSearchRequest = MulticastSearchRequest( - hostname = MediaHost(InetAddress.getByName("239.255.255.250"), 1900), - mx = 5, - searchTarget = "ssdp:all", - osVersion = "Windows/NT5.0", - productVersion = "GUPnP/1.0.5", - friendlyName = "LighthouseClient", - uuid = UUID.nameUUIDFromBytes("LighthouseClient".toByteArray()), - ) - val completeResultString = "${StartLine.SEARCH.rawString}\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nMX: 5\r\nST: ssdp:all\r\nUSER-AGENT: Windows/NT5.0 UPnP/2.0 GUPnP/1.0.5\r\nCPFN.UPNP.ORG: LighthouseClient\r\nCPUUID.UPNP.ORG: 747f550a-8dec-33a1-8470-e314bf440695\r\n\r\n" + val completeSearchRequest = + MulticastSearchRequest( + hostname = MediaHost(InetAddress.getByName("239.255.255.250"), 1900), + mx = 5, + searchTarget = "ssdp:all", + osVersion = "Windows/NT5.0", + productVersion = "GUPnP/1.0.5", + friendlyName = "LighthouseClient", + uuid = UUID.nameUUIDFromBytes("LighthouseClient".toByteArray()), + ) + val completeResultString = + "${StartLine.SEARCH.rawString}\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nMX: 5\r\nST: ssdp:all\r\nUSER-AGENT: Windows/NT5.0 UPnP/2.0 GUPnP/1.0.5\r\nCPFN.UPNP.ORG: LighthouseClient\r\nCPUUID.UPNP.ORG: 747f550a-8dec-33a1-8470-e314bf440695\r\n\r\n" assertEquals(completeResultString, completeSearchRequest.toString()) } diff --git a/lighthouse/src/test/java/com/ivanempire/lighthouse/models/search/UnicastSearchRequestTest.kt b/lighthouse/src/test/java/com/ivanempire/lighthouse/models/search/UnicastSearchRequestTest.kt index 04a5deb..247f7ee 100644 --- a/lighthouse/src/test/java/com/ivanempire/lighthouse/models/search/UnicastSearchRequestTest.kt +++ b/lighthouse/src/test/java/com/ivanempire/lighthouse/models/search/UnicastSearchRequestTest.kt @@ -2,9 +2,9 @@ package com.ivanempire.lighthouse.models.search import com.ivanempire.lighthouse.models.packets.MediaHost import com.ivanempire.lighthouse.models.packets.StartLine +import java.net.InetAddress import org.junit.Assert.assertEquals import org.junit.Test -import java.net.InetAddress /** Tests [UnicastSearchRequest] creation and conversion */ class UnicastSearchRequestTest { @@ -21,24 +21,28 @@ class UnicastSearchRequestTest { @Test fun `given valid values correctly creates string`() { - val baseSearchRequest = UnicastSearchRequest( - hostname = MediaHost(InetAddress.getByName("239.255.255.250"), 1900), - searchTarget = "ssdp:all", - osVersion = null, - productVersion = null, - ) - val baseResultString = "${StartLine.SEARCH.rawString}\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nST: ssdp:all\r\n\r\n" + val baseSearchRequest = + UnicastSearchRequest( + hostname = MediaHost(InetAddress.getByName("239.255.255.250"), 1900), + searchTarget = "ssdp:all", + osVersion = null, + productVersion = null, + ) + val baseResultString = + "${StartLine.SEARCH.rawString}\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nST: ssdp:all\r\n\r\n" assertEquals(baseResultString, baseSearchRequest.toString()) - val completeSearchRequest = UnicastSearchRequest( - hostname = MediaHost(InetAddress.getByName("239.255.255.250"), 1900), - searchTarget = "ssdp:all", - osVersion = "Windows/NT5.0", - productVersion = "GUPnP/1.0.5", - ) + val completeSearchRequest = + UnicastSearchRequest( + hostname = MediaHost(InetAddress.getByName("239.255.255.250"), 1900), + searchTarget = "ssdp:all", + osVersion = "Windows/NT5.0", + productVersion = "GUPnP/1.0.5", + ) - val completeResultString = "${StartLine.SEARCH.rawString}\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nST: ssdp:all\r\nUSER-AGENT: Windows/NT5.0 UPnP/2.0 GUPnP/1.0.5\r\n\r\n" + val completeResultString = + "${StartLine.SEARCH.rawString}\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nST: ssdp:all\r\nUSER-AGENT: Windows/NT5.0 UPnP/2.0 GUPnP/1.0.5\r\n\r\n" assertEquals(completeResultString, completeSearchRequest.toString()) } diff --git a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/DatagramPacketTransformerTest.kt b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/DatagramPacketTransformerTest.kt index 81afb60..8fdfe2a 100644 --- a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/DatagramPacketTransformerTest.kt +++ b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/DatagramPacketTransformerTest.kt @@ -6,15 +6,13 @@ import com.ivanempire.lighthouse.parsers.DatagramPacketTransformerTest.Fixtures. import com.ivanempire.lighthouse.parsers.DatagramPacketTransformerTest.Fixtures.VALID_ALIVE_PACKET import com.ivanempire.lighthouse.parsers.DatagramPacketTransformerTest.Fixtures.VALID_BYEBYE_PACKET import com.ivanempire.lighthouse.parsers.DatagramPacketTransformerTest.Fixtures.VALID_UPDATE_PACKET +import java.net.DatagramPacket import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Test -import java.net.DatagramPacket -/** - * Tests [DatagramPacketTransformer] - */ +/** Tests [DatagramPacketTransformer] */ class DatagramPacketTransformerTest { @Test @@ -61,21 +59,29 @@ class DatagramPacketTransformerTest { object Fixtures { val EMPTY_DATAGRAM = DatagramPacket(byteArrayOf(), 0) val INVALID_START_LINE = DatagramPacket("NOTIFYHTTP/1.1".toByteArray(), 14) - val VALID_ALIVE_PACKET = DatagramPacket( - "NOTIFY * HTTP/1.1\r\nHost: 239.255.255.250:1900\r\nCache-Control: max-age=1800\r\nLocation: http://192.168.1.190:8091/b9783ad2-d548-9793-0eb9-42db373ade07.xml\r\nServer: Linux/3.18.71+ UPnP/1.0 GUPnP/1.0.5\r\nNTS: ssdp:alive\r\nNT: urn:schemas-upnp-org:service:RenderingControl:1\r\nUSN: uuid:b9783ad2-d548-9793-0eb9-42db373ade07::urn:schemas-upnp-org:service:RenderingControl:1\r\nEcosystem.bose.com:ECO2".toByteArray(), - 386, - ) - val VALID_UPDATE_PACKET = DatagramPacket( - "NOTIFY * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nNT: urn:dial-multiscreen-org:service:dial:1\r\nNTS: ssdp:update\r\nLOCATION: http://192.168.1.160:8060/dial/dd.xml\r\nUSN: uuid:0175c106-5400-10f8-802d-b0a7374360b7::urn:dial-multiscreen-org:service:dial:1\r\nBOOTID.UPNP.ORG: 10\r\nCONFIGID.UPNP.ORG: 36\r\nNEXTBOOTID.UPNP.ORG: 11\r\nSEARCHPORT.UPNP.ORG: 1900\r\nSECURELOCATION.UPNP.ORG: https://192.168.1.160:8060/dial/dd.xml\r\n".toByteArray(), - 406, - ) - val VALID_BYEBYE_PACKET = DatagramPacket( - "NOTIFY * HTTP/1.1\r\nHost: 239.255.255.250:1900\r\nLOCATION: http://192.168.1.160:8060/dial/dd.xml\r\nNTS: ssdp:byebye\r\nNT: urn:schemas-upnp-org:service:RenderingControl:1\r\nUSN: uuid:b9783ad2-d548-9793-0eb9-42db373ade07::urn:schemas-upnp-org:service:RenderingControl:1".toByteArray(), - 213, - ) - val DATAGRAM_PACKET_NO_LOCATION = DatagramPacket( - "HTTP/1.1 200 OK\r\nCACHE-CONTROL: max-age=1900\r\nST: upnp:rootdevice\r\nUSN: uuid:73796E6F-6473-6D00-0000-00113296d0d7::upnp:rootdevice\r\nEXT:\r\nSERVER: Synology/DSM/192.168.2.41\r\nOPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n01-NLS: 1\r\nBOOTID.UPNP.ORG: 1\r\nCONFIGID.UPNP.ORG: 1337\r\n".toByteArray(), - 278, - ) + val VALID_ALIVE_PACKET = + DatagramPacket( + "NOTIFY * HTTP/1.1\r\nHost: 239.255.255.250:1900\r\nCache-Control: max-age=1800\r\nLocation: http://192.168.1.190:8091/b9783ad2-d548-9793-0eb9-42db373ade07.xml\r\nServer: Linux/3.18.71+ UPnP/1.0 GUPnP/1.0.5\r\nNTS: ssdp:alive\r\nNT: urn:schemas-upnp-org:service:RenderingControl:1\r\nUSN: uuid:b9783ad2-d548-9793-0eb9-42db373ade07::urn:schemas-upnp-org:service:RenderingControl:1\r\nEcosystem.bose.com:ECO2" + .toByteArray(), + 386, + ) + val VALID_UPDATE_PACKET = + DatagramPacket( + "NOTIFY * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nNT: urn:dial-multiscreen-org:service:dial:1\r\nNTS: ssdp:update\r\nLOCATION: http://192.168.1.160:8060/dial/dd.xml\r\nUSN: uuid:0175c106-5400-10f8-802d-b0a7374360b7::urn:dial-multiscreen-org:service:dial:1\r\nBOOTID.UPNP.ORG: 10\r\nCONFIGID.UPNP.ORG: 36\r\nNEXTBOOTID.UPNP.ORG: 11\r\nSEARCHPORT.UPNP.ORG: 1900\r\nSECURELOCATION.UPNP.ORG: https://192.168.1.160:8060/dial/dd.xml\r\n" + .toByteArray(), + 406, + ) + val VALID_BYEBYE_PACKET = + DatagramPacket( + "NOTIFY * HTTP/1.1\r\nHost: 239.255.255.250:1900\r\nLOCATION: http://192.168.1.160:8060/dial/dd.xml\r\nNTS: ssdp:byebye\r\nNT: urn:schemas-upnp-org:service:RenderingControl:1\r\nUSN: uuid:b9783ad2-d548-9793-0eb9-42db373ade07::urn:schemas-upnp-org:service:RenderingControl:1" + .toByteArray(), + 213, + ) + val DATAGRAM_PACKET_NO_LOCATION = + DatagramPacket( + "HTTP/1.1 200 OK\r\nCACHE-CONTROL: max-age=1900\r\nST: upnp:rootdevice\r\nUSN: uuid:73796E6F-6473-6D00-0000-00113296d0d7::upnp:rootdevice\r\nEXT:\r\nSERVER: Synology/DSM/192.168.2.41\r\nOPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n01-NLS: 1\r\nBOOTID.UPNP.ORG: 1\r\nCONFIGID.UPNP.ORG: 1337\r\n" + .toByteArray(), + 278, + ) } } diff --git a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/LighthouseStateTest.kt b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/LighthouseStateTest.kt index eff26d7..25c94f9 100644 --- a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/LighthouseStateTest.kt +++ b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/LighthouseStateTest.kt @@ -7,6 +7,8 @@ import com.ivanempire.lighthouse.parsers.TestUtils.generateAlivePacket import com.ivanempire.lighthouse.parsers.TestUtils.generateByeByePacket import com.ivanempire.lighthouse.parsers.TestUtils.generateUSN import com.ivanempire.lighthouse.parsers.TestUtils.generateUpdatePacket +import java.net.URL +import java.util.UUID import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -14,18 +16,13 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test -import java.net.URL -import java.util.UUID /** Tests [LighthouseState] */ class LighthouseStateTest { private lateinit var sut: LighthouseState - @Before - fun setup() = runTest { - sut = LighthouseState() - } + @Before fun setup() = runTest { sut = LighthouseState() } @Test fun `given ALIVE packet creates root device correctly`() = runTest { @@ -61,7 +58,11 @@ class LighthouseStateTest { sut.parseMediaPacket(ALIVE_PACKET_1) sut.parseMediaPacket(ALIVE_PACKET_2) - val ALIVE_PACKET_3 = generateAlivePacket(deviceUUID = RANDOM_UUID_2, uniqueServiceName = generateUSN(RANDOM_UUID_2)) + val ALIVE_PACKET_3 = + generateAlivePacket( + deviceUUID = RANDOM_UUID_2, + uniqueServiceName = generateUSN(RANDOM_UUID_2) + ) sut.parseMediaPacket(ALIVE_PACKET_3) @@ -73,7 +74,14 @@ class LighthouseStateTest { @Test fun `given UPDATE packet builds root device correctly`() = runTest { val RANDOM_UUID_1 = UUID.randomUUID().toString() - val UPDATE_PACKET_1 = generateUpdatePacket(deviceUUID = RANDOM_UUID_1, location = URL("http://127.0.0.1:9999/"), bootId = 200, configId = 300, secureLocation = URL("https://127.0.0.1:9999/")) + val UPDATE_PACKET_1 = + generateUpdatePacket( + deviceUUID = RANDOM_UUID_1, + location = URL("http://127.0.0.1:9999/"), + bootId = 200, + configId = 300, + secureLocation = URL("https://127.0.0.1:9999/") + ) sut.parseMediaPacket(UPDATE_PACKET_1) @@ -95,7 +103,11 @@ class LighthouseStateTest { val initialDevice = sut.deviceList.first()[0] assertTrue(initialDevice.deviceList.isEmpty()) - val UPDATE_PACKET_1 = generateUpdatePacket(deviceUUID = RANDOM_UUID_1, uniqueServiceName = generateUSN(deviceUUID = RANDOM_UUID_1)) + val UPDATE_PACKET_1 = + generateUpdatePacket( + deviceUUID = RANDOM_UUID_1, + uniqueServiceName = generateUSN(deviceUUID = RANDOM_UUID_1) + ) sut.parseMediaPacket(UPDATE_PACKET_1) val actualDevice = sut.deviceList.first()[0] @@ -105,8 +117,17 @@ class LighthouseStateTest { @Test fun `given UPDATE packet updates embedded components correctly`() = runTest { val RANDOM_UUID_1 = UUID.randomUUID().toString() - val ALIVE_PACKET_1 = generateAlivePacket(deviceUUID = RANDOM_UUID_1, uniqueServiceName = generateUSN(deviceUUID = RANDOM_UUID_1)) - val UPDATE_PACKET_1 = generateUpdatePacket(deviceUUID = RANDOM_UUID_1, uniqueServiceName = generateUSN(deviceUUID = RANDOM_UUID_1, version = "4.5")) + val ALIVE_PACKET_1 = + generateAlivePacket( + deviceUUID = RANDOM_UUID_1, + uniqueServiceName = generateUSN(deviceUUID = RANDOM_UUID_1) + ) + val UPDATE_PACKET_1 = + generateUpdatePacket( + deviceUUID = RANDOM_UUID_1, + uniqueServiceName = + generateUSN(deviceUUID = RANDOM_UUID_1, version = "4.5") + ) sut.parseMediaPacket(ALIVE_PACKET_1) sut.parseMediaPacket(UPDATE_PACKET_1) @@ -134,14 +155,20 @@ class LighthouseStateTest { fun `given BYEBYE packet removes embedded components correctly`() = runTest { val RANDOM_UUID_1 = UUID.randomUUID().toString() val EMBEDDED_SERVICE_1 = generateUSN(deviceUUID = RANDOM_UUID_1) - val ALIVE_PACKET_1 = generateAlivePacket(deviceUUID = RANDOM_UUID_1, uniqueServiceName = EMBEDDED_SERVICE_1) - val UPDATE_PACKET_1 = generateUpdatePacket(deviceUUID = RANDOM_UUID_1, uniqueServiceName = generateUSN(deviceUUID = RANDOM_UUID_1)) + val ALIVE_PACKET_1 = + generateAlivePacket(deviceUUID = RANDOM_UUID_1, uniqueServiceName = EMBEDDED_SERVICE_1) + val UPDATE_PACKET_1 = + generateUpdatePacket( + deviceUUID = RANDOM_UUID_1, + uniqueServiceName = generateUSN(deviceUUID = RANDOM_UUID_1) + ) sut.parseMediaPacket(ALIVE_PACKET_1) sut.parseMediaPacket(UPDATE_PACKET_1) assertEquals(1, sut.deviceList.first()[0].serviceList.size) - val BYEBYE_PACKET_1 = generateByeByePacket(deviceUUID = RANDOM_UUID_1, uniqueServiceName = EMBEDDED_SERVICE_1) + val BYEBYE_PACKET_1 = + generateByeByePacket(deviceUUID = RANDOM_UUID_1, uniqueServiceName = EMBEDDED_SERVICE_1) sut.parseMediaPacket(BYEBYE_PACKET_1) assertTrue(sut.deviceList.first()[0].serviceList.isEmpty()) diff --git a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/TestUtils.kt b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/TestUtils.kt index 653508c..a6cef15 100644 --- a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/TestUtils.kt +++ b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/TestUtils.kt @@ -28,7 +28,6 @@ object TestUtils { * @param embeddedServices List of [EmbeddedService] instances to add to the media device * @param cache TTL in seconds of the media device * @param latestTimestamp Timestamp of the latest received media packet targeting this device - * * @return An instance of [AbridgedMediaDevice] to use during unit testing */ fun generateMediaDevice( @@ -55,9 +54,7 @@ object TestUtils { ) } - /** - * Generates an instance of [AliveMediaPacket] - */ + /** Generates an instance of [AliveMediaPacket] */ internal fun generateAlivePacket( host: MediaHost = MediaHost(InetAddress.getByName("239.255.255.250"), 1900), cache: Int = 1900, @@ -84,9 +81,7 @@ object TestUtils { ) } - /** - * Generates an instance of [UpdateMediaPacket] - */ + /** Generates an instance of [UpdateMediaPacket] */ internal fun generateUpdatePacket( host: MediaHost = MediaHost(InetAddress.getByName("239.255.255.250"), 1900), location: URL = URL("http://192.168.2.50:58121/"), @@ -110,9 +105,7 @@ object TestUtils { ) } - /** - * Generates an instance of [ByeByeMediaPacket] - */ + /** Generates an instance of [ByeByeMediaPacket] */ internal fun generateByeByePacket( host: MediaHost = MediaHost(InetAddress.getByName("239.255.255.250"), 1900), deviceUUID: String, @@ -136,7 +129,6 @@ object TestUtils { * @param deviceUUID The device [UUID] to target * @param identifier The name of the component to create * @param version The version string of the component to create - * * @return An instance of [UniqueServiceName] to use in unit testing */ internal inline fun generateUSN( @@ -161,9 +153,10 @@ object TestUtils { } } - private val SERVER_LIST = listOf( - MediaDeviceServer("Windows", "NT/5.0,", "UPnP/1.0"), - MediaDeviceServer("N/A", "N/A", "N/A"), - MediaDeviceServer("Linux/3.18.71+", "UPnP/1.0", "GUPnP/1.0.5"), - ) + private val SERVER_LIST = + listOf( + MediaDeviceServer("Windows", "NT/5.0,", "UPnP/1.0"), + MediaDeviceServer("N/A", "N/A", "N/A"), + MediaDeviceServer("Linux/3.18.71+", "UPnP/1.0", "GUPnP/1.0.5"), + ) } diff --git a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/AliveMediaPacketParserTest.kt b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/AliveMediaPacketParserTest.kt index 89b345d..f15cb03 100644 --- a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/AliveMediaPacketParserTest.kt +++ b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/AliveMediaPacketParserTest.kt @@ -14,10 +14,10 @@ import com.ivanempire.lighthouse.parsers.packets.AliveMediaPacketParserTest.Fixt import com.ivanempire.lighthouse.parsers.packets.AliveMediaPacketParserTest.Fixtures.VALID_ALIVE_PACKET_HEADER_SET_1 import com.ivanempire.lighthouse.parsers.packets.AliveMediaPacketParserTest.Fixtures.VALID_ALIVE_PACKET_HEADER_SET_2 import com.ivanempire.lighthouse.parsers.packets.AliveMediaPacketParserTest.Fixtures.VALID_ALIVE_PACKET_HEADER_SET_3 -import org.junit.Assert.assertEquals -import org.junit.Test import java.net.InetAddress import java.net.URL +import org.junit.Assert.assertEquals +import org.junit.Test /** Tests [AliveMediaPacketParser] */ class AliveMediaPacketParserTest { @@ -165,56 +165,66 @@ class AliveMediaPacketParserTest { object Fixtures { - val PARTIAL_ALIVE_PACKET_HEADER_SET = hashMapOf( - HeaderKeys.NOTIFICATION_TYPE to "upnp:rootdevice", - HeaderKeys.CACHE_CONTROL to "max-age=900", - HeaderKeys.HOST to "239.255.255.250:1900", - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", - HeaderKeys.SERVER to "Windows NT/5.0, UPnP/1.0", - HeaderKeys.LOCATION to "http://192.168.2.50:58121/", - ) - - val VALID_ALIVE_PACKET_HEADER_SET_1 = hashMapOf( - HeaderKeys.HOST to "239.255.255.250:1900", - HeaderKeys.CACHE_CONTROL to "max-age=1800", - HeaderKeys.LOCATION to "http://192.168.1.190:8091/b9783ad2-d548-9793-0eb9-42db373ade07.xml", - HeaderKeys.SERVER to "Linux/3.18.71+ UPnP/1.0 GUPnP/1.0.5", - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", - HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:RenderingControl:1", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:b9783ad2-d548-9793-0eb9-42db373ade07::urn:schemas-upnp-org:service:RenderingControl:1", - HeaderKeys.BOOT_ID to "11", - HeaderKeys.CONFIG_ID to "120", - HeaderKeys.SEARCH_PORT to "1900", - HeaderKeys.SECURE_LOCATION to "https://192.168.1.190:8091/b9783ad2-d548-9793-0eb9-42db373ade07.xml", - ) - - val VALID_ALIVE_PACKET_HEADER_SET_2 = hashMapOf( - HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:SwitchPower:1", - HeaderKeys.CACHE_CONTROL to "max-age=900", - HeaderKeys.HOST to "239.255.255.250:1900", - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::urn:schemas-upnp-org:service:SwitchPower:1", - HeaderKeys.SERVER to "Windows/NT/5.0 UPnP/1.0", - HeaderKeys.LOCATION to "http://127.0.0.1:58122/", - HeaderKeys.BOOT_ID to "156", - HeaderKeys.CONFIG_ID to "144", - HeaderKeys.SEARCH_PORT to "1900", - HeaderKeys.SECURE_LOCATION to "https://127.0.0.1:58122/", - ) - - val VALID_ALIVE_PACKET_HEADER_SET_3 = hashMapOf( - HeaderKeys.HOST to "239.255.255.250:1900", - HeaderKeys.CACHE_CONTROL to "max-age=450", - HeaderKeys.LOCATION to "http://192.168.2.50:58121/", - HeaderKeys.SERVER to "Windows/NT/5.0 UPnP/1.0", - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", - HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:Dimming:1", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::urn:schemas-upnp-org:service:Dimming:1", - HeaderKeys.BOOT_ID to "5", - HeaderKeys.CONFIG_ID to "200", - HeaderKeys.SEARCH_PORT to "2100", - HeaderKeys.SECURE_LOCATION to "https://192.168.2.50:58121/", - ) + val PARTIAL_ALIVE_PACKET_HEADER_SET = + hashMapOf( + HeaderKeys.NOTIFICATION_TYPE to "upnp:rootdevice", + HeaderKeys.CACHE_CONTROL to "max-age=900", + HeaderKeys.HOST to "239.255.255.250:1900", + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", + HeaderKeys.SERVER to "Windows NT/5.0, UPnP/1.0", + HeaderKeys.LOCATION to "http://192.168.2.50:58121/", + ) + + val VALID_ALIVE_PACKET_HEADER_SET_1 = + hashMapOf( + HeaderKeys.HOST to "239.255.255.250:1900", + HeaderKeys.CACHE_CONTROL to "max-age=1800", + HeaderKeys.LOCATION to + "http://192.168.1.190:8091/b9783ad2-d548-9793-0eb9-42db373ade07.xml", + HeaderKeys.SERVER to "Linux/3.18.71+ UPnP/1.0 GUPnP/1.0.5", + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", + HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:RenderingControl:1", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:b9783ad2-d548-9793-0eb9-42db373ade07::urn:schemas-upnp-org:service:RenderingControl:1", + HeaderKeys.BOOT_ID to "11", + HeaderKeys.CONFIG_ID to "120", + HeaderKeys.SEARCH_PORT to "1900", + HeaderKeys.SECURE_LOCATION to + "https://192.168.1.190:8091/b9783ad2-d548-9793-0eb9-42db373ade07.xml", + ) + + val VALID_ALIVE_PACKET_HEADER_SET_2 = + hashMapOf( + HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:SwitchPower:1", + HeaderKeys.CACHE_CONTROL to "max-age=900", + HeaderKeys.HOST to "239.255.255.250:1900", + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::urn:schemas-upnp-org:service:SwitchPower:1", + HeaderKeys.SERVER to "Windows/NT/5.0 UPnP/1.0", + HeaderKeys.LOCATION to "http://127.0.0.1:58122/", + HeaderKeys.BOOT_ID to "156", + HeaderKeys.CONFIG_ID to "144", + HeaderKeys.SEARCH_PORT to "1900", + HeaderKeys.SECURE_LOCATION to "https://127.0.0.1:58122/", + ) + + val VALID_ALIVE_PACKET_HEADER_SET_3 = + hashMapOf( + HeaderKeys.HOST to "239.255.255.250:1900", + HeaderKeys.CACHE_CONTROL to "max-age=450", + HeaderKeys.LOCATION to "http://192.168.2.50:58121/", + HeaderKeys.SERVER to "Windows/NT/5.0 UPnP/1.0", + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", + HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:Dimming:1", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::urn:schemas-upnp-org:service:Dimming:1", + HeaderKeys.BOOT_ID to "5", + HeaderKeys.CONFIG_ID to "200", + HeaderKeys.SEARCH_PORT to "2100", + HeaderKeys.SECURE_LOCATION to "https://192.168.2.50:58121/", + ) } } diff --git a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/ByeByeMediaPacketParserTest.kt b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/ByeByeMediaPacketParserTest.kt index ffcacd4..c66dafe 100644 --- a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/ByeByeMediaPacketParserTest.kt +++ b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/ByeByeMediaPacketParserTest.kt @@ -13,9 +13,9 @@ import com.ivanempire.lighthouse.parsers.packets.ByeByeMediaPacketParserTest.Fix import com.ivanempire.lighthouse.parsers.packets.ByeByeMediaPacketParserTest.Fixtures.VALID_BYEBYE_PACKET_HEADER_SET_1 import com.ivanempire.lighthouse.parsers.packets.ByeByeMediaPacketParserTest.Fixtures.VALID_BYEBYE_PACKET_HEADER_SET_2 import com.ivanempire.lighthouse.parsers.packets.ByeByeMediaPacketParserTest.Fixtures.VALID_BYEBYE_PACKET_HEADER_SET_3 +import java.net.InetAddress import org.junit.Assert.assertEquals import org.junit.Test -import java.net.InetAddress /** Tests [ByeByeMediaPacketParser] */ class ByeByeMediaPacketParserTest { @@ -126,38 +126,46 @@ class ByeByeMediaPacketParserTest { } object Fixtures { - val PARTIAL_BYEBYE_PACKET_HEADER_SET = hashMapOf( - HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:RenderingControl:1", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:byebye", - HeaderKeys.BOOT_ID to "100", - ) - - val VALID_BYEBYE_PACKET_HEADER_SET_1 = hashMapOf( - HeaderKeys.HOST to "239.255.255.250:1900", - HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-microsoft-com:nhed:presence:1", - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:byebye", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:00000000-0000-0000-0200-00125A8A0960::urn:schemas-microsoft-com:device:presence:1", - HeaderKeys.BOOT_ID to "200", - HeaderKeys.CONFIG_ID to "50", - ) - - val VALID_BYEBYE_PACKET_HEADER_SET_2 = hashMapOf( - HeaderKeys.HOST to "239.255.255.250:1900", - HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:RenderingControl:1", - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:byebye", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:9ab0c000-f668-11de-9976-00a0ded0e859::urn:schemas-upnp-org:service:RenderingControl:1", - HeaderKeys.BOOT_ID to "4", - HeaderKeys.CONFIG_ID to "45", - ) - - val VALID_BYEBYE_PACKET_HEADER_SET_3 = hashMapOf( - HeaderKeys.HOST to "239.255.255.250:1900", - HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:SwitchPower:1", - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:byebye", - HeaderKeys.UNIQUE_SERVICE_NAME to "urn:upnp-org:serviceId:SwitchPower.0001::urn:schemas-upnp-org:service:SwitchPower:1", - HeaderKeys.BOOT_ID to "9", - HeaderKeys.CONFIG_ID to "55", - ) + val PARTIAL_BYEBYE_PACKET_HEADER_SET = + hashMapOf( + HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:RenderingControl:1", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:byebye", + HeaderKeys.BOOT_ID to "100", + ) + + val VALID_BYEBYE_PACKET_HEADER_SET_1 = + hashMapOf( + HeaderKeys.HOST to "239.255.255.250:1900", + HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-microsoft-com:nhed:presence:1", + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:byebye", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:00000000-0000-0000-0200-00125A8A0960::urn:schemas-microsoft-com:device:presence:1", + HeaderKeys.BOOT_ID to "200", + HeaderKeys.CONFIG_ID to "50", + ) + + val VALID_BYEBYE_PACKET_HEADER_SET_2 = + hashMapOf( + HeaderKeys.HOST to "239.255.255.250:1900", + HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:RenderingControl:1", + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:byebye", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:9ab0c000-f668-11de-9976-00a0ded0e859::urn:schemas-upnp-org:service:RenderingControl:1", + HeaderKeys.BOOT_ID to "4", + HeaderKeys.CONFIG_ID to "45", + ) + + val VALID_BYEBYE_PACKET_HEADER_SET_3 = + hashMapOf( + HeaderKeys.HOST to "239.255.255.250:1900", + HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:SwitchPower:1", + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:byebye", + HeaderKeys.UNIQUE_SERVICE_NAME to + "urn:upnp-org:serviceId:SwitchPower.0001::urn:schemas-upnp-org:service:SwitchPower:1", + HeaderKeys.BOOT_ID to "9", + HeaderKeys.CONFIG_ID to "55", + ) } } diff --git a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/MediaPacketParserTest.kt b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/MediaPacketParserTest.kt index 9adf6b7..b5fd17c 100644 --- a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/MediaPacketParserTest.kt +++ b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/MediaPacketParserTest.kt @@ -63,56 +63,68 @@ class MediaPacketParserTest { assertEquals("-1", parsedPacket.extraHeaders["apple.com"]) } -// @Test(expected = IllegalStateException::class) -// fun `given invalid header set throws IllegalStateException`() { -// val parsedPacket = MediaPacketParser(INVALID_PACKET_NTS_HEADER) -// -// assertTrue(parsedPacket is ByeByeMediaPacket) -// } + // @Test(expected = IllegalStateException::class) + // fun `given invalid header set throws IllegalStateException`() { + // val parsedPacket = MediaPacketParser(INVALID_PACKET_NTS_HEADER) + // + // assertTrue(parsedPacket is ByeByeMediaPacket) + // } object Fixtures { - val ALIVE_PACKET_NTS_HEADER = hashMapOf( - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", - ) - - val UPDATE_PACKET_NTS_HEADER = hashMapOf( - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:update", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", - ) - - val BYEBYE_PACKET_NTS_HEADER = hashMapOf( - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:byebye", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", - ) - - val INVALID_PACKET_NTS_HEADER = hashMapOf( - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:invalid", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", - ) - - val VALID_ALIVE_PACKET_HEADER_SET = hashMapOf( - HeaderKeys.HOST to "239.255.255.250:1900", - HeaderKeys.CACHE_CONTROL to "max-age=450", - HeaderKeys.LOCATION to "http://192.168.2.50:58121/", - HeaderKeys.SERVER to "Windows/NT/5.0 UPnP/1.0", - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", - HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:Dimming:1", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::urn:schemas-upnp-org:service:Dimming:1", - HeaderKeys.BOOT_ID to "5", - HeaderKeys.CONFIG_ID to "200", - HeaderKeys.SEARCH_PORT to "2100", - HeaderKeys.SECURE_LOCATION to "https://192.168.2.50:58121/", - "Ecosystem.bose.com" to "ECO2", - ) - - val VALID_ALIVE_PACKET_EXTRA_HEADER_SET = hashMapOf( - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", - "Ecosystem.bose.com" to "ECO2", - "custom.header.com" to "whatever", - "microsoft.com" to "someValue", - "apple.com" to "-1", - ) + val ALIVE_PACKET_NTS_HEADER = + hashMapOf( + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", + ) + + val UPDATE_PACKET_NTS_HEADER = + hashMapOf( + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:update", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", + ) + + val BYEBYE_PACKET_NTS_HEADER = + hashMapOf( + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:byebye", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", + ) + + val INVALID_PACKET_NTS_HEADER = + hashMapOf( + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:invalid", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", + ) + + val VALID_ALIVE_PACKET_HEADER_SET = + hashMapOf( + HeaderKeys.HOST to "239.255.255.250:1900", + HeaderKeys.CACHE_CONTROL to "max-age=450", + HeaderKeys.LOCATION to "http://192.168.2.50:58121/", + HeaderKeys.SERVER to "Windows/NT/5.0 UPnP/1.0", + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", + HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:Dimming:1", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::urn:schemas-upnp-org:service:Dimming:1", + HeaderKeys.BOOT_ID to "5", + HeaderKeys.CONFIG_ID to "200", + HeaderKeys.SEARCH_PORT to "2100", + HeaderKeys.SECURE_LOCATION to "https://192.168.2.50:58121/", + "Ecosystem.bose.com" to "ECO2", + ) + + val VALID_ALIVE_PACKET_EXTRA_HEADER_SET = + hashMapOf( + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::upnp:rootdevice", + "Ecosystem.bose.com" to "ECO2", + "custom.header.com" to "whatever", + "microsoft.com" to "someValue", + "apple.com" to "-1", + ) } } diff --git a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/UpdateMediaPacketParserTest.kt b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/UpdateMediaPacketParserTest.kt index dcde445..66279b8 100644 --- a/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/UpdateMediaPacketParserTest.kt +++ b/lighthouse/src/test/java/com/ivanempire/lighthouse/parsers/packets/UpdateMediaPacketParserTest.kt @@ -12,10 +12,10 @@ import com.ivanempire.lighthouse.parsers.packets.UpdateMediaPacketParserTest.Fix import com.ivanempire.lighthouse.parsers.packets.UpdateMediaPacketParserTest.Fixtures.VALID_UPDATE_PACKET_HEADER_SET_1 import com.ivanempire.lighthouse.parsers.packets.UpdateMediaPacketParserTest.Fixtures.VALID_UPDATE_PACKET_HEADER_SET_2 import com.ivanempire.lighthouse.parsers.packets.UpdateMediaPacketParserTest.Fixtures.VALID_UPDATE_PACKET_HEADER_SET_3 -import org.junit.Assert.assertEquals -import org.junit.Test import java.net.InetAddress import java.net.URL +import org.junit.Assert.assertEquals +import org.junit.Test /** Tests [UpdateMediaPacketParser] */ class UpdateMediaPacketParserTest { @@ -151,48 +151,57 @@ class UpdateMediaPacketParserTest { } object Fixtures { - val VALID_UPDATE_PACKET_HEADER_SET = hashMapOf( - HeaderKeys.HOST to "239.255.255.250:1900", - HeaderKeys.LOCATION to "http://127.0.0.1:58122/", - HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:SwitchPower:1", - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:update", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:b9783ad2-d548-9793-0eb9-42db373ade07::urn:schemas-upnp-org:service:SwitchPower:1", - HeaderKeys.BOOT_ID to "100", - HeaderKeys.CONFIG_ID to "30", - HeaderKeys.NEXT_BOOT_ID to "101", - HeaderKeys.SEARCH_PORT to "1900", - HeaderKeys.SECURE_LOCATION to "https://127.0.0.1:58122/", - ) - - val VALID_UPDATE_PACKET_HEADER_SET_1 = hashMapOf( - HeaderKeys.HOST to "239.255.255.250:1900", - HeaderKeys.LOCATION to "http://192.168.1.1:47343/rootDesc.xml", - HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:WANPPPConnection:1", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:3ddcd1d3-2380-45f5-b069-2c4d54008cf2::urn:schemas-upnp-org:service:WANPPPConnection:1", - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:update", - HeaderKeys.BOOT_ID to "1525511561", - HeaderKeys.CONFIG_ID to "1337", - ) - - val VALID_UPDATE_PACKET_HEADER_SET_2 = hashMapOf( - HeaderKeys.HOST to "239.255.255.250:1900", - HeaderKeys.LOCATION to "http://192.168.1.190:8091/b9783ad2-d548-9793-0eb9-42db373ade07.xml", - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", - HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:RenderingControl:1", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:b9783ad2-d548-9793-0eb9-42db373ade07::urn:schemas-upnp-org:service:RenderingControl:1", - ) - - val VALID_UPDATE_PACKET_HEADER_SET_3 = hashMapOf( - HeaderKeys.HOST to "239.255.255.250:1900", - HeaderKeys.LOCATION to "http://192.168.2.50:58121/", - HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:Dimming:1", - HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:update", - HeaderKeys.UNIQUE_SERVICE_NAME to "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::urn:schemas-upnp-org:service:Dimming:1", - HeaderKeys.BOOT_ID to "50", - HeaderKeys.CONFIG_ID to "454", - HeaderKeys.NEXT_BOOT_ID to "51", - HeaderKeys.SEARCH_PORT to "1900", - HeaderKeys.SECURE_LOCATION to "https://192.168.2.50:58121/", - ) + val VALID_UPDATE_PACKET_HEADER_SET = + hashMapOf( + HeaderKeys.HOST to "239.255.255.250:1900", + HeaderKeys.LOCATION to "http://127.0.0.1:58122/", + HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:SwitchPower:1", + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:update", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:b9783ad2-d548-9793-0eb9-42db373ade07::urn:schemas-upnp-org:service:SwitchPower:1", + HeaderKeys.BOOT_ID to "100", + HeaderKeys.CONFIG_ID to "30", + HeaderKeys.NEXT_BOOT_ID to "101", + HeaderKeys.SEARCH_PORT to "1900", + HeaderKeys.SECURE_LOCATION to "https://127.0.0.1:58122/", + ) + + val VALID_UPDATE_PACKET_HEADER_SET_1 = + hashMapOf( + HeaderKeys.HOST to "239.255.255.250:1900", + HeaderKeys.LOCATION to "http://192.168.1.1:47343/rootDesc.xml", + HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:WANPPPConnection:1", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:3ddcd1d3-2380-45f5-b069-2c4d54008cf2::urn:schemas-upnp-org:service:WANPPPConnection:1", + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:update", + HeaderKeys.BOOT_ID to "1525511561", + HeaderKeys.CONFIG_ID to "1337", + ) + + val VALID_UPDATE_PACKET_HEADER_SET_2 = + hashMapOf( + HeaderKeys.HOST to "239.255.255.250:1900", + HeaderKeys.LOCATION to + "http://192.168.1.190:8091/b9783ad2-d548-9793-0eb9-42db373ade07.xml", + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:alive", + HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:RenderingControl:1", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:b9783ad2-d548-9793-0eb9-42db373ade07::urn:schemas-upnp-org:service:RenderingControl:1", + ) + + val VALID_UPDATE_PACKET_HEADER_SET_3 = + hashMapOf( + HeaderKeys.HOST to "239.255.255.250:1900", + HeaderKeys.LOCATION to "http://192.168.2.50:58121/", + HeaderKeys.NOTIFICATION_TYPE to "urn:schemas-upnp-org:service:Dimming:1", + HeaderKeys.NOTIFICATION_SUBTYPE to "ssdp:update", + HeaderKeys.UNIQUE_SERVICE_NAME to + "uuid:3f8744cd-30bf-4fc9-8a42-bad80ae660c1::urn:schemas-upnp-org:service:Dimming:1", + HeaderKeys.BOOT_ID to "50", + HeaderKeys.CONFIG_ID to "454", + HeaderKeys.NEXT_BOOT_ID to "51", + HeaderKeys.SEARCH_PORT to "1900", + HeaderKeys.SECURE_LOCATION to "https://192.168.2.50:58121/", + ) } }