return (result || n <= 0)? 1 : (size_t)n;
}
+int Curl_conn_get_stream_error(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex)
+{
+ CURLcode result;
+ int n = 0;
+
+ struct Curl_cfilter *cf = conn->cfilter[sockindex];
+ result = cf? cf->cft->query(cf, data, CF_QUERY_STREAM_ERROR,
+ &n, NULL) : CURLE_UNKNOWN_OPTION;
+ return (result || n < 0)? 0 : n;
+}
+
int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd)
{
if(data && data->conn &&
#define CF_QUERY_SOCKET 3 /* - curl_socket_t */
#define CF_QUERY_TIMER_CONNECT 4 /* - struct curltime */
#define CF_QUERY_TIMER_APPCONNECT 5 /* - struct curltime */
+#define CF_QUERY_STREAM_ERROR 6 /* error code - */
/**
* Query the cfilter for properties. Filters ignorant of a query will
struct connectdata *conn,
int sockindex);
+/**
+ * Get the underlying error code for a transfer stream or 0 if not known.
+ */
+int Curl_conn_get_stream_error(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex);
/**
* Get the index of the given socket in the connection's sockets.
fprintf(stderr, "\n");
}
#endif
+
+void Curl_hash_offt_init(struct Curl_hash *h,
+ unsigned int slots,
+ Curl_hash_dtor dtor)
+{
+ Curl_hash_init(h, slots, Curl_hash_str, Curl_str_key_compare, dtor);
+}
+
+void *Curl_hash_offt_set(struct Curl_hash *h, curl_off_t id, void *elem)
+{
+ return Curl_hash_add(h, &id, sizeof(id), elem);
+}
+
+int Curl_hash_offt_remove(struct Curl_hash *h, curl_off_t id)
+{
+ return Curl_hash_delete(h, &id, sizeof(id));
+}
+
+void *Curl_hash_offt_get(struct Curl_hash *h, curl_off_t id)
+{
+ return Curl_hash_pick(h, &id, sizeof(id));
+}
void Curl_hash_print(struct Curl_hash *h,
void (*func)(void *));
+/* Hash for `curl_off_t` as key */
+void Curl_hash_offt_init(struct Curl_hash *h,
+ unsigned int slots,
+ Curl_hash_dtor dtor);
+
+void *Curl_hash_offt_set(struct Curl_hash *h, curl_off_t id, void *elem);
+int Curl_hash_offt_remove(struct Curl_hash *h, curl_off_t id);
+void *Curl_hash_offt_get(struct Curl_hash *h, curl_off_t id);
+
#endif /* HEADER_CURL_HASH_H */
* HTTP unique setup
***************************************************************************/
struct HTTP {
-#ifndef CURL_DISABLE_HTTP
- void *h2_ctx; /* HTTP/2 implementation context */
- void *h3_ctx; /* HTTP/3 implementation context */
-#else
+ /* TODO: no longer used, we should remove it from SingleRequest */
char unused;
-#endif
};
CURLcode Curl_http_size(struct Curl_easy *data);
#include <nghttp2/nghttp2.h>
#include "urldata.h"
#include "bufq.h"
+#include "hash.h"
#include "http1.h"
#include "http2.h"
#include "http.h"
struct bufc_pool stream_bufcp; /* spares for stream buffers */
struct dynbuf scratch; /* scratch buffer for temp use */
+ struct Curl_hash streams; /* hash of `data->id` to `h2_stream_ctx` */
size_t drain_total; /* sum of all stream's UrlState drain */
uint32_t max_concurrent_streams;
int32_t goaway_error;
Curl_bufq_free(&ctx->outbufq);
Curl_bufcp_free(&ctx->stream_bufcp);
Curl_dyn_free(&ctx->scratch);
+ Curl_hash_clean(&ctx->streams);
+ Curl_hash_destroy(&ctx->streams);
memset(ctx, 0, sizeof(*ctx));
ctx->call_data = save;
}
buffered data in stream->sendbuf to upload. */
};
-#define H2_STREAM_CTX(d) ((struct h2_stream_ctx *)(((d) && \
- (d)->req.p.http)? \
- ((struct HTTP *)(d)->req.p.http)->h2_ctx \
- : NULL))
-#define H2_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h2_ctx
-#define H2_STREAM_ID(d) (H2_STREAM_CTX(d)? \
- H2_STREAM_CTX(d)->id : -2)
+#define H2_STREAM_CTX(ctx,data) ((struct h2_stream_ctx *)(\
+ data? Curl_hash_offt_get(&(ctx)->streams, (data)->id) : NULL))
+
+static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx)
+{
+ struct h2_stream_ctx *stream;
+
+ (void)ctx;
+ stream = calloc(1, sizeof(*stream));
+ if(!stream)
+ return NULL;
+
+ stream->id = -1;
+ Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp,
+ H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE);
+ Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
+ Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST);
+ stream->resp_hds_len = 0;
+ stream->bodystarted = FALSE;
+ stream->status_code = -1;
+ stream->closed = FALSE;
+ stream->close_handled = FALSE;
+ stream->error = NGHTTP2_NO_ERROR;
+ stream->local_window_size = H2_STREAM_WINDOW_SIZE;
+ stream->upload_left = 0;
+ stream->nrcvd_data = 0;
+ return stream;
+}
+
+static void free_push_headers(struct h2_stream_ctx *stream)
+{
+ size_t i;
+ for(i = 0; i<stream->push_headers_used; i++)
+ free(stream->push_headers[i]);
+ Curl_safefree(stream->push_headers);
+ stream->push_headers_used = 0;
+}
+
+static void h2_stream_ctx_free(struct h2_stream_ctx *stream)
+{
+ Curl_bufq_free(&stream->sendbuf);
+ Curl_h1_req_parse_free(&stream->h1);
+ Curl_dynhds_free(&stream->resp_trailers);
+ free_push_headers(stream);
+ free(stream);
+}
+
+static void h2_stream_hash_free(void *stream)
+{
+ DEBUGASSERT(stream);
+ h2_stream_ctx_free((struct h2_stream_ctx *)stream);
+}
/*
* Mark this transfer to get "drained".
failf(data, "initialization failure, transfer not http initialized");
return CURLE_FAILED_INIT;
}
- stream = H2_STREAM_CTX(data);
+ stream = H2_STREAM_CTX(ctx, data);
if(stream) {
*pstream = stream;
return CURLE_OK;
}
- stream = calloc(1, sizeof(*stream));
+ stream = h2_stream_ctx_create(ctx);
if(!stream)
return CURLE_OUT_OF_MEMORY;
- stream->id = -1;
- Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp,
- H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE);
- Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
- Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST);
- stream->resp_hds_len = 0;
- stream->bodystarted = FALSE;
- stream->status_code = -1;
- stream->closed = FALSE;
- stream->close_handled = FALSE;
- stream->error = NGHTTP2_NO_ERROR;
- stream->local_window_size = H2_STREAM_WINDOW_SIZE;
- stream->upload_left = 0;
- stream->nrcvd_data = 0;
+ if(!Curl_hash_offt_set(&ctx->streams, data->id, stream)) {
+ h2_stream_ctx_free(stream);
+ return CURLE_OUT_OF_MEMORY;
+ }
- H2_STREAM_LCTX(data) = stream;
*pstream = stream;
return CURLE_OK;
}
-static void free_push_headers(struct h2_stream_ctx *stream)
-{
- size_t i;
- for(i = 0; i<stream->push_headers_used; i++)
- free(stream->push_headers[i]);
- Curl_safefree(stream->push_headers);
- stream->push_headers_used = 0;
-}
-
static void http2_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct cf_h2_ctx *ctx = cf->ctx;
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
DEBUGASSERT(ctx);
if(!stream)
nghttp2_session_send(ctx->h2);
}
- Curl_bufq_free(&stream->sendbuf);
- Curl_h1_req_parse_free(&stream->h1);
- Curl_dynhds_free(&stream->resp_trailers);
- free_push_headers(stream);
- free(stream);
- H2_STREAM_LCTX(data) = NULL;
+ Curl_hash_offt_remove(&ctx->streams, data->id);
}
static int h2_client_new(struct Curl_cfilter *cf,
Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0);
Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0);
Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER);
+ Curl_hash_offt_init(&ctx->streams, 63, h2_stream_hash_free);
ctx->last_stream_id = 2147483647;
rc = nghttp2_session_callbacks_new(&cbs);
the struct are hidden from the user. */
struct curl_pushheaders {
struct Curl_easy *data;
+ struct h2_stream_ctx *stream;
const nghttp2_push_promise *frame;
};
if(!h || !GOOD_EASY_HANDLE(h->data))
return NULL;
else {
- struct h2_stream_ctx *stream = H2_STREAM_CTX(h->data);
- if(stream && num < stream->push_headers_used)
- return stream->push_headers[num];
+ if(h->stream && num < h->stream->push_headers_used)
+ return h->stream->push_headers[num];
}
return NULL;
}
!strcmp(header, ":") || strchr(header + 1, ':'))
return NULL;
- stream = H2_STREAM_CTX(h->data);
+ stream = h->stream;
if(!stream)
return NULL;
goto fail;
}
- heads.data = data;
- heads.frame = frame;
/* ask the application */
CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ask application");
- stream = H2_STREAM_CTX(data);
+ stream = H2_STREAM_CTX(ctx, data);
if(!stream) {
failf(data, "Internal NULL stream");
discard_newhandle(cf, newhandle);
goto fail;
}
+ heads.data = data;
+ heads.stream = stream;
+ heads.frame = frame;
+
rv = set_transfer_url(newhandle, &heads);
if(rv) {
discard_newhandle(cf, newhandle);
const nghttp2_frame *frame)
{
struct cf_h2_ctx *ctx = cf->ctx;
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
int32_t stream_id = frame->hd.stream_id;
CURLcode result;
int rv;
* servers send an explicit WINDOW_UPDATE, but not all seem to do that.
* To be safe, we UNHOLD a stream in order not to stall. */
if(CURL_WANT_SEND(data)) {
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
if(stream)
drain_stream(cf, data, stream);
}
return 0;
}
- stream = H2_STREAM_CTX(data_s);
+ stream = H2_STREAM_CTX(ctx, data_s);
if(!stream)
return NGHTTP2_ERR_CALLBACK_FAILURE;
uint32_t error_code, void *userp)
{
struct Curl_cfilter *cf = userp;
+ struct cf_h2_ctx *ctx = cf->ctx;
struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf);
struct h2_stream_ctx *stream;
int rv;
(void)nghttp2_session_set_stream_user_data(session, stream_id, 0);
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
- stream = H2_STREAM_CTX(data_s);
+ stream = H2_STREAM_CTX(ctx, data_s);
if(!stream) {
CURL_TRC_CF(data_s, cf,
"[%d] on_stream_close, GOOD easy but no stream", stream_id);
const nghttp2_frame *frame, void *userp)
{
struct Curl_cfilter *cf = userp;
+ struct cf_h2_ctx *ctx = cf->ctx;
struct h2_stream_ctx *stream;
struct Curl_easy *data_s = NULL;
return 0;
}
- stream = H2_STREAM_CTX(data_s);
+ stream = H2_STREAM_CTX(ctx, data_s);
if(!stream || !stream->bodystarted) {
return 0;
}
internal error more than anything else! */
return NGHTTP2_ERR_CALLBACK_FAILURE;
- stream = H2_STREAM_CTX(data_s);
+ stream = H2_STREAM_CTX(ctx, data_s);
if(!stream) {
failf(data_s, "Internal NULL stream");
return NGHTTP2_ERR_CALLBACK_FAILURE;
void *userp)
{
struct Curl_cfilter *cf = userp;
+ struct cf_h2_ctx *ctx = cf->ctx;
struct Curl_easy *data_s;
struct h2_stream_ctx *stream = NULL;
CURLcode result;
internal error more than anything else! */
return NGHTTP2_ERR_CALLBACK_FAILURE;
- stream = H2_STREAM_CTX(data_s);
+ stream = H2_STREAM_CTX(ctx, data_s);
if(!stream)
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
{
struct cf_h2_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
if(!ctx || !ctx->h2 || !stream)
goto out;
* struct.
*/
-static void h2_pri_spec(struct Curl_easy *data,
+static void h2_pri_spec(struct cf_h2_ctx *ctx,
+ struct Curl_easy *data,
nghttp2_priority_spec *pri_spec)
{
struct Curl_data_priority *prio = &data->set.priority;
- struct h2_stream_ctx *depstream = H2_STREAM_CTX(prio->parent);
+ struct h2_stream_ctx *depstream = H2_STREAM_CTX(ctx, prio->parent);
int32_t depstream_id = depstream? depstream->id:0;
nghttp2_priority_spec_init(pri_spec, depstream_id,
sweight_wanted(data),
struct Curl_easy *data)
{
struct cf_h2_ctx *ctx = cf->ctx;
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
int rv = 0;
if(stream && stream->id > 0 &&
/* send new weight and/or dependency */
nghttp2_priority_spec pri_spec;
- h2_pri_spec(data, &pri_spec);
+ h2_pri_spec(ctx, data, &pri_spec);
CURL_TRC_CF(data, cf, "[%d] Queuing PRIORITY", stream->id);
DEBUGASSERT(stream->id != -1);
rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE,
* it is time to stop due to connection close or us not processing
* all network input */
while(!ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) {
- stream = H2_STREAM_CTX(data);
+ stream = H2_STREAM_CTX(ctx, data);
if(stream && (stream->closed || !data_max_bytes)) {
/* We would like to abort here and stop processing, so that
* the transfer loop can handle the data/close here. However,
char *buf, size_t len, CURLcode *err)
{
struct cf_h2_ctx *ctx = cf->ctx;
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
ssize_t nread = -1;
CURLcode result;
struct cf_call_data save;
goto out;
}
- h2_pri_spec(data, &pri_spec);
+ h2_pri_spec(ctx, data, &pri_spec);
if(!nghttp2_session_check_request_allowed(ctx->h2))
CURL_TRC_CF(data, cf, "send request NOT allowed (via nghttp2)");
const void *buf, size_t len, CURLcode *err)
{
struct cf_h2_ctx *ctx = cf->ctx;
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
struct cf_call_data save;
int rv;
ssize_t nwritten;
sock = Curl_conn_cf_get_socket(cf, data);
Curl_pollset_check(data, ps, sock, &want_recv, &want_send);
if(want_recv || want_send) {
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
struct cf_call_data save;
bool c_exhaust, s_exhaust;
{
#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE
struct cf_h2_ctx *ctx = cf->ctx;
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
DEBUGASSERT(data);
if(ctx && ctx->h2 && stream) {
const struct Curl_easy *data)
{
struct cf_h2_ctx *ctx = cf->ctx;
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
if(ctx && (!Curl_bufq_is_empty(&ctx->inbufq)
|| (stream && !Curl_bufq_is_empty(&stream->sendbuf))))
*pres1 = (effective_max > INT_MAX)? INT_MAX : (int)effective_max;
CF_DATA_RESTORE(cf, save);
return CURLE_OK;
+ case CF_QUERY_STREAM_ERROR: {
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
+ *pres1 = stream? (int)stream->error : 0;
+ return CURLE_OK;
+ }
default:
break;
}
CURLE_HTTP2_STREAM error! */
bool Curl_h2_http_1_1_error(struct Curl_easy *data)
{
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
- return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED);
+ if(Curl_conn_is_http2(data, data->conn, FIRSTSOCKET)) {
+ int err = Curl_conn_get_stream_error(data, data->conn, FIRSTSOCKET);
+ return (err == NGHTTP2_HTTP_1_1_REQUIRED);
+ }
+ return FALSE;
}
#else /* !USE_NGHTTP2 */
#ifdef USE_MSH3
#include "urldata.h"
+#include "hash.h"
#include "timeval.h"
#include "multiif.h"
#include "sendf.h"
struct cf_call_data call_data;
struct curltime connect_started; /* time the current attempt started */
struct curltime handshake_at; /* time connect handshake finished */
+ struct Curl_hash streams; /* hash `data->id` to `stream_ctx` */
/* Flags written by msh3/msquic thread */
bool handshake_complete;
bool handshake_succeeded;
BIT(active);
};
+static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data);
+
/* How to access `call_data` from a cf_msh3 filter */
#undef CF_CTX_CALL_DATA
#define CF_CTX_CALL_DATA(cf) \
bool recv_header_complete;
};
-#define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \
- ((struct HTTP *)(d)->req.p.http)->h3_ctx \
- : NULL))
-#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx
-#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \
- H3_STREAM_CTX(d)->id : -2)
+#define H3_STREAM_CTX(ctx,data) ((struct stream_ctx *)((data && ctx)? \
+ Curl_hash_offt_get(&(ctx)->streams, (data)->id) : NULL))
+
+static void h3_stream_ctx_free(struct stream_ctx *stream)
+{
+ Curl_bufq_free(&stream->recvbuf);
+ free(stream);
+}
+static void h3_stream_hash_free(void *stream)
+{
+ DEBUGASSERT(stream);
+ h3_stream_ctx_free((struct stream_ctx *)stream);
+}
static CURLcode h3_data_setup(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = cf->ctx;
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
if(stream)
return CURLE_OK;
if(!stream)
return CURLE_OUT_OF_MEMORY;
- H3_STREAM_LCTX(data) = stream;
stream->req = ZERO_NULL;
msh3_lock_initialize(&stream->recv_lock);
Curl_bufq_init2(&stream->recvbuf, H3_STREAM_CHUNK_SIZE,
H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
CURL_TRC_CF(data, cf, "data setup");
+
+ if(!Curl_hash_offt_set(&ctx->streams, data->id, stream)) {
+ h3_stream_ctx_free(stream);
+ return CURLE_OUT_OF_MEMORY;
+ }
+
return CURLE_OK;
}
static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = cf->ctx;
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
(void)cf;
if(stream) {
CURL_TRC_CF(data, cf, "easy handle is done");
- Curl_bufq_free(&stream->recvbuf);
- free(stream);
- H3_STREAM_LCTX(data) = NULL;
+ Curl_hash_offt_remove(&ctx->streams, data->id);
}
}
static void drain_stream(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = cf->ctx;
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
unsigned char bits;
(void)cf;
static CURLcode write_resp_raw(struct Curl_easy *data,
const void *mem, size_t memlen)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURLcode result = CURLE_OK;
ssize_t nwritten;
const MSH3_HEADER *hd)
{
struct Curl_easy *data = userp;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURLcode result;
(void)Request;
+ DEBUGF(infof(data, "[MSH3] header received, stream=%d", !!stream));
if(!stream || stream->recv_header_complete) {
return;
}
const uint8_t *buf)
{
struct Curl_easy *data = IfContext;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURLcode result;
bool rv = FALSE;
bool aborted, uint64_t error)
{
struct Curl_easy *data = IfContext;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
(void)Request;
if(!stream)
void *IfContext)
{
struct Curl_easy *data = IfContext;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
if(!stream)
return;
void *IfContext, void *SendContext)
{
struct Curl_easy *data = IfContext;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
if(!stream)
return;
(void)Request;
struct Curl_easy *data,
CURLcode *err)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = cf->ctx;
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
ssize_t nread = -1;
if(!stream) {
static void set_quic_expire(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = cf->ctx;
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
/* we have no indication from msh3 when it would be a good time
* to juggle the connection again. So, we compromise by calling
static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
char *buf, size_t len, CURLcode *err)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = cf->ctx;
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
ssize_t nread = -1;
struct cf_call_data save;
- (void)cf;
+ CURL_TRC_CF(data, cf, "cf_recv(len=%zu), stream=%d", len, !!stream);
if(!stream) {
*err = CURLE_RECV_ERROR;
return -1;
}
CF_DATA_SAVE(save, cf, data);
- CURL_TRC_CF(data, cf, "req: recv with %zu byte buffer", len);
msh3_lock_acquire(&stream->recv_lock);
const void *buf, size_t len, CURLcode *err)
{
struct cf_msh3_ctx *ctx = cf->ctx;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
struct h1_req_parser h1;
struct dynhds h2_headers;
MSH3_HEADER *nva = NULL;
struct easy_pollset *ps)
{
struct cf_msh3_ctx *ctx = cf->ctx;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
struct cf_call_data save;
CF_DATA_SAVE(save, cf, data);
static bool cf_msh3_data_pending(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = cf->ctx;
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
struct cf_call_data save;
bool pending = FALSE;
struct Curl_easy *data,
int event, int arg1, void *arg2)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_msh3_ctx *ctx = cf->ctx;
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
struct cf_call_data save;
CURLcode result = CURLE_OK;
CURLcode result;
bool verify;
+ Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free);
conn_config = Curl_ssl_cf_get_primary_config(cf);
if(!conn_config)
return CURLE_FAILED_INIT;
MsH3ApiClose(ctx->api);
ctx->api = NULL;
}
+ Curl_hash_destroy(&ctx->streams);
if(ctx->active) {
/* We share our socket at cf->conn->sock[cf->sockindex] when active.
cf_msh3_query,
};
+static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data)
+{
+ if(data && data->conn) {
+ struct Curl_cfilter *cf = data->conn->cfilter[FIRSTSOCKET];
+ while(cf) {
+ if(cf->cft == &Curl_cft_http3)
+ return cf->ctx;
+ cf = cf->next;
+ }
+ }
+ DEBUGF(infof(data, "no filter context found"));
+ return NULL;
+}
+
CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
#endif
#include "urldata.h"
+#include "hash.h"
#include "sendf.h"
#include "strdup.h"
#include "rand.h"
struct curltime reconnect_at; /* time the next attempt should start */
struct bufc_pool stream_bufcp; /* chunk pool for streams */
struct dynbuf scratch; /* temp buffer for header construction */
+ struct Curl_hash streams; /* hash `data->id` to `h3_stream_ctx` */
size_t max_stream_window; /* max flow window for one stream */
uint64_t max_idle_ms; /* max idle time for QUIC connection */
int qlogfd;
BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */
};
-#define H3_STREAM_CTX(d) ((struct h3_stream_ctx *)(((d) && (d)->req.p.http)? \
- ((struct HTTP *)(d)->req.p.http)->h3_ctx \
- : NULL))
-#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx
-#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \
- H3_STREAM_CTX(d)->id : -2)
+#define H3_STREAM_CTX(ctx,data) ((struct h3_stream_ctx *)(\
+ data? Curl_hash_offt_get(&(ctx)->streams, (data)->id) : NULL))
+
+static void h3_stream_ctx_free(struct h3_stream_ctx *stream)
+{
+ Curl_bufq_free(&stream->sendbuf);
+ Curl_h1_req_parse_free(&stream->h1);
+ free(stream);
+}
+
+static void h3_stream_hash_free(void *stream)
+{
+ DEBUGASSERT(stream);
+ h3_stream_ctx_free((struct h3_stream_ctx *)stream);
+}
static CURLcode h3_data_setup(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_ngtcp2_ctx *ctx = cf->ctx;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
if(!data || !data->req.p.http) {
failf(data, "initialization failure, transfer not http initialized");
stream->sendbuf_len_in_flight = 0;
Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
- H3_STREAM_LCTX(data) = stream;
+ if(!Curl_hash_offt_set(&ctx->streams, data->id, stream)) {
+ h3_stream_ctx_free(stream);
+ return CURLE_OUT_OF_MEMORY;
+ }
+
return CURLE_OK;
}
static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct cf_ngtcp2_ctx *ctx = cf->ctx;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
(void)cf;
if(stream) {
stream->closed = TRUE;
}
- Curl_bufq_free(&stream->sendbuf);
- Curl_h1_req_parse_free(&stream->h1);
- free(stream);
- H3_STREAM_LCTX(data) = NULL;
+ Curl_hash_offt_remove(&ctx->streams, data->id);
}
}
static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf,
struct Curl_easy *data,
- int64_t stream_id)
+ int64_t stream_id,
+ struct h3_stream_ctx **pstream)
{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
struct Curl_easy *sdata;
+ struct h3_stream_ctx *stream;
(void)cf;
- if(H3_STREAM_ID(data) == stream_id) {
+ stream = H3_STREAM_CTX(ctx, data);
+ if(stream && stream->id == stream_id) {
+ *pstream = stream;
return data;
}
else {
DEBUGASSERT(data->multi);
for(sdata = data->multi->easyp; sdata; sdata = sdata->next) {
- if((sdata->conn == data->conn) && H3_STREAM_ID(sdata) == stream_id) {
+ if(sdata->conn != data->conn)
+ continue;
+ stream = H3_STREAM_CTX(ctx, sdata);
+ if(stream && stream->id == stream_id) {
+ *pstream = stream;
return sdata;
}
}
}
+ *pstream = NULL;
return NULL;
}
static void h3_drain_stream(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
unsigned char bits;
(void)cf;
if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
- s_data = get_stream_easy(cf, data, stream_id);
- stream = H3_STREAM_CTX(s_data);
- if(stream && stream->quic_flow_blocked) {
- CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] unblock quic flow", stream_id);
+ s_data = get_stream_easy(cf, data, stream_id, &stream);
+ if(s_data && stream && stream->quic_flow_blocked) {
+ CURL_TRC_CF(s_data, cf, "[%" CURL_PRId64 "] unblock quic flow",
+ stream_id);
stream->quic_flow_blocked = FALSE;
- h3_drain_stream(cf, data);
+ h3_drain_stream(cf, s_data);
}
return 0;
}
Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send);
if(want_recv || want_send) {
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
struct cf_call_data save;
bool c_exhaust, s_exhaust;
void *stream_user_data)
{
struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
struct Curl_easy *data = stream_user_data;
curl_int64_t stream_id = (curl_int64_t)sid;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
(void)conn;
(void)stream_id;
struct Curl_cfilter *cf = user_data;
struct cf_ngtcp2_ctx *ctx = cf->ctx;
struct Curl_easy *data = stream_user_data;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURLcode result;
(void)conn;
int fin, void *user_data, void *stream_user_data)
{
struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
struct Curl_easy *data = stream_user_data;
curl_int64_t stream_id = (curl_int64_t)sid;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURLcode result = CURLE_OK;
(void)conn;
(void)stream_id;
nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name);
nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value);
struct Curl_easy *data = stream_user_data;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURLcode result = CURLE_OK;
(void)conn;
(void)stream_id;
char *buf, size_t blen, CURLcode *err)
{
struct cf_ngtcp2_ctx *ctx = cf->ctx;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
ssize_t nread = -1;
struct cf_call_data save;
struct pkt_io_ctx pktx;
void *stream_user_data)
{
struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
struct Curl_easy *data = stream_user_data;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
size_t skiplen;
(void)cf;
void *stream_user_data)
{
struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
struct Curl_easy *data = stream_user_data;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
ssize_t nwritten = 0;
size_t nvecs = 0;
(void)cf;
*err = h3_data_setup(cf, data);
if(*err)
goto out;
- stream = H3_STREAM_CTX(data);
+ stream = H3_STREAM_CTX(ctx, data);
DEBUGASSERT(stream);
if(!stream) {
*err = CURLE_FAILED_INIT;
const void *buf, size_t len, CURLcode *err)
{
struct cf_ngtcp2_ctx *ctx = cf->ctx;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
ssize_t sent = 0;
struct cf_call_data save;
struct pkt_io_ctx pktx;
CURL_TRC_CF(data, cf, "failed to open stream -> %d", *err);
goto out;
}
- stream = H3_STREAM_CTX(data);
+ stream = H3_STREAM_CTX(ctx, data);
}
else if(stream->upload_blocked_len) {
/* the data in `buf` has already been submitted or added to the
else if(n < 0) {
switch(n) {
case NGTCP2_ERR_STREAM_DATA_BLOCKED: {
- struct h3_stream_ctx *stream = H3_STREAM_CTX(x->data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, x->data);
DEBUGASSERT(ndatalen == -1);
nghttp3_conn_block_stream(ctx->h3conn, stream_id);
CURL_TRC_CF(x->data, x->cf, "[%" CURL_PRId64 "] block quic flow",
h3_data_done(cf, data);
break;
case CF_CTRL_DATA_DONE_SEND: {
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
if(stream && !stream->send_closed) {
stream->send_closed = TRUE;
stream->upload_left = Curl_bufq_len(&stream->sendbuf);
break;
}
case CF_CTRL_DATA_IDLE: {
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURL_TRC_CF(data, cf, "data idle");
if(stream && !stream->closed) {
result = check_and_set_expiry(cf, data, NULL);
ngtcp2_conn_del(ctx->qconn);
Curl_bufcp_free(&ctx->stream_bufcp);
Curl_dyn_free(&ctx->scratch);
+ Curl_hash_clean(&ctx->streams);
+ Curl_hash_destroy(&ctx->streams);
Curl_ssl_peer_cleanup(&ctx->peer);
memset(ctx, 0, sizeof(*ctx));
Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
H3_STREAM_POOL_SPARES);
Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER);
+ Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free);
result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC);
if(result)
#include <nghttp3/nghttp3.h>
#include "urldata.h"
+#include "hash.h"
#include "sendf.h"
#include "strdup.h"
#include "rand.h"
struct curltime first_byte_at; /* when first byte was recvd */
struct curltime reconnect_at; /* time the next attempt should start */
struct bufc_pool stream_bufcp; /* chunk pool for streams */
+ struct Curl_hash streams; /* hash `data->id` to `h3_stream_ctx` */
size_t max_stream_window; /* max flow window for one stream */
uint64_t max_idle_ms; /* max idle time for QUIC connection */
BIT(got_first_byte); /* if first byte was received */
Curl_vquic_tls_cleanup(&ctx->tls);
vquic_ctx_free(&ctx->q);
Curl_bufcp_free(&ctx->stream_bufcp);
+ Curl_hash_clean(&ctx->streams);
+ Curl_hash_destroy(&ctx->streams);
Curl_ssl_peer_cleanup(&ctx->peer);
memset(ctx, 0, sizeof(*ctx));
BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */
};
-#define H3_STREAM_CTX(d) ((struct h3_stream_ctx *)(((d) && (d)->req.p.http)? \
- ((struct HTTP *)(d)->req.p.http)->h3_ctx \
- : NULL))
-#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx
-#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \
- H3_STREAM_CTX(d)->s.id : -2)
+#define H3_STREAM_CTX(ctx,data) ((struct h3_stream_ctx *)(\
+ data? Curl_hash_offt_get(&(ctx)->streams, (data)->id) : NULL))
+
+static void h3_stream_ctx_free(struct h3_stream_ctx *stream)
+{
+ cf_osslq_stream_cleanup(&stream->s);
+ Curl_bufq_free(&stream->sendbuf);
+ Curl_bufq_free(&stream->recvbuf);
+ Curl_h1_req_parse_free(&stream->h1);
+ free(stream);
+}
+
+static void h3_stream_hash_free(void *stream)
+{
+ DEBUGASSERT(stream);
+ h3_stream_ctx_free((struct h3_stream_ctx *)stream);
+}
static CURLcode h3_data_setup(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_osslq_ctx *ctx = cf->ctx;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
if(!data || !data->req.p.http) {
failf(data, "initialization failure, transfer not http initialized");
stream->recv_buf_nonflow = 0;
Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
- H3_STREAM_LCTX(data) = stream;
+ if(!Curl_hash_offt_set(&ctx->streams, data->id, stream)) {
+ h3_stream_ctx_free(stream);
+ return CURLE_OUT_OF_MEMORY;
+ }
+
return CURLE_OK;
}
static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct cf_osslq_ctx *ctx = cf->ctx;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
(void)cf;
if(stream) {
stream->closed = TRUE;
}
- cf_osslq_stream_cleanup(&stream->s);
- Curl_bufq_free(&stream->sendbuf);
- Curl_bufq_free(&stream->recvbuf);
- Curl_h1_req_parse_free(&stream->h1);
- free(stream);
- H3_STREAM_LCTX(data) = NULL;
+ Curl_hash_offt_remove(&ctx->streams, data->id);
}
}
int64_t stream_id)
{
struct cf_osslq_ctx *ctx = cf->ctx;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
struct Curl_easy *sdata;
if(stream && stream->s.id == stream_id) {
else {
DEBUGASSERT(data->multi);
for(sdata = data->multi->easyp; sdata; sdata = sdata->next) {
- if((sdata->conn == data->conn) && H3_STREAM_ID(sdata) == stream_id) {
- stream = H3_STREAM_CTX(sdata);
- return stream? &stream->s : NULL;
+ if(sdata->conn != data->conn)
+ continue;
+ stream = H3_STREAM_CTX(ctx, sdata);
+ if(stream && stream->s.id == stream_id) {
+ return &stream->s;
}
}
}
static void h3_drain_stream(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_osslq_ctx *ctx = cf->ctx;
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
unsigned char bits;
(void)cf;
void *stream_user_data)
{
struct Curl_cfilter *cf = user_data;
+ struct cf_osslq_ctx *ctx = cf->ctx;
struct Curl_easy *data = stream_user_data;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
(void)conn;
(void)stream_id;
const void *mem, size_t memlen,
bool flow)
{
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_osslq_ctx *ctx = cf->ctx;
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURLcode result = CURLE_OK;
ssize_t nwritten;
void *user_data, void *stream_user_data)
{
struct Curl_cfilter *cf = user_data;
+ struct cf_osslq_ctx *ctx = cf->ctx;
struct Curl_easy *data = stream_user_data;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURLcode result;
(void)conn;
void *stream_user_data)
{
struct Curl_cfilter *cf = user_data;
+ struct cf_osslq_ctx *ctx = cf->ctx;
struct Curl_easy *data = stream_user_data;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
(void)conn;
(void)stream_id;
{
struct Curl_cfilter *cf = user_data;
curl_int64_t stream_id = sid;
+ struct cf_osslq_ctx *ctx = cf->ctx;
nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name);
nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value);
struct Curl_easy *data = stream_user_data;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURLcode result = CURLE_OK;
(void)conn;
(void)stream_id;
int fin, void *user_data, void *stream_user_data)
{
struct Curl_cfilter *cf = user_data;
+ struct cf_osslq_ctx *ctx = cf->ctx;
struct Curl_easy *data = stream_user_data;
curl_int64_t stream_id = sid;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURLcode result = CURLE_OK;
(void)conn;
(void)stream_id;
void *stream_user_data)
{
struct Curl_cfilter *cf = user_data;
+ struct cf_osslq_ctx *ctx = cf->ctx;
struct Curl_easy *data = stream_user_data;
curl_int64_t stream_id = sid;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
(void)conn;
(void)app_error_code;
uint64_t app_error_code, void *user_data,
void *stream_user_data) {
struct Curl_cfilter *cf = user_data;
+ struct cf_osslq_ctx *ctx = cf->ctx;
struct Curl_easy *data = stream_user_data;
curl_int64_t stream_id = sid;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
int rv;
(void)conn;
void *stream_user_data)
{
struct Curl_cfilter *cf = user_data;
+ struct cf_osslq_ctx *ctx = cf->ctx;
struct Curl_easy *data = stream_user_data;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
ssize_t nwritten = 0;
size_t nvecs = 0;
(void)cf;
void *stream_user_data)
{
struct Curl_cfilter *cf = user_data;
+ struct cf_osslq_ctx *ctx = cf->ctx;
struct Curl_easy *data = stream_user_data;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
size_t skiplen;
(void)cf;
Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
H3_STREAM_POOL_SPARES);
+ Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free);
result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC);
if(result)
goto out;
DEBUGASSERT(data->multi);
for(sdata = data->multi->easyp; sdata; sdata = sdata->next) {
if(sdata->conn == data->conn && CURL_WANT_RECV(sdata)) {
- stream = H3_STREAM_CTX(sdata);
+ stream = H3_STREAM_CTX(ctx, sdata);
if(stream && !stream->closed &&
!Curl_bufq_is_full(&stream->recvbuf)) {
result = cf_osslq_stream_recv(&stream->s, cf, sdata);
if(ctx->h3.conn) {
for(sdata = data->multi->easyp; sdata; sdata = sdata->next) {
if(sdata->conn == data->conn) {
- stream = H3_STREAM_CTX(sdata);
+ stream = H3_STREAM_CTX(ctx, sdata);
if(stream && stream->s.ssl && stream->s.send_blocked &&
!SSL_want_write(stream->s.ssl)) {
nghttp3_conn_unblock_stream(ctx->h3.conn, stream->s.id);
*err = h3_data_setup(cf, data);
if(*err)
goto out;
- stream = H3_STREAM_CTX(data);
+ stream = H3_STREAM_CTX(ctx, data);
DEBUGASSERT(stream);
if(!stream) {
*err = CURLE_FAILED_INIT;
const void *buf, size_t len, CURLcode *err)
{
struct cf_osslq_ctx *ctx = cf->ctx;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
struct cf_call_data save;
ssize_t nwritten;
CURLcode result;
CURL_TRC_CF(data, cf, "failed to open stream -> %d", *err);
goto out;
}
- stream = H3_STREAM_CTX(data);
+ stream = H3_STREAM_CTX(ctx, data);
}
else if(stream->upload_blocked_len) {
/* the data in `buf` has already been submitted or added to the
char *buf, size_t len, CURLcode *err)
{
struct cf_osslq_ctx *ctx = cf->ctx;
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
ssize_t nread = -1;
struct cf_call_data save;
CURLcode result;
static bool cf_osslq_data_pending(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
- const struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_osslq_ctx *ctx = cf->ctx;
+ const struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
(void)cf;
return stream && !Curl_bufq_is_empty(&stream->recvbuf);
}
h3_data_done(cf, data);
break;
case CF_CTRL_DATA_DONE_SEND: {
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
if(stream && !stream->send_closed) {
stream->send_closed = TRUE;
stream->upload_left = Curl_bufq_len(&stream->sendbuf);
break;
}
case CF_CTRL_DATA_IDLE: {
- struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
+ struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURL_TRC_CF(data, cf, "data idle");
if(stream && !stream->closed) {
result = check_and_set_expiry(cf, data);
#include <openssl/err.h>
#include <openssl/ssl.h>
#include "bufq.h"
+#include "hash.h"
#include "urldata.h"
#include "cfilters.h"
#include "cf-socket.h"
struct curltime handshake_at; /* time connect handshake finished */
struct curltime reconnect_at; /* time the next attempt should start */
struct bufc_pool stream_bufcp; /* chunk pool for streams */
+ struct Curl_hash streams; /* hash `data->id` to `stream_ctx` */
curl_off_t data_recvd;
curl_uint64_t max_idle_ms; /* max idle time for QUIC conn */
BIT(goaway); /* got GOAWAY from server */
Curl_ssl_peer_cleanup(&ctx->peer);
vquic_ctx_free(&ctx->q);
Curl_bufcp_free(&ctx->stream_bufcp);
+ Curl_hash_clean(&ctx->streams);
+ Curl_hash_destroy(&ctx->streams);
memset(ctx, 0, sizeof(*ctx));
}
BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */
};
-#define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \
- ((struct HTTP *)(d)->req.p.http)->h3_ctx \
- : NULL))
-#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx
-#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \
- H3_STREAM_CTX(d)->id : -2)
+#define H3_STREAM_CTX(ctx,data) ((struct stream_ctx *)(\
+ data? Curl_hash_offt_get(&(ctx)->streams, (data)->id) : NULL))
+
+static void h3_stream_ctx_free(struct stream_ctx *stream)
+{
+ Curl_bufq_free(&stream->recvbuf);
+ Curl_h1_req_parse_free(&stream->h1);
+ free(stream);
+}
+
+static void h3_stream_hash_free(void *stream)
+{
+ DEBUGASSERT(stream);
+ h3_stream_ctx_free((struct stream_ctx *)stream);
+}
static void check_resumes(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
+ struct cf_quiche_ctx *ctx = cf->ctx;
struct Curl_easy *sdata;
struct stream_ctx *stream;
DEBUGASSERT(data->multi);
for(sdata = data->multi->easyp; sdata; sdata = sdata->next) {
if(sdata->conn == data->conn) {
- stream = H3_STREAM_CTX(sdata);
+ stream = H3_STREAM_CTX(ctx, sdata);
if(stream && stream->quic_flow_blocked) {
stream->quic_flow_blocked = FALSE;
Curl_expire(data, 0, EXPIRE_RUN_NOW);
struct Curl_easy *data)
{
struct cf_quiche_ctx *ctx = cf->ctx;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
if(stream)
return CURLE_OK;
if(!stream)
return CURLE_OUT_OF_MEMORY;
- H3_STREAM_LCTX(data) = stream;
stream->id = -1;
Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp,
H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
+
+ if(!Curl_hash_offt_set(&ctx->streams, data->id, stream)) {
+ h3_stream_ctx_free(stream);
+ return CURLE_OUT_OF_MEMORY;
+ }
+
return CURLE_OK;
}
static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct cf_quiche_ctx *ctx = cf->ctx;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
(void)cf;
if(stream) {
}
stream->closed = TRUE;
}
- Curl_bufq_free(&stream->recvbuf);
- Curl_h1_req_parse_free(&stream->h1);
- free(stream);
- H3_STREAM_LCTX(data) = NULL;
+ Curl_hash_offt_remove(&ctx->streams, data->id);
}
}
static void drain_stream(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
unsigned char bits;
(void)cf;
static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf,
struct Curl_easy *data,
- curl_uint64_t stream3_id)
+ curl_uint64_t stream_id,
+ struct stream_ctx **pstream)
{
+ struct cf_quiche_ctx *ctx = cf->ctx;
struct Curl_easy *sdata;
+ struct stream_ctx *stream;
(void)cf;
- if(H3_STREAM_ID(data) == stream3_id) {
+ stream = H3_STREAM_CTX(ctx, data);
+ if(stream && stream->id == stream_id) {
+ *pstream = stream;
return data;
}
else {
DEBUGASSERT(data->multi);
for(sdata = data->multi->easyp; sdata; sdata = sdata->next) {
- if((sdata->conn == data->conn) && H3_STREAM_ID(sdata) == stream3_id) {
+ if(sdata->conn != data->conn)
+ continue;
+ stream = H3_STREAM_CTX(ctx, sdata);
+ if(stream && stream->id == stream_id) {
+ *pstream = stream;
return sdata;
}
}
}
+ *pstream = NULL;
return NULL;
}
struct Curl_easy *data,
const void *mem, size_t memlen)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURLcode result = CURLE_OK;
ssize_t nwritten;
void *argp)
{
struct cb_ctx *x = argp;
- struct stream_ctx *stream = H3_STREAM_CTX(x->data);
+ struct cf_quiche_ctx *ctx = x->cf->ctx;
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, x->data);
CURLcode result;
if(!stream)
{
struct cb_ctx *x = reader_ctx;
struct cf_quiche_ctx *ctx = x->cf->ctx;
- struct stream_ctx *stream = H3_STREAM_CTX(x->data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, x->data);
ssize_t nread;
if(!stream) {
static CURLcode cf_recv_body(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
ssize_t nwritten;
struct cb_ctx cb_ctx;
CURLcode result = CURLE_OK;
static CURLcode h3_process_event(struct Curl_cfilter *cf,
struct Curl_easy *data,
- curl_uint64_t stream3_id,
+ struct stream_ctx *stream,
quiche_h3_event *ev)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
struct cb_ctx cb_ctx;
CURLcode result = CURLE_OK;
int rc;
if(!stream)
return CURLE_OK;
- DEBUGASSERT(stream3_id == stream->id);
switch(quiche_h3_event_type(ev)) {
case QUICHE_H3_EVENT_HEADERS:
stream->resp_got_header = TRUE;
rc = quiche_h3_event_for_each_header(ev, cb_each_header, &cb_ctx);
if(rc) {
failf(data, "Error %d in HTTP/3 response header for stream[%"
- CURL_PRIu64"]", rc, stream3_id);
+ CURL_PRIu64"]", rc, stream->id);
return CURLE_RECV_ERROR;
}
- CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] <- [HEADERS]", stream3_id);
+ CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] <- [HEADERS]", stream->id);
break;
case QUICHE_H3_EVENT_DATA:
break;
case QUICHE_H3_EVENT_RESET:
- CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] RESET", stream3_id);
+ CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] RESET", stream->id);
stream->closed = TRUE;
stream->reset = TRUE;
stream->send_closed = TRUE;
break;
case QUICHE_H3_EVENT_FINISHED:
- CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] CLOSED", stream3_id);
+ CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] CLOSED", stream->id);
if(!stream->resp_hds_complete) {
result = write_resp_raw(cf, data, "\r\n", 2);
if(result)
break;
case QUICHE_H3_EVENT_GOAWAY:
- CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] <- [GOAWAY]", stream3_id);
+ CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] <- [GOAWAY]", stream->id);
break;
default:
CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] recv, unhandled event %d",
- stream3_id, quiche_h3_event_type(ev));
+ stream->id, quiche_h3_event_type(ev));
break;
}
return result;
struct Curl_easy *data)
{
struct cf_quiche_ctx *ctx = cf->ctx;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct stream_ctx *stream = NULL;
struct Curl_easy *sdata;
quiche_h3_event *ev;
CURLcode result;
break;
}
else if(stream3_id < 0) {
- CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] error poll: %"CURL_PRIu64,
- stream? stream->id : -1, stream3_id);
+ CURL_TRC_CF(data, cf, "error poll: %"CURL_PRId64, stream3_id);
return CURLE_HTTP3;
}
- sdata = get_stream_easy(cf, data, stream3_id);
- if(!sdata) {
- CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] discard event %s for "
- "unknown [%"CURL_PRIu64"]",
- stream? stream->id : -1, cf_ev_name(ev), stream3_id);
+ sdata = get_stream_easy(cf, data, stream3_id, &stream);
+ if(!sdata || !stream) {
+ CURL_TRC_CF(data, cf, "discard event %s for unknown [%"CURL_PRId64"]",
+ cf_ev_name(ev), stream3_id);
}
else {
- result = h3_process_event(cf, sdata, stream3_id, ev);
+ result = h3_process_event(cf, sdata, stream, ev);
drain_stream(cf, sdata);
if(result) {
- CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] error processing event %s "
- "for [%"CURL_PRIu64"] -> %d",
- stream? stream->id : -1, cf_ev_name(ev),
+ CURL_TRC_CF(data, cf, "error processing event %s "
+ "for [%"CURL_PRIu64"] -> %d", cf_ev_name(ev),
stream3_id, result);
if(data == sdata) {
/* Only report this error to the caller if it is about the
struct Curl_easy *data,
CURLcode *err)
{
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
ssize_t nread = -1;
DEBUGASSERT(stream);
char *buf, size_t len, CURLcode *err)
{
struct cf_quiche_ctx *ctx = cf->ctx;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
ssize_t nread = -1;
CURLcode result;
CURLcode *err)
{
struct cf_quiche_ctx *ctx = cf->ctx;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
size_t nheader, i;
curl_int64_t stream3_id;
struct dynhds h2_headers;
if(*err) {
return -1;
}
- stream = H3_STREAM_CTX(data);
+ stream = H3_STREAM_CTX(ctx, data);
DEBUGASSERT(stream);
}
const void *buf, size_t len, CURLcode *err)
{
struct cf_quiche_ctx *ctx = cf->ctx;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
CURLcode result;
ssize_t nwritten;
nwritten = h3_open_stream(cf, data, buf, len, err);
if(nwritten < 0)
goto out;
- stream = H3_STREAM_CTX(data);
+ stream = H3_STREAM_CTX(ctx, data);
}
else if(stream->closed) {
if(stream->resp_hds_complete) {
struct Curl_easy *data)
{
struct cf_quiche_ctx *ctx = cf->ctx;
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
return stream && (quiche_conn_stream_writable(
ctx->qconn, (curl_uint64_t)stream->id, 1) > 0);
Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send);
if(want_recv || want_send) {
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
bool c_exhaust, s_exhaust;
c_exhaust = FALSE; /* Have not found any call in quiche that tells
static bool cf_quiche_data_pending(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
- const struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ const struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
(void)cf;
return stream && !Curl_bufq_is_empty(&stream->recvbuf);
}
struct Curl_easy *data,
int event, int arg1, void *arg2)
{
+ struct cf_quiche_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
(void)arg1;
h3_data_done(cf, data);
break;
case CF_CTRL_DATA_DONE_SEND: {
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
if(stream && !stream->send_closed) {
unsigned char body[1];
ssize_t sent;
break;
}
case CF_CTRL_DATA_IDLE: {
- struct stream_ctx *stream = H3_STREAM_CTX(data);
+ struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
if(stream && !stream->closed) {
result = cf_flush_egress(cf, data);
if(result)
ctx->max_idle_ms = CURL_QUIC_MAX_IDLE_MS;
Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
H3_STREAM_POOL_SPARES);
+ Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free);
ctx->data_recvd = 0;
result = vquic_ctx_init(&ctx->q);
test1598 \
test1600 test1601 test1602 test1603 test1604 test1605 test1606 test1607 \
test1608 test1609 test1610 test1611 test1612 test1613 test1614 test1615 \
-\
+test1616 \
test1620 test1621 \
\
test1630 test1631 test1632 test1633 test1634 test1635 \
--- /dev/null
+<testcase>
+<info>
+<keywords>
+unittest
+hash
+</keywords>
+</info>
+
+#
+# Client-side
+<client>
+<server>
+none
+</server>
+<features>
+unittest
+</features>
+<name>
+Internal hash_offt create/add/destroy testing, exercising clean functions
+</name>
+</client>
+</testcase>
unit1330 unit1394 unit1395 unit1396 unit1397 unit1398 \
unit1399 \
unit1600 unit1601 unit1602 unit1603 unit1604 unit1605 unit1606 unit1607 \
- unit1608 unit1609 unit1610 unit1611 unit1612 unit1614 unit1615 \
+ unit1608 unit1609 unit1610 unit1611 unit1612 unit1614 unit1615 unit1616 \
unit1620 unit1621 \
unit1650 unit1651 unit1652 unit1653 unit1654 unit1655 \
unit1660 unit1661 \
unit1615_SOURCES = unit1615.c $(UNITFILES)
+unit1616_SOURCES = unit1616.c $(UNITFILES)
+
unit1620_SOURCES = unit1620.c $(UNITFILES)
unit1621_SOURCES = unit1621.c $(UNITFILES)
--- /dev/null
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curlcheck.h"
+
+#define ENABLE_CURLX_PRINTF
+#include "curlx.h"
+
+#include "hash.h"
+
+#include "memdebug.h" /* LAST include file */
+
+static struct Curl_hash hash_static;
+
+static void mydtor(void *elem)
+{
+ int *ptr = (int *)elem;
+ free(ptr);
+}
+
+static CURLcode unit_setup(void)
+{
+ Curl_hash_offt_init(&hash_static, 15, mydtor);
+ return CURLE_OK;
+}
+
+static void unit_stop(void)
+{
+ Curl_hash_destroy(&hash_static);
+}
+
+UNITTEST_START
+ int *value, *v;
+ int *value2;
+ int *nodep;
+
+ curl_off_t key = 20;
+ curl_off_t key2 = 25;
+
+
+ value = malloc(sizeof(int));
+ abort_unless(value != NULL, "Out of memory");
+ *value = 199;
+ nodep = Curl_hash_offt_set(&hash_static, key, value);
+ if(!nodep)
+ free(value);
+ abort_unless(nodep, "insertion into hash failed");
+ v = Curl_hash_offt_get(&hash_static, key);
+ abort_unless(v == value, "lookup present entry failed");
+ v = Curl_hash_offt_get(&hash_static, key2);
+ abort_unless(!v, "lookup missing entry failed");
+ Curl_hash_clean(&hash_static);
+
+ /* Attempt to add another key/value pair */
+ value2 = malloc(sizeof(int));
+ abort_unless(value2 != NULL, "Out of memory");
+ *value2 = 204;
+ nodep = Curl_hash_offt_set(&hash_static, key2, value2);
+ if(!nodep)
+ free(value2);
+ abort_unless(nodep, "insertion into hash failed");
+ v = Curl_hash_offt_get(&hash_static, key2);
+ abort_unless(v == value2, "lookup present entry failed");
+ v = Curl_hash_offt_get(&hash_static, key);
+ abort_unless(!v, "lookup missing entry failed");
+
+UNITTEST_STOP