Skip to content

Commit ed92442

Browse files
authored
Allow changing MAX_FRAME_SIZE. (#69)
Motivation: SETTINGS_MAX_FRAME_SIZE is changeable: we should allow that change to occur. Modifications: - Added fields for communicating MAX_FRAME_SIZE changes. - Update the encoders/decoders as those settings change. - Throw errors from the encoders/decoders if violated. Result: Users and remote peers can change MAX_FRAMES_SIZE.
1 parent 7e17e72 commit ed92442

File tree

9 files changed

+88
-4
lines changed

9 files changed

+88
-4
lines changed

Sources/NIOHTTP2/ConnectionStateMachine/HasLocalSettings.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@ extension HasLocalSettings {
6363
// respect that possibility.
6464
effect.streamWindowSizeChange += Int(delta)
6565
case .maxFrameSize:
66-
// TODO(cory): Implement!
67-
break
66+
effect.newMaxFrameSize = newValue
6867
default:
6968
// No operation required
7069
return

Sources/NIOHTTP2/ConnectionStateMachine/HasRemoteSettings.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@ extension HasRemoteSettings {
6363
// respect that possibility.
6464
effect.streamWindowSizeChange += Int(delta)
6565
case .maxFrameSize:
66-
// TODO(cory): Implement!
67-
break
66+
effect.newMaxFrameSize = newValue
6867
default:
6968
// No operation required
7069
return

Sources/NIOHTTP2/HTTP2ChannelHandler.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,10 +464,17 @@ extension NIOHTTP2Handler {
464464
if settingsChange.streamWindowSizeChange != 0 {
465465
self.outboundBuffer.initialWindowSizeChanged(settingsChange.streamWindowSizeChange)
466466
}
467+
if let newMaxFrameSize = settingsChange.newMaxFrameSize {
468+
self.frameEncoder.maxFrameSize = newMaxFrameSize
469+
self.outboundBuffer.maxFrameSize = Int(newMaxFrameSize)
470+
}
467471
case .localSettingsChanged(let settingsChange):
468472
if settingsChange.streamWindowSizeChange != 0 {
469473
self.inboundEventBuffer.pendingUserEvent(NIOHTTP2BulkStreamWindowChangeEvent(delta: settingsChange.streamWindowSizeChange))
470474
}
475+
if let newMaxFrameSize = settingsChange.newMaxFrameSize {
476+
self.frameDecoder.maxFrameSize = newMaxFrameSize
477+
}
471478
}
472479
}
473480

Sources/NIOHTTP2/HTTP2ConnectionStateChange.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ internal enum NIOHTTP2ConnectionStateChange: Hashable {
146146
/// SETTINGS frame.
147147
internal struct RemoteSettingsChanged: Hashable {
148148
internal var streamWindowSizeChange: Int = 0
149+
150+
internal var newMaxFrameSize: UInt32?
149151
}
150152

151153
/// The local peer's settings have changed in a way that is not trivial to decode.
@@ -154,6 +156,8 @@ internal enum NIOHTTP2ConnectionStateChange: Hashable {
154156
/// SETTINGS frame.
155157
internal struct LocalSettingsChanged: Hashable {
156158
internal var streamWindowSizeChange: Int = 0
159+
160+
internal var newMaxFrameSize: UInt32?
157161
}
158162
}
159163

Sources/NIOHTTP2/HTTP2FrameParser.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,9 @@ struct HTTP2FrameDecoder {
228228
private var state: ParserState
229229
private var allocator: ByteBufferAllocator
230230

231+
// RFC 7540 § 6.5.2 puts the initial value of SETTINGS_MAX_FRAME_SIZE at 2**14 octets
232+
internal var maxFrameSize: UInt32 = 1<<14
233+
231234
/// Creates a new HTTP2 frame decoder.
232235
///
233236
/// - parameter allocator: A `ByteBufferAllocator` used when accumulating blocks of data
@@ -319,6 +322,11 @@ struct HTTP2FrameDecoder {
319322
return .needMoreData
320323
}
321324

325+
// Confirm that SETTINGS_MAX_FRAME_SIZE is respected.
326+
guard header.length <= self.maxFrameSize else {
327+
throw InternalError.codecError(code: .frameSizeError)
328+
}
329+
322330
if header.type != 0 {
323331
// Not a DATA frame.
324332
self.state = .accumulatingData(AccumulatingPayloadParserState(fromIdle: state, header: header))
@@ -924,6 +932,9 @@ struct HTTP2FrameEncoder {
924932
private let allocator: ByteBufferAllocator
925933
var headerEncoder: HPACKEncoder
926934

935+
// RFC 7540 § 6.5.2 puts the initial value of SETTINGS_MAX_FRAME_SIZE at 2**14 octets
936+
var maxFrameSize: UInt32 = 1<<14
937+
927938
init(allocator: ByteBufferAllocator) {
928939
self.allocator = allocator
929940
self.headerEncoder = HPACKEncoder(allocator: allocator)
@@ -1125,6 +1136,11 @@ struct HTTP2FrameEncoder {
11251136
extraFrameData = nil
11261137
}
11271138

1139+
// Confirm we're not about to violate SETTINGS_MAX_FRAME_SIZE.
1140+
guard payloadSize <= Int(self.maxFrameSize) else {
1141+
throw InternalError.codecError(code: .frameSizeError)
1142+
}
1143+
11281144
// Write the frame data. This is the payload size and the flags byte.
11291145
buf.writePayloadSize(payloadSize, at: start)
11301146
buf.setInteger(flags.rawValue, at: flagsIndex)

Tests/NIOHTTP2Tests/HTTP2FrameParserTests+XCTest.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ extension HTTP2FrameParserTests {
3838
("testDataFrameDecodingWithOnlyPaddingSplitOverBuffers", testDataFrameDecodingWithOnlyPaddingSplitOverBuffers),
3939
("testDataFrameEncoding", testDataFrameEncoding),
4040
("testDataFrameEncodingMultibyteLength", testDataFrameEncodingMultibyteLength),
41+
("testDataFrameEncodingViolatingMaxFrameSize", testDataFrameEncodingViolatingMaxFrameSize),
4142
("testDataFrameDecodeFailureRootStream", testDataFrameDecodeFailureRootStream),
4243
("testDataFrameDecodeFailureExcessPadding", testDataFrameDecodeFailureExcessPadding),
44+
("testDataFrameDecodingViolatingMaxFrameSize", testDataFrameDecodingViolatingMaxFrameSize),
4345
("testHeadersFrameDecodingNoPriorityNoPadding", testHeadersFrameDecodingNoPriorityNoPadding),
4446
("testHeadersFrameDecodingNoPriorityWithPadding", testHeadersFrameDecodingNoPriorityWithPadding),
4547
("testHeadersFrameDecodingWithPriorityNoPadding", testHeadersFrameDecodingWithPriorityNoPadding),

Tests/NIOHTTP2Tests/HTTP2FrameParserTests.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,22 @@ class HTTP2FrameParserTests: XCTestCase {
460460
XCTAssertEqual(buf, expectedBufContent)
461461
}
462462

463+
464+
func testDataFrameEncodingViolatingMaxFrameSize() throws {
465+
var encoder = HTTP2FrameEncoder(allocator: self.allocator)
466+
XCTAssertEqual(encoder.maxFrameSize, 16384)
467+
468+
var content = self.allocator.buffer(capacity: 16385)
469+
content.writeBytes(repeatElement(UInt8(0), count: 16385))
470+
471+
let frame = HTTP2Frame(streamID: 1, payload: .data(.init(data: .byteBuffer(content))))
472+
var target = self.allocator.buffer(capacity: 1024)
473+
474+
XCTAssertThrowsError(try encoder.encode(frame: frame, to: &target)) { error in
475+
XCTAssertEqual(error as? InternalError, .codecError(code: .frameSizeError))
476+
}
477+
}
478+
463479
func testDataFrameDecodeFailureRootStream() {
464480
let frameBytes: [UInt8] = [
465481
0x00, 0x00, 0x01, // 3-byte payload length (1 byte)
@@ -500,6 +516,23 @@ class HTTP2FrameParserTests: XCTestCase {
500516
}
501517
})
502518
}
519+
520+
func testDataFrameDecodingViolatingMaxFrameSize() throws {
521+
let frameBytes: [UInt8] = [
522+
0x00, 0x40, 0x01, // 3-byte payload length (16385 bytes)
523+
0x00, // 1-byte frame type (DATA)
524+
0x01, // 1-byte flags (END_STREAM)
525+
0x00, 0x00, 0x00, 0x01, // 4-byte stream identifier
526+
]
527+
var badFrameBuf = byteBuffer(withBytes: frameBytes)
528+
var decoder = HTTP2FrameDecoder(allocator: self.allocator, expectClientMagic: false)
529+
XCTAssertEqual(decoder.maxFrameSize, 16384)
530+
531+
decoder.append(bytes: &badFrameBuf)
532+
XCTAssertThrowsError(try decoder.nextFrame()) { error in
533+
XCTAssertEqual(error as? InternalError, .codecError(code: .frameSizeError))
534+
}
535+
}
503536

504537
// MARK: - HEADERS frames
505538

Tests/NIOHTTP2Tests/SimpleClientServerTests+XCTest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ extension SimpleClientServerTests {
5252
("testOpeningWindowsViaSettingsInitialWindowSize", testOpeningWindowsViaSettingsInitialWindowSize),
5353
("testSettingsAckNotifiesAboutChangedFlowControl", testSettingsAckNotifiesAboutChangedFlowControl),
5454
("testStreamMultiplexerAcknowledgesSettingsBasedFlowControlChanges", testStreamMultiplexerAcknowledgesSettingsBasedFlowControlChanges),
55+
("testChangingMaxFrameSize", testChangingMaxFrameSize),
5556
]
5657
}
5758
}

Tests/NIOHTTP2Tests/SimpleClientServerTests.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,4 +1170,27 @@ class SimpleClientServerTests: XCTestCase {
11701170
XCTAssertNoThrow(try self.clientChannel.finish())
11711171
XCTAssertNoThrow(try self.serverChannel.finish())
11721172
}
1173+
1174+
func testChangingMaxFrameSize() throws {
1175+
// Begin by getting the connection up.
1176+
try self.basicHTTP2Connection()
1177+
1178+
// We're now going to try to send a request from the client to the server.
1179+
let headers = HPACKHeaders([(":path", "/"), (":method", "POST"), (":scheme", "https"), (":authority", "localhost")])
1180+
let clientStreamID = HTTP2StreamID(1)
1181+
let reqFrame = HTTP2Frame(streamID: clientStreamID, payload: .headers(.init(headers: headers)))
1182+
try self.assertFramesRoundTrip(frames: [reqFrame], sender: self.clientChannel, receiver: self.serverChannel)
1183+
1184+
// Now the server is going to change SETTINGS_MAX_FRAME_SIZE. We set this twice to confirm we do this properly.
1185+
try self.assertSettingsUpdateWithAck([HTTP2Setting(parameter: .maxFrameSize, value: 1<<15), HTTP2Setting(parameter: .maxFrameSize, value: 1<<16)], sender: self.serverChannel, receiver: self.clientChannel)
1186+
1187+
// Now the client will send a very large DATA frame. It must be passed through as a single entity, and accepted by the remote peer.
1188+
var buffer = self.clientChannel.allocator.buffer(capacity: 65535)
1189+
buffer.writeBytes(repeatElement(UInt8(0xFF), count: 65535))
1190+
let bodyFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.init(data: .byteBuffer(buffer))))
1191+
try self.assertFramesRoundTrip(frames: [bodyFrame], sender: self.clientChannel, receiver: self.serverChannel)
1192+
1193+
XCTAssertNoThrow(try self.clientChannel.finish())
1194+
XCTAssertNoThrow(try self.serverChannel.finish())
1195+
}
11731196
}

0 commit comments

Comments
 (0)