Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
jvmTest.okhttp3.internal.http2.Http2ConnectionTest.kt Maven / Gradle / Ivy
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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.http2
import java.io.EOFException
import java.io.IOException
import java.io.InterruptedIOException
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import okhttp3.Headers.Companion.headersOf
import okhttp3.TestUtil.headerEntries
import okhttp3.TestUtil.repeat
import okhttp3.internal.EMPTY_BYTE_ARRAY
import okhttp3.internal.EMPTY_HEADERS
import okhttp3.internal.concurrent.TaskFaker
import okhttp3.internal.concurrent.TaskRunner
import okhttp3.internal.notifyAll
import okhttp3.internal.wait
import okio.AsyncTimeout
import okio.Buffer
import okio.BufferedSource
import okio.Source
import okio.buffer
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.data.Offset
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.fail
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
@Timeout(5)
@Tag("Slow")
class Http2ConnectionTest {
private val peer = MockHttp2Peer()
private val taskFaker = TaskFaker()
@AfterEach fun tearDown() {
peer.close()
taskFaker.close()
}
@Test fun serverPingsClientHttp2() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.sendFrame().ping(false, 2, 3)
peer.acceptFrame() // PING
peer.play()
// Play it back.
connect(peer)
// Verify the peer received what was expected.
val ping = peer.takeFrame()
assertThat(ping.type).isEqualTo(Http2.TYPE_PING)
assertThat(ping.streamId).isEqualTo(0)
assertThat(ping.payload1).isEqualTo(2)
assertThat(ping.payload2).isEqualTo(3)
assertThat(ping.ack).isTrue
}
@Test fun peerHttp2ServerLowersInitialWindowSize() {
val initial = Settings()
initial[Settings.INITIAL_WINDOW_SIZE] = 1684
val shouldntImpactConnection = Settings()
shouldntImpactConnection[Settings.INITIAL_WINDOW_SIZE] = 3368
peer.sendFrame().settings(initial)
peer.acceptFrame() // ACK
peer.sendFrame().settings(shouldntImpactConnection)
peer.acceptFrame() // ACK 2
peer.acceptFrame() // HEADERS
peer.play()
val connection = connect(peer)
// Verify the peer received the second ACK.
val ackFrame = peer.takeFrame()
assertThat(ackFrame.type).isEqualTo(Http2.TYPE_SETTINGS)
assertThat(ackFrame.streamId).isEqualTo(0)
assertThat(ackFrame.ack).isTrue
// This stream was created *after* the connection settings were adjusted.
val stream = connection.newStream(headerEntries("a", "android"), false)
assertThat(connection.peerSettings.initialWindowSize).isEqualTo(3368L)
// New Stream is has the most recent initial window size.
assertThat(stream.writeBytesTotal).isEqualTo(0L)
assertThat(stream.writeBytesMaximum).isEqualTo(3368L)
}
@Test fun peerHttp2ServerZerosCompressionTable() {
val client = false // Peer is server, so we are client.
val settings = Settings()
settings[Settings.HEADER_TABLE_SIZE] = 0
val connection = connectWithSettings(client, settings)
// Verify the peer's settings were read and applied.
assertThat(connection.peerSettings.headerTableSize).isEqualTo(0)
val writer = connection.writer
assertThat(writer.hpackWriter.dynamicTableByteCount).isEqualTo(0)
assertThat(writer.hpackWriter.headerTableSizeSetting).isEqualTo(0)
}
@Test fun peerHttp2ClientDisablesPush() {
val client = false // Peer is client, so we are server.
val settings = Settings()
settings[Settings.ENABLE_PUSH] = 0 // The peer client disables push.
val connection = connectWithSettings(client, settings)
// verify the peer's settings were read and applied.
assertThat(connection.peerSettings.getEnablePush(true)).isFalse
}
@Test fun peerIncreasesMaxFrameSize() {
val newMaxFrameSize = 0x4001
val settings = Settings()
settings[Settings.MAX_FRAME_SIZE] = newMaxFrameSize
val connection = connectWithSettings(true, settings)
// verify the peer's settings were read and applied.
assertThat(connection.peerSettings.getMaxFrameSize(-1)).isEqualTo(newMaxFrameSize)
assertThat(connection.writer.maxDataLength()).isEqualTo(newMaxFrameSize)
}
/**
* Webservers may set the initial window size to zero, which is a special case because it means
* that we have to flush headers immediately before any request body can be sent.
* https://github.com/square/okhttp/issues/2543
*/
@Test fun peerSetsZeroFlowControl() {
peer.setClient(true)
// Write the mocking script.
peer.sendFrame().settings(Settings().set(Settings.INITIAL_WINDOW_SIZE, 0))
peer.acceptFrame() // ACK
peer.sendFrame().windowUpdate(0, 10) // Increase the connection window size.
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0)
peer.acceptFrame() // HEADERS STREAM 3
peer.sendFrame().windowUpdate(3, 5)
peer.acceptFrame() // DATA STREAM 3 "abcde"
peer.sendFrame().windowUpdate(3, 5)
peer.acceptFrame() // DATA STREAM 3 "fghi"
peer.play()
// Play it back.
val connection = connect(peer)
connection.writePingAndAwaitPong() // Ensure the SETTINGS have been received.
val stream = connection.newStream(headerEntries("a", "android"), true)
val sink = stream.getSink().buffer()
sink.writeUtf8("abcdefghi")
sink.flush()
// Verify the peer received what was expected.
peer.takeFrame() // PING
val headers = peer.takeFrame()
assertThat(headers.type).isEqualTo(Http2.TYPE_HEADERS)
val data1 = peer.takeFrame()
assertThat(data1.type).isEqualTo(Http2.TYPE_DATA)
assertThat(data1.streamId).isEqualTo(3)
assertArrayEquals("abcde".toByteArray(), data1.data)
val data2 = peer.takeFrame()
assertThat(data2.type).isEqualTo(Http2.TYPE_DATA)
assertThat(data2.streamId).isEqualTo(3)
assertArrayEquals("fghi".toByteArray(), data2.data)
}
/**
* Confirm that we account for discarded data frames. It's possible that data frames are in-flight
* just prior to us canceling a stream.
*/
@Test fun discardedDataFramesAreCounted() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM 3
peer.sendFrame().headers(false, 3, headerEntries("a", "apple"))
peer.sendFrame().data(false, 3, data(1024), 1024)
peer.acceptFrame() // RST_STREAM
peer.sendFrame().data(true, 3, data(1024), 1024)
peer.acceptFrame() // RST_STREAM
peer.play()
val connection = connect(peer)
val stream1 = connection.newStream(headerEntries("b", "bark"), false)
val source = stream1.getSource()
val buffer = Buffer()
while (buffer.size != 1024L) source.read(buffer, 1024)
stream1.close(ErrorCode.CANCEL, null)
val frame1 = peer.takeFrame()
assertThat(frame1.type).isEqualTo(Http2.TYPE_HEADERS)
val frame2 = peer.takeFrame()
assertThat(frame2.type).isEqualTo(Http2.TYPE_RST_STREAM)
val frame3 = peer.takeFrame()
assertThat(frame3.type).isEqualTo(Http2.TYPE_RST_STREAM)
assertThat(connection.readBytesAcknowledged).isEqualTo(0L)
assertThat(connection.readBytesTotal).isEqualTo(2048L)
}
@Test fun receiveGoAwayHttp2() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM 3
peer.acceptFrame() // SYN_STREAM 5
peer.sendFrame().goAway(3, ErrorCode.PROTOCOL_ERROR, EMPTY_BYTE_ARRAY)
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0)
peer.acceptFrame() // DATA STREAM 3
peer.play()
// Play it back.
val connection = connect(peer)
val stream1 = connection.newStream(headerEntries("a", "android"), true)
val stream2 = connection.newStream(headerEntries("b", "banana"), true)
connection.writePingAndAwaitPong() // Ensure the GO_AWAY that resets stream2 has been received.
val sink1 = stream1.getSink().buffer()
val sink2 = stream2.getSink().buffer()
sink1.writeUtf8("abc")
try {
sink2.writeUtf8("abc")
sink2.flush()
fail()
} catch (expected: IOException) {
assertThat(expected.message).isEqualTo("stream was reset: REFUSED_STREAM")
}
sink1.writeUtf8("def")
sink1.close()
try {
connection.newStream(headerEntries("c", "cola"), true)
fail()
} catch (expected: ConnectionShutdownException) {
}
assertThat(stream1.isOpen).isTrue
assertThat(stream2.isOpen).isFalse
assertThat(connection.openStreamCount()).isEqualTo(1)
// Verify the peer received what was expected.
val synStream1 = peer.takeFrame()
assertThat(synStream1.type).isEqualTo(Http2.TYPE_HEADERS)
val synStream2 = peer.takeFrame()
assertThat(synStream2.type).isEqualTo(Http2.TYPE_HEADERS)
val ping = peer.takeFrame()
assertThat(ping.type).isEqualTo(Http2.TYPE_PING)
val data1 = peer.takeFrame()
assertThat(data1.type).isEqualTo(Http2.TYPE_DATA)
assertThat(data1.streamId).isEqualTo(3)
assertArrayEquals("abcdef".toByteArray(), data1.data)
}
@Test fun readSendsWindowUpdateHttp2() {
val windowSize = 100
val windowUpdateThreshold = 50
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
for (i in 0..2) {
// Send frames of summing to size 50, which is windowUpdateThreshold.
peer.sendFrame().data(false, 3, data(24), 24)
peer.sendFrame().data(false, 3, data(25), 25)
peer.sendFrame().data(false, 3, data(1), 1)
peer.acceptFrame() // connection WINDOW UPDATE
peer.acceptFrame() // stream WINDOW UPDATE
}
peer.sendFrame().data(true, 3, data(0), 0)
peer.play()
// Play it back.
val connection = connect(peer)
connection.okHttpSettings[Settings.INITIAL_WINDOW_SIZE] = windowSize
val stream = connection.newStream(headerEntries("b", "banana"), false)
assertThat(stream.readBytesAcknowledged).isEqualTo(0L)
assertThat(stream.readBytesTotal).isEqualTo(0L)
assertThat(stream.takeHeaders()).isEqualTo(headersOf("a", "android"))
val source = stream.getSource()
val buffer = Buffer()
buffer.writeAll(source)
assertThat(source.read(buffer, 1)).isEqualTo(-1)
assertThat(buffer.size).isEqualTo(150)
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
for (i in 0..2) {
val windowUpdateStreamIds: MutableList = ArrayList(2)
for (j in 0..1) {
val windowUpdate = peer.takeFrame()
assertThat(windowUpdate.type).isEqualTo(Http2.TYPE_WINDOW_UPDATE)
windowUpdateStreamIds.add(windowUpdate.streamId)
assertThat(windowUpdate.windowSizeIncrement).isEqualTo(windowUpdateThreshold.toLong())
}
// connection
assertThat(windowUpdateStreamIds).contains(0)
// stream
assertThat(windowUpdateStreamIds).contains(3)
}
}
@Test fun serverSendsEmptyDataClientDoesntSendWindowUpdateHttp2() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.sendFrame().data(true, 3, data(0), 0)
peer.play()
// Play it back.
val connection = connect(peer)
val client = connection.newStream(headerEntries("b", "banana"), false)
assertThat(client.getSource().read(Buffer(), 1)).isEqualTo(-1)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(peer.frameCount()).isEqualTo(5)
}
@Test fun clientSendsEmptyDataServerDoesntSendWindowUpdateHttp2() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // DATA
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.play()
// Play it back.
val connection = connect(peer)
val client = connection.newStream(headerEntries("b", "banana"), true)
val out = client.getSink().buffer()
out.write(EMPTY_BYTE_ARRAY)
out.flush()
out.close()
// Verify the peer received what was expected.
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_DATA)
assertThat(peer.frameCount()).isEqualTo(5)
}
@Test fun maxFrameSizeHonored() {
val buff = ByteArray(peer.maxOutboundDataLength() + 1)
buff.fill('*'.code.toByte())
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.acceptFrame() // DATA
peer.acceptFrame() // DATA
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("b", "banana"), true)
val out = stream.getSink().buffer()
out.write(buff)
out.flush()
out.close()
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
var data = peer.takeFrame()
assertThat(data.data!!.size).isEqualTo(peer.maxOutboundDataLength())
data = peer.takeFrame()
assertThat(data.data!!.size).isEqualTo(1)
}
@Test fun pushPromiseStream() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
val expectedRequestHeaders = listOf(
Header(Header.TARGET_METHOD, "GET"),
Header(Header.TARGET_SCHEME, "https"),
Header(Header.TARGET_AUTHORITY, "squareup.com"),
Header(Header.TARGET_PATH, "/cached")
)
peer.sendFrame().pushPromise(3, 2, expectedRequestHeaders)
val expectedResponseHeaders = listOf(
Header(Header.RESPONSE_STATUS, "200")
)
peer.sendFrame().headers(true, 2, expectedResponseHeaders)
peer.sendFrame().data(true, 3, data(0), 0)
peer.play()
val observer = RecordingPushObserver()
// Play it back.
val connection = connect(peer, observer, Http2Connection.Listener.REFUSE_INCOMING_STREAMS)
val client = connection.newStream(headerEntries("b", "banana"), false)
assertThat(client.getSource().read(Buffer(), 1)).isEqualTo(-1)
// Verify the peer received what was expected.
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(observer.takeEvent()).isEqualTo(expectedRequestHeaders)
assertThat(observer.takeEvent()).isEqualTo(expectedResponseHeaders)
}
@Test fun doublePushPromise() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.sendFrame().pushPromise(3, 2, headerEntries("a", "android"))
peer.acceptFrame() // SYN_REPLY
peer.sendFrame().pushPromise(3, 2, headerEntries("b", "banana"))
peer.acceptFrame() // RST_STREAM
peer.play()
// Play it back.
val connection = connect(peer)
connection.newStream(headerEntries("b", "banana"), false)
// Verify the peer received what was expected.
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(peer.takeFrame().errorCode).isEqualTo(ErrorCode.PROTOCOL_ERROR)
}
@Test fun pushPromiseStreamsAutomaticallyCancel() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.sendFrame()
.pushPromise(
streamId = 3,
promisedStreamId = 2,
requestHeaders = listOf(
Header(Header.TARGET_METHOD, "GET"),
Header(Header.TARGET_SCHEME, "https"),
Header(Header.TARGET_AUTHORITY, "squareup.com"),
Header(Header.TARGET_PATH, "/cached")
)
)
peer.sendFrame()
.headers(
outFinished = true,
streamId = 2,
headerBlock = listOf(
Header(Header.RESPONSE_STATUS, "200")
)
)
peer.acceptFrame() // RST_STREAM
peer.play()
// Play it back.
connect(peer, PushObserver.CANCEL, Http2Connection.Listener.REFUSE_INCOMING_STREAMS)
// Verify the peer received what was expected.
val rstStream = peer.takeFrame()
assertThat(rstStream.type).isEqualTo(Http2.TYPE_RST_STREAM)
assertThat(rstStream.streamId).isEqualTo(2)
assertThat(rstStream.errorCode).isEqualTo(ErrorCode.CANCEL)
}
/**
* When writing a set of headers fails due to an `IOException`, make sure the writer is left
* in a consistent state so the next writer also gets an `IOException` also instead of
* something worse (like an [IllegalStateException].
*
*
* See https://github.com/square/okhttp/issues/1651
*/
@Test fun socketExceptionWhileWritingHeaders() {
peer.acceptFrame() // SYN_STREAM.
peer.play()
val longString = repeat('a', Http2.INITIAL_MAX_FRAME_SIZE + 1)
val socket = peer.openSocket()
val connection = Http2Connection.Builder(true, TaskRunner.INSTANCE)
.socket(socket)
.pushObserver(IGNORE)
.build()
connection.start( /* sendConnectionPreface = */false)
socket.shutdownOutput()
try {
connection.newStream(headerEntries("a", longString), false)
fail()
} catch (expected: IOException) {
}
try {
connection.newStream(headerEntries("b", longString), false)
fail()
} catch (expected: IOException) {
}
}
@Test fun clientCreatesStreamAndServerReplies() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // DATA
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.sendFrame().data(true, 3, Buffer().writeUtf8("robot"), 5)
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0) // PING
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("b", "banana"), true)
val out = stream.getSink().buffer()
out.writeUtf8("c3po")
out.close()
assertThat(stream.takeHeaders()).isEqualTo(headersOf("a", "android"))
assertStreamData("robot", stream.getSource())
connection.writePingAndAwaitPong()
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(synStream.outFinished).isFalse
assertThat(synStream.streamId).isEqualTo(3)
assertThat(synStream.associatedStreamId).isEqualTo(-1)
assertThat(synStream.headerBlock).isEqualTo(headerEntries("b", "banana"))
val requestData = peer.takeFrame()
assertArrayEquals("c3po".toByteArray(), requestData.data)
}
@Test fun serverFinishesStreamWithHeaders() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // PING
peer.sendFrame().headers(true, 3, headerEntries("headers", "bam"))
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0) // PONG
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("a", "artichaut"), false)
connection.writePingAndAwaitPong()
assertThat(stream.takeHeaders()).isEqualTo(headersOf("headers", "bam"))
assertThat(stream.trailers()).isEqualTo(EMPTY_HEADERS)
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(synStream.outFinished).isFalse
assertThat(synStream.streamId).isEqualTo(3)
assertThat(synStream.associatedStreamId).isEqualTo(-1)
assertThat(synStream.headerBlock).isEqualTo(headerEntries("a", "artichaut"))
}
@Test fun serverWritesTrailersAndClientReadsTrailers() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("headers", "bam"))
peer.acceptFrame() // PING
peer.sendFrame().headers(true, 3, headerEntries("trailers", "boom"))
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0) // PONG
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("a", "artichaut"), false)
assertThat(stream.takeHeaders()).isEqualTo(headersOf("headers", "bam"))
connection.writePingAndAwaitPong()
assertThat(stream.trailers()).isEqualTo(headersOf("trailers", "boom"))
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(synStream.outFinished).isFalse
assertThat(synStream.streamId).isEqualTo(3)
assertThat(synStream.associatedStreamId).isEqualTo(-1)
assertThat(synStream.headerBlock).isEqualTo(headerEntries("a", "artichaut"))
}
/** A server RST_STREAM shouldn't prevent the client from consuming the response body. */
@Test fun serverResponseBodyRstStream() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // PING
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.sendFrame().data(true, 3, Buffer().writeUtf8("robot"), 5)
peer.sendFrame().rstStream(3, ErrorCode.NO_ERROR)
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0) // PONG
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries(), false)
connection.writePingAndAwaitPong()
assertThat(stream.takeHeaders()).isEqualTo(headersOf("a", "android"))
val source = stream.getSource().buffer()
assertThat(source.readUtf8(5)).isEqualTo("robot")
stream.getSink().close()
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
val ping = peer.takeFrame()
assertThat(ping.type).isEqualTo(Http2.TYPE_PING)
}
/** A server RST_STREAM shouldn't prevent the client from consuming trailers. */
@Test fun serverTrailersRstStream() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // PING
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.sendFrame().headers(true, 3, headerEntries("z", "zebra"))
peer.sendFrame().rstStream(3, ErrorCode.NO_ERROR)
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0) // PONG
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries(), true)
connection.writePingAndAwaitPong()
assertThat(stream.takeHeaders()).isEqualTo(headersOf("a", "android"))
stream.getSink().close()
assertThat(stream.trailers()).isEqualTo(headersOf("z", "zebra"))
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
val ping = peer.takeFrame()
assertThat(ping.type).isEqualTo(Http2.TYPE_PING)
}
/**
* A server RST_STREAM shouldn't prevent the client from consuming the response body, even if it
* follows a truncated request body.
*/
@Test fun clientRequestBodyServerResponseBodyRstStream() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // PING
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.sendFrame().data(true, 3, Buffer().writeUtf8("robot"), 5)
peer.sendFrame().rstStream(3, ErrorCode.NO_ERROR)
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0) // PONG
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries(), true)
connection.writePingAndAwaitPong()
val sink = stream.getSink().buffer()
sink.writeUtf8("abc")
try {
sink.close()
fail()
} catch (expected: StreamResetException) {
assertThat(expected.errorCode).isEqualTo(ErrorCode.NO_ERROR)
}
assertThat(stream.takeHeaders()).isEqualTo(headersOf("a", "android"))
val source = stream.getSource().buffer()
assertThat(source.readUtf8(5)).isEqualTo("robot")
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
val ping = peer.takeFrame()
assertThat(ping.type).isEqualTo(Http2.TYPE_PING)
}
@Test fun serverWritesTrailersWithData() {
// We buffer some outbound data and headers and confirm that the END_STREAM flag comes with the
// headers (and not with the data).
// Write the mocking script. for the client
peer.setClient(true)
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.sendFrame().headers(true, 3, headerEntries("client", "abc"))
peer.acceptFrame() // ACK
peer.acceptFrame() // HEADERS STREAM 3
peer.acceptFrame() // DATA STREAM 3 "abcde"
peer.acceptFrame() // HEADERS STREAM 3
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("a", "android"), true)
stream.enqueueTrailers(headersOf("foo", "bar"))
val sink = stream.getSink().buffer()
sink.writeUtf8("abcdefghi")
sink.close()
// Verify the peer received what was expected.
val headers1 = peer.takeFrame()
assertThat(headers1.type).isEqualTo(Http2.TYPE_HEADERS)
val data1 = peer.takeFrame()
assertThat(data1.type).isEqualTo(Http2.TYPE_DATA)
assertThat(data1.streamId).isEqualTo(3)
assertArrayEquals("abcdefghi".toByteArray(), data1.data)
assertThat(data1.inFinished).isFalse
val headers2 = peer.takeFrame()
assertThat(headers2.type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(headers2.inFinished).isTrue
}
@Test fun clientCannotReadTrailersWithoutExhaustingStream() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().data(false, 3, Buffer().writeUtf8("robot"), 5)
peer.sendFrame().headers(true, 3, headerEntries("trailers", "boom"))
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0) // PONG
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("a", "artichaut"), true)
connection.writePingAndAwaitPong()
try {
stream.trailers()
fail()
} catch (expected: IllegalStateException) {
}
}
@Test fun clientCannotReadTrailersIfTheStreamFailed() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().rstStream(3, ErrorCode.PROTOCOL_ERROR)
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0) // PONG
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("a", "artichaut"), true)
connection.writePingAndAwaitPong()
try {
stream.trailers()
fail()
} catch (expected: StreamResetException) {
}
}
@Test fun serverCannotEnqueueTrailersAfterFinishingTheStream() {
peer.setClient(true)
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0)
peer.play()
// Play it back.
val connection = connect(peer)
connection.writePingAndAwaitPong()
val stream = connection.newStream(headerEntries("a", "android"), true)
// finish the stream
stream.writeHeaders(headerEntries("b", "berserk"), true, false)
try {
stream.enqueueTrailers(headersOf("trailers", "boom"))
fail()
} catch (expected: IllegalStateException) {
}
}
@Test fun noTrailersFrameYieldsEmptyTrailers() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("headers", "bam"))
peer.sendFrame().data(true, 3, Buffer().writeUtf8("robot"), 5)
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0) // PONG
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("a", "artichaut"), false)
val source = stream.getSource().buffer()
connection.writePingAndAwaitPong()
assertThat(stream.takeHeaders()).isEqualTo(headersOf("headers", "bam"))
assertThat(source.readUtf8(5)).isEqualTo("robot")
assertThat(stream.trailers()).isEqualTo(EMPTY_HEADERS)
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(synStream.outFinished).isFalse
assertThat(synStream.streamId).isEqualTo(3)
assertThat(synStream.associatedStreamId).isEqualTo(-1)
assertThat(synStream.headerBlock).isEqualTo(headerEntries("a", "artichaut"))
}
@Test fun serverReadsHeadersDataHeaders() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // DATA
peer.acceptFrame() // HEADERS
peer.sendFrame().headers(true, 3, headerEntries("a", "android"))
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0) // PING
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("b", "banana"), true)
val out = stream.getSink().buffer()
out.writeUtf8("c3po")
out.close()
stream.writeHeaders(headerEntries("e", "elephant"), false, false)
connection.writePingAndAwaitPong()
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(synStream.outFinished).isFalse
assertThat(synStream.streamId).isEqualTo(3)
assertThat(synStream.associatedStreamId).isEqualTo(-1)
assertThat(synStream.headerBlock).isEqualTo(headerEntries("b", "banana"))
val requestData = peer.takeFrame()
assertArrayEquals("c3po".toByteArray(), requestData.data)
val nextFrame = peer.takeFrame()
assertThat(nextFrame.headerBlock).isEqualTo(headerEntries("e", "elephant"))
}
@Test fun clientCreatesStreamAndServerRepliesWithFin() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // PING
peer.sendFrame().headers(true, 3, headerEntries("a", "android"))
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0)
peer.play()
// Play it back.
val connection = connect(peer)
connection.newStream(headerEntries("b", "banana"), false)
assertThat(connection.openStreamCount()).isEqualTo(1)
connection.writePingAndAwaitPong() // Ensure that the SYN_REPLY has been received.
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
val ping = peer.takeFrame()
assertThat(ping.type).isEqualTo(Http2.TYPE_PING)
}
@Test fun serverPingsClient() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.sendFrame().ping(false, 2, 0)
peer.acceptFrame() // PING
peer.play()
// Play it back.
connect(peer)
// Verify the peer received what was expected.
val ping = peer.takeFrame()
assertThat(ping.streamId).isEqualTo(0)
assertThat(ping.payload1).isEqualTo(2)
assertThat(ping.payload2).isEqualTo(0)
assertThat(ping.ack).isTrue
}
@Test fun clientPingsServer() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 5)
peer.play()
// Play it back.
val connection = connect(peer)
val pingAtNanos = System.nanoTime()
connection.writePingAndAwaitPong()
val elapsedNanos = System.nanoTime() - pingAtNanos
assertThat(elapsedNanos).isGreaterThan(0L)
assertThat(elapsedNanos).isLessThan(TimeUnit.SECONDS.toNanos(1))
// Verify the peer received what was expected.
val pingFrame = peer.takeFrame()
assertThat(pingFrame.type).isEqualTo(Http2.TYPE_PING)
assertThat(pingFrame.streamId).isEqualTo(0)
assertThat(pingFrame.payload1).isEqualTo(Http2Connection.AWAIT_PING)
assertThat(pingFrame.payload2).isEqualTo(0x4f4b6f6b) // OKok.
assertThat(pingFrame.ack).isFalse
}
@Test fun unexpectedPongIsNotReturned() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.sendFrame().ping(false, 2, 0)
peer.acceptFrame() // PING
peer.sendFrame().ping(true, 99, 0) // This pong is silently ignored.
peer.sendFrame().ping(false, 4, 0)
peer.acceptFrame() // PING
peer.play()
// Play it back.
connect(peer)
// Verify the peer received what was expected.
val ping2 = peer.takeFrame()
assertThat(ping2.payload1).isEqualTo(2)
val ping4 = peer.takeFrame()
assertThat(ping4.payload1).isEqualTo(4)
}
@Test fun serverSendsSettingsToClient() {
// Write the mocking script.
val settings = Settings()
settings[Settings.MAX_CONCURRENT_STREAMS] = 10
peer.sendFrame().settings(settings)
peer.acceptFrame() // ACK
peer.sendFrame().ping(false, 2, 0)
peer.acceptFrame() // PING
peer.play()
// Play it back.
val maxConcurrentStreamsUpdated = CountDownLatch(1)
val maxConcurrentStreams = AtomicInteger()
val listener: Http2Connection.Listener = object : Http2Connection.Listener() {
override fun onStream(stream: Http2Stream) {
throw AssertionError()
}
override fun onSettings(connection: Http2Connection, settings: Settings) {
maxConcurrentStreams.set(settings.getMaxConcurrentStreams())
maxConcurrentStreamsUpdated.countDown()
}
}
val connection = connect(peer, IGNORE, listener)
synchronized(connection) {
assertThat(connection.peerSettings.getMaxConcurrentStreams()).isEqualTo(10)
}
maxConcurrentStreamsUpdated.await()
assertThat(maxConcurrentStreams.get()).isEqualTo(10)
}
@Test fun multipleSettingsFramesAreMerged() {
// Write the mocking script.
val settings1 = Settings()
settings1[Settings.HEADER_TABLE_SIZE] = 10000
settings1[Settings.INITIAL_WINDOW_SIZE] = 20000
settings1[Settings.MAX_FRAME_SIZE] = 30000
peer.sendFrame().settings(settings1)
peer.acceptFrame() // ACK SETTINGS
val settings2 = Settings()
settings2[Settings.INITIAL_WINDOW_SIZE] = 40000
settings2[Settings.MAX_FRAME_SIZE] = 50000
settings2[Settings.MAX_CONCURRENT_STREAMS] = 60000
peer.sendFrame().settings(settings2)
peer.acceptFrame() // ACK SETTINGS
peer.sendFrame().ping(false, 2, 0)
peer.acceptFrame() // PING
peer.play()
// Play it back.
val connection = connect(peer)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_SETTINGS)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_PING)
synchronized(connection) {
assertThat(connection.peerSettings.headerTableSize).isEqualTo(10000)
assertThat(connection.peerSettings.initialWindowSize).isEqualTo(40000)
assertThat(connection.peerSettings.getMaxFrameSize(-1)).isEqualTo(50000)
assertThat(connection.peerSettings.getMaxConcurrentStreams()).isEqualTo(60000)
}
}
@Test fun clearSettingsBeforeMerge() {
// Write the mocking script.
val settings1 = Settings()
settings1[Settings.HEADER_TABLE_SIZE] = 10000
settings1[Settings.INITIAL_WINDOW_SIZE] = 20000
settings1[Settings.MAX_FRAME_SIZE] = 30000
peer.sendFrame().settings(settings1)
peer.acceptFrame() // ACK
peer.sendFrame().ping(false, 2, 0)
peer.acceptFrame()
peer.play()
// Play it back.
val connection = connect(peer)
// fake a settings frame with clear flag set.
val settings2 = Settings()
settings2[Settings.MAX_CONCURRENT_STREAMS] = 60000
connection.readerRunnable.applyAndAckSettings(true, settings2)
synchronized(connection) {
assertThat(connection.peerSettings.headerTableSize).isEqualTo(-1)
assertThat(connection.peerSettings.initialWindowSize)
.isEqualTo(Settings.DEFAULT_INITIAL_WINDOW_SIZE.toLong())
assertThat(connection.peerSettings.getMaxFrameSize(-1)).isEqualTo(-1)
assertThat(connection.peerSettings.getMaxConcurrentStreams()).isEqualTo(60000)
}
}
@Test fun bogusDataFrameDoesNotDisruptConnection() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.sendFrame().data(true, 41, Buffer().writeUtf8("bogus"), 5)
peer.acceptFrame() // RST_STREAM
peer.sendFrame().ping(false, 2, 0)
peer.acceptFrame() // PING
peer.play()
// Play it back.
connect(peer)
// Verify the peer received what was expected.
val rstStream = peer.takeFrame()
assertThat(rstStream.type).isEqualTo(Http2.TYPE_RST_STREAM)
assertThat(rstStream.streamId).isEqualTo(41)
assertThat(rstStream.errorCode).isEqualTo(ErrorCode.PROTOCOL_ERROR)
val ping = peer.takeFrame()
assertThat(ping.payload1).isEqualTo(2)
}
@Test fun bogusReplySilentlyIgnored() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.sendFrame().headers(false, 41, headerEntries("a", "android"))
peer.sendFrame().ping(false, 2, 0)
peer.acceptFrame() // PING
peer.play()
// Play it back.
connect(peer)
// Verify the peer received what was expected.
val ping = peer.takeFrame()
assertThat(ping.payload1).isEqualTo(2)
}
@Test fun serverClosesClientOutputStream() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().rstStream(3, ErrorCode.CANCEL)
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0)
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("a", "android"), true)
val out = stream.getSink().buffer()
connection.writePingAndAwaitPong() // Ensure that the RST_CANCEL has been received.
try {
out.writeUtf8("square")
out.flush()
fail()
} catch (expected: IOException) {
assertThat(expected.message).isEqualTo("stream was reset: CANCEL")
}
try {
out.close()
fail()
} catch (expected: IOException) {
// Close throws because buffered data wasn't flushed.
}
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(synStream.inFinished).isFalse
assertThat(synStream.outFinished).isFalse
val ping = peer.takeFrame()
assertThat(ping.type).isEqualTo(Http2.TYPE_PING)
}
/**
* Test that the client sends a RST_STREAM if doing so won't disrupt the output stream.
*/
@Test fun clientClosesClientInputStream() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // RST_STREAM
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("a", "android"), false)
val source = stream.getSource()
val out = stream.getSink().buffer()
source.close()
try {
source.read(Buffer(), 1)
fail()
} catch (expected: IOException) {
assertThat(expected.message).isEqualTo("stream closed")
}
try {
out.writeUtf8("a")
out.flush()
fail()
} catch (expected: IOException) {
assertThat(expected.message).isEqualTo("stream finished")
}
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(synStream.inFinished).isTrue
assertThat(synStream.outFinished).isFalse
val rstStream = peer.takeFrame()
assertThat(rstStream.type).isEqualTo(Http2.TYPE_RST_STREAM)
assertThat(rstStream.errorCode).isEqualTo(ErrorCode.CANCEL)
}
/**
* Test that the client doesn't send a RST_STREAM if doing so will disrupt the output stream.
*/
@Test fun clientClosesClientInputStreamIfOutputStreamIsClosed() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // DATA
peer.acceptFrame() // DATA with FLAG_FIN
peer.acceptFrame() // RST_STREAM
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("a", "android"), true)
val source = stream.getSource()
val out = stream.getSink().buffer()
source.close()
try {
source.read(Buffer(), 1)
fail()
} catch (expected: IOException) {
assertThat(expected.message).isEqualTo("stream closed")
}
out.writeUtf8("square")
out.flush()
out.close()
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(synStream.inFinished).isFalse
assertThat(synStream.outFinished).isFalse
val data = peer.takeFrame()
assertThat(data.type).isEqualTo(Http2.TYPE_DATA)
assertArrayEquals("square".toByteArray(), data.data)
val fin = peer.takeFrame()
assertThat(fin.type).isEqualTo(Http2.TYPE_DATA)
assertThat(fin.inFinished).isTrue
assertThat(fin.outFinished).isFalse
val rstStream = peer.takeFrame()
assertThat(rstStream.type).isEqualTo(Http2.TYPE_RST_STREAM)
assertThat(rstStream.errorCode).isEqualTo(ErrorCode.CANCEL)
}
@Test fun serverClosesClientInputStream() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("b", "banana"))
peer.sendFrame().data(true, 3, Buffer().writeUtf8("square"), 6)
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0)
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("a", "android"), false)
val source = stream.getSource()
assertStreamData("square", source)
connection.writePingAndAwaitPong() // Ensure that inFinished has been received.
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(synStream.inFinished).isTrue
assertThat(synStream.outFinished).isFalse
}
@Test fun remoteDoubleSynReply() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.acceptFrame() // PING
peer.sendFrame().headers(false, 3, headerEntries("b", "banana"))
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0)
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("c", "cola"), false)
assertThat(stream.takeHeaders()).isEqualTo(headersOf("a", "android"))
connection.writePingAndAwaitPong() // Ensure that the 2nd SYN REPLY has been received.
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
val ping = peer.takeFrame()
assertThat(ping.type).isEqualTo(Http2.TYPE_PING)
}
@Test fun remoteSendsDataAfterInFinished() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.sendFrame().data(true, 3, Buffer().writeUtf8("robot"), 5)
peer.sendFrame().data(true, 3, Buffer().writeUtf8("c3po"), 4)
peer.acceptFrame() // RST_STREAM
peer.sendFrame().ping(false, 2, 0) // Ping just to make sure the stream was fastforwarded.
peer.acceptFrame() // PING
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("b", "banana"), false)
assertThat(stream.takeHeaders()).isEqualTo(headersOf("a", "android"))
assertStreamData("robot", stream.getSource())
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
val rstStream = peer.takeFrame()
assertThat(rstStream.type).isEqualTo(Http2.TYPE_RST_STREAM)
assertThat(rstStream.streamId).isEqualTo(3)
val ping = peer.takeFrame()
assertThat(ping.type).isEqualTo(Http2.TYPE_PING)
assertThat(ping.payload1).isEqualTo(2)
}
@Test fun clientDoesNotLimitFlowControl() {
val dataLength = 16384
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("b", "banana"))
peer.sendFrame().data(false, 3, Buffer().write(ByteArray(dataLength)), dataLength)
peer.sendFrame().data(false, 3, Buffer().write(ByteArray(dataLength)), dataLength)
peer.sendFrame().data(false, 3, Buffer().write(ByteArray(dataLength)), dataLength)
peer.sendFrame().data(false, 3, Buffer().write(ByteArray(dataLength)), dataLength)
peer.sendFrame().data(false, 3, Buffer().write(ByteArray(1)), 1)
peer.sendFrame().ping(false, 2, 0) // Ping just to make sure the stream was fastforwarded.
peer.acceptFrame() // PING
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("a", "android"), false)
assertThat(stream.takeHeaders()).isEqualTo(headersOf("b", "banana"))
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
val ping = peer.takeFrame()
assertThat(ping.type).isEqualTo(Http2.TYPE_PING)
assertThat(ping.payload1).isEqualTo(2)
}
@Test fun remoteSendsRefusedStreamBeforeReplyHeaders() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().rstStream(3, ErrorCode.REFUSED_STREAM)
peer.sendFrame().ping(false, 2, 0)
peer.acceptFrame() // PING
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("a", "android"), false)
try {
stream.takeHeaders()
fail()
} catch (expected: IOException) {
assertThat(expected.message).isEqualTo("stream was reset: REFUSED_STREAM")
}
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
val ping = peer.takeFrame()
assertThat(ping.type).isEqualTo(Http2.TYPE_PING)
assertThat(ping.payload1).isEqualTo(2)
}
@Test fun receiveGoAway() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM 1
peer.acceptFrame() // SYN_STREAM 3
peer.acceptFrame() // PING.
peer.sendFrame().goAway(3, ErrorCode.PROTOCOL_ERROR, EMPTY_BYTE_ARRAY)
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0)
peer.acceptFrame() // DATA STREAM 1
peer.play()
// Play it back.
val connection = connect(peer)
val stream1 = connection.newStream(headerEntries("a", "android"), true)
val stream2 = connection.newStream(headerEntries("b", "banana"), true)
connection.writePingAndAwaitPong() // Ensure the GO_AWAY that resets stream2 has been received.
val sink1 = stream1.getSink().buffer()
val sink2 = stream2.getSink().buffer()
sink1.writeUtf8("abc")
try {
sink2.writeUtf8("abc")
sink2.flush()
fail()
} catch (expected: IOException) {
assertThat(expected.message).isEqualTo("stream was reset: REFUSED_STREAM")
}
sink1.writeUtf8("def")
sink1.close()
try {
connection.newStream(headerEntries("c", "cola"), false)
fail()
} catch (expected: ConnectionShutdownException) {
}
assertThat(stream1.isOpen).isTrue
assertThat(stream2.isOpen).isFalse
assertThat(connection.openStreamCount()).isEqualTo(1)
// Verify the peer received what was expected.
val synStream1 = peer.takeFrame()
assertThat(synStream1.type).isEqualTo(Http2.TYPE_HEADERS)
val synStream2 = peer.takeFrame()
assertThat(synStream2.type).isEqualTo(Http2.TYPE_HEADERS)
val ping = peer.takeFrame()
assertThat(ping.type).isEqualTo(Http2.TYPE_PING)
val data1 = peer.takeFrame()
assertThat(data1.type).isEqualTo(Http2.TYPE_DATA)
assertThat(data1.streamId).isEqualTo(3)
assertArrayEquals("abcdef".toByteArray(), data1.data)
}
@Test fun sendGoAway() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM 1
peer.acceptFrame() // GOAWAY
peer.acceptFrame() // PING
peer.sendFrame().headers(false, 2, headerEntries("b", "b")) // Should be ignored!
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0)
peer.play()
// Play it back.
val connection = connect(peer)
connection.newStream(headerEntries("a", "android"), false)
synchronized(connection) {
if (!connection.isHealthy(System.nanoTime())) {
throw ConnectionShutdownException()
}
}
connection.writePing()
connection.shutdown(ErrorCode.PROTOCOL_ERROR)
assertThat(connection.openStreamCount()).isEqualTo(1)
connection.awaitPong() // Prevent the peer from exiting prematurely.
// Verify the peer received what was expected.
val synStream1 = peer.takeFrame()
assertThat(synStream1.type).isEqualTo(Http2.TYPE_HEADERS)
val pingFrame = peer.takeFrame()
assertThat(pingFrame.type).isEqualTo(Http2.TYPE_PING)
val goaway = peer.takeFrame()
assertThat(goaway.type).isEqualTo(Http2.TYPE_GOAWAY)
assertThat(goaway.streamId).isEqualTo(0)
assertThat(goaway.errorCode).isEqualTo(ErrorCode.PROTOCOL_ERROR)
}
@Test fun close() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // GOAWAY
peer.acceptFrame() // RST_STREAM
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("a", "android"), false)
assertThat(connection.openStreamCount()).isEqualTo(1)
connection.close()
assertThat(connection.openStreamCount()).isEqualTo(0)
try {
connection.newStream(headerEntries("b", "banana"), false)
fail()
} catch (expected: ConnectionShutdownException) {
}
val sink = stream.getSink().buffer()
try {
sink.writeByte(0)
sink.flush()
fail()
} catch (expected: IOException) {
assertThat(expected.message).isEqualTo("stream finished")
}
try {
stream.getSource().read(Buffer(), 1)
fail()
} catch (expected: IOException) {
assertThat(expected.message).isEqualTo("stream was reset: CANCEL")
}
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
val goaway = peer.takeFrame()
assertThat(goaway.type).isEqualTo(Http2.TYPE_GOAWAY)
val rstStream = peer.takeFrame()
assertThat(rstStream.type).isEqualTo(Http2.TYPE_RST_STREAM)
assertThat(rstStream.streamId).isEqualTo(3)
}
@Test fun getResponseHeadersTimesOut() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // RST_STREAM
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("b", "banana"), false)
stream.readTimeout().timeout(500, TimeUnit.MILLISECONDS)
val startNanos = System.nanoTime()
try {
stream.takeHeaders()
fail()
} catch (expected: InterruptedIOException) {
}
val elapsedNanos = System.nanoTime() - startNanos
awaitWatchdogIdle()
/* 200ms delta */
assertThat(TimeUnit.NANOSECONDS.toMillis(elapsedNanos).toDouble())
.isCloseTo(500.0, Offset.offset(200.0))
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_RST_STREAM)
}
/**
* Confirm that the client times out if the server stalls after 3 bytes. After the timeout the
* connection is still considered healthy while we await the degraded pong. When that doesn't
* arrive the connection goes unhealthy.
*/
@Test fun readTimesOut() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.sendFrame().data(false, 3, Buffer().writeUtf8("abc"), 3)
peer.acceptFrame() // RST_STREAM
peer.acceptFrame() // DEGRADED PING
peer.acceptFrame() // AWAIT PING
peer.sendFrame().ping(true, Http2Connection.DEGRADED_PING, 1) // DEGRADED PONG
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0) // AWAIT PONG
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("b", "banana"), false)
stream.readTimeout().timeout(500, TimeUnit.MILLISECONDS)
val source = stream.getSource().buffer()
source.require(3)
val startNanos = System.nanoTime()
try {
source.require(4)
fail()
} catch (expected: InterruptedIOException) {
}
val elapsedNanos = System.nanoTime() - startNanos
awaitWatchdogIdle()
/* 200ms delta */assertThat(TimeUnit.NANOSECONDS.toMillis(elapsedNanos).toDouble())
.isCloseTo(500.0, Offset.offset(200.0))
assertThat(connection.openStreamCount()).isEqualTo(0)
// When the timeout is sent the connection doesn't immediately go unhealthy.
assertThat(connection.isHealthy(System.nanoTime())).isTrue
// But if the ping doesn't arrive, the connection goes unhealthy.
Thread.sleep(TimeUnit.NANOSECONDS.toMillis(Http2Connection.DEGRADED_PONG_TIMEOUT_NS.toLong()))
assertThat(connection.isHealthy(System.nanoTime())).isFalse
// When a pong does arrive, the connection becomes healthy again.
connection.writePingAndAwaitPong()
assertThat(connection.isHealthy(System.nanoTime())).isTrue
// Verify the peer received what was expected.
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_RST_STREAM)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_PING)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_PING)
}
@Test fun writeTimesOutAwaitingStreamWindow() {
// Set the peer's receive window to 5 bytes!
val peerSettings = Settings().set(Settings.INITIAL_WINDOW_SIZE, 5)
// Write the mocking script.
peer.sendFrame().settings(peerSettings)
peer.acceptFrame() // ACK SETTINGS
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0)
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.acceptFrame() // DATA
peer.acceptFrame() // RST_STREAM
peer.play()
// Play it back.
val connection = connect(peer)
connection.writePingAndAwaitPong() // Make sure settings have been received.
val stream = connection.newStream(headerEntries("b", "banana"), true)
val sink = stream.getSink()
sink.write(Buffer().writeUtf8("abcde"), 5)
stream.writeTimeout().timeout(500, TimeUnit.MILLISECONDS)
val startNanos = System.nanoTime()
sink.write(Buffer().writeUtf8("f"), 1)
try {
sink.flush() // This will time out waiting on the write window.
fail()
} catch (expected: InterruptedIOException) {
}
val elapsedNanos = System.nanoTime() - startNanos
awaitWatchdogIdle()
/* 200ms delta */assertThat(TimeUnit.NANOSECONDS.toMillis(elapsedNanos).toDouble())
.isCloseTo(500.0, Offset.offset(200.0))
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_PING)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_DATA)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_RST_STREAM)
}
@Test fun writeTimesOutAwaitingConnectionWindow() {
// Set the peer's receive window to 5 bytes. Give the stream 5 bytes back, so only the
// connection-level window is applicable.
val peerSettings = Settings().set(Settings.INITIAL_WINDOW_SIZE, 5)
// Write the mocking script.
peer.sendFrame().settings(peerSettings)
peer.acceptFrame() // ACK SETTINGS
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0)
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.acceptFrame() // PING
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0)
peer.acceptFrame() // DATA
peer.acceptFrame() // RST_STREAM
peer.play()
// Play it back.
val connection = connect(peer)
connection.writePingAndAwaitPong() // Make sure settings have been acked.
val stream = connection.newStream(headerEntries("b", "banana"), true)
connection.writePingAndAwaitPong() // Make sure the window update has been received.
val sink = stream.getSink()
stream.writeTimeout().timeout(500, TimeUnit.MILLISECONDS)
sink.write(Buffer().writeUtf8("abcdef"), 6)
val startNanos = System.nanoTime()
try {
sink.flush() // This will time out waiting on the write window.
fail()
} catch (expected: InterruptedIOException) {
}
val elapsedNanos = System.nanoTime() - startNanos
awaitWatchdogIdle()
/* 200ms delta */assertThat(TimeUnit.NANOSECONDS.toMillis(elapsedNanos).toDouble())
.isCloseTo(500.0, Offset.offset(200.0))
assertThat(connection.openStreamCount()).isEqualTo(0)
// Verify the peer received what was expected.
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_PING)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_PING)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_DATA)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_RST_STREAM)
}
@Test fun outgoingWritesAreBatched() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.acceptFrame() // DATA
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("b", "banana"), true)
// two outgoing writes
val sink = stream.getSink()
sink.write(Buffer().writeUtf8("abcde"), 5)
sink.write(Buffer().writeUtf8("fghij"), 5)
sink.close()
// verify the peer received one incoming frame
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_HEADERS)
val data = peer.takeFrame()
assertThat(data.type).isEqualTo(Http2.TYPE_DATA)
assertArrayEquals("abcdefghij".toByteArray(), data.data)
assertThat(data.inFinished).isTrue
}
@Test fun headers() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // PING
peer.sendFrame()
.headers(false, 3, headerEntries(Header.RESPONSE_STATUS_UTF8, "HTTP/1.1 100"))
peer.sendFrame()
.headers(false, 3, headerEntries(Header.RESPONSE_STATUS_UTF8, "HTTP/1.1 200"))
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0)
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("b", "banana"), true)
connection.writePingAndAwaitPong() // Ensure that the HEADERS has been received.
assertThat(stream.takeHeaders())
.isEqualTo(headersOf(Header.RESPONSE_STATUS_UTF8, "HTTP/1.1 100"))
assertThat(stream.takeHeaders())
.isEqualTo(headersOf(Header.RESPONSE_STATUS_UTF8, "HTTP/1.1 200"))
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
val ping = peer.takeFrame()
assertThat(ping.type).isEqualTo(Http2.TYPE_PING)
}
@Test fun readMultipleSetsOfResponseHeaders() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.acceptFrame() // PING
peer.sendFrame().headers(true, 3, headerEntries("c", "cola"))
peer.sendFrame().ping(true, Http2Connection.AWAIT_PING, 0) // PONG
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("b", "banana"), true)
stream.connection.flush()
assertThat(stream.takeHeaders()).isEqualTo(headersOf("a", "android"))
connection.writePingAndAwaitPong()
assertThat(stream.trailers()).isEqualTo(headersOf("c", "cola"))
// Verify the peer received what was expected.
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_PING)
}
@Test fun readSendsWindowUpdate() {
val windowSize = 100
val windowUpdateThreshold = 50
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
for (i in 0..2) {
// Send frames of summing to size 50, which is windowUpdateThreshold.
peer.sendFrame().data(false, 3, data(24), 24)
peer.sendFrame().data(false, 3, data(25), 25)
peer.sendFrame().data(false, 3, data(1), 1)
peer.acceptFrame() // connection WINDOW UPDATE
peer.acceptFrame() // stream WINDOW UPDATE
}
peer.sendFrame().data(true, 3, data(0), 0)
peer.play()
// Play it back.
val connection = connect(peer)
connection.okHttpSettings[Settings.INITIAL_WINDOW_SIZE] = windowSize
val stream = connection.newStream(headerEntries("b", "banana"), false)
assertThat(stream.readBytesAcknowledged).isEqualTo(0L)
assertThat(stream.readBytesTotal).isEqualTo(0L)
assertThat(stream.takeHeaders()).isEqualTo(headersOf("a", "android"))
val source = stream.getSource()
val buffer = Buffer()
buffer.writeAll(source)
assertThat(source.read(buffer, 1)).isEqualTo(-1)
assertThat(buffer.size).isEqualTo(150)
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
for (i in 0..2) {
val windowUpdateStreamIds: MutableList = ArrayList(2)
for (j in 0..1) {
val windowUpdate = peer.takeFrame()
assertThat(windowUpdate.type).isEqualTo(Http2.TYPE_WINDOW_UPDATE)
windowUpdateStreamIds.add(windowUpdate.streamId)
assertThat(windowUpdate.windowSizeIncrement)
.isEqualTo(windowUpdateThreshold.toLong())
}
// connection
assertThat(windowUpdateStreamIds).contains(0)
// stream
assertThat(windowUpdateStreamIds).contains(3)
}
}
@Test fun serverSendsEmptyDataClientDoesntSendWindowUpdate() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.sendFrame().data(true, 3, data(0), 0)
peer.play()
// Play it back.
val connection = connect(peer)
val client = connection.newStream(headerEntries("b", "banana"), false)
assertThat(client.getSource().read(Buffer(), 1)).isEqualTo(-1)
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(peer.frameCount()).isEqualTo(5)
}
@Test fun clientSendsEmptyDataServerDoesntSendWindowUpdate() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.acceptFrame() // DATA
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.play()
// Play it back.
val connection = connect(peer)
val client = connection.newStream(headerEntries("b", "banana"), true)
val out = client.getSink().buffer()
out.write(EMPTY_BYTE_ARRAY)
out.flush()
out.close()
// Verify the peer received what was expected.
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_HEADERS)
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_DATA)
assertThat(peer.frameCount()).isEqualTo(5)
}
@Test fun testTruncatedDataFrame() {
// Write the mocking script.
peer.sendFrame().settings(Settings())
peer.acceptFrame() // ACK
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.sendFrame().data(false, 3, data(1024), 1024)
peer.truncateLastFrame(8 + 100)
peer.play()
// Play it back.
val connection = connect(peer)
val stream = connection.newStream(headerEntries("b", "banana"), false)
assertThat(stream.takeHeaders()).isEqualTo(headersOf("a", "android"))
val source = stream.getSource()
try {
source.buffer().readByteString(101)
fail()
} catch (expected: EOFException) {
}
}
@Test fun blockedStreamDoesntStarveNewStream() {
val framesThatFillWindow =
roundUp(Settings.DEFAULT_INITIAL_WINDOW_SIZE, peer.maxOutboundDataLength())
// Write the mocking script. This accepts more data frames than necessary!
peer.sendFrame().settings(Settings())
peer.acceptFrame() // SETTINGS ACK
peer.acceptFrame() // SYN_STREAM on stream 1
for (i in 0 until framesThatFillWindow) {
peer.acceptFrame() // DATA on stream 1
}
peer.acceptFrame() // SYN_STREAM on stream 2
peer.acceptFrame() // DATA on stream 2
peer.play()
// Play it back.
val connection = connect(peer)
val stream1 = connection.newStream(headerEntries("a", "apple"), true)
val out1 = stream1.getSink().buffer()
out1.write(ByteArray(Settings.DEFAULT_INITIAL_WINDOW_SIZE))
out1.flush()
// Check that we've filled the window for both the stream and also the connection.
assertThat(connection.writeBytesTotal)
.isEqualTo(Settings.DEFAULT_INITIAL_WINDOW_SIZE.toLong())
assertThat(connection.writeBytesMaximum)
.isEqualTo(Settings.DEFAULT_INITIAL_WINDOW_SIZE.toLong())
assertThat(stream1.writeBytesTotal)
.isEqualTo(Settings.DEFAULT_INITIAL_WINDOW_SIZE.toLong())
assertThat(stream1.writeBytesMaximum)
.isEqualTo(Settings.DEFAULT_INITIAL_WINDOW_SIZE.toLong())
// receiving a window update on the connection will unblock new streams.
connection.readerRunnable.windowUpdate(0, 3)
assertThat(connection.writeBytesTotal)
.isEqualTo(Settings.DEFAULT_INITIAL_WINDOW_SIZE.toLong())
assertThat(connection.writeBytesMaximum)
.isEqualTo((Settings.DEFAULT_INITIAL_WINDOW_SIZE + 3).toLong())
assertThat(stream1.writeBytesTotal)
.isEqualTo(Settings.DEFAULT_INITIAL_WINDOW_SIZE.toLong())
assertThat(stream1.writeBytesMaximum)
.isEqualTo(Settings.DEFAULT_INITIAL_WINDOW_SIZE.toLong())
// Another stream should be able to send data even though 1 is blocked.
val stream2 = connection.newStream(headerEntries("b", "banana"), true)
val out2 = stream2.getSink().buffer()
out2.writeUtf8("foo")
out2.flush()
assertThat(connection.writeBytesTotal)
.isEqualTo((Settings.DEFAULT_INITIAL_WINDOW_SIZE + 3).toLong())
assertThat(connection.writeBytesMaximum)
.isEqualTo((Settings.DEFAULT_INITIAL_WINDOW_SIZE + 3).toLong())
assertThat(stream1.writeBytesTotal)
.isEqualTo(Settings.DEFAULT_INITIAL_WINDOW_SIZE.toLong())
assertThat(stream1.writeBytesMaximum)
.isEqualTo(Settings.DEFAULT_INITIAL_WINDOW_SIZE.toLong())
assertThat(stream2.writeBytesTotal).isEqualTo(3L)
assertThat(stream2.writeBytesMaximum)
.isEqualTo(Settings.DEFAULT_INITIAL_WINDOW_SIZE.toLong())
}
@Test fun remoteOmitsInitialSettings() {
// Write the mocking script. Note no SETTINGS frame is sent or acknowledged.
peer.acceptFrame() // SYN_STREAM
peer.sendFrame().headers(false, 3, headerEntries("a", "android"))
peer.acceptFrame() // GOAWAY
peer.play()
val connection = Http2Connection.Builder(true, TaskRunner.INSTANCE)
.socket(peer.openSocket())
.build()
connection.start( /* sendConnectionPreface = */false)
val stream = connection.newStream(headerEntries("b", "banana"), false)
try {
stream.takeHeaders()
fail()
} catch (expected: IOException) {
assertThat(expected.message).isEqualTo("Expected a SETTINGS frame but was HEADERS")
}
// Verify the peer received what was expected.
val synStream = peer.takeFrame()
assertThat(synStream.type).isEqualTo(Http2.TYPE_HEADERS)
val goaway = peer.takeFrame()
assertThat(goaway.type).isEqualTo(Http2.TYPE_GOAWAY)
assertThat(goaway.errorCode).isEqualTo(ErrorCode.PROTOCOL_ERROR)
}
@Test fun connectionUsesTaskRunner() {
peer.acceptFrame() // SYN_STREAM.
peer.play()
val taskRunner = taskFaker.taskRunner
val socket = peer.openSocket()
val connection = Http2Connection.Builder(true, taskRunner)
.socket(socket)
.pushObserver(IGNORE)
.build()
connection.start(sendConnectionPreface = false)
val queues = taskRunner.activeQueues()
assertThat(queues).hasSize(1)
}
private fun data(byteCount: Int): Buffer = Buffer().write(ByteArray(byteCount))
private fun assertStreamData(expected: String?, source: Source?) {
val actual = source!!.buffer().readUtf8()
assertThat(actual).isEqualTo(expected)
}
/**
* Returns true when all work currently in progress by the watchdog have completed. This method
* creates more work for the watchdog and waits for that work to be executed. When it is, we know
* work that preceded this call is complete.
*/
private fun awaitWatchdogIdle() {
val latch = CountDownLatch(1)
val watchdogJob: AsyncTimeout = object : AsyncTimeout() {
override fun timedOut() {
latch.countDown()
}
}
watchdogJob.deadlineNanoTime(System.nanoTime()) // Due immediately!
watchdogJob.enter()
latch.await()
}
private fun connectWithSettings(
client: Boolean,
settings: Settings?
): Http2Connection {
peer.setClient(client)
peer.sendFrame().settings(settings!!)
peer.acceptFrame() // ACK
peer.play()
return connect(peer)
}
/** Builds a new connection to `peer` with settings acked. */
private fun connect(
peer: MockHttp2Peer,
pushObserver: PushObserver = IGNORE,
listener: Http2Connection.Listener = Http2Connection.Listener.REFUSE_INCOMING_STREAMS
): Http2Connection {
val connection = Http2Connection.Builder(true, TaskRunner.INSTANCE)
.socket(peer.openSocket())
.pushObserver(pushObserver)
.listener(listener)
.build()
connection.start(sendConnectionPreface = false)
// verify the peer received the ACK
val ackFrame = peer.takeFrame()
assertThat(ackFrame.type).isEqualTo(Http2.TYPE_SETTINGS)
assertThat(ackFrame.streamId).isEqualTo(0)
assertThat(ackFrame.ack).isTrue
return connection
}
private class RecordingPushObserver : PushObserver {
val events = mutableListOf()
@Synchronized fun takeEvent(): Any {
while (events.isEmpty()) {
wait()
}
return events.removeAt(0)
}
@Synchronized override fun onRequest(streamId: Int, requestHeaders: List): Boolean {
assertThat(streamId).isEqualTo(2)
events.add(requestHeaders)
notifyAll()
return false
}
@Synchronized override fun onHeaders(
streamId: Int, responseHeaders: List, last: Boolean
): Boolean {
assertThat(streamId).isEqualTo(2)
assertThat(last).isTrue
events.add(responseHeaders)
notifyAll()
return false
}
@Synchronized override fun onData(
streamId: Int, source: BufferedSource, byteCount: Int, last: Boolean
): Boolean {
events.add(AssertionError("onData"))
notifyAll()
return false
}
@Synchronized override fun onReset(streamId: Int, errorCode: ErrorCode) {
events.add(AssertionError("onReset"))
notifyAll()
}
}
companion object {
fun roundUp(num: Int, divisor: Int): Int = (num + divisor - 1) / divisor
val IGNORE = object : PushObserver {
override fun onRequest(streamId: Int, requestHeaders: List) = false
override fun onHeaders(
streamId: Int,
responseHeaders: List,
last: Boolean
) = false
override fun onData(
streamId: Int, source: BufferedSource, byteCount: Int, last: Boolean
): Boolean {
source.skip(byteCount.toLong())
return false
}
override fun onReset(streamId: Int, errorCode: ErrorCode) {}
}
}
}