Network/src/dorkbox/network/handshake/ServerConnectionDriver.kt

182 lines
7.8 KiB
Kotlin

/*
* Copyright 2023 dorkbox, llc
*
* 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.handshake
import dorkbox.network.aeron.AeronDriver
import dorkbox.network.aeron.AeronDriver.Companion.uri
import dorkbox.network.connection.EndPoint
import dorkbox.network.connection.IpInfo
import io.aeron.CommonContext
import java.net.Inet4Address
import java.net.InetAddress
/**
* Set up the subscription + publication channels back to the client
*
* Note: this class is NOT closed the traditional way! It's pub/sub objects are used by the connection (which is where they are closed)
*
* This represents the connection PAIR between a server<->client
*/
internal class ServerConnectionDriver(val pubSub: PubSub) {
companion object {
fun build(isIpc: Boolean,
aeronDriver: AeronDriver,
sessionIdPub: Int, sessionIdSub: Int,
streamIdPub: Int, streamIdSub: Int,
ipInfo: IpInfo,
remoteAddress: InetAddress?,
remoteAddressString: String,
portPubMdc: Int, portPub: Int, portSub: Int,
reliable: Boolean,
tagName: String,
logInfo: String): ServerConnectionDriver {
val pubSub: PubSub
if (isIpc) {
pubSub = buildIPC(
aeronDriver = aeronDriver,
sessionIdPub = sessionIdPub,
sessionIdSub = sessionIdSub,
streamIdPub = streamIdPub,
streamIdSub = streamIdSub,
reliable = reliable,
tagName = tagName,
logInfo = logInfo
)
} else {
pubSub = buildUdp(
aeronDriver = aeronDriver,
ipInfo = ipInfo,
sessionIdPub = sessionIdPub,
sessionIdSub = sessionIdSub,
streamIdPub = streamIdPub,
streamIdSub = streamIdSub,
remoteAddress = remoteAddress!!,
remoteAddressString = remoteAddressString,
portPubMdc = portPubMdc,
portPub = portPub,
portSub = portSub,
reliable = reliable,
tagName = tagName,
logInfo = logInfo
)
}
return ServerConnectionDriver(pubSub)
}
private fun buildIPC(
aeronDriver: AeronDriver,
sessionIdPub: Int, sessionIdSub: Int,
streamIdPub: Int, streamIdSub: Int,
reliable: Boolean,
tagName: String,
logInfo: String
): PubSub {
// on close, the publication CAN linger (in case a client goes away, and then comes back)
// AERON_PUBLICATION_LINGER_TIMEOUT, 5s by default (this can also be set as a URI param)
// create a new publication for the connection (since the handshake ALWAYS closes the current publication)
val publicationUri = uri(CommonContext.IPC_MEDIA, sessionIdPub, reliable)
// 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 or re-entrant with the client.
val publication = aeronDriver.addPublication(publicationUri, streamIdPub, logInfo, true)
// Create a subscription at the given address and port, using the given stream ID.
val subscriptionUri = uri(CommonContext.IPC_MEDIA, sessionIdSub, reliable)
val subscription = aeronDriver.addSubscription(subscriptionUri, streamIdSub, logInfo, true)
return PubSub(
pub = publication,
sub = subscription,
sessionIdPub = sessionIdPub,
sessionIdSub = sessionIdSub,
streamIdPub = streamIdPub,
streamIdSub = streamIdSub,
reliable = reliable,
remoteAddress = null,
remoteAddressString = EndPoint.IPC_NAME,
portPub = 0,
portSub = 0,
tagName = tagName
)
}
private fun buildUdp(
aeronDriver: AeronDriver,
ipInfo: IpInfo,
sessionIdPub: Int, sessionIdSub: Int,
streamIdPub: Int, streamIdSub: Int,
remoteAddress: InetAddress, remoteAddressString: String,
portPubMdc: Int, // this is the MDC port - used to dynamically discover the portPub value (but we manually save this info)
portPub: Int,
portSub: Int,
reliable: Boolean,
tagName: String,
logInfo: String
): PubSub {
// on close, the publication CAN linger (in case a client goes away, and then comes back)
// AERON_PUBLICATION_LINGER_TIMEOUT, 5s by default (this can also be set as a URI param)
// connection timeout of 0 doesn't matter. it is not used by the server
// the client address WILL BE either IPv4 or IPv6
val isRemoteIpv4 = remoteAddress is Inet4Address
// create a new publication for the connection (since the handshake ALWAYS closes the current publication)
// we explicitly have the publisher "connect to itself", because we are using MDC to work around NAT
// A control endpoint for the subscriptions will cause a periodic service management "heartbeat" to be sent to the
// remote endpoint publication, which permits the remote publication to send us data, thereby getting us around NAT
val publicationUri = uri(CommonContext.UDP_MEDIA, sessionIdPub, reliable)
.controlEndpoint(ipInfo.getAeronPubAddress(isRemoteIpv4) + ":" + portPubMdc) // this is the control port! (listens to status messages and NAK from client)
// 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 or re-entrant with the client.
val publication = aeronDriver.addPublication(publicationUri, streamIdPub, logInfo, false)
// if we are IPv6 WILDCARD -- then our subscription must ALSO be IPv6, even if our connection is via IPv4
// Create a subscription at the given address and port, using the given stream ID.
val subscriptionUri = uri(CommonContext.UDP_MEDIA, sessionIdSub, reliable)
.endpoint(ipInfo.formattedListenAddressString + ":" + portSub)
val subscription = aeronDriver.addSubscription(subscriptionUri, streamIdSub, logInfo, false)
return PubSub(
pub = publication,
sub = subscription,
sessionIdPub = sessionIdPub,
sessionIdSub = sessionIdSub,
streamIdPub = streamIdPub,
streamIdSub = streamIdSub,
reliable = reliable,
remoteAddress = remoteAddress,
remoteAddressString = remoteAddressString,
portPub = portPub,
portSub = portSub,
tagName = tagName
)
}
}
}