7#ifndef _DARWIN_C_SOURCE
8#define _DARWIN_C_SOURCE
10#elif !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && \
11 !defined(__DragonFly__)
12#define _POSIX_C_SOURCE 200809L
27#include <sys/socket.h>
35#include <openssl/x509.h>
42#ifdef CWIST_WEBTRANSPORT_CLIENT
43#error "the WebTransport client is experimental and not built on main"
46#include <lsxpack_header.h>
59static int g_h3c_global_ref = 0;
66static void h3c_global_init(
void) {
68 if (g_h3c_global_ref == 0) {
80static void h3c_global_cleanup(
void) {
82 if (g_h3c_global_ref > 0) {
84 if (g_h3c_global_ref == 0) {
95typedef struct h3c_stream_ctx {
103 size_t req_body_sent;
105 size_t resp_body_len;
106 size_t resp_body_cap;
110#ifdef CWIST_WEBTRANSPORT_CLIENT
115#ifdef CWIST_WEBTRANSPORT_CLIENT
136 int datagram_enabled;
156#ifdef CWIST_WEBTRANSPORT_CLIENT
186 if (client->local_addr_len == 0) {
187 msg.msg_name = (
void *)
spec->dest_sa;
207#define H3C_MAX_HEADERS 64
208#define H3C_DECODE_BUF_SIZE 65536
210typedef struct h3c_hset {
252 (
size_t)
xhdr->name_offset >=
sizeof(
hset->decode_buf) ||
263 sizeof(
hset->decode_buf) -
hset->decode_off);
281 if (total >
sizeof(
hset->decode_buf) -
hset->decode_off)
return -1;
282 hset->decode_off += total;
292static void h3c_hsi_discard(
void *
hset_p) {
297 .hsi_create_header_set = h3c_hsi_create,
298 .hsi_prepare_decode = h3c_hsi_prepare,
299 .hsi_process_header = h3c_hsi_process_header,
300 .hsi_discard_header_set = h3c_hsi_discard,
327 if (client && client->conn == conn) {
353 client->active_stream =
st;
358#ifdef CWIST_WEBTRANSPORT_CLIENT
370 if (!client || !client->wt_connecting)
return NULL;
372 client->wt_connecting->native =
native;
373 client->wt_connecting->open = 1;
374 if (client->active_stream) client->active_stream->response_ready = 1;
393 if (!client || !client->wt_connecting)
return;
395 client->wt_connecting->rejected = 1;
396 if (client->active_stream) client->active_stream->response_ready = 1;
410 session->native =
NULL;
440 unsigned char buf[8192];
443 if (!
st->headers_done) {
449 for (
i = 0;
i <
hs->count &&
st->res; ++
i) {
461 if (!name || !value) {
470 if (
strcmp(name,
":status") == 0) {
478 st->headers_done = 1;
479#ifdef CWIST_WEBTRANSPORT_CLIENT
480 if (
st->is_webtransport_connect) {
483 if (!client || !
st->res ||
st->res->status_code < 200 ||
484 st->res->status_code >= 300) {
485 if (client && client->wt_connecting) client->wt_connecting->rejected = 1;
486 st->response_ready = 1;
490 .wtci_authority = client->host,
491 .wtci_path =
st->req_path,
496 .wtap_wt_if_ctx = client,
497 .wtap_connect_info = &
info,
500 if (client->wt_connecting) client->wt_connecting->rejected = 1;
501 st->response_ready = 1;
511 if (
need >
st->resp_body_cap) {
512 size_t new_cap =
st->resp_body_cap ?
st->resp_body_cap * 2 : 4096;
527 if (
st->resp_body_len > 0 &&
st->res &&
st->res->body) {
530 st->response_ready = 1;
549 if (!
st ||
st->write_done)
return;
558 h3_hdr_out_add_str(&
hdrs,
":path",
st->req_path ?
st->req_path :
"/");
561 h3_hdr_out_add_str(&
hdrs,
":authority", client && client->host ? client->host :
"localhost");
562 h3_hdr_out_add_str(&
hdrs,
":scheme",
"https");
565 while (node &&
hdrs.count <
hdrs.max) {
570 const char *name = node->
key->
data;
598 st->req_body_len -
st->req_body_sent);
604 if (
st->req_body_sent >=
st->req_body_len) {
638 st->response_ready = 1;
639 if (client->active_stream ==
st) {
640 client->active_stream =
NULL;
663 if (!client)
return 0;
665 if (client->out_dgram.pending && client->out_dgram.data && client->out_dgram.len > 0) {
666 size_t to_copy = client->out_dgram.len < len ? client->out_dgram.len : len;
668 free(client->out_dgram.data);
669 client->out_dgram.data =
NULL;
670 client->out_dgram.len = 0;
671 client->out_dgram.pending = 0;
691static void h3c_on_datagram(
lsquic_conn_t *conn,
const void *buf,
size_t len) {
693 if (!client || !buf || len == 0)
return;
695 if (client->in_dgram.data)
free(client->in_dgram.data);
696 client->in_dgram.data =
malloc(len);
697 if (client->in_dgram.data) {
698 memcpy(client->in_dgram.data, buf, len);
699 client->in_dgram.len = len;
700 client->in_dgram.ready = 1;
706 .on_new_conn = h3c_on_new_conn,
707 .on_conn_closed = h3c_on_conn_closed,
708 .on_new_stream = h3c_on_new_stream,
709 .on_read = h3c_on_read,
710 .on_write = h3c_on_write,
711 .on_close = h3c_on_close,
712 .on_dg_write = h3c_on_dg_write,
713 .on_datagram = h3c_on_datagram,
732static void h3c_process_io(cwist_http3_client *client,
int timeout_ms) {
734 int diff = timeout_ms * 1000;
740 else if (
diff > 1000000)
748 unsigned char buf[65535];
750 socklen_t peer_addr_len =
sizeof(peer_addr);
755 client->local_addr_len ? (
struct sockaddr *)&client->local_addr
777 return client ? client->ssl_ctx :
NULL;
789 h3c_global_cleanup();
794 client->timeout_ms = 30000;
795 client->max_retries = 0;
796 client->retry_delay_ms = 1000;
797 client->conn_timeout_ms = 5000;
804 if (!client->ssl_ctx) {
806 h3c_global_cleanup();
822 settings.es_noprogress_timeout = 30;
826 settings.es_datagrams = client->datagram_enabled ? 1 : 0;
827#ifdef CWIST_WEBTRANSPORT_CLIENT
829 settings.es_max_webtransport_sessions = 4;
841 h3c_global_cleanup();
846 .ea_stream_if = &h3c_stream_if,
847 .ea_stream_if_ctx = client,
848 .ea_packets_out = h3c_packets_out,
849 .ea_packets_out_ctx = client,
850 .ea_get_ssl_ctx = h3c_get_ssl_ctx,
851 .ea_hsi_if = &h3c_hset_if,
858 if (!client->engine) {
863 h3c_global_cleanup();
872 if (client->engine) {
875 if (client->ssl_ctx) {
878 if (client->udp_fd >= 0) {
879 close(client->udp_fd);
882 free(client->out_dgram.data);
883 free(client->in_dgram.data);
884#ifdef CWIST_WEBTRANSPORT_CLIENT
885 free(client->wt_connecting);
891 h3c_global_cleanup();
895 if (!client || !host)
return -1;
896 char *copy =
strdup(host);
897 if (!copy)
return -1;
905 if (!client || !client->ssl_ctx)
return -1;
916 if (!client || !client->ssl_ctx)
return;
921 if (client) client->timeout_ms = timeout_ms;
925 if (!client || !client->ssl_ctx)
return;
930 if (client) client->datagram_enabled = enabled;
942 if (!client || !client->engine || !client->host || !path || !
out_response) {
943 err.error.err_i16 = -1;
950 if (client->udp_fd < 0) {
952 if (client->udp_fd < 0) {
953 err.error.err_i16 = -1;
961 if (client->peer_addr_len == 0) {
968 err.error.err_i16 = -1;
971 memcpy(&client->peer_addr, res->ai_addr, res->ai_addrlen);
972 client->peer_addr_len = res->ai_addrlen;
979 if (client->local_addr_len == 0) {
980 if (
connect(client->udp_fd, (
struct sockaddr *)&client->peer_addr, client->peer_addr_len) !=
982 err.error.err_i16 = -1;
985 client->local_addr_len =
sizeof(client->local_addr);
987 &client->local_addr_len) != 0) {
988 client->local_addr_len = 0;
989 err.error.err_i16 = -1;
998 int max_attempts = 1 + client->max_retries;
999 while (
attempt < max_attempts) {
1006 client->conn =
NULL;
1010 if (!client->conn) {
1012 (
struct sockaddr *)&client->local_addr,
1013 (
struct sockaddr *)&client->peer_addr, client,
1015 if (!client->conn) {
1016 err.error.err_i16 = -1;
1023 client->active_stream =
NULL;
1031 stream_deadline.tv_nsec += (client->conn_timeout_ms % 1000) * 1000000;
1038 h3c_process_io(client, 100);
1040 st = client->active_stream;
1047 if (!client->conn)
break;
1053 client->conn =
NULL;
1064 err.error.err_i16 = -1;
1069 if (!
st->req_path) {
1070 err.error.err_i16 = -1;
1073 st->req_method = method;
1074 st->req_headers = headers;
1075#ifdef CWIST_WEBTRANSPORT_CLIENT
1077 if (header->key && header->value && header->key->data && header->value->data &&
1078 strcasecmp(header->key->data,
":protocol") == 0 &&
1079 strcasecmp(header->value->data,
"webtransport") == 0) {
1080 st->is_webtransport_connect = 1;
1087 if (body && body_len > 0) {
1089 if (!
st->req_body) {
1090 err.error.err_i16 = -1;
1093 memcpy(
st->req_body, body, body_len);
1094 st->req_body_len = body_len;
1103 deadline.tv_sec += client->timeout_ms / 1000;
1104 deadline.tv_nsec += (client->timeout_ms % 1000) * 1000000;
1105 if (deadline.tv_nsec >= 1000000000) {
1107 deadline.tv_nsec -= 1000000000;
1111 while (!
st->response_ready) {
1113 h3c_process_io(client, 100);
1117 if (
now.tv_sec > deadline.tv_sec ||
1118 (
now.tv_sec == deadline.tv_sec &&
now.tv_nsec >= deadline.tv_nsec)) {
1124 if (
st->response_ready) {
1127 err.error.err_i16 = 0;
1134 err.error.err_i16 = -1;
1136 if (client->active_stream ==
st) {
1137 client->active_stream =
NULL;
1163 if (!client || !client->conn || !data || len == 0)
return -1;
1164 if (!client->datagram_enabled)
return -1;
1167 if (client->out_dgram.pending) {
1171 client->out_dgram.data =
malloc(len);
1172 if (!client->out_dgram.data) {
1176 memcpy(client->out_dgram.data, data, len);
1177 client->out_dgram.len = len;
1178 client->out_dgram.pending = 1;
1186 if (!client || !buf || len == 0)
return -1;
1189 if (!client->in_dgram.ready || !client->in_dgram.data) {
1193 size_t to_copy = client->in_dgram.len < len ? client->in_dgram.len : len;
1195 free(client->in_dgram.data);
1196 client->in_dgram.data =
NULL;
1197 client->in_dgram.len = 0;
1198 client->in_dgram.ready = 0;
1207 if (client) client->max_retries = max_retries > 0 ? max_retries : 0;
1215 if (client) client->conn_timeout_ms = timeout_ms > 0 ? timeout_ms : 5000;
Memory allocation wrappers backed by libttak.
cwist_error_t cwist_sstring_assign_len(cwist_sstring *str, const char *data, size_t len)
Assign raw bytes with length.
@ CWIST_ERR_INT16
used when checking common errcodes in Unix/Linux
cwist_error_t make_error(cwist_errtype_t type)
Creates an error object initialized with the specified type.
mbedTLS TLS backend: EVP subset (HMAC digest tags, AES-256-CBC).
Outgoing HTTP/3 header set builder for lsquic (private).
ssize_t cwist_http3_client_recv_datagram(cwist_http3_client *client, void *buf, size_t len)
Receive an unreliable QUIC datagram.
void cwist_http3_client_set_conn_timeout_ms(cwist_http3_client *client, int timeout_ms)
Set the connection establishment / stream creation timeout.
int cwist_http3_client_send_datagram(cwist_http3_client *client, const void *data, size_t len)
Send an unreliable QUIC datagram.
cwist_error_t cwist_http3_client_request(cwist_http3_client *client, const char *path, cwist_http_method_t method, cwist_http_header_node *headers, const char *body, size_t body_len, cwist_http_response **out_response)
Perform a synchronous HTTP/3 request.
int cwist_http3_client_set_ca_bundle(cwist_http3_client *client, const char *ca_path)
Load CA bundle for TLS certificate verification.
struct h3c_stream_ctx h3c_stream_ctx_t
void cwist_http3_client_enable_datagrams(cwist_http3_client *client, int enabled)
Enable or disable QUIC datagram support (RFC 9221).
void cwist_http3_client_set_insecure(cwist_http3_client *client, int enabled)
Disable or re-enable TLS certificate verification.
int cwist_http3_client_set_server(cwist_http3_client *client, const char *host, uint16_t port)
Set server address for subsequent requests.
void cwist_http3_client_set_timeout_ms(cwist_http3_client *client, int timeout_ms)
Set request timeout in milliseconds.
void cwist_http3_client_set_max_retries(cwist_http3_client *client, int max_retries)
Set the maximum number of request retries on transient failure.
void cwist_http3_client_enable_0rtt(cwist_http3_client *client, int enabled)
Enable or disable 0-RTT session resumption.
#define H3C_DECODE_BUF_SIZE
struct h3c_hset h3c_hset_t
void cwist_http3_client_set_retry_delay_ms(cwist_http3_client *client, int delay_ms)
Set the base delay between retries in milliseconds.
cwist_http3_client * cwist_http3_client_create(void)
void cwist_http3_client_destroy(cwist_http3_client *client)
HTTP/3 client using lsquic/BoringSSL.
struct cwist_http3_client cwist_http3_client
Opaque HTTP/3 client handle.
cwist_error_t cwist_http_header_add(cwist_http_header_node **head, const char *key, const char *value)
Add a header to the list.
void cwist_http_response_destroy(cwist_http_response *res)
Destroy an HTTP response object.
cwist_http_response * cwist_http_response_create(void)
Create a new HTTP response object.
const char * cwist_http_method_to_string(cwist_http_method_t method)
Convert method enum to string.
mbedTLS TLS backend: OpenSSL-shaped seam over the CWIST TLS subset.
struct cwist_mtls_method SSL_METHOD
#define SSL_CTX_set_default_verify_paths
#define SSL_CTX_set_verify
struct cwist_mtls_ctx SSL_CTX
#define SSL_CTX_set_min_proto_version
Integrated error structure used throughout the system.
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
char * data
Access directly only when raw handling is necessary.
struct lsxpack_header * arr
Unified error code and type definitions for the CWIST system layer.