mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-09-27 11:49:03 +00:00
Added separate error status code for transport rejection. Added unhandled exception handler for relay loop. Added additional booleans to keep track of the server/relay connections being up/down. Added additional messaging to let the user know when something is wrong.
This commit is contained in:
parent
5d44f0f2b6
commit
94ab3da0e4
5 changed files with 198 additions and 134 deletions
|
@ -9,6 +9,7 @@ import android.widget.LinearLayout
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
|
import com.futo.platformplayer.UIDialogs
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.setNavigationBarColorAndIcons
|
import com.futo.platformplayer.setNavigationBarColorAndIcons
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
|
@ -100,12 +101,18 @@ class SyncHomeActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StateSync.instance.confirmStarted(this, {
|
StateSync.instance.confirmStarted(this, onStarted = {
|
||||||
StateSync.instance.showFailedToBindDialogIfNecessary(this@SyncHomeActivity)
|
if (StateSync.instance.syncService?.serverSocketFailedToStart == true) {
|
||||||
}, {
|
UIDialogs.toast(this, "Server socket failed to start, is the port in use?", true)
|
||||||
|
}
|
||||||
|
if (StateSync.instance.syncService?.relayConnected == false) {
|
||||||
|
UIDialogs.toast(this, "Not connected to relay, remote connections will work.", false)
|
||||||
|
}
|
||||||
|
if (StateSync.instance.syncService?.serverSocketStarted == false) {
|
||||||
|
UIDialogs.toast(this, "Listener not started, local connections will not work.", false)
|
||||||
|
}
|
||||||
|
}, onNotStarted = {
|
||||||
finish()
|
finish()
|
||||||
}, {
|
|
||||||
StateSync.instance.showFailedToBindDialogIfNecessary(this@SyncHomeActivity)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -412,24 +412,12 @@ class StateApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings.instance.synchronization.enabled) {
|
if (Settings.instance.synchronization.enabled) {
|
||||||
StateSync.instance.start(context, {
|
StateSync.instance.start(context)
|
||||||
try {
|
|
||||||
UIDialogs.toast("Failed to start sync, port in use")
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
//Ignored
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsActivityClosed.subscribe {
|
settingsActivityClosed.subscribe {
|
||||||
if (Settings.instance.synchronization.enabled) {
|
if (Settings.instance.synchronization.enabled) {
|
||||||
StateSync.instance.start(context, {
|
StateSync.instance.start(context)
|
||||||
try {
|
|
||||||
UIDialogs.toast("Failed to start sync, port in use")
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
//Ignored
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
StateSync.instance.stop()
|
StateSync.instance.stop()
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ class StateSync {
|
||||||
val deviceRemoved: Event1<String> = Event1()
|
val deviceRemoved: Event1<String> = Event1()
|
||||||
val deviceUpdatedOrAdded: Event2<String, SyncSession> = Event2()
|
val deviceUpdatedOrAdded: Event2<String, SyncSession> = Event2()
|
||||||
|
|
||||||
fun start(context: Context, onServerBindFail: () -> Unit) {
|
fun start(context: Context) {
|
||||||
if (syncService != null) {
|
if (syncService != null) {
|
||||||
Logger.i(TAG, "Already started.")
|
Logger.i(TAG, "Already started.")
|
||||||
return
|
return
|
||||||
|
@ -150,24 +150,14 @@ class StateSync {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syncService?.start(context, onServerBindFail)
|
syncService?.start(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showFailedToBindDialogIfNecessary(context: Context) {
|
fun confirmStarted(context: Context, onStarted: () -> Unit, onNotStarted: () -> Unit) {
|
||||||
if (syncService?.serverSocketFailedToStart == true && Settings.instance.synchronization.localConnections) {
|
|
||||||
try {
|
|
||||||
UIDialogs.showDialogOk(context, R.drawable.ic_warning, "Local discovery unavailable, port was in use")
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
//Ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun confirmStarted(context: Context, onStarted: () -> Unit, onNotStarted: () -> Unit, onServerBindFail: () -> Unit) {
|
|
||||||
if (syncService == null) {
|
if (syncService == null) {
|
||||||
UIDialogs.showConfirmationDialog(context, "Sync has not been enabled yet, would you like to enable sync?", {
|
UIDialogs.showConfirmationDialog(context, "Sync has not been enabled yet, would you like to enable sync?", {
|
||||||
Settings.instance.synchronization.enabled = true
|
Settings.instance.synchronization.enabled = true
|
||||||
start(context, onServerBindFail)
|
start(context)
|
||||||
Settings.instance.save()
|
Settings.instance.save()
|
||||||
onStarted.invoke()
|
onStarted.invoke()
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -72,6 +72,8 @@ class SyncService(
|
||||||
private val _lastConnectTimesMdns: MutableMap<String, Long> = mutableMapOf()
|
private val _lastConnectTimesMdns: MutableMap<String, Long> = mutableMapOf()
|
||||||
private val _lastConnectTimesIp: MutableMap<String, Long> = mutableMapOf()
|
private val _lastConnectTimesIp: MutableMap<String, Long> = mutableMapOf()
|
||||||
var serverSocketFailedToStart = false
|
var serverSocketFailedToStart = false
|
||||||
|
var serverSocketStarted = false
|
||||||
|
var relayConnected = false
|
||||||
//TODO: Should sync mdns and casting mdns be merged?
|
//TODO: Should sync mdns and casting mdns be merged?
|
||||||
//TODO: Decrease interval that devices are updated
|
//TODO: Decrease interval that devices are updated
|
||||||
//TODO: Send less data
|
//TODO: Send less data
|
||||||
|
@ -212,7 +214,7 @@ class SyncService(
|
||||||
var onData: ((SyncSession, UByte, UByte, ByteBuffer) -> Unit)? = null
|
var onData: ((SyncSession, UByte, UByte, ByteBuffer) -> Unit)? = null
|
||||||
var authorizePrompt: ((String, (Boolean) -> Unit) -> Unit)? = null
|
var authorizePrompt: ((String, (Boolean) -> Unit) -> Unit)? = null
|
||||||
|
|
||||||
fun start(context: Context, onServerBindFail: (() -> Unit)? = null) {
|
fun start(context: Context) {
|
||||||
if (_started) {
|
if (_started) {
|
||||||
Logger.i(TAG, "Already started.")
|
Logger.i(TAG, "Already started.")
|
||||||
return
|
return
|
||||||
|
@ -273,10 +275,12 @@ class SyncService(
|
||||||
|
|
||||||
Logger.i(TAG, "Sync key pair initialized (public key = $publicKey)")
|
Logger.i(TAG, "Sync key pair initialized (public key = $publicKey)")
|
||||||
|
|
||||||
|
serverSocketStarted = false
|
||||||
if (settings.bindListener) {
|
if (settings.bindListener) {
|
||||||
startListener(onServerBindFail)
|
startListener()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
relayConnected = false
|
||||||
if (settings.relayEnabled) {
|
if (settings.relayEnabled) {
|
||||||
startRelayLoop()
|
startRelayLoop()
|
||||||
}
|
}
|
||||||
|
@ -286,13 +290,15 @@ class SyncService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startListener(onServerBindFail: (() -> Unit)? = null) {
|
private fun startListener() {
|
||||||
serverSocketFailedToStart = false
|
serverSocketFailedToStart = false
|
||||||
|
serverSocketStarted = false
|
||||||
_thread = Thread {
|
_thread = Thread {
|
||||||
try {
|
try {
|
||||||
val serverSocket = ServerSocket(settings.listenerPort)
|
val serverSocket = ServerSocket(settings.listenerPort)
|
||||||
_serverSocket = serverSocket
|
_serverSocket = serverSocket
|
||||||
|
|
||||||
|
serverSocketStarted = true
|
||||||
Log.i(TAG, "Running on port ${settings.listenerPort} (TCP)")
|
Log.i(TAG, "Running on port ${settings.listenerPort} (TCP)")
|
||||||
|
|
||||||
while (_started) {
|
while (_started) {
|
||||||
|
@ -300,10 +306,12 @@ class SyncService(
|
||||||
val session = createSocketSession(socket, true)
|
val session = createSocketSession(socket, true)
|
||||||
session.startAsResponder()
|
session.startAsResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serverSocketStarted = false
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Logger.e(TAG, "Failed to bind server socket to port ${settings.listenerPort}", e)
|
Logger.e(TAG, "Failed to bind server socket to port ${settings.listenerPort}", e)
|
||||||
serverSocketFailedToStart = true
|
serverSocketFailedToStart = true
|
||||||
onServerBindFail?.invoke()
|
serverSocketStarted = false
|
||||||
}
|
}
|
||||||
}.apply { start() }
|
}.apply { start() }
|
||||||
}
|
}
|
||||||
|
@ -386,121 +394,192 @@ class SyncService(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startRelayLoop() {
|
private fun startRelayLoop() {
|
||||||
|
relayConnected = false
|
||||||
_threadRelay = Thread {
|
_threadRelay = Thread {
|
||||||
var backoffs: Array<Long> = arrayOf(1000, 5000, 10000, 20000)
|
try {
|
||||||
var backoffIndex = 0;
|
var backoffs: Array<Long> = arrayOf(1000, 5000, 10000, 20000)
|
||||||
|
var backoffIndex = 0;
|
||||||
|
|
||||||
while (_started) {
|
while (_started) {
|
||||||
try {
|
try {
|
||||||
Log.i(TAG, "Starting relay session...")
|
Log.i(TAG, "Starting relay session...")
|
||||||
|
relayConnected = false
|
||||||
|
|
||||||
var socketClosed = false;
|
var socketClosed = false;
|
||||||
val socket = Socket(relayServer, 9000)
|
val socket = Socket(relayServer, 9000)
|
||||||
_relaySession = SyncSocketSession(
|
_relaySession = SyncSocketSession(
|
||||||
(socket.remoteSocketAddress as InetSocketAddress).address.hostAddress!!,
|
(socket.remoteSocketAddress as InetSocketAddress).address.hostAddress!!,
|
||||||
keyPair!!,
|
keyPair!!,
|
||||||
socket,
|
socket,
|
||||||
isHandshakeAllowed = { linkType, syncSocketSession, publicKey, pairingCode, appId -> isHandshakeAllowed(linkType, syncSocketSession, publicKey, pairingCode, appId) },
|
isHandshakeAllowed = { linkType, syncSocketSession, publicKey, pairingCode, appId ->
|
||||||
onNewChannel = { _, c ->
|
isHandshakeAllowed(
|
||||||
val remotePublicKey = c.remotePublicKey
|
linkType,
|
||||||
if (remotePublicKey == null) {
|
syncSocketSession,
|
||||||
Log.e(TAG, "Remote public key should never be null in onNewChannel.")
|
publicKey,
|
||||||
return@SyncSocketSession
|
pairingCode,
|
||||||
}
|
appId
|
||||||
|
)
|
||||||
Log.i(TAG, "New channel established from relay (pk: '$remotePublicKey').")
|
},
|
||||||
|
onNewChannel = { _, c ->
|
||||||
var session: SyncSession?
|
val remotePublicKey = c.remotePublicKey
|
||||||
synchronized(_sessions) {
|
if (remotePublicKey == null) {
|
||||||
session = _sessions[remotePublicKey]
|
Log.e(
|
||||||
if (session == null) {
|
TAG,
|
||||||
val remoteDeviceName = database.getDeviceName(remotePublicKey)
|
"Remote public key should never be null in onNewChannel."
|
||||||
session = createNewSyncSession(remotePublicKey, remoteDeviceName)
|
)
|
||||||
_sessions[remotePublicKey] = session!!
|
return@SyncSocketSession
|
||||||
}
|
}
|
||||||
session!!.addChannel(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.setDataHandler { _, channel, opcode, subOpcode, data ->
|
Log.i(
|
||||||
session?.handlePacket(opcode, subOpcode, data)
|
TAG,
|
||||||
}
|
"New channel established from relay (pk: '$remotePublicKey')."
|
||||||
c.setCloseHandler { channel ->
|
)
|
||||||
session?.removeChannel(channel)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onChannelEstablished = { _, channel, isResponder ->
|
|
||||||
handleAuthorization(channel, isResponder)
|
|
||||||
},
|
|
||||||
onClose = { socketClosed = true },
|
|
||||||
onHandshakeComplete = { relaySession ->
|
|
||||||
backoffIndex = 0
|
|
||||||
|
|
||||||
Thread {
|
var session: SyncSession?
|
||||||
try {
|
synchronized(_sessions) {
|
||||||
while (_started && !socketClosed) {
|
session = _sessions[remotePublicKey]
|
||||||
val unconnectedAuthorizedDevices = database.getAllAuthorizedDevices()?.filter { !isConnected(it) }?.toTypedArray() ?: arrayOf()
|
if (session == null) {
|
||||||
relaySession.publishConnectionInformation(unconnectedAuthorizedDevices, settings.listenerPort, settings.relayConnectDirect, false, false, settings.relayConnectRelayed)
|
val remoteDeviceName =
|
||||||
|
database.getDeviceName(remotePublicKey)
|
||||||
|
session =
|
||||||
|
createNewSyncSession(remotePublicKey, remoteDeviceName)
|
||||||
|
_sessions[remotePublicKey] = session!!
|
||||||
|
}
|
||||||
|
session!!.addChannel(c)
|
||||||
|
}
|
||||||
|
|
||||||
Logger.v(TAG, "Requesting ${unconnectedAuthorizedDevices.size} devices connection information")
|
c.setDataHandler { _, channel, opcode, subOpcode, data ->
|
||||||
val connectionInfos = runBlocking { relaySession.requestBulkConnectionInfo(unconnectedAuthorizedDevices) }
|
session?.handlePacket(opcode, subOpcode, data)
|
||||||
Logger.v(TAG, "Received ${connectionInfos.size} devices connection information")
|
}
|
||||||
|
c.setCloseHandler { channel ->
|
||||||
|
session?.removeChannel(channel)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChannelEstablished = { _, channel, isResponder ->
|
||||||
|
handleAuthorization(channel, isResponder)
|
||||||
|
},
|
||||||
|
onClose = { socketClosed = true },
|
||||||
|
onHandshakeComplete = { relaySession ->
|
||||||
|
backoffIndex = 0
|
||||||
|
|
||||||
for ((targetKey, connectionInfo) in connectionInfos) {
|
Thread {
|
||||||
val potentialLocalAddresses = connectionInfo.ipv4Addresses
|
try {
|
||||||
.filter { it != connectionInfo.remoteIp }
|
while (_started && !socketClosed) {
|
||||||
if (connectionInfo.allowLocalDirect && Settings.instance.synchronization.connectLocalDirectThroughRelay) {
|
val unconnectedAuthorizedDevices =
|
||||||
Thread {
|
database.getAllAuthorizedDevices()
|
||||||
|
?.filter { !isConnected(it) }?.toTypedArray()
|
||||||
|
?: arrayOf()
|
||||||
|
relaySession.publishConnectionInformation(
|
||||||
|
unconnectedAuthorizedDevices,
|
||||||
|
settings.listenerPort,
|
||||||
|
settings.relayConnectDirect,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
settings.relayConnectRelayed
|
||||||
|
)
|
||||||
|
|
||||||
|
Logger.v(
|
||||||
|
TAG,
|
||||||
|
"Requesting ${unconnectedAuthorizedDevices.size} devices connection information"
|
||||||
|
)
|
||||||
|
val connectionInfos = runBlocking {
|
||||||
|
relaySession.requestBulkConnectionInfo(
|
||||||
|
unconnectedAuthorizedDevices
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Logger.v(
|
||||||
|
TAG,
|
||||||
|
"Received ${connectionInfos.size} devices connection information"
|
||||||
|
)
|
||||||
|
|
||||||
|
for ((targetKey, connectionInfo) in connectionInfos) {
|
||||||
|
val potentialLocalAddresses =
|
||||||
|
connectionInfo.ipv4Addresses
|
||||||
|
.filter { it != connectionInfo.remoteIp }
|
||||||
|
if (connectionInfo.allowLocalDirect && Settings.instance.synchronization.connectLocalDirectThroughRelay) {
|
||||||
|
Thread {
|
||||||
|
try {
|
||||||
|
Log.v(
|
||||||
|
TAG,
|
||||||
|
"Attempting to connect directly, locally to '$targetKey'."
|
||||||
|
)
|
||||||
|
connect(
|
||||||
|
potentialLocalAddresses.map { it }
|
||||||
|
.toTypedArray(),
|
||||||
|
settings.listenerPort,
|
||||||
|
targetKey,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e(
|
||||||
|
TAG,
|
||||||
|
"Failed to start direct connection using connection info with $targetKey.",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connectionInfo.allowRemoteDirect) {
|
||||||
|
// TODO: Implement direct remote connection if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connectionInfo.allowRemoteHolePunched) {
|
||||||
|
// TODO: Implement hole punching if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connectionInfo.allowRemoteRelayed && Settings.instance.synchronization.connectThroughRelay) {
|
||||||
try {
|
try {
|
||||||
Log.v(TAG, "Attempting to connect directly, locally to '$targetKey'.")
|
Logger.v(
|
||||||
connect(potentialLocalAddresses.map { it }.toTypedArray(), settings.listenerPort, targetKey, null)
|
TAG,
|
||||||
|
"Attempting relayed connection with '$targetKey'."
|
||||||
|
)
|
||||||
|
runBlocking {
|
||||||
|
relaySession.startRelayedChannel(
|
||||||
|
targetKey,
|
||||||
|
appId,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.e(TAG, "Failed to start direct connection using connection info with $targetKey.", e)
|
Logger.e(
|
||||||
|
TAG,
|
||||||
|
"Failed to start relayed channel with $targetKey.",
|
||||||
|
e
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connectionInfo.allowRemoteDirect) {
|
|
||||||
// TODO: Implement direct remote connection if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connectionInfo.allowRemoteHolePunched) {
|
|
||||||
// TODO: Implement hole punching if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connectionInfo.allowRemoteRelayed && Settings.instance.synchronization.connectThroughRelay) {
|
|
||||||
try {
|
|
||||||
Logger.v(TAG, "Attempting relayed connection with '$targetKey'.")
|
|
||||||
runBlocking { relaySession.startRelayedChannel(targetKey, appId, null) }
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Logger.e(TAG, "Failed to start relayed channel with $targetKey.", e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Thread.sleep(15000)
|
||||||
}
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
Thread.sleep(15000)
|
Logger.e(TAG, "Unhandled exception in relay session.", e)
|
||||||
|
relaySession.stop()
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
}.start()
|
||||||
Logger.e(TAG, "Unhandled exception in relay session.", e)
|
}
|
||||||
relaySession.stop()
|
)
|
||||||
}
|
|
||||||
}.start()
|
_relaySession!!.authorizable = object : IAuthorizable {
|
||||||
|
override val isAuthorized: Boolean get() = true
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
_relaySession!!.authorizable = object : IAuthorizable {
|
relayConnected = true
|
||||||
override val isAuthorized: Boolean get() = true
|
_relaySession!!.runAsInitiator(relayPublicKey, appId, null)
|
||||||
|
|
||||||
|
Log.i(TAG, "Started relay session.")
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e(TAG, "Relay session failed.", e)
|
||||||
|
} finally {
|
||||||
|
relayConnected = false
|
||||||
|
_relaySession?.stop()
|
||||||
|
_relaySession = null
|
||||||
|
Thread.sleep(backoffs[min(backoffs.size - 1, backoffIndex++)])
|
||||||
}
|
}
|
||||||
|
|
||||||
_relaySession!!.runAsInitiator(relayPublicKey, appId, null)
|
|
||||||
|
|
||||||
Log.i(TAG, "Started relay session.")
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Log.e(TAG, "Relay session failed.", e)
|
|
||||||
} finally {
|
|
||||||
_relaySession?.stop()
|
|
||||||
_relaySession = null
|
|
||||||
Thread.sleep(backoffs[min(backoffs.size - 1, backoffIndex++)])
|
|
||||||
}
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
Log.i(TAG, "Unhandled exception in relay loop.", ex)
|
||||||
}
|
}
|
||||||
}.apply { start() }
|
}.apply { start() }
|
||||||
}
|
}
|
||||||
|
|
|
@ -529,7 +529,7 @@ class SyncSocketSession {
|
||||||
val isAllowed = publicKey != _localPublicKey && (_isHandshakeAllowed?.invoke(LinkType.Relayed, this, publicKey, pairingCode, appId) ?: true)
|
val isAllowed = publicKey != _localPublicKey && (_isHandshakeAllowed?.invoke(LinkType.Relayed, this, publicKey, pairingCode, appId) ?: true)
|
||||||
if (!isAllowed) {
|
if (!isAllowed) {
|
||||||
val rp = ByteBuffer.allocate(16).order(ByteOrder.LITTLE_ENDIAN)
|
val rp = ByteBuffer.allocate(16).order(ByteOrder.LITTLE_ENDIAN)
|
||||||
rp.putInt(2) // Status code for not allowed
|
rp.putInt(7) // Status code for not allowed
|
||||||
rp.putLong(connectionId)
|
rp.putLong(connectionId)
|
||||||
rp.putInt(requestId)
|
rp.putInt(requestId)
|
||||||
rp.rewind()
|
rp.rewind()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue