Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2026 OkHttp Authors
*
* 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 okhttp3.internal.concurrent

import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

fun TaskRunner.schedule(
name: String,
delay: Duration = 0.milliseconds,
block: () -> Unit,
) {
newQueue().schedule(name, delay.inWholeNanoseconds) {
block()
-1L
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright (c) 2026 OkHttp Authors
*
* 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 okhttp3.sockets

import java.net.InetSocketAddress
import java.net.SocketException
import okhttp3.internal.concurrent.Lockable
import okhttp3.internal.concurrent.notifyAll
import okhttp3.internal.concurrent.withLock
import okio.Socket
import okio.Timeout

/**
* This class implements a rendezvous point for [Handshaker.ClientInputs] and [Handshaker.ServerInputs]. When they're
* both provided, the client caller does a handshake and shares the handshake result.
*/
internal class FakeConnection(
val clientAddress: InetSocketAddress,
val serverAddress: InetSocketAddress,
val clientSocket: Socket,
val serverSocket: Socket,
) : Lockable {
private var closed = false
private var serverInputs: Handshaker.ServerInputs? = null
private var handshakeResult: Result<Handshaker.Result>? = null

fun handshake(
handshaker: Handshaker,
clientInputs: Handshaker.ClientInputs,
timeout: Timeout,
): Handshaker.Result {
val serverInputs =
withLock {
awaitServerInputs(timeout)
}

// Destroy the unencrypted socket pair; we'll build a new encrypted one to replace it.
clientSocket.cancel()
serverSocket.cancel()

val result =
runCatching {
handshaker.handshake(clientInputs, serverInputs)
}

withLock {
this.handshakeResult = result
notifyAll()
}

return result.getOrThrow()
}

private tailrec fun awaitServerInputs(timeout: Timeout): Handshaker.ServerInputs {
if (closed) throw SocketException("closed")
serverInputs?.let { return it }
timeout.waitUntilNotified(this)
return awaitServerInputs(timeout)
}

fun handshake(
serverInputs: Handshaker.ServerInputs,
timeout: Timeout,
): Handshaker.Result {
withLock {
this.serverInputs = serverInputs
notifyAll()
return awaitResult(timeout).getOrThrow()
}
}

private tailrec fun awaitResult(timeout: Timeout): Result<Handshaker.Result> {
if (closed) throw SocketException("closed")
handshakeResult?.let { return it }
timeout.waitUntilNotified(this)
return awaitResult(timeout)
}

fun close() {
withLock {
closed = true
notifyAll()
}
}

override fun toString() = "$clientAddress<->$serverAddress"
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import okhttp3.internal.concurrent.wait
import okhttp3.internal.concurrent.withLock
import okhttp3.internal.connection.asBufferedSocket
import okio.Buffer
import okio.Socket
import okio.Timeout
import okio.inMemorySocketPair

Expand All @@ -57,7 +56,7 @@ class FakeNetwork {
get() = InetAddress.getByAddress(byteArrayOf(0, 0, 0, 0))

/** Generate a new unique address. */
internal fun nextSocketAddress(): InetSocketAddress {
fun nextSocketAddress(): InetSocketAddress {
val ipv4AddressInt = nextIpv4Address.getAndIncrement()
val ipv4AddressBytes =
Buffer()
Expand Down Expand Up @@ -269,12 +268,3 @@ internal class BoundServer(

override fun toString() = "Server@$serverAddress"
}

internal class FakeConnection(
val clientAddress: InetSocketAddress,
val serverAddress: InetSocketAddress,
val clientSocket: Socket,
val serverSocket: Socket,
) {
override fun toString() = "$clientAddress<->$serverAddress"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2026 OkHttp Authors
*
* 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 okhttp3.sockets

import okio.inMemorySocketPair

/** Returns a two-element array containing mutually-connected sockets. */
internal fun FakeNetwork.socketPair(): Array<FakeSocket> {
val (clientOkioSocket, serverOkioSocket) = inMemorySocketPair(maxBufferSize = 1024 * 1024)
val connection =
FakeConnection(
clientAddress = nextSocketAddress(),
serverAddress = nextSocketAddress(),
clientSocket = clientOkioSocket,
serverSocket = serverOkioSocket,
)

val clientJavaNetSocket =
FakeSocket(
network = this,
initialState =
FakeSocket.State.Connected(
connection = connection,
localAddress = connection.clientAddress,
remoteAddress = connection.serverAddress,
socket = connection.clientSocket,
),
)

val serverJavaNetSocket =
FakeSocket(
network = this,
initialState =
FakeSocket.State.Connected(
connection = connection,
localAddress = connection.serverAddress,
remoteAddress = connection.clientAddress,
socket = connection.serverSocket,
),
)

return arrayOf(clientJavaNetSocket, serverJavaNetSocket)
}

internal fun FakeTls.clientSocket(
socket: FakeSocket,
serverHostname: String = "testing.lysine.dev",
serverPort: Int = 443,
): FakeSslSocket = sslSocketFactory.createSocket(socket, serverHostname, serverPort, true) as FakeSslSocket

internal fun FakeTls.serverSocket(
socket: FakeSocket,
clientPort: Int = 1024,
): FakeSslSocket {
val result = sslSocketFactory.createSocket(socket, null, clientPort, true) as FakeSslSocket
result.useClientMode = false
return result
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ internal class FakeServerSocket(
network = network,
initialState =
FakeSocket.State.Connected(
connection = connection,
localAddress = connection.serverAddress,
remoteAddress = connection.clientAddress,
source = SocketSource(connection.serverSocket.source),
sink = SocketSink(connection.serverSocket.sink),
socket = connection.serverSocket,
),
)

Expand Down
Loading
Loading