diff --git a/.gitignore b/.gitignore index a9d37c5..14518c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ target Cargo.lock +autobahn/client/ +autobahn/server/ diff --git a/Cargo.toml b/Cargo.toml index c17264c..647b104 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ __rustls-tls = ["rustls", "webpki"] base64 = "0.13.0" byteorder = "1.3.2" bytes = "1.0" +flate2 = "1.0" http = "0.2" httparse = "1.3.4" log = "0.4.8" diff --git a/README.md b/README.md index 318b925..d4460f2 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,6 @@ TLS is supported on all platforms using native-tls or rustls available through t and `rustls-tls` feature flags. By default **no TLS feature is activated**, so make sure you use `native-tls` or `rustls-tls` feature if you need support of the TLS. -There is no support for permessage-deflate at the moment. It's planned. - Testing ------- diff --git a/autobahn/expected-results.json b/autobahn/expected-results.json index 4add9bb..63d2887 100644 --- a/autobahn/expected-results.json +++ b/autobahn/expected-results.json @@ -3,56 +3,56 @@ "1.1.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_1_1_1.json" }, "1.1.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_1_1_2.json" }, "1.1.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_1_1_3.json" }, "1.1.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_1_1_4.json" }, "1.1.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_1_1_5.json" }, "1.1.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 6, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_1_1_6.json" }, "1.1.7": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 3, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_1_1_7.json" }, "1.1.8": { "behavior": "OK", "behaviorClose": "OK", - "duration": 6, + "duration": 11, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_1_1_8.json" }, @@ -66,7 +66,7 @@ "1.2.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_1_2_2.json" }, @@ -94,910 +94,910 @@ "1.2.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 31, + "duration": 5, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_1_2_6.json" }, "1.2.7": { "behavior": "OK", "behaviorClose": "OK", - "duration": 26, + "duration": 5, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_1_2_7.json" }, "1.2.8": { "behavior": "OK", "behaviorClose": "OK", - "duration": 36, + "duration": 12, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_1_2_8.json" }, "10.1.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 5, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_10_1_1.json" }, "12.1.1": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 411, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_1.json" }, "12.1.10": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 3952, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_10.json" }, "12.1.11": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 817, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_11.json" }, "12.1.12": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 818, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_12.json" }, "12.1.13": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1421, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_13.json" }, "12.1.14": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 2126, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_14.json" }, "12.1.15": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 3966, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_15.json" }, "12.1.16": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 3751, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_16.json" }, "12.1.17": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 3738, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_17.json" }, "12.1.18": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 3726, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_18.json" }, "12.1.2": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 516, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_2.json" }, "12.1.3": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 307, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_3.json" }, "12.1.4": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 282, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_4.json" }, "12.1.5": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 450, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_5.json" }, "12.1.6": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 536, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_6.json" }, "12.1.7": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 741, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_7.json" }, "12.1.8": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1328, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_8.json" }, "12.1.9": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 2133, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_1_9.json" }, "12.2.1": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 150, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_1.json" }, "12.2.10": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 14629, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_10.json" }, "12.2.11": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1077, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_11.json" }, "12.2.12": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 1962, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_12.json" }, "12.2.13": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 3922, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_13.json" }, "12.2.14": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 8660, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_14.json" }, "12.2.15": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 16334, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_15.json" }, "12.2.16": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 15329, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_16.json" }, "12.2.17": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 15202, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_17.json" }, "12.2.18": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 15065, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_18.json" }, "12.2.2": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 106, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_2.json" }, "12.2.3": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 168, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_3.json" }, "12.2.4": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 271, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_4.json" }, "12.2.5": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 671, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_5.json" }, "12.2.6": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1044, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_6.json" }, "12.2.7": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 2139, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_7.json" }, "12.2.8": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 3435, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_8.json" }, "12.2.9": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 7263, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_2_9.json" }, "12.3.1": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 101, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_1.json" }, "12.3.10": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 20119, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_10.json" }, "12.3.11": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 1317, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_11.json" }, "12.3.12": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2429, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_12.json" }, "12.3.13": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 4916, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_13.json" }, "12.3.14": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 10318, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_14.json" }, "12.3.15": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 20840, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_15.json" }, "12.3.16": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 20795, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_16.json" }, "12.3.17": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 20491, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_17.json" }, "12.3.18": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 20238, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_18.json" }, "12.3.2": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 122, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_2.json" }, "12.3.3": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 164, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_3.json" }, "12.3.4": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 315, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_4.json" }, "12.3.5": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 717, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_5.json" }, "12.3.6": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 1213, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_6.json" }, "12.3.7": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2361, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_7.json" }, "12.3.8": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 4617, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_8.json" }, "12.3.9": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 9549, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_3_9.json" }, "12.4.1": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 407, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_1.json" }, "12.4.10": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 4848, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_10.json" }, "12.4.11": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 790, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_11.json" }, "12.4.12": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 1050, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_12.json" }, "12.4.13": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 1609, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_13.json" }, "12.4.14": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2860, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_14.json" }, "12.4.15": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 5382, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_15.json" }, "12.4.16": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 5252, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_16.json" }, "12.4.17": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 5096, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_17.json" }, "12.4.18": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 5027, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_18.json" }, "12.4.2": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 469, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_2.json" }, "12.4.3": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 480, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_3.json" }, "12.4.4": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 610, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_4.json" }, "12.4.5": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 625, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_5.json" }, "12.4.6": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 922, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_6.json" }, "12.4.7": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 1037, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_7.json" }, "12.4.8": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1558, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_8.json" }, "12.4.9": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 2636, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_4_9.json" }, "12.5.1": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 145, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_1.json" }, "12.5.10": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 12087, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_10.json" }, "12.5.11": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1830, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_11.json" }, "12.5.12": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 1952, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_12.json" }, "12.5.13": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 4592, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_13.json" }, "12.5.14": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 8835, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_14.json" }, "12.5.15": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 13050, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_15.json" }, "12.5.16": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 11856, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_16.json" }, "12.5.17": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 11422, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_17.json" }, "12.5.18": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 11346, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_18.json" }, "12.5.2": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 171, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_2.json" }, "12.5.3": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 183, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_3.json" }, "12.5.4": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 302, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_4.json" }, "12.5.5": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 605, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_5.json" }, "12.5.6": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 1020, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_6.json" }, "12.5.7": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1466, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_7.json" }, "12.5.8": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 3207, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_8.json" }, "12.5.9": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 5814, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_12_5_9.json" }, "13.1.1": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 205, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_1.json" }, "13.1.10": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 3938, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_10.json" }, "13.1.11": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 536, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_11.json" }, "13.1.12": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 751, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_12.json" }, "13.1.13": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 1562, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_13.json" }, "13.1.14": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 2147, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_14.json" }, "13.1.15": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 3936, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_15.json" }, "13.1.16": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 3996, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_16.json" }, "13.1.17": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 3912, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_17.json" }, "13.1.18": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 3924, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_18.json" }, "13.1.2": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 222, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_2.json" }, "13.1.3": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 247, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_3.json" }, "13.1.4": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 284, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_4.json" }, "13.1.5": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 368, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_5.json" }, "13.1.6": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 490, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_6.json" }, "13.1.7": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 722, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_7.json" }, "13.1.8": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 1164, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_8.json" }, "13.1.9": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2252, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_1_9.json" }, "13.2.1": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 260, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_1.json" }, "13.2.10": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 4048, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_10.json" }, "13.2.11": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 608, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_11.json" }, "13.2.12": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 734, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_12.json" }, "13.2.13": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1301, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_13.json" }, "13.2.14": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2085, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_14.json" }, "13.2.15": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 3887, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_15.json" }, "13.2.16": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 4, + "duration": 3807, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_16.json" }, "13.2.17": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 3766, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_17.json" }, "13.2.18": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 3852, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_18.json" }, "13.2.2": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 245, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_2.json" }, "13.2.3": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 275, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_3.json" }, "13.2.4": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 295, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_4.json" }, "13.2.5": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 373, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_5.json" }, "13.2.6": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 498, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_6.json" }, "13.2.7": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 734, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_7.json" }, "13.2.8": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1106, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_8.json" }, "13.2.9": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 2428, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_2_9.json" }, @@ -1011,7 +1011,7 @@ "13.3.10": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_3_10.json" }, @@ -1032,42 +1032,42 @@ "13.3.13": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_3_13.json" }, "13.3.14": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_3_14.json" }, "13.3.15": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_3_15.json" }, "13.3.16": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_3_16.json" }, "13.3.17": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_3_17.json" }, "13.3.18": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_3_18.json" }, @@ -1123,14 +1123,14 @@ "13.3.9": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_3_9.json" }, "13.4.1": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_4_1.json" }, @@ -1151,7 +1151,7 @@ "13.4.12": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_4_12.json" }, @@ -1165,7 +1165,7 @@ "13.4.14": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_4_14.json" }, @@ -1186,7 +1186,7 @@ "13.4.17": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_4_17.json" }, @@ -1200,21 +1200,21 @@ "13.4.2": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_4_2.json" }, "13.4.3": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_4_3.json" }, "13.4.4": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_4_4.json" }, @@ -1228,21 +1228,21 @@ "13.4.6": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 2, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_4_6.json" }, "13.4.7": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 2, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_4_7.json" }, "13.4.8": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_4_8.json" }, @@ -1263,21 +1263,21 @@ "13.5.10": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_5_10.json" }, "13.5.11": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_5_11.json" }, "13.5.12": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_5_12.json" }, @@ -1298,14 +1298,14 @@ "13.5.15": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 2, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_5_15.json" }, "13.5.16": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 2, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_5_16.json" }, @@ -1333,49 +1333,49 @@ "13.5.3": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_5_3.json" }, "13.5.4": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_5_4.json" }, "13.5.5": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_5_5.json" }, "13.5.6": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_5_6.json" }, "13.5.7": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_5_7.json" }, "13.5.8": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_5_8.json" }, "13.5.9": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_5_9.json" }, @@ -1389,7 +1389,7 @@ "13.6.10": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_10.json" }, @@ -1410,35 +1410,35 @@ "13.6.13": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_13.json" }, "13.6.14": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_14.json" }, "13.6.15": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_15.json" }, "13.6.16": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_16.json" }, "13.6.17": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_17.json" }, @@ -1452,224 +1452,224 @@ "13.6.2": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_2.json" }, "13.6.3": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_3.json" }, "13.6.4": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_4.json" }, "13.6.5": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_5.json" }, "13.6.6": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_6.json" }, "13.6.7": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_7.json" }, "13.6.8": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_8.json" }, "13.6.9": { "behavior": "UNIMPLEMENTED", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_6_9.json" }, "13.7.1": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 271, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_1.json" }, "13.7.10": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 3822, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_10.json" }, "13.7.11": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 529, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_11.json" }, "13.7.12": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 716, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_12.json" }, "13.7.13": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 1263, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_13.json" }, "13.7.14": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 2147, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_14.json" }, "13.7.15": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 3948, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_15.json" }, "13.7.16": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 3864, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_16.json" }, "13.7.17": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 4061, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_17.json" }, "13.7.18": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 3912, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_18.json" }, "13.7.2": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 234, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_2.json" }, "13.7.3": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 271, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_3.json" }, "13.7.4": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 294, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_4.json" }, "13.7.5": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 372, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_5.json" }, "13.7.6": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 501, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_6.json" }, "13.7.7": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 709, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_7.json" }, "13.7.8": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 1147, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_8.json" }, "13.7.9": { - "behavior": "UNIMPLEMENTED", + "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2040, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_13_7_9.json" }, "2.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_2_1.json" }, "2.10": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 6, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_2_10.json" }, "2.11": { "behavior": "OK", "behaviorClose": "OK", - "duration": 10, + "duration": 24, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_2_11.json" }, "2.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 3, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_2_2.json" }, "2.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 3, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_2_3.json" }, "2.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 3, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_2_4.json" }, @@ -1683,7 +1683,7 @@ "2.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 9, + "duration": 28, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_2_6.json" }, @@ -1711,14 +1711,14 @@ "3.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_3_1.json" }, "3.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": null, "reportfile": "tungstenite_case_3_2.json" }, @@ -1732,7 +1732,7 @@ "3.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 5, + "duration": 6, "remoteCloseCode": null, "reportfile": "tungstenite_case_3_4.json" }, @@ -1760,21 +1760,21 @@ "4.1.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_4_1_1.json" }, "4.1.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_4_1_2.json" }, "4.1.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": null, "reportfile": "tungstenite_case_4_1_3.json" }, @@ -1788,21 +1788,21 @@ "4.1.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 8, "remoteCloseCode": null, "reportfile": "tungstenite_case_4_1_5.json" }, "4.2.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_4_2_1.json" }, "4.2.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_4_2_2.json" }, @@ -1823,14 +1823,14 @@ "4.2.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 5, "remoteCloseCode": null, "reportfile": "tungstenite_case_4_2_5.json" }, "5.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_5_1.json" }, @@ -1844,28 +1844,28 @@ "5.11": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 4, "remoteCloseCode": null, "reportfile": "tungstenite_case_5_11.json" }, "5.12": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_5_12.json" }, "5.13": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_5_13.json" }, "5.14": { "behavior": "OK", "behaviorClose": "OK", - "duration": 3, + "duration": 8, "remoteCloseCode": null, "reportfile": "tungstenite_case_5_14.json" }, @@ -1879,84 +1879,84 @@ "5.16": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_5_16.json" }, "5.17": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_5_17.json" }, "5.18": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_5_18.json" }, "5.19": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1004, + "duration": 1029, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_5_19.json" }, "5.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_5_2.json" }, "5.20": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1005, + "duration": 1012, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_5_20.json" }, "5.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_5_3.json" }, "5.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 3, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_5_4.json" }, "5.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 8, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_5_5.json" }, "5.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 8, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_5_6.json" }, "5.7": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_5_7.json" }, "5.8": { "behavior": "OK", "behaviorClose": "OK", - "duration": 17, + "duration": 7, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_5_8.json" }, @@ -1970,42 +1970,42 @@ "6.1.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 6, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_1_1.json" }, "6.1.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 3, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_1_2.json" }, "6.1.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 3, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_1_3.json" }, "6.10.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_10_1.json" }, "6.10.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_10_2.json" }, "6.10.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_10_3.json" }, @@ -2026,7 +2026,7 @@ "6.11.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_11_3.json" }, @@ -2040,35 +2040,35 @@ "6.11.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_11_5.json" }, "6.12.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_12_1.json" }, "6.12.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 11, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_12_2.json" }, "6.12.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_12_3.json" }, "6.12.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_12_4.json" }, @@ -2082,63 +2082,63 @@ "6.12.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_12_6.json" }, "6.12.7": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_12_7.json" }, "6.12.8": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_12_8.json" }, "6.13.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_13_1.json" }, "6.13.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_13_2.json" }, "6.13.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_13_3.json" }, "6.13.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_13_4.json" }, "6.13.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_13_5.json" }, "6.14.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_14_1.json" }, @@ -2152,49 +2152,49 @@ "6.14.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_14_2.json" }, "6.14.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_14_3.json" }, "6.14.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_14_4.json" }, "6.14.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_14_5.json" }, "6.14.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_14_6.json" }, "6.14.7": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_14_7.json" }, "6.14.8": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_14_8.json" }, @@ -2208,14 +2208,14 @@ "6.15.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_15_1.json" }, "6.16.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_16_1.json" }, @@ -2264,14 +2264,14 @@ "6.17.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_17_5.json" }, "6.18.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_18_1.json" }, @@ -2292,49 +2292,49 @@ "6.18.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_18_4.json" }, "6.18.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_18_5.json" }, "6.19.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_19_1.json" }, "6.19.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_19_2.json" }, "6.19.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_19_3.json" }, "6.19.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_19_4.json" }, "6.19.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_19_5.json" }, @@ -2369,119 +2369,119 @@ "6.20.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_20_1.json" }, "6.20.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_20_2.json" }, "6.20.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_20_3.json" }, "6.20.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_20_4.json" }, "6.20.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_20_5.json" }, "6.20.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_20_6.json" }, "6.20.7": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_20_7.json" }, "6.21.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_21_1.json" }, "6.21.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_21_2.json" }, "6.21.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_21_3.json" }, "6.21.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_21_4.json" }, "6.21.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_21_5.json" }, "6.21.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_21_6.json" }, "6.21.7": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_21_7.json" }, "6.21.8": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_21_8.json" }, "6.22.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_1.json" }, "6.22.10": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_10.json" }, @@ -2495,7 +2495,7 @@ "6.22.12": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_12.json" }, @@ -2523,7 +2523,7 @@ "6.22.16": { "behavior": "OK", "behaviorClose": "OK", - "duration": 3, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_16.json" }, @@ -2551,7 +2551,7 @@ "6.22.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_2.json" }, @@ -2565,7 +2565,7 @@ "6.22.21": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_21.json" }, @@ -2579,28 +2579,28 @@ "6.22.23": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_23.json" }, "6.22.24": { "behavior": "OK", "behaviorClose": "OK", - "duration": 3, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_24.json" }, "6.22.25": { "behavior": "OK", "behaviorClose": "OK", - "duration": 3, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_25.json" }, "6.22.26": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 17, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_26.json" }, @@ -2628,14 +2628,14 @@ "6.22.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 3, + "duration": 11, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_3.json" }, "6.22.30": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_30.json" }, @@ -2656,28 +2656,28 @@ "6.22.33": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_33.json" }, "6.22.34": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_34.json" }, "6.22.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 4, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_4.json" }, "6.22.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_5.json" }, @@ -2698,14 +2698,14 @@ "6.22.8": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_8.json" }, "6.22.9": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 4, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_22_9.json" }, @@ -2747,14 +2747,14 @@ "6.23.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 4, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_23_6.json" }, "6.23.7": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_23_7.json" }, @@ -2775,63 +2775,63 @@ "6.4.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1001, + "duration": 1003, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_4_1.json" }, "6.4.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1002, + "duration": 1005, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_4_2.json" }, "6.4.3": { "behavior": "NON-STRICT", "behaviorClose": "OK", - "duration": 2002, + "duration": 2005, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_4_3.json" }, "6.4.4": { "behavior": "NON-STRICT", "behaviorClose": "OK", - "duration": 2002, + "duration": 2005, "remoteCloseCode": null, "reportfile": "tungstenite_case_6_4_4.json" }, "6.5.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 5, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_5_1.json" }, "6.5.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 5, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_5_2.json" }, "6.5.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 11, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_5_3.json" }, "6.5.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 3, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_5_4.json" }, "6.5.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 7, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_5_5.json" }, @@ -2852,7 +2852,7 @@ "6.6.11": { "behavior": "OK", "behaviorClose": "OK", - "duration": 5, + "duration": 6, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_6_11.json" }, @@ -2908,14 +2908,14 @@ "6.6.9": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_6_9.json" }, "6.7.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 2, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_7_1.json" }, @@ -2929,14 +2929,14 @@ "6.7.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_7_3.json" }, "6.7.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 3, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_6_7_4.json" }, @@ -2985,7 +2985,7 @@ "7.1.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 3, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_1_1.json" }, @@ -3006,7 +3006,7 @@ "7.1.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_1_4.json" }, @@ -3020,7 +3020,7 @@ "7.1.6": { "behavior": "INFORMATIONAL", "behaviorClose": "INFORMATIONAL", - "duration": 16, + "duration": 11, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_1_6.json" }, @@ -3034,7 +3034,7 @@ "7.13.2": { "behavior": "INFORMATIONAL", "behaviorClose": "INFORMATIONAL", - "duration": 2, + "duration": 1, "remoteCloseCode": 1002, "reportfile": "tungstenite_case_7_13_2.json" }, @@ -3048,21 +3048,21 @@ "7.3.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_7_3_2.json" }, "7.3.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_3_3.json" }, "7.3.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_3_4.json" }, @@ -3076,14 +3076,14 @@ "7.3.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_7_3_6.json" }, "7.5.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": null, "reportfile": "tungstenite_case_7_5_1.json" }, @@ -3097,28 +3097,28 @@ "7.7.10": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_7_10.json" }, "7.7.11": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_7_11.json" }, "7.7.12": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_7_12.json" }, "7.7.13": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_7_13.json" }, @@ -3132,7 +3132,7 @@ "7.7.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_7_3.json" }, @@ -3146,42 +3146,42 @@ "7.7.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_7_5.json" }, "7.7.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_7_6.json" }, "7.7.7": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_7_7.json" }, "7.7.8": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_7_8.json" }, "7.7.9": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_7_7_9.json" }, "7.9.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1002, "reportfile": "tungstenite_case_7_9_1.json" }, @@ -3209,21 +3209,21 @@ "7.9.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1002, "reportfile": "tungstenite_case_7_9_5.json" }, "7.9.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1002, "reportfile": "tungstenite_case_7_9_6.json" }, "7.9.7": { "behavior": "OK", "behaviorClose": "OK", - "duration": 0, + "duration": 1, "remoteCloseCode": 1002, "reportfile": "tungstenite_case_7_9_7.json" }, @@ -3237,14 +3237,14 @@ "7.9.9": { "behavior": "OK", "behaviorClose": "OK", - "duration": 1, + "duration": 5, "remoteCloseCode": 1002, "reportfile": "tungstenite_case_7_9_9.json" }, "9.1.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 5, + "duration": 2, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_1_1.json" }, @@ -3258,28 +3258,28 @@ "9.1.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 22, + "duration": 16, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_1_3.json" }, "9.1.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 110, + "duration": 94, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_1_4.json" }, "9.1.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 205, + "duration": 158, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_1_5.json" }, "9.1.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 320, + "duration": 235, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_1_6.json" }, @@ -3293,329 +3293,329 @@ "9.2.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 6, + "duration": 3, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_2_2.json" }, "9.2.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 16, + "duration": 8, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_2_3.json" }, "9.2.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 123, + "duration": 34, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_2_4.json" }, "9.2.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 169, + "duration": 106, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_2_5.json" }, "9.2.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 360, + "duration": 206, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_2_6.json" }, "9.3.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 638, + "duration": 232, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_3_1.json" }, "9.3.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 209, + "duration": 91, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_3_2.json" }, "9.3.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 93, + "duration": 63, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_3_3.json" }, "9.3.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 64, + "duration": 35, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_3_4.json" }, "9.3.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 43, + "duration": 29, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_3_5.json" }, "9.3.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 50, + "duration": 54, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_3_6.json" }, "9.3.7": { "behavior": "OK", "behaviorClose": "OK", - "duration": 45, + "duration": 30, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_3_7.json" }, "9.3.8": { "behavior": "OK", "behaviorClose": "OK", - "duration": 51, + "duration": 31, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_3_8.json" }, "9.3.9": { "behavior": "OK", "behaviorClose": "OK", - "duration": 57, + "duration": 31, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_3_9.json" }, "9.4.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 608, + "duration": 171, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_4_1.json" }, "9.4.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 192, + "duration": 65, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_4_2.json" }, "9.4.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 75, + "duration": 65, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_4_3.json" }, "9.4.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 44, + "duration": 30, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_4_4.json" }, "9.4.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 29, + "duration": 20, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_4_5.json" }, "9.4.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 39, + "duration": 24, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_4_6.json" }, "9.4.7": { "behavior": "OK", "behaviorClose": "OK", - "duration": 35, + "duration": 24, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_4_7.json" }, "9.4.8": { "behavior": "OK", "behaviorClose": "OK", - "duration": 24, + "duration": 45, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_4_8.json" }, "9.4.9": { "behavior": "OK", "behaviorClose": "OK", - "duration": 29, + "duration": 20, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_4_9.json" }, "9.5.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 795, + "duration": 336, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_5_1.json" }, "9.5.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 425, + "duration": 138, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_5_2.json" }, "9.5.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 228, + "duration": 79, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_5_3.json" }, "9.5.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 114, + "duration": 60, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_5_4.json" }, "9.5.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 84, + "duration": 28, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_5_5.json" }, "9.5.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 51, + "duration": 20, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_5_6.json" }, "9.6.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 799, + "duration": 299, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_6_1.json" }, "9.6.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 429, + "duration": 151, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_6_2.json" }, "9.6.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 214, + "duration": 74, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_6_3.json" }, "9.6.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 127, + "duration": 54, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_6_4.json" }, "9.6.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 80, + "duration": 25, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_6_5.json" }, "9.6.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 57, + "duration": 17, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_6_6.json" }, "9.7.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 127, + "duration": 224, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_7_1.json" }, "9.7.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 121, + "duration": 109, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_7_2.json" }, "9.7.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 124, + "duration": 50, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_7_3.json" }, "9.7.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 146, + "duration": 109, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_7_4.json" }, "9.7.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 196, + "duration": 64, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_7_5.json" }, "9.7.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 351, + "duration": 118, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_7_6.json" }, "9.8.1": { "behavior": "OK", "behaviorClose": "OK", - "duration": 95, + "duration": 92, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_8_1.json" }, "9.8.2": { "behavior": "OK", "behaviorClose": "OK", - "duration": 119, + "duration": 77, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_8_2.json" }, "9.8.3": { "behavior": "OK", "behaviorClose": "OK", - "duration": 115, + "duration": 53, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_8_3.json" }, "9.8.4": { "behavior": "OK", "behaviorClose": "OK", - "duration": 134, + "duration": 73, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_8_4.json" }, "9.8.5": { "behavior": "OK", "behaviorClose": "OK", - "duration": 187, + "duration": 62, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_8_5.json" }, "9.8.6": { "behavior": "OK", "behaviorClose": "OK", - "duration": 328, + "duration": 102, "remoteCloseCode": 1000, "reportfile": "tungstenite_case_9_8_6.json" } diff --git a/examples/autobahn-client.rs b/examples/autobahn-client.rs index 8143175..4d5e18d 100644 --- a/examples/autobahn-client.rs +++ b/examples/autobahn-client.rs @@ -1,7 +1,10 @@ use log::*; use url::Url; -use tungstenite::{connect, Error, Message, Result}; +use tungstenite::{ + client::connect_with_config, connect, extensions::DeflateConfig, protocol::WebSocketConfig, + Error, Message, Result, +}; const AGENT: &str = "Tungstenite"; @@ -24,7 +27,14 @@ fn run_test(case: u32) -> Result<()> { info!("Running test case {}", case); let case_url = Url::parse(&format!("ws://localhost:9001/runCase?case={}&agent={}", case, AGENT)).unwrap(); - let (mut socket, _) = connect(case_url)?; + let (mut socket, _) = connect_with_config( + case_url, + Some(WebSocketConfig { + compression: Some(DeflateConfig::default()), + ..WebSocketConfig::default() + }), + 3, + )?; loop { match socket.read_message()? { msg @ Message::Text(_) | msg @ Message::Binary(_) => { diff --git a/examples/autobahn-server.rs b/examples/autobahn-server.rs index 3250b2c..8feb1f4 100644 --- a/examples/autobahn-server.rs +++ b/examples/autobahn-server.rs @@ -4,7 +4,10 @@ use std::{ }; use log::*; -use tungstenite::{accept, handshake::HandshakeRole, Error, HandshakeError, Message, Result}; +use tungstenite::{ + accept_with_config, extensions::DeflateConfig, handshake::HandshakeRole, + protocol::WebSocketConfig, Error, HandshakeError, Message, Result, +}; fn must_not_block(err: HandshakeError) -> Error { match err { @@ -14,7 +17,14 @@ fn must_not_block(err: HandshakeError) -> Error { } fn handle_client(stream: TcpStream) -> Result<()> { - let mut socket = accept(stream).map_err(must_not_block)?; + let mut socket = accept_with_config( + stream, + Some(WebSocketConfig { + compression: Some(DeflateConfig::default()), + ..WebSocketConfig::default() + }), + ) + .map_err(must_not_block)?; info!("Running test"); loop { match socket.read_message()? { diff --git a/examples/srv_accept_unmasked_frames.rs b/examples/srv_accept_unmasked_frames.rs index b280fba..a3134f0 100644 --- a/examples/srv_accept_unmasked_frames.rs +++ b/examples/srv_accept_unmasked_frames.rs @@ -35,6 +35,7 @@ fn main() { // rare cases where it is necessary to integrate with existing/legacy // clients which are sending unmasked frames accept_unmasked_frames: true, + ..WebSocketConfig::default() }); let mut websocket = accept_hdr_with_config(stream.unwrap(), callback, config).unwrap(); diff --git a/src/error.rs b/src/error.rs index 510e3f4..fc7c59f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,7 +2,10 @@ use std::{io, result, str, string}; -use crate::protocol::{frame::coding::Data, Message}; +use crate::{ + extensions, + protocol::{frame::coding::Data, Message}, +}; use http::Response; use thiserror::Error; @@ -67,6 +70,9 @@ pub enum Error { /// HTTP format error. #[error("HTTP format error: {0}")] HttpFormat(#[from] http::Error), + /// Error from `permessage-deflate` extension. + #[error("deflate error: {0}")] + Deflate(#[from] extensions::DeflateError), } impl From for Error { @@ -138,7 +144,7 @@ pub enum CapacityError { } /// Indicates the specific type/cause of a protocol error. -#[derive(Error, Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Error, Debug, PartialEq, Eq, Clone)] pub enum ProtocolError { /// Use of the wrong HTTP method (the WebSocket protocol requires the GET method be used). #[error("Unsupported HTTP method used - only GET is allowed")] @@ -191,6 +197,9 @@ pub enum ProtocolError { /// Control frames must not be fragmented. #[error("Fragmented control frame")] FragmentedControlFrame, + /// Control frames must not be compressed. + #[error("Compressed control frame")] + CompressedControlFrame, /// Control frames must have a payload of 125 bytes or less. #[error("Control frame too big (payload must be 125 bytes or less)")] ControlFrameTooBig, @@ -203,6 +212,9 @@ pub enum ProtocolError { /// Received a continue frame despite there being nothing to continue. #[error("Continue frame but nothing to continue")] UnexpectedContinueFrame, + /// Received a compressed continue frame. + #[error("Continue frame must not have compress bit set")] + CompressedContinueFrame, /// Received data while waiting for more fragments. #[error("While waiting for more fragments received: {0}")] ExpectedFragment(Data), @@ -215,6 +227,12 @@ pub enum ProtocolError { /// The payload for the closing frame is invalid. #[error("Invalid close sequence")] InvalidCloseSequence, + /// The negotiation response included an extension not offered. + #[error("Extension negotiation response had invalid extension: {0}")] + InvalidExtension(String), + /// The negotiation response included an extension more than once. + #[error("Extension negotiation response had conflicting extension: {0}")] + ExtensionConflict(String), } /// Indicates the specific type/cause of URL error. diff --git a/src/extensions/compression/deflate.rs b/src/extensions/compression/deflate.rs new file mode 100644 index 0000000..9149b14 --- /dev/null +++ b/src/extensions/compression/deflate.rs @@ -0,0 +1,289 @@ +use std::io::Write; + +use flate2::{Compress, Compression, Decompress, FlushCompress, FlushDecompress, Status}; +use http::HeaderValue; +use thiserror::Error; + +use crate::{ + extensions::{self, Param}, + protocol::Role, +}; + +const PER_MESSAGE_DEFLATE: &str = "permessage-deflate"; +const CLIENT_NO_CONTEXT_TAKEOVER: &str = "client_no_context_takeover"; +const SERVER_NO_CONTEXT_TAKEOVER: &str = "server_no_context_takeover"; +const CLIENT_MAX_WINDOW_BITS: &str = "client_max_window_bits"; +const SERVER_MAX_WINDOW_BITS: &str = "server_max_window_bits"; + +const TRAILER: [u8; 4] = [0x00, 0x00, 0xff, 0xff]; + +/// Error from `permessage-deflate` extension. +#[derive(Debug, Error)] +pub enum DeflateError { + /// Compress failed + #[error("failed to compress: {0}")] + Compress(std::io::Error), + /// Decompress failed + #[error("failed to decompress: {0}")] + Decompress(std::io::Error), +} + +// Parameters `server_max_window_bits` and `client_max_window_bits` are not supported for now +// because custom window size requires `flate2/zlib` feature. +// TODO Configs for how the server accepts these offers. +/// Configurations for `permessage-deflate` Per-Message Compression Extension. +#[derive(Clone, Copy, Debug)] +pub struct DeflateConfig { + /// Compression level. + pub compression: Compression, + /// Request the peer server not to use context takeover. + pub server_no_context_takeover: bool, + /// Hint that context takeover is not used. + pub client_no_context_takeover: bool, +} + +impl Default for DeflateConfig { + fn default() -> Self { + Self { + compression: Compression::default(), + server_no_context_takeover: false, + client_no_context_takeover: false, + } + } +} + +impl DeflateConfig { + pub(crate) fn name(&self) -> &str { + PER_MESSAGE_DEFLATE + } + + /// Value for `Sec-WebSocket-Extensions` request header. + pub(crate) fn negotiation_offers(&self) -> HeaderValue { + let mut offers = Vec::new(); + if self.server_no_context_takeover { + offers.push(Param::new(SERVER_NO_CONTEXT_TAKEOVER)); + } + if self.client_no_context_takeover { + offers.push(Param::new(CLIENT_NO_CONTEXT_TAKEOVER)); + } + to_header_value(&offers) + } + + // This can be used for `WebSocket::from_raw_socket_with_compression`. + /// Returns negotiation response based on offers and `DeflateContext` to manage per message compression. + pub fn negotiation_response(&self, extensions: &str) -> Option<(HeaderValue, DeflateContext)> { + // Accept the first valid offer for `permessage-deflate`. + // A server MUST decline an extension negotiation offer for this + // extension if any of the following conditions are met: + // * The negotiation offer contains an extension parameter not defined + // for use in an offer. + // * The negotiation offer contains an extension parameter with an + // invalid value. + // * The negotiation offer contains multiple extension parameters with + // the same name. + // * The server doesn't support the offered configuration. + 'outer: for (_, offer) in + extensions::parse_header(extensions).iter().filter(|(k, _)| k == self.name()) + { + let mut config = + DeflateConfig { compression: self.compression, ..DeflateConfig::default() }; + let mut agreed = Vec::new(); + let mut seen_server_no_context_takeover = false; + let mut seen_client_no_context_takeover = false; + let mut seen_client_max_window_bits = false; + for param in offer { + match param.name() { + SERVER_NO_CONTEXT_TAKEOVER => { + // Invalid offer with multiple params with same name is declined. + if seen_server_no_context_takeover { + continue 'outer; + } + seen_server_no_context_takeover = true; + config.server_no_context_takeover = true; + agreed.push(Param::new(SERVER_NO_CONTEXT_TAKEOVER)); + } + + CLIENT_NO_CONTEXT_TAKEOVER => { + // Invalid offer with multiple params with same name is declined. + if seen_client_no_context_takeover { + continue 'outer; + } + seen_client_no_context_takeover = true; + config.client_no_context_takeover = true; + agreed.push(Param::new(CLIENT_NO_CONTEXT_TAKEOVER)); + } + + // Max window bits are not supported at the moment. + SERVER_MAX_WINDOW_BITS => { + // A server declines an extension negotiation offer with this parameter + // if the server doesn't support it. + continue 'outer; + } + // Not supported, but server may ignore and accept the offer. + CLIENT_MAX_WINDOW_BITS => { + // Invalid offer with multiple params with same name is declined. + if seen_client_max_window_bits { + continue 'outer; + } + seen_client_max_window_bits = true; + } + + // Offer with unknown parameter MUST be declined. + _ => { + continue 'outer; + } + } + } + + return Some((to_header_value(&agreed), DeflateContext::new(Role::Server, config))); + } + + None + } + + pub(crate) fn accept_response(&self, agreed: &[Param]) -> Result { + let mut config = + DeflateConfig { compression: self.compression, ..DeflateConfig::default() }; + for param in agreed { + match param.name() { + SERVER_NO_CONTEXT_TAKEOVER => { + config.server_no_context_takeover = true; + } + + CLIENT_NO_CONTEXT_TAKEOVER => { + config.client_no_context_takeover = true; + } + + SERVER_MAX_WINDOW_BITS => {} + CLIENT_MAX_WINDOW_BITS => {} + + _ => { + // + } + } + } + Ok(DeflateContext::new(Role::Client, config)) + } +} + +#[derive(Debug)] +/// Manages per message compression using DEFLATE. +pub struct DeflateContext { + role: Role, + config: DeflateConfig, + compressor: Compress, + decompressor: Decompress, +} + +impl DeflateContext { + fn new(role: Role, config: DeflateConfig) -> Self { + DeflateContext { + role, + config, + compressor: Compress::new(config.compression, false), + decompressor: Decompress::new(false), + } + } + + fn own_context_takeover(&self) -> bool { + match self.role { + Role::Server => !self.config.server_no_context_takeover, + Role::Client => !self.config.client_no_context_takeover, + } + } + + fn peer_context_takeover(&self) -> bool { + match self.role { + Role::Server => !self.config.client_no_context_takeover, + Role::Client => !self.config.server_no_context_takeover, + } + } + + // Compress the data of message. + pub(crate) fn compress(&mut self, data: &[u8]) -> Result, DeflateError> { + // https://datatracker.ietf.org/doc/html/rfc7692#section-7.2.1 + // 1. Compress all the octets of the payload of the message using DEFLATE. + let mut output = Vec::with_capacity(data.len()); + let before_in = self.compressor.total_in() as usize; + while (self.compressor.total_in() as usize) - before_in < data.len() { + let offset = (self.compressor.total_in() as usize) - before_in; + match self + .compressor + .compress_vec(&data[offset..], &mut output, FlushCompress::None) + .map_err(|e| DeflateError::Compress(e.into()))? + { + Status::Ok => continue, + Status::BufError => output.reserve(4096), + Status::StreamEnd => break, + } + } + // 2. If the resulting data does not end with an empty DEFLATE block + // with no compression (the "BTYPE" bits are set to 00), append an + // empty DEFLATE block with no compression to the tail end. + while !output.ends_with(&TRAILER) { + output.reserve(5); + match self + .compressor + .compress_vec(&[], &mut output, FlushCompress::Sync) + .map_err(|e| DeflateError::Compress(e.into()))? + { + Status::Ok | Status::BufError => continue, + Status::StreamEnd => break, + } + } + // 3. Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end. + // After this step, the last octet of the compressed data contains + // (possibly part of) the DEFLATE header bits with the "BTYPE" bits + // set to 00. + output.truncate(output.len() - 4); + + if !self.own_context_takeover() { + self.compressor.reset(); + } + + Ok(output) + } + + pub(crate) fn decompress( + &mut self, + mut data: Vec, + is_final: bool, + ) -> Result, DeflateError> { + if is_final { + data.extend_from_slice(&TRAILER); + } + + let before_in = self.decompressor.total_in() as usize; + let mut output = Vec::with_capacity(2 * data.len()); + loop { + let offset = (self.decompressor.total_in() as usize) - before_in; + match self + .decompressor + .decompress_vec(&data[offset..], &mut output, FlushDecompress::None) + .map_err(|e| DeflateError::Decompress(e.into()))? + { + Status::Ok => output.reserve(2 * output.len()), + Status::BufError | Status::StreamEnd => break, + } + } + + if is_final && !self.peer_context_takeover() { + self.decompressor.reset(false); + } + + Ok(output) + } +} + +fn to_header_value(params: &[Param]) -> HeaderValue { + let mut value = Vec::new(); + write!(value, "{}", PER_MESSAGE_DEFLATE).unwrap(); + for param in params { + if let Some(v) = param.value() { + write!(value, "; {}={}", param.name(), v).unwrap(); + } else { + write!(value, "; {}", param.name()).unwrap(); + } + } + HeaderValue::from_bytes(&value).unwrap() +} diff --git a/src/extensions/compression/mod.rs b/src/extensions/compression/mod.rs new file mode 100644 index 0000000..467640c --- /dev/null +++ b/src/extensions/compression/mod.rs @@ -0,0 +1,4 @@ +//! [Per-Message Compression Extensions][rfc7692] +//! +//! [rfc7692]: https://tools.ietf.org/html/rfc7692 +pub mod deflate; diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs new file mode 100644 index 0000000..1e9dd16 --- /dev/null +++ b/src/extensions/mod.rs @@ -0,0 +1,81 @@ +//! WebSocket extensions. +// Only `permessage-deflate` is supported at the moment. + +use std::borrow::Cow; + +mod compression; +pub use compression::deflate::{DeflateConfig, DeflateContext, DeflateError}; + +/// Extension parameter. +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct Param<'a> { + name: Cow<'a, str>, + value: Option>, +} + +impl<'a> Param<'a> { + /// Create a new parameter with name. + pub fn new(name: impl Into>) -> Self { + Param { name: name.into(), value: None } + } + + /// Consume itself to create a parameter with value. + pub fn with_value(mut self, value: impl Into>) -> Self { + self.value = Some(value.into()); + self + } + + /// Get the name of the parameter. + pub fn name(&self) -> &str { + &self.name + } + + /// Get the optional value of the parameter. + pub fn value(&self) -> Option<&str> { + self.value.as_ref().map(|v| v.as_ref()) + } +} + +// NOTE This doesn't support quoted values +/// Parse `Sec-WebSocket-Extensions` offer/response. +pub(crate) fn parse_header(exts: &str) -> Vec<(Cow<'_, str>, Vec>)> { + let mut collected = Vec::new(); + // ext-name; a; b=c, ext-name; x, y=z + for ext in exts.split(',') { + let mut parts = ext.split(';'); + if let Some(name) = parts.next().map(str::trim) { + let mut params = Vec::new(); + for p in parts { + let mut kv = p.splitn(2, '='); + if let Some(key) = kv.next().map(str::trim) { + let param = if let Some(value) = kv.next().map(str::trim) { + Param::new(key).with_value(value) + } else { + Param::new(key) + }; + params.push(param); + } + } + collected.push((Cow::from(name), params)); + } + } + collected +} + +#[test] +fn test_parse_extensions() { + let extensions = "permessage-deflate; client_max_window_bits; server_max_window_bits=10, permessage-deflate; client_max_window_bits"; + assert_eq!( + parse_header(extensions), + vec![ + ( + Cow::from("permessage-deflate"), + vec![ + Param::new("client_max_window_bits"), + Param::new("server_max_window_bits").with_value("10") + ] + ), + (Cow::from("permessage-deflate"), vec![Param::new("client_max_window_bits")]) + ] + ); +} diff --git a/src/handshake/client.rs b/src/handshake/client.rs index 07c41c1..f1bc15e 100644 --- a/src/handshake/client.rs +++ b/src/handshake/client.rs @@ -17,6 +17,7 @@ use super::{ }; use crate::{ error::{Error, ProtocolError, Result, UrlError}, + extensions::{self, DeflateContext}, protocol::{Role, WebSocket, WebSocketConfig}, }; @@ -55,7 +56,7 @@ impl ClientHandshake { let key = generate_key(); let machine = { - let req = generate_request(request, &key)?; + let req = generate_request(request, &key, &config)?; HandshakeMachine::start_write(stream, req) }; @@ -82,10 +83,15 @@ impl HandshakeRole for ClientHandshake { ProcessingResult::Continue(HandshakeMachine::start_read(stream)) } StageResult::DoneReading { stream, result, tail } => { - let result = self.verify_data.verify_response(result)?; + let (result, pmce) = self.verify_data.verify_response(result, &self.config)?; debug!("Client handshake done."); - let websocket = - WebSocket::from_partially_read(stream, tail, Role::Client, self.config); + let websocket = WebSocket::from_partially_read_with_compression( + stream, + tail, + Role::Client, + self.config, + pmce, + ); ProcessingResult::Done((websocket, result)) } }) @@ -93,7 +99,11 @@ impl HandshakeRole for ClientHandshake { } /// Generate client request. -fn generate_request(request: Request, key: &str) -> Result> { +fn generate_request( + request: Request, + key: &str, + config: &Option, +) -> Result> { let mut req = Vec::new(); let uri = request.uri(); @@ -131,6 +141,10 @@ fn generate_request(request: Request, key: &str) -> Result> { } writeln!(req, "{}: {}\r", k, v.to_str()?).unwrap(); } + if let Some(compression) = &config.and_then(|c| c.compression) { + let offer = compression.negotiation_offers(); + writeln!(req, "Sec-WebSocket-Extensions: {}\r", offer.to_str()?).unwrap(); + } writeln!(req, "\r").unwrap(); trace!("Request: {:?}", String::from_utf8_lossy(&req)); Ok(req) @@ -144,7 +158,11 @@ struct VerifyData { } impl VerifyData { - pub fn verify_response(&self, response: Response) -> Result { + pub fn verify_response( + &self, + response: Response, + config: &Option, + ) -> Result<(Response, Option)> { // 1. If the status code received from the server is not 101, the // client handles the response per HTTP [RFC2616] procedures. (RFC 6455) if response.status() != StatusCode::SWITCHING_PROTOCOLS { @@ -184,12 +202,39 @@ impl VerifyData { if !headers.get("Sec-WebSocket-Accept").map(|h| h == &self.accept_key).unwrap_or(false) { return Err(Error::Protocol(ProtocolError::SecWebSocketAcceptKeyMismatch)); } + let mut pmce = None; // 5. If the response includes a |Sec-WebSocket-Extensions| header // field and this header field indicates the use of an extension // that was not present in the client's handshake (the server has // indicated an extension not requested by the client), the client // MUST _Fail the WebSocket Connection_. (RFC 6455) - // TODO + if let Some(exts) = headers + .get("Sec-WebSocket-Extensions") + .and_then(|h| h.to_str().ok()) + .map(extensions::parse_header) + { + if let Some(compression) = &config.and_then(|c| c.compression) { + for (name, params) in exts { + if name != compression.name() { + return Err(Error::Protocol(ProtocolError::InvalidExtension( + name.to_string(), + ))); + } + + // Already had PMCE configured + if pmce.is_some() { + return Err(Error::Protocol(ProtocolError::ExtensionConflict( + name.to_string(), + ))); + } + + pmce = Some(compression.accept_response(¶ms)?); + } + } else if let Some((name, _)) = exts.get(0) { + // The client didn't request anything, but got something + return Err(Error::Protocol(ProtocolError::InvalidExtension(name.to_string()))); + } + } // 6. If the response includes a |Sec-WebSocket-Protocol| header field // and this header field indicates the use of a subprotocol that was @@ -198,7 +243,7 @@ impl VerifyData { // the WebSocket Connection_. (RFC 6455) // TODO - Ok(response) + Ok((response, pmce)) } } @@ -243,7 +288,7 @@ fn generate_key() -> String { #[cfg(test)] mod tests { use super::{super::machine::TryParse, generate_key, generate_request, Response}; - use crate::client::IntoClientRequest; + use crate::{client::IntoClientRequest, extensions::DeflateConfig, protocol::WebSocketConfig}; #[test] fn random_keys() { @@ -273,7 +318,7 @@ mod tests { Sec-WebSocket-Version: 13\r\n\ Sec-WebSocket-Key: A70tsIbeMZUbJHh5BWFw6Q==\r\n\ \r\n"; - let request = generate_request(request, key).unwrap(); + let request = generate_request(request, key, &None).unwrap(); println!("Request: {}", String::from_utf8_lossy(&request)); assert_eq!(&request[..], &correct[..]); } @@ -290,7 +335,7 @@ mod tests { Sec-WebSocket-Version: 13\r\n\ Sec-WebSocket-Key: A70tsIbeMZUbJHh5BWFw6Q==\r\n\ \r\n"; - let request = generate_request(request, key).unwrap(); + let request = generate_request(request, key, &None).unwrap(); println!("Request: {}", String::from_utf8_lossy(&request)); assert_eq!(&request[..], &correct[..]); } @@ -307,7 +352,33 @@ mod tests { Sec-WebSocket-Version: 13\r\n\ Sec-WebSocket-Key: A70tsIbeMZUbJHh5BWFw6Q==\r\n\ \r\n"; - let request = generate_request(request, key).unwrap(); + let request = generate_request(request, key, &None).unwrap(); + println!("Request: {}", String::from_utf8_lossy(&request)); + assert_eq!(&request[..], &correct[..]); + } + + #[test] + fn request_with_compression() { + let request = "ws://localhost/getCaseCount".into_client_request().unwrap(); + let key = "A70tsIbeMZUbJHh5BWFw6Q=="; + let correct = b"\ + GET /getCaseCount HTTP/1.1\r\n\ + Host: localhost\r\n\ + Connection: Upgrade\r\n\ + Upgrade: websocket\r\n\ + Sec-WebSocket-Version: 13\r\n\ + Sec-WebSocket-Key: A70tsIbeMZUbJHh5BWFw6Q==\r\n\ + Sec-WebSocket-Extensions: permessage-deflate\r\n\ + \r\n"; + let request = generate_request( + request, + key, + &Some(WebSocketConfig { + compression: Some(DeflateConfig::default()), + ..WebSocketConfig::default() + }), + ) + .unwrap(); println!("Request: {}", String::from_utf8_lossy(&request)); assert_eq!(&request[..], &correct[..]); } diff --git a/src/handshake/server.rs b/src/handshake/server.rs index fddf953..3c8dbd8 100644 --- a/src/handshake/server.rs +++ b/src/handshake/server.rs @@ -20,6 +20,7 @@ use super::{ }; use crate::{ error::{Error, ProtocolError, Result}, + extensions, protocol::{Role, WebSocket, WebSocketConfig}, }; @@ -202,6 +203,8 @@ pub struct ServerHandshake { config: Option, /// Error code/flag. If set, an error will be returned after sending response to the client. error_response: Option, + // Negotiated Per-Message Compression Extension context for server. + pmce: Option, /// Internal stream type. _marker: PhantomData, } @@ -219,6 +222,7 @@ impl ServerHandshake { callback: Some(callback), config, error_response: None, + pmce: None, _marker: PhantomData, }, } @@ -240,7 +244,20 @@ impl HandshakeRole for ServerHandshake { return Err(Error::Protocol(ProtocolError::JunkAfterRequest)); } - let response = create_response(&result)?; + let mut response = create_response(&result)?; + if let Some(compression) = &self.config.and_then(|c| c.compression) { + if let Some(extensions) = result + .headers() + .get("Sec-WebSocket-Extensions") + .and_then(|v| v.to_str().ok()) + { + if let Some((agreed, pmce)) = compression.negotiation_response(extensions) { + self.pmce = Some(pmce); + response.headers_mut().insert("Sec-WebSocket-Extensions", agreed); + } + } + } + let callback_result = if let Some(callback) = self.callback.take() { callback.on_request(&result, response) } else { @@ -280,7 +297,12 @@ impl HandshakeRole for ServerHandshake { return Err(Error::Http(err)); } else { debug!("Server handshake done."); - let websocket = WebSocket::from_raw_socket(stream, Role::Server, self.config); + let websocket = WebSocket::from_raw_socket_with_compression( + stream, + Role::Server, + self.config, + self.pmce.take(), + ); ProcessingResult::Done(websocket) } } diff --git a/src/lib.rs b/src/lib.rs index c2f29c2..c999342 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ pub use http; pub mod buffer; pub mod client; pub mod error; +pub mod extensions; pub mod handshake; pub mod protocol; mod server; diff --git a/src/protocol/frame/frame.rs b/src/protocol/frame/frame.rs index 8222fa1..e6051d5 100644 --- a/src/protocol/frame/frame.rs +++ b/src/protocol/frame/frame.rs @@ -304,6 +304,17 @@ impl Frame { Frame { header: FrameHeader { is_final, opcode, ..FrameHeader::default() }, payload: data } } + /// Create a new compressed data frame. + #[inline] + pub(crate) fn compressed_message(data: Vec, opcode: OpCode, is_final: bool) -> Frame { + debug_assert!(matches!(opcode, OpCode::Data(_)), "Invalid opcode for data frame."); + + Frame { + header: FrameHeader { is_final, opcode, rsv1: true, ..FrameHeader::default() }, + payload: data, + } + } + /// Create a new Pong control frame. #[inline] pub fn pong(data: Vec) -> Frame { diff --git a/src/protocol/message.rs b/src/protocol/message.rs index e8eb90e..6fa8301 100644 --- a/src/protocol/message.rs +++ b/src/protocol/message.rs @@ -84,6 +84,7 @@ use self::string_collect::StringCollector; #[derive(Debug)] pub struct IncompleteMessage { collector: IncompleteMessageCollector, + compressed: bool, } #[derive(Debug)] @@ -94,7 +95,7 @@ enum IncompleteMessageCollector { impl IncompleteMessage { /// Create new. - pub fn new(message_type: IncompleteMessageType) -> Self { + pub fn new(message_type: IncompleteMessageType, compressed: bool) -> Self { IncompleteMessage { collector: match message_type { IncompleteMessageType::Binary => IncompleteMessageCollector::Binary(Vec::new()), @@ -102,9 +103,14 @@ impl IncompleteMessage { IncompleteMessageCollector::Text(StringCollector::new()) } }, + compressed, } } + pub fn compressed(&self) -> bool { + self.compressed + } + /// Get the current filled size of the buffer. pub fn len(&self) -> usize { match self.collector { diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 215b061..a7f1483 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -22,6 +22,7 @@ use self::{ }; use crate::{ error::{Error, ProtocolError, Result}, + extensions::{self, DeflateContext}, util::NonBlockingResult, }; @@ -56,6 +57,8 @@ pub struct WebSocketConfig { /// some popular libraries that are sending unmasked frames, ignoring the RFC. /// By default this option is set to `false`, i.e. according to RFC 6455. pub accept_unmasked_frames: bool, + /// Optional configuration for Per-Message Compression Extension. + pub compression: Option, } impl Default for WebSocketConfig { @@ -65,6 +68,7 @@ impl Default for WebSocketConfig { max_message_size: Some(64 << 20), max_frame_size: Some(16 << 20), accept_unmasked_frames: false, + compression: None, } } } @@ -91,6 +95,18 @@ impl WebSocket { WebSocket { socket: stream, context: WebSocketContext::new(role, config) } } + /// Convert a raw socket into a WebSocket without performing a handshake. + pub fn from_raw_socket_with_compression( + stream: Stream, + role: Role, + config: Option, + pmce: Option, + ) -> Self { + let mut context = WebSocketContext::new(role, config); + context.pmce = pmce; + WebSocket { socket: stream, context } + } + /// Convert a raw socket into a WebSocket without performing a handshake. /// /// Call this function if you're using Tungstenite as a part of a web framework @@ -108,6 +124,21 @@ impl WebSocket { } } + pub(crate) fn from_partially_read_with_compression( + stream: Stream, + part: Vec, + role: Role, + config: Option, + pmce: Option, + ) -> Self { + WebSocket { + socket: stream, + context: WebSocketContext::from_partially_read_with_compression( + part, role, config, pmce, + ), + } + } + /// Returns a shared reference to the inner stream. pub fn get_ref(&self) -> &Stream { &self.socket @@ -241,6 +272,8 @@ pub struct WebSocketContext { pong: Option, /// The configuration for the websocket session. config: WebSocketConfig, + /// Per-Message Compression Extension. Only deflate is supported at the moment. + pub(crate) pmce: Option, } impl WebSocketContext { @@ -254,6 +287,7 @@ impl WebSocketContext { send_queue: VecDeque::new(), pong: None, config: config.unwrap_or_else(WebSocketConfig::default), + pmce: None, } } @@ -265,6 +299,19 @@ impl WebSocketContext { } } + pub(crate) fn from_partially_read_with_compression( + part: Vec, + role: Role, + config: Option, + pmce: Option, + ) -> Self { + WebSocketContext { + frame: FrameCodec::from_partially_read(part), + pmce, + ..WebSocketContext::new(role, config) + } + } + /// Change the configuration. pub fn set_config(&mut self, set_func: impl FnOnce(&mut WebSocketConfig)) { set_func(&mut self.config) @@ -348,8 +395,28 @@ impl WebSocketContext { } let frame = match message { - Message::Text(data) => Frame::message(data.into(), OpCode::Data(OpData::Text), true), - Message::Binary(data) => Frame::message(data, OpCode::Data(OpData::Binary), true), + Message::Text(data) => { + if let Some(pmce) = self.pmce.as_mut() { + Frame::compressed_message( + pmce.compress(data.as_bytes())?, + OpCode::Data(OpData::Text), + true, + ) + } else { + Frame::message(data.into(), OpCode::Data(OpData::Text), true) + } + } + Message::Binary(data) => { + if let Some(pmce) = self.pmce.as_mut() { + Frame::compressed_message( + pmce.compress(&data)?, + OpCode::Data(OpData::Binary), + true, + ) + } else { + Frame::message(data, OpCode::Data(OpData::Binary), true) + } + } Message::Ping(data) => Frame::ping(data), Message::Pong(data) => { self.pong = Some(Frame::pong(data)); @@ -438,11 +505,16 @@ impl WebSocketContext { // the negotiated extensions defines the meaning of such a nonzero // value, the receiving endpoint MUST _Fail the WebSocket // Connection_. + let mut is_compressed = false; { let hdr = frame.header(); - if hdr.rsv1 || hdr.rsv2 || hdr.rsv3 { + if (hdr.rsv1 && self.pmce.is_none()) || hdr.rsv2 || hdr.rsv3 { return Err(Error::Protocol(ProtocolError::NonZeroReservedBits)); } + + if hdr.rsv1 && self.pmce.is_some() { + is_compressed = true; + } } match self.role { @@ -478,6 +550,10 @@ impl WebSocketContext { _ if frame.payload().len() > 125 => { Err(Error::Protocol(ProtocolError::ControlFrameTooBig)) } + // Control frames must not have compress bit. + _ if is_compressed => { + Err(Error::Protocol(ProtocolError::CompressedControlFrame)) + } OpCtl::Close => Ok(self.do_close(frame.into_close()?).map(Message::Close)), OpCtl::Reserved(i) => { Err(Error::Protocol(ProtocolError::UnknownControlFrameType(i))) @@ -498,22 +574,37 @@ impl WebSocketContext { let fin = frame.header().is_final; match data { OpData::Continue => { - if let Some(ref mut msg) = self.incomplete { - msg.extend(frame.into_data(), self.config.max_message_size)?; - } else { + if self.incomplete.is_some() && is_compressed { return Err(Error::Protocol( - ProtocolError::UnexpectedContinueFrame, + ProtocolError::CompressedContinueFrame, )); } - if fin { - Ok(Some(self.incomplete.take().unwrap().complete()?)) + + if let Some(ref mut msg) = self.incomplete { + let data = if msg.compressed() { + // `msg.compressed` is only set when compression is enabled so it's safe to unwrap + self.pmce + .as_mut() + .unwrap() + .decompress(frame.into_data(), fin)? + } else { + frame.into_data() + }; + msg.extend(data, self.config.max_message_size)?; + if fin { + Ok(Some(self.incomplete.take().unwrap().complete()?)) + } else { + Ok(None) + } } else { - Ok(None) + Err(Error::Protocol(ProtocolError::UnexpectedContinueFrame)) } } + c if self.incomplete.is_some() => { Err(Error::Protocol(ProtocolError::ExpectedFragment(c))) } + OpData::Text | OpData::Binary => { let msg = { let message_type = match data { @@ -521,8 +612,16 @@ impl WebSocketContext { OpData::Binary => IncompleteMessageType::Binary, _ => panic!("Bug: message is not text nor binary"), }; - let mut m = IncompleteMessage::new(message_type); - m.extend(frame.into_data(), self.config.max_message_size)?; + let mut m = IncompleteMessage::new(message_type, is_compressed); + let data = if is_compressed { + self.pmce + .as_mut() + .unwrap() + .decompress(frame.into_data(), fin)? + } else { + frame.into_data() + }; + m.extend(data, self.config.max_message_size)?; m }; if fin {