I'm trying to create a TLS Socket after reading so...
# ktor
e
I'm trying to create a TLS Socket after reading something from a plain Socket.
Copy code
val tcp = aSocket(SelectorManagerHolder.instance()).tcp()
socket = tcp.connect(address.host, address.port) {
  noDelay = true
  keepAlive = true
}

readChannel = socket.openReadChannel()
writeChannel = socket.openWriteChannel()

// This reads from the plain socket
val properties = deserialize(::ServerProperties)

if (properties.ssl) {
  socket = socket.tls(currentCoroutineContext()) {
    trustManager = NoopTrustManager
  }
}
However I get the following exception:
Copy code
reading channel has already been set
java.lang.IllegalStateException: reading channel has already been set
	at io.ktor.network.sockets.NIOSocketImpl.attachFor(NIOSocketImpl.kt:90)
	at io.ktor.network.sockets.NIOSocketImpl.attachForReading(NIOSocketImpl.kt:46)
	at io.ktor.network.sockets.SocketsKt.openReadChannel(Sockets.kt:113)
What am I missing here?
a
The exception occurs because the
tls
method call opens the read and write channels, which are already opened explicitly in your code.
e
@Aleksei Tirman [JB] ahh! But the problem is, how do I read the initial properties from the server without opening a read channel myself?
val properties = deserialize(::ServerProperties)
uses
readChannel
internally, so I need it.
a
Can you reconnect to the socket after reading the initial properties?
e
Nope, because the server maintains the session information. An initial read for those properties is required
That is, I can't perform a straight connection via the TLS socket. I would not receive those properties.
o
try to create
Connection
in the beginning from
socket
and then use
tls
extension on `Connection`: https://github.com/ktorio/ktor/blob/b813aebb82b8c9c76dd575762250815b6b27e446/ktor-[…]ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSCommon.kt AFAIK it doesn't open channels, as they are already opened and stored inside
Connection
gratitude thank you 1
Copy code
val tcp = aSocket(SelectorManagerHolder.instance()).tcp()
val connection = tcp.connect(address.host, address.port) {
  noDelay = true
  keepAlive = true
}

socket = connection.socket
readChannel = connection.input
writeChannel = connection.output

// This reads from the plain socket
val properties = deserialize(::ServerProperties)

if (properties.ssl) {
  socket = connection.tls(currentCoroutineContext()) {
    trustManager = NoopTrustManager
  }
}
something like this or you can even store
connection
instead of
socket
and it's channels, so to make sure that they are kept in sync
e
Yup, that's a step forward! Thank you! Now I need to understand why it throws
Copy code
Operation is already in progress
java.lang.IllegalStateException: Operation is already in progress
	at io.ktor.utils.io.ByteBufferChannel.suspensionForSize(ByteBufferChannel.kt:3638)
	at io.ktor.utils.io.ByteBufferChannel.readSuspendImpl(ByteBufferChannel.kt:2238)
	at io.ktor.utils.io.ByteBufferChannel.readSuspendLoop(ByteBufferChannel.kt:2189)
	at io.ktor.utils.io.ByteBufferChannel.readSuspend(ByteBufferChannel.kt:2173)
	at io.ktor.utils.io.ByteBufferChannel.readInt(ByteBufferChannel.kt:2560)
	at com.hcl.mainframe.proto.internal.JvmSocket$readUInt$uint$1.invokeSuspend(JvmSocket.kt:133)
	at com.hcl.mainframe.proto.internal.JvmSocket$readUInt$uint$1.invoke(JvmSocket.kt)
	at com.hcl.mainframe.proto.internal.JvmSocket$readUInt$uint$1.invoke(JvmSocket.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:72)
	at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:148)
	at kotlinx.coroutines.TimeoutKt.withTimeoutOrNull(Timeout.kt:104)
	at com.hcl.mainframe.proto.internal.JvmSocket.readUInt(JvmSocket.kt:132)
	at com.hcl.mainframe.proto.internal.JvmSocket.deserialize(JvmSocket.kt:101)
Ah ok! Found out why.
Copy code
val tlsSocket = this.connection.tls(currentCoroutineContext()) {
 trustManager = NoopTrustManager
}

this.connection = tlsSocket.connection()
Basically the original plain socket channels are used for the TLS handshake, and then new channels need to be binded.
Probably this requires a new chapter in the socket docs. Wouldn't have figured it out for sure on my own.
Will open a PR to add an example if you're OK with it.
I've just discovered the original Java implementation does a second handshake, for some reason which I need to investigate.
Copy code
if (sslSocket != null) {
    // Closes the SSL socket before the second handshake
    sslSocket.close();
}

SSLContext ctx = getSSLContext(hostDetails);
sslSocket = (SSLSocket) ctx.getSocketFactory().createSocket(plainSocket, this.hostName, this.portNumber, false);
Any way to do the same in Ktor? Basically I'd need to re-negotiate. The plain socket is kept alive between all this.