Skip to content

Commit 640b6b7

Browse files
Fix - Http3FrameCodec decode fail during unknown settings (#15998)
**Motivation:** The change in [https://github.com/netty/netty/pull/15909](https://github.com/netty/netty/pull/15909) is merged but not yet released. While working on HTTP/3 connections I noticed a logical issue: when an unknown settings key is received, the current implementation returns a value, which causes Http3FrameCodec to treat it as an error (because settings.put(key, value) returns the previous value for known keys and null for new ones). For unknown keys we should ignore them, so we now always return null to achieve the expected behavior. **Modification:** * [x] settings.put(key, value) now returns null for unknown keys (non null triggers an error in Http3FrameCodec decoding). * [x] Added unit tests to verify this. > Note: Http3CodecTest didn’t catch the issue previously because it used frames produced by the same put logic, so both encoding and decoding behaved consistently and the issue was hidden. --------- Co-authored-by: Norman Maurer <norman_maurer@apple.com>
1 parent 35f6ad1 commit 640b6b7

3 files changed

Lines changed: 45 additions & 7 deletions

File tree

codec-http3/src/main/java/io/netty/handler/codec/http3/Http3SettingIdentifier.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,7 @@ public enum Http3SettingIdentifier {
9191
private final long id;
9292

9393
private static final Map<Long, Http3SettingIdentifier> LOOKUP = Collections.unmodifiableMap(
94-
Arrays.stream(values())
95-
.collect(Collectors.toMap(
96-
Http3SettingIdentifier::id,
97-
Function.identity()
98-
))
94+
Arrays.stream(values()).collect(Collectors.toMap(Http3SettingIdentifier::id, Function.identity()))
9995
);
10096

10197
Http3SettingIdentifier(long id) {

codec-http3/src/main/java/io/netty/handler/codec/http3/Http3Settings.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public final class Http3Settings implements Iterable<Map.Entry<Long, Long>> {
5454
* Creates a new instance
5555
*/
5656
public Http3Settings() {
57-
this.settings = new LongObjectHashMap<>(4);
57+
this.settings = new LongObjectHashMap<>(Http3SettingIdentifier.values().length);
5858
}
5959

6060
/**
@@ -99,7 +99,7 @@ public Long put(long key, Long value) {
9999

100100
// When Non-Standard/Unknown settings identifier identifier present - Ignore
101101
if (identifier == null) {
102-
return value;
102+
return null;
103103
}
104104

105105
//Validation

codec-http3/src/test/java/io/netty/handler/codec/http3/Http3SettingsTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,4 +264,46 @@ void testCopyFromNullThrows() {
264264
Http3Settings settings = new Http3Settings();
265265
assertThrows(NullPointerException.class, () -> settings.putAll(null));
266266
}
267+
268+
@Test
269+
void duplicateSettingsValuesInsideFrameTest() {
270+
Http3SettingsFrame settingsFrame = new DefaultHttp3SettingsFrame();
271+
assertNull(settingsFrame.put(Http3SettingIdentifier.HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY.id(), 100L));
272+
assertNull(settingsFrame.put(Http3SettingIdentifier.HTTP3_SETTINGS_QPACK_BLOCKED_STREAMS.id(), 1L));
273+
assertNull(settingsFrame.put(Http3SettingIdentifier.HTTP3_SETTINGS_MAX_FIELD_SECTION_SIZE.id(), 128L));
274+
assertNull(settingsFrame.put(Http3SettingIdentifier.HTTP3_SETTINGS_ENABLE_CONNECT_PROTOCOL.id(), 0L));
275+
assertNull(settingsFrame.put(Http3SettingIdentifier.HTTP3_SETTINGS_H3_DATAGRAM.id(), 1L));
276+
//known headers should not contain duplicate so give non-null
277+
// which is used in http3framecodec to throw error
278+
assertNotNull(settingsFrame.put(Http3SettingIdentifier.HTTP3_SETTINGS_H3_DATAGRAM.id(), 1L));
279+
// Ensure we can encode and decode all sizes correctly.
280+
// unknown settings id/key will be ignored
281+
assertNull(settingsFrame.put(63, 63L));
282+
assertNull(settingsFrame.put(16383, 16383L));
283+
assertNull(settingsFrame.put(1073741823, 1073741823L));
284+
assertNull(settingsFrame.put(4611686018427387903L, 4611686018427387903L));
285+
//even duplicates of unknown ignored as we ignore unknown
286+
assertNull(settingsFrame.put(4611686018427387903L, 4611686018427387903L));
287+
}
288+
289+
@Test
290+
void duplicateSettingsValuesInsideHttp3SettingsTest() {
291+
Http3Settings http3Settings = new Http3Settings();
292+
assertNull(http3Settings.put(Http3SettingIdentifier.HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY.id(), 100L));
293+
assertNull(http3Settings.put(Http3SettingIdentifier.HTTP3_SETTINGS_QPACK_BLOCKED_STREAMS.id(), 1L));
294+
assertNull(http3Settings.put(Http3SettingIdentifier.HTTP3_SETTINGS_MAX_FIELD_SECTION_SIZE.id(), 128L));
295+
assertNull(http3Settings.put(Http3SettingIdentifier.HTTP3_SETTINGS_ENABLE_CONNECT_PROTOCOL.id(), 0L));
296+
assertNull(http3Settings.put(Http3SettingIdentifier.HTTP3_SETTINGS_H3_DATAGRAM.id(), 1L));
297+
//known headers should not contain duplicate so give non-null
298+
// which is used in http3framecodec to throw error
299+
assertNotNull(http3Settings.put(Http3SettingIdentifier.HTTP3_SETTINGS_H3_DATAGRAM.id(), 1L));
300+
// Ensure we can encode and decode all sizes correctly.
301+
// unknown settings id/key will be ignored
302+
assertNull(http3Settings.put(63, 63L));
303+
assertNull(http3Settings.put(16383, 16383L));
304+
assertNull(http3Settings.put(1073741823, 1073741823L));
305+
assertNull(http3Settings.put(4611686018427387903L, 4611686018427387903L));
306+
//even duplicates of unknown ignored as we ignore unknown
307+
assertNull(http3Settings.put(4611686018427387903L, 4611686018427387903L));
308+
}
267309
}

0 commit comments

Comments
 (0)