2020-08-12 23:30:16 +02:00
/ *
2020-08-19 15:29:35 +02:00
* Copyright 2020 dorkbox , llc
2020-08-12 23:30:16 +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 .
* /
package dorkbox.network
2021-08-23 08:48:05 +02:00
import dorkbox.bytes.toHexString
2022-06-04 01:32:58 +02:00
import dorkbox.dns.DnsClient
2022-03-08 08:41:06 +01:00
import dorkbox.netUtil.IP
import dorkbox.netUtil.IPv4
import dorkbox.netUtil.IPv6
import dorkbox.netUtil.Inet4
import dorkbox.netUtil.Inet6
2022-06-04 01:32:58 +02:00
import dorkbox.netUtil.dnsUtils.ResolvedAddressTypes
2021-04-27 13:57:47 +02:00
import dorkbox.network.aeron.AeronDriver
2022-08-02 12:11:36 +02:00
import dorkbox.network.aeron.mediaDriver.ClientIpcDriver
import dorkbox.network.aeron.mediaDriver.ClientUdpDriver
import dorkbox.network.aeron.mediaDriver.MediaDriverClient
import dorkbox.network.aeron.mediaDriver.MediaDriverConnectInfo
2022-03-08 08:41:06 +01:00
import dorkbox.network.connection.Connection
import dorkbox.network.connection.ConnectionParams
import dorkbox.network.connection.EndPoint
import dorkbox.network.connection.ListenerManager
import dorkbox.network.connection.PublicKeyValidationState
2020-09-09 01:33:09 +02:00
import dorkbox.network.exceptions.ClientException
import dorkbox.network.exceptions.ClientRejectedException
2022-04-04 16:30:05 +02:00
import dorkbox.network.exceptions.ClientRetryException
2022-06-14 22:18:08 +02:00
import dorkbox.network.exceptions.ClientShutdownException
2020-09-09 01:33:09 +02:00
import dorkbox.network.exceptions.ClientTimedOutException
2020-08-12 23:30:16 +02:00
import dorkbox.network.handshake.ClientHandshake
2021-04-30 16:01:25 +02:00
import dorkbox.network.ping.Ping
2021-07-08 14:33:24 +02:00
import dorkbox.network.ping.PingManager
2020-09-02 02:39:05 +02:00
import kotlinx.atomicfu.atomic
2020-08-12 23:30:16 +02:00
import kotlinx.coroutines.launch
2021-04-30 16:01:25 +02:00
import kotlinx.coroutines.runBlocking
2022-06-15 23:53:26 +02:00
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
2022-07-27 00:20:34 +02:00
import java.lang.Thread.sleep
2020-09-09 01:33:09 +02:00
import java.net.Inet4Address
2020-09-10 00:35:01 +02:00
import java.net.Inet6Address
2020-09-09 01:33:09 +02:00
import java.net.InetAddress
2022-03-24 00:32:58 +01:00
import java.util.concurrent.*
2020-08-12 23:30:16 +02:00
/ * *
* The client is both SYNC and ASYNC . It starts off SYNC ( blocks thread until it ' s done ) , then once it ' s connected to the server , it ' s
* ASYNC .
2021-07-09 15:16:12 +02:00
*
* @param config these are the specific connection options
* @param connectionFunc allows for custom connection implementations defined as a unit function
2022-05-28 12:15:45 +02:00
* @param loggerName allows for a custom logger name for this endpoint ( for when there are multiple endpoints )
2020-08-12 23:30:16 +02:00
* /
2022-03-24 00:32:58 +01:00
@Suppress ( " unused " )
2021-07-09 15:16:12 +02:00
open class Client < CONNECTION : Connection > (
2022-07-29 04:52:11 +02:00
config : ClientConfiguration = ClientConfiguration ( ) ,
2022-05-28 12:15:45 +02:00
connectionFunc : ( connectionParameters : ConnectionParams < CONNECTION > ) -> CONNECTION ,
loggerName : String = Client :: class . java . simpleName )
: EndPoint < CONNECTION > ( config , connectionFunc , loggerName ) {
2022-06-11 23:53:39 +02:00
/ * *
* The client is both SYNC and ASYNC . It starts off SYNC ( blocks thread until it ' s done ) , then once it ' s connected to the server , it ' s
* ASYNC .
*
* @param config these are the specific connection options
* @param loggerName allows for a custom logger name for this endpoint ( for when there are multiple endpoints )
* @param connectionFunc allows for custom connection implementations defined as a unit function
* /
2022-07-29 04:52:11 +02:00
constructor ( config : ClientConfiguration ,
2022-06-11 23:53:39 +02:00
loggerName : String ,
connectionFunc : ( connectionParameters : ConnectionParams < CONNECTION > ) -> CONNECTION )
: this ( config , connectionFunc , loggerName )
2022-05-28 12:15:45 +02:00
/ * *
* The client is both SYNC and ASYNC . It starts off SYNC ( blocks thread until it ' s done ) , then once it ' s connected to the server , it ' s
* ASYNC .
*
* @param config these are the specific connection options
* @param connectionFunc allows for custom connection implementations defined as a unit function
* /
2022-07-29 04:52:11 +02:00
constructor ( config : ClientConfiguration ,
2022-05-28 12:15:45 +02:00
connectionFunc : ( connectionParameters : ConnectionParams < CONNECTION > ) -> CONNECTION )
: this ( config , connectionFunc , Client :: class . java . simpleName )
/ * *
* The client is both SYNC and ASYNC . It starts off SYNC ( blocks thread until it ' s done ) , then once it ' s connected to the server , it ' s
* ASYNC .
*
* @param config these are the specific connection options
* @param loggerName allows for a custom logger name for this endpoint ( for when there are multiple endpoints )
* /
2022-07-29 04:52:11 +02:00
constructor ( config : ClientConfiguration ,
2022-05-28 12:15:45 +02:00
loggerName : String )
: this ( config ,
{
@Suppress ( " UNCHECKED_CAST " )
Connection ( it ) as CONNECTION
} ,
loggerName )
/ * *
* The client is both SYNC and ASYNC . It starts off SYNC ( blocks thread until it ' s done ) , then once it ' s connected to the server , it ' s
* ASYNC .
*
* @param config these are the specific connection options
* /
2022-07-29 04:52:11 +02:00
constructor ( config : ClientConfiguration )
2022-05-28 12:15:45 +02:00
: this ( config ,
{
@Suppress ( " UNCHECKED_CAST " )
Connection ( it ) as CONNECTION
} ,
Client :: class . java . simpleName )
2021-07-09 15:16:12 +02:00
2020-08-12 23:30:16 +02:00
companion object {
/ * *
* Gets the version number .
* /
2022-08-03 01:52:50 +02:00
const val version = " 5.32 "
2021-04-09 20:24:45 +02:00
2021-04-30 21:18:57 +02:00
/ * *
* Checks to see if a client ( using the specified configuration ) is running .
*
* This method should only be used to check if a client is running for a DIFFERENT configuration than the currently running client
* /
fun isRunning ( configuration : Configuration ) : Boolean {
return AeronDriver ( configuration ) . isRunning ( )
}
2021-04-09 20:24:45 +02:00
init {
// Add this project to the updates system, which verifies this class + UUID + version information
2021-04-27 14:00:31 +02:00
dorkbox . updates . Updates . add ( Client :: class . java , " 5be42ae40cac49fb90dea86bc513141b " , version )
2021-04-09 20:24:45 +02:00
}
2020-08-12 23:30:16 +02:00
}
/ * *
* The network or IPC address for the client to connect to .
*
* For a network address , it can be :
* - a network name ( " localhost " , " loopback " , " lo " , " bob.example.org " )
* - an IP address ( " 127.0.0.1 " , " 123.123.123.123 " , " ::1 " )
*
* For the IPC ( Inter - Process - Communication ) address . it must be :
* - the IPC integer ID , " 0x1337c0de " , " 0x12312312 " , etc .
* /
2022-06-27 15:31:40 +02:00
@Volatile
var remoteAddress : InetAddress ? = IPv4 . LOCALHOST
private set
/ * *
* the remote address , as a string .
* /
@Volatile
var remoteAddressString : String = " UNKNOWN "
private set
2020-08-12 23:30:16 +02:00
2020-08-15 13:21:20 +02:00
@Volatile
private var isConnected = false
2020-08-12 23:30:16 +02:00
// is valid when there is a connection to the server, otherwise it is null
2020-09-03 01:31:08 +02:00
private var connection0 : CONNECTION ? = null
2020-08-12 23:30:16 +02:00
2021-04-29 02:10:34 +02:00
// This is set by the client so if there is a "connect()" call in the the disconnect callback, we can have proper
// lock-stop ordering for how disconnect and connect work with each-other
// GUARANTEE that the callbacks for 'onDisconnect' happens-before the 'onConnect'.
2022-06-15 23:53:26 +02:00
private val lockStepForConnect = atomic < Mutex ? > ( null )
2020-09-02 02:39:05 +02:00
2020-09-25 14:49:17 +02:00
final override fun newException ( message : String , cause : Throwable ? ) : Throwable {
2020-08-12 23:30:16 +02:00
return ClientException ( message , cause )
}
2022-06-27 15:31:40 +02:00
/ * *
* Will attempt to connect to the server , with a default 30 second connection timeout and will block until completed .
*
* Default connection is to localhost
*
* # # # For a network address , it can be :
* - a network name ( " localhost " , " bob.example.org " )
* - an IP address ( " 127.0.0.1 " , " 123.123.123.123 " , " ::1 " )
* - an InetAddress address
*
* # # # For the IPC ( Inter - Process - Communication ) it must be :
* - ` connect ( ) `
* - ` connect ( " " ) `
* - ` connectIpc ( ) `
*
* # # # Case does not matter , and " localhost " is the default .
*
* @param remoteAddress The network or if localhost , IPC address for the client to connect to
* @param connectionTimeoutSec wait for x seconds . 0 will wait indefinitely
* @param reliable true if we want to create a reliable connection ( for UDP connections , is message loss acceptable ? ) .
*
* @throws IllegalArgumentException if the remote address is invalid
* @throws ClientTimedOutException if the client is unable to connect in x amount of time
* @throws ClientRejectedException if the client connection is rejected
* /
2022-07-15 06:58:49 +02:00
fun connect (
remoteAddress : InetAddress ,
connectionTimeoutSec : Int = 30 ,
reliable : Boolean = true )
2022-07-27 00:20:34 +02:00
{
2022-06-27 15:31:40 +02:00
val remoteAddressString = when ( remoteAddress ) {
is Inet4Address -> IPv4 . toString ( remoteAddress )
is Inet6Address -> IPv6 . toString ( remoteAddress , true )
else -> throw IllegalArgumentException ( " Cannot connect to $remoteAddress It is an invalid address! " )
}
// Default IPC ports are flipped because they are in the perspective of the SERVER
connect ( remoteAddress = remoteAddress ,
remoteAddressString = remoteAddressString ,
connectionTimeoutSec = connectionTimeoutSec ,
reliable = reliable )
}
/ * *
* Will attempt to connect to the server via IPC , with a default 30 second connection timeout and will block until completed .
*
2022-08-02 12:11:36 +02:00
* @param ipcId The IPC publication address for the client to connect to
2022-06-27 15:31:40 +02:00
* @param ipcSubscriptionId The IPC subscription address for the client to connect to
* @param connectionTimeoutSec wait for x seconds . 0 will wait indefinitely .
*
* @throws IllegalArgumentException if the remote address is invalid
* @throws ClientTimedOutException if the client is unable to connect in x amount of time
* @throws ClientRejectedException if the client connection is rejected
* /
@Suppress ( " DuplicatedCode " )
2022-07-15 06:58:49 +02:00
fun connectIpc (
2022-08-02 12:11:36 +02:00
ipcId : Int = AeronDriver . IPC _HANDSHAKE _STREAM _ID _SUB ,
2022-07-15 06:58:49 +02:00
connectionTimeoutSec : Int = 30 )
2022-07-27 00:20:34 +02:00
{
2022-06-27 15:31:40 +02:00
connect ( remoteAddress = null , // required!
remoteAddressString = " IPC " ,
2022-08-02 12:11:36 +02:00
ipcId = ipcId ,
2022-06-27 15:31:40 +02:00
connectionTimeoutSec = connectionTimeoutSec )
}
2020-08-12 23:30:16 +02:00
/ * *
2020-08-15 13:21:20 +02:00
* Will attempt to connect to the server , with a default 30 second connection timeout and will block until completed .
2020-08-12 23:30:16 +02:00
*
2020-08-15 13:21:20 +02:00
* Default connection is to localhost
2020-08-12 23:30:16 +02:00
*
2020-08-15 13:21:20 +02:00
* # # # For a network address , it can be :
2022-03-15 20:43:43 +01:00
* - a network name ( " localhost " , " bob.example.org " )
2020-08-15 13:21:20 +02:00
* - an IP address ( " 127.0.0.1 " , " 123.123.123.123 " , " ::1 " )
2020-09-09 01:33:09 +02:00
* - an InetAddress address
2022-03-15 20:43:43 +01:00
* - if no address is specified , and IPC is disabled in the config , then localhost will be selected
2020-08-12 23:30:16 +02:00
*
2020-09-02 02:39:05 +02:00
* # # # For the IPC ( Inter - Process - Communication ) it must be :
2021-07-06 15:38:53 +02:00
* - ` connect ( ) ` ( only if ipc is enabled in the configuration )
* - ` connect ( " " ) ` ( only if ipc is enabled in the configuration )
2021-04-30 16:01:25 +02:00
* - ` connectIpc ( ) `
2020-08-12 23:30:16 +02:00
*
2021-04-30 16:01:25 +02:00
* # # # Case does not matter , and " localhost " is the default .
2020-08-12 23:30:16 +02:00
*
2021-04-30 16:01:25 +02:00
* @param remoteAddress The network host or ip address
2022-03-08 08:41:06 +01:00
* @param connectionTimeoutSec wait for x seconds . 0 will wait indefinitely
2021-04-30 16:01:25 +02:00
* @param reliable true if we want to create a reliable connection ( for UDP connections , is message loss acceptable ? ) .
2020-08-12 23:30:16 +02:00
*
2020-08-15 13:21:20 +02:00
* @throws IllegalArgumentException if the remote address is invalid
2020-08-12 23:30:16 +02:00
* @throws ClientTimedOutException if the client is unable to connect in x amount of time
2020-08-15 13:21:20 +02:00
* @throws ClientRejectedException if the client connection is rejected
2020-08-12 23:30:16 +02:00
* /
2022-07-15 06:58:49 +02:00
fun connect (
remoteAddress : String = " " ,
connectionTimeoutSec : Int = 30 ,
reliable : Boolean = true )
2022-07-27 00:20:34 +02:00
{
2022-06-27 15:31:40 +02:00
2020-09-09 01:33:09 +02:00
when {
// this is default IPC settings
2022-03-24 00:32:58 +01:00
remoteAddress . isEmpty ( ) && config . enableIpc -> {
2022-04-04 23:04:27 +02:00
connectIpc ( connectionTimeoutSec = connectionTimeoutSec )
2021-04-30 16:01:25 +02:00
}
2020-09-09 01:33:09 +02:00
2022-08-02 12:11:36 +02:00
config . enableIPv4 || IPv4 . isPreferred -> {
2022-06-04 01:32:58 +02:00
// we have to check first if it's a valid IPv4 address. If not, maybe it's a DNS lookup
val inet4Address = if ( IPv4 . isValid ( remoteAddress ) ) {
Inet4 . toAddress ( remoteAddress )
} else {
val client = DnsClient ( )
client . resolvedAddressTypes ( ResolvedAddressTypes . IPV4 _ONLY )
val records = client . resolve ( remoteAddress )
client . stop ( )
2022-06-08 00:25:01 +02:00
records ?. get ( 0 )
2022-06-04 01:32:58 +02:00
}
if ( inet4Address == null ) {
throw IllegalArgumentException ( " The remote address ' $remoteAddress ' cannot be found. " )
}
connect ( remoteAddress = inet4Address ,
2022-06-27 15:31:40 +02:00
remoteAddressString = remoteAddress ,
2022-03-24 00:32:58 +01:00
connectionTimeoutSec = connectionTimeoutSec ,
2022-06-27 15:31:40 +02:00
reliable = reliable )
2021-04-30 16:01:25 +02:00
}
2020-09-09 01:33:09 +02:00
2022-08-02 12:11:36 +02:00
config . enableIPv6 || IPv6 . isPreferred -> {
2022-06-04 01:32:58 +02:00
// we have to check first if it's a valid IPv6 address. If not, maybe it's a DNS lookup
2022-08-02 12:11:36 +02:00
var inet6Address = if ( IPv6 . isValid ( remoteAddress ) ) {
2022-06-04 01:32:58 +02:00
Inet6 . toAddress ( remoteAddress )
} else {
val client = DnsClient ( )
client . resolvedAddressTypes ( ResolvedAddressTypes . IPV6 _ONLY )
val records = client . resolve ( remoteAddress )
client . stop ( )
2022-06-08 00:25:01 +02:00
records ?. get ( 0 )
2022-06-04 01:32:58 +02:00
}
if ( inet6Address == null ) {
throw IllegalArgumentException ( " The remote address ' $remoteAddress ' cannot be found. " )
}
2022-08-02 12:11:36 +02:00
when ( inet6Address ) {
IPv4 . LOCALHOST -> {
connect ( remoteAddress = IPv6 . LOCALHOST ,
remoteAddressString = IPv6 . toString ( IPv6 . LOCALHOST ) ,
connectionTimeoutSec = connectionTimeoutSec ,
reliable = reliable )
}
is Inet4Address -> {
// we can map the IPv4 address to an IPv6 address.
val address = IPv6 . toAddress ( IPv4 . toString ( inet6Address ) , true ) !!
connect ( remoteAddress = address ,
remoteAddressString = IPv6 . toString ( address ) ,
connectionTimeoutSec = connectionTimeoutSec ,
reliable = reliable )
}
else -> {
connect ( remoteAddress = inet6Address ,
remoteAddressString = remoteAddress ,
connectionTimeoutSec = connectionTimeoutSec ,
reliable = reliable )
}
}
2021-04-30 16:01:25 +02:00
}
2020-09-22 19:39:42 +02:00
// if there is no preference, then try to connect via IPv4
2021-04-30 16:01:25 +02:00
else -> {
2022-06-04 01:32:58 +02:00
// we have to check first if it's a valid IPv4 address. If not, maybe it's a DNS lookup
val inetAddress = if ( IP . isValid ( remoteAddress ) ) {
IP . toAddress ( remoteAddress )
} else {
val client = DnsClient ( )
client . resolvedAddressTypes ( ResolvedAddressTypes . IPV4 _PREFERRED )
val records = client . resolve ( remoteAddress )
client . stop ( )
2022-06-08 00:25:01 +02:00
records ?. get ( 0 )
2022-06-04 01:32:58 +02:00
}
if ( inetAddress == null ) {
throw IllegalArgumentException ( " The remote address ' $remoteAddress ' cannot be found. " )
}
connect ( remoteAddress = inetAddress ,
2022-06-27 15:31:40 +02:00
remoteAddressString = remoteAddress ,
2022-03-24 00:32:58 +01:00
connectionTimeoutSec = connectionTimeoutSec ,
reliable = reliable
2021-04-30 16:01:25 +02:00
)
}
2020-09-09 01:33:09 +02:00
}
}
/ * *
* Will attempt to connect to the server , with a default 30 second connection timeout and will block until completed .
2022-06-14 22:18:08 +02:00
* If unable to connect within the specified timeout an exception will be thrown
2020-09-09 01:33:09 +02:00
*
* Default connection is to localhost
*
* # # # For a network address , it can be :
2022-03-15 20:43:43 +01:00
* - a network name ( " localhost " , " bob.example.org " )
2020-09-09 01:33:09 +02:00
* - an IP address ( " 127.0.0.1 " , " 123.123.123.123 " , " ::1 " )
2021-04-30 16:01:25 +02:00
* - an InetAddress address
2020-09-09 01:33:09 +02:00
*
* # # # For the IPC ( Inter - Process - Communication ) it must be :
2021-04-30 16:01:25 +02:00
* - ` connect ( ) `
* - ` connect ( " " ) `
* - ` connectIpc ( ) `
*
* # # # Case does not matter , and " localhost " is the default .
2020-09-09 01:33:09 +02:00
*
* @param remoteAddress The network or if localhost , IPC address for the client to connect to
* @param ipcPublicationId The IPC publication address for the client to connect to
* @param ipcSubscriptionId The IPC subscription address for the client to connect to
2022-03-08 08:41:06 +01:00
* @param connectionTimeoutSec wait for x seconds . 0 will wait indefinitely .
2021-04-30 16:01:25 +02:00
* @param reliable true if we want to create a reliable connection ( for UDP connections , is message loss acceptable ? ) .
2020-09-09 01:33:09 +02:00
*
* @throws IllegalArgumentException if the remote address is invalid
* @throws ClientTimedOutException if the client is unable to connect in x amount of time
* @throws ClientRejectedException if the client connection is rejected
2022-06-14 22:18:08 +02:00
* @throws ClientShutdownException if the client connection is shutdown while trying to connect
2021-07-06 15:38:53 +02:00
* @throws ClientException if there are misc errors
2020-09-09 01:33:09 +02:00
* /
@Suppress ( " DuplicatedCode " )
2022-07-27 00:20:34 +02:00
private fun connect (
2022-07-15 06:58:49 +02:00
remoteAddress : InetAddress ? = null ,
remoteAddressString : String ,
// Default IPC ports are flipped because they are in the perspective of the SERVER
2022-08-02 12:11:36 +02:00
ipcId : Int = AeronDriver . IPC _HANDSHAKE _STREAM _ID _SUB ,
2022-07-15 06:58:49 +02:00
connectionTimeoutSec : Int = 30 ,
reliable : Boolean = true )
2022-07-29 04:52:11 +02:00
{
// NOTE: it is critical to remember that Aeron DOES NOT like running from coroutines!
config as ClientConfiguration
2022-07-15 06:58:49 +02:00
2022-03-15 10:32:06 +01:00
require ( connectionTimeoutSec >= 0 ) { " connectionTimeoutSec ' $connectionTimeoutSec ' is invalid. It must be >=0 " }
2020-09-25 18:31:23 +02:00
2020-08-15 13:21:20 +02:00
if ( isConnected ) {
2022-05-30 02:45:50 +02:00
logger . error { " Unable to connect when already connected! " }
2020-08-12 23:30:16 +02:00
return
}
2020-09-03 01:31:08 +02:00
connection0 = null
2020-09-02 02:39:05 +02:00
2022-06-27 15:31:40 +02:00
// localhost/loopback IP might not always be 127.0.0.1 or ::1
2022-08-02 12:11:36 +02:00
this . remoteAddress = remoteAddress
this . remoteAddressString = remoteAddressString
2022-06-27 15:31:40 +02:00
2020-08-25 17:45:08 +02:00
// we are done with initial configuration, now initialize aeron and the general state of this endpoint
2021-07-06 15:38:53 +02:00
try {
2022-08-02 13:10:06 +02:00
startDriver ( )
2021-07-06 15:38:53 +02:00
} catch ( e : Exception ) {
2022-08-02 13:10:06 +02:00
logger . error ( e ) { " Unable to start the network driver " }
2021-07-06 15:38:53 +02:00
return
}
2020-08-25 17:45:08 +02:00
2020-09-10 00:35:01 +02:00
// only try to connect via IPv4 if we have a network interface that supports it!
if ( remoteAddress is Inet4Address && ! IPv4 . isAvailable ) {
2022-06-27 15:31:40 +02:00
require ( false ) { " Unable to connect to the IPv4 address $remoteAddressString , there are no IPv4 interfaces available! " }
2020-09-10 00:35:01 +02:00
}
// only try to connect via IPv6 if we have a network interface that supports it!
if ( remoteAddress is Inet6Address && ! IPv6 . isAvailable ) {
2022-06-27 15:31:40 +02:00
require ( false ) { " Unable to connect to the IPv6 address $remoteAddressString , there are no IPv6 interfaces available! " }
2020-09-10 00:35:01 +02:00
}
2020-09-11 01:14:22 +02:00
if ( remoteAddress != null && remoteAddress . isAnyLocalAddress ) {
2022-06-27 15:31:40 +02:00
require ( false ) { " Cannot connect to $remoteAddressString It is an invalid address! " }
2020-08-12 23:30:16 +02:00
}
2021-04-30 22:39:36 +02:00
// IPC can be enabled TWO ways!
// - config.enableIpc
// - NULL remoteAddress
// It is entirely possible that the server does not have IPC enabled!
2022-03-24 00:32:58 +01:00
val autoChangeToIpc =
( config . enableIpc && ( remoteAddress == null || remoteAddress . isLoopbackAddress ) ) || ( ! config . enableIpc && remoteAddress == null )
2020-08-12 23:30:16 +02:00
2022-08-02 12:11:36 +02:00
val handshake = ClientHandshake ( crypto , this , logger )
2022-03-24 00:32:58 +01:00
2022-07-15 06:58:49 +02:00
val handshakeTimeout = 5
val timoutInNanos = TimeUnit . SECONDS . toNanos ( connectionTimeoutSec . toLong ( ) )
val startTime = System . nanoTime ( )
var success = false
while ( timoutInNanos == 0L || System . nanoTime ( ) - startTime < timoutInNanos ) {
if ( isShutdown ( ) ) {
// If we are connecting indefinitely, we have to make sure to end the connection process
val exception = ClientShutdownException ( " Unable to connect while shutting down " )
logger . error ( exception ) { " Aborting connection retry attempt to server. " }
listenerManager . notifyError ( exception )
throw exception
}
2022-03-24 00:32:58 +01:00
2022-08-02 12:11:36 +02:00
// we have to pre-set the type (which will ultimately get set to the correct type on success)
2022-07-29 04:52:11 +02:00
var type = " "
val localSessionId = crypto . secureRandom . nextInt ( ) + 1 // this isn't SUPER important, but it helps prevent handshake collisions
2022-07-15 06:58:49 +02:00
try {
2022-07-29 04:52:11 +02:00
// the handshake connection is closed when the handshake has an error, or it is finished
2022-07-15 06:58:49 +02:00
val handshakeConnection = if ( autoChangeToIpc ) {
2022-07-29 04:52:11 +02:00
if ( remoteAddress == null ) {
logger . info { " IPC enabled " }
} else {
logger . info { " IPC for loopback enabled and aeron is already running. Auto-changing network connection from ' $remoteAddressString ' -> IPC " }
}
// MAYBE the server doesn't have IPC enabled? If no, we need to connect via network instead
2022-08-02 12:11:36 +02:00
val ipcConnection = ClientIpcDriver (
streamId = ipcId ,
2022-07-29 04:52:11 +02:00
sessionId = AeronDriver . RESERVED _SESSION _ID _INVALID ,
localSessionId = localSessionId ,
2022-07-15 06:58:49 +02:00
)
2022-07-29 04:52:11 +02:00
2022-08-02 12:11:36 +02:00
type = " ${ipcConnection.type} ' $remoteAddressString : $ipcId ' "
2022-07-29 04:52:11 +02:00
// throws a ConnectTimedOutException if the client cannot connect for any reason to the server handshake ports
try {
ipcConnection . build ( aeronDriver , logger )
ipcConnection
} catch ( e : Exception ) {
if ( remoteAddress == null ) {
// if we specified that we MUST use IPC, then we have to throw the exception, because there is no IPC
2022-08-02 12:11:36 +02:00
val clientException = ClientException ( " Unable to connect via IPC to server. No address specified so fallback is unavailable " , e )
2022-07-29 04:52:11 +02:00
ListenerManager . cleanStackTraceInternal ( clientException )
throw clientException
}
logger . info { " IPC for loopback enabled, but unable to connect. Retrying with address $remoteAddressString " }
// try a UDP connection instead
2022-08-02 12:11:36 +02:00
val udpConnection = ClientUdpDriver (
2022-07-29 04:52:11 +02:00
address = remoteAddress ,
2022-08-02 12:11:36 +02:00
addressString = remoteAddressString ,
port = config . port ,
2022-07-29 04:52:11 +02:00
streamId = AeronDriver . UDP _HANDSHAKE _STREAM _ID ,
sessionId = AeronDriver . RESERVED _SESSION _ID _INVALID ,
localSessionId = localSessionId ,
connectionTimeoutSec = connectionTimeoutSec ,
isReliable = reliable
)
2022-08-02 12:11:36 +02:00
type = " ${udpConnection.type} ' $remoteAddressString : ${config.port} ' "
2022-07-29 04:52:11 +02:00
// throws a ConnectTimedOutException if the client cannot connect for any reason to the server handshake ports
udpConnection . build ( aeronDriver , logger )
udpConnection
}
2022-07-15 06:58:49 +02:00
} else {
2022-08-02 12:11:36 +02:00
val udpConnection = ClientUdpDriver (
2022-07-29 04:52:11 +02:00
address = remoteAddress !! ,
2022-08-02 12:11:36 +02:00
addressString = remoteAddressString ,
port = config . port ,
2022-07-29 04:52:11 +02:00
streamId = AeronDriver . UDP _HANDSHAKE _STREAM _ID ,
sessionId = AeronDriver . RESERVED _SESSION _ID _INVALID ,
localSessionId = localSessionId ,
connectionTimeoutSec = handshakeTimeout ,
isReliable = reliable
)
2022-08-02 12:11:36 +02:00
type = " ${udpConnection.type} ' $remoteAddressString : ${config.port} ' "
2022-07-29 04:52:11 +02:00
// throws a ConnectTimedOutException if the client cannot connect for any reason to the server handshake ports
udpConnection . build ( aeronDriver , logger )
udpConnection
2022-07-15 06:58:49 +02:00
}
2022-03-24 00:32:58 +01:00
2022-07-29 04:52:11 +02:00
logger . info { handshakeConnection . info }
2022-03-24 00:32:58 +01:00
2022-07-15 06:58:49 +02:00
connect0 ( handshake , handshakeConnection , handshakeTimeout )
success = true
2022-07-12 16:56:47 +02:00
2022-08-02 12:11:36 +02:00
// finished with the handshake, so always close the connection publication
// The subscription is RE-USED
handshakeConnection . publication . close ( )
2022-07-29 04:52:11 +02:00
2022-07-15 06:58:49 +02:00
// once we're done with the connection process, stop trying
break
} catch ( e : ClientRetryException ) {
handshake . reset ( )
2022-03-24 00:32:58 +01:00
2022-07-27 00:20:34 +02:00
// maybe the aeron driver isn't running? (or isn't running correctly?)
2022-07-29 04:52:11 +02:00
aeronDriver . closeIfSingle ( ) // if we are the ONLY instance using the media driver, restart it
2022-07-15 06:58:49 +02:00
aeronDriver . start ( )
2022-06-27 01:45:10 +02:00
2022-07-15 06:58:49 +02:00
// short delay, since it failed we want to limit the retry rate to something slower than "as fast as the CPU can do it"
2022-07-18 05:12:06 +02:00
// we also want to go at SLIGHTLY slower that the aeron driver timeout frequency, this way - if there are connection or handshake issues, the server has the chance to expire the connections.
2022-07-27 00:20:34 +02:00
// If we go TOO FAST, then the server will EVENTUALLY have aeron errors (since it can't keep up per client). We literally
// want to have 1 in-flight handshake, per connection attempt, during the aeron connection timeout
2022-08-02 12:11:36 +02:00
// ALSO, we want to make sure we DO NOT approach the linger timeout!
sleep ( aeronDriver . driverTimeout ( ) . coerceAtLeast ( TimeUnit . NANOSECONDS . toSeconds ( aeronDriver . getLingerNs ( ) ) ) )
2022-07-15 06:58:49 +02:00
if ( logger . isTraceEnabled ) {
2022-08-02 12:11:36 +02:00
logger . trace ( e ) { " Unable to connect to $type , retrying... " }
2022-07-15 06:58:49 +02:00
} else {
2022-08-02 12:11:36 +02:00
logger . info { " Unable to connect to $type , retrying... " }
2020-09-11 01:14:22 +02:00
}
2022-06-14 22:18:08 +02:00
2022-07-15 06:58:49 +02:00
} catch ( e : Exception ) {
2022-08-02 12:11:36 +02:00
logger . error ( e ) { " [ ${handshake.connectKey} ] : Un-recoverable error during handshake with $type . Aborting. " }
2022-07-15 06:58:49 +02:00
handshake . reset ( )
2022-06-14 22:18:08 +02:00
2022-07-15 06:58:49 +02:00
listenerManager . notifyError ( e )
throw e
}
}
2022-07-14 04:54:22 +02:00
2022-07-15 06:58:49 +02:00
if ( ! success ) {
if ( System . nanoTime ( ) - startTime < timoutInNanos ) {
// we timed out. Throw the appropriate exception
2022-08-02 12:11:36 +02:00
val exception = ClientTimedOutException ( " Unable to connect to the server at $type in $connectionTimeoutSec seconds " )
2022-07-15 06:58:49 +02:00
logger . error ( exception ) { " Aborting connection attempt to server. " }
2022-06-14 22:18:08 +02:00
listenerManager . notifyError ( exception )
throw exception
}
2022-07-15 06:58:49 +02:00
// If we did not connect - throw an error. When `client.connect()` is called, either it connects or throws an error
val exception = ClientRejectedException ( " The server did not respond or permit the connection attempt " )
ListenerManager . cleanStackTrace ( exception )
logger . error ( exception ) { " Aborting connection retry attempt to server. " }
listenerManager . notifyError ( exception )
throw exception
2022-03-24 00:32:58 +01:00
}
}
2020-08-12 23:30:16 +02:00
2022-03-24 00:32:58 +01:00
// the handshake process might have to restart this connection process.
2022-07-29 04:52:11 +02:00
private fun connect0 ( handshake : ClientHandshake < CONNECTION > , handshakeConnection : MediaDriverClient , connectionTimeoutSec : Int ) {
2020-09-02 02:39:05 +02:00
// this will block until the connection timeout, and throw an exception if we were unable to connect with the server
2022-08-02 12:11:36 +02:00
val isUsingIPC = handshakeConnection is ClientIpcDriver
2022-03-24 00:32:58 +01:00
2020-08-12 23:30:16 +02:00
2021-07-06 15:38:53 +02:00
// throws(ConnectTimedOutException::class, ClientRejectedException::class, ClientException::class)
2022-04-04 16:30:05 +02:00
val connectionInfo = handshake . hello ( handshakeConnection , connectionTimeoutSec )
2020-08-12 23:30:16 +02:00
2020-09-02 02:39:05 +02:00
// VALIDATE:: check to see if the remote connection's public key has changed!
2020-09-22 19:41:19 +02:00
val validateRemoteAddress = if ( isUsingIPC ) {
2020-09-02 02:39:05 +02:00
PublicKeyValidationState . VALID
} else {
2022-05-30 02:45:50 +02:00
crypto . validateRemoteAddress ( remoteAddress !! , remoteAddressString , connectionInfo . publicKey )
2020-09-02 02:39:05 +02:00
}
2020-08-12 23:30:16 +02:00
2020-09-02 02:39:05 +02:00
if ( validateRemoteAddress == PublicKeyValidationState . INVALID ) {
2022-07-29 04:52:11 +02:00
handshakeConnection . subscription . close ( )
handshakeConnection . publication . close ( )
2022-06-27 15:31:40 +02:00
val exception = ClientRejectedException ( " Connection to $remoteAddressString not allowed! Public key mismatch. " )
2022-05-30 02:45:50 +02:00
logger . error ( exception ) { " Validation error " }
2020-09-02 02:39:05 +02:00
throw exception
}
2020-08-12 23:30:16 +02:00
2020-08-25 17:45:08 +02:00
2022-03-24 00:32:58 +01:00
// VALIDATE:: If the serialization DOES NOT match between the client/server, then the server will emit a log, and the
2022-06-04 01:32:58 +02:00
// client will timeout. SPECIFICALLY.... we do not give class serialization/registration info to the client - in case the client
2020-09-02 02:39:05 +02:00
// is rogue, we do not want to carelessly provide info.
// we are now connected, so we can connect to the NEW client-specific ports
2021-04-27 10:28:36 +02:00
val clientConnection = if ( isUsingIPC ) {
2022-07-29 04:52:11 +02:00
// Create a subscription at the given address and port, using the given stream ID.
2022-08-02 12:11:36 +02:00
val driver = ClientIpcDriver ( sessionId = connectionInfo . sessionId ,
streamId = connectionInfo . port ,
localSessionId = 1 // doesn't matter
2022-07-29 04:52:11 +02:00
)
driver . build ( aeronDriver , logger )
logger . info { " Creating new IPC connection to $driver " }
driver . subscription . close ( )
MediaDriverConnectInfo (
publication = driver . publication ,
subscription = handshakeConnection . subscription ,
2022-08-02 12:11:36 +02:00
subscriptionPort = driver . localSessionId ,
2022-07-29 04:52:11 +02:00
publicationPort = driver . streamId ,
streamId = 0 , // this is because with IPC, we have stream sub/pub (which are replaced as port sub/pub)
2022-08-02 12:11:36 +02:00
sessionId = driver . remoteSessionId ,
2022-07-29 04:52:11 +02:00
isReliable = driver . isReliable ,
remoteAddress = null ,
remoteAddressString = " ipc "
)
2020-08-12 23:30:16 +02:00
}
else {
2022-08-02 12:11:36 +02:00
val driver = ClientUdpDriver (
address = ( handshakeConnection as ClientUdpDriver ) . address ,
addressString = handshakeConnection . addressString ,
port = connectionInfo . port , // this is the port that we connect to
2021-04-27 10:28:36 +02:00
streamId = connectionInfo . streamId ,
sessionId = connectionInfo . sessionId ,
2022-07-29 04:52:11 +02:00
localSessionId = 1 , // doesn't matter here
2022-03-08 08:41:06 +01:00
connectionTimeoutSec = connectionTimeoutSec ,
2021-04-27 10:28:36 +02:00
isReliable = handshakeConnection . isReliable )
2020-08-12 23:30:16 +02:00
2022-07-29 04:52:11 +02:00
// we have to construct how the connection will communicate!
// we don't care about the subscription, only the publication
driver . build ( aeronDriver , logger )
logger . info { " Creating new connection to $driver " }
2020-08-12 23:30:16 +02:00
2022-07-29 04:52:11 +02:00
driver . subscription . close ( )
MediaDriverConnectInfo (
subscription = handshakeConnection . subscription ,
publication = driver . publication ,
2022-08-02 12:11:36 +02:00
subscriptionPort = 0 ,
publicationPort = driver . port ,
2022-07-29 04:52:11 +02:00
streamId = driver . streamId ,
2022-08-02 12:11:36 +02:00
sessionId = driver . remoteSessionId ,
2022-07-29 04:52:11 +02:00
isReliable = driver . isReliable ,
remoteAddress = driver . address ,
2022-08-02 12:11:36 +02:00
remoteAddressString = IP . toString ( driver . address )
2022-07-29 04:52:11 +02:00
)
}
2020-08-12 23:30:16 +02:00
2020-09-02 15:03:57 +02:00
///////////////
//// RMI
///////////////
2022-03-24 00:32:58 +01:00
// we set up our kryo information once we connect to a server (using the server's kryo registration details)
2020-09-15 21:17:47 +02:00
if ( ! serialization . finishInit ( type , connectionInfo . kryoRegistrationDetails ) ) {
2022-07-29 04:52:11 +02:00
handshakeConnection . subscription . close ( )
handshakeConnection . publication . close ( )
2020-09-02 15:03:57 +02:00
// because we are getting the class registration details from the SERVER, this should never be the case.
// It is still and edge case where the reconstruction of the registration details fails (maybe because of custom serializers)
2020-09-22 19:41:19 +02:00
val exception = if ( isUsingIPC ) {
2022-06-27 00:11:14 +02:00
ClientRejectedException ( " [ ${handshake.connectKey} ] Connection to IPC has incorrect class registration details!! " )
2020-09-19 22:06:54 +02:00
} else {
2022-06-27 00:11:14 +02:00
ClientRejectedException ( " [ ${handshake.connectKey} ] Connection to $remoteAddressString has incorrect class registration details!! " )
2020-09-19 22:06:54 +02:00
}
2022-04-04 23:04:27 +02:00
ListenerManager . cleanStackTraceInternal ( exception )
2020-09-02 15:03:57 +02:00
throw exception
}
2022-07-18 05:08:01 +02:00
val sessionId = clientConnection . sessionId
val streamId = clientConnection . streamId
val aeronLogInfo = " $sessionId / $streamId "
2021-07-06 15:38:53 +02:00
2020-09-19 22:06:54 +02:00
val newConnection : CONNECTION
2020-09-22 19:41:19 +02:00
if ( isUsingIPC ) {
2022-06-16 18:04:52 +02:00
newConnection = connectionFunc ( ConnectionParams ( this , clientConnection , PublicKeyValidationState . VALID ) )
2020-09-02 02:39:05 +02:00
} else {
2022-06-16 18:04:52 +02:00
newConnection = connectionFunc ( ConnectionParams ( this , clientConnection , validateRemoteAddress ) )
2020-09-19 22:06:54 +02:00
remoteAddress !!
// VALIDATE are we allowed to connect to this server (now that we have the initial server information)
val permitConnection = listenerManager . notifyFilter ( newConnection )
if ( ! permitConnection ) {
2022-07-29 04:52:11 +02:00
handshakeConnection . subscription . close ( )
handshakeConnection . publication . close ( )
2022-07-18 05:08:01 +02:00
val exception = ClientRejectedException ( " [ $aeronLogInfo - ${handshake.connectKey} ] Connection ( ${newConnection.id} ) to $remoteAddressString was not permitted! " )
2021-04-27 13:57:47 +02:00
ListenerManager . cleanStackTrace ( exception )
2022-05-30 02:45:50 +02:00
logger . error ( exception ) { " Permission error " }
2020-09-19 22:06:54 +02:00
throw exception
}
2020-08-12 23:30:16 +02:00
2022-07-18 05:08:01 +02:00
logger . info { " [ $aeronLogInfo - ${handshake.connectKey} ] Connection ( ${newConnection.id} ) adding new signature for $remoteAddressString : ${connectionInfo.publicKey.toHexString()} " }
2022-03-24 00:32:58 +01:00
storage . addRegisteredServerKey ( remoteAddress !! , connectionInfo . publicKey )
2020-09-02 02:39:05 +02:00
}
2020-08-12 23:30:16 +02:00
2020-09-19 22:06:54 +02:00
2020-09-02 02:39:05 +02:00
//////////////
/// Extra Close action
//////////////
2022-08-02 21:14:54 +02:00
newConnection . closeAction = {
2020-09-02 02:39:05 +02:00
// this is called whenever connection.close() is called by the framework or via client.close()
2021-04-29 02:10:34 +02:00
2022-03-24 00:32:58 +01:00
// on the client, we want to GUARANTEE that the disconnect happens-before connect.
2022-06-15 23:53:26 +02:00
if ( ! lockStepForConnect . compareAndSet ( null , Mutex ( locked = true ) ) ) {
2022-07-18 05:08:01 +02:00
logger . error { " [ $aeronLogInfo - ${handshake.connectKey} ] Connection ${newConnection.id} : close lockStep for disconnect was in the wrong state! " }
2021-04-29 02:10:34 +02:00
}
2022-06-06 17:18:46 +02:00
2021-04-30 16:01:25 +02:00
isConnected = false
2020-09-02 02:39:05 +02:00
// this is called whenever connection.close() is called by the framework or via client.close()
2020-08-12 23:30:16 +02:00
2020-09-02 02:39:05 +02:00
// make sure to call our client.notifyDisconnect() callbacks
2020-08-25 17:45:08 +02:00
2021-04-30 18:15:54 +02:00
// this always has to be on event dispatch, otherwise we can have weird logic loops if we reconnect within a disconnect callback
2022-07-14 04:54:22 +02:00
actionDispatch . launch {
2022-08-02 21:14:54 +02:00
listenerManager . notifyDisconnect ( connection )
2022-06-15 23:53:26 +02:00
lockStepForConnect . getAndSet ( null ) ?. unlock ( )
2020-09-02 02:39:05 +02:00
}
}
2020-08-12 23:30:16 +02:00
2022-06-16 18:04:52 +02:00
// before we finish creating the connection, we initialize it (in case there needs to be logic that happens-before `onConnect` calls occur
listenerManager . notifyInit ( newConnection )
2020-09-03 01:31:08 +02:00
connection0 = newConnection
2020-09-11 01:14:22 +02:00
addConnection ( newConnection )
2020-08-25 02:43:20 +02:00
2020-09-03 01:31:08 +02:00
// tell the server our connection handshake is done, and the connection can now listen for data.
2020-09-11 11:06:53 +02:00
// also closes the handshake (will also throw connect timeout exception)
2022-06-27 15:31:40 +02:00
// this value matches the server, and allows for a more robust connection attempt
val successAttemptTimeout = config . connectionCloseTimeoutInSeconds * 2
try {
handshake . done ( handshakeConnection , successAttemptTimeout )
} catch ( e : Exception ) {
2022-07-18 05:08:01 +02:00
logger . error ( e ) { " [ $aeronLogInfo - ${handshake.connectKey} ] Connection ( ${newConnection.id} ) to $remoteAddressString error during handshake " }
2022-06-27 15:31:40 +02:00
throw e
2021-07-06 15:38:53 +02:00
}
2022-06-27 15:31:40 +02:00
isConnected = true
2020-08-12 23:30:16 +02:00
2022-07-18 05:08:01 +02:00
logger . debug { " [ $aeronLogInfo - ${handshake.connectKey} ] Connection ( ${newConnection.id} ) to $remoteAddressString done with handshake. " }
2022-06-16 18:04:52 +02:00
2022-08-02 12:11:36 +02:00
// this forces the current thread to WAIT until the network poll system has started
val pollStartupLatch = CountDownLatch ( 1 )
2021-04-30 16:01:25 +02:00
2022-06-27 15:31:40 +02:00
// have to make a new thread to listen for incoming data!
// SUBSCRIPTIONS ARE NOT THREAD SAFE! Only one thread at a time can poll them
2021-04-29 02:10:34 +02:00
2022-08-02 12:11:36 +02:00
val networkEventProcessor = Runnable {
pollStartupLatch . countDown ( )
2020-09-02 02:39:05 +02:00
2022-08-02 12:11:36 +02:00
val pollIdleStrategy = config . pollIdleStrategy . cloneToNormal ( )
2021-04-30 16:01:25 +02:00
2022-06-27 15:31:40 +02:00
while ( !is Shutdown ( ) ) {
2022-07-03 13:13:38 +02:00
if ( ! newConnection . isClosedViaAeron ( ) ) {
// Polls the AERON media driver subscription channel for incoming messages
2022-08-02 12:11:36 +02:00
val pollCount = newConnection . poll ( )
2022-07-03 13:13:38 +02:00
// 0 means we idle. >0 means reset and don't idle (because there are likely more poll events)
pollIdleStrategy . idle ( pollCount )
} else {
2022-06-27 15:31:40 +02:00
// If the connection has either been closed, or has expired, it needs to be cleaned-up/deleted.
2022-07-18 05:08:01 +02:00
logger . debug { " [ $aeronLogInfo ] connection expired " }
2021-04-30 16:01:25 +02:00
2022-08-02 21:15:45 +02:00
// NOTE: We do not shutdown the client!! The client is only closed by explicitly calling `client.close()`
newConnection . close ( )
2022-08-02 12:11:36 +02:00
return @Runnable
2022-06-27 15:31:40 +02:00
}
2021-04-30 16:01:25 +02:00
}
2022-06-27 15:31:40 +02:00
}
2022-08-02 12:11:36 +02:00
config . networkInterfaceEventDispatcher . submit ( networkEventProcessor )
2021-07-06 15:38:53 +02:00
2022-08-02 12:11:36 +02:00
pollStartupLatch . await ( )
2022-06-27 15:31:40 +02:00
2022-08-02 12:11:36 +02:00
// these have to be in two SEPARATE "runnables" otherwise...
// if something inside-of listenerManager.notifyConnect is blocking or suspends, then polling will never happen!
actionDispatch . launch {
2022-06-27 15:31:40 +02:00
lockStepForConnect . getAndSet ( null ) ?. withLock { }
listenerManager . notifyConnect ( newConnection )
2020-08-12 23:30:16 +02:00
}
}
2020-09-02 02:39:05 +02:00
/ * *
2020-09-03 01:31:08 +02:00
* true if the remote public key changed . This can be useful if specific actions are necessary when the key has changed .
2020-09-02 02:39:05 +02:00
* /
2020-09-03 01:31:08 +02:00
val remoteKeyHasChanged : Boolean
get ( ) = connection . hasRemoteKeyChanged ( )
2020-08-12 23:30:43 +02:00
2020-09-02 02:39:05 +02:00
/ * *
2020-09-03 01:31:08 +02:00
* true if this connection is an IPC connection
2020-09-02 02:39:05 +02:00
* /
2020-09-03 01:31:08 +02:00
val isIPC : Boolean
get ( ) = connection . isIpc
2020-08-12 23:30:43 +02:00
2020-09-02 02:39:05 +02:00
/ * *
* @return true if this connection is a network connection
* /
2020-09-03 01:31:08 +02:00
val isNetwork : Boolean
get ( ) = connection . isNetwork
2020-08-12 23:30:43 +02:00
2020-09-02 02:39:05 +02:00
/ * *
* @return the connection ( TCP or IPC ) id of this connection .
* /
2020-09-03 01:31:08 +02:00
val id : Int
get ( ) = connection . id
2020-08-12 23:30:16 +02:00
/ * *
2020-09-03 01:31:08 +02:00
* the connection used by the client , this is only valid after the client has connected
2020-08-12 23:30:16 +02:00
* /
2020-09-03 01:31:08 +02:00
val connection : CONNECTION
get ( ) = connection0 as CONNECTION
2020-08-12 23:30:16 +02:00
/ * *
2020-09-22 19:42:04 +02:00
* Sends a message to the server , if the connection is closed for any reason , this returns false .
*
* @return true if the message was sent successfully , false if the connection has been closed
* /
2022-08-02 12:11:36 +02:00
fun send ( message : Any ) : Boolean {
2020-09-22 19:42:04 +02:00
val c = connection0
return if ( c != null ) {
c . send ( message )
} else {
val exception = ClientException ( " Cannot send a message when there is no connection! " )
2022-05-30 02:45:50 +02:00
logger . error ( exception ) { " No connection! " }
2020-09-22 19:42:04 +02:00
false
}
}
2021-04-29 10:25:25 +02:00
/ * *
* Sends a " ping " packet to measure * * ROUND TRIP * * time to the remote connection .
2020-09-22 19:42:04 +02:00
*
2021-04-29 10:25:25 +02:00
* @param function called when the ping returns ( ie : update time / latency counters / metrics / etc )
2021-07-06 15:38:53 +02:00
*
* @return true if the ping was successfully sent to the client
2020-08-12 23:30:16 +02:00
* /
2021-07-08 14:33:24 +02:00
suspend fun ping ( pingTimeoutSeconds : Int = PingManager . DEFAULT _TIMEOUT _SECONDS , function : suspend Ping . ( ) -> Unit ) : Boolean {
2020-09-03 01:31:08 +02:00
val c = connection0
2021-04-29 10:25:25 +02:00
2020-08-12 23:30:16 +02:00
if ( c != null ) {
2021-07-08 14:33:24 +02:00
return pingManager . ping ( c , pingTimeoutSeconds , actionDispatch , responseManager , logger , function )
2020-08-12 23:30:16 +02:00
} else {
2022-05-30 02:45:50 +02:00
logger . error ( ClientException ( " Cannot send a ping when there is no connection! " ) ) { " No connection! " }
2020-08-12 23:30:16 +02:00
}
2021-07-06 15:38:53 +02:00
return false
2020-08-12 23:30:16 +02:00
}
2020-08-12 23:30:43 +02:00
/ * *
2021-04-29 10:25:25 +02:00
* Sends a " ping " packet to measure * * ROUND TRIP * * time to the remote connection .
*
* @param function called when the ping returns ( ie : update time / latency counters / metrics / etc )
2020-08-12 23:30:43 +02:00
* /
2021-07-08 14:33:24 +02:00
fun pingBlocking ( pingTimeoutSeconds : Int = PingManager . DEFAULT _TIMEOUT _SECONDS , function : suspend Ping . ( ) -> Unit ) : Boolean {
2021-07-06 15:38:53 +02:00
return runBlocking {
2021-07-08 14:33:24 +02:00
ping ( pingTimeoutSeconds , function )
2021-04-29 10:25:25 +02:00
}
}
2020-08-12 23:30:16 +02:00
2020-09-03 01:31:08 +02:00
/ * *
* Removes the specified host address from the list of registered server keys .
* /
2020-09-09 01:33:09 +02:00
fun removeRegisteredServerKey ( address : InetAddress ) {
2021-04-29 10:02:59 +02:00
val savedPublicKey = storage . getRegisteredServerKey ( address )
2020-08-12 23:30:16 +02:00
if ( savedPublicKey != null ) {
2020-09-09 01:33:09 +02:00
logger . debug { " Deleting remote IP address key $address " }
2021-04-29 10:02:59 +02:00
storage . removeRegisteredServerKey ( address )
2020-08-12 23:30:16 +02:00
}
}
2021-07-06 15:38:53 +02:00
final override fun close0 ( ) {
2022-07-15 06:58:49 +02:00
// no impl
2020-08-12 23:30:43 +02:00
}
2020-08-12 23:30:16 +02:00
}