summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlijia <[email protected]>2024-10-25 15:30:31 +0800
committerlijia <[email protected]>2024-11-08 11:52:03 +0800
commita01ac0e7274854a16ce125a059b3d2ab5fb7d7cf (patch)
tree571788768c273dec1f7db0a4744ecbe505259a37
parentd0a868591470a4a9d71a65a5d540058e72c8d92c (diff)
quicv2.0 rebase onto stellar develop-2.0dev-quic-v2.0
-rw-r--r--conf/stellar.toml4
-rw-r--r--decoders/CMakeLists.txt3
-rw-r--r--decoders/quic/CMakeLists.txt9
-rw-r--r--decoders/quic/quic_decoder.c758
-rw-r--r--decoders/quic/quic_decoder.h306
-rw-r--r--decoders/quic/quic_deprotection.c1129
-rw-r--r--decoders/quic/quic_deprotection.h111
-rw-r--r--decoders/quic/quic_module.c97
-rw-r--r--decoders/quic/quic_process.c386
-rw-r--r--decoders/quic/quic_process.h116
-rw-r--r--decoders/quic/quic_util.h28
-rw-r--r--decoders/quic/version.map10
-rw-r--r--include/stellar/quic.h26
-rw-r--r--infra/CMakeLists.txt2
-rw-r--r--infra/version.map4
-rw-r--r--test/CMakeLists.txt3
-rw-r--r--test/decoders/quic/CMakeLists.txt76
-rw-r--r--test/decoders/quic/gtest_quic_main.cpp97
-rw-r--r--test/decoders/quic/gtest_quic_module.cpp122
-rw-r--r--test/decoders/quic/gtest_quic_unit.cpp108
-rw-r--r--test/decoders/quic/pcap/airport/Old-Airport-quic.pcapbin0 -> 17503969 bytes
-rw-r--r--test/decoders/quic/pcap/airport/quic_result.json111
-rw-r--r--test/decoders/quic/pcap/gquic/23/1-gquic-023-85.117.117.169.47762-173.194.73.95.443.pcapbin0 -> 1647 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/23/2-gquic-023-85.117.123.193.19270-64.233.165.95.443.pcapbin0 -> 3142 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/23/quic_result.json15
-rw-r--r--test/decoders/quic/pcap/gquic/25/1-gquic-025-85.117.113.98.4340-74.125.131.95.443.pcapbin0 -> 3142 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/25/2-gquic-025-90.143.184.225.63062-173.194.222.95.443.pcapbin0 -> 1647 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/25/quic_result.json12
-rw-r--r--test/decoders/quic/pcap/gquic/33/1-gquic-033-90.143.189.5.8026-173.194.188.40.443.pcapbin0 -> 3143 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/33/quic_result.json9
-rw-r--r--test/decoders/quic/pcap/gquic/34/1-gquic-034-85.117.125.8.21243-173.194.73.102.443.pcapbin0 -> 3141 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/34/quic_result.json9
-rw-r--r--test/decoders/quic/pcap/gquic/35/1-gquic-035-redirector.googlevideo.com-85.117.122.194.32370-173.194.220.138.443.pcapbin0 -> 7537 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/35/2-gquic-035-redirector.googlevideo.com-85.117.122.21.21396-173.194.220.138.443.pcapbin0 -> 9489 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/35/quic_result.json18
-rw-r--r--test/decoders/quic/pcap/gquic/37/1-gquic-037-10.32.121.249.33765-64.233.161.95.443.pcapbin0 -> 1996 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/37/2-gquic-037-10.35.127.134.42356-64.233.165.139.443.pcapbin0 -> 1996 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/37/3-gquic-037-185.57.74.232.43276-173.194.44.78.443.pcapbin0 -> 1794 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/37/4-gquic-037-185.57.74.32.52134-74.125.232.249.443.pcapbin0 -> 1794 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/37/5-gquic-037-195.162.27.132.31404-217.76.77.81.443.pcapbin0 -> 1790 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/37/6-gquic-037-195.162.27.132.31405-217.76.77.81.443.pcapbin0 -> 1790 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/37/7-gquic-037-85.117.112.160.21969-64.233.165.95.443.pcapbin0 -> 1794 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/37/8-gquic-037-85.117.113.62.29644-173.194.73.95.443.pcapbin0 -> 1794 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/37/quic_result.json58
-rw-r--r--test/decoders/quic/pcap/gquic/39/1-gquic-039-redirector.googlevideo.com-85.117.119.45.22495-173.194.73.101.443.pcapbin0 -> 10833 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/39/quic_result.json13
-rw-r--r--test/decoders/quic/pcap/gquic/41/1-gquic-041-90.143.180.56.28496-64.233.165.113.443.pcapbin0 -> 3205 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/41/2-gquic-041-90.143.189.30.53357-64.233.165.95.443.pcapbin0 -> 1710 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/41/quic_result.json15
-rw-r--r--test/decoders/quic/pcap/gquic/43/1-gquic-043-client.weixin.qq.com-112.43.145.231.18699-112.46.25.216.443.pcapbin0 -> 4256 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/43/2-gquic-043-without-sni-192.168.50.26.55209-34.102.215.99.443.pcapbin0 -> 4158 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/43/3-gquic-043-without-sni-192.168.50.26.60851-34.102.215.99.443.pcapbin0 -> 12071 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/43/quic_result.json23
-rw-r--r--test/decoders/quic/pcap/gquic/44/2-gquic-044-185.57.75.21.44739-173.194.73.132.443.pcapbin0 -> 7391 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/44/3-gquic-044-212.154.234.46.62716-74.125.131.156.443.pcapbin0 -> 22034 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/44/4-gquic-044-2.135.246.186.56653-173.194.113.166.443.pcapbin0 -> 9930 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/44/quic_result.json23
-rw-r--r--test/decoders/quic/pcap/gquic/46/1-gquic-046-36.142.158.169.16385-36.189.11.71.443.pcapbin0 -> 704270 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/46/2-gquic-046-pagead2.googlesyndication.com-172.16.30.79.65003-203.208.50.45.443.pcapbin0 -> 37555 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/46/quic_result.json16
-rw-r--r--test/decoders/quic/pcap/gquic/50/1-gquic-050-i.ytimg.com-172.20.9.135.65045-64.233.162.119.443.pcapbin0 -> 2050587 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/50/2-gquic-050-www.google.com-172.20.9.135.61564-173.194.221.103.443.pcapbin0 -> 24888 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/50/3-gquic-050-www.youtube.com-172.20.9.135.49347-64.233.165.93.443.pcapbin0 -> 2087533 bytes
-rw-r--r--test/decoders/quic/pcap/gquic/50/quic_result.json23
-rw-r--r--test/decoders/quic/pcap/iquic/29/1-iquic-29-192.168.50.29.61891-31.13.77.35.443.pcapbin0 -> 73949 bytes
-rw-r--r--test/decoders/quic/pcap/iquic/29/2-iquic-29-223.104.233.102.13650-203.208.40.98.443.pcapbin0 -> 41934 bytes
-rw-r--r--test/decoders/quic/pcap/iquic/29/3-iquic-29-192.168.50.33.57220-114.250.70.38.443.pcapbin0 -> 5489 bytes
-rw-r--r--test/decoders/quic/pcap/iquic/29/quic_result.json21
-rw-r--r--test/decoders/quic/pcap/mvfst/01/1-mvfst-01-i.instagram.com-192.168.60.9.55659-69.171.250.63.443.pcapbin0 -> 49232 bytes
-rw-r--r--test/decoders/quic/pcap/mvfst/01/quic_result.json8
-rw-r--r--test/decoders/quic/pcap/mvfst/02/1-mvfst-02-192.168.137.141.50006-31.13.77.17.443.pcapbin0 -> 777583 bytes
-rw-r--r--test/decoders/quic/pcap/mvfst/02/2-mvfst-02-192.168.137.141.63314-31.13.77.10.443.pcapbin0 -> 21334 bytes
-rw-r--r--test/decoders/quic/pcap/mvfst/02/3-mvfst-02-192.168.137.141.60630-157.240.7.26.443.pcapbin0 -> 92026 bytes
-rw-r--r--test/decoders/quic/pcap/mvfst/02/4-mvfst-02-192.168.137.141.50293-157.240.15.13.443.pcapbin0 -> 115662 bytes
-rw-r--r--test/decoders/quic/pcap/mvfst/02/quic_result.json26
-rw-r--r--test/decoders/quic/pcap/port-8443/1-iquic-29-192.168.50.49.58445-45.77.96.66.8443.pcapbin0 -> 50179 bytes
-rw-r--r--test/decoders/quic/pcap/port-8443/quic_result.json7
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.33232-173.194.220.105.443.pcapbin0 -> 7783 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.39896-173.194.220.105.443.pcapbin0 -> 7931 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.43145-173.194.220.105.443.pcapbin0 -> 7783 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.49914-173.194.220.105.443.pcapbin0 -> 7783 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.54549-173.194.220.105.443.pcapbin0 -> 15743 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.57394-173.194.220.105.443.pcapbin0 -> 21174 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.62303-173.194.220.105.443.pcapbin0 -> 17352 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.63484-173.194.220.105.443.pcapbin0 -> 7931 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.34722-74.125.131.106.443.pcapbin0 -> 24644 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.43786-74.125.131.106.443.pcapbin0 -> 20284 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.44344-74.125.131.106.443.pcapbin0 -> 7783 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.44360-74.125.131.106.443.pcapbin0 -> 31901 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.45109-74.125.131.106.443.pcapbin0 -> 7783 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.45314-74.125.131.106.443.pcapbin0 -> 7931 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.47041-74.125.131.106.443.pcapbin0 -> 7783 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.47174-74.125.131.106.443.pcapbin0 -> 7931 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.50829-74.125.131.106.443.pcapbin0 -> 7931 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.61598-74.125.131.106.443.pcapbin0 -> 26255 bytes
-rw-r--r--test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.62585-74.125.131.106.443.pcapbin0 -> 7931 bytes
-rw-r--r--test/decoders/quic/pcap/quic_len_-2.pcapngbin0 -> 436 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-chlo-fragment/1-google-chlo-fragment-2.pcapbin0 -> 13118 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-chlo-fragment/2-google-chlo-fragment-3.pcapbin0 -> 237635 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-chlo-fragment/3-facebook-chlo-fragment-2.pcapbin0 -> 76188 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-chlo-fragment/4-googleapis.com-chlo-fragment-3.pcapbin0 -> 18729 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-chlo-fragment/5-google_quic-2.pcapbin0 -> 722043 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-chlo-fragment/quic_result.json32
-rw-r--r--test/decoders/quic/pcap/rfc9000-fragment/1-addis-quic-sni-not-parsed-filtered.pcapbin0 -> 1332207 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-fragment/2-quic-no-parse-sni-RFC9000-192.168.8.106.53736-142.250.185.36.443-6.pcapbin0 -> 1308172 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-fragment/3-quic-no-parse-sni-RFC9000-63821-443-192.168.8.106-142.250.185.36.pcapbin0 -> 5877 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-fragment/quic_result.json37
-rw-r--r--test/decoders/quic/pcap/rfc9000-special/1-quic-rc9000-no-parse-UA-192.168.8.110.49832-172.217.18.150.443.pcapbin0 -> 5127842 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-special/2-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.50339-172.217.169.227.443.pcapbin0 -> 8824 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-special/3-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.52455-213.55.110.12.443.pcapbin0 -> 2499594 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-special/4-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.64550-142.250.185.36.443.pcapbin0 -> 5920 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-special/5-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.65140-213.55.110.13.443.pcapbin0 -> 1215697 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000-special/quic_result.json37
-rw-r--r--test/decoders/quic/pcap/rfc9000/1-ietf-rfc9000-192.168.60.32.59699-64.233.164.84.443.pcapbin0 -> 18421 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000/2-ietf-rfc9000-124.88.191.113.39716-114.250.66.33-443.pcapbin0 -> 10619 bytes
-rw-r--r--test/decoders/quic/pcap/rfc9000/quic_result.json15
-rw-r--r--test/decoders/quic/pcap/special/1-no-parse-sni-draft-29-10.83.31.23.37645-142.250.180.42.443.pcapbin0 -> 10262 bytes
-rw-r--r--test/decoders/quic/pcap/special/1-no-parse-sni-draft-29.37645.pcapbin0 -> 10262 bytes
-rw-r--r--test/decoders/quic/pcap/special/2-no-parse-sni-rfc9000-192.168.1.6.61269-142.250.180.42.443.pcapbin0 -> 2173 bytes
-rw-r--r--test/decoders/quic/pcap/special/2-no-parse-sni-rfc9000.61269.pcapbin0 -> 2173 bytes
-rw-r--r--test/decoders/quic/pcap/special/3-no-parse-sni-gquic-43-10.129.24.130.52558-213.55.110.12.443.pcapbin0 -> 75539 bytes
-rw-r--r--test/decoders/quic/pcap/special/4-no-sni-rfc9000-197.156.101.97.12388-142.250.185.46.443.pcapbin0 -> 15656 bytes
-rw-r--r--test/decoders/quic/pcap/special/quic_result.json26
-rw-r--r--test/decoders/quic/pcap/tquic/1-tquic-51-195.12.120.14.41803-173.194.222.101.443.pcapbin0 -> 38915 bytes
-rw-r--r--test/decoders/quic/pcap/tquic/2-tquic-51-195.12.120.14.39526-64.233.165.113.443.pcapbin0 -> 240485 bytes
-rw-r--r--test/decoders/quic/pcap/tquic/3-tquic-51-195.12.120.14.41747-173.194.222.138.443.pcapbin0 -> 24277 bytes
-rw-r--r--test/decoders/quic/pcap/tquic/4-tquic-51-195.12.120.14.59012-173.194.222.138.443.pcapbin0 -> 18390 bytes
-rw-r--r--test/decoders/quic/pcap/tquic/quic_result.json30
-rw-r--r--test/decoders/quic/quic_spec.toml12
-rw-r--r--vendors/CMakeLists.txt25
-rw-r--r--vendors/openssl-1.1.1l.tar.gzbin0 -> 9834044 bytes
131 files changed, 4013 insertions, 3 deletions
diff --git a/conf/stellar.toml b/conf/stellar.toml
index fdd7ba9..5f90320 100644
--- a/conf/stellar.toml
+++ b/conf/stellar.toml
@@ -64,6 +64,10 @@
output = "both" # stderr, file, both
file = "log/stellar.log"
level = "INFO" # TRACE, DEBUG, INFO, WARN, ERROR, FATAL
+
+[quic]
+ chlo_frag_max_size = 4096
+ parse_packet_max = 8
[monitor]
listen_port = 80
diff --git a/decoders/CMakeLists.txt b/decoders/CMakeLists.txt
index 7946822..6c6a42f 100644
--- a/decoders/CMakeLists.txt
+++ b/decoders/CMakeLists.txt
@@ -2,4 +2,5 @@ add_subdirectory(lpi_plus)
#add_subdirectory(http)
#add_subdirectory(socks)
#add_subdirectory(stratum)
-#add_subdirectory(session_flags) \ No newline at end of file
+#add_subdirectory(session_flags)
+add_subdirectory(quic)
diff --git a/decoders/quic/CMakeLists.txt b/decoders/quic/CMakeLists.txt
new file mode 100644
index 0000000..9636387
--- /dev/null
+++ b/decoders/quic/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_definitions(-fPIC)
+file(GLOB QUIC_SRC "*.c")
+add_library(quic ${QUIC_SRC})
+add_definitions ("-Wno-error=implicit-fallthrough")
+target_include_directories(quic PUBLIC ${CMAKE_SOURCE_DIR}/deps/)
+target_include_directories(quic PUBLIC ${CMAKE_SOURCE_DIR}/include/)
+set_target_properties(quic PROPERTIES LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/version.map")
+set_target_properties(quic PROPERTIES PREFIX "")
+target_link_libraries(quic toml openssl-crypto-static openssl-ssl-static) \ No newline at end of file
diff --git a/decoders/quic/quic_decoder.c b/decoders/quic/quic_decoder.c
new file mode 100644
index 0000000..2e3c765
--- /dev/null
+++ b/decoders/quic/quic_decoder.c
@@ -0,0 +1,758 @@
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include "quic_util.h"
+#include "quic_decoder.h"
+
+struct quic_client_hello_msg_hdr
+{
+ uint8_t handshake_type;
+ uint8_t client_hello_len[3];
+ uint16_t tls_version;
+ uint8_t random[32];
+};
+
+static int gquic_pkn_bit2length(unsigned char bit_value)
+{
+ switch (bit_value)
+ {
+ case 0x30:
+ return 6;
+ case 0x20:
+ return 4;
+ case 0x10:
+ return 2;
+ default:
+ return 1;
+ }
+ return 1;
+}
+
+static int copy_extension_tag(const unsigned char *tag_start_pos, int tag_len, char **out, size_t *out_len)
+{
+ if (tag_start_pos != NULL && tag_len > 0)
+ {
+ FREE(*out);
+ *out = CALLOC(1, tag_len + 1);
+ memcpy((void *)*out, tag_start_pos, tag_len);
+ *out_len = tag_len;
+ return tag_len;
+ }
+ return 0;
+}
+
+// Source: https://wise2.ipac.caltech.edu/staff/slw/docs/html/varint_8h_source.html
+static int msb2_varint_decode(const unsigned char *buf, long *out)
+{
+ unsigned long val = buf[0] & 0x3f;
+ unsigned int nfollow = 1 << (buf[0] >> 6);
+ switch (nfollow - 1)
+ {
+ case 7:
+ val = (val << 8) | buf[nfollow - 7]; /*fail through*/
+ case 6:
+ val = (val << 8) | buf[nfollow - 6]; /*fail through*/
+ case 5:
+ val = (val << 8) | buf[nfollow - 5]; /*fail through*/
+ case 4:
+ val = (val << 8) | buf[nfollow - 4]; /*fail through*/
+ case 3:
+ val = (val << 8) | buf[nfollow - 3]; /*fail through*/
+ case 2:
+ val = (val << 8) | buf[nfollow - 2]; /*fail through*/
+ case 1:
+ val = (val << 8) | buf[nfollow - 1];
+ case 0:
+ break;
+ }
+ *out = val;
+ return nfollow;
+}
+
+/*
+
+//https://docs.google.com/document/d/1FcpCJGTDEMblAs-Bm5TYuqhHyUqeWpqrItw2vkMFsdY/edit
+
+Long Header (used for packets that are sent prior to the completion of version negotiation and establishment of 1-RTT keys):
++-+-+-+-+-+-+-+-+
+|1|1|T|T|R|R|P|P|
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Version (32) |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|DCIL(4)|SCIL(4)|
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Destination Connection ID (0/64) ...
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Source Connection ID (0/64) ...
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Packet Number (8/16/24/32) ...
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+*/
+
+static int join_client_hello_frames(const unsigned char *payload, int payload_len, unsigned char *join_payload, int *join_payload_len)
+{
+ int joined_length = 0;
+ int payload_offset = 0;
+ long frame_type = 0, frame_offset = 0, frame_length = 0;
+
+ for (; payload_offset < payload_len;)
+ {
+ frame_type = payload[payload_offset++]; // Frame Type=1
+ if (frame_type == IQUIC_FRAME_PADDING || frame_type == IQUIC_FRAME_PING)
+ {
+ continue;
+ }
+
+ payload_offset += msb2_varint_decode((const unsigned char *)(payload + payload_offset), &frame_offset);
+ payload_offset += msb2_varint_decode((const unsigned char *)(payload + payload_offset), &frame_length);
+
+ if (joined_length + frame_length > (*join_payload_len) || frame_offset < 0 || frame_offset + frame_length > payload_len)
+ {
+ return -1;
+ }
+ memcpy(join_payload + frame_offset, payload + payload_offset, frame_length);
+ joined_length += frame_length;
+ payload_offset += frame_length;
+ }
+ (*join_payload_len) = joined_length;
+ return joined_length;
+}
+
+static enum QUIC_VERSION_T parse_gquic_version_44to48_header(enum QUIC_VERSION_T quic_version, const char *payload, int payload_len UNUSED, int *payload_offset)
+{
+ unsigned pkn_length = 0;
+ unsigned char client_CID_len = 0;
+ unsigned char server_CID_len = 0;
+ unsigned char public_flags = payload[*payload_offset];
+ *payload_offset += 1; // skip public flags
+
+ *payload_offset += sizeof(int); // skip version
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ if ((payload[*payload_offset]) & GQUIC_VERSION_44to48_CID_MASK)
+ {
+ client_CID_len = (payload[*payload_offset] & GQUIC_VERSION_44to48_CID_MASK) + 3;
+ }
+ if ((payload[*payload_offset] >> 4) & GQUIC_VERSION_44to48_CID_MASK)
+ {
+ server_CID_len = ((payload[*payload_offset] >> 4) & GQUIC_VERSION_44to48_CID_MASK) + 3;
+ }
+
+ *payload_offset += 1; // both connection_id length
+ *payload_offset += server_CID_len; // Destination connection id length
+ *payload_offset += client_CID_len; // source connection id length
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ pkn_length = (public_flags & GQUIC_VERSION_44to48_PKN_LEN_MASK) + 1;
+ *payload_offset += pkn_length;
+
+ *payload_offset += 12; // message authentication hash
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ return quic_version;
+}
+
+int parse_special_frame_stream(struct quic_info *quic_info, const unsigned char *payload, int payload_len)
+{
+ int tag_num = 0;
+ int payload_offset = 0;
+ unsigned int message_tag;
+ int pass_tag_num = 0;
+ int ext_tag_type = 0;
+ int tag_value_start_offset = 0;
+ int one_tag_len = 0;
+ int parse_result = PARSE_RESULT_VERSION;
+ int one_tag_offset_end = 0, pre_one_tag_offset_end = 0;
+
+ if (payload_len - payload_offset <= 8)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+
+ switch (quic_info->version)
+ {
+ case GQUIC_VERSION_Q041:
+ payload_offset += 1; // unknown
+ case GQUIC_VERSION_Q044:
+ message_tag = (unsigned int)ntohl(*(unsigned int *)(payload + payload_offset));
+ payload_offset += 4;
+
+ tag_num = *(int *)(payload + payload_offset);
+ payload_offset += 4; // tag_num
+ break;
+ default:
+ message_tag = (unsigned int)ntohl(*(unsigned int *)(payload + payload_offset));
+ payload_offset += 4;
+
+ tag_num = *(unsigned short *)(payload + payload_offset);
+ payload_offset += 2; // tag_num
+ payload_offset += 2; // padding
+ break;
+ }
+
+ if (message_tag != CHLO || tag_num > 64 || tag_num <= 0)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+
+ tag_value_start_offset = payload_offset + tag_num * 4 * 2; // skip length of type and offset, type(offset)=szieof(int)
+
+ while (tag_num > pass_tag_num)
+ {
+ ext_tag_type = ntohl(*(unsigned int *)(payload + payload_offset));
+ payload_offset += sizeof(int);
+
+ one_tag_offset_end = *(unsigned int *)(payload + payload_offset);
+ payload_offset += sizeof(int);
+
+ one_tag_len = one_tag_offset_end - pre_one_tag_offset_end;
+ if (one_tag_len <= 0 || (one_tag_offset_end >= payload_len) || (one_tag_len + tag_value_start_offset) > payload_len)
+ {
+ break;
+ }
+
+ switch (ext_tag_type)
+ {
+ case TAG_UAID:
+ copy_extension_tag(payload + tag_value_start_offset, one_tag_len, &quic_info->chlo.user_agent, &quic_info->chlo.user_agent_len);
+ parse_result = PARSE_RESULT_CLIENT_HELLO;
+ break;
+ case TAG_SNI:
+ copy_extension_tag(payload + tag_value_start_offset, one_tag_len, &quic_info->chlo.sni, &quic_info->chlo.sni_len);
+ parse_result = PARSE_RESULT_CLIENT_HELLO;
+ break;
+ default:
+ break;
+ }
+
+ pass_tag_num++;
+ tag_value_start_offset += one_tag_len;
+ // total_tag_len += one_tag_len;
+ pre_one_tag_offset_end = one_tag_offset_end;
+ }
+ return parse_result;
+}
+
+int parse_quic_transport_parameter(struct quic_info *quic_info, const unsigned char *quic_para, int quic_para_len)
+{
+ int one_para_length = 0;
+ int para_offset = 0;
+ long one_para_type = 0;
+
+ while (quic_para_len > para_offset)
+ {
+ para_offset += msb2_varint_decode((const unsigned char *)(quic_para + para_offset), &one_para_type);
+ switch (one_para_type)
+ {
+ case EXT_QUIC_PARAM_USER_AGENT: // 2021-10-20 deprecated
+ one_para_length = quic_para[para_offset++]; // length=1
+ if (one_para_length + para_offset > quic_para_len)
+ {
+ return 0;
+ }
+ para_offset += copy_extension_tag(quic_para + para_offset, one_para_length, &quic_info->chlo.user_agent, &quic_info->chlo.user_agent_len);
+ return 1;
+ default:
+ one_para_length = (int)(quic_para[para_offset++]); // length=1
+ if (one_para_length < 0 || one_para_length > quic_para_len)
+ {
+ break;
+ }
+ para_offset += one_para_length;
+ break;
+ }
+ }
+ return 0;
+}
+
+int parse_extension_server_name(struct quic_info *quic_info, const unsigned char *ext_server_name, int ext_server_name_length)
+{
+ unsigned short sni_type = 0;
+ unsigned short sni_length = 0;
+ unsigned short extension_offset = 0;
+ unsigned short server_name_list_len = 0;
+
+ server_name_list_len = ntohs(*(unsigned short *)(ext_server_name + extension_offset)); // Server Name List length
+ if (server_name_list_len == 0 || server_name_list_len > ext_server_name_length)
+ {
+ return 0;
+ }
+ extension_offset += 2; // Server Name List length
+
+ sni_type = ext_server_name[extension_offset++]; // Server Name type
+ if (sni_type != EXTENSION_SERVER_NAME)
+ {
+ return 0;
+ }
+
+ sni_length = ntohs(*(unsigned short *)(ext_server_name + extension_offset)); // Server Name length
+ if (sni_length == 0 || sni_length > ext_server_name_length)
+ {
+ return 0;
+ }
+ extension_offset += 2;
+ copy_extension_tag(ext_server_name + extension_offset, sni_length, &quic_info->chlo.sni, &quic_info->chlo.sni_len);
+ return 1;
+}
+
+int parse_tls_client_hello(struct quic_info *quic_info, const unsigned char *payload, int payload_len)
+{
+ int ret = 0, skip_len = 0;
+ int payload_offset = 0;
+ int extension_offset = 0;
+ const unsigned char *extension_start_pos = NULL;
+ int parse_result = PARSE_RESULT_VERSION;
+ unsigned short one_ext_type = 0, one_ext_len = 0, extension_total_len = 0;
+
+ if (payload_len - payload_offset <= (int)sizeof(struct quic_client_hello_msg_hdr))
+ {
+ return PARSE_RESULT_VERSION;
+ }
+
+ // handshake type(1), client hello length(3), ssl_version(2), Random(32)
+ payload_offset += sizeof(struct quic_client_hello_msg_hdr);
+
+ skip_len = payload[payload_offset++]; // Session ID length
+ if (payload_len - payload_offset <= skip_len)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+ payload_offset += skip_len;
+
+ skip_len = ntohs(*(unsigned short *)(payload + payload_offset)); // Ciper Suites length
+ if (payload_len - payload_offset <= skip_len + 2)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+ payload_offset += skip_len + 2;
+
+ skip_len = payload[payload_offset++]; // Compression Methods
+ if (payload_len - payload_offset < skip_len)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+ payload_offset += skip_len;
+
+ extension_total_len = ntohs(*(unsigned short *)(payload + payload_offset)); // Extension length
+ if (payload_len - payload_offset < extension_total_len)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+
+ payload_offset += 2;
+ extension_start_pos = payload + payload_offset;
+
+ while (extension_total_len > extension_offset)
+ {
+ one_ext_type = ntohs(*(unsigned short *)(extension_start_pos + extension_offset)); // Extension type
+ extension_offset += 2;
+
+ one_ext_len = ntohs(*(unsigned short *)(extension_start_pos + extension_offset)); // length
+ extension_offset += 2;
+
+ if (extension_total_len - extension_offset < one_ext_len)
+ {
+ break;
+ }
+
+ switch (one_ext_type)
+ {
+ case EXTENSION_SERVER_NAME:
+ ret = parse_extension_server_name(quic_info, extension_start_pos + extension_offset, one_ext_len);
+ break;
+ case EXTENSION_QUIC_PARAM_TLS_13:
+ case EXTENSION_QUIC_PARAM_TLS_33:
+ ret = parse_quic_transport_parameter(quic_info, extension_start_pos + extension_offset, one_ext_len);
+ break;
+ default:
+ break;
+ }
+
+ if (ret == 1)
+ {
+ ret = 0;
+ parse_result = PARSE_RESULT_CLIENT_HELLO;
+ }
+ extension_offset += one_ext_len;
+ }
+ return parse_result;
+}
+
+int qk_get_chlo_length(const unsigned char *payload, int payload_len UNUSED)
+{
+ const struct quic_client_hello_msg_hdr *chlo_hdr = (const struct quic_client_hello_msg_hdr *)payload;
+ int chlo_len = 0;
+ chlo_len |= ((int)chlo_hdr->client_hello_len[0] << 16);
+ chlo_len |= ((int)chlo_hdr->client_hello_len[1] << 8);
+ chlo_len |= ((int)chlo_hdr->client_hello_len[2]);
+ return chlo_len;
+}
+
+/*
+ 1: is chlo, and complete!
+ 0: is chlo, but fragment;
+ -1: not support
+*/
+int qk_chlo_is_complete(enum QUIC_VERSION_T quic_version, const unsigned char *payload, int payload_len)
+{
+ if (!((quic_version >= MVFST_VERSION_00 && quic_version <= MVFST_VERSION_0F) ||
+ (quic_version >= GQUIC_VERSION_T050 && quic_version <= GQUIC_VERSION_T059) ||
+ (quic_version >= IQUIC_VERSION_I022 && quic_version <= IQUIC_VERSION_I029) ||
+ (quic_version == IQUIC_VERSION_RFC9000)))
+ {
+ return -1;
+ }
+
+ if ((payload[0] != IQUIC_FRAME_CRYPTO) && (payload[0] != 0x08))
+ {
+ return -1;
+ }
+ int payload_offset = 0;
+ long frame_offset, frame_length;
+ payload_offset = 1; // skip frame type
+ payload_offset += msb2_varint_decode(payload + payload_offset, &frame_offset);
+ payload_offset += msb2_varint_decode(payload + payload_offset, &frame_length);
+
+ if (payload[payload_offset] != QUIC_HANDSHAKE_TYPE_CLIENTHELLO)
+ {
+ return -1;
+ }
+ int expect_len = qk_get_chlo_length(payload + payload_offset, payload_len - payload_offset);
+ if (payload_len - payload_offset >= expect_len)
+ {
+ return 1;
+ }
+ return 0;
+}
+
+int parse_quic_decrypted_payload(struct quic_info *quic_info, const unsigned char *payload, int payload_len, uint32_t chlo_frag_max_size)
+{
+ unsigned char join_payload[chlo_frag_max_size];
+ int join_payload_len = sizeof(join_payload);
+ unsigned int quic_version = quic_info->version;
+
+ if ((quic_version >= MVFST_VERSION_00 && quic_version <= MVFST_VERSION_0F) ||
+ (quic_version >= GQUIC_VERSION_T050 && quic_version <= GQUIC_VERSION_T059) ||
+ (quic_version >= IQUIC_VERSION_I022 && quic_version <= IQUIC_VERSION_I029) ||
+ (quic_version == IQUIC_VERSION_RFC9000))
+ {
+ join_payload_len = join_client_hello_frames(payload, payload_len, join_payload, &join_payload_len);
+ if (join_payload_len <= 0)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+
+ if (join_payload[0] == QUIC_HANDSHAKE_TYPE_CLIENTHELLO)
+ {
+ return parse_tls_client_hello(quic_info, join_payload, join_payload_len);
+ }
+ }
+ else // if(quic_version>=GQUIC_VERSION_Q047 && quic_version<=GQUIC_VERSION_Q059)
+ {
+ return parse_special_frame_stream(quic_info, payload + 4, payload_len - 4); // Frame type=1,offset=1,length=2
+ }
+ return PARSE_RESULT_VERSION;
+}
+
+int parse_quic_uncryption_payload(struct quic_info *quic_info, const unsigned char *payload, int payload_len)
+{
+ int stream_id_len = 0;
+ int offset_len = 0;
+ unsigned char frame_type;
+ int payload_offset = 0;
+
+ frame_type = payload[payload_offset];
+ payload_offset += 1; // skip frame_type
+
+ // https://docs.google.com/document/d/1WJvyZflAO2pq77yOLbp9NsGjC1CHetAXV8I0fQe-B_U/edit#
+ // Frame Type: The Frame Type byte is an 8-bit value containing various flags (1fdooossB):
+ if (frame_type & GQUIC_SPECIAL_FRAME_STREAM || (frame_type & 0xC0) == GQUIC_SPECIAL_FRAME_ACK)
+ {
+ stream_id_len = (frame_type & GQUIC_SPECIAL_FRAME_STREAM_ID) + 1;
+ if (payload_len - payload_offset <= stream_id_len)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+
+ payload_offset += stream_id_len; // stream ID length
+
+ if (frame_type & GQUIC_SPECIAL_FRAME_STREAM_DLEN)
+ {
+ if (payload_len - payload_offset < 2)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+ payload_offset += 2; // data length
+ }
+
+ if (frame_type & GQUIC_SPECIAL_FRAME_STREAM_OFFSET)
+ { // The next three 'ooo' bits encode the length of the Offset header field as 0, 16, 24, 32, 40, 48, 56, or 64 bits long.
+ offset_len = (((frame_type & GQUIC_SPECIAL_FRAME_STREAM_OFFSET)) >> 2) + 1;
+ if (payload_len - payload_offset <= offset_len)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+ payload_offset += offset_len; // data length
+ }
+
+ return parse_special_frame_stream(quic_info, payload + payload_offset, payload_len - payload_offset);
+ }
+ return PARSE_RESULT_VERSION;
+}
+
+/*
+//https://docs.google.com/document/d/1WJvyZflAO2pq77yOLbp9NsGjC1CHetAXV8I0fQe-B_U/edit
+
+--- src
+ 0 1 2 3 4 8
++--------+--------+--------+--------+--------+--- ---+
+| Public | Connection ID (64) ... | ->
+|Flags(8)| (optional) |
++--------+--------+--------+--------+--------+--- ---+
+
+ 9 10 11 12
++--------+--------+--------+--------+
+| QUIC Version (32) | ->
+| (optional) |
++--------+--------+--------+--------+
+
+
+ 13 14 15 16 17 18 19 20
++--------+--------+--------+--------+--------+--------+--------+--------+
+| Diversification Nonce | ->
+| (optional) |
++--------+--------+--------+--------+--------+--------+--------+--------+
+
+ 21 22 23 24 25 26 27 28
++--------+--------+--------+--------+--------+--------+--------+--------+
+| Diversification Nonce Continued | ->
+| (optional) |
++--------+--------+--------+--------+--------+--------+--------+--------+
+
+ 29 30 31 32 33 34 35 36
++--------+--------+--------+--------+--------+--------+--------+--------+
+| Diversification Nonce Continued | ->
+| (optional) |
++--------+--------+--------+--------+--------+--------+--------+--------+
+
+ 37 38 39 40 41 42 43 44
++--------+--------+--------+--------+--------+--------+--------+--------+
+| Diversification Nonce Continued | ->
+| (optional) |
++--------+--------+--------+--------+--------+--------+--------+--------+
+
+
+ 45 46 47 48 49 50
++--------+--------+--------+--------+--------+--------+
+| Packet Number (8, 16, 32, or 48) |
+| (variable length) |
++--------+--------+--------+--------+--------+--------+
+
+*/
+enum QUIC_VERSION_T identify_gquic_version0to43(const char *payload, int payload_len UNUSED, int *payload_offset)
+{
+ unsigned char pkn_length = 0;
+ unsigned char public_flags = 0;
+ enum QUIC_VERSION_T quic_version = QUIC_VERSION_UNKNOWN;
+
+ public_flags = payload[*payload_offset];
+ *payload_offset += 1;
+
+ if (!(public_flags & GQUIC_PUBLIC_FLAG_VERSION))
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+
+ /*
+ 0x08 = Indicates the full 8 byte Connection ID is present in the packet.
+ This must be set in all packets until negotiated to a different value for a given direction
+ (e.g., client may request fewer bytes of the Connection ID be presented)
+ */
+ if (public_flags & GQUIC_PUBLIC_FLAG_CID)
+ {
+ *payload_offset += 8; // CID length
+ }
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+
+ if (public_flags & GQUIC_PUBLIC_FLAG_VERSION && (*(unsigned char *)(payload + *payload_offset) == 0x51))
+ {
+ quic_version = (enum QUIC_VERSION_T)ntohl(*(unsigned int *)(payload + *payload_offset));
+ *payload_offset += sizeof(int); // skip version
+ }
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ if (quic_version < GQUIC_VERSION_Q001 || quic_version > GQUIC_VERSION_Q043)
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+
+ pkn_length = gquic_pkn_bit2length(public_flags & GQUIC_VERSION_0to43_PKN_LEN_MASK);
+ *payload_offset += pkn_length; // packet number length
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ if (public_flags == GQUIC_PUBLIC_FLAG_NONCE)
+ {
+ *payload_offset += 32; // diversification nonce
+ }
+
+ // Version 11 reduced the length of null encryption authentication tag from 16 to 12 bytes
+ if (quic_version > GQUIC_VERSION_Q010)
+ {
+ *payload_offset += 12;
+ }
+ else
+ {
+ *payload_offset += 16;
+ }
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ // Version 34 removed entropy bits from packets and ACK frames,
+ // removed private flag from packet header and changed the ACK format to specify ranges of packets acknowledged rather than missing ranges.
+ if (quic_version < GQUIC_VERSION_Q034)
+ {
+ *payload_offset += 1; // private flags
+ }
+ return quic_version;
+}
+
+enum QUIC_VERSION_T identify_quic_version(const char *payload, int payload_len, int *payload_offset)
+{
+ enum QUIC_VERSION_T quic_version = (enum QUIC_VERSION_T)ntohl(*(unsigned int *)(payload + (*payload_offset + 1)));
+ if (quic_version >= GQUIC_VERSION_Q044 && quic_version <= GQUIC_VERSION_Q048)
+ {
+ if (QUIC_VERSION_UNKNOWN == parse_gquic_version_44to48_header(quic_version, payload, payload_len, payload_offset))
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ return quic_version;
+ }
+ else if (
+ (quic_version == GQUIC_VERSION_Q099) ||
+ (quic_version == PICOQUIC_VERSION_30) ||
+ (quic_version == PQUIC_VERSION_PROX) ||
+ (quic_version == GQUIC_VERSION_T099) ||
+ (quic_version >= GQUIC_VERSION_Q049 && quic_version <= GQUIC_VERSION_Q050) ||
+ (quic_version >= GQUIC_VERSION_Q051 && quic_version <= GQUIC_VERSION_Q059) ||
+ (quic_version >= GQUIC_VERSION_T048 && quic_version <= GQUIC_VERSION_T049) ||
+ (quic_version >= GQUIC_VERSION_T050 && quic_version <= GQUIC_VERSION_T059) ||
+ (quic_version >= QUANT_VERSION_00 && quic_version <= QUANT_VERSION_FF) ||
+ (quic_version >= QUIC_GO_VERSION_00 && quic_version <= QUIC_GO_VERSION_FF) ||
+ (quic_version >= QUICLY_VERSION_00 && quic_version <= QUICLY_VERSION_FF) ||
+ (quic_version >= MSQUIC_VERSION_00 && quic_version <= MSQUIC_VERSION_0F) ||
+ (quic_version >= MOZQUIC_VERSION_00 && quic_version <= MOZQUIC_VERSION_0F) ||
+ (quic_version >= MVFST_VERSION_00 && quic_version <= MVFST_VERSION_0F) ||
+ (quic_version >= IQUIC_VERSION_I001 && quic_version <= IQUIC_VERSION_I032) ||
+ (quic_version == IQUIC_VERSION_RFC9000))
+ {
+ return quic_version;
+ }
+ return QUIC_VERSION_UNKNOWN;
+}
+
+enum QUIC_VERSION_T is_quic_protocol(const char *payload, int payload_len, int *payload_offset)
+{
+ enum QUIC_VERSION_T quic_version = QUIC_VERSION_UNKNOWN;
+ unsigned char frame_type = (unsigned char)(payload[0]);
+
+ if (payload_len <= 4)
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+
+ if (frame_type & QUIC_LONG_HEADER_MASK)
+ {
+ quic_version = identify_quic_version(payload, payload_len, payload_offset);
+ }
+ else if (frame_type < QUIC_LONG_HEADER_MASK)
+ {
+ quic_version = identify_gquic_version0to43(payload, payload_len, payload_offset);
+ }
+ else
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ return quic_version;
+}
+
+int quic_version_to_readable_string(unsigned int version, char *buff, size_t buff_len)
+{
+ assert(buff != NULL);
+ if (version >= GQUIC_VERSION_Q001 && version <= GQUIC_VERSION_Q099)
+ {
+ snprintf(buff, buff_len, "Google QUIC %02u", (((version >> 8) & 0x0000000F) * 10) + (version & 0x0000000F));
+ return 1;
+ }
+ if (version >= GQUIC_VERSION_T048 && version <= GQUIC_VERSION_T099)
+ {
+ snprintf(buff, buff_len, "Google QUIC with TLS %02u", (((version >> 8) & 0x0000000F) * 10) + (version & 0x0000000F));
+ return 1;
+ }
+ if (version == IQUIC_VERSION_RFC9000)
+ {
+ snprintf(buff, buff_len, "IETF QUIC RFC9000");
+ return 1;
+ }
+ if (version >= IQUIC_VERSION_I001 && version <= IQUIC_VERSION_I032)
+ {
+ snprintf(buff, buff_len, "IETF QUIC %02u", (((version >> 16) & 0x000000FF) * 10) + (((version >> 8) & 0x000000FF) * 10) + (version & 0x000000FF));
+ return 1;
+ }
+ if (version >= QUANT_VERSION_00 && version <= QUANT_VERSION_FF)
+ {
+ snprintf(buff, buff_len, "NetApp QUANT %02u", (version & 0x000000FF));
+ return 1;
+ }
+ if (version == PICOQUIC_VERSION_30)
+ {
+ snprintf(buff, buff_len, "Private Octopus");
+ return 1;
+ }
+ if (version == PQUIC_VERSION_PROX)
+ {
+ snprintf(buff, buff_len, "Proxied QUIC");
+ return 1;
+ }
+ if (version >= QUIC_GO_VERSION_00 && version <= QUIC_GO_VERSION_FF)
+ {
+ snprintf(buff, buff_len, "quic-go QGO %02u", (version & 0x000000FF));
+ return 1;
+ }
+ if (version >= MSQUIC_VERSION_00 && version <= MSQUIC_VERSION_0F)
+ {
+ snprintf(buff, buff_len, "Microsoft MsQuic %02u", (version & 0x0000000F));
+ return 1;
+ }
+ if (version >= MOZQUIC_VERSION_00 && version <= MOZQUIC_VERSION_0F)
+ {
+ snprintf(buff, buff_len, "Mozilla MozQuic %02u", (version & 0x0000000F));
+ return 1;
+ }
+ if (version >= MVFST_VERSION_00 && version <= MVFST_VERSION_0F)
+ {
+ snprintf(buff, buff_len, "Facebook mvfst %02u", (version & 0x0000000F));
+ return 1;
+ }
+ snprintf(buff, buff_len, "Unknown QUIC VERSION");
+ return 0;
+}
diff --git a/decoders/quic/quic_decoder.h b/decoders/quic/quic_decoder.h
new file mode 100644
index 0000000..05212da
--- /dev/null
+++ b/decoders/quic/quic_decoder.h
@@ -0,0 +1,306 @@
+#pragma once
+#include "stellar/quic.h"
+
+#define QUIC_LONG_HEADER_MASK 0x80
+
+#define GQUIC_VERSION_44to48_CID_MASK 0x0F
+#define GQUIC_VERSION_44to48_PKN_LEN_MASK 0x03
+#define GQUIC_VERSION_0to43_PKN_LEN_MASK 0x30
+
+#define GQUIC_PUBLIC_FLAG_VERSION 0x01
+#define GQUIC_PUBLIC_FLAG_RST 0x02
+#define GQUIC_PUBLIC_FLAG_NONCE 0x04
+#define GQUIC_PUBLIC_FLAG_CID 0x08
+#define GQUIC_PUBLIC_FLAG_PKT_NUM 0x30
+
+// GQIIC Frame type
+#define GQUIC_SPECIAL_FRAME_FLAG 0xE0 // Special Frame Types
+#define GQUIC_SPECIAL_FRAME_STREAM 0x80
+#define GQUIC_SPECIAL_FRAME_ACK 0x40
+#define GQUIC_SPECIAL_FRAME_CONGEST_FB 0x20
+
+#define GQUIC_SPECIAL_FRAME_STREAM_FIN 0x40 // FIN
+#define GQUIC_SPECIAL_FRAME_STREAM_DLEN 0x20 // stream length
+#define GQUIC_SPECIAL_FRAME_STREAM_OFFSET 0x1C // offset header field
+#define GQUIC_SPECIAL_FRAME_STREAM_ID 0x03 // offset header field
+
+#define GQUIC_REGULAR_FRAME_PADDING 0x00
+#define GQUIC_REGULAR_FRAME_RST_STREAM 0x01
+#define GQUIC_REGULAR_FRAME_CONNECTION_CLOSE 0x02
+#define GQUIC_REGULAR_FRAME_GOAWAY 0x03
+#define GQUIC_REGULAR_FRAME_WINDOW_UPDATE 0x04
+#define GQUIC_REGULAR_FRAME_BLOCKED 0x05
+#define GQUIC_REGULAR_FRAME_STOP_WAITING 0x06
+#define GQUIC_REGULAR_FRAME_PING 0x07
+
+// https://www.rfc-editor.org/rfc/rfc9000.html#name-frames-and-frame-types
+#define IQUIC_FRAME_PADDING 0x00
+#define IQUIC_FRAME_PING 0x01
+#define IQUIC_FRAME_ACK 0x02
+#define IQUIC_FRAME_RST_STREAM 0x03
+#define IQUIC_FRAME_STOP_WAITING 0x04
+#define IQUIC_FRAME_CRYPTO 0x06
+#define IQUIC_FRAME_NEW_TOKEN 0x07
+#define IQUIC_FRAME_STREAM 0x08
+#define IQUIC_FRAME_MAX_DATA 0x10
+#define IQUIC_FRAME_MAX_STREAM_DATA 0x11
+#define IQUIC_FRAME_MAX_STREAMS 0x12
+#define IQUIC_FRAME_DATA_BLOCKED 0x13
+#define IQUIC_FRAME_STREAM_DATA_BLOCKED 0x14
+#define IQUIC_FRAME_STREAMS_BLOCKED 0x15
+#define IQUIC_FRAME_NEW_CONNECTION_ID 0x18
+#define IQUIC_FRAME_RETIRE_CONNECTION_ID 0x19
+#define IQUIC_FRAME_PATH_CHALLENGE 0x1A
+#define IQUIC_FRAME_PATH_RESPONSE 0x1B
+#define IQUIC_FRAME_CONNECTION_CLOSE 0x1C
+#define IQUIC_FRAME_APPLICATION_CLOSE 0x1D
+#define IQUIC_FRAME_HANDSHAKE_DONE 0x1E
+
+#define QUIC_HANDSHAKE_TYPE_CLIENTHELLO 0x01
+
+// https://datatracker.ietf.org/doc/html/draft-ietf-quic-transport-27#section-12.4
+// IQIIC Frame type (GQUIC_Q046 is iQUIC 17)
+
+/**************************************************************************/
+/* Message tag */
+/**************************************************************************/
+#define CHLO 0x43484C4F
+#define SHLO 0x53484C4F
+#define REJ 0x52454A00
+#define PRST 0x50525354
+
+/**************************************************************************/
+/* Tag */
+/**************************************************************************/
+#define TAG_SNI 0x534E4900
+#define TAG_VER 0x56455200
+#define TAG_UAID 0x55414944
+
+#define EXTENSION_SERVER_NAME 0x0000
+#define EXTENSION_SUPPORT_GROUP 0x000A
+#define EXTENSION_APP_PROT_NEGO 0x0010 // application layer protocol negotiation
+#define EXTENSION_SIG_ALGORITHM 0x000D
+#define EXTENSION_KEY_SHARE 0x0033
+#define EXTENSION_PSK_EXCHANGE 0x002D
+#define EXTENSION_SUPP_SSL_VER 0x002B
+#define EXTENSION_QUIC_PARAM_TLS_33 0x0039 /* draft-ietf-quic-tls-33 */
+#define EXTENSION_QUIC_PARAM_TLS_13 0xFFA5 /* 0xffa5 draft-ietf-quic-tls-13 */
+#define EXTENSION_COMPRESS_CERT 0x001B
+#define EXTENTION_UNKNOWN 0x4469
+
+// https://www.iana.org/assignments/quic/quic.xhtml
+#define EXT_QUIC_PARAM_ORIGINAL_DST_CONN_ID 0x00
+#define EXT_QUIC_PARAM_MAX_IDLE_TIMEOUT 0x01
+#define EXT_QUIC_PARAM_STATELESS_RST_TOKEN 0x02
+#define EXT_QUIC_PARAM_MAX_UDP_PAYLOAD 0x03
+#define EXT_QUIC_PARAM_MAX_INIT_DATA 0x04
+#define EXT_QUIC_PARAM_MAX_STREAM_BIDI_LOCAL 0x05
+#define EXT_QUIC_PARAM_MAX_STREAM_BIDI_REMOTE 0x06
+#define EXT_QUIC_PARAM_MAX_STREAM_UNI 0x07
+#define EXT_QUIC_PARAM_MAX_STREAMS_BIDI 0x08
+#define EXT_QUIC_PARAM_MAX_STREAMS_UNI 0x09
+#define EXT_QUIC_PARAM_ACK_DELAY_EXPONENT 0x0A
+#define EXT_QUIC_PARAM_MAX_ACK_DELAY 0x0B
+#define EXT_QUIC_PARAM_DISABLE_ACTIVE_MIGRATION 0x0C
+#define EXT_QUIC_PARAM_PREFERRED_ADDRESS 0x0D
+#define EXT_QUIC_PARAM_ACTIVE_CONN_ID_LINIT 0x0E
+#define EXT_QUIC_PARAM_INIT_SRC_CONN_ID 0x0F
+#define EXT_QUIC_PARAM_RETRY_SRC_CONN_ID 0x10
+#define EXT_QUIC_PARAM_MAX_DATAGRAM_FRAME_SIZE 0x20
+#define EXT_QUIC_PARAM_INIT_RTT 0x3127
+#define EXT_QUIC_PARAM_GOOGLE_CONN_OPTIONS 0x3128
+#define EXT_QUIC_PARAM_USER_AGENT 0x3129 // 2021-10-20 deprecated
+#define EXT_QUIC_PARAM_QUIC_VERSION 0x4752
+
+// https://github.com/quicwg/base-drafts/wiki/QUIC-Versions
+enum QUIC_VERSION_T
+{
+ QUIC_VERSION_UNKNOWN = 0,
+ // NetApp
+ QUANT_VERSION_00 = 0x45474700,
+ QUANT_VERSION_FF = 0x454747FF,
+
+ // Private Octopus
+ PICOQUIC_VERSION_30 = 0x50435130,
+
+ // google
+ GQUIC_VERSION_Q001 = 0x51303031,
+ GQUIC_VERSION_Q002 = 0x51303032,
+ GQUIC_VERSION_Q003 = 0x51303033,
+ GQUIC_VERSION_Q004 = 0x51303034,
+ GQUIC_VERSION_Q005 = 0x51303035,
+ GQUIC_VERSION_Q006 = 0x51303036,
+ GQUIC_VERSION_Q007 = 0x51303037,
+ GQUIC_VERSION_Q008 = 0x51303038,
+ GQUIC_VERSION_Q009 = 0x51303039,
+
+ GQUIC_VERSION_Q010 = 0x51303130,
+ GQUIC_VERSION_Q011 = 0x51303131,
+ GQUIC_VERSION_Q012 = 0x51303132,
+ GQUIC_VERSION_Q013 = 0x51303133,
+ GQUIC_VERSION_Q014 = 0x51303134,
+ GQUIC_VERSION_Q015 = 0x51303135,
+ GQUIC_VERSION_Q016 = 0x51303136,
+ GQUIC_VERSION_Q017 = 0x51303137,
+ GQUIC_VERSION_Q018 = 0x51303138,
+ GQUIC_VERSION_Q019 = 0x51303139,
+
+ GQUIC_VERSION_Q020 = 0x51303230,
+ GQUIC_VERSION_Q021 = 0x51303231,
+ GQUIC_VERSION_Q022 = 0x51303232,
+ GQUIC_VERSION_Q023 = 0x51303233,
+ GQUIC_VERSION_Q024 = 0x51303234,
+ GQUIC_VERSION_Q025 = 0x51303235,
+ GQUIC_VERSION_Q026 = 0x51303236,
+ GQUIC_VERSION_Q027 = 0x51303237,
+ GQUIC_VERSION_Q028 = 0x51303238,
+ GQUIC_VERSION_Q029 = 0x51303239,
+
+ GQUIC_VERSION_Q030 = 0x51303330,
+ GQUIC_VERSION_Q031 = 0x51303331,
+ GQUIC_VERSION_Q032 = 0x51303332,
+ GQUIC_VERSION_Q033 = 0x51303333,
+ GQUIC_VERSION_Q034 = 0x51303334,
+ GQUIC_VERSION_Q035 = 0x51303335,
+ GQUIC_VERSION_Q036 = 0x51303336,
+ GQUIC_VERSION_Q037 = 0x51303337,
+ GQUIC_VERSION_Q038 = 0x51303338,
+ GQUIC_VERSION_Q039 = 0x51303339,
+
+ GQUIC_VERSION_Q040 = 0x51303430,
+ GQUIC_VERSION_Q041 = 0x51303431,
+ GQUIC_VERSION_Q042 = 0x51303432,
+ GQUIC_VERSION_Q043 = 0x51303433,
+ GQUIC_VERSION_Q044 = 0x51303434,
+ GQUIC_VERSION_Q045 = 0x51303435,
+ GQUIC_VERSION_Q046 = 0x51303436,
+ GQUIC_VERSION_Q047 = 0x51303437,
+ GQUIC_VERSION_Q048 = 0x51303438,
+ GQUIC_VERSION_Q049 = 0x51303439,
+
+ GQUIC_VERSION_Q050 = 0x51303530,
+ GQUIC_VERSION_Q051 = 0x51303531,
+ GQUIC_VERSION_Q052 = 0x51303532,
+ GQUIC_VERSION_Q053 = 0x51303533,
+ GQUIC_VERSION_Q054 = 0x51303534,
+ GQUIC_VERSION_Q055 = 0x51303535,
+ GQUIC_VERSION_Q056 = 0x51303536,
+ GQUIC_VERSION_Q057 = 0x51303537,
+ GQUIC_VERSION_Q058 = 0x51303538,
+ GQUIC_VERSION_Q059 = 0x51303539,
+
+ GQUIC_VERSION_Q099 = 0x51303939,
+
+ // Google QUIC with TLS 48 - 49 (T048 - T049)
+ GQUIC_VERSION_T048 = 0x54303438,
+ GQUIC_VERSION_T049 = 0x54303439,
+
+ // Google QUIC with TLS 50 - 59 (T050 - T059)
+ GQUIC_VERSION_T050 = 0x54303530,
+ GQUIC_VERSION_T051 = 0x54303531,
+ GQUIC_VERSION_T052 = 0x54303532,
+ GQUIC_VERSION_T053 = 0x54303533,
+ GQUIC_VERSION_T054 = 0x54303534,
+ GQUIC_VERSION_T055 = 0x54303535,
+ GQUIC_VERSION_T056 = 0x54303536,
+ GQUIC_VERSION_T057 = 0x54303537,
+ GQUIC_VERSION_T058 = 0x54303538,
+ GQUIC_VERSION_T059 = 0x54303539,
+
+ // Google QUIC with TLS 99 (T099)
+ GQUIC_VERSION_T099 = 0x54303939,
+
+ // Google Proxied QUIC
+ PQUIC_VERSION_PROX = 0x50524f58,
+
+ // quic-go
+ QUIC_GO_VERSION_00 = 0x51474F00,
+ QUIC_GO_VERSION_FF = 0x51474FFF,
+
+ // quicly
+ QUICLY_VERSION_00 = 0x91c17000,
+ QUICLY_VERSION_FF = 0x91c170FF,
+
+ // Microsoft
+ MSQUIC_VERSION_00 = 0xabcd0000,
+ MSQUIC_VERSION_0F = 0xabcd000F,
+
+ // Mozilla
+ MOZQUIC_VERSION_00 = 0xf123f0c0,
+ MOZQUIC_VERSION_0F = 0xf123f0cF,
+
+ // Facebook
+ MVFST_VERSION_00 = 0xfaceb000,
+ MVFST_VERSION_01 = 0xfaceb001,
+ MVFST_VERSION_02 = 0xfaceb002,
+ MVFST_VERSION_03 = 0xfaceb003,
+ MVFST_VERSION_04 = 0xfaceb004,
+ MVFST_VERSION_05 = 0xfaceb005,
+ MVFST_VERSION_06 = 0xfaceb006,
+ MVFST_VERSION_07 = 0xfaceb007,
+ MVFST_VERSION_08 = 0xfaceb008,
+ MVFST_VERSION_09 = 0xfaceb009,
+ MVFST_VERSION_0A = 0xfaceb00A,
+ MVFST_VERSION_0B = 0xfaceb00B,
+ MVFST_VERSION_0C = 0xfaceb00C,
+ MVFST_VERSION_0D = 0xfaceb00D,
+ MVFST_VERSION_0E = 0xfaceb00E,
+ MVFST_VERSION_0F = 0xfaceb00F,
+
+ // IETF
+ IQUIC_VERSION_RFC9000 = 0x00000001,
+ IQUIC_VERSION_I001 = 0xFF000001,
+ IQUIC_VERSION_I002 = 0xFF000002,
+ IQUIC_VERSION_I003 = 0xFF000003,
+ IQUIC_VERSION_I004 = 0xFF000004,
+ IQUIC_VERSION_I005 = 0xFF000005,
+ IQUIC_VERSION_I006 = 0xFF000006,
+ IQUIC_VERSION_I007 = 0xFF000007,
+ IQUIC_VERSION_I008 = 0xFF000008,
+ IQUIC_VERSION_I009 = 0xFF000009,
+ IQUIC_VERSION_I010 = 0xFF00000A,
+ IQUIC_VERSION_I011 = 0xFF00000B,
+ IQUIC_VERSION_I012 = 0xFF00000C,
+ IQUIC_VERSION_I013 = 0xFF00000D,
+ IQUIC_VERSION_I014 = 0xFF00000E,
+ IQUIC_VERSION_I015 = 0xFF00000F,
+ IQUIC_VERSION_I016 = 0xFF000010,
+ IQUIC_VERSION_I017 = 0xFF000011,
+ IQUIC_VERSION_I018 = 0xFF000012,
+ IQUIC_VERSION_I019 = 0xFF000013,
+ IQUIC_VERSION_I020 = 0xFF000014,
+ IQUIC_VERSION_I021 = 0xFF000015,
+ IQUIC_VERSION_I022 = 0xFF000016,
+ IQUIC_VERSION_I023 = 0xFF000017,
+ IQUIC_VERSION_I024 = 0xFF000018,
+ IQUIC_VERSION_I025 = 0xFF000019,
+ IQUIC_VERSION_I026 = 0xFF00001A,
+ IQUIC_VERSION_I027 = 0xFF00001B,
+ IQUIC_VERSION_I028 = 0xFF00001C,
+ IQUIC_VERSION_I029 = 0xFF00001D,
+ IQUIC_VERSION_I030 = 0xFF00001E,
+ IQUIC_VERSION_I031 = 0xFF00001F,
+ IQUIC_VERSION_I032 = 0xFF000020
+};
+
+enum PARSE_RESULT
+{
+ PARSE_RESULT_UNKNOWN,
+ PARSE_RESULT_VERSION,
+ PARSE_RESULT_CLIENT_HELLO,
+ PARSE_RESULT_PAYLOAD,
+ PARSE_RESULT_MAX
+};
+
+struct quic_info
+{
+ uint32_t version;
+ struct quic_client_hello chlo;
+ // struct quic_encrypted_payload enc_payload;
+};
+
+enum QUIC_VERSION_T is_quic_protocol(const char *payload, int payload_len, int *payload_offset);
+int qk_get_chlo_length(const unsigned char *payload, int payload_len);
+int qk_chlo_is_complete(enum QUIC_VERSION_T quic_version, const unsigned char *payload, int payload_len);
+int parse_quic_decrypted_payload(struct quic_info *quic_info, const unsigned char *payload, int payload_len,uint32_t chlo_frag_max_size);
+int parse_quic_uncryption_payload(struct quic_info *quic_info, const unsigned char *payload, int payload_len); \ No newline at end of file
diff --git a/decoders/quic/quic_deprotection.c b/decoders/quic/quic_deprotection.c
new file mode 100644
index 0000000..9bc96b4
--- /dev/null
+++ b/decoders/quic/quic_deprotection.c
@@ -0,0 +1,1129 @@
+#include "quic_deprotection.h"
+
+#include <inttypes.h>
+#include <openssl/evp.h>
+#include <openssl/kdf.h>
+#include <openssl/sha.h>
+
+#define QUIC_IV_LEN 12 /* RFC 5116, 5.1 and RFC 8439, 2.3 for all supported ciphers */
+#define QUIC_HP_LEN 5 /* RFC 9001, 5.4.1. Header Protection Application: 5-byte mask */
+
+#define QUIC_MAX_CID_LEN 20
+#define QUIC_AES_128_KEY_LEN 16
+#define QUIC_MIN_INITIAL_SIZE 1200
+#define QUIC_UNSET_PN (uint64_t) - 1
+
+/*
+ * RFC 9000, 17.2. Long Header Packets
+ * 17.3. Short Header Packets
+ * QUIC flags in first byte
+ */
+#define QUIC_PKT_LONG 0x80 /* header form */
+#define QUIC_PKT_FIXED_BIT 0x40
+#define QUIC_PKT_TYPE 0x30 /* in long packet */
+#define QUIC_PKT_KPHASE 0x04 /* in short packet */
+
+#define quic_pkt_header_is_long(flags) ((flags) & QUIC_PKT_LONG)
+#define quic_pkt_header_is_short(flags) (((flags) & QUIC_PKT_LONG) == 0)
+#define quic_pkt_hp_mask(flags) (quic_pkt_header_is_long(flags) ? 0x0F : 0x1F)
+#define quic_pkt_rb_mask(flags) (quic_pkt_header_is_long(flags) ? 0x0C : 0x18)
+
+/* Long packet types */
+#define quic_pkt_level_is_initial(flags) (((flags) & QUIC_PKT_TYPE) == 0x00)
+#define quic_pkt_level_is_zrtt(flags) (((flags) & QUIC_PKT_TYPE) == 0x10)
+#define quic_pkt_level_is_handshake(flags) (((flags) & QUIC_PKT_TYPE) == 0x20)
+
+/* MAX MIN */
+#define MAX(val1, val2) ((val1 < val2) ? (val2) : (val1))
+#define MIN(val1, val2) ((val1 > val2) ? (val2) : (val1))
+
+#define quic_pkt_level_to_name(lvl) \
+ (lvl == ssl_encryption_application) ? "application" \
+ : (lvl == ssl_encryption_initial) ? "initial" \
+ : (lvl == ssl_encryption_handshake) ? "handshake" \
+ : "early"
+
+typedef struct
+{
+ const EVP_CIPHER *c;
+ const EVP_CIPHER *hp;
+ const EVP_MD *d;
+} quic_ciphers_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// QUIC version
+///////////////////////////////////////////////////////////////////////////////
+
+#define QUIC_NVERSIONS (sizeof(quic_version_vals) / sizeof(quic_version_vals[0]))
+
+const quic_str_t quic_version_vals[] = {
+ {0x00000000, (u_char *)"Version Negotiation"},
+ {0x00000001, (u_char *)"1"},
+ /* Versions QXXX < Q050 are dissected by Wireshark as GQUIC and not as QUIC.
+ Nonetheless, some implementations report these values in "Version Negotiation"
+ packets, so decode these fields */
+ {0x51303433, (u_char *)"Google Q043"},
+ {0x51303434, (u_char *)"Google Q044"},
+ {0x51303436, (u_char *)"Google Q046"},
+ {0x51303530, (u_char *)"Google Q050"},
+ {0x54303530, (u_char *)"Google T050"},
+ {0x54303531, (u_char *)"Google T051"},
+ {0xfaceb001, (u_char *)"Facebook mvfst (draft-22)"},
+ {0xfaceb002, (u_char *)"Facebook mvfst (draft-27)"},
+ {0xfaceb00e, (u_char *)"Facebook mvfst (Experimental)"},
+ {0xff000004, (u_char *)"draft-04"},
+ {0xff000005, (u_char *)"draft-05"},
+ {0xff000006, (u_char *)"draft-06"},
+ {0xff000007, (u_char *)"draft-07"},
+ {0xff000008, (u_char *)"draft-08"},
+ {0xff000009, (u_char *)"draft-09"},
+ {0xff00000a, (u_char *)"draft-10"},
+ {0xff00000b, (u_char *)"draft-11"},
+ {0xff00000c, (u_char *)"draft-12"},
+ {0xff00000d, (u_char *)"draft-13"},
+ {0xff00000e, (u_char *)"draft-14"},
+ {0xff00000f, (u_char *)"draft-15"},
+ {0xff000010, (u_char *)"draft-16"},
+ {0xff000011, (u_char *)"draft-17"},
+ {0xff000012, (u_char *)"draft-18"},
+ {0xff000013, (u_char *)"draft-19"},
+ {0xff000014, (u_char *)"draft-20"},
+ {0xff000015, (u_char *)"draft-21"},
+ {0xff000016, (u_char *)"draft-22"},
+ {0xff000017, (u_char *)"draft-23"},
+ {0xff000018, (u_char *)"draft-24"},
+ {0xff000019, (u_char *)"draft-25"},
+ {0xff00001a, (u_char *)"draft-26"},
+ {0xff00001b, (u_char *)"draft-27"},
+ {0xff00001c, (u_char *)"draft-28"},
+ {0xff00001d, (u_char *)"draft-29"},
+ {0xff00001e, (u_char *)"draft-30"},
+ {0xff00001f, (u_char *)"draft-31"},
+ {0xff000020, (u_char *)"draft-32"},
+ {0, NULL}};
+
+static int quic_version_is_supported(uint32_t version)
+{
+ unsigned int i = 0;
+ for (i = 0; i < QUIC_NVERSIONS; i++)
+ {
+ if (quic_version_vals[i].len == version)
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+// Returns the QUIC draft version or 0 if not applicable.
+static inline uint8_t quic_draft_version(uint32_t version)
+{
+ if ((version >> 8) == 0xff0000)
+ return (uint8_t)version;
+
+ // Facebook mvfst, based on draft -22.
+ if (version == 0xfaceb001)
+ return 22;
+
+ // Facebook mvfst, based on draft -27.
+ if (version == 0xfaceb002 || version == 0xfaceb00e)
+ return 27;
+
+ // GQUIC Q050, T050 and T051: they are not really based on any drafts,
+ // but we must return a sensible value
+ if (version == 0x51303530 || version == 0x54303530 || version == 0x54303531)
+ return 27;
+
+ /*
+ * https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-15
+ * "Versions that follow the pattern 0x?a?a?a?a are reserved for use in
+ * forcing version negotiation to be exercised"
+ * It is tricky to return a correct draft version: such number is primarly
+ * used to select a proper salt (which depends on the version itself), but
+ * we don't have a real version here! Let's hope that we need to handle
+ * only latest drafts...
+ */
+ if ((version & 0x0F0F0F0F) == 0x0a0a0a0a)
+ return 29;
+
+ return 0;
+}
+
+static inline uint8_t quic_draft_is_max(uint32_t version, uint8_t max_version)
+{
+ uint8_t draft_version = quic_draft_version(version);
+ return draft_version && draft_version <= max_version;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// quic_parse_packet_header()
+///////////////////////////////////////////////////////////////////////////////
+
+static inline u_char *quic_parse_uint8(const u_char *pos, const u_char *end, uint8_t *out)
+{
+ if ((size_t)(end - pos) < 1)
+ {
+ return NULL;
+ }
+
+ *out = *pos;
+ return (u_char *)pos + 1;
+}
+
+static inline u_char *quic_parse_int(const u_char *pos, const u_char *end, uint64_t *out)
+{
+ u_char *p;
+ uint64_t value;
+ int len;
+
+ if (pos >= end)
+ {
+ return NULL;
+ }
+
+ p = (u_char *)pos;
+ len = 1 << (*p >> 6);
+
+ value = *p++ & 0x3f;
+
+ if ((size_t)(end - p) < (size_t)(len - 1))
+ {
+ return NULL;
+ }
+
+ while (--len)
+ {
+ value = (value << 8) + *p++;
+ }
+
+ *out = value;
+ return p;
+}
+
+static inline u_char *quic_parse_uint32(const u_char *pos, const u_char *end, uint32_t *out)
+{
+ if ((size_t)(end - pos) < sizeof(uint32_t))
+ {
+ return NULL;
+ }
+
+ *out = ((uint32_t)(pos)[0] << 24 | (pos)[1] << 16 | (pos)[2] << 8 | (pos)[3]);
+ return (u_char *)pos + sizeof(uint32_t);
+}
+
+static inline u_char *quic_parse_nbytes(const u_char *pos, const u_char *end, size_t len, u_char **out)
+{
+ if ((size_t)(end - pos) < len)
+ {
+ return NULL;
+ }
+
+ *out = (u_char *)pos;
+ return (u_char *)pos + len;
+}
+
+static int quic_parse_short_header(quic_dpt_t *dpt, size_t dcid_len)
+{
+ u_char *p = dpt->pos;
+ u_char *end = dpt->data + dpt->len;
+
+ if (!(dpt->flags & QUIC_PKT_FIXED_BIT))
+ {
+ LOG_WARN("QUIC fixed bit is not set");
+ return -1;
+ }
+
+ p = quic_parse_nbytes(p, end, dcid_len, &dpt->dcid.data);
+ if (p == NULL)
+ {
+ LOG_WARN("QUIC packet is too small to read dcid");
+ return -1;
+ }
+ dpt->dcid.len = dcid_len;
+
+ dpt->pos = p;
+ return 0;
+}
+
+static int quic_parse_long_header(quic_dpt_t *dpt)
+{
+ uint8_t idlen;
+
+ u_char *p = dpt->pos;
+ u_char *end = dpt->data + dpt->len;
+
+ // Parser Version
+ p = quic_parse_uint32(p, end, &dpt->version);
+ if (p == NULL)
+ {
+ LOG_WARN("QUIC packet is too small to read version");
+ return -1;
+ }
+
+ if (!(dpt->flags & QUIC_PKT_FIXED_BIT))
+ {
+ LOG_WARN("QUIC fixed bit is not set");
+ return -1;
+ }
+
+ // Parser DCID
+ p = quic_parse_uint8(p, end, &idlen);
+ if (p == NULL)
+ {
+ LOG_WARN("QUIC packet is too small to read dcid len");
+ return -1;
+ }
+ if (idlen > QUIC_MAX_CID_LEN)
+ {
+ LOG_WARN("QUIC packet dcid is too long");
+ return -1;
+ }
+ dpt->dcid.len = idlen;
+
+ p = quic_parse_nbytes(p, end, idlen, &dpt->dcid.data);
+ if (p == NULL)
+ {
+ LOG_WARN("QUIC packet is too small to read dcid");
+ return -1;
+ }
+
+ // Parser SCID
+ p = quic_parse_uint8(p, end, &idlen);
+ if (p == NULL)
+ {
+ LOG_WARN("QUIC packet is too small to read scid len");
+ return -1;
+ }
+ if (idlen > QUIC_MAX_CID_LEN)
+ {
+ LOG_WARN("QUIC packet scid is too long");
+ return -1;
+ }
+
+ dpt->scid.len = idlen;
+
+ p = quic_parse_nbytes(p, end, idlen, &dpt->scid.data);
+ if (p == NULL)
+ {
+ LOG_WARN("QUIC packet is too small to read scid");
+ return -1;
+ }
+
+ dpt->pos = p;
+ return 0;
+}
+
+static int quic_parse_long_header_v1(quic_dpt_t *dpt)
+{
+ uint64_t varint;
+
+ u_char *p = dpt->pos;
+ u_char *end = (u_char *)dpt->data + dpt->len;
+
+ // ssl_encryption_initial
+ if (quic_pkt_level_is_initial(dpt->flags))
+ {
+ dpt->level = ssl_encryption_initial;
+ if (dpt->len < QUIC_MIN_INITIAL_SIZE)
+ {
+ LOG_WARN("QUIC UDP datagram is too small for initial packet");
+ return -1;
+ }
+
+ // Parse Token
+ p = quic_parse_int(p, end, &varint);
+ if (p == NULL)
+ {
+ LOG_WARN("QUIC failed to parse token length");
+ return -1;
+ }
+ dpt->token.len = varint;
+
+ p = quic_parse_nbytes(p, end, dpt->token.len, &dpt->token.data);
+ if (p == NULL)
+ {
+ LOG_WARN("QUIC packet too small to read token data");
+ return -1;
+ }
+ }
+ // ssl_encryption_early_data
+ else if (quic_pkt_level_is_zrtt(dpt->flags))
+ {
+ dpt->level = ssl_encryption_early_data;
+ }
+ // ssl_encryption_handshake
+ else if (quic_pkt_level_is_handshake(dpt->flags))
+ {
+ dpt->level = ssl_encryption_handshake;
+ }
+ else
+ {
+ LOG_WARN("QUIC bad packet type");
+ return -1;
+ }
+
+ // Parse Packet Length
+ p = quic_parse_int(p, end, &varint);
+ if (p == NULL)
+ {
+ LOG_WARN("QUIC bad packet length");
+ return -1;
+ }
+ dpt->pkt_len = varint;
+
+ if (varint > (uint64_t)((dpt->data + dpt->len) - p))
+ {
+ LOG_WARN("QUIC truncated packet with level %s", quic_pkt_level_to_name(dpt->level));
+ return -1;
+ }
+
+ dpt->pos = p;
+ dpt->len = p + varint - dpt->data;
+
+ return 0;
+}
+
+static int quic_parse_packet_header(quic_dpt_t *dpt)
+{
+ if (quic_pkt_header_is_short(dpt->flags))
+ {
+ dpt->header_type = SHORT;
+ dpt->level = ssl_encryption_application;
+
+ // Parser DCID
+ if (quic_parse_short_header(dpt, QUIC_MAX_CID_LEN) != 0)
+ {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ dpt->header_type = LONG;
+
+ // Parser Version, DCID, SCID
+ if (quic_parse_long_header(dpt) != 0)
+ {
+ return -1;
+ }
+
+ // Check QUIC Version
+ if (!quic_version_is_supported(dpt->version))
+ {
+ LOG_WARN("QUIC version %08" PRIx32 "unsupport", dpt->version);
+ return -1;
+ }
+
+ // Parser Level, Token, Packet Length
+ if (quic_parse_long_header_v1(dpt) != 0)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// quic_keys_set_initial_secret()
+///////////////////////////////////////////////////////////////////////////////
+
+static int hkdf_expand(quic_str_t *out, const EVP_MD *digest, const quic_str_t *prk, const quic_str_t *info)
+{
+ EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+ if (pctx == NULL)
+ {
+ LOG_ERROR("EVP_PKEY_CTX_new_id() failed");
+ return -1;
+ }
+
+ if (EVP_PKEY_derive_init(pctx) <= 0)
+ {
+ LOG_ERROR("EVP_PKEY_derive_init() failed");
+ goto failed;
+ }
+
+ if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0)
+ {
+ LOG_ERROR("EVP_PKEY_CTX_hkdf_mode() failed");
+ goto failed;
+ }
+
+ if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0)
+ {
+ LOG_ERROR("EVP_PKEY_CTX_set_hkdf_md() failed");
+ goto failed;
+ }
+
+ if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk->data, prk->len) <= 0)
+ {
+ LOG_ERROR("EVP_PKEY_CTX_set1_hkdf_key() failed");
+ goto failed;
+ }
+
+ if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info->data, info->len) <= 0)
+ {
+ LOG_ERROR("EVP_PKEY_CTX_add1_hkdf_info() failed");
+ goto failed;
+ }
+
+ if (EVP_PKEY_derive(pctx, out->data, &(out->len)) <= 0)
+ {
+ LOG_ERROR("EVP_PKEY_derive() failed");
+ goto failed;
+ }
+
+ EVP_PKEY_CTX_free(pctx);
+ return 0;
+
+failed:
+
+ EVP_PKEY_CTX_free(pctx);
+ return -1;
+}
+
+static int quic_hkdf_expand(const EVP_MD *digest, quic_str_t *out, const quic_str_t *label, const quic_str_t *prk)
+{
+ uint8_t info_buf[20];
+ info_buf[0] = 0;
+ info_buf[1] = out->len;
+ info_buf[2] = label->len;
+
+ uint8_t *p = (u_char *)memcpy(&info_buf[3], label->data, label->len) + label->len;
+ *p = '\0';
+
+ quic_str_t info;
+ info.len = 2 + 1 + label->len + 1;
+ info.data = info_buf;
+
+ if (hkdf_expand(out, digest, prk, &info) != 0)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int hkdf_extract(quic_str_t *out, const EVP_MD *digest, const quic_str_t *secret, const quic_str_t *initial_salt)
+{
+ EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+ if (pctx == NULL)
+ {
+ LOG_ERROR("EVP_PKEY_CTX_new_id() failed");
+ return -1;
+ }
+
+ if (EVP_PKEY_derive_init(pctx) <= 0)
+ {
+ LOG_ERROR("EVP_PKEY_derive_init() failed");
+ goto failed;
+ }
+
+ if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0)
+ {
+ LOG_ERROR("EVP_PKEY_CTX_hkdf_mode() failed");
+ goto failed;
+ }
+
+ if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0)
+ {
+ LOG_ERROR("EVP_PKEY_CTX_set_hkdf_md() failed");
+ goto failed;
+ }
+
+ if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret->data, secret->len) <= 0)
+ {
+ LOG_ERROR("EVP_PKEY_CTX_set1_hkdf_key() failed");
+ goto failed;
+ }
+
+ if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, initial_salt->data, initial_salt->len) <= 0)
+ {
+ LOG_ERROR("EVP_PKEY_CTX_set1_hkdf_salt() failed");
+ goto failed;
+ }
+
+ if (EVP_PKEY_derive(pctx, out->data, &(out->len)) <= 0)
+ {
+ LOG_ERROR("EVP_PKEY_derive() failed");
+ goto failed;
+ }
+
+ EVP_PKEY_CTX_free(pctx);
+ return 0;
+
+failed:
+
+ EVP_PKEY_CTX_free(pctx);
+ return -1;
+}
+
+static int quic_keys_set_initial_secret(quic_secret_t *client_secret, const quic_str_t *dcid, uint32_t version)
+{
+ unsigned int i;
+ const quic_str_t initial_salt_v1 = quic_string(
+ "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a");
+ const quic_str_t initial_salt_draft_22 = quic_string(
+ "\x7f\xbc\xdb\x0e\x7c\x66\xbb\xe9\x19\x3a\x96\xcd\x21\x51\x9e\xbd\x7a\x02\x64\x4a");
+ const quic_str_t initial_salt_draft_23 = quic_string(
+ "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02");
+ const quic_str_t initial_salt_draft_29 = quic_string(
+ "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99");
+ const quic_str_t initial_salt_draft_q50 = quic_string(
+ "\x50\x45\x74\xEF\xD0\x66\xFE\x2F\x9D\x94\x5C\xFC\xDB\xD3\xA7\xF0\xD3\xB5\x6B\x45");
+ const quic_str_t initial_salt_draft_t50 = quic_string(
+ "\x7f\xf5\x79\xe5\xac\xd0\x72\x91\x55\x80\x30\x4c\x43\xa2\x36\x7c\x60\x48\x83\x10");
+ const quic_str_t initial_salt_draft_t51 = quic_string(
+ "\x7a\x4e\xde\xf4\xe7\xcc\xee\x5f\xa4\x50\x6c\x19\x12\x4f\xc8\xcc\xda\x6e\x03\x3d");
+
+ const quic_str_t *initial_salt;
+ if (version == 0x51303530)
+ {
+ initial_salt = &initial_salt_draft_q50;
+ }
+ else if (version == 0x54303530)
+ {
+ initial_salt = &initial_salt_draft_t50;
+ }
+ else if (version == 0x54303531)
+ {
+ initial_salt = &initial_salt_draft_t51;
+ }
+ else if (quic_draft_is_max(version, 22))
+ {
+ initial_salt = &initial_salt_draft_22;
+ }
+ else if (quic_draft_is_max(version, 28))
+ {
+ initial_salt = &initial_salt_draft_23;
+ }
+ else if (quic_draft_is_max(version, 32))
+ {
+ initial_salt = &initial_salt_draft_29;
+ }
+ else
+ {
+ initial_salt = &initial_salt_v1;
+ }
+
+ /*
+ * RFC 9001, section 5. Packet Protection
+ *
+ * Initial packets use AEAD_AES_128_GCM. The hash function
+ * for HKDF when deriving initial secrets and keys is SHA-256.
+ */
+ const EVP_MD *digest = EVP_sha256();
+
+ uint8_t is[SHA256_DIGEST_LENGTH] = {0};
+ quic_str_t initial_secret;
+ initial_secret.data = is;
+ initial_secret.len = SHA256_DIGEST_LENGTH;
+
+ // Use dcid and initial_salt get initial_secret
+ if (hkdf_extract(&initial_secret, digest, dcid, initial_salt) != 0)
+ {
+ return -1;
+ }
+
+ struct
+ {
+ quic_str_t label;
+ quic_str_t *key;
+ quic_str_t *prk;
+ } seq[] = {
+ /* labels per RFC 9001, 5.1. Packet Protection Keys */
+ {quic_string("tls13 client in"), &client_secret->secret, &initial_secret},
+ {quic_string("tls13 quic key"), &client_secret->key, &client_secret->secret},
+ {quic_string("tls13 quic iv"), &client_secret->iv, &client_secret->secret},
+ {quic_string("tls13 quic hp"), &client_secret->hp, &client_secret->secret},
+ };
+
+ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++)
+ {
+ if (quic_hkdf_expand(digest, seq[i].key, &seq[i].label, seq[i].prk) != 0)
+ {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// quic_deprotection_packet()
+///////////////////////////////////////////////////////////////////////////////
+
+static int quic_deprotection_header(const EVP_CIPHER *cipher, const quic_secret_t *secret, u_char *out, const u_char *in)
+{
+ int outlen;
+ u_char zero[QUIC_HP_LEN] = {0};
+
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL)
+ {
+ return -1;
+ }
+
+ if (EVP_EncryptInit_ex(ctx, cipher, NULL, secret->hp.data, in) != 1)
+ {
+ LOG_ERROR("EVP_EncryptInit_ex() failed");
+ goto failed;
+ }
+
+ if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, QUIC_HP_LEN))
+ {
+ LOG_ERROR("EVP_EncryptUpdate() failed");
+ goto failed;
+ }
+
+ if (!EVP_EncryptFinal_ex(ctx, out + QUIC_HP_LEN, &outlen))
+ {
+ LOG_ERROR("EVP_EncryptFinal_ex() failed");
+ goto failed;
+ }
+
+ EVP_CIPHER_CTX_free(ctx);
+ return 0;
+
+failed:
+
+ EVP_CIPHER_CTX_free(ctx);
+ return -1;
+}
+
+static uint64_t quic_deprotection_pktnum(u_char **pos, int len, const u_char *mask, uint64_t *largest_pkt_num)
+{
+ u_char *p;
+ uint64_t truncated_pn, expected_pn, candidate_pn;
+ uint64_t pn_nbits, pn_win, pn_hwin, pn_mask;
+
+ pn_nbits = MIN(len * 8, 62);
+
+ p = *pos;
+ truncated_pn = *p++ ^ *mask++;
+
+ while (--len)
+ {
+ truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++);
+ }
+
+ *pos = p;
+
+ expected_pn = *largest_pkt_num + 1;
+ pn_win = 1ULL << pn_nbits;
+ pn_hwin = pn_win / 2;
+ pn_mask = pn_win - 1;
+
+ candidate_pn = (expected_pn & ~pn_mask) | truncated_pn;
+
+ if ((int64_t)candidate_pn <= (int64_t)(expected_pn - pn_hwin) && candidate_pn < (1ULL << 62) - pn_win)
+ {
+ candidate_pn += pn_win;
+ }
+ else if (candidate_pn > expected_pn + pn_hwin && candidate_pn >= pn_win)
+ {
+ candidate_pn -= pn_win;
+ }
+
+ *largest_pkt_num = MAX((int64_t)*largest_pkt_num, (int64_t)candidate_pn);
+
+ return candidate_pn;
+}
+
+static void quic_deprotection_nonce(u_char *nonce, size_t len, uint64_t pkt_num)
+{
+ nonce[len - 4] ^= (pkt_num & 0xff000000) >> 24;
+ nonce[len - 3] ^= (pkt_num & 0x00ff0000) >> 16;
+ nonce[len - 2] ^= (pkt_num & 0x0000ff00) >> 8;
+ nonce[len - 1] ^= (pkt_num & 0x000000ff);
+}
+
+static int quic_deprotection_payload(const EVP_CIPHER *cipher, const quic_secret_t *secret, quic_str_t *out, const u_char *nonce, const quic_str_t *in, const quic_str_t *ad)
+{
+ int len;
+ u_char *tag;
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL)
+ {
+ LOG_ERROR("EVP_CIPHER_CTX_new() failed");
+ return -1;
+ }
+
+ if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1)
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ LOG_ERROR("EVP_DecryptInit_ex() failed");
+ return -1;
+ }
+
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, secret->iv.len, NULL) == 0)
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ LOG_ERROR("EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed");
+ return -1;
+ }
+
+ if (EVP_DecryptInit_ex(ctx, NULL, NULL, secret->key.data, nonce) != 1)
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ LOG_ERROR("EVP_DecryptInit_ex() failed");
+ return -1;
+ }
+
+ if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1)
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ LOG_ERROR("EVP_DecryptUpdate() failed");
+ return -1;
+ }
+
+ if (EVP_DecryptUpdate(ctx, out->data, &len, in->data, in->len - EVP_GCM_TLS_TAG_LEN) != 1)
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ LOG_ERROR("EVP_DecryptUpdate() failed");
+ return -1;
+ }
+
+ out->len = len;
+ tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN;
+
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag) == 0)
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ LOG_ERROR("EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed");
+ return -1;
+ }
+
+ if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0)
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ LOG_ERROR("EVP_DecryptFinal_ex failed");
+ return -1;
+ }
+
+ out->len += len;
+ EVP_CIPHER_CTX_free(ctx);
+
+ return 0;
+}
+
+static int quic_deprotection_packet(quic_dpt_t *dpt)
+{
+ u_char *p, *sample;
+ size_t len;
+ uint64_t pkt_num, lpn;
+ int pnl, rc, key_phase;
+ quic_str_t in, ad;
+ uint8_t nonce[QUIC_IV_LEN] = {0}, mask[QUIC_HP_LEN] = {0};
+
+ // Init ciphers, only process the quic package whose level is init, so only use AES_128_GCM_SHA256
+ quic_ciphers_t ciphers;
+ ciphers.c = EVP_aes_128_gcm();
+ ciphers.hp = EVP_aes_128_ctr();
+ ciphers.d = EVP_sha256();
+
+ // 使用 client_secret secrets
+ quic_secret_t *secret = &dpt->client_secret;
+
+ p = dpt->pos;
+ len = dpt->data + dpt->len - p;
+
+ /*
+ * RFC 9001, 5.4.2. Header Protection Sample
+ * 5.4.3. AES-Based Header Protection
+ * 5.4.4. ChaCha20-Based Header Protection
+ *
+ * the Packet Number field is assumed to be 4 bytes long
+ * AES and ChaCha20 algorithms sample 16 bytes
+ */
+
+ if (len < EVP_GCM_TLS_TAG_LEN + 4)
+ {
+ LOG_WARN("QUIC payload length %zu too small", len);
+ return -1;
+ }
+
+ sample = p + 4;
+
+ /******************************************************
+ * header protection
+ ******************************************************/
+
+ // Use the ciphers.hp algorithm and the secret.HP to encrypt the data in the sample and store it in the mask
+ if (quic_deprotection_header(ciphers.hp, secret, mask, sample) != 0)
+ {
+ return -1;
+ }
+
+ // Parse Flags After Decode
+ dpt->flags ^= mask[0] & quic_pkt_hp_mask(dpt->flags);
+
+ if (quic_pkt_header_is_short(dpt->flags))
+ {
+ key_phase = (dpt->flags & QUIC_PKT_KPHASE) != 0;
+
+ if (key_phase != dpt->key_phase)
+ {
+ // TODO
+ LOG_WARN("key need update !!!!");
+ }
+ }
+
+ lpn = dpt->largest_pkt_num;
+
+ // RFC pn_length = (packet[0] & 0x03) + 1
+ // Parser Packet Number length
+ pnl = (dpt->flags & 0x03) + 1;
+
+ // Parser Packet number
+ pkt_num = quic_deprotection_pktnum(&p, pnl, &mask[1], &lpn);
+ dpt->pkt_num = pkt_num;
+
+ /******************************************************
+ * packet protection
+ ******************************************************/
+
+ in.data = p;
+ in.len = len - pnl;
+
+ ad.len = p - dpt->data;
+ ad.data = dpt->plaintext;
+
+ memcpy(ad.data, dpt->data, ad.len);
+ ad.data[0] = dpt->flags;
+
+ do
+ {
+ ad.data[ad.len - pnl] = pkt_num >> (8 * (pnl - 1)) % 256;
+ } while (--pnl);
+
+ memcpy(nonce, secret->iv.data, secret->iv.len);
+ quic_deprotection_nonce(nonce, sizeof(nonce), pkt_num);
+
+ dpt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN;
+ dpt->payload.data = dpt->plaintext + ad.len;
+
+ // Parser payload
+ rc = quic_deprotection_payload(ciphers.c, secret, &dpt->payload, nonce, &in, &ad);
+ if (rc != 0)
+ {
+ return -1;
+ }
+
+ if (dpt->payload.len == 0)
+ {
+ /*
+ * RFC 9000, 12.4. Frames and Frame Types
+ *
+ * An endpoint MUST treat receipt of a packet containing no
+ * frames as a connection error of type PROTOCOL_VIOLATION.
+ */
+ LOG_WARN("QUIC zero-length packet");
+ return -1;
+ }
+
+ if (dpt->flags & quic_pkt_rb_mask(dpt->flags))
+ {
+ /*
+ * RFC 9000, Reserved Bits
+ *
+ * An endpoint MUST treat receipt of a packet that has
+ * a non-zero value for these bits, after removing both
+ * packet and header protection, as a connection error
+ * of type PROTOCOL_VIOLATION.
+ */
+ LOG_WARN("QUIC reserved bit set in packet");
+ return -1;
+ }
+
+ // Set Largest Packet Number
+ dpt->largest_pkt_num = lpn;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// debug
+///////////////////////////////////////////////////////////////////////////////
+
+static void quic_str_to_hex(u_char *data, size_t len)
+{
+ for (unsigned int i = 0; i < len; i++)
+ {
+ printf("%02x", data[i]);
+ }
+ printf("\n");
+}
+
+static u_char *quic_version_to_str(uint32_t version)
+{
+ unsigned int i = 0;
+ for (i = 0; i < QUIC_NVERSIONS; i++)
+ {
+ if (quic_version_vals[i].len == version)
+ {
+ return quic_version_vals[i].data;
+ }
+ }
+ return NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// PUB API
+///////////////////////////////////////////////////////////////////////////////
+
+quic_dpt_t *quic_deprotection_new(void)
+{
+ quic_dpt_t *dpt = (quic_dpt_t *)calloc(1, sizeof(quic_dpt_t));
+
+ // Allocate the space of length len + 1, instead of allocating the space of length len, mainly for the convenience of debugging printf
+ dpt->client_secret.secret.len = SHA256_DIGEST_LENGTH;
+ dpt->client_secret.secret.data = (u_char *)calloc(SHA256_DIGEST_LENGTH + 1, sizeof(u_char));
+
+ dpt->client_secret.key.len = QUIC_AES_128_KEY_LEN;
+ dpt->client_secret.key.data = (u_char *)calloc(QUIC_AES_128_KEY_LEN + 1, sizeof(u_char));
+
+ dpt->client_secret.hp.len = QUIC_AES_128_KEY_LEN;
+ dpt->client_secret.hp.data = (u_char *)calloc(QUIC_AES_128_KEY_LEN + 1, sizeof(u_char));
+
+ dpt->client_secret.iv.len = QUIC_IV_LEN;
+ dpt->client_secret.iv.data = (u_char *)calloc(QUIC_IV_LEN + 1, sizeof(u_char));
+
+ // NOTE: QUIC_MAX_UDP_PAYLOAD_SIZE is 65527(form Nginx-quice offical)
+ dpt->plaintext = (u_char *)calloc(QUIC_MAX_UDP_PAYLOAD_SIZE + 1, sizeof(u_char));
+
+ return dpt;
+}
+
+void quic_deprotection_free(quic_dpt_t *dpt)
+{
+ if (dpt == NULL)
+ {
+ return;
+ }
+
+ if (dpt->client_secret.secret.data)
+ {
+ free(dpt->client_secret.secret.data);
+ dpt->client_secret.secret.data = NULL;
+ dpt->client_secret.secret.len = 0;
+ }
+
+ if (dpt->client_secret.key.data)
+ {
+ free(dpt->client_secret.key.data);
+ dpt->client_secret.key.data = NULL;
+ dpt->client_secret.key.len = 0;
+ }
+
+ if (dpt->client_secret.hp.data)
+ {
+ free(dpt->client_secret.hp.data);
+ dpt->client_secret.hp.data = NULL;
+ dpt->client_secret.hp.len = 0;
+ }
+
+ if (dpt->client_secret.iv.data)
+ {
+ free(dpt->client_secret.iv.data);
+ dpt->client_secret.iv.data = NULL;
+ dpt->client_secret.iv.len = 0;
+ }
+
+ if (dpt->plaintext)
+ {
+ free(dpt->plaintext);
+ dpt->plaintext = NULL;
+ }
+
+ free(dpt);
+ dpt = NULL;
+}
+
+void quic_deprotection_dump(quic_dpt_t *dpt)
+{
+ printf("QUIC version : %08" PRIx32 " %s\n", dpt->version, quic_version_to_str(dpt->version));
+ printf("QUIC type : %s\n", dpt->header_type == LONG ? "long" : "short");
+ printf("QUIC level : %s\n", quic_pkt_level_to_name(dpt->level));
+ printf("QUIC flags : %x\n", dpt->flags);
+ printf("QUIC num : %" PRIu64 "\n", dpt->pkt_num);
+ printf("QUIC len : %" PRIu64 "\n", dpt->pkt_len);
+ printf("QUIC larg_num : %" PRIu64 "\n", dpt->largest_pkt_num);
+
+ printf("QUIC dcid : %zu, ", dpt->dcid.len);
+ quic_str_to_hex(dpt->dcid.data, dpt->dcid.len);
+
+ printf("QUIC scid : %zu, ", dpt->scid.len);
+ quic_str_to_hex(dpt->scid.data, dpt->scid.len);
+
+ printf("QUIC token : %zu, ", dpt->token.len);
+ quic_str_to_hex(dpt->token.data, dpt->token.len);
+
+ printf("QUIC hp : %zu, ", dpt->client_secret.hp.len);
+ quic_str_to_hex(dpt->client_secret.hp.data, dpt->client_secret.hp.len);
+
+ printf("QUIC iv : %zu, ", dpt->client_secret.iv.len);
+ quic_str_to_hex(dpt->client_secret.iv.data, dpt->client_secret.iv.len);
+
+ printf("QUIC key : %zu, ", dpt->client_secret.key.len);
+ quic_str_to_hex(dpt->client_secret.key.data, dpt->client_secret.key.len);
+
+ printf("QUIC secretn : %zu, ", dpt->client_secret.secret.len);
+ quic_str_to_hex(dpt->client_secret.secret.data, dpt->client_secret.secret.len);
+
+ printf("QUIC plaintex : %zu, ", strlen((char *)dpt->plaintext));
+ quic_str_to_hex(dpt->plaintext, strlen((char *)dpt->plaintext));
+
+ printf("QUIC payload : %zu, ", dpt->payload.len);
+ quic_str_to_hex(dpt->payload.data, dpt->payload.len);
+}
+
+int quic_deprotection(quic_dpt_t *dpt, const u_char *payload, size_t payload_len)
+{
+ dpt->data = (u_char *)payload;
+ dpt->len = payload_len;
+ dpt->pos = (u_char *)dpt->data;
+ dpt->flags = payload[0];
+ dpt->pos++;
+ dpt->largest_pkt_num = QUIC_UNSET_PN;
+
+ // Parser Level, Version, DCID, SCID, Token, Packet Length
+ if (quic_parse_packet_header(dpt) == -1)
+ {
+ return -1;
+ }
+
+ if (dpt->level != ssl_encryption_initial)
+ {
+ LOG_WARN("QUIC level %s ignoring", quic_pkt_level_to_name(dpt->level))
+ return -1;
+ }
+
+ /*
+ * 1.Get the initial_salt to be used through pkt_version
+ * 2.DCID and initial_salt generate initial_secret through SHA-256
+ * 3.IN/Key/IV/HP lable of initial_secret and client_secret generates HP/Key/IV key of client_secret
+ */
+ if (quic_keys_set_initial_secret(&dpt->client_secret, &dpt->dcid, dpt->version) == -1)
+ {
+ goto failed;
+ }
+
+ if (dpt->client_secret.key.len == 0)
+ {
+ LOG_WARN("QUIC packet no client_secret, ignoring");
+ goto failed;
+ }
+
+ if (quic_deprotection_packet(dpt) == -1)
+ {
+ goto failed;
+ }
+
+ // deprotection data: dpt->payload.data
+
+ return 0;
+
+failed:
+
+ return -1;
+} \ No newline at end of file
diff --git a/decoders/quic/quic_deprotection.h b/decoders/quic/quic_deprotection.h
new file mode 100644
index 0000000..525e398
--- /dev/null
+++ b/decoders/quic/quic_deprotection.h
@@ -0,0 +1,111 @@
+#pragma once
+#ifdef __cpluscplus
+extern "C"
+{
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <arpa/inet.h>
+
+#ifdef DEBUG_SWITCH
+
+#define LOG_DEBUG(format, ...) \
+ { \
+ fprintf(stdout, format "\n", ##__VA_ARGS__); \
+ fflush(stdout); \
+ }
+
+#define LOG_WARN(format, ...) \
+ { \
+ fprintf(stderr, format "\n", ##__VA_ARGS__); \
+ fflush(stderr); \
+ }
+
+#define LOG_ERROR(format, ...) \
+ { \
+ fprintf(stderr, format "\n", ##__VA_ARGS__); \
+ fflush(stderr); \
+ }
+
+#else
+
+#define LOG_DEBUG(format, ...)
+#define LOG_WARN(format, ...)
+#define LOG_ERROR(format, ...)
+
+#endif
+
+#define QUIC_MAX_UDP_PAYLOAD_SIZE 1460
+
+#define quic_string(str) \
+ { \
+ sizeof(str) - 1, (u_char *)str}
+
+ typedef struct
+ {
+ size_t len;
+ u_char *data;
+ } quic_str_t;
+
+ typedef struct quic_secret_s
+ {
+ quic_str_t secret;
+ quic_str_t key;
+ quic_str_t iv;
+ quic_str_t hp;
+ } quic_secret_t;
+
+ typedef enum
+ {
+ ssl_encryption_initial = 0,
+ ssl_encryption_early_data = 1,
+ ssl_encryption_handshake = 2,
+ ssl_encryption_application = 3,
+ } ssl_encryption_level_t;
+
+ typedef enum
+ {
+ LONG = 0,
+ SHORT = 1,
+ } quic_header_type;
+
+ typedef struct
+ {
+ quic_secret_t client_secret;
+ ssl_encryption_level_t level; // QUIC Packet Process Level
+ quic_header_type header_type; // QUIC Packet Header Type
+
+ uint32_t version; // QUIC Version
+ uint8_t flags; // QUIC Flags
+ u_char *data; // QUIC Packet Data
+ size_t len; // QUIC Packet Length
+ u_char *pos; // Process Ptr
+ uint64_t largest_pkt_num;
+
+ quic_str_t dcid; // QUIC DCID
+ quic_str_t scid; // QUIC SCID
+ quic_str_t token; // QUIC TOKEN
+
+ size_t pkt_len;
+ uint64_t pkt_num; // QUIC Packet Number
+ u_char *plaintext;
+ quic_str_t payload; // Decrypted data
+
+ unsigned key_phase : 1;
+ } quic_dpt_t;
+
+ quic_dpt_t *quic_deprotection_new(void);
+ void quic_deprotection_free(quic_dpt_t *dpt);
+ void quic_deprotection_dump(quic_dpt_t *dpt);
+ int quic_deprotection(quic_dpt_t *dpt, const u_char *payload, size_t payload_len);
+
+#ifdef __cpluscplus
+}
+#endif
diff --git a/decoders/quic/quic_module.c b/decoders/quic/quic_module.c
new file mode 100644
index 0000000..e564019
--- /dev/null
+++ b/decoders/quic/quic_module.c
@@ -0,0 +1,97 @@
+#include <stdio.h>
+#include <assert.h>
+#include "stellar/session.h"
+#include "stellar/module.h"
+#include "quic_util.h"
+#include "quic_process.h"
+
+#define QUIC_TOPIC_NAME_CLIENT_HELLO "QUIC_TOPIC_CLIENT_HELLO"
+#define QUIC_TOPIC_NAME_ENCRYPTED_PAYLOAD "QUIC_TOPIC_ENCRYPTED_PAYLOAD"
+
+static int quic_create_topic_nx(struct module_manager *mod_mgr, const char *topic_name)
+{
+ struct mq_schema *mq_s = module_manager_get_mq_schema(mod_mgr);
+ assert(mq_s != NULL);
+ int quic_topic_id = mq_schema_get_topic_id(mq_s, topic_name);
+ if (quic_topic_id >= 0)
+ {
+ return quic_topic_id;
+ }
+ int topic_id = mq_schema_create_topic(mq_s, topic_name, quic_on_msg_dispatch, NULL, quic_msg_free_cb, NULL);
+ return topic_id;
+}
+
+static struct fieldstat_easy *quick_get_fs4_ins(struct module_manager *mod_mgr)
+{
+ // todo: use stellar global fieldstat4 instance ��
+ struct fieldstat_easy *fs4 = fieldstat_easy_new(module_manager_get_max_thread_num(mod_mgr), "QUIC", NULL, 0);
+ fieldstat_easy_enable_auto_output(fs4, "./log/quic.fs4", 1);
+ return fs4;
+}
+
+int quic_subscribe_client_hello(struct quic *quic_env, quic_on_client_hello_callback *cb, void *args)
+{
+ assert(quic_env != NULL);
+ struct module_manager *mod_mgr = quic_env->mod_mgr_ref;
+ struct mq_schema *mq_s = module_manager_get_mq_schema(mod_mgr);
+ int quic_topi_id = quic_create_topic_nx(mod_mgr, QUIC_TOPIC_NAME_CLIENT_HELLO);
+ return mq_schema_subscribe(mq_s, quic_topi_id, (on_msg_cb_func *)((void *)cb), args);
+}
+
+void quic_exit(struct module_manager *mod_mgr, struct module *mod)
+{
+ if (mod_mgr == NULL || mod == NULL)
+ {
+ return;
+ }
+ struct quic *quic_env = (struct quic *)module_get_ctx(mod);
+ fieldstat_easy_free(quic_env->stat.fs4_ins);
+ FREE(quic_env);
+ module_free(mod);
+}
+
+struct quic *quic_module_to_quic(struct module *quic_mod)
+{
+ assert(quic_mod);
+ return (struct quic *)module_get_ctx(quic_mod);
+}
+
+struct module *quic_init(struct module_manager *mod_mgr)
+{
+ assert(mod_mgr != NULL);
+ struct quic *quic_env = (struct quic *)CALLOC(1, sizeof(struct quic));
+ quic_load_config(module_manager_get_toml_path(mod_mgr), &quic_env->config);
+ quic_env->stat.fs4_ins = quick_get_fs4_ins(mod_mgr);
+ quic_stat_init(&quic_env->stat);
+ struct module *mod = module_new(QUIC_MODULE_NAME, quic_env);
+ quic_env->mod_mgr_ref = mod_mgr;
+
+ quic_env->logger_ref = module_manager_get_logger(mod_mgr);
+ assert(quic_env->logger_ref != NULL);
+ struct module *sess_mod = module_manager_get_module(mod_mgr, SESSION_MANAGER_MODULE_NAME);
+ struct session_manager *sess_mgr = module_to_session_manager(sess_mod);
+ assert(sess_mgr != NULL);
+
+ session_manager_subscribe_udp(sess_mgr, quic_on_udp_msg_cb, quic_env);
+ quic_env->exdata_id = session_manager_new_session_exdata_index(sess_mgr, QUIC_EXDATA_NAME, quic_exdata_free_cb, quic_env);
+ quic_env->quic_topic_chlo_id = quic_create_topic_nx(mod_mgr, QUIC_TOPIC_NAME_CLIENT_HELLO);
+ if (quic_env->quic_topic_chlo_id < 0)
+ {
+ STELLAR_LOG_FATAL(quic_env->logger_ref, QUIC_MODULE_NAME, "quic create topic:%s failed", QUIC_TOPIC_NAME_CLIENT_HELLO);
+ goto fail_exit;
+ }
+ quic_env->quic_topic_encrypt_id = quic_create_topic_nx(mod_mgr, QUIC_TOPIC_NAME_ENCRYPTED_PAYLOAD);
+ if (quic_env->quic_topic_encrypt_id < 0)
+ {
+ STELLAR_LOG_FATAL(quic_env->logger_ref, QUIC_MODULE_NAME, "quic create topic:%s failed", QUIC_TOPIC_NAME_ENCRYPTED_PAYLOAD);
+ goto fail_exit;
+ }
+ STELLAR_LOG_FATAL(quic_env->logger_ref, QUIC_MODULE_NAME,
+ "quic init success, config: chlo_frag_max_size=%u, parse_packet_max=%u",
+ quic_env->config.chlo_frag_max_size, quic_env->config.parse_packet_max);
+ return mod;
+
+fail_exit:
+ quic_exit(mod_mgr, mod);
+ return NULL;
+} \ No newline at end of file
diff --git a/decoders/quic/quic_process.c b/decoders/quic/quic_process.c
new file mode 100644
index 0000000..a1ee869
--- /dev/null
+++ b/decoders/quic/quic_process.c
@@ -0,0 +1,386 @@
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include "quic_process.h"
+#include "quic_decoder.h"
+#include "quic_deprotection.h"
+#include "toml/toml.h"
+
+static void quic_set_default_config(struct quic_config *config)
+{
+ config->chlo_frag_max_size = QUIC_CHLO_FRAG_MAX_SIZE;
+ config->parse_packet_max = QUIC_PARSE_FIRST_N_PACKETS;
+}
+
+int quic_load_config(const char *file, struct quic_config *config)
+{
+ int ret = -1;
+ char errbuf[200];
+ const char *ptr;
+ FILE *fp = NULL;
+ toml_table_t *root = NULL;
+ toml_table_t *sub = NULL;
+
+ memset(config, 0, sizeof(struct quic_config));
+ quic_set_default_config(config);
+
+ fp = fopen(file, "r");
+ if (fp == NULL)
+ {
+ goto error_out;
+ }
+ root = toml_parse_file(fp, errbuf, sizeof(errbuf));
+ if (root == NULL)
+ {
+ goto error_out;
+ }
+
+ sub = toml_table_in(root, "quic");
+ if (sub == NULL)
+ {
+ goto error_out;
+ }
+
+ ptr = toml_raw_in(sub, "chlo_frag_max_size");
+ if (ptr != NULL)
+ {
+ config->chlo_frag_max_size = (uint32_t)atoi(ptr);
+ }
+ ptr = toml_raw_in(sub, "parse_packet_max");
+ if (ptr != NULL)
+ {
+ config->parse_packet_max = (uint32_t)atoi(ptr);
+ }
+error_out:
+ if (root)
+ {
+ toml_free(root);
+ }
+ if (fp)
+ {
+ fclose(fp);
+ }
+ return ret;
+}
+
+int quic_stat_init(struct quic_stat *qstat)
+{
+ qstat->fs4_session_cnt_id = fieldstat_easy_register_counter(qstat->fs4_ins, "QUIC_SESS_CNT");
+ qstat->fs4_packet_cnt_id = fieldstat_easy_register_counter(qstat->fs4_ins, "QUIC_PKT_CNT");
+ qstat->fs4_packet_size_id = fieldstat_easy_register_counter(qstat->fs4_ins, "QUIC_PKT_SIZE");
+ qstat->fs4_quic_version_id = fieldstat_easy_register_counter(qstat->fs4_ins, "QUIC_VERSION");
+ return 0;
+}
+
+void quic_stat_update_cnt(struct quic_stat *qstat, int thread_idx, enum quic_stat_type type, long long value)
+{
+ switch (type)
+ {
+ case QUIC_STAT_SESSION_CNT:
+ fieldstat_easy_counter_incrby(qstat->fs4_ins, thread_idx, qstat->fs4_session_cnt_id, NULL, 0, value);
+ break;
+ case QUIC_STAT_PACKET_CNT:
+ fieldstat_easy_counter_incrby(qstat->fs4_ins, thread_idx, qstat->fs4_packet_cnt_id, NULL, 0, value);
+ break;
+ case QUIC_STAT_PACKET_SIZE:
+ fieldstat_easy_counter_incrby(qstat->fs4_ins, thread_idx, qstat->fs4_packet_size_id, NULL, 0, value);
+ break;
+ case QUIC_STAT_VERSION:
+ fieldstat_easy_counter_incrby(qstat->fs4_ins, thread_idx, qstat->fs4_quic_version_id, NULL, 0, value);
+ break;
+ default:
+ break;
+ }
+}
+
+void quic_stat_update_version(struct quic_stat *qstat, int thread_idx, uint32_t quic_version, long long value)
+{
+ struct field version_tag = {};
+ char version_str[128] = {0};
+ quic_version_to_readable_string(quic_version, version_str, sizeof(version_str));
+ version_tag.key = "ver";
+ version_tag.type = FIELD_VALUE_CSTRING;
+ version_tag.value_str = version_str;
+ fieldstat_easy_counter_incrby(qstat->fs4_ins, thread_idx, qstat->fs4_quic_version_id, &version_tag, 1, value);
+}
+
+int quic_get_topic_id(const struct quic *quic_env, enum quic_topic_type topic_type)
+{
+ switch (topic_type)
+ {
+ case QUIC_TOPIC_CLIENT_HELLO:
+ return quic_env->quic_topic_chlo_id;
+ case QUIC_TOPIC_ENCRYPTED_PAYLOAD:
+ return quic_env->quic_topic_encrypt_id;
+ default:
+ assert(0);
+ return -1;
+ }
+ return -1;
+}
+
+static struct quic_message *quic_create_message(enum quic_topic_type topic_type, struct session *sess, struct quic_exdata *quic_ext, void *data)
+{
+ struct quic_message *qmsg = (struct quic_message *)CALLOC(1, sizeof(struct quic_message));
+#ifndef NDEBUG
+ qmsg->magic = QUIC_MSG_HDR_MAGIC;
+#endif
+ qmsg->topic_type = topic_type;
+ qmsg->quic_ext = quic_ext;
+ qmsg->sess_ref = sess;
+ qmsg->topic_data = data;
+ return qmsg;
+}
+
+static void free_quicinfo(struct quic_info *quic_info)
+{
+ FREE(quic_info->chlo.sni);
+ quic_info->chlo.sni_len = 0;
+ FREE(quic_info->chlo.user_agent);
+ quic_info->chlo.user_agent_len = 0;
+}
+
+void quic_msg_free_cb(void *msg, void *msg_free_arg UNUSED)
+{
+ assert(msg != NULL);
+ struct quic_message *qmsg = (struct quic_message *)msg;
+ assert(QUIC_MSG_HDR_MAGIC == qmsg->magic);
+ if (qmsg->quic_ext)
+ {
+ free_quicinfo(&qmsg->quic_ext->quic_info);
+ }
+ FREE(msg);
+}
+
+void quic_session_mq_publish_message_safe(const struct quic *quic_env, struct quic_message *qmsg)
+{
+ struct mq_runtime *mq_rt = module_manager_get_mq_runtime(quic_env->mod_mgr_ref);
+ int ret = mq_runtime_publish_message(mq_rt, quic_get_topic_id(quic_env, qmsg->topic_type), qmsg);
+ assert(ret >= 0);
+ if (ret < 0)
+ {
+ STELLAR_LOG_FATAL(quic_env->logger_ref, QUIC_MODULE_NAME, "quic mq_runtime_publish_message fail, ret=%d", ret);
+ quic_msg_free_cb(qmsg, NULL);
+ }
+ return;
+}
+
+struct quic_exdata *quic_exdata_new(void)
+{
+ struct quic_exdata *quic_ext = (struct quic_exdata *)CALLOC(1, sizeof(struct quic_exdata));
+ return quic_ext;
+}
+
+static struct quic_exdata *quic_session_new(int thread_idx, struct session *sess, struct quic *quic_env, const char *payload, uint16_t payload_len)
+{
+ struct quic_exdata *quic_ext = quic_exdata_new();
+ session_set_exdata(sess, quic_env->exdata_id, quic_ext);
+ int payload_offset = 0;
+ enum QUIC_VERSION_T qver = is_quic_protocol(payload, payload_len, &payload_offset);
+ if (QUIC_VERSION_UNKNOWN == qver)
+ {
+ quic_ext->ignore_session = 1;
+ return quic_ext;
+ }
+ quic_stat_update_cnt(&quic_env->stat, thread_idx, QUIC_STAT_SESSION_CNT, 1);
+ return quic_ext;
+}
+
+void quic_on_udp_msg_cb(struct session *sess, enum session_state state UNUSED, struct packet *pkt, void *plugin_env)
+{
+ struct quic *quic_env = (struct quic *)plugin_env;
+ struct quic_exdata *quic_ext = (struct quic_exdata *)session_get_exdata(sess, quic_env->exdata_id);
+ uint16_t payload_len = packet_get_payload_len(pkt);
+ const char *payload = packet_get_payload_data(pkt);
+ if (NULL == payload || payload_len == 0)
+ {
+ return;
+ }
+ int thread_idx = module_manager_get_thread_id(quic_env->mod_mgr_ref);
+ if (NULL == quic_ext)
+ {
+ quic_ext = quic_session_new(thread_idx, sess, quic_env, payload, payload_len);
+ assert(quic_ext != NULL);
+ }
+
+ if (quic_ext->ignore_session) // quic path
+ {
+ return;
+ }
+ // int thread_seq = module_manager_get_thread_id(quic_env->mod_mgr_ref);
+ quic_analyze_entry(sess, quic_env, quic_ext, payload, payload_len);
+ return;
+}
+
+void quic_exdata_free_cb(int idx UNUSED, void *ex_ptr, void *arg UNUSED)
+{
+ struct quic_exdata *quic_ext = (struct quic_exdata *)ex_ptr;
+ free_quicinfo(&quic_ext->quic_info);
+ FREE(quic_ext->quic_buf.buffer);
+ FREE(ex_ptr);
+}
+
+void quic_on_msg_dispatch(int topic_id UNUSED, void *msg, on_msg_cb_func *on_msg_cb,
+ void *on_msg_cb_arg, void *dispatch_arg UNUSED)
+{
+ assert(msg != NULL && on_msg_cb != NULL);
+ struct quic_message *qmsg = (struct quic_message *)msg;
+ switch (qmsg->topic_type)
+ {
+ case QUIC_TOPIC_CLIENT_HELLO:
+ {
+ quic_on_client_hello_callback *on_chlo_cb = (quic_on_client_hello_callback *)((void *)on_msg_cb);
+ on_chlo_cb(qmsg->sess_ref, qmsg->quic_ext->quic_info.version, (struct quic_client_hello *)qmsg->topic_data, on_msg_cb_arg);
+ }
+ break;
+ // case QUIC_TOPIC_ENCRYPTED_PAYLOAD:
+ // {
+ // quic_on_encrypted_payload_callback *on_encrypt_cb = (quic_on_encrypted_payload_callback *)((void *)on_msg_cb);
+ // on_encrypt_cb(qmsg->sess_ref, (struct quic_encrypted_payload *)qmsg->topic_data, on_msg_cb_arg);
+ // }
+ break;
+ default:
+ break;
+ }
+}
+
+static void quic_chlo_chunk_assemble(struct quic_buffer *qbuf, unsigned char *new_payload, int new_len)
+{
+ if (NULL == new_payload || new_len <= 0)
+ {
+ return;
+ }
+ if (NULL == qbuf->buffer)
+ {
+ qbuf->buffer = (unsigned char *)calloc(1, QUIC_CHLO_FRAG_MAX_SIZE);
+ qbuf->max_size = QUIC_CHLO_FRAG_MAX_SIZE;
+ }
+ int max_copy_len = MIN((int)(qbuf->max_size - qbuf->datalen), new_len);
+ memcpy(qbuf->buffer + qbuf->datalen, new_payload, max_copy_len);
+ qbuf->datalen += max_copy_len;
+}
+
+static enum PARSE_RESULT quic_decrypting_payload(struct quic_exdata *qcontext, unsigned char *udp_payload, int udp_payload_len, uint32_t chlo_frag_max_size)
+{
+ struct quic_buffer *qbuf = &qcontext->quic_buf;
+ unsigned char *quic_decrypted_payload;
+ int ret, quic_decrypted_payload_len;
+ quic_dpt_t *dpt = quic_deprotection_new();
+
+ if (quic_deprotection(dpt, (const uint8_t *)udp_payload, udp_payload_len) < 0)
+ {
+ goto err_exit;
+ }
+
+ if (qbuf->datalen > 0)
+ { // some fragment exist
+ quic_chlo_chunk_assemble(qbuf, dpt->payload.data, dpt->payload.len);
+ quic_decrypted_payload = qbuf->buffer;
+ quic_decrypted_payload_len = qbuf->datalen;
+ }
+ else
+ {
+ quic_decrypted_payload = dpt->payload.data;
+ quic_decrypted_payload_len = dpt->payload.len;
+ }
+
+ ret = qk_chlo_is_complete((enum QUIC_VERSION_T)qcontext->quic_info.version, quic_decrypted_payload, quic_decrypted_payload_len);
+ if (0 == ret)
+ {
+ if (NULL == qbuf->buffer || 0 == qbuf->datalen)
+ {
+ quic_chlo_chunk_assemble(qbuf, dpt->payload.data, dpt->payload.len);
+ }
+ goto fun_exit;
+ }
+ parse_quic_decrypted_payload(&qcontext->quic_info, quic_decrypted_payload, quic_decrypted_payload_len, chlo_frag_max_size);
+
+fun_exit:
+ quic_deprotection_free(dpt);
+ return PARSE_RESULT_VERSION;
+
+err_exit:
+ quic_deprotection_free(dpt);
+ return PARSE_RESULT_UNKNOWN;
+}
+
+static enum PARSE_RESULT quic_parse_all_version(struct quic_exdata *qcontext, const char *payload, int payload_len, uint32_t chlo_frag_max_size)
+{
+ struct quic_info *quic_info = &qcontext->quic_info;
+ int payload_offset = 0;
+ enum QUIC_VERSION_T quic_version = QUIC_VERSION_UNKNOWN;
+
+ if (payload == NULL || payload_len <= 0)
+ {
+ return PARSE_RESULT_UNKNOWN;
+ }
+ quic_version = is_quic_protocol(payload, payload_len, &payload_offset);
+ if (quic_version == QUIC_VERSION_UNKNOWN)
+ {
+ return PARSE_RESULT_UNKNOWN;
+ }
+ quic_info->version = (uint32_t)quic_version;
+
+ if (quic_version >= GQUIC_VERSION_Q001 && quic_version <= GQUIC_VERSION_Q048)
+ {
+ if (payload_len > payload_offset)
+ {
+ return (enum PARSE_RESULT)parse_quic_uncryption_payload(quic_info, (const unsigned char *)payload + payload_offset, payload_len - payload_offset);
+ }
+ return PARSE_RESULT_VERSION;
+ }
+ else if ((quic_version >= MVFST_VERSION_00 && quic_version <= MVFST_VERSION_0F) ||
+ (quic_version >= GQUIC_VERSION_Q049 && quic_version <= GQUIC_VERSION_Q059) ||
+ (quic_version >= GQUIC_VERSION_T050 && quic_version <= GQUIC_VERSION_T059) ||
+ (quic_version >= IQUIC_VERSION_I022 && quic_version <= IQUIC_VERSION_I029) ||
+ (quic_version == IQUIC_VERSION_RFC9000))
+ {
+ return quic_decrypting_payload(qcontext, (unsigned char *)payload, payload_len, chlo_frag_max_size);
+ }
+ else
+ {
+ return PARSE_RESULT_VERSION;
+ }
+
+ return PARSE_RESULT_VERSION;
+}
+
+void quic_analyze_entry(struct session *sess, struct quic *quic_env, struct quic_exdata *qcontext, const char *payload, size_t payload_len)
+{
+ int thread_idx = module_manager_get_thread_id(quic_env->mod_mgr_ref);
+ struct quic_message *qmsg;
+ qcontext->parse_pkt_num++;
+ qcontext->parse_payload_size += payload_len;
+ if (qcontext->parse_payload_size > (unsigned int)QUIC_CHLO_FRAG_MAX_SIZE || qcontext->parse_pkt_num > QUIC_PARSE_FIRST_N_PACKETS)
+ {
+ if (0 == qcontext->pub_chlo_msg_flag && qcontext->quic_info.version != QUIC_VERSION_UNKNOWN)
+ {
+ qmsg = quic_create_message(QUIC_TOPIC_CLIENT_HELLO, sess, qcontext, &qcontext->quic_info.chlo);
+ quic_session_mq_publish_message_safe(quic_env, qmsg);
+ qcontext->pub_chlo_msg_flag = 1;
+ }
+ }
+ else
+ {
+ quic_parse_all_version(qcontext, payload, payload_len, quic_env->config.chlo_frag_max_size);
+ if (PARSE_RESULT_UNKNOWN == qcontext->quic_info.version)
+ {
+ qcontext->ignore_session = 1;
+ return;
+ }
+ if (qcontext->quic_info.chlo.sni != NULL)
+ {
+ qmsg = quic_create_message(QUIC_TOPIC_CLIENT_HELLO, sess, qcontext, &qcontext->quic_info.chlo);
+ quic_session_mq_publish_message_safe(quic_env, qmsg);
+ qcontext->pub_chlo_msg_flag = 1;
+ quic_stat_update_version(&quic_env->stat, thread_idx, qcontext->quic_info.version, 1);
+ }
+ }
+ quic_stat_update_cnt(&quic_env->stat, thread_idx, QUIC_STAT_PACKET_CNT, 1);
+ quic_stat_update_cnt(&quic_env->stat, thread_idx, QUIC_STAT_PACKET_SIZE, payload_len);
+ return;
+} \ No newline at end of file
diff --git a/decoders/quic/quic_process.h b/decoders/quic/quic_process.h
new file mode 100644
index 0000000..ed24aed
--- /dev/null
+++ b/decoders/quic/quic_process.h
@@ -0,0 +1,116 @@
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include "quic_decoder.h"
+#include "quic_util.h"
+#include "stellar/quic.h"
+#include "stellar/session.h"
+#include "fieldstat/fieldstat_easy.h"
+
+#define QUIC_TOPIC_NAME "QUIC_TOPIC"
+#define QUIC_MODULE_NAME "QUIC"
+#define QUIC_EXDATA_NAME "QUIC_EXDATA"
+
+#define FALSE 0x00
+#define TRUE 0x01
+#define MAYBE 0x02
+
+#define SUPPORT_QUIC_PORT_NUM 128
+
+#define QUIC_HALF_CLOSE 0x01
+#define QUIC_WHOLE_CLOSE 0x02
+#define QUIC_DATA 0x03
+#define QUIC_KEY 1
+#define QUIC_RETURN_NORM 0x60
+#define QUIC_RETURN_UNNORM 0x61
+#define QUIC_RETURN_RESET_BUFFER 0x62
+#define QUIC_RETURN_DROPME 0x63
+#define MAX_REGION_NUM 15
+#define REGION_NAME_LEN 32
+
+enum quic_topic_type
+{
+ QUIC_TOPIC_CLIENT_HELLO = 1,
+ QUIC_TOPIC_ENCRYPTED_PAYLOAD,
+};
+
+struct quic_buffer
+{
+ unsigned char *buffer;
+ size_t max_size;
+ size_t datalen;
+};
+
+struct quic_message
+{
+#ifndef NDEBUG
+#define QUIC_MSG_HDR_MAGIC 0x51554943 // ASCII: "QUIC", for sanity check
+ unsigned int magic;
+#endif
+ struct session *sess_ref;
+ struct quic_exdata *quic_ext;
+ enum quic_topic_type topic_type; // use type but not id, because id is dynamically get from mq manager
+ void *topic_data; // refer to quic_client_hello or quic_encrypted_payload
+};
+
+struct quic_exdata
+{
+ uint8_t ignore_session; // not quic protocol, ignore subsequent packets of this session
+ uint8_t pub_chlo_msg_flag;
+ unsigned int parse_payload_size;
+ unsigned int parse_pkt_num;
+ struct quic_info quic_info;
+ struct quic_buffer quic_buf; // for client hello fragment
+};
+
+#define QUIC_CHLO_FRAG_MAX_SIZE (4096)
+#define QUIC_PARSE_FIRST_N_PACKETS (8)
+struct quic_config
+{
+ uint32_t chlo_frag_max_size;
+ uint32_t parse_packet_max;
+};
+
+enum quic_stat_type
+{
+ QUIC_STAT_SESSION_CNT = 0,
+ QUIC_STAT_PACKET_CNT,
+ QUIC_STAT_PACKET_SIZE,
+ QUIC_STAT_VERSION,
+ QUIC_STAT_MAX,
+};
+
+struct quic_stat
+{
+ struct fieldstat_easy *fs4_ins;
+ int fs4_session_cnt_id;
+ int fs4_packet_cnt_id;
+ int fs4_packet_size_id;
+ int fs4_quic_version_id;
+};
+
+struct quic
+{
+ struct quic_config config;
+ struct module_manager *mod_mgr_ref;
+ struct logger *logger_ref;
+ int exdata_id;
+ int udp_topic_id; // as subscriber
+ int quic_topic_chlo_id; // as quic publisher
+ int quic_topic_encrypt_id; // as quic publisher
+ struct quic_stat stat;
+};
+int quic_load_config(const char *file, struct quic_config *config);
+int quic_stat_init(struct quic_stat *qstat);
+void quic_on_udp_msg_cb(struct session *sess, enum session_state state, struct packet *pkt, void *plugin_env);
+void quic_exdata_free_cb(int idx UNUSED, void *ex_ptr, void *arg);
+void quic_before_session_free_cb(struct session *sess, void *args);
+void quic_on_msg_dispatch(int topic_id UNUSED, void *msg, on_msg_cb_func *on_msg_cb,
+ void *on_msg_cb_arg, void *dispatch_arg);
+void quic_msg_free_cb(void *msg, void *msg_free_arg);
+void quic_analyze_entry(struct session *sess, struct quic *g_quic_plug_env, struct quic_exdata *context, const char *payload, size_t payload_len);
+void quic_session_mq_publish_message_safe(const struct quic *quic_env, struct quic_message *qmsg_pr);
+void quic_stat_update_cnt(struct quic_stat *qstat, int thread_idx, enum quic_stat_type type, long long value);
+void quic_stat_update_version(struct quic_stat *qstat, int thread_idx, uint32_t quic_version, long long value); \ No newline at end of file
diff --git a/decoders/quic/quic_util.h b/decoders/quic/quic_util.h
new file mode 100644
index 0000000..0b75852
--- /dev/null
+++ b/decoders/quic/quic_util.h
@@ -0,0 +1,28 @@
+#pragma once
+#include <stddef.h>
+#include <stdlib.h>
+
+#ifndef CALLOC
+#define CALLOC(nmemb, size) calloc(nmemb, size)
+#endif
+#ifndef REALLOC
+#define REALLOC(ptr, newsize) realloc(ptr, newsize)
+#endif
+#ifndef FREE
+#define FREE(p) \
+ { \
+ if (p) \
+ { \
+ free((void *)p); \
+ } \
+ p = NULL; \
+ }
+#endif
+
+#ifndef UNUSED
+#define UNUSED __attribute__((unused))
+#endif
+
+#ifndef MIN
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif \ No newline at end of file
diff --git a/decoders/quic/version.map b/decoders/quic/version.map
new file mode 100644
index 0000000..4eb803c
--- /dev/null
+++ b/decoders/quic/version.map
@@ -0,0 +1,10 @@
+VERS_3.0{
+global:
+ extern "C" {
+ quic_init;
+ quic_exit;
+ quic_version_to_readable_string;
+ quic_subscribe_client_hello*;
+ };
+ local: *;
+};
diff --git a/include/stellar/quic.h b/include/stellar/quic.h
new file mode 100644
index 0000000..0a5ce9f
--- /dev/null
+++ b/include/stellar/quic.h
@@ -0,0 +1,26 @@
+#pragma once
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+#include "stellar/session.h"
+#include "stellar/module.h"
+
+ struct quic_client_hello
+ {
+ char *sni; // maybe NULL
+ size_t sni_len;
+ char *user_agent; // maybe NULL
+ size_t user_agent_len;
+ };
+
+#define QUIC_MODULE_NAME "QUIC"
+ struct quic;
+ // call module_manager_get_module(QUIC_MODULE_NAME) to get quic module
+ typedef void(quic_on_client_hello_callback)(struct session *sess, uint32_t version, const struct quic_client_hello *chlo, void *args);
+ struct quic *quic_module_to_quic(struct module *quic_mod);
+ int quic_subscribe_client_hello(struct quic *quic_env, quic_on_client_hello_callback *cb, void *args);
+ int quic_version_to_readable_string(unsigned int version, char *out_buf, size_t out_buf_len);
+#ifdef __cplusplus
+}
+#endif
diff --git a/infra/CMakeLists.txt b/infra/CMakeLists.txt
index fc0ad21..a358fae 100644
--- a/infra/CMakeLists.txt
+++ b/infra/CMakeLists.txt
@@ -1,6 +1,6 @@
set(INFRA exdata mq tuple packet_manager packet_io ip_reassembly tcp_reassembly session_manager module_manager monitor)
set(DEPS bitmap dablooms interval_tree logger nmx_pool rbtree timeout toml ringbuf)
-set(DECODERS lpi_plus)
+set(DECODERS lpi_plus quic)
set(WHOLE_ARCHIVE ${DEPS} ${INFRA} ${DECODERS})
set(LIBS fieldstat4)
diff --git a/infra/version.map b/infra/version.map
index 385a2a2..57cddd3 100644
--- a/infra/version.map
+++ b/infra/version.map
@@ -79,5 +79,9 @@ global:
monitor_on_init;
monitor_on_exit;
+ quic_init;
+ quic_exit;
+ quic_version_to_readable_string;
+ quic_subscribe_client_hello*;
local: *;
};
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 11d1abc..5c05a12 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -6,4 +6,5 @@ add_subdirectory(lpi_plus)
#add_subdirectory(decoders/socks)
#add_subdirectory(decoders/stratum)
#add_subdirectory(decoders/session_flags)
-add_subdirectory(monitor) \ No newline at end of file
+add_subdirectory(monitor)
+add_subdirectory(decoders/quic)
diff --git a/test/decoders/quic/CMakeLists.txt b/test/decoders/quic/CMakeLists.txt
new file mode 100644
index 0000000..faf57c6
--- /dev/null
+++ b/test/decoders/quic/CMakeLists.txt
@@ -0,0 +1,76 @@
+include_directories(${CMAKE_SOURCE_DIR}/decoders)
+add_executable(gtest_quic_unit gtest_quic_unit.cpp)
+target_link_libraries(gtest_quic_unit gtest quic openssl-crypto-static openssl-ssl-static)
+include(GoogleTest)
+gtest_discover_tests(gtest_quic_unit)
+
+add_executable(gtest_quic_main gtest_quic_main.cpp gtest_quic_module.cpp)
+target_include_directories(gtest_quic_main PRIVATE ${CMAKE_SOURCE_DIR}/deps/)
+target_include_directories(gtest_quic_main PRIVATE ${CMAKE_SOURCE_DIR}/decoders/)
+target_link_libraries(gtest_quic_main PRIVATE cjson-static -Wl,--whole-archive stellar_lib session_manager quic -Wl,--no-whole-archive
+ dl "-rdynamic"
+ gtest gmock)
+set(TEST_PREFIX QUIC)
+add_test(NAME ${TEST_PREFIX}.SETUP COMMAND sh -c "
+ mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/conf &&
+ mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/log &&
+ cp ${CMAKE_SOURCE_DIR}/conf/stellar.toml ${CMAKE_CURRENT_BINARY_DIR}/conf/ &&
+ cat ${CMAKE_SOURCE_DIR}/test/decoders/quic/quic_spec.toml >> ${CMAKE_CURRENT_BINARY_DIR}/conf/stellar.toml &&
+ tomlq -t -i '.packet_io.pcap_path=\"-\"' ${CMAKE_CURRENT_BINARY_DIR}/conf/stellar.toml &&
+ tomlq -t -i '.packet_io.mode=\"pcaplist\"' ${CMAKE_CURRENT_BINARY_DIR}/conf/stellar.toml
+ ")
+set_tests_properties(${TEST_PREFIX}.SETUP
+ PROPERTIES FIXTURES_SETUP ${TEST_PREFIX}.SETUP)
+
+set(QUIC_BENCHMARK_DIR ${CMAKE_SOURCE_DIR}/test/decoders/quic/pcap)
+set(QUIC_GTEST_MAIN ${CMAKE_CURRENT_BINARY_DIR}/gtest_quic_main)
+set(QUIC_TEST_JSON quic_gtest.json)
+add_test(NAME ${TEST_PREFIX}.IQUIC_29 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/iquic/29 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/iquic/29/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.IQUIC_PORT8443 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/port-8443 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/port-8443/quic_result.json ${QUIC_TEST_JSON}" )
+
+add_test(NAME ${TEST_PREFIX}.GQUIC_23 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/gquic/23 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/gquic/23/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.GQUIC_25 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/gquic/25 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/gquic/25/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.GQUIC_33 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/gquic/33 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/gquic/33/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.GQUIC_34 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/gquic/34 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/gquic/34/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.GQUIC_35 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/gquic/35 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/gquic/35/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.GQUIC_37 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/gquic/37 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/gquic/37/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.GQUIC_39 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/gquic/39 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/gquic/39/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.GQUIC_41 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/gquic/41 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/gquic/41/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.GQUIC_43 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/gquic/43 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/gquic/43/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.GQUIC_44 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/gquic/44 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/gquic/44/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.GQUIC_46 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/gquic/46 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/gquic/46/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.GQUIC_50 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/gquic/50 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/gquic/50/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.MVFST01 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/mvfst/01 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/mvfst/01/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.MVFST02 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/mvfst/02 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/mvfst/02/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.TQUIC COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/tquic -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/tquic/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.RFC9000 COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/rfc9000 -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/rfc9000/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.RFC9000-FRAG COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/rfc9000-fragment -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/rfc9000-fragment/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.RFC9000-SPECIAL COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/rfc9000-special -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/rfc9000-special/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.RFC9000-CHLO-FRAG COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/rfc9000-chlo-fragment -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/rfc9000-chlo-fragment/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.AIRPORT COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/airport -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/airport/quic_result.json ${QUIC_TEST_JSON}" )
+add_test(NAME ${TEST_PREFIX}.SPECIAL COMMAND sh -c "find ${QUIC_BENCHMARK_DIR}/special -type f -name *.pcap | sort -V | ${QUIC_GTEST_MAIN} ${QUIC_BENCHMARK_DIR}/special/quic_result.json ${QUIC_TEST_JSON}" )
+
+set_tests_properties(${TEST_PREFIX}.IQUIC_29
+ ${TEST_PREFIX}.IQUIC_PORT8443
+ ${TEST_PREFIX}.GQUIC_23
+ ${TEST_PREFIX}.GQUIC_25
+ ${TEST_PREFIX}.GQUIC_33
+ ${TEST_PREFIX}.GQUIC_34
+ ${TEST_PREFIX}.GQUIC_35
+ ${TEST_PREFIX}.GQUIC_37
+ ${TEST_PREFIX}.GQUIC_41
+ ${TEST_PREFIX}.GQUIC_43
+ ${TEST_PREFIX}.GQUIC_44
+ ${TEST_PREFIX}.GQUIC_46
+ ${TEST_PREFIX}.GQUIC_50
+ ${TEST_PREFIX}.MVFST01
+ ${TEST_PREFIX}.MVFST02
+ ${TEST_PREFIX}.TQUIC
+ ${TEST_PREFIX}.RFC9000
+ ${TEST_PREFIX}.RFC9000-FRAG
+ ${TEST_PREFIX}.RFC9000-SPECIAL
+ ${TEST_PREFIX}.RFC9000-CHLO-FRAG
+ ${TEST_PREFIX}.AIRPORT
+ ${TEST_PREFIX}.SPECIAL
+ PROPERTIES FIXTURES_REQUIRED ${TEST_PREFIX}.SETUP
+ ) \ No newline at end of file
diff --git a/test/decoders/quic/gtest_quic_main.cpp b/test/decoders/quic/gtest_quic_main.cpp
new file mode 100644
index 0000000..2e9f7fa
--- /dev/null
+++ b/test/decoders/quic/gtest_quic_main.cpp
@@ -0,0 +1,97 @@
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <assert.h>
+#include <gtest/gtest.h>
+#include "stellar/stellar.h"
+#include "cJSON.h"
+
+static cJSON *load_result_from_jsonfile(const char *json_path)
+{
+ if (json_path == NULL)
+ return NULL;
+
+ long file_len = 0;
+ char *file_content = NULL;
+ FILE *fp = NULL;
+
+ fp = fopen(json_path, "r+");
+ if (NULL == fp)
+ {
+ return NULL;
+ }
+ fseek(fp, 0, SEEK_END);
+ file_len = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ if (file_len == 0)
+ {
+ fclose(fp);
+ return NULL;
+ }
+ file_content = (char *)malloc(file_len + 1);
+ fread(file_content, file_len, 1, fp);
+ file_content[file_len] = '\0';
+ cJSON *load = cJSON_Parse(file_content);
+ free(file_content);
+ fclose(fp);
+ return load;
+}
+
+static void show_json_diff(cJSON *expect_json_root, cJSON *test_module_root)
+{
+ if (NULL == expect_json_root || NULL == test_module_root)
+ {
+ return;
+ }
+ cJSON *t_load = expect_json_root->child, *t_test = test_module_root->child;
+ int ret;
+ char *load_json_str = NULL;
+ char *result_json_str = NULL;
+ while (t_load != NULL && t_test != NULL)
+ {
+ ret = cJSON_Compare(t_load, t_test, 0);
+ if (ret != 1)
+ {
+ load_json_str = cJSON_Print(t_load);
+ printf("LOAD Diff:\n%s\n", load_json_str);
+ free(load_json_str);
+ result_json_str = cJSON_Print(t_test);
+ printf("TEST Diff:\n%s\n", result_json_str);
+ free(result_json_str);
+ }
+ t_load = t_load->next;
+ t_test = t_test->next;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc != 3)
+ {
+ fprintf(stderr, "\nUsage: ./%s <expect-result> <test-module-result>\n\n", argv[0]);
+ exit(1);
+ }
+ ::testing::InitGoogleTest(&argc, (char **)argv);
+ const char *expect_json_path = argv[1];
+ const char *test_module_json_path = argv[2];
+
+ struct stellar *st = stellar_new("./conf/stellar.toml");
+ EXPECT_TRUE(st != NULL);
+ stellar_run(st);
+ stellar_free(st);
+
+ cJSON *expect_json_root = load_result_from_jsonfile(expect_json_path);
+ EXPECT_TRUE(expect_json_root != NULL);
+ cJSON *test_json_root = load_result_from_jsonfile(test_module_json_path);
+ EXPECT_TRUE(test_json_root != NULL);
+
+ int ret = cJSON_Compare(expect_json_root, test_json_root, 0);
+ EXPECT_EQ(1, ret);
+ if (ret != 1)
+ {
+ show_json_diff(expect_json_root, test_json_root);
+ }
+ cJSON_Delete(expect_json_root);
+ cJSON_Delete(test_json_root);
+ return ::testing::Test::HasFailure() ? 1 : 0;
+} \ No newline at end of file
diff --git a/test/decoders/quic/gtest_quic_module.cpp b/test/decoders/quic/gtest_quic_module.cpp
new file mode 100644
index 0000000..f3e2d77
--- /dev/null
+++ b/test/decoders/quic/gtest_quic_module.cpp
@@ -0,0 +1,122 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include "cJSON.h"
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+#include "stellar/quic.h"
+#include "stellar/session.h"
+#include "stellar/module.h"
+#include "quic/quic_decoder.h"
+#include "stellar/quic.h"
+
+ static cJSON *gtest_quic_json_root;
+ static int g_result_count = 1;
+
+ struct quic_gtest_exdata
+ {
+ cJSON *json_root;
+ };
+
+ struct quic_gtest_env
+ {
+ struct module_manager *mod_mgr;
+ int exdata_idx;
+ };
+
+ static void commit_test_result_json(cJSON *new_node)
+ {
+ if (gtest_quic_json_root)
+ {
+ char test_index[32] = {};
+ sprintf(test_index, "QUIC_RESULT_%d", g_result_count);
+ // cJSON_AddItemToObject(g_test_result_root, name, node);
+ cJSON_AddStringToObject(new_node, "name", test_index);
+ cJSON_AddItemToArray(gtest_quic_json_root, new_node);
+ }
+ }
+
+ static void cJSON_Add_QStringToObject(cJSON *object, const char *name, const char *value, size_t value_len)
+ {
+ char *tmp = (char *)calloc(1, value_len + 1);
+ memcpy(tmp, value, value_len);
+ cJSON_AddStringToObject(object, name, tmp);
+ free(tmp);
+ }
+
+ void quic_gtest_session_exdata_free_cb(int idx, void *ex_ptr, void *arg)
+ {
+ (void)idx;
+ (void)arg;
+ struct quic_gtest_exdata *quic_ext = (struct quic_gtest_exdata *)ex_ptr;
+ if (quic_ext && quic_ext->json_root)
+ {
+ char result_name[128] = "";
+ snprintf(result_name, sizeof(result_name), "QUIC_RESULT_%d", g_result_count);
+ commit_test_result_json(quic_ext->json_root);
+ g_result_count += 1;
+ free(ex_ptr);
+ }
+ }
+
+ void quic_gtest_on_client_hello_cb(struct session *sess, unsigned int version, const struct quic_client_hello *chlo, void *args)
+ {
+ struct quic_gtest_env *env = (struct quic_gtest_env *)args;
+ struct quic_gtest_exdata *quic_ext = (struct quic_gtest_exdata *)session_get_exdata(sess, env->exdata_idx);
+ if (NULL == quic_ext)
+ {
+ quic_ext = (struct quic_gtest_exdata *)calloc(1, sizeof(struct quic_gtest_exdata));
+ session_set_exdata(sess, env->exdata_idx, quic_ext);
+ quic_ext->json_root = cJSON_CreateObject();
+ cJSON_AddStringToObject(quic_ext->json_root, "Tuple4", session_get_readable_addr(sess));
+ }
+ char version_str[128] = {0};
+ quic_version_to_readable_string(version, version_str, sizeof(version_str));
+ cJSON_AddStringToObject(quic_ext->json_root, "VERSION", version_str);
+ if (chlo && chlo->sni && chlo->sni_len > 0)
+ {
+ cJSON_Add_QStringToObject(quic_ext->json_root, "SNI", chlo->sni, chlo->sni_len);
+ }
+ if (chlo && chlo->user_agent && chlo->user_agent_len > 0)
+ {
+ cJSON_Add_QStringToObject(quic_ext->json_root, "UA", chlo->user_agent, chlo->user_agent_len);
+ }
+ }
+
+ struct module *gtest_quic_init(struct module_manager *mod_mgr)
+ {
+ gtest_quic_json_root = cJSON_CreateArray();
+ struct quic_gtest_env *env = (struct quic_gtest_env *)calloc(1, sizeof(struct quic_gtest_env));
+ env->mod_mgr = mod_mgr;
+ struct module *quic_decoder_mod = module_manager_get_module(mod_mgr, QUIC_MODULE_NAME);
+ struct quic *quic_env = quic_module_to_quic(quic_decoder_mod);
+ quic_subscribe_client_hello(quic_env, quic_gtest_on_client_hello_cb, env);
+
+ struct module *sess_mod = module_manager_get_module(mod_mgr, SESSION_MANAGER_MODULE_NAME);
+ struct session_manager *sess_mgr = module_to_session_manager(sess_mod);
+ env->exdata_idx = session_manager_new_session_exdata_index(sess_mgr, "QUIC_GTEST_EXDATA", quic_gtest_session_exdata_free_cb, env);
+ return module_new("GTEST_QUIC", env);
+ }
+
+ void gtest_quic_exit(struct module_manager *mod_mgr, struct module *mod)
+ {
+ (void)mod_mgr;
+ struct quic_gtest_env *env = (struct quic_gtest_env *)module_get_ctx(mod);
+ free(env);
+ char *json_str = cJSON_PrintUnformatted(gtest_quic_json_root);
+ FILE *json_fp = fopen("quic_gtest.json", "w+");
+ fwrite(json_str, strlen(json_str), 1, json_fp);
+ fclose(json_fp);
+ free(json_str);
+ cJSON_Delete(gtest_quic_json_root);
+ module_free(mod);
+ return;
+ }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/test/decoders/quic/gtest_quic_unit.cpp b/test/decoders/quic/gtest_quic_unit.cpp
new file mode 100644
index 0000000..40efde4
--- /dev/null
+++ b/test/decoders/quic/gtest_quic_unit.cpp
@@ -0,0 +1,108 @@
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <stdio.h>
+
+extern "C"
+{
+#include "quic/quic_process.h"
+#include "quic/quic_decoder.h"
+}
+
+TEST(QUIC, non_quic_protocol)
+{
+ const unsigned char dns_header[] =
+ {
+ 0x93,
+ 0xa4,
+ 0xa8,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ };
+ int payload_offset = 0;
+ enum QUIC_VERSION_T qver = is_quic_protocol((char *)dns_header, sizeof(dns_header), &payload_offset);
+ ASSERT_EQ(qver, QUIC_VERSION_UNKNOWN);
+}
+
+TEST(QUIC, is_quic_protocol_rfc9000)
+{
+ const unsigned char quic_header[] =
+ {
+ 0xcc, // flags
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x01, // version
+ 0x08,
+ 0x01,
+ 0x02,
+ 0x03,
+ 0x04,
+ 0x05,
+ 0x06,
+ 0x07,
+ 0x08,
+ };
+ int payload_offset = 0;
+ enum QUIC_VERSION_T qver = is_quic_protocol((char *)quic_header, sizeof(quic_header), &payload_offset);
+ ASSERT_EQ(qver, IQUIC_VERSION_RFC9000);
+}
+
+TEST(QUIC, is_quic_protocol_gquic23)
+{
+ const unsigned char quic_header[1500] =
+ {
+ 0x0d, // flags
+ 0x01,
+ 0x02,
+ 0x03,
+ 0x04,
+ 0x05,
+ 0x06,
+ 0x07,
+ 0x08, // CID
+ 0x51, 0x30, 0x32, 0x33 // version
+ };
+ int payload_offset = 0;
+ enum QUIC_VERSION_T qver = is_quic_protocol((char *)quic_header, sizeof(quic_header), &payload_offset);
+ ASSERT_EQ(qver, GQUIC_VERSION_Q023);
+}
+
+TEST(QUIC, is_quic_protocol_iquic29)
+{
+ const unsigned char quic_header[] =
+ {
+ 0xc2, // flags
+ 0xff, 0x00, 0x00, 0x1d, // version
+ 0x00, // DCID
+ 0x08, // ID length
+ };
+ int payload_offset = 0;
+ enum QUIC_VERSION_T qver = is_quic_protocol((char *)quic_header, sizeof(quic_header), &payload_offset);
+ ASSERT_EQ(qver, IQUIC_VERSION_I029);
+}
+
+TEST(QUIC, qk_get_chlo_length)
+{
+ const unsigned char quic_chlo_header[] =
+ {
+ 0x01, // handshark type
+ 0x00, 0x01, 0x41, // length
+ 0x03, 0x03, // tls version
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // random
+ 0x09, 0xa, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0f, // random
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // random
+ 0x09, 0xa, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0f, // random
+ };
+
+ int chlo_len = qk_get_chlo_length(quic_chlo_header, sizeof(quic_chlo_header));
+ ASSERT_EQ(321, chlo_len);
+}
+
+int main(int argc, char const *argv[])
+{
+ ::testing::InitGoogleTest(&argc, (char **)argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/test/decoders/quic/pcap/airport/Old-Airport-quic.pcap b/test/decoders/quic/pcap/airport/Old-Airport-quic.pcap
new file mode 100644
index 0000000..d6dc469
--- /dev/null
+++ b/test/decoders/quic/pcap/airport/Old-Airport-quic.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/airport/quic_result.json b/test/decoders/quic/pcap/airport/quic_result.json
new file mode 100644
index 0000000..6f03e9e
--- /dev/null
+++ b/test/decoders/quic/pcap/airport/quic_result.json
@@ -0,0 +1,111 @@
+[
+ {
+ "Tuple4": "10.56.160.76:44417-216.58.209.132:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "www.google.com",
+ "UA": "com.google.android.googlequicksearchbox Cronet/96.0.4664.17",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "10.56.160.76:47427-213.55.110.12:443-17-0",
+ "VERSION": "Google QUIC 43",
+ "SNI": "r1---sn-xuj-5qqz.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/96.0.4655.4",
+ "name": "QUIC_RESULT_2"
+ },
+ {
+ "Tuple4": "10.56.160.76:39996-142.250.185.33:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "yt3.ggpht.com",
+ "UA": "com.google.android.youtube Cronet/96.0.4655.4",
+ "name": "QUIC_RESULT_3"
+ },
+ {
+ "Tuple4": "10.56.160.76:48527-216.58.209.130:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "www.googleadservices.com",
+ "UA": "com.google.android.youtube Cronet/96.0.4655.4",
+ "name": "QUIC_RESULT_4"
+ },
+ {
+ "Tuple4": "10.56.160.76:43569-197.156.74.146:443-17-0",
+ "VERSION": "Google QUIC 43",
+ "SNI": "r7---sn-xuj-5qqs.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/96.0.4655.4",
+ "name": "QUIC_RESULT_5"
+ },
+ {
+ "Tuple4": "10.56.160.76:52114-197.156.74.147:443-17-0",
+ "VERSION": "Google QUIC 43",
+ "SNI": "r8---sn-xuj-5qqs.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/96.0.4655.4",
+ "name": "QUIC_RESULT_6"
+ },
+ {
+ "Tuple4": "10.56.160.76:59023-213.55.110.13:443-17-0",
+ "VERSION": "Google QUIC 43",
+ "SNI": "r2---sn-xuj-5qqz.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/96.0.4655.4",
+ "name": "QUIC_RESULT_7"
+ },
+ {
+ "Tuple4": "10.56.160.76:46224-197.156.74.140:443-17-0",
+ "VERSION": "Google QUIC 43",
+ "SNI": "r1---sn-xuj-5qqs.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/96.0.4655.4",
+ "name": "QUIC_RESULT_8"
+ },
+ {
+ "Tuple4": "10.56.160.76:54334-197.156.74.145:443-17-0",
+ "VERSION": "Google QUIC 43",
+ "SNI": "r6---sn-xuj-5qqs.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/96.0.4655.4",
+ "name": "QUIC_RESULT_9"
+ },
+ {
+ "Tuple4": "10.56.160.76:41069-142.250.180.42:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "youtubei.googleapis.com",
+ "UA": "com.google.android.youtube Cronet/96.0.4655.4",
+ "name": "QUIC_RESULT_10"
+ },
+ {
+ "Tuple4": "10.56.160.76:48756-213.55.110.14:443-17-0",
+ "VERSION": "Google QUIC 43",
+ "SNI": "r3---sn-xuj-5qqz.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/96.0.4655.4",
+ "name": "QUIC_RESULT_11"
+ },
+ {
+ "Tuple4": "10.56.160.76:51113-142.250.180.54:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "i.ytimg.com",
+ "UA": "com.google.android.youtube Cronet/96.0.4655.4",
+ "name": "QUIC_RESULT_12"
+ },
+ {
+ "Tuple4": "10.56.160.76:38866-197.156.74.141:443-17-0",
+ "VERSION": "Google QUIC 43",
+ "SNI": "r2---sn-xuj-5qqs.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/96.0.4655.4",
+ "name": "QUIC_RESULT_13"
+ },
+ {
+ "Tuple4": "10.56.160.76:46131-196.188.31.18:443-17-0",
+ "VERSION": "Facebook mvfst 02",
+ "SNI": "video.fadd1-1.fna.fbcdn.net",
+ "name": "QUIC_RESULT_14"
+ },
+ {
+ "Tuple4": "10.56.160.76:40267-102.132.96.18:443-17-0",
+ "VERSION": "Facebook mvfst 02",
+ "SNI": "graph.facebook.com",
+ "name": "QUIC_RESULT_15"
+ },
+ {
+ "Tuple4": "10.56.160.76:46761-196.188.31.17:443-17-0",
+ "VERSION": "Facebook mvfst 02",
+ "SNI": "scontent.fadd1-1.fna.fbcdn.net",
+ "name": "QUIC_RESULT_16"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/gquic/23/1-gquic-023-85.117.117.169.47762-173.194.73.95.443.pcap b/test/decoders/quic/pcap/gquic/23/1-gquic-023-85.117.117.169.47762-173.194.73.95.443.pcap
new file mode 100644
index 0000000..db29a3b
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/23/1-gquic-023-85.117.117.169.47762-173.194.73.95.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/23/2-gquic-023-85.117.123.193.19270-64.233.165.95.443.pcap b/test/decoders/quic/pcap/gquic/23/2-gquic-023-85.117.123.193.19270-64.233.165.95.443.pcap
new file mode 100644
index 0000000..110620d
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/23/2-gquic-023-85.117.123.193.19270-64.233.165.95.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/23/quic_result.json b/test/decoders/quic/pcap/gquic/23/quic_result.json
new file mode 100644
index 0000000..0325ce4
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/23/quic_result.json
@@ -0,0 +1,15 @@
+[
+ {
+ "Tuple4": "85.117.117.169:47762-173.194.73.95:443-17-0",
+ "VERSION": "Google QUIC 23",
+ "SNI": "www.googleapis.com",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "85.117.123.193:19270-64.233.165.95:443-17-0",
+ "VERSION": "Google QUIC 23",
+ "SNI": "www.googleapis.com",
+ "SNI": "www.googleapis.com",
+ "name": "QUIC_RESULT_2"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/gquic/25/1-gquic-025-85.117.113.98.4340-74.125.131.95.443.pcap b/test/decoders/quic/pcap/gquic/25/1-gquic-025-85.117.113.98.4340-74.125.131.95.443.pcap
new file mode 100644
index 0000000..299e573
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/25/1-gquic-025-85.117.113.98.4340-74.125.131.95.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/25/2-gquic-025-90.143.184.225.63062-173.194.222.95.443.pcap b/test/decoders/quic/pcap/gquic/25/2-gquic-025-90.143.184.225.63062-173.194.222.95.443.pcap
new file mode 100644
index 0000000..ad5a080
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/25/2-gquic-025-90.143.184.225.63062-173.194.222.95.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/25/quic_result.json b/test/decoders/quic/pcap/gquic/25/quic_result.json
new file mode 100644
index 0000000..ad9de51
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/25/quic_result.json
@@ -0,0 +1,12 @@
+[{
+ "Tuple4": "85.117.113.98:4340-74.125.131.95:443-17-0",
+ "VERSION": "Google QUIC 25",
+ "SNI": "www.googleapis.com",
+ "SNI": "www.googleapis.com",
+ "name": "QUIC_RESULT_1"
+ }, {
+ "Tuple4": "90.143.184.225:63062-173.194.222.95:443-17-0",
+ "VERSION": "Google QUIC 25",
+ "SNI": "www.googleapis.com",
+ "name": "QUIC_RESULT_2"
+}]
diff --git a/test/decoders/quic/pcap/gquic/33/1-gquic-033-90.143.189.5.8026-173.194.188.40.443.pcap b/test/decoders/quic/pcap/gquic/33/1-gquic-033-90.143.189.5.8026-173.194.188.40.443.pcap
new file mode 100644
index 0000000..b0e4c5b
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/33/1-gquic-033-90.143.189.5.8026-173.194.188.40.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/33/quic_result.json b/test/decoders/quic/pcap/gquic/33/quic_result.json
new file mode 100644
index 0000000..852897c
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/33/quic_result.json
@@ -0,0 +1,9 @@
+[{
+ "Tuple4": "90.143.189.5:8026-173.194.188.40:443-17-0",
+ "VERSION": "Google QUIC 33",
+ "SNI": "r3---sn-4g5ednse.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/53.0.2768.0",
+ "SNI": "r3---sn-4g5ednse.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/53.0.2768.0",
+ "name": "QUIC_RESULT_1"
+}]
diff --git a/test/decoders/quic/pcap/gquic/34/1-gquic-034-85.117.125.8.21243-173.194.73.102.443.pcap b/test/decoders/quic/pcap/gquic/34/1-gquic-034-85.117.125.8.21243-173.194.73.102.443.pcap
new file mode 100644
index 0000000..eee3c8e
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/34/1-gquic-034-85.117.125.8.21243-173.194.73.102.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/34/quic_result.json b/test/decoders/quic/pcap/gquic/34/quic_result.json
new file mode 100644
index 0000000..d1fd2c7
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/34/quic_result.json
@@ -0,0 +1,9 @@
+[{
+ "Tuple4": "85.117.125.8:21243-173.194.73.102:443-17-0",
+ "VERSION": "Google QUIC 34",
+ "SNI": "redirector.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/54.0.2823.2",
+ "SNI": "redirector.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/54.0.2823.2",
+ "name": "QUIC_RESULT_1"
+}]
diff --git a/test/decoders/quic/pcap/gquic/35/1-gquic-035-redirector.googlevideo.com-85.117.122.194.32370-173.194.220.138.443.pcap b/test/decoders/quic/pcap/gquic/35/1-gquic-035-redirector.googlevideo.com-85.117.122.194.32370-173.194.220.138.443.pcap
new file mode 100644
index 0000000..5dc1f81
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/35/1-gquic-035-redirector.googlevideo.com-85.117.122.194.32370-173.194.220.138.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/35/2-gquic-035-redirector.googlevideo.com-85.117.122.21.21396-173.194.220.138.443.pcap b/test/decoders/quic/pcap/gquic/35/2-gquic-035-redirector.googlevideo.com-85.117.122.21.21396-173.194.220.138.443.pcap
new file mode 100644
index 0000000..075c665
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/35/2-gquic-035-redirector.googlevideo.com-85.117.122.21.21396-173.194.220.138.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/35/quic_result.json b/test/decoders/quic/pcap/gquic/35/quic_result.json
new file mode 100644
index 0000000..276fce5
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/35/quic_result.json
@@ -0,0 +1,18 @@
+[{
+ "Tuple4": "85.117.122.194:32370-173.194.220.138:443-17-0",
+ "VERSION": "Google QUIC 35",
+ "SNI": "redirector.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/56.0.2900.3",
+ "SNI": "redirector.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/56.0.2900.3",
+ "SNI": "redirector.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/56.0.2900.3",
+ "name": "QUIC_RESULT_1"
+ }, {
+ "Tuple4": "85.117.122.21:21396-173.194.220.138:443-17-0",
+ "VERSION": "Google QUIC 35",
+ "SNI": "redirector.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/59.0.3068.4",
+ "name": "QUIC_RESULT_2"
+}]
+
diff --git a/test/decoders/quic/pcap/gquic/37/1-gquic-037-10.32.121.249.33765-64.233.161.95.443.pcap b/test/decoders/quic/pcap/gquic/37/1-gquic-037-10.32.121.249.33765-64.233.161.95.443.pcap
new file mode 100644
index 0000000..ca6388d
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/37/1-gquic-037-10.32.121.249.33765-64.233.161.95.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/37/2-gquic-037-10.35.127.134.42356-64.233.165.139.443.pcap b/test/decoders/quic/pcap/gquic/37/2-gquic-037-10.35.127.134.42356-64.233.165.139.443.pcap
new file mode 100644
index 0000000..86d19de
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/37/2-gquic-037-10.35.127.134.42356-64.233.165.139.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/37/3-gquic-037-185.57.74.232.43276-173.194.44.78.443.pcap b/test/decoders/quic/pcap/gquic/37/3-gquic-037-185.57.74.232.43276-173.194.44.78.443.pcap
new file mode 100644
index 0000000..4b3660b
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/37/3-gquic-037-185.57.74.232.43276-173.194.44.78.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/37/4-gquic-037-185.57.74.32.52134-74.125.232.249.443.pcap b/test/decoders/quic/pcap/gquic/37/4-gquic-037-185.57.74.32.52134-74.125.232.249.443.pcap
new file mode 100644
index 0000000..d075163
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/37/4-gquic-037-185.57.74.32.52134-74.125.232.249.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/37/5-gquic-037-195.162.27.132.31404-217.76.77.81.443.pcap b/test/decoders/quic/pcap/gquic/37/5-gquic-037-195.162.27.132.31404-217.76.77.81.443.pcap
new file mode 100644
index 0000000..73eec87
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/37/5-gquic-037-195.162.27.132.31404-217.76.77.81.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/37/6-gquic-037-195.162.27.132.31405-217.76.77.81.443.pcap b/test/decoders/quic/pcap/gquic/37/6-gquic-037-195.162.27.132.31405-217.76.77.81.443.pcap
new file mode 100644
index 0000000..b78a422
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/37/6-gquic-037-195.162.27.132.31405-217.76.77.81.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/37/7-gquic-037-85.117.112.160.21969-64.233.165.95.443.pcap b/test/decoders/quic/pcap/gquic/37/7-gquic-037-85.117.112.160.21969-64.233.165.95.443.pcap
new file mode 100644
index 0000000..8b53513
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/37/7-gquic-037-85.117.112.160.21969-64.233.165.95.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/37/8-gquic-037-85.117.113.62.29644-173.194.73.95.443.pcap b/test/decoders/quic/pcap/gquic/37/8-gquic-037-85.117.113.62.29644-173.194.73.95.443.pcap
new file mode 100644
index 0000000..370b9fc
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/37/8-gquic-037-85.117.113.62.29644-173.194.73.95.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/37/quic_result.json b/test/decoders/quic/pcap/gquic/37/quic_result.json
new file mode 100644
index 0000000..af84dd5
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/37/quic_result.json
@@ -0,0 +1,58 @@
+[
+ {
+ "Tuple4": "10.32.121.249:33765-64.233.161.95:443-17-0",
+ "VERSION": "Google QUIC 37",
+ "SNI": "instantmessaging-pa.googleapis.com",
+ "UA": "com.google.android.apps.tachyon Cronet/61.0.3142.0",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "10.35.127.134:42356-64.233.165.139:443-17-0",
+ "VERSION": "Google QUIC 37",
+ "SNI": "clients4.google.com",
+ "UA": "com.google.android.apps.maps Cronet/60.0.3108.3",
+ "name": "QUIC_RESULT_2"
+ },
+ {
+ "Tuple4": "185.57.74.232:43276-173.194.44.78:443-17-0",
+ "VERSION": "Google QUIC 37",
+ "SNI": "www.youtube.com",
+ "UA": "com.google.android.youtube Cronet/61.0.3142.0",
+ "name": "QUIC_RESULT_3"
+ },
+ {
+ "Tuple4": "185.57.74.32:52134-74.125.232.249:443-17-0",
+ "VERSION": "Google QUIC 37",
+ "SNI": "googleads.g.doubleclick.net",
+ "UA": "com.google.android.youtube Cronet/61.0.3142.0",
+ "name": "QUIC_RESULT_4"
+ },
+ {
+ "Tuple4": "195.162.27.132:31404-217.76.77.81:443-17-0",
+ "VERSION": "Google QUIC 37",
+ "SNI": "r6---sn-5auxa-unxe.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/61.0.3142.0",
+ "name": "QUIC_RESULT_5"
+ },
+ {
+ "Tuple4": "195.162.27.132:31405-217.76.77.81:443-17-0",
+ "VERSION": "Google QUIC 37",
+ "SNI": "r6---sn-5auxa-unxe.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/61.0.3142.0",
+ "name": "QUIC_RESULT_6"
+ },
+ {
+ "Tuple4": "85.117.112.160:21969-64.233.165.95:443-17-0",
+ "VERSION": "Google QUIC 37",
+ "SNI": "www.googleapis.com",
+ "UA": "com.google.android.youtube Cronet/60.0.3112.12",
+ "name": "QUIC_RESULT_7"
+ },
+ {
+ "Tuple4": "85.117.113.62:29644-173.194.73.95:443-17-0",
+ "VERSION": "Google QUIC 37",
+ "SNI": "youtubei.googleapis.com",
+ "UA": "com.google.android.youtube Cronet/61.0.3142.0",
+ "name": "QUIC_RESULT_8"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/gquic/39/1-gquic-039-redirector.googlevideo.com-85.117.119.45.22495-173.194.73.101.443.pcap b/test/decoders/quic/pcap/gquic/39/1-gquic-039-redirector.googlevideo.com-85.117.119.45.22495-173.194.73.101.443.pcap
new file mode 100644
index 0000000..bd088b0
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/39/1-gquic-039-redirector.googlevideo.com-85.117.119.45.22495-173.194.73.101.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/39/quic_result.json b/test/decoders/quic/pcap/gquic/39/quic_result.json
new file mode 100644
index 0000000..5f6b1fa
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/39/quic_result.json
@@ -0,0 +1,13 @@
+[{
+ "Tuple4": "85.117.119.45:22495-173.194.73.101:443-17-0",
+ "VERSION": "Google QUIC 39",
+ "SNI": "redirector.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/65.0.3322.0",
+ "SNI": "redirector.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/65.0.3322.0",
+ "SNI": "redirector.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/65.0.3322.0",
+ "SNI": "redirector.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/65.0.3322.0",
+ "name": "QUIC_RESULT_1"
+}]
diff --git a/test/decoders/quic/pcap/gquic/41/1-gquic-041-90.143.180.56.28496-64.233.165.113.443.pcap b/test/decoders/quic/pcap/gquic/41/1-gquic-041-90.143.180.56.28496-64.233.165.113.443.pcap
new file mode 100644
index 0000000..0a6e5ff
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/41/1-gquic-041-90.143.180.56.28496-64.233.165.113.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/41/2-gquic-041-90.143.189.30.53357-64.233.165.95.443.pcap b/test/decoders/quic/pcap/gquic/41/2-gquic-041-90.143.189.30.53357-64.233.165.95.443.pcap
new file mode 100644
index 0000000..171c264
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/41/2-gquic-041-90.143.189.30.53357-64.233.165.95.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/41/quic_result.json b/test/decoders/quic/pcap/gquic/41/quic_result.json
new file mode 100644
index 0000000..16b0a13
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/41/quic_result.json
@@ -0,0 +1,15 @@
+[{
+ "Tuple4": "90.143.180.56:28496-64.233.165.113:443-17-0",
+ "VERSION": "Google QUIC 41",
+ "SNI": "s.youtube.com",
+ "UA": "com.google.android.youtube Cronet/66.0.3335.4",
+ "SNI": "s.youtube.com",
+ "UA": "com.google.android.youtube Cronet/66.0.3335.4",
+ "name": "QUIC_RESULT_1"
+ }, {
+ "Tuple4": "90.143.189.30:53357-64.233.165.95:443-17-0",
+ "VERSION": "Google QUIC 41",
+ "SNI": "youtubei.googleapis.com",
+ "UA": "com.google.android.youtube Cronet/66.0.3335.4",
+ "name": "QUIC_RESULT_2"
+}]
diff --git a/test/decoders/quic/pcap/gquic/43/1-gquic-043-client.weixin.qq.com-112.43.145.231.18699-112.46.25.216.443.pcap b/test/decoders/quic/pcap/gquic/43/1-gquic-043-client.weixin.qq.com-112.43.145.231.18699-112.46.25.216.443.pcap
new file mode 100644
index 0000000..0abc861
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/43/1-gquic-043-client.weixin.qq.com-112.43.145.231.18699-112.46.25.216.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/43/2-gquic-043-without-sni-192.168.50.26.55209-34.102.215.99.443.pcap b/test/decoders/quic/pcap/gquic/43/2-gquic-043-without-sni-192.168.50.26.55209-34.102.215.99.443.pcap
new file mode 100644
index 0000000..ae75b00
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/43/2-gquic-043-without-sni-192.168.50.26.55209-34.102.215.99.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/43/3-gquic-043-without-sni-192.168.50.26.60851-34.102.215.99.443.pcap b/test/decoders/quic/pcap/gquic/43/3-gquic-043-without-sni-192.168.50.26.60851-34.102.215.99.443.pcap
new file mode 100644
index 0000000..3bad2e1
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/43/3-gquic-043-without-sni-192.168.50.26.60851-34.102.215.99.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/43/quic_result.json b/test/decoders/quic/pcap/gquic/43/quic_result.json
new file mode 100644
index 0000000..dbf2d06
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/43/quic_result.json
@@ -0,0 +1,23 @@
+[
+ {
+ "Tuple4": "112.43.145.231:18699-112.46.25.216:443-17-0",
+ "SNI": "client.weixin.qq.com",
+ "VERSION": "Google QUIC 43",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "192.168.50.26:55209-34.102.215.99:443-17-0",
+ "VERSION": "Google QUIC 43",
+ "name": "QUIC_RESULT_2"
+ },
+ {
+ "Tuple4": "192.168.50.26:60851-34.102.215.99:443-17-0",
+ "VERSION": "Google QUIC 43",
+ "name": "QUIC_RESULT_3"
+ },
+ {
+ "Tuple4": "192.168.50.26:63533-34.102.215.99:443-17-0",
+ "VERSION": "Google QUIC 43",
+ "name": "QUIC_RESULT_4"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/gquic/44/2-gquic-044-185.57.75.21.44739-173.194.73.132.443.pcap b/test/decoders/quic/pcap/gquic/44/2-gquic-044-185.57.75.21.44739-173.194.73.132.443.pcap
new file mode 100644
index 0000000..200d565
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/44/2-gquic-044-185.57.75.21.44739-173.194.73.132.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/44/3-gquic-044-212.154.234.46.62716-74.125.131.156.443.pcap b/test/decoders/quic/pcap/gquic/44/3-gquic-044-212.154.234.46.62716-74.125.131.156.443.pcap
new file mode 100644
index 0000000..963dc30
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/44/3-gquic-044-212.154.234.46.62716-74.125.131.156.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/44/4-gquic-044-2.135.246.186.56653-173.194.113.166.443.pcap b/test/decoders/quic/pcap/gquic/44/4-gquic-044-2.135.246.186.56653-173.194.113.166.443.pcap
new file mode 100644
index 0000000..677f71b
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/44/4-gquic-044-2.135.246.186.56653-173.194.113.166.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/44/quic_result.json b/test/decoders/quic/pcap/gquic/44/quic_result.json
new file mode 100644
index 0000000..839939d
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/44/quic_result.json
@@ -0,0 +1,23 @@
+[
+ {
+ "Tuple4": "185.57.75.21:44739-173.194.73.132:443-17-0",
+ "VERSION": "Google QUIC 44",
+ "SNI": "yt3.ggpht.com",
+ "UA": "dev Chrome/73.0.3664.3 Windows NT 6.1; Win64; x64",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "212.154.234.46:62716-74.125.131.156:443-17-0",
+ "VERSION": "Google QUIC 44",
+ "SNI": "stats.g.doubleclick.net",
+ "UA": "dev Chrome/73.0.3664.3 Windows NT 6.1; Win64; x64",
+ "name": "QUIC_RESULT_2"
+ },
+ {
+ "Tuple4": "2.135.246.186:56653-173.194.113.166:443-17-0",
+ "VERSION": "Google QUIC 44",
+ "SNI": "www.google-analytics.com",
+ "UA": "dev Chrome/73.0.3664.3 Windows NT 6.1",
+ "name": "QUIC_RESULT_3"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/gquic/46/1-gquic-046-36.142.158.169.16385-36.189.11.71.443.pcap b/test/decoders/quic/pcap/gquic/46/1-gquic-046-36.142.158.169.16385-36.189.11.71.443.pcap
new file mode 100644
index 0000000..0b77c4d
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/46/1-gquic-046-36.142.158.169.16385-36.189.11.71.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/46/2-gquic-046-pagead2.googlesyndication.com-172.16.30.79.65003-203.208.50.45.443.pcap b/test/decoders/quic/pcap/gquic/46/2-gquic-046-pagead2.googlesyndication.com-172.16.30.79.65003-203.208.50.45.443.pcap
new file mode 100644
index 0000000..7c2a2a4
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/46/2-gquic-046-pagead2.googlesyndication.com-172.16.30.79.65003-203.208.50.45.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/46/quic_result.json b/test/decoders/quic/pcap/gquic/46/quic_result.json
new file mode 100644
index 0000000..20d8213
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/46/quic_result.json
@@ -0,0 +1,16 @@
+[
+ {
+ "Tuple4": "36.142.158.169:16385-36.189.11.71:443-17-0",
+ "VERSION": "Google QUIC 46",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "172.16.30.79:65003-203.208.50.45:443-17-0",
+ "VERSION": "Google QUIC 46",
+ "SNI": "pagead2.googlesyndication.com",
+ "UA": "Chrome/79.0.3945.79 Windows NT 6.1; Win64; x64",
+ "SNI": "pagead2.googlesyndication.com",
+ "UA": "Chrome/79.0.3945.79 Windows NT 6.1; Win64; x64",
+ "name": "QUIC_RESULT_2"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/gquic/50/1-gquic-050-i.ytimg.com-172.20.9.135.65045-64.233.162.119.443.pcap b/test/decoders/quic/pcap/gquic/50/1-gquic-050-i.ytimg.com-172.20.9.135.65045-64.233.162.119.443.pcap
new file mode 100644
index 0000000..1d5c46a
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/50/1-gquic-050-i.ytimg.com-172.20.9.135.65045-64.233.162.119.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/50/2-gquic-050-www.google.com-172.20.9.135.61564-173.194.221.103.443.pcap b/test/decoders/quic/pcap/gquic/50/2-gquic-050-www.google.com-172.20.9.135.61564-173.194.221.103.443.pcap
new file mode 100644
index 0000000..4a58ff7
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/50/2-gquic-050-www.google.com-172.20.9.135.61564-173.194.221.103.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/50/3-gquic-050-www.youtube.com-172.20.9.135.49347-64.233.165.93.443.pcap b/test/decoders/quic/pcap/gquic/50/3-gquic-050-www.youtube.com-172.20.9.135.49347-64.233.165.93.443.pcap
new file mode 100644
index 0000000..81cad56
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/50/3-gquic-050-www.youtube.com-172.20.9.135.49347-64.233.165.93.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/gquic/50/quic_result.json b/test/decoders/quic/pcap/gquic/50/quic_result.json
new file mode 100644
index 0000000..68a30d7
--- /dev/null
+++ b/test/decoders/quic/pcap/gquic/50/quic_result.json
@@ -0,0 +1,23 @@
+[
+ {
+ "Tuple4": "172.20.9.135:65045-64.233.162.119:443-17-0",
+ "VERSION": "Google QUIC 50",
+ "SNI": "i.ytimg.com",
+ "UA": "Chrome/86.0.4240.75 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "172.20.9.135:61564-173.194.221.103:443-17-0",
+ "VERSION": "Google QUIC 50",
+ "SNI": "www.google.com",
+ "UA": "Chrome/86.0.4240.75 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_2"
+ },
+ {
+ "Tuple4": "172.20.9.135:49347-64.233.165.93:443-17-0",
+ "VERSION": "Google QUIC 50",
+ "SNI": "www.youtube.com",
+ "UA": "Chrome/86.0.4240.75 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_3"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/iquic/29/1-iquic-29-192.168.50.29.61891-31.13.77.35.443.pcap b/test/decoders/quic/pcap/iquic/29/1-iquic-29-192.168.50.29.61891-31.13.77.35.443.pcap
new file mode 100644
index 0000000..dcd22db
--- /dev/null
+++ b/test/decoders/quic/pcap/iquic/29/1-iquic-29-192.168.50.29.61891-31.13.77.35.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/iquic/29/2-iquic-29-223.104.233.102.13650-203.208.40.98.443.pcap b/test/decoders/quic/pcap/iquic/29/2-iquic-29-223.104.233.102.13650-203.208.40.98.443.pcap
new file mode 100644
index 0000000..3f2a3dc
--- /dev/null
+++ b/test/decoders/quic/pcap/iquic/29/2-iquic-29-223.104.233.102.13650-203.208.40.98.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/iquic/29/3-iquic-29-192.168.50.33.57220-114.250.70.38.443.pcap b/test/decoders/quic/pcap/iquic/29/3-iquic-29-192.168.50.33.57220-114.250.70.38.443.pcap
new file mode 100644
index 0000000..f6a188c
--- /dev/null
+++ b/test/decoders/quic/pcap/iquic/29/3-iquic-29-192.168.50.33.57220-114.250.70.38.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/iquic/29/quic_result.json b/test/decoders/quic/pcap/iquic/29/quic_result.json
new file mode 100644
index 0000000..ee59487
--- /dev/null
+++ b/test/decoders/quic/pcap/iquic/29/quic_result.json
@@ -0,0 +1,21 @@
+[
+ {
+ "Tuple4": "192.168.50.29:61891-31.13.77.35:443-17-0",
+ "SNI": "www.facebook.com",
+ "UA": "Chrome/86.0.4240.183 Windows NT 10.0; Win64; x64",
+ "VERSION": "IETF QUIC 29",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "223.104.233.102:13650-203.208.40.98:443-17-0",
+ "VERSION": "IETF QUIC 29",
+ "name": "QUIC_RESULT_2"
+ },
+ {
+ "Tuple4": "192.168.50.33:57220-114.250.70.38:443-17-0",
+ "SNI": "securepubads.g.doubleclick.net",
+ "UA": "Chrome/90.0.4430.72 Windows NT 10.0; Win64; x64",
+ "VERSION": "IETF QUIC 29",
+ "name": "QUIC_RESULT_3"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/mvfst/01/1-mvfst-01-i.instagram.com-192.168.60.9.55659-69.171.250.63.443.pcap b/test/decoders/quic/pcap/mvfst/01/1-mvfst-01-i.instagram.com-192.168.60.9.55659-69.171.250.63.443.pcap
new file mode 100644
index 0000000..be91a45
--- /dev/null
+++ b/test/decoders/quic/pcap/mvfst/01/1-mvfst-01-i.instagram.com-192.168.60.9.55659-69.171.250.63.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/mvfst/01/quic_result.json b/test/decoders/quic/pcap/mvfst/01/quic_result.json
new file mode 100644
index 0000000..672abbb
--- /dev/null
+++ b/test/decoders/quic/pcap/mvfst/01/quic_result.json
@@ -0,0 +1,8 @@
+[
+ {
+ "Tuple4": "192.168.60.9:55659-69.171.250.63:443-17-0",
+ "VERSION": "Facebook mvfst 01",
+ "SNI": "i.instagram.com",
+ "name": "QUIC_RESULT_1"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/mvfst/02/1-mvfst-02-192.168.137.141.50006-31.13.77.17.443.pcap b/test/decoders/quic/pcap/mvfst/02/1-mvfst-02-192.168.137.141.50006-31.13.77.17.443.pcap
new file mode 100644
index 0000000..9b88cc0
--- /dev/null
+++ b/test/decoders/quic/pcap/mvfst/02/1-mvfst-02-192.168.137.141.50006-31.13.77.17.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/mvfst/02/2-mvfst-02-192.168.137.141.63314-31.13.77.10.443.pcap b/test/decoders/quic/pcap/mvfst/02/2-mvfst-02-192.168.137.141.63314-31.13.77.10.443.pcap
new file mode 100644
index 0000000..27f209d
--- /dev/null
+++ b/test/decoders/quic/pcap/mvfst/02/2-mvfst-02-192.168.137.141.63314-31.13.77.10.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/mvfst/02/3-mvfst-02-192.168.137.141.60630-157.240.7.26.443.pcap b/test/decoders/quic/pcap/mvfst/02/3-mvfst-02-192.168.137.141.60630-157.240.7.26.443.pcap
new file mode 100644
index 0000000..4a6fa6c
--- /dev/null
+++ b/test/decoders/quic/pcap/mvfst/02/3-mvfst-02-192.168.137.141.60630-157.240.7.26.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/mvfst/02/4-mvfst-02-192.168.137.141.50293-157.240.15.13.443.pcap b/test/decoders/quic/pcap/mvfst/02/4-mvfst-02-192.168.137.141.50293-157.240.15.13.443.pcap
new file mode 100644
index 0000000..016d260
--- /dev/null
+++ b/test/decoders/quic/pcap/mvfst/02/4-mvfst-02-192.168.137.141.50293-157.240.15.13.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/mvfst/02/quic_result.json b/test/decoders/quic/pcap/mvfst/02/quic_result.json
new file mode 100644
index 0000000..a01dd35
--- /dev/null
+++ b/test/decoders/quic/pcap/mvfst/02/quic_result.json
@@ -0,0 +1,26 @@
+[
+ {
+ "Tuple4": "192.168.137.141:50006-31.13.77.17:443-17-0",
+ "VERSION": "Facebook mvfst 02",
+ "SNI": "graph.facebook.com",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "192.168.137.141:63314-31.13.77.10:443-17-0",
+ "VERSION": "Facebook mvfst 02",
+ "SNI": "gateway.facebook.com",
+ "name": "QUIC_RESULT_2"
+ },
+ {
+ "Tuple4": "192.168.137.141:60630-157.240.7.26:443-17-0",
+ "VERSION": "Facebook mvfst 02",
+ "SNI": "scontent-sin6-1.xx.fbcdn.net",
+ "name": "QUIC_RESULT_3"
+ },
+ {
+ "Tuple4": "192.168.137.141:50293-157.240.15.13:443-17-0",
+ "VERSION": "Facebook mvfst 02",
+ "SNI": "scontent-sin6-3.xx.fbcdn.net",
+ "name": "QUIC_RESULT_4"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/port-8443/1-iquic-29-192.168.50.49.58445-45.77.96.66.8443.pcap b/test/decoders/quic/pcap/port-8443/1-iquic-29-192.168.50.49.58445-45.77.96.66.8443.pcap
new file mode 100644
index 0000000..dd8e74a
--- /dev/null
+++ b/test/decoders/quic/pcap/port-8443/1-iquic-29-192.168.50.49.58445-45.77.96.66.8443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/port-8443/quic_result.json b/test/decoders/quic/pcap/port-8443/quic_result.json
new file mode 100644
index 0000000..630db3a
--- /dev/null
+++ b/test/decoders/quic/pcap/port-8443/quic_result.json
@@ -0,0 +1,7 @@
+[{
+ "Tuple4": "192.168.50.49:58445-45.77.96.66:8443-17-0",
+ "VERSION": "IETF QUIC 29",
+ "SNI": "quic.tech",
+ "UA": "Chrome/92.0.4515.159 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_1"
+}]
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.33232-173.194.220.105.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.33232-173.194.220.105.443.pcap
new file mode 100644
index 0000000..a2efae4
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.33232-173.194.220.105.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.39896-173.194.220.105.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.39896-173.194.220.105.443.pcap
new file mode 100644
index 0000000..5e20732
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.39896-173.194.220.105.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.43145-173.194.220.105.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.43145-173.194.220.105.443.pcap
new file mode 100644
index 0000000..9be1464
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.43145-173.194.220.105.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.49914-173.194.220.105.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.49914-173.194.220.105.443.pcap
new file mode 100644
index 0000000..1929ace
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.49914-173.194.220.105.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.54549-173.194.220.105.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.54549-173.194.220.105.443.pcap
new file mode 100644
index 0000000..019bc20
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.54549-173.194.220.105.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.57394-173.194.220.105.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.57394-173.194.220.105.443.pcap
new file mode 100644
index 0000000..2dd3dca
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.57394-173.194.220.105.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.62303-173.194.220.105.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.62303-173.194.220.105.443.pcap
new file mode 100644
index 0000000..9b2cdf6
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.62303-173.194.220.105.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.63484-173.194.220.105.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.63484-173.194.220.105.443.pcap
new file mode 100644
index 0000000..7cbfba3
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.70.63484-173.194.220.105.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.34722-74.125.131.106.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.34722-74.125.131.106.443.pcap
new file mode 100644
index 0000000..841f59a
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.34722-74.125.131.106.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.43786-74.125.131.106.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.43786-74.125.131.106.443.pcap
new file mode 100644
index 0000000..d6f9917
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.43786-74.125.131.106.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.44344-74.125.131.106.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.44344-74.125.131.106.443.pcap
new file mode 100644
index 0000000..e95bf1a
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.44344-74.125.131.106.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.44360-74.125.131.106.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.44360-74.125.131.106.443.pcap
new file mode 100644
index 0000000..fa1f129
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.44360-74.125.131.106.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.45109-74.125.131.106.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.45109-74.125.131.106.443.pcap
new file mode 100644
index 0000000..efd735f
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.45109-74.125.131.106.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.45314-74.125.131.106.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.45314-74.125.131.106.443.pcap
new file mode 100644
index 0000000..9ab26f1
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.45314-74.125.131.106.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.47041-74.125.131.106.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.47041-74.125.131.106.443.pcap
new file mode 100644
index 0000000..fe56a34
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.47041-74.125.131.106.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.47174-74.125.131.106.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.47174-74.125.131.106.443.pcap
new file mode 100644
index 0000000..928fe88
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.47174-74.125.131.106.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.50829-74.125.131.106.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.50829-74.125.131.106.443.pcap
new file mode 100644
index 0000000..a3951a5
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.50829-74.125.131.106.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.61598-74.125.131.106.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.61598-74.125.131.106.443.pcap
new file mode 100644
index 0000000..89bbf37
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.61598-74.125.131.106.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.62585-74.125.131.106.443.pcap b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.62585-74.125.131.106.443.pcap
new file mode 100644
index 0000000..5d00506
--- /dev/null
+++ b/test/decoders/quic/pcap/prox/prox-quic-217.76.77.73.62585-74.125.131.106.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/quic_len_-2.pcapng b/test/decoders/quic/pcap/quic_len_-2.pcapng
new file mode 100644
index 0000000..44f3965
--- /dev/null
+++ b/test/decoders/quic/pcap/quic_len_-2.pcapng
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-chlo-fragment/1-google-chlo-fragment-2.pcap b/test/decoders/quic/pcap/rfc9000-chlo-fragment/1-google-chlo-fragment-2.pcap
new file mode 100644
index 0000000..c9115fe
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-chlo-fragment/1-google-chlo-fragment-2.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-chlo-fragment/2-google-chlo-fragment-3.pcap b/test/decoders/quic/pcap/rfc9000-chlo-fragment/2-google-chlo-fragment-3.pcap
new file mode 100644
index 0000000..80ea303
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-chlo-fragment/2-google-chlo-fragment-3.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-chlo-fragment/3-facebook-chlo-fragment-2.pcap b/test/decoders/quic/pcap/rfc9000-chlo-fragment/3-facebook-chlo-fragment-2.pcap
new file mode 100644
index 0000000..afde2ee
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-chlo-fragment/3-facebook-chlo-fragment-2.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-chlo-fragment/4-googleapis.com-chlo-fragment-3.pcap b/test/decoders/quic/pcap/rfc9000-chlo-fragment/4-googleapis.com-chlo-fragment-3.pcap
new file mode 100644
index 0000000..1dd5354
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-chlo-fragment/4-googleapis.com-chlo-fragment-3.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-chlo-fragment/5-google_quic-2.pcap b/test/decoders/quic/pcap/rfc9000-chlo-fragment/5-google_quic-2.pcap
new file mode 100644
index 0000000..98b4cb2
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-chlo-fragment/5-google_quic-2.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-chlo-fragment/quic_result.json b/test/decoders/quic/pcap/rfc9000-chlo-fragment/quic_result.json
new file mode 100644
index 0000000..2f37adb
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-chlo-fragment/quic_result.json
@@ -0,0 +1,32 @@
+[
+ {
+ "Tuple4": "2607:5d00:2:2::38:2:53977-2404:6800:4005:807::2004:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "www.google.com",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "2607:5d00:2:2::38:2:50835-2404:6800:4005:80d::2003:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "www.google.com.hk",
+ "name": "QUIC_RESULT_2"
+ },
+ {
+ "Tuple4": "192.168.64.25:61166-157.240.245.35:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "www.facebook.com",
+ "name": "QUIC_RESULT_3"
+ },
+ {
+ "Tuple4": "2607:5d00:2:2::38:2:54817-2404:6800:4005:80c::200a:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "optimizationguide-pa.googleapis.com",
+ "name": "QUIC_RESULT_4"
+ },
+ {
+ "Tuple4": "192.168.54.157:60388-142.250.71.164:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "www.google.com",
+ "name": "QUIC_RESULT_5"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/rfc9000-fragment/1-addis-quic-sni-not-parsed-filtered.pcap b/test/decoders/quic/pcap/rfc9000-fragment/1-addis-quic-sni-not-parsed-filtered.pcap
new file mode 100644
index 0000000..34b5c2d
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-fragment/1-addis-quic-sni-not-parsed-filtered.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-fragment/2-quic-no-parse-sni-RFC9000-192.168.8.106.53736-142.250.185.36.443-6.pcap b/test/decoders/quic/pcap/rfc9000-fragment/2-quic-no-parse-sni-RFC9000-192.168.8.106.53736-142.250.185.36.443-6.pcap
new file mode 100644
index 0000000..eb8d602
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-fragment/2-quic-no-parse-sni-RFC9000-192.168.8.106.53736-142.250.185.36.443-6.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-fragment/3-quic-no-parse-sni-RFC9000-63821-443-192.168.8.106-142.250.185.36.pcap b/test/decoders/quic/pcap/rfc9000-fragment/3-quic-no-parse-sni-RFC9000-63821-443-192.168.8.106-142.250.185.36.pcap
new file mode 100644
index 0000000..c86c379
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-fragment/3-quic-no-parse-sni-RFC9000-63821-443-192.168.8.106-142.250.185.36.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-fragment/quic_result.json b/test/decoders/quic/pcap/rfc9000-fragment/quic_result.json
new file mode 100644
index 0000000..3dfe47f
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-fragment/quic_result.json
@@ -0,0 +1,37 @@
+[
+ {
+ "Tuple4": "192.168.8.106:57644-142.250.185.36:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "www.google.com",
+ "UA": "Chrome/94.0.4606.81 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "192.168.8.106:60687-142.250.185.36:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "www.google.com",
+ "UA": "Chrome/94.0.4606.81 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_2"
+ },
+ {
+ "Tuple4": "192.168.8.106:53622-142.250.185.36:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "www.google.com",
+ "UA": "Chrome/94.0.4606.81 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_3"
+ },
+ {
+ "Tuple4": "192.168.8.106:53736-142.250.185.36:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "www.google.com",
+ "UA": "Chrome/94.0.4606.81 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_4"
+ },
+ {
+ "Tuple4": "192.168.8.106:63821-142.250.185.36:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "www.google.com",
+ "UA": "Chrome/94.0.4606.81 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_5"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/rfc9000-special/1-quic-rc9000-no-parse-UA-192.168.8.110.49832-172.217.18.150.443.pcap b/test/decoders/quic/pcap/rfc9000-special/1-quic-rc9000-no-parse-UA-192.168.8.110.49832-172.217.18.150.443.pcap
new file mode 100644
index 0000000..f79f09d
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-special/1-quic-rc9000-no-parse-UA-192.168.8.110.49832-172.217.18.150.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-special/2-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.50339-172.217.169.227.443.pcap b/test/decoders/quic/pcap/rfc9000-special/2-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.50339-172.217.169.227.443.pcap
new file mode 100644
index 0000000..c53cbee
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-special/2-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.50339-172.217.169.227.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-special/3-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.52455-213.55.110.12.443.pcap b/test/decoders/quic/pcap/rfc9000-special/3-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.52455-213.55.110.12.443.pcap
new file mode 100644
index 0000000..3a1736c
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-special/3-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.52455-213.55.110.12.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-special/4-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.64550-142.250.185.36.443.pcap b/test/decoders/quic/pcap/rfc9000-special/4-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.64550-142.250.185.36.443.pcap
new file mode 100644
index 0000000..09e3dc2
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-special/4-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.64550-142.250.185.36.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-special/5-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.65140-213.55.110.13.443.pcap b/test/decoders/quic/pcap/rfc9000-special/5-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.65140-213.55.110.13.443.pcap
new file mode 100644
index 0000000..7e484b7
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-special/5-quic-rfc9000-no-parse-SNI-UA-192.168.8.110.65140-213.55.110.13.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000-special/quic_result.json b/test/decoders/quic/pcap/rfc9000-special/quic_result.json
new file mode 100644
index 0000000..bb9562a
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000-special/quic_result.json
@@ -0,0 +1,37 @@
+[
+ {
+ "Tuple4": "192.168.8.110:49832-172.217.18.150:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "i.ytimg.com",
+ "UA": "Chrome/95.0.4638.54 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "192.168.8.110:50339-172.217.169.227:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "www.google.com.et",
+ "UA": "Chrome/95.0.4638.54 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_2"
+ },
+ {
+ "Tuple4": "192.168.8.110:52455-213.55.110.12:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "r1---sn-xuj-5qqz.googlevideo.com",
+ "UA": "Chrome/95.0.4638.54 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_3"
+ },
+ {
+ "Tuple4": "192.168.8.110:64550-142.250.185.36:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "www.google.com",
+ "UA": "Chrome/95.0.4638.54 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_4"
+ },
+ {
+ "Tuple4": "192.168.8.110:65140-213.55.110.13:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "r2---sn-xuj-5qqz.googlevideo.com",
+ "UA": "Chrome/95.0.4638.54 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_5"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/rfc9000/1-ietf-rfc9000-192.168.60.32.59699-64.233.164.84.443.pcap b/test/decoders/quic/pcap/rfc9000/1-ietf-rfc9000-192.168.60.32.59699-64.233.164.84.443.pcap
new file mode 100644
index 0000000..84097c8
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000/1-ietf-rfc9000-192.168.60.32.59699-64.233.164.84.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000/2-ietf-rfc9000-124.88.191.113.39716-114.250.66.33-443.pcap b/test/decoders/quic/pcap/rfc9000/2-ietf-rfc9000-124.88.191.113.39716-114.250.66.33-443.pcap
new file mode 100644
index 0000000..ee760fb
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000/2-ietf-rfc9000-124.88.191.113.39716-114.250.66.33-443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/rfc9000/quic_result.json b/test/decoders/quic/pcap/rfc9000/quic_result.json
new file mode 100644
index 0000000..e5155b1
--- /dev/null
+++ b/test/decoders/quic/pcap/rfc9000/quic_result.json
@@ -0,0 +1,15 @@
+[
+ {
+ "Tuple4": "192.168.60.32:59699-64.233.164.84:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "accounts.google.com",
+ "UA": "Chrome/92.0.4515.159 Windows NT 10.0; Win64; x64",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "124.88.191.113:39716-114.250.66.33:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "safebrowsing.googleapis.com",
+ "name": "QUIC_RESULT_2"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/pcap/special/1-no-parse-sni-draft-29-10.83.31.23.37645-142.250.180.42.443.pcap b/test/decoders/quic/pcap/special/1-no-parse-sni-draft-29-10.83.31.23.37645-142.250.180.42.443.pcap
new file mode 100644
index 0000000..ad40e1d
--- /dev/null
+++ b/test/decoders/quic/pcap/special/1-no-parse-sni-draft-29-10.83.31.23.37645-142.250.180.42.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/special/1-no-parse-sni-draft-29.37645.pcap b/test/decoders/quic/pcap/special/1-no-parse-sni-draft-29.37645.pcap
new file mode 100644
index 0000000..ad40e1d
--- /dev/null
+++ b/test/decoders/quic/pcap/special/1-no-parse-sni-draft-29.37645.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/special/2-no-parse-sni-rfc9000-192.168.1.6.61269-142.250.180.42.443.pcap b/test/decoders/quic/pcap/special/2-no-parse-sni-rfc9000-192.168.1.6.61269-142.250.180.42.443.pcap
new file mode 100644
index 0000000..ad965e8
--- /dev/null
+++ b/test/decoders/quic/pcap/special/2-no-parse-sni-rfc9000-192.168.1.6.61269-142.250.180.42.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/special/2-no-parse-sni-rfc9000.61269.pcap b/test/decoders/quic/pcap/special/2-no-parse-sni-rfc9000.61269.pcap
new file mode 100644
index 0000000..ad965e8
--- /dev/null
+++ b/test/decoders/quic/pcap/special/2-no-parse-sni-rfc9000.61269.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/special/3-no-parse-sni-gquic-43-10.129.24.130.52558-213.55.110.12.443.pcap b/test/decoders/quic/pcap/special/3-no-parse-sni-gquic-43-10.129.24.130.52558-213.55.110.12.443.pcap
new file mode 100644
index 0000000..9ea020b
--- /dev/null
+++ b/test/decoders/quic/pcap/special/3-no-parse-sni-gquic-43-10.129.24.130.52558-213.55.110.12.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/special/4-no-sni-rfc9000-197.156.101.97.12388-142.250.185.46.443.pcap b/test/decoders/quic/pcap/special/4-no-sni-rfc9000-197.156.101.97.12388-142.250.185.46.443.pcap
new file mode 100644
index 0000000..36d7457
--- /dev/null
+++ b/test/decoders/quic/pcap/special/4-no-sni-rfc9000-197.156.101.97.12388-142.250.185.46.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/special/quic_result.json b/test/decoders/quic/pcap/special/quic_result.json
new file mode 100644
index 0000000..362c6e4
--- /dev/null
+++ b/test/decoders/quic/pcap/special/quic_result.json
@@ -0,0 +1,26 @@
+[{
+ "Tuple4": "10.83.31.23:37645-142.250.180.42:443-17-0",
+ "VERSION": "IETF QUIC 29",
+ "SNI": "play.googleapis.com",
+ "UA": "com.google.android.gms Cronet/92.0.4515.131",
+ "VERSION": "IETF QUIC 29",
+ "name": "QUIC_RESULT_1"
+ }, {
+ "Tuple4": "192.168.1.6:61269-142.250.180.42:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "SNI": "content-autofill.googleapis.com",
+ "UA": "Chrome/94.0.4606.81 Windows NT 10.0; Win64; x64",
+ "VERSION": "IETF QUIC RFC9000",
+ "name": "QUIC_RESULT_2"
+ }, {
+ "Tuple4": "10.129.24.130:52558-213.55.110.12:443-17-0",
+ "VERSION": "Google QUIC 43",
+ "SNI": "r1---sn-xuj-5qqz.googlevideo.com",
+ "UA": "com.google.android.youtube Cronet/80.0.3970.3",
+ "VERSION": "Google QUIC 43",
+ "name": "QUIC_RESULT_3"
+}, {
+ "Tuple4": "197.156.101.97:12388-142.250.185.46:443-17-0",
+ "VERSION": "IETF QUIC RFC9000",
+ "name": "QUIC_RESULT_4"
+}]
diff --git a/test/decoders/quic/pcap/tquic/1-tquic-51-195.12.120.14.41803-173.194.222.101.443.pcap b/test/decoders/quic/pcap/tquic/1-tquic-51-195.12.120.14.41803-173.194.222.101.443.pcap
new file mode 100644
index 0000000..357777c
--- /dev/null
+++ b/test/decoders/quic/pcap/tquic/1-tquic-51-195.12.120.14.41803-173.194.222.101.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/tquic/2-tquic-51-195.12.120.14.39526-64.233.165.113.443.pcap b/test/decoders/quic/pcap/tquic/2-tquic-51-195.12.120.14.39526-64.233.165.113.443.pcap
new file mode 100644
index 0000000..f64152a
--- /dev/null
+++ b/test/decoders/quic/pcap/tquic/2-tquic-51-195.12.120.14.39526-64.233.165.113.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/tquic/3-tquic-51-195.12.120.14.41747-173.194.222.138.443.pcap b/test/decoders/quic/pcap/tquic/3-tquic-51-195.12.120.14.41747-173.194.222.138.443.pcap
new file mode 100644
index 0000000..8d87b8d
--- /dev/null
+++ b/test/decoders/quic/pcap/tquic/3-tquic-51-195.12.120.14.41747-173.194.222.138.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/tquic/4-tquic-51-195.12.120.14.59012-173.194.222.138.443.pcap b/test/decoders/quic/pcap/tquic/4-tquic-51-195.12.120.14.59012-173.194.222.138.443.pcap
new file mode 100644
index 0000000..bb67486
--- /dev/null
+++ b/test/decoders/quic/pcap/tquic/4-tquic-51-195.12.120.14.59012-173.194.222.138.443.pcap
Binary files differ
diff --git a/test/decoders/quic/pcap/tquic/quic_result.json b/test/decoders/quic/pcap/tquic/quic_result.json
new file mode 100644
index 0000000..1737864
--- /dev/null
+++ b/test/decoders/quic/pcap/tquic/quic_result.json
@@ -0,0 +1,30 @@
+[
+ {
+ "Tuple4": "195.12.120.14:41803-173.194.222.101:443-17-0",
+ "VERSION": "Google QUIC with TLS 51",
+ "SNI": "clients4.google.com",
+ "UA": "Chrome/87.0.4280.101 Android 9; ANE-LX1",
+ "name": "QUIC_RESULT_1"
+ },
+ {
+ "Tuple4": "195.12.120.14:39526-64.233.165.113:443-17-0",
+ "VERSION": "Google QUIC with TLS 51",
+ "SNI": "m.youtube.com",
+ "UA": "Chrome/87.0.4280.101 Android 9; ANE-LX1",
+ "name": "QUIC_RESULT_2"
+ },
+ {
+ "Tuple4": "195.12.120.14:41747-173.194.222.138:443-17-0",
+ "VERSION": "Google QUIC with TLS 51",
+ "SNI": "clients4.google.com",
+ "UA": "Chrome/87.0.4280.101 Android 9; ANE-LX1",
+ "name": "QUIC_RESULT_3"
+ },
+ {
+ "Tuple4": "195.12.120.14:59012-173.194.222.138:443-17-0",
+ "VERSION": "Google QUIC with TLS 51",
+ "SNI": "clients4.google.com",
+ "UA": "Chrome/87.0.4280.101 Android 9; ANE-LX1",
+ "name": "QUIC_RESULT_4"
+ }
+] \ No newline at end of file
diff --git a/test/decoders/quic/quic_spec.toml b/test/decoders/quic/quic_spec.toml
new file mode 100644
index 0000000..b9ebe05
--- /dev/null
+++ b/test/decoders/quic/quic_spec.toml
@@ -0,0 +1,12 @@
+
+#load quic module
+[[module]]
+path = ""
+init = "quic_init"
+exit = "quic_exit"
+
+#load quic gtest module
+[[module]]
+path = ""
+init = "gtest_quic_init"
+exit = "gtest_quic_exit"
diff --git a/vendors/CMakeLists.txt b/vendors/CMakeLists.txt
index db34d81..2a36df9 100644
--- a/vendors/CMakeLists.txt
+++ b/vendors/CMakeLists.txt
@@ -98,3 +98,28 @@ add_dependencies(libevent-static libevent)
set_property(TARGET libevent-static PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/libevent.a)
set_property(TARGET libevent-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include)
set_property(TARGET libevent-static PROPERTY INTERFACE_LINK_LIBRARIES pthread)
+
+### OpenSSL 1.1.1
+ExternalProject_Add(OpenSSL PREFIX openssl
+ URL ${CMAKE_CURRENT_SOURCE_DIR}/openssl-1.1.1l.tar.gz
+ URL_MD5 ac0d4387f3ba0ad741b0580dd45f6ff3
+ CONFIGURE_COMMAND ./Configure linux-x86_64 --prefix=<INSTALL_DIR> --openssldir=<INSTALL_DIR>/lib/ssl enable-ec_nistp_64_gcc_128 no-shared
+ BUILD_COMMAND ${MAKE_COMMAND}
+ INSTALL_COMMAND make install_sw
+ BUILD_IN_SOURCE 1)
+
+ExternalProject_Get_Property(OpenSSL INSTALL_DIR)
+set(OPENSSL_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include)
+set(OPENSSL_LINK_DIRECTORIES ${INSTALL_DIR}/lib)
+set(OPENSSL_PKGCONFIG_PATH ${INSTALL_DIR}/lib/pkgconfig/)
+file(MAKE_DIRECTORY ${INSTALL_DIR}/include)
+
+add_library(openssl-crypto-static STATIC IMPORTED GLOBAL)
+add_dependencies(openssl-crypto-static OpenSSL)
+set_property(TARGET openssl-crypto-static PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/libcrypto.a)
+set_property(TARGET openssl-crypto-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include)
+
+add_library(openssl-ssl-static STATIC IMPORTED GLOBAL)
+add_dependencies(openssl-ssl-static OpenSSL)
+set_property(TARGET openssl-ssl-static PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/libssl.a)
+set_property(TARGET openssl-ssl-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include)
diff --git a/vendors/openssl-1.1.1l.tar.gz b/vendors/openssl-1.1.1l.tar.gz
new file mode 100644
index 0000000..81be7f9
--- /dev/null
+++ b/vendors/openssl-1.1.1l.tar.gz
Binary files differ