diff --git a/.github/workflows/plot-goodput.py b/.github/workflows/plot-goodput.py new file mode 100644 index 00000000..1116c115 --- /dev/null +++ b/.github/workflows/plot-goodput.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +import os +import sys + +import json +import matplotlib.pyplot as plt +import numpy as np + +# QUIC implementes +IMPLS = ["tquic", "lsquic", "picoquic", "quiche"] + +# Different file sizes +SIZES = ["10m", "1m", "100k"] + +# Different loss rates +RATES = [0, 1, 3, 5] + +# Running count for each test +COUNT = 10 + + +# Read a measurement file generated by goodput testing +def read_data(data_dir, impl, cc, size, loss): + dirname = "goodput%s-%s-%s" % (size, cc, impl) + filename = "goodput%s-%s-%s-%s.json" % (size, loss, cc, impl) + path = os.path.join(data_dir, dirname, filename) + try: + with open(path) as f: + data = json.load(f) + return data["measurements"][0][0]["data"] + except: + return None + + +# Plot the throughput graph for the specified CC algorithm under different file +# sizes and packet loss rates. +def plot(data_dir, cc): + fig, axs = plt.subplots(len(SIZES), len(RATES), figsize=(15,10)) + x = np.linspace(0, COUNT, COUNT) + for i in range(len(SIZES)): + for j in range(len(RATES)): + for impl in IMPLS: + data = read_data(data_dir, impl, cc, SIZES[i], RATES[j]) + if data is None or len(data) != COUNT: + continue + axs[i, j].plot(x, data, label=impl, marker="o") + axs[i, j].set_xlabel("Run #") + axs[i, j].set_ylabel("Goodput") + axs[i, j].set_title("%s loss rate %s%%" % (SIZES[i], RATES[j])) + axs[i, j].legend() + plt.suptitle(cc.upper()) + plt.tight_layout() + plt.savefig("goodput-%s.png" % (cc), dpi=300) + + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Usage: %s [data_dir]" % (sys.argv[0])) + exit(1) + + data_dir= sys.argv[1] + plot(data_dir, "bbr") + plot(data_dir, "cubic") + diff --git a/.github/workflows/plot-interop.py b/.github/workflows/plot-interop.py new file mode 100644 index 00000000..e0877970 --- /dev/null +++ b/.github/workflows/plot-interop.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +import os +import sys + +import json +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.colors import ListedColormap + +# QUIC implementes +CLIENT_IMPLS = ["tquic", "lsquic", "quiche", "picoquic", "ngtcp2", "msquic", + "s2n-quic", "quinn", "neqo", "kwik", "aioquic", "chrome", + "go-x-net", "quic-go", "mvfst"] + +SERVER_IMPLS = ["lsquic", "quiche", "picoquic", "ngtcp2", "msquic", "s2n-quic", + "quinn", "neqo", "kwik", "aioquic", "nginx", "haproxy", + "go-x-net", "quic-go", "mvfst"] + +# Interop test cases +INTEROP_TESTS = ["handshake", "retry", "resumption", "zerortt", "amplificationlimit", + "http3", "ipv6", "transfer", "multiplexing", "longrtt", "blackhole", + "handshakeloss", "handshakecorruption", "transferloss","transfercorruption"] + + +# Read a interop file generated by interop testing +def read_data(data_dir, server, client): + dirname = "%s-%s/%s-%s-logs" % (server, client, server, client) + path = os.path.join(data_dir, dirname, "interop.json") + try: + with open(path) as f: + data = json.load(f) + return convert_data(data["results"][0]) + except: + return [0] * len(INTEROP_TESTS) + + +# Convert interop results to a vector +def convert_data(result): + data = [0] * len(INTEROP_TESTS) + for i, name in enumerate(INTEROP_TESTS): + for item in result: + if item["name"] != name: + continue + if item["result"] == "succeeded": + data[i] = 3 + elif item["result"] == "unsupported": + data[i] = 2 + else: + data[i] = 1 + break + return data + + +# Convert test result to a letter +def convert_text(value): + if value == 3: + return "Y" # success + elif value == 2: + return "U" # unsupported + elif value == 1: + return "N" # failure + else: + return "-" # unknown + + +# Plot the interop graph +def plot(data_dir, is_tquic_server): + impls = CLIENT_IMPLS if is_tquic_server else SERVER_IMPLS + name = "server" if is_tquic_server else "client" + if is_tquic_server: + data = [read_data(data_dir, "tquic", impl) for impl in impls] + else: + data = [read_data(data_dir, impl, "tquic") for impl in impls] + interop_result = np.array(data) + print(interop_result) + + fig, ax = plt.subplots() + im = ax.imshow(interop_result, cmap=ListedColormap(['gray', 'red', 'blue', 'green']), + interpolation='nearest') + ax.set_xticks(np.arange(len(INTEROP_TESTS)), labels=INTEROP_TESTS) + ax.set_yticks(np.arange(len(impls)), labels=impls) + plt.setp(ax.get_xticklabels(), rotation=45, ha="right", + rotation_mode="anchor") + for i in range(len(INTEROP_TESTS)): + for j in range(len(impls)): + text = ax.text(j, i, convert_text(interop_result[i, j]), + ha="center", va="center", color="w") + ax.set_title("TQUIC %s interop results" % (name)) + fig.tight_layout() + + filename = "interop-tquic-%s.png" % (name) + plt.savefig(filename, dpi=300) + + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Usage: %s [data_dir]" % (sys.argv[0])) + exit(1) + + data_dir= sys.argv[1] + plot(data_dir, True) + plot(data_dir, False) + diff --git a/.github/workflows/tquic-goodput.yml b/.github/workflows/tquic-goodput.yml index 90b495aa..0460cf97 100644 --- a/.github/workflows/tquic-goodput.yml +++ b/.github/workflows/tquic-goodput.yml @@ -23,6 +23,13 @@ jobs: # You can manually trigger it if necessary. if: ${{ ( github.event_name == 'schedule' && github.repository == 'tencent/tquic' ) || github.event_name == 'workflow_dispatch' }} steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + + - name: Build docker image + run: docker build -t tquic_interop:v1 -f interop/Dockerfile . + - name: Install quic-interop-runner run: | git clone https://github.com/tquic-group/quic-interop-runner.git @@ -38,15 +45,15 @@ jobs: - name: Run the interop tests run: | cd quic-interop-runner - python3 run.py -r $QUIC_IMAGES -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=0 --rate_to_client=0" -j ${{ matrix.case }}-0-${{ matrix.cc }}-${{ matrix.impl }}.json - python3 run.py -r $QUIC_IMAGES -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=1 --rate_to_client=1" -j ${{ matrix.case }}-1-${{ matrix.cc }}-${{ matrix.impl }}.json - python3 run.py -r $QUIC_IMAGES -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=3 --rate_to_client=3" -j ${{ matrix.case }}-3-${{ matrix.cc }}-${{ matrix.impl }}.json - python3 run.py -r $QUIC_IMAGES -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=5 --rate_to_client=5" -j ${{ matrix.case }}-5-${{ matrix.cc }}-${{ matrix.impl }}.json + python3 run.py -r "$QUIC_IMAGES,tquic=tquic_interop:v1" -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=0 --rate_to_client=0" -j ${{ matrix.case }}-0-${{ matrix.cc }}-${{ matrix.impl }}.json + python3 run.py -r "$QUIC_IMAGES,tquic=tquic_interop:v1" -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=1 --rate_to_client=1" -j ${{ matrix.case }}-1-${{ matrix.cc }}-${{ matrix.impl }}.json + python3 run.py -r "$QUIC_IMAGES,tquic=tquic_interop:v1" -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=3 --rate_to_client=3" -j ${{ matrix.case }}-3-${{ matrix.cc }}-${{ matrix.impl }}.json + python3 run.py -r "$QUIC_IMAGES,tquic=tquic_interop:v1" -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=5 --rate_to_client=5" -j ${{ matrix.case }}-5-${{ matrix.cc }}-${{ matrix.impl }}.json - name: Store measurement results uses: actions/upload-artifact@v4 with: - name: ${{ matrix.impl }}-${{ matrix.case }}-${{ matrix.cc }} + name: ${{ matrix.case }}-${{ matrix.cc }}-${{ matrix.impl }} path: quic-interop-runner/goodput*.json result: @@ -55,8 +62,25 @@ jobs: steps: - name: Download all workflow run artifacts uses: actions/download-artifact@v4 + - name: Display structure of downloaded files run: ls -R + + - name: Display all measurement details + run: grep "details.*" . -Ro + + - name: Download plot tools + uses: actions/checkout@v4 + with: + path: tools + + - name: Install dependences + run: | + sudo apt install python3-matplotlib + + - name: Plot all measurement results + run: python3 tools/.github/workflows/plot-goodput.py . + - name: Store all measurement results uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/tquic-interop-all.yml b/.github/workflows/tquic-interop-all.yml index 88af0f36..c5f33655 100644 --- a/.github/workflows/tquic-interop-all.yml +++ b/.github/workflows/tquic-interop-all.yml @@ -113,7 +113,7 @@ jobs: python3 -m json.tool ${{ matrix.server }}-${{ matrix.client }}-logs/interop.json - name: Store interop logs - if: ${{ failure() }} + if: ${{ always() }} uses: actions/upload-artifact@v4 with: name: ${{ matrix.server }}-${{ matrix.client }} @@ -121,3 +121,38 @@ jobs: quic-interop-runner/*logs/* !quic-interop-runner/*logs/**/crosstraffic/ !quic-interop-runner/*logs/**/goodput/ + + result: + runs-on: ubuntu-latest + needs: tquic_interop_testing + if: ${{ (( github.event_name == 'schedule' && github.repository == 'tencent/tquic' ) || github.event_name == 'workflow_dispatch') && !cancelled() }} + steps: + - name: Download all workflow run artifacts + uses: actions/download-artifact@v4 + + - name: Display structure of downloaded files + run: ls -R + + - name: Display failed interop tests + run: grep -Ho "{[^{]*failed" */*/*.json + + - name: Download plot tools + uses: actions/checkout@v4 + with: + path: tools + + - name: Install dependences + run: | + sudo apt install python3-matplotlib + + - name: Plot all interop results + run: python3 tools/.github/workflows/plot-interop.py ./ + + - name: Store all interop results + uses: actions/upload-artifact@v4 + with: + name: tquic-interop-all-result + path: | + interop*.png + */*logs/* + !*/*logs/**/**/sim/ diff --git a/.github/workflows/tquic-interop-main.yaml b/.github/workflows/tquic-interop-main.yaml index 48fb01ee..06beec80 100644 --- a/.github/workflows/tquic-interop-main.yaml +++ b/.github/workflows/tquic-interop-main.yaml @@ -27,8 +27,6 @@ jobs: client: tquic - server: tquic client: ngtcp2 - - server: ngtcp2 - client: tquic steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c374d3d..d3ccf103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,17 +11,35 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v0.9.0] - 2024-04-10 + +### Added +- Improve FFI for quic_tls_config_t +- Update the handling of probe timeout to conform with RFC 9002 +- Update limit of the output buffer for Connection::send() +- Add plot tools for goodput and interop testing + +### Changed +- Change `quic_config_set_tls_config()` in FFI +- Change `quic_tls_config_select_methods_t` in FFI + +### Fixed +- Fix NewToken frame in qlog +- Fix the unit test case `conn_write_qlog` that fails with low probability + +### Security +- limit the number of queued RETIRE_CONNECTION_ID frames + + ## [v0.8.1] - 2024-03-18 ### Removed - - Remove the sfv feature flag from h3 (to resolve a build issue at docs.rs) ## [v0.8.0] - 2024-03-15 ### Added - - Support anti-amplification limit for server - Support customized config when initiating a connection - Add callback based FFI for writing the keylog and qlog @@ -88,7 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - tquic_client: output the stats first and then exit when it receives an SIGINT signal. ### Changed -- Simplify FFI quic_set_logger() to avoid from return unnessary errors +- Simplify FFI quic_set_logger() to avoid from return unnecessary errors - Rename set_multipath() in Config to enable_multipath() - Rename set_multipath_algor() in Config to set_multipath_algorithm() - Change default congestion control algorithm to BBR @@ -164,6 +182,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Provide example clients and servers. +[v0.9.0]: https://github.com/tencent/tquic/compare/v0.8.1...v0.9.0 [v0.8.1]: https://github.com/tencent/tquic/compare/v0.8.0...v0.8.1 [v0.8.0]: https://github.com/tencent/tquic/compare/v0.7.0...v0.8.0 [v0.7.0]: https://github.com/tencent/tquic/compare/v0.6.0...v0.7.0 diff --git a/Cargo.toml b/Cargo.toml index 12d38b38..cd03ec04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tquic" -version = "0.8.1" +version = "0.9.0" edition = "2021" rust-version = "1.70.0" license = "Apache-2.0" diff --git a/include/tquic.h b/include/tquic.h index aa592877..69878b61 100644 --- a/include/tquic.h +++ b/include/tquic.h @@ -137,9 +137,11 @@ typedef struct http3_conn_t http3_conn_t; */ typedef struct http3_headers_t http3_headers_t; +typedef struct quic_tls_config_t quic_tls_config_t; + typedef struct quic_tls_config_select_methods_t { - SSL_CTX *(*get_default)(void *ctx); - SSL_CTX *(*select)(void *ctx, const uint8_t *server_name, size_t server_name_len); + struct quic_tls_config_t *(*get_default)(void *ctx); + struct quic_tls_config_t *(*select)(void *ctx, const uint8_t *server_name, size_t server_name_len); } quic_tls_config_select_methods_t; typedef void *quic_tls_config_select_context_t; @@ -317,8 +319,9 @@ void quic_config_set_max_handshake_timeout(struct quic_config_t *config, uint64_ void quic_config_set_recv_udp_payload_size(struct quic_config_t *config, uint16_t v); /** - * Set the maximum outgoing UDP payload size. + * Set the initial maximum outgoing UDP payload size. * The default and minimum value is `1200`. + * * The configuration should be changed with caution. The connection may * not work properly if an inappropriate value is set. */ @@ -488,6 +491,86 @@ void quic_config_set_cid_len(struct quic_config_t *config, uint8_t v); */ void quic_config_set_send_batch_size(struct quic_config_t *config, uint16_t v); +/** + * Create a new TlsConfig. + * The caller is responsible for the memory of the TlsConfig and should properly + * destroy it by calling `quic_tls_config_free`. + */ +struct quic_tls_config_t *quic_tls_config_new(void); + +/** + * Create a new TlsConfig with SSL_CTX. + * When using raw SSL_CTX, TlsSession::session() and TlsSession::set_keylog() won't take effect. + * The caller is responsible for the memory of TlsConfig and SSL_CTX when use this function. + */ +struct quic_tls_config_t *quic_tls_config_new_with_ssl_ctx(SSL_CTX *ssl_ctx); + +/** + * Create a new client side TlsConfig. + * The caller is responsible for the memory of the TlsConfig and should properly + * destroy it by calling `quic_tls_config_free`. + */ +struct quic_tls_config_t *quic_tls_config_new_client_config(const char *const *protos, + intptr_t proto_num, + bool enable_early_data); + +/** + * Create a new server side TlsConfig. + * The caller is responsible for the memory of the TlsConfig and should properly + * destroy it by calling `quic_tls_config_free`. + */ +struct quic_tls_config_t *quic_tls_config_new_server_config(const char *cert_file, + const char *key_file, + const char *const *protos, + intptr_t proto_num, + bool enable_early_data); + +/** + * Destroy a TlsConfig instance. + */ +void quic_tls_config_free(struct quic_tls_config_t *tls_config); + +/** + * Set whether early data is allowed. + */ +void quic_tls_config_set_early_data_enabled(struct quic_tls_config_t *tls_config, bool enable); + +/** + * Set the list of supported application protocols. + */ +int quic_tls_config_set_application_protos(struct quic_tls_config_t *tls_config, + const char *const *protos, + intptr_t proto_num); + +/** + * Set session ticket key for server. + */ +int quic_tls_config_set_ticket_key(struct quic_tls_config_t *tls_config, + const uint8_t *ticket_key, + size_t ticket_key_len); + +/** + * Set the certificate verification behavior. + */ +void quic_tls_config_set_verify(struct quic_tls_config_t *tls_config, bool verify); + +/** + * Set the PEM-encoded certificate file. + */ +int quic_tls_config_set_certificate_file(struct quic_tls_config_t *tls_config, + const char *cert_file); + +/** + * Set the PEM-encoded private key file. + */ +int quic_tls_config_set_private_key_file(struct quic_tls_config_t *tls_config, + const char *key_file); + +/** + * Set CA certificates. + */ +int quic_tls_config_set_ca_certs(struct quic_tls_config_t *tls_config, const char *ca_path); + /** * Set TLS config selector. */ @@ -497,9 +580,11 @@ void quic_config_set_tls_selector(struct quic_config_t *config, /** * Set TLS config. - * The caller is responsible for the memory of SSL_CTX when use this function. + * + * Note: Config doesn't own the TlsConfig when using this function. + * It is the responsibility of the caller to release it. */ -void quic_config_set_tls_config(struct quic_config_t *config, SSL_CTX *ssl_ctx); +void quic_config_set_tls_config(struct quic_config_t *config, struct quic_tls_config_t *tls_config); /** * Create a QUIC endpoint. @@ -630,6 +715,11 @@ void quic_conn_server_name(struct quic_conn_t *conn, const uint8_t **out, size_t */ void quic_conn_session(struct quic_conn_t *conn, const uint8_t **out, size_t *out_len); +/** + * Return details why 0-RTT was accepted or rejected. + */ +int quic_conn_early_data_reason(struct quic_conn_t *conn, const uint8_t **out, size_t *out_len); + /** * Add a new path on the client connection. */ diff --git a/interop/run_endpoint.sh b/interop/run_endpoint.sh index 3a72685c..1e05edab 100644 --- a/interop/run_endpoint.sh +++ b/interop/run_endpoint.sh @@ -59,7 +59,11 @@ COPA) ;; esac -COMMON_ARGS="--keylog-file $SSLKEYLOGFILE --qlog-dir $QLOG_DIR --log-level TRACE --log-file $LOG_DIR/$ROLE.log --idle-timeout 30000 --handshake-timeout 30000 --congestion-control-algor $CC_ALGOR" +COMMON_ARGS="--keylog-file $SSLKEYLOGFILE --log-level DEBUG --log-file $LOG_DIR/$ROLE.log --idle-timeout 30000 --handshake-timeout 30000 --initial-rtt 100 --congestion-control-algor $CC_ALGOR" + +if [ "$TESTCASE" != "transfer" ]; then + COMMON_ARGS="$COMMON_ARGS --qlog-dir $QLOG_DIR" +fi if [ "$ROLE" == "client" ]; then # Wait for the simulator to start up. @@ -89,7 +93,6 @@ if [ "$ROLE" == "client" ]; then case $TESTCASE in multiconnect|resumption) - CLIENT_ARGS="$CLIENT_ARGS --initial-rtt 100" for REQ in $REQUESTS do $TQUIC_DIR/$TQUIC_CLIENT $CLIENT_ARGS $REQ @@ -109,9 +112,6 @@ elif [ "$ROLE" == "server" ]; then retry) SERVER_ARGS="$SERVER_ARGS --enable-retry" ;; - multiconnect) - SERVER_ARGS="$SERVER_ARGS --initial-rtt 100" - ;; transfer) SERVER_ARGS="$SERVER_ARGS --send-udp-payload-size 1400" ;; diff --git a/src/connection/cid.rs b/src/connection/cid.rs index 435270cf..92f35808 100644 --- a/src/connection/cid.rs +++ b/src/connection/cid.rs @@ -130,13 +130,17 @@ impl ConnectionIdDeque { /// ConnectionIdMgr maintains all the Connection IDs on a QUIC connection #[derive(Default)] pub struct ConnectionIdMgr { - /// All the Destination Connection IDs provided by our peer. + /// All the destination connection IDs provided by our peer. dcids: ConnectionIdDeque, - /// All the Source Connection IDs we provide to our peer. + /// All the source connection IDs we provide to our peer. scids: ConnectionIdDeque, - /// The maximum number of source Connection IDs our peer allows us. + /// The maximum number of destination connection IDs from peer that this + /// endpoint is willing to store. + dcid_limit: usize, + + /// The maximum number of source connection IDs our peer allows us. scid_limit: usize, /// Whether the host use zero-length Source Connection ID. @@ -185,6 +189,7 @@ impl ConnectionIdMgr { ConnectionIdMgr { scids, dcids, + dcid_limit, scid_limit, zero_length_scid: initial_scid.is_empty(), next_scid_seq: 1, @@ -386,6 +391,7 @@ impl ConnectionIdMgr { // (RFC 9000 Section 19.15) if seq < self.largest_peer_retire_prior_to && !self.dcids_to_retire.contains(&seq) { self.dcids_to_retire.push_back(seq); + self.check_dcids_to_retire()?; return Ok(retired_path_ids); } @@ -407,6 +413,7 @@ impl ConnectionIdMgr { } }); self.largest_peer_retire_prior_to = retire_prior_to; + self.check_dcids_to_retire()?; } // After processing a NEW_CONNECTION_ID frame and adding and retiring @@ -419,6 +426,22 @@ impl ConnectionIdMgr { Ok(retired_path_ids) } + /// Check and limit the total number of dcids to be retried. + fn check_dcids_to_retire(&self) -> Result<()> { + // An attacker might flood the server with NEW_CONNECTION_ID frames, and + // force it to respond with numerous RETIRE_CONNECTION_ID frames. If the + // attacker ignores these responses, a large queue of unacknowledged + // RETIRE_CONNECTION_ID frames will accumulate on the server. Over time, + // this could exhaust the server's memory. + // + // The endpoint should limit the number of queued RETIRE_CONNECTION)ID + // frames and break the connection if the peer exceeds this limit. + if self.dcids_to_retire.len() > self.dcid_limit * 4 { + return Err(Error::ProtocolViolation); + } + Ok(()) + } + /// Retire the Source CID from a RETIRE_CONNECTION_ID frame pub fn retire_scid(&mut self, seq: u64, pkt_dcid: &ConnectionId) -> Result> { // Receipt of a RETIRE_CONNECTION_ID frame containing a sequence number @@ -818,4 +841,26 @@ mod tests { Ok(()) } + + #[test] + fn new_connection_id_flood() -> Result<()> { + let dcid_limit = 8; + let scid0 = ConnectionId::random(); + let dcid0 = ConnectionId::random(); + let mut cids = ConnectionIdMgr::new(dcid_limit, &scid0, 0, None); + cids.set_initial_dcid(dcid0, Some(0), Some(0)); + + let max_dcids_to_retire = dcid_limit * 4; + for i in 1..50 { + // Fake receiving of NEW_CONNECTION_ID that retries a previously issued CID + let dcid = ConnectionId::random(); + let ret = cids.add_dcid(dcid, i, i as u128, i); + if cids.dcids_to_retire.len() > max_dcids_to_retire { + assert_eq!(ret, Err(Error::ProtocolViolation)); + } + } + assert!(cids.dcids_to_retire.len() <= max_dcids_to_retire + 1); + + Ok(()) + } } diff --git a/src/connection/connection.rs b/src/connection/connection.rs index 4a84fd45..c37f2792 100644 --- a/src/connection/connection.rs +++ b/src/connection/connection.rs @@ -1344,10 +1344,12 @@ impl Connection { /// Write coalesced multiple QUIC packets to the given buffer which will /// then be sent to the peer. /// + /// The size of `out` should be at least 1200 bytes, ideally matching or + /// exceeding the maximum possible MTU. + /// /// Return Error::Done if no packet can be sent. pub(crate) fn send(&mut self, out: &mut [u8]) -> Result<(usize, PacketInfo)> { - if out.is_empty() { - // TODO: use more strict condition + if out.len() < crate::MIN_CLIENT_INITIAL_LEN { return Err(Error::BufferTooShort); } @@ -3051,6 +3053,11 @@ impl Connection { self.tls_session.session() } + /// Return details why 0-RTT was accepted or rejected. + pub fn early_data_reason(&self) -> Result> { + self.tls_session.early_data_reason() + } + /// Check whether the connection is draining. /// /// If true, the connection object can not yet be dropped, but no data can @@ -4987,7 +4994,7 @@ pub(crate) mod tests { assert!(test_pair.client.timers.get(Timer::LossDetection).is_some()); // Server retransmit Handshake but lost again - for i in 0..3 { + for i in 0..5 { let dur = test_pair.server.timeout().unwrap(); test_pair.server.on_timeout(time::Instant::now() + dur); let _ = TestPair::conn_packets_out(&mut test_pair.server)?; @@ -6381,8 +6388,8 @@ pub(crate) mod tests { #[test] fn conn_multi_incremental_streams_send_round_robin() -> Result<()> { let server_transport_params = TransportParams { - initial_max_data: 2000, - initial_max_stream_data_bidi_remote: 2000, + initial_max_data: 20000, + initial_max_stream_data_bidi_remote: 20000, initial_max_streams_bidi: 4, ..TransportParams::default() }; @@ -6395,7 +6402,7 @@ pub(crate) mod tests { assert_eq!(test_pair.handshake(), Ok(())); // 1. Client create four bidi streams [0, 4, 8, 12], and write data on them - let data = TestPair::new_test_data(500); + let data = TestPair::new_test_data(1000); for i in 0..4 { assert_eq!( test_pair.client.stream_write(i * 4, data.clone(), true)?, @@ -6406,7 +6413,7 @@ pub(crate) mod tests { // 2. Try to send stream data in round-robin order let mut packets = Vec::new(); for i in 0..4 { - let mut out = vec![0u8; 100]; + let mut out = vec![0u8; 1500]; let info = match test_pair.client.send(&mut out) { Ok((written, info)) => { out.truncate(written); @@ -6791,10 +6798,12 @@ pub(crate) mod tests { let packets = TestPair::conn_packets_out(&mut test_pair.server)?; TestPair::conn_packets_in(&mut test_pair.client, packets)?; - // Advance ticks until loss timeout - assert!(test_pair.client.timeout().is_some()); - let timeout = test_pair.client.timers.get(Timer::LossDetection); - test_pair.client.on_timeout(timeout.unwrap()); + // The dropped packets may be declared as lost based on the time threshold. + // If not, advance ticks until loss timeout. + if test_pair.client.timeout().is_some() { + let timeout = test_pair.client.timers.get(Timer::LossDetection); + test_pair.client.on_timeout(timeout.unwrap()); + } // Check client qlog let mut clog_content = String::new(); diff --git a/src/connection/recovery.rs b/src/connection/recovery.rs index ae6c3b98..ef6281cf 100644 --- a/src/connection/recovery.rs +++ b/src/connection/recovery.rs @@ -573,7 +573,7 @@ impl Recovery { return (lost_packets, lost_bytes); } - // PTO timer mode (REVISIT) + // PTO timer mode let sid = if self.ack_eliciting_in_flight > 0 { // Send new data if available, else retransmit old data. If neither // is available, send a single PING frame. @@ -595,31 +595,27 @@ impl Recovery { }; self.pto_count += 1; - space.loss_probes = cmp::min(self.pto_count, MAX_PTO_PROBES_COUNT); + space.loss_probes = match sid { + Initial | Handshake => 1, + _ => cmp::min(self.pto_count, MAX_PTO_PROBES_COUNT), + }; + // An endpoint SHOULD include new data in packets that are sent on PTO + // expiration. Previously sent data MAY be sent if no new data can be + // sent. However, we only try to retransmit the oldest unacked data. let unacked_iter = space .sent .iter_mut() - // Skip packets that have already been acked or lost, and packets - // that don't contain either CRYPTO or STREAM frames. .filter(|p| p.has_data && p.time_acked.is_none() && p.time_lost.is_none()) - // Only return as many packets as the number of probe packets that - // will be sent. .take(space.loss_probes); - // Retransmit the frames from the oldest sent packets on PTO. However - // the packets are not actually declared lost (so there is no effect to - // congestion control), we just reschedule the data they carried. - // - // This will also trigger sending an ACK and retransmitting frames like - // HANDSHAKE_DONE and MAX_DATA / MAX_STREAM_DATA as well, in addition - // to CRYPTO and STREAM, if the original packet carried them. for unacked in unacked_iter { + // A PTO timer expiration event does not indicate packet loss and + // MUST NOT cause prior unacknowledged packets to be marked as lost. space.lost.extend_from_slice(&unacked.frames); } self.set_loss_detection_timer(space_id, spaces, handshake_status, now); - (0, 0) } diff --git a/src/ffi.rs b/src/ffi.rs index 7d3bd058..78782c4f 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -58,6 +58,8 @@ use crate::h3::Http3Event; use crate::h3::Http3Headers; use crate::h3::NameValue; use crate::qlog::events; +use crate::tls::SslCtx; +use crate::tls::TlsConfig; use crate::Config; use crate::Connection; use crate::Endpoint; @@ -380,6 +382,213 @@ pub extern "C" fn quic_config_set_send_batch_size(config: &mut Config, v: u16) { config.set_send_batch_size(v as usize); } +/// Create a new TlsConfig. +/// The caller is responsible for the memory of the TlsConfig and should properly +/// destroy it by calling `quic_tls_config_free`. +#[no_mangle] +pub extern "C" fn quic_tls_config_new() -> *mut TlsConfig { + match TlsConfig::new() { + Ok(tls_config) => Box::into_raw(Box::new(tls_config)), + Err(_) => ptr::null_mut(), + } +} + +/// Create a new TlsConfig with SSL_CTX. +/// When using raw SSL_CTX, TlsSession::session() and TlsSession::set_keylog() won't take effect. +/// The caller is responsible for the memory of TlsConfig and SSL_CTX when use this function. +#[no_mangle] +pub extern "C" fn quic_tls_config_new_with_ssl_ctx(ssl_ctx: *mut SslCtx) -> *mut TlsConfig { + Box::into_raw(Box::new(TlsConfig::new_with_ssl_ctx(ssl_ctx))) +} + +fn convert_application_protos(protos: *const *const c_char, proto_num: isize) -> Vec> { + let mut application_protos = vec![]; + for i in 0..proto_num { + let proto = unsafe { (*protos).offset(i) }; + if proto.is_null() { + continue; + } + + let proto = unsafe { ffi::CStr::from_ptr(proto).to_bytes().to_vec() }; + application_protos.push(proto); + } + + application_protos +} + +/// Create a new client side TlsConfig. +/// The caller is responsible for the memory of the TlsConfig and should properly +/// destroy it by calling `quic_tls_config_free`. +#[no_mangle] +pub extern "C" fn quic_tls_config_new_client_config( + protos: *const *const c_char, + proto_num: isize, + enable_early_data: bool, +) -> *mut TlsConfig { + if protos.is_null() { + return ptr::null_mut(); + } + + let application_protos = convert_application_protos(protos, proto_num); + match TlsConfig::new_client_config(application_protos, enable_early_data) { + Ok(tls_config) => Box::into_raw(Box::new(tls_config)), + Err(_) => ptr::null_mut(), + } +} + +/// Create a new server side TlsConfig. +/// The caller is responsible for the memory of the TlsConfig and should properly +/// destroy it by calling `quic_tls_config_free`. +#[no_mangle] +pub extern "C" fn quic_tls_config_new_server_config( + cert_file: *const c_char, + key_file: *const c_char, + protos: *const *const c_char, + proto_num: isize, + enable_early_data: bool, +) -> *mut TlsConfig { + if cert_file.is_null() || key_file.is_null() || protos.is_null() { + return ptr::null_mut(); + } + + let application_protos = convert_application_protos(protos, proto_num); + let cert_file = unsafe { + match ffi::CStr::from_ptr(cert_file).to_str() { + Ok(cert_file) => cert_file, + Err(_) => return ptr::null_mut(), + } + }; + let key_file = unsafe { + match ffi::CStr::from_ptr(key_file).to_str() { + Ok(key_file) => key_file, + Err(_) => return ptr::null_mut(), + } + }; + match TlsConfig::new_server_config(&cert_file, &key_file, application_protos, enable_early_data) + { + Ok(tls_config) => Box::into_raw(Box::new(tls_config)), + Err(_) => ptr::null_mut(), + } +} + +/// Destroy a TlsConfig instance. +#[no_mangle] +pub extern "C" fn quic_tls_config_free(tls_config: *mut TlsConfig) { + unsafe { + let _ = Box::from_raw(tls_config); + }; +} + +/// Set whether early data is allowed. +#[no_mangle] +pub extern "C" fn quic_tls_config_set_early_data_enabled(tls_config: &mut TlsConfig, enable: bool) { + tls_config.set_early_data_enabled(enable) +} + +/// Set the list of supported application protocols. +#[no_mangle] +pub extern "C" fn quic_tls_config_set_application_protos( + tls_config: &mut TlsConfig, + protos: *const *const c_char, + proto_num: isize, +) -> c_int { + if protos.is_null() { + return -1; + } + + let application_protos = convert_application_protos(protos, proto_num); + match tls_config.set_application_protos(application_protos) { + Ok(_) => 0, + Err(e) => e.to_errno() as c_int, + } +} + +/// Set session ticket key for server. +#[no_mangle] +pub extern "C" fn quic_tls_config_set_ticket_key( + tls_config: &mut TlsConfig, + ticket_key: *const u8, + ticket_key_len: size_t, +) -> c_int { + let ticket_key = unsafe { slice::from_raw_parts(ticket_key, ticket_key_len) }; + match tls_config.set_ticket_key(&ticket_key) { + Ok(_) => 0, + Err(e) => e.to_errno() as c_int, + } +} + +/// Set the certificate verification behavior. +#[no_mangle] +pub extern "C" fn quic_tls_config_set_verify(tls_config: &mut TlsConfig, verify: bool) { + tls_config.set_verify(verify) +} + +/// Set the PEM-encoded certificate file. +#[no_mangle] +pub extern "C" fn quic_tls_config_set_certificate_file( + tls_config: &mut TlsConfig, + cert_file: *const c_char, +) -> c_int { + if cert_file.is_null() { + return -1; + } + + let cert_file = unsafe { + match ffi::CStr::from_ptr(cert_file).to_str() { + Ok(cert_file) => cert_file, + Err(_) => return -1, + } + }; + match tls_config.set_certificate_file(&cert_file) { + Ok(_) => 0, + Err(e) => e.to_errno() as c_int, + } +} + +/// Set the PEM-encoded private key file. +#[no_mangle] +pub extern "C" fn quic_tls_config_set_private_key_file( + tls_config: &mut TlsConfig, + key_file: *const c_char, +) -> c_int { + if key_file.is_null() { + return -1; + } + + let key_file = unsafe { + match ffi::CStr::from_ptr(key_file).to_str() { + Ok(key_file) => key_file, + Err(_) => return -1, + } + }; + match tls_config.set_private_key_file(&key_file) { + Ok(_) => 0, + Err(e) => e.to_errno() as c_int, + } +} + +/// Set CA certificates. +#[no_mangle] +pub extern "C" fn quic_tls_config_set_ca_certs( + tls_config: &mut TlsConfig, + ca_path: *const c_char, +) -> c_int { + if ca_path.is_null() { + return -1; + } + + let ca_path = unsafe { + match ffi::CStr::from_ptr(ca_path).to_str() { + Ok(ca_path) => ca_path, + Err(_) => return -1, + } + }; + match tls_config.set_ca_certs(&ca_path) { + Ok(_) => 0, + Err(e) => e.to_errno() as c_int, + } +} + /// Set TLS config selector. #[no_mangle] pub extern "C" fn quic_config_set_tls_selector( @@ -392,13 +601,17 @@ pub extern "C" fn quic_config_set_tls_selector( } /// Set TLS config. -/// The caller is responsible for the memory of SSL_CTX when use this function. +/// +/// Note: Config doesn't own the TlsConfig when using this function. +/// It is the responsibility of the caller to release it. #[no_mangle] -pub extern "C" fn quic_config_set_tls_config( - config: &mut Config, - ssl_ctx: *mut crate::tls::SslCtx, -) { - let tls_config = crate::tls::TlsConfig::new_with_ssl_ctx(ssl_ctx); +pub extern "C" fn quic_config_set_tls_config(config: &mut Config, tls_config: *mut TlsConfig) { + if tls_config.is_null() { + return; + } + + let tls_config = unsafe { tls_config.as_mut().unwrap() }; + let tls_config = TlsConfig::new_with_ssl_ctx(tls_config.ssl_ctx()); config.set_tls_config(tls_config); } @@ -656,6 +869,28 @@ pub extern "C" fn quic_conn_session( } } +/// Return details why 0-RTT was accepted or rejected. +#[no_mangle] +pub extern "C" fn quic_conn_early_data_reason( + conn: &mut Connection, + out: &mut *const u8, + out_len: &mut size_t, +) -> c_int { + match conn.early_data_reason() { + Ok(reason) => { + match reason { + Some(reason) => { + *out = reason.as_ptr(); + *out_len = reason.len(); + } + None => *out_len = 0, + } + 0 + } + Err(e) => e.to_errno() as i32, + } +} + /// Add a new path on the client connection. #[no_mangle] pub extern "C" fn quic_conn_add_path( @@ -1086,12 +1321,9 @@ pub struct TlsConfigSelectorContext(*mut c_void); #[repr(C)] pub struct TlsConfigSelectMethods { - pub get_default: fn(ctx: *mut c_void) -> *mut crate::tls::SslCtx, - pub select: fn( - ctx: *mut c_void, - server_name: *const u8, - server_name_len: size_t, - ) -> *mut crate::tls::SslCtx, + pub get_default: fn(ctx: *mut c_void) -> *mut TlsConfig, + pub select: + fn(ctx: *mut c_void, server_name: *const u8, server_name_len: size_t) -> *mut TlsConfig, } #[repr(C)] @@ -1104,29 +1336,31 @@ unsafe impl Send for TlsConfigSelector {} unsafe impl Sync for TlsConfigSelector {} impl crate::tls::TlsConfigSelector for TlsConfigSelector { - fn get_default(&self) -> Option> { - let ssl_ctx = unsafe { ((*self.methods).get_default)(self.context.0) }; - if ssl_ctx.is_null() { + fn get_default(&self) -> Option> { + let tls_config = unsafe { ((*self.methods).get_default)(self.context.0) }; + if tls_config.is_null() { return None; } - let tls_config = Arc::new(crate::tls::TlsConfig::new_with_ssl_ctx(ssl_ctx)); + let tls_config = unsafe { tls_config.as_mut().unwrap() }; + let tls_config = Arc::new(TlsConfig::new_with_ssl_ctx(tls_config.ssl_ctx())); Some(tls_config) } - fn select(&self, server_name: &str) -> Option> { - let ssl_ctx = unsafe { + fn select(&self, server_name: &str) -> Option> { + let tls_config = unsafe { ((*self.methods).select)( self.context.0, server_name.as_ptr(), server_name.len() as size_t, ) }; - if ssl_ctx.is_null() { + if tls_config.is_null() { return None; } - let tls_config = Arc::new(crate::tls::TlsConfig::new_with_ssl_ctx(ssl_ctx)); + let tls_config = unsafe { tls_config.as_mut().unwrap() }; + let tls_config = Arc::new(TlsConfig::new_with_ssl_ctx(tls_config.ssl_ctx())); Some(tls_config) } } diff --git a/src/frame.rs b/src/frame.rs index 310b5069..745a1037 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -823,7 +823,7 @@ impl Frame { Frame::NewToken { .. } => QuicFrame::NewToken { token: qlog::events::Token { - token_type: Some(qlog::events::TokenType::Retry), + token_type: Some(qlog::events::TokenType::Resumption), raw: None, details: None, }, diff --git a/src/tls/boringssl/tls.rs b/src/tls/boringssl/tls.rs index fcb169ab..6bbe1b95 100644 --- a/src/tls/boringssl/tls.rs +++ b/src/tls/boringssl/tls.rs @@ -85,6 +85,39 @@ struct SslQuicMethod { send_alert: extern "C" fn(ssl: *mut Ssl, level: tls::Level, alert: u8) -> c_int, } +#[repr(C)] +enum SslEarlyDataReason { + // The handshake has not progressed far enough for the 0-RTT status to be known. + Unknown = 0, + // 0-RTT is disabled for this connection. + Disabled = 1, + // 0-RTT was accepted. + Accepted = 2, + // The negotiated protocol version does not support 0-RTT. + ProtocolVersion = 3, + // The peer declined to offer or accept 0-RTT for an unknown reason. + PeerDeclined = 4, + // The client did not offer a session. + NoSessionOffered = 5, + // The server declined to resume the session. + SessionNotResumed = 6, + // The session does not support 0-RTT. + UnsupportedForSession = 7, + // The server sent a HelloRetryRequest. + HelloRetryRequest = 8, + // The negotiated ALPN protocol did not match the session. + AlpnMismatch = 9, + // The connection negotiated Channel ID, which is incompatible with 0-RTT. + ChannelId = 10, + // Value 11 is reserved. (It has historically |ssl_early_data_token_binding|.) + // The client and server ticket age were too far apart. + TicketAgeSkew = 12, + // QUIC parameters differ between this connection and the original. + QuicParameterMismatch = 13, + // The application settings did not match the session. + AlpsMismatch = 14, +} + /// Called when TLS context is being destroyed. /// See https://commondatastorage.googleapis.com/chromium-boringssl-docs/ex_data.h.html extern "C" fn context_data_free( @@ -127,7 +160,7 @@ static SSL_QUIC_METHOD: SslQuicMethod = SslQuicMethod { /// Rust wrapper of SSL_CTX which holds various configuration and data relevant /// to SSL/TLS session establishment. -pub struct Context { +pub(crate) struct Context { ctx_raw: *mut SslCtx, owned: bool, } @@ -167,12 +200,12 @@ impl Context { } /// Return the mutable pointer of the inner SSL_CTX. - fn as_mut_ptr(&mut self) -> *mut SslCtx { + pub fn as_mut_ptr(&mut self) -> *mut SslCtx { self.ctx_raw } /// Return the const pointer of the inner SSL_CTX. - fn as_ptr(&self) -> *const SslCtx { + pub fn as_ptr(&self) -> *const SslCtx { self.ctx_raw } @@ -254,7 +287,7 @@ impl Context { } /// Set the callback function that is called whenever a new session was negotiated. - fn set_session_callback(&mut self) { + pub fn set_session_callback(&mut self) { unsafe { SSL_CTX_set_session_cache_mode( self.as_mut_ptr(), @@ -629,7 +662,6 @@ impl Session { let sigalg_name = SSL_get_signature_algorithm_name(sigalg_id, 1); match ffi::CStr::from_ptr(sigalg_name).to_str() { Ok(v) => v, - Err(_) => return None, } }; @@ -686,6 +718,23 @@ impl Session { Some(peer_cert) } + pub fn early_data_reason(&self) -> Result> { + let reason = unsafe { + let reason = SSL_early_data_reason_string(SSL_get_early_data_reason(self.as_ptr())); + match ffi::CStr::from_ptr(reason).to_str() { + Ok(v) => v, + Err(e) => { + return Err(Error::TlsFail(format!( + "early data reason format error {:?}", + e + ))) + } + } + }; + + Ok(Some(reason)) + } + /// Return true if ssl has a completed handshake. pub fn is_completed(&self) -> bool { unsafe { SSL_in_init(self.as_ptr()) == 0 } @@ -1420,6 +1469,12 @@ extern "C" { /// For a server, return the hostname supplied by the client. fn SSL_get_servername(ssl: *const Ssl, ty: c_int) -> *const c_char; + /// Return details why 0-RTT was accepted or rejected on ssl. + fn SSL_get_early_data_reason(ssl: *const Ssl) -> SslEarlyDataReason; + + /// Return a string representation for reason, or NULL if reason is unknown. + fn SSL_early_data_reason_string(reason: SslEarlyDataReason) -> *const c_char; + /// Reset ssl to allow another connection. fn SSL_clear(ssl: *mut Ssl) -> c_int; diff --git a/src/tls/tls.rs b/src/tls/tls.rs index a5a08056..790a5873 100644 --- a/src/tls/tls.rs +++ b/src/tls/tls.rs @@ -75,28 +75,28 @@ pub struct TlsConfig { impl TlsConfig { /// Create a new TlsConfig. - pub fn new() -> Result { + pub fn new() -> Result { let mut tls_ctx = boringssl::tls::Context::new()?; tls_ctx.enable_keylog(); - Ok(TlsConfig { tls_ctx }) + Ok(Self { tls_ctx }) } /// Create a new TlsConfig with SSL_CTX. + /// When using raw SSL_CTX, TlsSession::session() and TlsSession::set_keylog() won't take effect. /// The caller is responsible for the memory of SSL_CTX when use this function. - #[doc(hidden)] - pub fn new_with_ssl_ctx(ssl_ctx: *mut boringssl::tls::SslCtx) -> TlsConfig { - Self { - tls_ctx: boringssl::tls::Context::new_with_ssl_ctx(ssl_ctx), - } + pub fn new_with_ssl_ctx(ssl_ctx: *mut boringssl::tls::SslCtx) -> Self { + let tls_ctx = boringssl::tls::Context::new_with_ssl_ctx(ssl_ctx); + + Self { tls_ctx } } /// Create a new client side TlsConfig. pub fn new_client_config( application_protos: Vec>, enable_early_data: bool, - ) -> Result { - let mut tls_config = TlsConfig::new()?; + ) -> Result { + let mut tls_config = Self::new()?; tls_config.set_application_protos(application_protos)?; tls_config.set_early_data_enabled(enable_early_data); @@ -109,8 +109,8 @@ impl TlsConfig { key_file: &str, application_protos: Vec>, enable_early_data: bool, - ) -> Result { - let mut tls_config = TlsConfig::new()?; + ) -> Result { + let mut tls_config = Self::new()?; tls_config.set_certificate_file(cert_file)?; tls_config.set_private_key_file(key_file)?; tls_config.set_application_protos(application_protos)?; @@ -160,6 +160,11 @@ impl TlsConfig { Ok(()) } + + /// Get the underlying SSL_CTX. + pub(crate) fn ssl_ctx(&mut self) -> *mut boringssl::tls::SslCtx { + self.tls_ctx.as_mut_ptr() + } } impl TlsConfig { @@ -212,6 +217,7 @@ impl TlsConfigSelector for DefaultTlsConfigSelector { } } +/// Used for selecting TLS config according to SNI. pub trait TlsConfigSelector: Send + Sync { /// Get default TLS config. fn get_default(&self) -> Option>; @@ -399,6 +405,10 @@ impl TlsSession { pub fn peer_sign_algor(&self) -> Option { self.session.peer_sign_algor() } + + pub fn early_data_reason(&self) -> Result> { + self.session.early_data_reason() + } } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 4d895935..c6d244c1 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tquic_tools" -version = "0.8.1" +version = "0.9.0" edition = "2021" rust-version = "1.70.0" license = "Apache-2.0" @@ -23,7 +23,7 @@ rand = "0.8.5" statrs = "0.16" jemallocator = { version = "0.5", package = "tikv-jemallocator" } signal-hook = "0.3.17" -tquic = { path = "..", version = "0.8.1"} +tquic = { path = "..", version = "0.9.0"} [lib] crate-type = ["lib"] diff --git a/typos.toml b/typos.toml index 0391977e..aa41b7cd 100644 --- a/typos.toml +++ b/typos.toml @@ -9,6 +9,10 @@ StatuS = "StatuS" [default.extend-words] # ECN Capable Transport ect = "ect" +# Packet Number +pn = "pn" +# Retransmission Timeout +RTO = "RTO" [files] extend-exclude = [