diff --git a/app/src/main/java/com/futo/platformplayer/Utility.kt b/app/src/main/java/com/futo/platformplayer/Utility.kt index 3eea4f26..02b4e01a 100644 --- a/app/src/main/java/com/futo/platformplayer/Utility.kt +++ b/app/src/main/java/com/futo/platformplayer/Utility.kt @@ -32,6 +32,7 @@ import java.io.IOException import java.io.InputStream import java.io.OutputStream import java.nio.ByteBuffer +import java.nio.ByteOrder import java.util.* import java.util.concurrent.ThreadLocalRandom @@ -232,4 +233,10 @@ fun String.decodeUnicode(): String { i++ } return sb.toString() -} \ No newline at end of file +} + +fun ByteBuffer.toUtf8String(): String { + val remainingBytes = ByteArray(remaining()) + get(remainingBytes) + return String(remainingBytes, Charsets.UTF_8) +} diff --git a/app/src/main/java/com/futo/platformplayer/states/StateSync.kt b/app/src/main/java/com/futo/platformplayer/states/StateSync.kt index 4de1b41c..89c3b6c5 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateSync.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateSync.kt @@ -284,12 +284,16 @@ class StateSync { return@SyncSocketSession } - Logger.i(TAG, "Handshake complete with ${s.remotePublicKey}") + Logger.i(TAG, "Handshake complete with (LocalPublicKey = ${s.localPublicKey}, RemotePublicKey = ${s.remotePublicKey})") synchronized(_sessions) { session = _sessions[s.remotePublicKey] if (session == null) { - session = SyncSession(remotePublicKey, onAuthorized = { + session = SyncSession(remotePublicKey, onAuthorized = { it, isNewlyAuthorized, isNewSession -> + if (!isNewSession) { + return@SyncSession + } + Logger.i(TAG, "${s.remotePublicKey} authorized") synchronized(_lastAddressStorage) { _lastAddressStorage.setAndSave(remotePublicKey, s.remoteAddress) @@ -358,6 +362,16 @@ class StateSync { } }) } + } else { + val publicKey = session!!.remotePublicKey + session!!.unauthorize(s) + session!!.close() + + synchronized(_sessions) { + _sessions.remove(publicKey) + } + + Logger.i(TAG, "Connection unauthorized for ${remotePublicKey} because not authorized and not on pairing activity to ask") } } else { //Responder does not need to check because already approved diff --git a/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSession.kt b/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSession.kt index cca42a54..15b69fcd 100644 --- a/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSession.kt +++ b/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSession.kt @@ -21,6 +21,7 @@ import com.futo.platformplayer.sync.models.SendToDevicePackage import com.futo.platformplayer.sync.models.SyncPlaylistsPackage import com.futo.platformplayer.sync.models.SyncSubscriptionGroupsPackage import com.futo.platformplayer.sync.models.SyncSubscriptionsPackage +import com.futo.platformplayer.toUtf8String import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString @@ -30,6 +31,7 @@ import java.nio.ByteBuffer import java.time.Instant import java.time.OffsetDateTime import java.time.ZoneOffset +import java.util.UUID interface IAuthorizable { val isAuthorized: Boolean @@ -39,13 +41,16 @@ class SyncSession : IAuthorizable { private val _socketSessions: MutableList = mutableListOf() private var _authorized: Boolean = false private var _remoteAuthorized: Boolean = false - private val _onAuthorized: (session: SyncSession) -> Unit + private val _onAuthorized: (session: SyncSession, isNewlyAuthorized: Boolean, isNewSession: Boolean) -> Unit private val _onUnauthorized: (session: SyncSession) -> Unit private val _onClose: (session: SyncSession) -> Unit private val _onConnectedChanged: (session: SyncSession, connected: Boolean) -> Unit val remotePublicKey: String override val isAuthorized get() = _authorized && _remoteAuthorized private var _wasAuthorized = false + private val _id = UUID.randomUUID() + private var _remoteId: UUID? = null + private var _lastAuthorizedRemoteId: UUID? = null var connected: Boolean = false private set(v) { @@ -55,7 +60,7 @@ class SyncSession : IAuthorizable { } } - constructor(remotePublicKey: String, onAuthorized: (session: SyncSession) -> Unit, onUnauthorized: (session: SyncSession) -> Unit, onConnectedChanged: (session: SyncSession, connected: Boolean) -> Unit, onClose: (session: SyncSession) -> Unit) { + constructor(remotePublicKey: String, onAuthorized: (session: SyncSession, isNewlyAuthorized: Boolean, isNewSession: Boolean) -> Unit, onUnauthorized: (session: SyncSession) -> Unit, onConnectedChanged: (session: SyncSession, connected: Boolean) -> Unit, onClose: (session: SyncSession) -> Unit) { this.remotePublicKey = remotePublicKey _onAuthorized = onAuthorized _onUnauthorized = onUnauthorized @@ -77,7 +82,8 @@ class SyncSession : IAuthorizable { } fun authorize(socketSession: SyncSocketSession) { - socketSession.send(Opcode.NOTIFY_AUTHORIZED.value) + Logger.i(TAG, "Sent AUTHORIZED with session id $_id") + socketSession.send(Opcode.NOTIFY_AUTHORIZED.value, 0u, ByteBuffer.wrap(_id.toString().toByteArray())) _authorized = true checkAuthorized() } @@ -95,9 +101,13 @@ class SyncSession : IAuthorizable { } private fun checkAuthorized() { - if (!_wasAuthorized && isAuthorized) { + if (isAuthorized) { + val isNewlyAuthorized = !_wasAuthorized; + val isNewSession = _lastAuthorizedRemoteId != _remoteId; + Logger.i(TAG, "onAuthorized (isNewlyAuthorized = $isNewlyAuthorized, isNewSession = $isNewSession)"); + _onAuthorized.invoke(this, !_wasAuthorized, _lastAuthorizedRemoteId != _remoteId) _wasAuthorized = true - _onAuthorized.invoke(this) + _lastAuthorizedRemoteId = _remoteId } } @@ -126,12 +136,19 @@ class SyncSession : IAuthorizable { when (opcode) { Opcode.NOTIFY_AUTHORIZED.value -> { + val str = data.toUtf8String() + _remoteId = if (data.remaining() >= 0) UUID.fromString(str) else UUID.fromString("00000000-0000-0000-0000-000000000000") _remoteAuthorized = true + Logger.i(TAG, "Received AUTHORIZED with session id $_remoteId") checkAuthorized() + return } Opcode.NOTIFY_UNAUTHORIZED.value -> { + _remoteId = null + _lastAuthorizedRemoteId = null _remoteAuthorized = false _onUnauthorized(this) + return } //TODO: Handle any kind of packet (that is not necessarily authorized) } diff --git a/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSocketSession.kt b/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSocketSession.kt index 8b5f305a..585f9562 100644 --- a/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSocketSession.kt +++ b/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSocketSession.kt @@ -10,6 +10,7 @@ import com.futo.platformplayer.noise.protocol.HandshakeState import com.futo.platformplayer.states.StateSync import java.nio.ByteBuffer import java.nio.ByteOrder +import java.util.UUID class SyncSocketSession { enum class Opcode(val value: UByte) {