From 32b93cb553ec8de2690247dfc3d159d326c2a683 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Tue, 14 Jul 2026 13:15:41 -0400 Subject: [PATCH 1/4] Update rustls to 0.23.42 See https://github.com/rustls/rustls/releases/tag/v%2F0.23.42 --- Cargo.lock | 4 ++-- librustls/Cargo.toml | 2 +- librustls/build.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c36575b2..ee8a51a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1105,9 +1105,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.41" +version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ "aws-lc-rs", "brotli", diff --git a/librustls/Cargo.toml b/librustls/Cargo.toml index 5c78f0dd..83a4977f 100644 --- a/librustls/Cargo.toml +++ b/librustls/Cargo.toml @@ -30,7 +30,7 @@ prefer-post-quantum = ["aws-lc-rs", "rustls/prefer-post-quantum"] [dependencies] # Keep in sync with RUSTLS_CRATE_VERSION in build.rs -rustls = { version = "=0.23.41", default-features = false, features = ["std", "tls12"] } +rustls = { version = "=0.23.42", default-features = false, features = ["std", "tls12"] } webpki = { workspace = true } libc = { workspace = true } log = { workspace = true } diff --git a/librustls/build.rs b/librustls/build.rs index c595ab84..793b6337 100644 --- a/librustls/build.rs +++ b/librustls/build.rs @@ -37,4 +37,4 @@ fn main() { // because doing so would require a heavy-weight deserialization lib dependency // (and it couldn't be a _dev_ dep for use in a build script) or doing brittle // by-hand parsing. -const RUSTLS_CRATE_VERSION: &str = "0.23.41"; +const RUSTLS_CRATE_VERSION: &str = "0.23.42"; From ea1cc7a42d7b9bee2cb5adfeb53691954e0d1590 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Tue, 14 Jul 2026 14:08:12 -0400 Subject: [PATCH 2/4] librustls: expose RFC 9149 support rustls 0.23.42 added opt-in support for the RFC 9149 ticket_request extension. Expose both halves via the C API: - rustls_client_config_builder_set_send_ticket_request() populates ClientConfig::send_ticket_request, offering the extension with the given new-session and resumption ticket counts. The default remains to not send the extension. - rustls_server_config_builder_set_max_tls13_tickets() populates ServerConfig::max_tls13_tickets, the cap applied to client ticket requests. The default remains 0 (requests ignored). The demo client/server get new REQUEST_TLS13_TICKETS/EXPECT_TLS13_TICKETS and MAX_TLS13_TICKETS env vars, and a new integration test verifies end-to-end that a client requesting under the server max receives the requested count while a request over the max is capped, using rustls_connection_get_tls13_tickets_received(). --- librustls/src/client.rs | 81 +++++++++++++++++++++++++++++- librustls/src/rustls.h | 32 ++++++++++++ librustls/src/server.rs | 86 ++++++++++++++++++++++++++++++++ librustls/tests/client.c | 55 ++++++++++++++++++++ librustls/tests/client.h | 11 ++++ librustls/tests/client_server.rs | 31 ++++++++++++ librustls/tests/server.c | 19 +++++++ website/static/api.json | 16 ++++++ 8 files changed, 330 insertions(+), 1 deletion(-) diff --git a/librustls/src/client.rs b/librustls/src/client.rs index 6b766118..11c9c313 100644 --- a/librustls/src/client.rs +++ b/librustls/src/client.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use libc::{c_char, size_t}; use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; -use rustls::client::{EchConfig, EchGreaseConfig, EchMode, ResolvesClientCert}; +use rustls::client::{EchConfig, EchGreaseConfig, EchMode, ResolvesClientCert, TicketRequest}; use rustls::crypto::{CryptoProvider, verify_tls12_signature, verify_tls13_signature}; use rustls::pki_types::{CertificateDer, EchConfigListBytes, ServerName, UnixTime}; use rustls::{ @@ -60,6 +60,7 @@ pub(crate) struct ClientConfigBuilder { cert_resolver: Option>, key_log: Option>, ech_mode: Option, + send_ticket_request: Option, } impl Default for ClientConfigBuilder { @@ -79,6 +80,7 @@ impl Default for ClientConfigBuilder { cert_resolver: None, key_log: None, ech_mode: None, + send_ticket_request: None, } } } @@ -287,6 +289,34 @@ impl rustls_client_config_builder { } } + /// Request a specific number of TLS 1.3 session tickets. + /// + /// This is done via the RFC 9149 `ticket_request` extension. + /// + /// `new_session_count` is the number of tickets desired when the server + /// negotiates a new connection, and `resumption_count` the number desired + /// when the server resumes using a presented ticket. + /// + /// By default the extension is not sent. Note that servers are free to + /// ignore the request, it is only a hint. + /// + /// + #[no_mangle] + pub extern "C" fn rustls_client_config_builder_set_send_ticket_request( + builder: *mut rustls_client_config_builder, + new_session_count: u8, + resumption_count: u8, + ) -> rustls_result { + ffi_panic_boundary! { + let config = try_mut_from_ptr!(builder); + config.send_ticket_request = Some(TicketRequest { + new_session_count, + resumption_count, + }); + rustls_result::Ok + } + } + /// Enable or disable SNI. /// #[no_mangle] @@ -557,6 +587,7 @@ impl rustls_client_config_builder { }; config.alpn_protocols = builder.alpn_protocols; config.enable_sni = builder.enable_sni; + config.send_ticket_request = builder.send_ticket_request; if let Some(key_log) = builder.key_log { config.key_log = key_log; @@ -926,6 +957,54 @@ mod tests { rustls_server_cert_verifier::rustls_server_cert_verifier_free(verifier); } + #[test] + #[cfg_attr(miri, ignore)] + fn test_config_builder_set_send_ticket_request() { + // The default is to not send the ticket_request extension. + let builder = rustls_client_config_builder::rustls_client_config_builder_new(); + let mut verifier = null_mut(); + let result = + rustls_server_cert_verifier::rustls_platform_server_cert_verifier(&mut verifier); + assert_eq!(result, rustls_result::Ok); + rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( + builder, verifier, + ); + let mut config = null(); + let result = + rustls_client_config_builder::rustls_client_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + { + let config2 = try_ref_from_ptr!(config); + assert_eq!(config2.send_ticket_request, None); + } + rustls_client_config::rustls_client_config_free(config); + + // Configured counts flow through to the config. + let builder = rustls_client_config_builder::rustls_client_config_builder_new(); + rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( + builder, verifier, + ); + rustls_client_config_builder::rustls_client_config_builder_set_send_ticket_request( + builder, 6, 3, + ); + let mut config = null(); + let result = + rustls_client_config_builder::rustls_client_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + { + let config2 = try_ref_from_ptr!(config); + assert_eq!( + config2.send_ticket_request, + Some(TicketRequest { + new_session_count: 6, + resumption_count: 3, + }) + ); + } + rustls_client_config::rustls_client_config_free(config); + rustls_server_cert_verifier::rustls_server_cert_verifier_free(verifier); + } + // Build a client connection and test the getters and initial values. #[test] #[cfg_attr(miri, ignore)] diff --git a/librustls/src/rustls.h b/librustls/src/rustls.h index b45cc3e3..bc5f2b16 100644 --- a/librustls/src/rustls.h +++ b/librustls/src/rustls.h @@ -1458,6 +1458,24 @@ rustls_result rustls_client_config_builder_set_alpn_protocols(struct rustls_clie void rustls_client_config_builder_set_check_selected_alpn(struct rustls_client_config_builder *config, bool enable); +/** + * Request a specific number of TLS 1.3 session tickets. + * + * This is done via the RFC 9149 `ticket_request` extension. + * + * `new_session_count` is the number of tickets desired when the server + * negotiates a new connection, and `resumption_count` the number desired + * when the server resumes using a presented ticket. + * + * By default the extension is not sent. Note that servers are free to + * ignore the request, it is only a hint. + * + * + */ +rustls_result rustls_client_config_builder_set_send_ticket_request(struct rustls_client_config_builder *builder, + uint8_t new_session_count, + uint8_t resumption_count); + /** * Enable or disable SNI. * @@ -2401,6 +2419,20 @@ rustls_result rustls_server_config_builder_set_ignore_client_order(struct rustls rustls_result rustls_server_config_builder_set_send_tls13_tickets(struct rustls_server_config_builder *builder, size_t n); +/** + * Set the upper bound on the number of TLS 1.3 tickets sent in response to a client. + * + * This modifies the server's response to a client's RFC 9149 `ticket_request` extension. + * + * A client requesting `n` tickets receives `min(n, max)`. Setting this to + * 0 (the default) ignores client requests entirely, clients get the configured + * `send_tls13_tickets` count regardless. + * + * + */ +rustls_result rustls_server_config_builder_set_max_tls13_tickets(struct rustls_server_config_builder *builder, + size_t max); + /** * Set the ALPN protocol list to the given protocols. * diff --git a/librustls/src/server.rs b/librustls/src/server.rs index f0069555..9ca65101 100644 --- a/librustls/src/server.rs +++ b/librustls/src/server.rs @@ -61,6 +61,7 @@ pub(crate) struct ServerConfigBuilder { alpn_protocols: Vec>, ignore_client_order: Option, send_tls13_tickets: Option, + max_tls13_tickets: Option, key_log: Option>, } @@ -100,6 +101,7 @@ impl rustls_server_config_builder { alpn_protocols: vec![], ignore_client_order: None, send_tls13_tickets: None, + max_tls13_tickets: None, key_log: None, }; to_boxed_mut_ptr(builder) @@ -156,6 +158,7 @@ impl rustls_server_config_builder { alpn_protocols: vec![], ignore_client_order: None, send_tls13_tickets: None, + max_tls13_tickets: None, key_log: None, }; set_boxed_mut_ptr(builder_out, builder); @@ -290,6 +293,27 @@ impl rustls_server_config_builder { } } + /// Set the upper bound on the number of TLS 1.3 tickets sent in response to a client. + /// + /// This modifies the server's response to a client's RFC 9149 `ticket_request` extension. + /// + /// A client requesting `n` tickets receives `min(n, max)`. Setting this to + /// 0 (the default) ignores client requests entirely, clients get the configured + /// `send_tls13_tickets` count regardless. + /// + /// + #[no_mangle] + pub extern "C" fn rustls_server_config_builder_set_max_tls13_tickets( + builder: *mut rustls_server_config_builder, + max: usize, + ) -> rustls_result { + ffi_panic_boundary! { + let config = try_mut_from_ptr!(builder); + config.max_tls13_tickets = Some(max); + rustls_result::Ok + } + } + /// Set the ALPN protocol list to the given protocols. /// /// `protocols` must point to a buffer of `rustls_slice_bytes` (built by the caller) @@ -398,6 +422,9 @@ impl rustls_server_config_builder { if let Some(send_tls13_tickets) = builder.send_tls13_tickets { config.send_tls13_tickets = send_tls13_tickets; } + if let Some(max_tls13_tickets) = builder.max_tls13_tickets { + config.max_tls13_tickets = max_tls13_tickets; + } if let Some(key_log) = builder.key_log { config.key_log = key_log; @@ -905,6 +932,65 @@ mod tests { rustls_certified_key::rustls_certified_key_free(certified_key); } + #[test] + #[cfg_attr(miri, ignore)] + fn test_server_config_builder_set_max_tls13_tickets() { + let builder = rustls_server_config_builder::rustls_server_config_builder_new(); + + let cert_pem = include_str!("../testdata/localhost/cert.pem").as_bytes(); + let key_pem = include_str!("../testdata/localhost/key.pem").as_bytes(); + let mut certified_key = null(); + let result = rustls_certified_key::rustls_certified_key_build( + cert_pem.as_ptr(), + cert_pem.len(), + key_pem.as_ptr(), + key_pem.len(), + &mut certified_key, + ); + if !matches!(result, rustls_result::Ok) { + panic!("expected RUSTLS_RESULT_OK from rustls_certified_key_build, got {result:?}"); + } + rustls_server_config_builder::rustls_server_config_builder_set_certified_keys( + builder, + &certified_key, + 1, + ); + + // The default is 0 (client ticket requests are ignored). + let mut config = null(); + let result = + rustls_server_config_builder::rustls_server_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); + { + let config2 = try_ref_from_ptr!(config); + assert_eq!(config2.max_tls13_tickets, 0); + } + rustls_server_config::rustls_server_config_free(config); + + // A non-zero value flows through. + let builder = rustls_server_config_builder::rustls_server_config_builder_new(); + rustls_server_config_builder::rustls_server_config_builder_set_certified_keys( + builder, + &certified_key, + 1, + ); + rustls_server_config_builder::rustls_server_config_builder_set_max_tls13_tickets( + builder, 8, + ); + let mut config = null(); + let result = + rustls_server_config_builder::rustls_server_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); + { + let config2 = try_ref_from_ptr!(config); + assert_eq!(config2.max_tls13_tickets, 8); + } + rustls_server_config::rustls_server_config_free(config); + rustls_certified_key::rustls_certified_key_free(certified_key); + } + // Build a server connection and test the getters and initial values. #[test] fn test_server_config_builder_new_empty() { diff --git a/librustls/tests/client.c b/librustls/tests/client.c index 56407811..1d1ad1b1 100644 --- a/librustls/tests/client.c +++ b/librustls/tests/client.c @@ -90,6 +90,7 @@ main(const int argc, const char **argv) .port = port, .path = path, .use_vectored_io = opts.use_vectored_io, + .expect_tls13_tickets = opts.expect_tls13_tickets, }; // Make GET requests with the rustls client config. @@ -205,6 +206,33 @@ options_from_env(demo_client_options *opts) } #endif + // Consider RFC 9149 TLS 1.3 ticket request options. + opts->request_tls13_tickets = -1; + opts->expect_tls13_tickets = -1; + const char *request_tls13_tickets = getenv("REQUEST_TLS13_TICKETS"); + if(request_tls13_tickets) { + char *end = NULL; + const long int count = strtol(request_tls13_tickets, &end, 10); + if(end == request_tls13_tickets || *end != '\0' || count < 0 || + count > UINT8_MAX) { + LOG_SIMPLE("REQUEST_TLS13_TICKETS must be an integer in [0, 255]"); + return 1; + } + LOG("requesting %ld TLS 1.3 ticket(s) per handshake", count); + opts->request_tls13_tickets = (int)count; + } + const char *expect_tls13_tickets = getenv("EXPECT_TLS13_TICKETS"); + if(expect_tls13_tickets) { + char *end = NULL; + const long int count = strtol(expect_tls13_tickets, &end, 10); + if(end == expect_tls13_tickets || *end != '\0' || count < 0) { + LOG_SIMPLE("EXPECT_TLS13_TICKETS must be a non-negative integer"); + return 1; + } + LOG("expecting %ld TLS 1.3 ticket(s) per handshake", count); + opts->expect_tls13_tickets = (int)count; + } + return 0; } @@ -425,6 +453,18 @@ new_tls_config(const demo_client_options *opts) goto cleanup; } + // Then configure an RFC 9149 ticket request if required. + if(opts->request_tls13_tickets >= 0) { + rr = rustls_client_config_builder_set_send_ticket_request( + config_builder, + (uint8_t)opts->request_tls13_tickets, + (uint8_t)opts->request_tls13_tickets); + if(rr != RUSTLS_RESULT_OK) { + print_error("setting ticket request", rr); + goto cleanup; + } + } + // Finally consume the config_builder by trying to build it into a client // config. We can't use the config_builder (even to free it!) after this // point. @@ -541,6 +581,21 @@ do_get_request(const demo_client_request_options *options) LOG_SIMPLE("I/O loop fell through"); log_connection_info(demo_conn->rconn); + // Verify the expected number of TLS 1.3 session tickets were received. + if(options->expect_tls13_tickets >= 0) { + const uint32_t tickets = + rustls_connection_get_tls13_tickets_received(demo_conn->rconn); + if(tickets != (uint32_t)options->expect_tls13_tickets) { + LOG("expected %d TLS 1.3 ticket(s), received %u", + options->expect_tls13_tickets, + tickets); + ret = 1; + } + else { + LOG("received %u TLS 1.3 ticket(s), as expected", tickets); + } + } + // Print whatever is in the user data buffer. // TODO(@cpu): refactor conndata struct to avoid "data data data" naming // when digging in to the conndata's bytevec's data. diff --git a/librustls/tests/client.h b/librustls/tests/client.h index 259b4dfe..8f767ec4 100644 --- a/librustls/tests/client.h +++ b/librustls/tests/client.h @@ -33,6 +33,14 @@ typedef struct demo_client_options // Optional vectored IO support. bool use_vectored_io; + + // Optional RFC 9149 ticket request count. If >= 0 the client requests + // this many TLS 1.3 session tickets per handshake. + int request_tls13_tickets; + + // Optional expected TLS 1.3 session ticket count. If >= 0 each request + // fails when the number of tickets received differs. + int expect_tls13_tickets; } demo_client_options; // Populate the provided options with values from the environment. @@ -57,6 +65,9 @@ typedef struct demo_client_request_options const char *port; const char *path; bool use_vectored_io; + // If >= 0, the number of TLS 1.3 session tickets that must be received + // for the request to be considered successful. + int expect_tls13_tickets; } demo_client_request_options; // Make an HTTP request based on the provided options. The resulting diff --git a/librustls/tests/client_server.rs b/librustls/tests/client_server.rs index 024df404..e726b303 100644 --- a/librustls/tests/client_server.rs +++ b/librustls/tests/client_server.rs @@ -144,6 +144,36 @@ fn client_server_integration() { ], }; + let ticket_request_server = TestCase { + name: "Ticket request (RFC 9149) tests", + server_opts: ServerOptions { + valgrind: valgrind.clone(), + env: vec![("MAX_TLS13_TICKETS", "3")], + }, + client_tests: vec![ + ClientTest { + name: "Request under the server max", + valgrind: valgrind.clone(), + env: vec![ + ("CA_FILE", "testdata/minica.pem"), + ("REQUEST_TLS13_TICKETS", "2"), + ("EXPECT_TLS13_TICKETS", "2"), + ], + expect_error: false, + }, + ClientTest { + name: "Request over the server max", + valgrind: valgrind.clone(), + env: vec![ + ("CA_FILE", "testdata/minica.pem"), + ("REQUEST_TLS13_TICKETS", "9"), + ("EXPECT_TLS13_TICKETS", "3"), + ], + expect_error: false, + }, + ], + }; + TestCases(vec![ standard_server, vectored_server, @@ -152,6 +182,7 @@ fn client_server_integration() { mandatory_client_auth_server, mandatory_client_auth_server_with_crls, custom_ciphersuites, + ticket_request_server, ]) .run(); } diff --git a/librustls/tests/server.c b/librustls/tests/server.c index ea7d1db0..26f5a136 100644 --- a/librustls/tests/server.c +++ b/librustls/tests/server.c @@ -398,6 +398,25 @@ main(const int argc, const char **argv) } } + const char *max_tls13_tickets = getenv("MAX_TLS13_TICKETS"); + if(max_tls13_tickets) { + char *end = NULL; + const long int max = strtol(max_tls13_tickets, &end, 10); + if(end == max_tls13_tickets || *end != '\0' || max < 0) { + fprintf(stderr, + "server: MAX_TLS13_TICKETS must be a non-negative integer\n"); + goto cleanup; + } + const rustls_result rr = + rustls_server_config_builder_set_max_tls13_tickets(config_builder, + (size_t)max); + if(rr != RUSTLS_RESULT_OK) { + print_error("setting max TLS 1.3 tickets", rr); + goto cleanup; + } + printf("honoring client ticket requests up to %ld ticket(s)\n", max); + } + rustls_result rr = rustls_server_config_builder_build(config_builder, &server_config); if(rr != RUSTLS_RESULT_OK) { diff --git a/website/static/api.json b/website/static/api.json index 14e11147..5971a30b 100644 --- a/website/static/api.json +++ b/website/static/api.json @@ -514,6 +514,14 @@ "name": "rustls_client_config_builder_set_check_selected_alpn", "text": "```c\nvoid rustls_client_config_builder_set_check_selected_alpn(struct rustls_client_config_builder *config,\n bool enable);\n```" }, + { + "anchor": "rustls-client-config-builder-set-send-ticket-request", + "comment": "Request a specific number of TLS 1.3 session tickets.\n\n This is done via the RFC 9149 `ticket_request` extension.\n\n `new_session_count` is the number of tickets desired when the server\n negotiates a new connection, and `resumption_count` the number desired\n when the server resumes using a presented ticket.\n\n By default the extension is not sent. Note that servers are free to\n ignore the request, it is only a hint.\n\n ", + "feature": null, + "deprecation": null, + "name": "rustls_client_config_builder_set_send_ticket_request", + "text": "```c\nrustls_result rustls_client_config_builder_set_send_ticket_request(struct rustls_client_config_builder *builder,\n uint8_t new_session_count,\n uint8_t resumption_count);\n```" + }, { "anchor": "rustls-client-config-builder-set-enable-sni", "comment": "Enable or disable SNI.\n ", @@ -1114,6 +1122,14 @@ "name": "rustls_server_config_builder_set_send_tls13_tickets", "text": "```c\nrustls_result rustls_server_config_builder_set_send_tls13_tickets(struct rustls_server_config_builder *builder,\n size_t n);\n```" }, + { + "anchor": "rustls-server-config-builder-set-max-tls13-tickets", + "comment": "Set the upper bound on the number of TLS 1.3 tickets sent in response to a client.\n\n This modifies the server's response to a client's RFC 9149 `ticket_request` extension.\n\n A client requesting `n` tickets receives `min(n, max)`. Setting this to\n 0 (the default) ignores client requests entirely, clients get the configured\n `send_tls13_tickets` count regardless.\n\n ", + "feature": null, + "deprecation": null, + "name": "rustls_server_config_builder_set_max_tls13_tickets", + "text": "```c\nrustls_result rustls_server_config_builder_set_max_tls13_tickets(struct rustls_server_config_builder *builder,\n size_t max);\n```" + }, { "anchor": "rustls-server-config-builder-set-alpn-protocols", "comment": "Set the ALPN protocol list to the given protocols.\n\n `protocols` must point to a buffer of [`rustls_slice_bytes`](#rustls-slice-bytes) (built by the caller)\n with `len` elements. Each element of the buffer must point to a slice of bytes that\n contains a single ALPN protocol from\n .\n\n This function makes a copy of the data in `protocols` and does not retain\n any pointers, so the caller can free the pointed-to memory after calling.\n\n ", From 2ac4f26a8c63b28fa371cbec0f7d59f8b8ba7eb9 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Tue, 14 Jul 2026 14:11:50 -0400 Subject: [PATCH 3/4] librustls: fix no-op check_selected_alpn The setter added previously stored check_selected_alpn on the FFI builder but rustls_client_config_builder_build() never copied it to the ClientConfig, so disabling the check had no effect. --- librustls/src/client.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/librustls/src/client.rs b/librustls/src/client.rs index 11c9c313..64c28a8b 100644 --- a/librustls/src/client.rs +++ b/librustls/src/client.rs @@ -586,6 +586,7 @@ impl rustls_client_config_builder { None => config.with_no_client_auth(), }; config.alpn_protocols = builder.alpn_protocols; + config.check_selected_alpn = builder.check_selected_alpn; config.enable_sni = builder.enable_sni; config.send_ticket_request = builder.send_ticket_request; @@ -943,6 +944,9 @@ mod tests { alpn.len(), ); rustls_client_config_builder::rustls_client_config_builder_set_enable_sni(builder, false); + rustls_client_config_builder::rustls_client_config_builder_set_check_selected_alpn( + builder, false, + ); let mut config = null(); let result = rustls_client_config_builder::rustls_client_config_builder_build(builder, &mut config); @@ -952,6 +956,7 @@ mod tests { let config2 = try_ref_from_ptr!(config); assert!(!config2.enable_sni); assert_eq!(config2.alpn_protocols, vec![h1, h2]); + assert!(!config2.check_selected_alpn); } rustls_client_config::rustls_client_config_free(config); rustls_server_cert_verifier::rustls_server_cert_verifier_free(verifier); From 56b55d66205b1fc00a5c6d0adb24188be37ed72b Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Tue, 14 Jul 2026 14:34:16 -0400 Subject: [PATCH 4/4] Update pinned cbindgen 0.28.0 -> 0.29.4 Regenerates rustls.h with the new version. cbindgen 0.29 moves rustls_handshake_kind after rustls_tls_version and emits enums with a fixed representation (rustls_result) using C23 typed enum syntax, guarded by __STDC_VERSION__ >= 202311L preprocessor conditionals. Tree-sitter's C grammar can't parse a preprocessor conditional in the middle of an enum declaration, so teach docgen to resolve the C23 guards to the pre-C23 branch before parsing. This restores the header shape older cbindgen emitted and produces identical api.json content modulo the enum reordering. --- .github/workflows/test.yaml | 2 +- librustls/src/rustls.h | 80 ++++++++++++++++++++---------------- tools/src/bin/docgen/main.rs | 50 ++++++++++++++++++++++ website/static/api.json | 66 ++++++++++++++--------------- 4 files changed, 128 insertions(+), 70 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3a2fd738..d6ed07ef 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -276,7 +276,7 @@ jobs: # reliably matched to CI. There can be non-semantic differences in # output between point releases of cbindgen that will fail this check # otherwise. - run: cargo install cbindgen --force --version 0.28.0 + run: cargo install cbindgen --force --version 0.29.4 - name: Configure CMake run: cmake -S librustls -B build diff --git a/librustls/src/rustls.h b/librustls/src/rustls.h index bc5f2b16..85327bdc 100644 --- a/librustls/src/rustls.h +++ b/librustls/src/rustls.h @@ -28,45 +28,14 @@ #endif -/** - * Describes which sort of handshake happened. - */ -typedef enum rustls_handshake_kind { - /** - * The type of handshake could not be determined. - * - * This variant should not be used. - */ - RUSTLS_HANDSHAKE_KIND_UNKNOWN = 0, - /** - * A full TLS handshake. - * - * This is the typical TLS connection initiation process when resumption is - * not yet unavailable, and the initial client hello was accepted by the server. - */ - RUSTLS_HANDSHAKE_KIND_FULL = 1, - /** - * A full TLS handshake, with an extra round-trip for a hello retry request. - * - * The server can respond with a hello retry request (HRR) if the initial client - * hello is unacceptable for several reasons, the most likely if no supported key - * shares were offered by the client. - */ - RUSTLS_HANDSHAKE_KIND_FULL_WITH_HELLO_RETRY_REQUEST = 2, - /** - * A resumed TLS handshake. - * - * Resumed handshakes involve fewer round trips and less cryptography than - * full ones, but can only happen when the peers have previously done a full - * handshake together, and then remember data about it. - */ - RUSTLS_HANDSHAKE_KIND_RESUMED = 3, -} rustls_handshake_kind; - /** * Numeric error codes returned from rustls-ffi API functions. */ -enum rustls_result { +enum rustls_result +#if __STDC_VERSION__ >= 202311L + : uint32_t +#endif // __STDC_VERSION__ >= 202311L + { RUSTLS_RESULT_OK = 7000, RUSTLS_RESULT_IO = 7001, RUSTLS_RESULT_NULL_PARAMETER = 7002, @@ -193,7 +162,11 @@ enum rustls_result { RUSTLS_RESULT_INVALID_ENCRYPTED_CLIENT_HELLO_NO_COMPATIBLE_CONFIG = 7701, RUSTLS_RESULT_INVALID_ENCRYPTED_CLIENT_HELLO_SNI_REQUIRED = 7702, }; +#if __STDC_VERSION__ >= 202311L +typedef enum rustls_result rustls_result; +#else typedef uint32_t rustls_result; +#endif // __STDC_VERSION__ >= 202311L /** * Definitions of known TLS protocol versions. @@ -208,6 +181,41 @@ typedef enum rustls_tls_version { RUSTLS_TLS_VERSION_TLSV1_3 = 772, } rustls_tls_version; +/** + * Describes which sort of handshake happened. + */ +typedef enum rustls_handshake_kind { + /** + * The type of handshake could not be determined. + * + * This variant should not be used. + */ + RUSTLS_HANDSHAKE_KIND_UNKNOWN = 0, + /** + * A full TLS handshake. + * + * This is the typical TLS connection initiation process when resumption is + * not yet unavailable, and the initial client hello was accepted by the server. + */ + RUSTLS_HANDSHAKE_KIND_FULL = 1, + /** + * A full TLS handshake, with an extra round-trip for a hello retry request. + * + * The server can respond with a hello retry request (HRR) if the initial client + * hello is unacceptable for several reasons, the most likely if no supported key + * shares were offered by the client. + */ + RUSTLS_HANDSHAKE_KIND_FULL_WITH_HELLO_RETRY_REQUEST = 2, + /** + * A resumed TLS handshake. + * + * Resumed handshakes involve fewer round trips and less cryptography than + * full ones, but can only happen when the peers have previously done a full + * handshake together, and then remember data about it. + */ + RUSTLS_HANDSHAKE_KIND_RESUMED = 3, +} rustls_handshake_kind; + /** * A parsed ClientHello produced by a rustls_acceptor. * diff --git a/tools/src/bin/docgen/main.rs b/tools/src/bin/docgen/main.rs index 0026bb38..7b4a3695 100644 --- a/tools/src/bin/docgen/main.rs +++ b/tools/src/bin/docgen/main.rs @@ -16,6 +16,7 @@ fn main() -> Result<(), Box> { // Parse the .h into an AST. let header_file = fs::read_to_string("librustls/src/rustls.h")?; //let header_file = fs::read_to_string("test.h")?; + let header_file = resolve_c23_conditionals(&header_file); let header_file_bytes = header_file.as_bytes(); let tree = parser .parse(&header_file, None) @@ -35,6 +36,55 @@ fn main() -> Result<(), Box> { Ok(()) } +/// Resolve `#if __STDC_VERSION__ >= 202311L` conditionals to the pre-C23 branch. +/// +/// cbindgen 0.29+ emits enums with a fixed representation using C23 typed enum +/// syntax, guarded by `__STDC_VERSION__` preprocessor conditionals, e.g.: +/// +/// ```c +/// enum rustls_result +/// #if __STDC_VERSION__ >= 202311L +/// : uint32_t +/// #endif // __STDC_VERSION__ >= 202311L +/// { +/// ... +/// }; +/// #if __STDC_VERSION__ >= 202311L +/// typedef enum rustls_result rustls_result; +/// #else +/// typedef uint32_t rustls_result; +/// #endif // __STDC_VERSION__ >= 202311L +/// ``` +/// +/// Tree-sitter's C grammar can't parse a preprocessor conditional in the middle +/// of an enum declaration, so evaluate the conditionals as if `__STDC_VERSION__` +/// were pre-C23 before parsing. This restores the shape older cbindgen emitted. +fn resolve_c23_conditionals(src: &str) -> String { + const C23_GUARD: &str = "#if __STDC_VERSION__ >= 202311L"; + + let mut out = Vec::new(); + let mut lines = src.lines(); + while let Some(line) = lines.next() { + if line.trim_end() != C23_GUARD { + out.push(line); + continue; + } + // Skip the C23 branch, keeping any #else branch. cbindgen doesn't nest + // further conditionals inside these guards. + let mut in_else = false; + for line in lines.by_ref() { + if line.starts_with("#endif") { + break; + } else if line.starts_with("#else") { + in_else = true; + } else if in_else { + out.push(line); + } + } + } + out.join("\n") + "\n" +} + #[derive(Debug, Default, Serialize)] struct ApiDocs { structs: Vec, diff --git a/website/static/api.json b/website/static/api.json index 5971a30b..07d29620 100644 --- a/website/static/api.json +++ b/website/static/api.json @@ -1486,39 +1486,6 @@ } ], "enums": [ - { - "anchor": "rustls-handshake-kind", - "comment": "Describes which sort of handshake happened.", - "feature": null, - "deprecation": null, - "name": "rustls_handshake_kind", - "variants": [ - { - "anchor": "rustls-handshake-kind-unknown", - "comment": "The type of handshake could not be determined.\n\n This variant should not be used.", - "name": "RUSTLS_HANDSHAKE_KIND_UNKNOWN", - "value": "0" - }, - { - "anchor": "rustls-handshake-kind-full", - "comment": "A full TLS handshake.\n\n This is the typical TLS connection initiation process when resumption is\n not yet unavailable, and the initial client hello was accepted by the server.", - "name": "RUSTLS_HANDSHAKE_KIND_FULL", - "value": "1" - }, - { - "anchor": "rustls-handshake-kind-full-with-hello-retry-request", - "comment": "A full TLS handshake, with an extra round-trip for a hello retry request.\n\n The server can respond with a hello retry request (HRR) if the initial client\n hello is unacceptable for several reasons, the most likely if no supported key\n shares were offered by the client.", - "name": "RUSTLS_HANDSHAKE_KIND_FULL_WITH_HELLO_RETRY_REQUEST", - "value": "2" - }, - { - "anchor": "rustls-handshake-kind-resumed", - "comment": "A resumed TLS handshake.\n\n Resumed handshakes involve fewer round trips and less cryptography than\n full ones, but can only happen when the peers have previously done a full\n handshake together, and then remember data about it.", - "name": "RUSTLS_HANDSHAKE_KIND_RESUMED", - "value": "3" - } - ] - }, { "anchor": "rustls-result", "comment": "Numeric error codes returned from rustls-ffi API functions.", @@ -2328,6 +2295,39 @@ "value": "772" } ] + }, + { + "anchor": "rustls-handshake-kind", + "comment": "Describes which sort of handshake happened.", + "feature": null, + "deprecation": null, + "name": "rustls_handshake_kind", + "variants": [ + { + "anchor": "rustls-handshake-kind-unknown", + "comment": "The type of handshake could not be determined.\n\n This variant should not be used.", + "name": "RUSTLS_HANDSHAKE_KIND_UNKNOWN", + "value": "0" + }, + { + "anchor": "rustls-handshake-kind-full", + "comment": "A full TLS handshake.\n\n This is the typical TLS connection initiation process when resumption is\n not yet unavailable, and the initial client hello was accepted by the server.", + "name": "RUSTLS_HANDSHAKE_KIND_FULL", + "value": "1" + }, + { + "anchor": "rustls-handshake-kind-full-with-hello-retry-request", + "comment": "A full TLS handshake, with an extra round-trip for a hello retry request.\n\n The server can respond with a hello retry request (HRR) if the initial client\n hello is unacceptable for several reasons, the most likely if no supported key\n shares were offered by the client.", + "name": "RUSTLS_HANDSHAKE_KIND_FULL_WITH_HELLO_RETRY_REQUEST", + "value": "2" + }, + { + "anchor": "rustls-handshake-kind-resumed", + "comment": "A resumed TLS handshake.\n\n Resumed handshakes involve fewer round trips and less cryptography than\n full ones, but can only happen when the peers have previously done a full\n handshake together, and then remember data about it.", + "name": "RUSTLS_HANDSHAKE_KIND_RESUMED", + "value": "3" + } + ] } ], "externs": [