diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index abd3f7ade..aae314658 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -827,6 +827,12 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ngx_quic_queue_frame(qc, f); break; + case NGX_QUIC_FT_DATA_BLOCKED: + if (qc->streams.send_max_data == f->u.data_blocked.limit) { + ngx_queue_insert_tail(&ctx->frames, &f->queue); + } + break; + case NGX_QUIC_FT_STREAM: qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 33922cf80..ad5025519 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -166,8 +166,8 @@ typedef struct { uint64_t client_streams_uni; uint64_t client_streams_bidi; - ngx_uint_t initialized; - /* unsigned initialized:1; */ + unsigned initialized:1; + unsigned flow_control_blocked:1; } ngx_quic_streams_t; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 18fffeabe..26637e8fb 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -1054,6 +1054,30 @@ ngx_quic_stream_flush(ngx_quic_stream_t *qs) } if (len == 0 && !last) { + /* + * RFC 9000, 4.1. Data Flow Control + * + * A sender SHOULD send a STREAM_DATA_BLOCKED or DATA_BLOCKED frame to + * indicate to the receiver that it has data to write but is blocked by + * flow control limits. + */ + if (qc->streams.send_max_data == qc->streams.send_offset + && !qc->streams.flow_control_blocked) + { + qc->streams.flow_control_blocked = 1; + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_DATA_BLOCKED; + frame->u.data_blocked.limit = qc->streams.send_max_data; + + ngx_quic_queue_frame(qc, frame); + } + return NGX_OK; } @@ -1344,6 +1368,8 @@ ngx_quic_handle_max_data_frame(ngx_connection_t *c, } qc->streams.send_max_data = f->max_data; + qc->streams.flow_control_blocked = 0; + node = ngx_rbtree_min(tree->root, tree->sentinel); while (node && qc->streams.send_offset < qc->streams.send_max_data) { diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index ba6211c33..ddbdea47d 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -118,6 +118,8 @@ static size_t ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms); static size_t ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md); +static size_t ngx_quic_create_data_blocked(u_char *p, + ngx_quic_data_blocked_frame_t *db); static size_t ngx_quic_create_path_challenge(u_char *p, ngx_quic_path_challenge_frame_t *pc); static size_t ngx_quic_create_path_response(u_char *p, @@ -1328,6 +1330,9 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) case NGX_QUIC_FT_MAX_DATA: return ngx_quic_create_max_data(p, &f->u.max_data); + case NGX_QUIC_FT_DATA_BLOCKED: + return ngx_quic_create_data_blocked(p, &f->u.data_blocked); + case NGX_QUIC_FT_PATH_CHALLENGE: return ngx_quic_create_path_challenge(p, &f->u.path_challenge); @@ -1889,6 +1894,27 @@ ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md) } +static size_t +ngx_quic_create_data_blocked(u_char *p, ngx_quic_data_blocked_frame_t *db) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_DATA_BLOCKED); + len += ngx_quic_varint_len(db->limit); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_DATA_BLOCKED); + ngx_quic_build_int(&p, db->limit); + + return p - start; +} + + static size_t ngx_quic_create_path_challenge(u_char *p, ngx_quic_path_challenge_frame_t *pc) {