diff --git a/.idea/compiler.xml b/.idea/compiler.xml index fb7f4a8..b589d56 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..0c0c338 --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index eb81c22..654045d 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,18 +4,16 @@ diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..7e340a7 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index a44d119..17bc113 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,16 +1,16 @@ - - + diff --git a/app/build.gradle b/app/build.gradle index 041765a..b7d494c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,6 @@ apply plugin: 'kotlin-kapt' android { compileSdkVersion 31 - buildToolsVersion "30.0.2" defaultConfig { applicationId "de.csicar.ning" minSdkVersion 23 @@ -24,6 +23,7 @@ android { buildFeatures { viewBinding true } + namespace 'de.csicar.ning' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 657ba57..9a9a826 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> diff --git a/app/src/main/java/de/csicar/ning/Dao.kt b/app/src/main/java/de/csicar/ning/Dao.kt index dec7651..3665406 100644 --- a/app/src/main/java/de/csicar/ning/Dao.kt +++ b/app/src/main/java/de/csicar/ning/Dao.kt @@ -114,7 +114,7 @@ interface PortDao { fun insert(port: Port): Long @Transaction - suspend fun upsert(port: Port): Long { + fun upsert(port: Port): Long { val portFromDB = getPortFromNumber(port.deviceId, port.port) ?: return insert(port) update(Port(portFromDB.portId, port.port, port.protocol, port.deviceId)) @@ -135,7 +135,7 @@ interface PortDao { @Dao interface ScanDao { @Insert - suspend fun insert(scan: Scan): Long + fun insert(scan: Scan): Long @Query("Select * FROM SCAN") fun getAll(): LiveData> diff --git a/app/src/main/java/de/csicar/ning/DeviceInfoFragment.kt b/app/src/main/java/de/csicar/ning/DeviceInfoFragment.kt index 8eeb2dd..73ea26d 100644 --- a/app/src/main/java/de/csicar/ning/DeviceInfoFragment.kt +++ b/app/src/main/java/de/csicar/ning/DeviceInfoFragment.kt @@ -3,16 +3,16 @@ package de.csicar.ning import android.content.Intent import android.net.Uri import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button +import android.widget.ProgressBar import android.widget.TextView import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.fragment.app.viewModels import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import de.csicar.ning.scanner.PortScanner import de.csicar.ning.ui.RecyclerViewCommon @@ -20,6 +20,9 @@ import de.csicar.ning.util.AppPreferences import de.csicar.ning.util.CopyUtil //import kotlinx.android.synthetic.main.fragment_port_item.view.* import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -30,6 +33,10 @@ import kotlinx.coroutines.withContext * [DeviceInfoFragment.OnListFragmentInteractionListener] interface. */ class DeviceInfoFragment : Fragment() { + companion object { + val TAG: String = DeviceInfoFragment::class.java.name + } + val viewModel: ScanViewModel by activityViewModels() lateinit var scanAllPortsButton: Button @@ -47,6 +54,7 @@ class DeviceInfoFragment : Fragment() { val deviceNameTextView = view.findViewById(R.id.deviceNameTextView) val deviceHwAddressTextView = view.findViewById(R.id.deviceHwAddressTextView) val deviceVendorTextView = view.findViewById(R.id.deviceVendorTextView) + val portScanProgressBar = view.findViewById(R.id.portScanProgressBar) copyUtil.makeTextViewCopyable((deviceTypeTextView)) copyUtil.makeTextViewCopyable((deviceIpTextView)) @@ -54,8 +62,10 @@ class DeviceInfoFragment : Fragment() { copyUtil.makeTextViewCopyable(deviceHwAddressTextView) copyUtil.makeTextViewCopyable(deviceVendorTextView) - viewModel.deviceDao.getById(argumentDeviceId).observe(this, Observer { - fetchInfo(it.asDevice) + viewModel.deviceDao.getById(argumentDeviceId).observe(viewLifecycleOwner, Observer { + fetchCommonPorts(it.asDevice) { progress -> + portScanProgressBar.progress = (progress * 1000).toInt() + } deviceTypeTextView.text = getString(it.deviceType.label) deviceIpTextView.text = it.ip.hostAddress deviceNameTextView.text = if (it.isScanningDevice) { @@ -70,7 +80,18 @@ class DeviceInfoFragment : Fragment() { val ports = viewModel.portDao.getAllForDevice(argumentDeviceId) - recyclerView.setHandler(context!!, this, object : + this.scanAllPortsButton = view.findViewById(R.id.scanAllPorts) + + this.scanAllPortsButton.setOnClickListener { + viewModel.viewModelScope.launch(context = Dispatchers.IO) { + Log.d(TAG, "scan all ports") + fetchAllPorts(viewModel.deviceDao.getByIdNow(argumentDeviceId)) { + portScanProgressBar.progress = (it * 1000).toInt() + } + } + } + + recyclerView.setHandler(requireContext(), this, object : RecyclerViewCommon.Handler(R.layout.fragment_port_item, ports) { override fun shareIdentity(a: Port, b: Port) = a.port == b.port override fun areContentsTheSame(a: Port, b: Port) = a == b @@ -118,12 +139,40 @@ class DeviceInfoFragment : Fragment() { return view } - fun fetchInfo(device: Device) { + private fun fetchAllPorts(device: Device, onProgress: (Double) -> Unit) { + viewModel.viewModelScope.launch { + withContext(Dispatchers.IO) { + Log.d(TAG, "fetchAllPort") + val portScans = PortScanner(device.ip).scanAllPorts() + var currentProgress = 0.0 + + portScans.onEach { + currentProgress += it.progressPortion + Log.d(TAG, "from progress scan $it ($currentProgress)") + onProgress(currentProgress) + }.collect { result: PortScanner.PortResult -> + if (result.isOpen) { + viewModel.portDao.upsert( + Port(0, result.port, result.protocol, device.deviceId) + ) + } + } + } + } + } + + private fun fetchCommonPorts(device: Device, onProgress: (Double) -> Unit) { viewModel.viewModelScope.launch { withContext(Dispatchers.IO) { - PortScanner(device.ip).scanPorts().forEach { + val portScans = PortScanner(device.ip).scanCommonPorts() + var currentProgress = 0.0 + portScans.forEach { launch { val result = it.await() + + currentProgress += 1.0 / portScans.size + onProgress(currentProgress) + if (result.isOpen) { viewModel.portDao.upsert( Port( diff --git a/app/src/main/java/de/csicar/ning/scanner/PortScanner.kt b/app/src/main/java/de/csicar/ning/scanner/PortScanner.kt index 233a961..ead8813 100644 --- a/app/src/main/java/de/csicar/ning/scanner/PortScanner.kt +++ b/app/src/main/java/de/csicar/ning/scanner/PortScanner.kt @@ -1,12 +1,18 @@ package de.csicar.ning.scanner import android.util.Log +import de.csicar.ning.DeviceInfoFragment import de.csicar.ning.Port import de.csicar.ning.PortDescription import de.csicar.ning.Protocol import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.flatMapMerge +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.withContext import java.io.IOException import java.io.InterruptedIOException @@ -48,24 +54,39 @@ class PortScanner(val ip: InetAddress) { return@withContext false } catch (ex: NoRouteToHostException) { Log.d(TAG, "No Route to Host: $ex") - return@withContext false + return@withContext false } finally { socket?.close() } } - suspend fun scanPorts() = withContext(Dispatchers.Main) { - PortDescription.commonPorts.flatMap { - listOf( - async { - PortResult(it.port, Protocol.TCP, isTcpPortOpen(it.port)) - }, - async { - PortResult(it.port, Protocol.UDP, isUdpPortOpen(it.port)) - } - ) + suspend fun scanAllPorts(): Flow { + val range = (1..65535) + return range.asFlow().flatMapMerge(concurrency = 128) { + Log.d(DeviceInfoFragment.TAG, "scan port $it") + flow { + emit(PortResult(it, Protocol.TCP, isTcpPortOpen(it), 1.0 / range.count())) + emit(PortResult(it, Protocol.UDP, isUdpPortOpen(it), 1.0 / range.count())) + } + } + } + + + suspend fun scanCommonPorts() = withContext(Dispatchers.Main) { + val ports = PortDescription.commonPorts + ports.flatMap { + listOf(async { + PortResult(it.port, Protocol.TCP, isTcpPortOpen(it.port), 1.0 / ports.count()) + }, async { + PortResult(it.port, Protocol.UDP, isUdpPortOpen(it.port), 1.0 / ports.count()) + }) } } - data class PortResult(val port: Int, val protocol: Protocol, val isOpen: Boolean) + data class PortResult( + val port: Int, + val protocol: Protocol, + val isOpen: Boolean, + val progressPortion: Double + ) } diff --git a/app/src/main/res/layout/fragment_deviceinfo_list.xml b/app/src/main/res/layout/fragment_deviceinfo_list.xml index 27c6ec9..aa8ccaa 100644 --- a/app/src/main/res/layout/fragment_deviceinfo_list.xml +++ b/app/src/main/res/layout/fragment_deviceinfo_list.xml @@ -144,6 +144,13 @@ android:text="@string/title_open_ports" android:textAppearance="@style/TextAppearance.AppCompat.Body2" /> + + tools:listitem="@layout/fragment_port_item"> + + + +