2020-08-19 15:29:35 +02:00
/ *
2023-02-27 11:28:24 +01:00
* Copyright 2023 dorkbox , llc
2020-08-19 15:29:35 +02:00
*
* Licensed under the Apache License , Version 2.0 ( the " License " ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2020-08-12 23:30:16 +02:00
package dorkbox.network.handshake
2021-07-06 15:38:53 +02:00
import dorkbox.network.Client
2020-08-15 13:21:20 +02:00
import dorkbox.network.connection.Connection
2023-06-16 14:18:42 +02:00
import dorkbox.network.connection.CryptoManagement
2023-09-26 19:53:27 +02:00
import dorkbox.network.connection.EndPoint
2023-04-29 00:46:16 +02:00
import dorkbox.network.connection.ListenerManager.Companion.cleanAllStackTrace
import dorkbox.network.connection.ListenerManager.Companion.cleanStackTraceInternal
2023-06-28 15:12:40 +02:00
import dorkbox.network.exceptions.*
2023-07-03 19:18:34 +02:00
import dorkbox.util.Sys
2020-08-12 23:30:16 +02:00
import io.aeron.FragmentAssembler
2023-02-16 00:50:32 +01:00
import io.aeron.Image
2020-08-12 23:30:16 +02:00
import io.aeron.logbuffer.FragmentHandler
import io.aeron.logbuffer.Header
import org.agrona.DirectBuffer
2023-09-13 16:57:32 +02:00
import org.slf4j.Logger
2020-08-12 23:30:16 +02:00
2021-07-06 15:38:53 +02:00
internal class ClientHandshake < CONNECTION : Connection > (
2023-06-30 00:04:29 +02:00
private val client : Client < CONNECTION > ,
2023-09-13 16:57:32 +02:00
private val logger : Logger
2021-07-06 15:38:53 +02:00
) {
2020-09-10 14:38:54 +02:00
// @Volatile is used BECAUSE suspension of coroutines can continue on a DIFFERENT thread. We want to make sure that thread visibility is
// correct when this happens. There are no race-conditions to be wary of.
2023-06-30 00:04:29 +02:00
private val crypto = client . crypto
2020-09-10 14:38:54 +02:00
private val handler : FragmentHandler
2020-08-12 23:30:16 +02:00
2023-06-30 00:04:29 +02:00
private val handshaker = client . handshaker
2023-05-28 16:53:56 +02:00
2022-05-30 02:45:50 +02:00
// used to keep track and associate UDP/IPC handshakes between client/server
2020-09-28 16:30:38 +02:00
@Volatile
2022-05-30 02:45:50 +02:00
var connectKey = 0L
2020-09-28 16:30:38 +02:00
2020-08-12 23:30:16 +02:00
@Volatile
2020-08-25 17:45:08 +02:00
private var connectionHelloInfo : ClientConnectionInfo ? = null
2020-08-12 23:30:16 +02:00
@Volatile
2020-08-25 17:45:08 +02:00
private var connectionDone = false
2020-08-12 23:30:16 +02:00
2020-09-23 17:08:25 +02:00
@Volatile
private var needToRetry = false
2020-08-15 13:21:20 +02:00
@Volatile
2022-04-04 16:30:05 +02:00
private var failedException : Exception ? = null
2020-08-12 23:30:16 +02:00
2020-08-25 17:45:08 +02:00
init {
2023-06-07 11:49:01 +02:00
// NOTE: subscriptions (ie: reading from buffers, etc) are not thread safe! Because it is ambiguous HOW EXACTLY they are unsafe,
// we exclusively read from the DirectBuffer on a single thread.
// NOTE: Handlers are called on the client conductor thread. The client conductor thread expects handlers to do safe
// publication of any state to other threads and not be:
// - long running
// - re-entrant with the client
2020-08-25 17:45:08 +02:00
handler = FragmentAssembler { buffer : DirectBuffer , offset : Int , length : Int , header : Header ->
// this is processed on the thread that calls "poll". Subscriptions are NOT multi-thread safe!
2022-07-18 05:08:01 +02:00
val sessionId = header . sessionId ( )
val streamId = header . streamId ( )
2023-02-16 00:50:32 +01:00
// note: this address will ALWAYS be an IP:PORT combo OR it will be aeron:ipc (if IPC, it will be a different handler!)
val remoteIpAndPort = ( header . context ( ) as Image ) . sourceIdentity ( )
// split
val splitPoint = remoteIpAndPort . lastIndexOf ( ':' )
val clientAddressString = remoteIpAndPort . substring ( 0 , splitPoint )
2023-06-30 00:04:29 +02:00
val logInfo = " $sessionId / $streamId : $clientAddressString "
2020-08-12 23:30:16 +02:00
2022-04-04 16:30:05 +02:00
failedException = null
needToRetry = false
2023-06-30 00:04:29 +02:00
// ugh, this is verbose -- but necessary
val message = try {
2023-07-02 21:56:46 +02:00
val msg = handshaker . readMessage ( buffer , offset , length )
2023-06-30 00:04:29 +02:00
// VALIDATE:: a Registration object is the only acceptable message during the connection phase
if ( msg !is HandshakeMessage ) {
throw ClientRejectedException ( " [ $logInfo ] Connection not allowed! unrecognized message: $msg " ) . apply { cleanAllStackTrace ( ) }
2023-09-13 16:01:14 +02:00
} else if ( logger . isTraceEnabled ) {
2023-09-13 16:57:32 +02:00
logger . trace ( " [ $logInfo ] ( ${msg.connectKey} ) received HS: $msg " )
2023-06-30 00:04:29 +02:00
}
msg
} catch ( e : Exception ) {
client . listenerManager . notifyError ( ClientHandshakeException ( " [ $logInfo ] Error de-serializing handshake message!! " , e ) )
null
} ?: return @FragmentAssembler
2020-08-25 17:45:08 +02:00
// this is an error message
2020-09-23 17:08:25 +02:00
if ( message . state == HandshakeMessage . INVALID ) {
2023-06-25 12:01:37 +02:00
val cause = ServerException ( message . errorMessage ?: " Unknown " ) . apply { stackTrace = emptyArray ( ) }
2023-06-30 00:04:29 +02:00
failedException = ClientRejectedException ( " [ $logInfo }] ( ${message.connectKey} ) cancelled handshake " , cause )
2023-04-29 00:46:16 +02:00
. apply { cleanAllStackTrace ( ) }
2020-08-25 17:45:08 +02:00
return @FragmentAssembler
}
2020-08-15 13:21:20 +02:00
2022-03-08 08:41:06 +01:00
// this is a retry message
2020-09-23 17:08:25 +02:00
// this can happen if there are multiple connections from the SAME ip address (ie: localhost)
if ( message . state == HandshakeMessage . RETRY ) {
needToRetry = true
return @FragmentAssembler
}
2022-05-30 02:45:50 +02:00
if ( connectKey != message . connectKey ) {
2023-06-30 00:04:29 +02:00
logger . error ( " [ $logInfo ] ( $connectKey ) ignored handshake for ${message.connectKey} (Was for another client) " )
2020-08-25 17:45:08 +02:00
return @FragmentAssembler
}
2020-08-12 23:30:16 +02:00
2020-08-25 17:45:08 +02:00
// it must be the correct state
2020-09-25 19:52:46 +02:00
val registrationData = message . registrationData
2021-04-24 00:07:26 +02:00
2020-08-25 17:45:08 +02:00
when ( message . state ) {
HandshakeMessage . HELLO _ACK -> {
// The message was intended for this client. Try to parse it as one of the available message types.
// this message is ENCRYPTED!
2020-09-25 19:52:46 +02:00
val serverPublicKeyBytes = message . publicKey
if ( registrationData != null && serverPublicKeyBytes != null ) {
connectionHelloInfo = crypto . decrypt ( registrationData , serverPublicKeyBytes )
} else {
2023-06-30 00:04:29 +02:00
failedException = ClientRejectedException ( " [ $logInfo }] ( ${message.connectKey} ) canceled handshake for message without registration and/or public key info " )
2023-04-29 00:46:16 +02:00
. apply { cleanAllStackTrace ( ) }
2020-09-25 19:52:46 +02:00
}
2020-09-02 02:39:05 +02:00
}
HandshakeMessage . HELLO _ACK _IPC -> {
// The message was intended for this client. Try to parse it as one of the available message types.
2022-08-02 12:11:36 +02:00
// this message is NOT-ENCRYPTED!
2023-05-08 09:58:24 +02:00
val serverPublicKeyBytes = message . publicKey
if ( registrationData != null && serverPublicKeyBytes != null ) {
connectionHelloInfo = crypto . nocrypt ( registrationData , serverPublicKeyBytes )
2021-04-24 00:07:26 +02:00
} else {
2023-06-30 00:04:29 +02:00
failedException = ClientRejectedException ( " [ $logInfo }] ( ${message.connectKey} ) canceled handshake for message without registration and/or public key info " )
2023-05-08 09:58:24 +02:00
. apply { cleanAllStackTrace ( ) }
2021-04-24 00:07:26 +02:00
}
2020-08-25 17:45:08 +02:00
}
HandshakeMessage . DONE _ACK -> {
connectionDone = true
}
else -> {
2022-04-04 16:30:05 +02:00
val stateString = HandshakeMessage . toStateString ( message . state )
2023-06-30 00:04:29 +02:00
failedException = ClientRejectedException ( " [ $logInfo ] ( ${message.connectKey} ) cancelled handshake for message that is $stateString " )
2023-04-29 00:46:16 +02:00
. apply { cleanAllStackTrace ( ) }
2020-08-12 23:30:16 +02:00
}
}
2020-08-25 17:45:08 +02:00
}
2020-08-12 23:30:16 +02:00
}
2022-05-30 02:45:50 +02:00
/ * *
* Make sure that NON - ZERO is returned
* /
private fun getSafeConnectKey ( ) : Long {
2023-06-16 14:18:42 +02:00
var key = CryptoManagement . secureRandom . nextLong ( )
2022-05-30 02:45:50 +02:00
while ( key == 0L ) {
2023-06-16 14:18:42 +02:00
key = CryptoManagement . secureRandom . nextLong ( )
2022-05-30 02:45:50 +02:00
}
return key
}
2020-09-09 12:24:04 +02:00
// called from the connect thread
2022-12-17 23:21:44 +01:00
// when exceptions are thrown, the handshake pub/sub will be closed
2023-09-26 19:53:27 +02:00
fun hello (
2023-10-28 20:55:49 +02:00
tagName : String ,
2023-09-26 19:53:27 +02:00
endPoint : EndPoint < CONNECTION > ,
handshakeConnection : ClientHandshakeDriver ,
handshakeTimeoutNs : Long
) : ClientConnectionInfo {
2023-06-25 12:01:37 +02:00
val pubSub = handshakeConnection . pubSub
// is our pub still connected??
if ( ! pubSub . pub . isConnected ) {
throw ClientException ( " Handshake publication is not connected, and it is expected to be connected! " )
}
2023-06-19 14:03:18 +02:00
// always make sure that we reset the state when we start (in the event of reconnects)
reset ( )
2022-05-30 02:45:50 +02:00
connectKey = getSafeConnectKey ( )
2023-06-19 14:03:18 +02:00
2021-07-06 15:38:53 +02:00
try {
2023-06-25 12:01:37 +02:00
// Send the one-time pad to the server.
handshaker . writeMessage ( pubSub . pub , handshakeConnection . details ,
2023-05-28 16:53:56 +02:00
HandshakeMessage . helloFromClient (
connectKey = connectKey ,
2023-07-11 00:12:09 +02:00
publicKey = client . storage . publicKey ,
2023-06-25 12:01:37 +02:00
streamIdSub = pubSub . streamIdSub ,
2023-10-28 20:55:49 +02:00
portSub = pubSub . portSub ,
tagName = tagName
2023-05-28 16:53:56 +02:00
) )
2021-07-06 15:38:53 +02:00
} catch ( e : Exception ) {
2023-09-26 19:53:27 +02:00
handshakeConnection . close ( endPoint )
2023-06-25 12:01:37 +02:00
throw TransmitException ( " $handshakeConnection Handshake message error! " , e )
2021-07-06 15:38:53 +02:00
}
2020-09-28 16:30:38 +02:00
2020-08-12 23:30:16 +02:00
// block until we receive the connection information from the server
2022-03-08 08:41:06 +01:00
val startTime = System . nanoTime ( )
2023-07-03 19:18:34 +02:00
while ( System . nanoTime ( ) - startTime < handshakeTimeoutNs ) {
2020-08-26 16:27:59 +02:00
// NOTE: regarding fragment limit size. Repeated calls to '.poll' will reassemble a fragment.
// `.poll(handler, 4)` == `.poll(handler, 2)` + `.poll(handler, 2)`
2023-06-25 12:01:37 +02:00
pubSub . sub . poll ( handler , 1 )
2020-08-12 23:30:16 +02:00
2022-04-04 16:30:05 +02:00
if ( failedException != null || connectionHelloInfo != null ) {
2020-08-12 23:30:16 +02:00
break
}
2023-09-04 00:47:46 +02:00
Thread . sleep ( 100 )
2020-08-12 23:30:16 +02:00
}
2022-04-04 16:30:05 +02:00
val failedEx = failedException
if ( failedEx != null ) {
2023-09-26 19:53:27 +02:00
handshakeConnection . close ( endPoint )
2022-12-17 23:21:44 +01:00
2023-05-08 09:58:24 +02:00
failedEx . cleanStackTraceInternal ( )
2022-04-04 16:30:05 +02:00
throw failedEx
2021-07-06 15:38:53 +02:00
}
2022-04-04 16:30:59 +02:00
2020-08-12 23:30:16 +02:00
if ( connectionHelloInfo == null ) {
2023-09-26 19:53:27 +02:00
handshakeConnection . close ( endPoint )
2022-12-17 23:21:44 +01:00
2023-07-03 19:18:34 +02:00
val exception = ClientTimedOutException ( " $handshakeConnection Waiting for registration response from server for more than ${Sys.getTimePrettyFull(handshakeTimeoutNs)} " )
2022-04-04 16:30:59 +02:00
throw exception
2020-08-12 23:30:16 +02:00
}
return connectionHelloInfo !!
}
2020-09-09 12:24:04 +02:00
// called from the connect thread
2022-12-17 23:21:44 +01:00
// when exceptions are thrown, the handshake pub/sub will be closed
2023-09-04 00:47:46 +02:00
fun done (
2023-09-26 19:53:27 +02:00
endPoint : EndPoint < CONNECTION > ,
2023-05-08 09:58:24 +02:00
handshakeConnection : ClientHandshakeDriver ,
clientConnection : ClientConnectionDriver ,
2023-07-03 19:18:34 +02:00
handshakeTimeoutNs : Long ,
2023-07-15 13:11:50 +02:00
logInfo : String
2023-05-08 09:58:24 +02:00
) {
2023-06-25 12:01:37 +02:00
val pubSub = clientConnection . connectionInfo
2023-06-28 15:12:40 +02:00
val handshakePubSub = handshakeConnection . pubSub
2023-06-25 12:01:37 +02:00
// is our pub still connected??
if ( ! pubSub . pub . isConnected ) {
throw ClientException ( " Handshake publication is not connected, and it is expected to be connected! " )
}
2020-08-12 23:30:16 +02:00
// Send the done message to the server.
2021-07-06 15:38:53 +02:00
try {
2023-07-15 13:11:50 +02:00
handshaker . writeMessage ( handshakeConnection . pubSub . pub , logInfo ,
2023-06-25 12:01:37 +02:00
HandshakeMessage . doneFromClient (
connectKey = connectKey ,
2023-06-28 15:12:40 +02:00
sessionIdSub = handshakePubSub . sessionIdSub ,
2023-07-11 00:11:58 +02:00
streamIdSub = handshakePubSub . streamIdSub
2023-06-25 12:01:37 +02:00
) )
2021-07-06 15:38:53 +02:00
} catch ( e : Exception ) {
2023-09-26 19:53:27 +02:00
handshakeConnection . close ( endPoint )
2023-06-25 12:01:37 +02:00
throw TransmitException ( " $handshakeConnection Handshake message error! " , e )
2021-07-06 15:38:53 +02:00
}
2020-08-12 23:30:16 +02:00
2022-04-04 16:30:05 +02:00
failedException = null
2023-06-25 12:01:37 +02:00
connectionDone = false
2022-07-29 04:52:11 +02:00
2023-06-25 12:01:37 +02:00
// block until we receive the connection information from the server
2022-03-08 08:41:06 +01:00
var startTime = System . nanoTime ( )
2023-07-03 19:18:34 +02:00
while ( System . nanoTime ( ) - startTime < handshakeTimeoutNs ) {
2020-08-26 16:27:59 +02:00
// NOTE: regarding fragment limit size. Repeated calls to '.poll' will reassemble a fragment.
// `.poll(handler, 4)` == `.poll(handler, 2)` + `.poll(handler, 2)`
2023-06-28 15:12:40 +02:00
handshakePubSub . sub . poll ( handler , 1 )
2020-08-12 23:30:16 +02:00
2022-04-04 16:30:05 +02:00
if ( failedException != null || connectionDone ) {
2021-07-06 15:38:53 +02:00
break
2020-08-12 23:30:16 +02:00
}
2020-09-23 17:08:25 +02:00
if ( needToRetry ) {
needToRetry = false
// start over with the timeout!
2022-03-08 08:41:06 +01:00
startTime = System . nanoTime ( )
2020-09-23 17:08:25 +02:00
}
2023-09-04 00:47:46 +02:00
Thread . sleep ( 100 )
2020-08-12 23:30:16 +02:00
}
2022-04-04 16:30:05 +02:00
val failedEx = failedException
if ( failedEx != null ) {
2023-09-26 19:53:27 +02:00
handshakeConnection . close ( endPoint )
2023-04-20 18:11:28 +02:00
2022-04-04 16:30:05 +02:00
throw failedEx
2021-07-06 15:38:53 +02:00
}
2022-04-04 16:30:05 +02:00
2023-06-19 14:03:18 +02:00
if ( ! connectionDone ) {
2022-12-15 23:57:22 +01:00
// since this failed, close everything
2023-09-26 19:53:27 +02:00
handshakeConnection . close ( endPoint )
2022-12-15 23:57:22 +01:00
2023-07-03 19:18:34 +02:00
val exception = ClientTimedOutException ( " Timed out waiting for registration response from server: ${Sys.getTimePrettyFull(handshakeTimeoutNs)} " )
2022-04-04 16:30:59 +02:00
throw exception
2020-08-12 23:30:16 +02:00
}
}
2022-03-24 00:32:58 +01:00
fun reset ( ) {
2022-05-30 02:45:50 +02:00
connectKey = 0L
2022-03-24 00:32:58 +01:00
connectionHelloInfo = null
connectionDone = false
needToRetry = false
2022-04-04 16:30:05 +02:00
failedException = null
2022-03-24 00:32:58 +01:00
}
2020-08-12 23:30:16 +02:00
}