diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c index 90b64087ed01cc57e3ef8ae2db6877404019bc51..a4e0882b278c3e9ad90fd2a4cd98570db6c58a79 100644 --- a/lib/ngtcp2_conn.c +++ b/lib/ngtcp2_conn.c @@ -1335,10 +1335,10 @@ static size_t conn_retry_early_payloadlen(ngtcp2_conn *conn) { /* * conn_cryptofrq_unacked_offset returns the CRYPTO frame offset by * taking into account acknowledged offset. If there is no data to - * send, this function returns (size_t)-1. + * send, this function returns (uint64_t)-1. */ -static size_t conn_cryptofrq_unacked_offset(ngtcp2_conn *conn, - ngtcp2_pktns *pktns) { +static uint64_t conn_cryptofrq_unacked_offset(ngtcp2_conn *conn, + ngtcp2_pktns *pktns) { ngtcp2_frame_chain *frc; ngtcp2_crypto *fr; ngtcp2_range gap; @@ -1365,7 +1365,7 @@ static size_t conn_cryptofrq_unacked_offset(ngtcp2_conn *conn, } } - return (size_t)-1; + return (uint64_t)-1; } static int conn_cryptofrq_unacked_pop(ngtcp2_conn *conn, ngtcp2_pktns *pktns, @@ -2550,6 +2550,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, int credit_expanded = 0; ngtcp2_cc_pkt cc_pkt; uint64_t crypto_offset; + uint64_t stream_offset; /* Return 0 if destlen is less than minimum packet length which can trigger Stateless Reset */ @@ -2683,6 +2684,14 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, } for (pfrc = &pktns->tx.frq; *pfrc;) { + if ((*pfrc)->binder && + ((*pfrc)->binder->flags & NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK)) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_del(frc, conn->mem); + continue; + } + switch ((*pfrc)->fr.type) { case NGTCP2_FRAME_STOP_SENDING: strm = @@ -2887,11 +2896,17 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, continue; } + stream_offset = ngtcp2_strm_streamfrq_unacked_offset(strm); + if (stream_offset == (uint64_t)-1) { + ngtcp2_strm_streamfrq_clear(strm); + ngtcp2_conn_tx_strmq_pop(conn); + continue; + } + left = ngtcp2_ppe_left(ppe); - left = ngtcp2_pkt_stream_max_datalen( - strm->stream_id, ngtcp2_strm_streamfrq_top(strm)->fr.stream.offset, - left, left); + left = ngtcp2_pkt_stream_max_datalen(strm->stream_id, stream_offset, + left, left); if (left == (size_t)-1) { break; @@ -3807,7 +3822,11 @@ int ngtcp2_conn_detect_lost_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns, int rv; ngtcp2_duration pto = conn_compute_pto(conn, pktns); - ngtcp2_rtb_detect_lost_pkt(&pktns->rtb, &frc, cstat, pto, ts); + rv = ngtcp2_rtb_detect_lost_pkt(&pktns->rtb, &frc, cstat, conn, pto, ts); + if (rv != 0) { + ngtcp2_frame_chain_list_del(frc, conn->mem); + return rv; + } rv = conn_resched_frames(conn, pktns, &frc); if (rv != 0) { diff --git a/lib/ngtcp2_rtb.c b/lib/ngtcp2_rtb.c index 4b60b07563bc5847044960ba6feedd58c1d5d115..f55e18d62debd75f338ee2b1cd17a0c3c9f8291f 100644 --- a/lib/ngtcp2_rtb.c +++ b/lib/ngtcp2_rtb.c @@ -84,11 +84,53 @@ int ngtcp2_frame_chain_crypto_datacnt_new(ngtcp2_frame_chain **pfrc, return ngtcp2_frame_chain_new(pfrc, mem); } +int ngtcp2_frame_chain_new_token_new(ngtcp2_frame_chain **pfrc, + const ngtcp2_vec *token, + const ngtcp2_mem *mem) { + size_t avail = sizeof(ngtcp2_frame) - sizeof(ngtcp2_new_token); + int rv; + uint8_t *p; + ngtcp2_frame *fr; + + if (token->len > avail) { + rv = ngtcp2_frame_chain_extralen_new(pfrc, token->len - avail, mem); + } else { + rv = ngtcp2_frame_chain_new(pfrc, mem); + } + if (rv != 0) { + return rv; + } + + fr = &(*pfrc)->fr; + fr->type = NGTCP2_FRAME_NEW_TOKEN; + + p = (uint8_t *)(*pfrc) + sizeof(ngtcp2_new_token); + memcpy(p, token->base, token->len); + + ngtcp2_vec_init(&fr->new_token.token, p, token->len); + + return 0; +} + void ngtcp2_frame_chain_del(ngtcp2_frame_chain *frc, const ngtcp2_mem *mem) { + ngtcp2_frame_chain_binder *binder; + + if (frc == NULL) { + return; + } + + binder = frc->binder; + if (binder && --binder->refcount == 0) { + ngtcp2_mem_free(mem, binder); + } + ngtcp2_mem_free(mem, frc); } -void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc) { frc->next = NULL; } +void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc) { + frc->next = NULL; + frc->binder = NULL; +} void ngtcp2_frame_chain_list_del(ngtcp2_frame_chain *frc, const ngtcp2_mem *mem) { @@ -96,7 +138,7 @@ void ngtcp2_frame_chain_list_del(ngtcp2_frame_chain *frc, for (; frc;) { next = frc->next; - ngtcp2_mem_free(mem, frc); + ngtcp2_frame_chain_del(frc, mem); frc = next; } } @@ -114,6 +156,39 @@ static void frame_chain_insert(ngtcp2_frame_chain **pfrc, *pfrc = frc; } +int ngtcp2_frame_chain_binder_new(ngtcp2_frame_chain_binder **pbinder, + const ngtcp2_mem *mem) { + *pbinder = ngtcp2_mem_calloc(mem, 1, sizeof(ngtcp2_frame_chain_binder)); + if (*pbinder == NULL) { + return NGTCP2_ERR_NOMEM; + } + + return 0; +} + +int ngtcp2_bind_frame_chains(ngtcp2_frame_chain *a, ngtcp2_frame_chain *b, + const ngtcp2_mem *mem) { + ngtcp2_frame_chain_binder *binder; + int rv; + + assert(b->binder == NULL); + + if (a->binder == NULL) { + rv = ngtcp2_frame_chain_binder_new(&binder, mem); + if (rv != 0) { + return rv; + } + + a->binder = binder; + ++a->binder->refcount; + } + + b->binder = a->binder; + ++b->binder->refcount; + + return 0; +} + int ngtcp2_rtb_entry_new(ngtcp2_rtb_entry **pent, const ngtcp2_pkt_hd *hd, ngtcp2_frame_chain *frc, ngtcp2_tstamp ts, size_t pktlen, uint8_t flags, const ngtcp2_mem *mem) { @@ -127,6 +202,7 @@ int ngtcp2_rtb_entry_new(ngtcp2_rtb_entry **pent, const ngtcp2_pkt_hd *hd, (*pent)->hd.flags = hd->flags; (*pent)->frc = frc; (*pent)->ts = ts; + (*pent)->lost_ts = UINT64_MAX; (*pent)->pktlen = pktlen; (*pent)->flags = flags; @@ -201,6 +277,10 @@ static void rtb_on_add(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, static void rtb_on_remove(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, ngtcp2_conn_stat *cstat) { + if (ent->flags & NGTCP2_RTB_FLAG_LOST_RETRANSMITTED) { + return; + } + if (ent->flags & NGTCP2_RTB_FLAG_ACK_ELICITING) { assert(rtb->num_ack_eliciting); --rtb->num_ack_eliciting; @@ -215,8 +295,15 @@ static void rtb_on_remove(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, } } -static void rtb_on_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, - ngtcp2_rtb_entry *ent) { +static int rtb_on_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, + ngtcp2_ksl_it *it, ngtcp2_rtb_entry *ent, + ngtcp2_conn *conn, ngtcp2_tstamp ts) { + ngtcp2_frame_chain *first = *pfrc, *frc, *nfrc; + ngtcp2_frame *fr; + ngtcp2_range gap, range; + ngtcp2_strm *strm; + int rv; + ngtcp2_log_pkt_lost(rtb->log, ent->hd.pkt_num, ent->hd.type, ent->hd.flags, ent->ts); @@ -229,12 +316,125 @@ static void rtb_on_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, "pkn=%" PRId64 " CRYPTO has already been retransmitted", ent->hd.pkt_num); - } else if (ent->frc) { + assert(!(ent->flags & NGTCP2_RTB_FLAG_LOST_RETRANSMITTED)); + assert(UINT64_MAX == ent->lost_ts); + + ent->flags |= NGTCP2_RTB_FLAG_LOST_RETRANSMITTED; + ent->lost_ts = ts; + + ngtcp2_ksl_it_next(it); + + return 0; + } + + if (ent->frc) { + assert(!(ent->flags & NGTCP2_RTB_FLAG_LOST_RETRANSMITTED)); + assert(UINT64_MAX == ent->lost_ts); + /* PADDING only (or PADDING + ACK ) packets will have NULL ent->frc. */ /* TODO Reconsider the order of pfrc */ - frame_chain_insert(pfrc, ent->frc); - ent->frc = NULL; + for (frc = ent->frc; frc; frc = frc->next) { + fr = &frc->fr; + /* Check that a late ACK acknowledged this frame. */ + if (frc->binder && + (frc->binder->flags & NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK)) { + continue; + } + switch (frc->fr.type) { + case NGTCP2_FRAME_STREAM: + strm = ngtcp2_conn_find_stream(conn, fr->stream.stream_id); + if (strm == NULL) { + continue; + } + + gap = ngtcp2_strm_get_unacked_range_after(strm, fr->stream.offset); + + range.begin = fr->stream.offset; + range.end = fr->stream.offset + + ngtcp2_vec_len(fr->stream.data, fr->stream.datacnt); + range = ngtcp2_range_intersect(&range, &gap); + if (ngtcp2_range_len(&range) == 0 && + (!fr->stream.fin || (strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED))) { + continue; + } + + rv = ngtcp2_frame_chain_stream_datacnt_new(&nfrc, fr->stream.datacnt, + rtb->mem); + if (rv != 0) { + return rv; + } + + nfrc->fr = *fr; + ngtcp2_vec_copy(nfrc->fr.stream.data, fr->stream.data, + fr->stream.datacnt); + + break; + case NGTCP2_FRAME_CRYPTO: + /* Don't resend CRYPTO frame if the whole region it contains has + been acknowledged */ + gap = ngtcp2_strm_get_unacked_range_after(rtb->crypto, + fr->crypto.offset); + + range.begin = fr->crypto.offset; + range.end = fr->crypto.offset + + ngtcp2_vec_len(fr->crypto.data, fr->crypto.datacnt); + range = ngtcp2_range_intersect(&range, &gap); + if (ngtcp2_range_len(&range) == 0) { + continue; + } + + rv = ngtcp2_frame_chain_crypto_datacnt_new(&nfrc, fr->crypto.datacnt, + rtb->mem); + if (rv != 0) { + return rv; + } + + nfrc->fr = *fr; + ngtcp2_vec_copy(nfrc->fr.crypto.data, fr->crypto.data, + fr->crypto.datacnt); + + break; + case NGTCP2_FRAME_NEW_TOKEN: + rv = ngtcp2_frame_chain_new_token_new(&nfrc, &fr->new_token.token, + rtb->mem); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_bind_frame_chains(frc, nfrc, rtb->mem); + if (rv != 0) { + return rv; + } + + break; + default: + rv = ngtcp2_frame_chain_new(&nfrc, rtb->mem); + if (rv != 0) { + return rv; + } + + nfrc->fr = *fr; + + rv = ngtcp2_bind_frame_chains(frc, nfrc, rtb->mem); + if (rv != 0) { + return rv; + } + + break; + } + + frame_chain_insert(pfrc, nfrc); + } + + if (*pfrc != first) { + ent->flags |= NGTCP2_RTB_FLAG_LOST_RETRANSMITTED; + ent->lost_ts = ts; + + ngtcp2_ksl_it_next(it); + + return 0; + } } } else { ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, @@ -243,7 +443,10 @@ static void rtb_on_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, ent->hd.pkt_num); } + ngtcp2_ksl_remove(&rtb->ents, it, &ent->hd.pkt_num); ngtcp2_rtb_entry_del(ent, rtb->mem); + + return 0; } int ngtcp2_rtb_add(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, @@ -284,12 +487,21 @@ static int rtb_process_acked_pkt(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, ngtcp2_crypto_level crypto_level; for (frc = ent->frc; frc; frc = frc->next) { + if (frc->binder) { + frc->binder->flags |= NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK; + } + switch (frc->fr.type) { case NGTCP2_FRAME_STREAM: strm = ngtcp2_conn_find_stream(conn, frc->fr.stream.stream_id); if (strm == NULL) { break; } + + if (frc->fr.stream.fin) { + strm->flags |= NGTCP2_STRM_FLAG_FIN_ACKED; + } + prev_stream_offset = ngtcp2_strm_get_acked_offset(strm); rv = ngtcp2_strm_ack_data( strm, frc->fr.stream.offset, @@ -544,9 +756,9 @@ static ngtcp2_duration compute_pkt_loss_delay(const ngtcp2_conn_stat *cstat) { return ngtcp2_max(loss_delay, NGTCP2_GRANULARITY); } -void ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, - ngtcp2_conn_stat *cstat, ngtcp2_duration pto, - ngtcp2_tstamp ts) { +int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, + ngtcp2_conn_stat *cstat, ngtcp2_conn *conn, + ngtcp2_duration pto, ngtcp2_tstamp ts) { ngtcp2_rtb_entry *ent; ngtcp2_duration loss_delay; ngtcp2_tstamp lost_send_time; @@ -555,6 +767,7 @@ void ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, int64_t last_lost_pkt_num; ngtcp2_duration loss_window, congestion_period; ngtcp2_cc *cc = rtb->cc; + int rv; cstat->loss_time[rtb->pktns_id] = UINT64_MAX; loss_delay = compute_pkt_loss_delay(cstat); @@ -564,14 +777,19 @@ void ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) { ent = ngtcp2_ksl_it_get(&it); + if (ent->flags & NGTCP2_RTB_FLAG_LOST_RETRANSMITTED) { + break; + } + if (rtb_pkt_lost(rtb, cstat, ent, loss_delay, lost_send_time)) { /* All entries from ent are considered to be lost. */ latest_ts = oldest_ts = ent->ts; last_lost_pkt_num = ent->hd.pkt_num; + congestion_period = pto * NGTCP2_PERSISTENT_CONGESTION_THRESHOLD; + for (; !ngtcp2_ksl_it_end(&it);) { ent = ngtcp2_ksl_it_get(&it); - ngtcp2_ksl_remove(&rtb->ents, &it, &ent->hd.pkt_num); if (last_lost_pkt_num == ent->hd.pkt_num + 1 && ent->ts >= rtb->persistent_congestion_start_ts) { @@ -581,8 +799,20 @@ void ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, last_lost_pkt_num = -1; } + if ((ent->flags & NGTCP2_RTB_FLAG_LOST_RETRANSMITTED)) { + if (rtb->pktns_id != NGTCP2_PKTNS_ID_APP || last_lost_pkt_num == -1 || + latest_ts - oldest_ts >= congestion_period) { + break; + } + ngtcp2_ksl_it_next(&it); + continue; + } + rtb_on_remove(rtb, ent, cstat); - rtb_on_pkt_lost(rtb, pfrc, ent); + rv = rtb_on_pkt_lost(rtb, pfrc, &it, ent, conn, ts); + if (rv != 0) { + return rv; + } } cc->congestion_event(cc, cstat, latest_ts, ts); @@ -596,7 +826,6 @@ void ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, * not enable it during handshake. */ if (rtb->pktns_id == NGTCP2_PKTNS_ID_APP && loss_window > 0) { - congestion_period = pto * NGTCP2_PERSISTENT_CONGESTION_THRESHOLD; if (loss_window >= congestion_period) { ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, "persistent congestion loss_window=%" PRIu64 @@ -607,11 +836,73 @@ void ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, } } - return; + break; + } + } + + if (ngtcp2_ksl_len(&rtb->ents) == 0) { + return 0; + } + + it = ngtcp2_ksl_end(&rtb->ents); + + for (;;) { + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + + if (!(ent->flags & NGTCP2_RTB_FLAG_LOST_RETRANSMITTED) || + ts - ent->lost_ts < pto) { + return 0; + } + + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, + "removing stale lost packet pkn=%" PRId64, ent->hd.pkt_num); + + ngtcp2_ksl_remove(&rtb->ents, &it, &ent->hd.pkt_num); + ngtcp2_rtb_entry_del(ent, rtb->mem); + + if (ngtcp2_ksl_len(&rtb->ents) == 0) { + return 0; } } } +static void rtb_on_pkt_lost2(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, + ngtcp2_rtb_entry *ent) { + ngtcp2_log_pkt_lost(rtb->log, ent->hd.pkt_num, ent->hd.type, ent->hd.flags, + ent->ts); + + if (rtb->qlog) { + ngtcp2_qlog_pkt_lost(rtb->qlog, ent); + } + + if (!(ent->flags & NGTCP2_RTB_FLAG_PROBE)) { + if (ent->flags & NGTCP2_RTB_FLAG_CRYPTO_TIMEOUT_RETRANSMITTED) { + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, + "pkn=%" PRId64 " CRYPTO has already been retransmitted", + ent->hd.pkt_num); + } else if (ent->flags & NGTCP2_RTB_FLAG_LOST_RETRANSMITTED) { + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, + "pkn=%" PRId64 + " was declared lost and has already been retransmitted", + ent->hd.pkt_num); + } else if (ent->frc) { + /* PADDING only (or PADDING + ACK ) packets will have NULL + ent->frc. */ + /* TODO Reconsider the order of pfrc */ + frame_chain_insert(pfrc, ent->frc); + ent->frc = NULL; + } + } else { + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, + "pkn=%" PRId64 + " is a probe packet, no retransmission is necessary", + ent->hd.pkt_num); + } + + ngtcp2_rtb_entry_del(ent, rtb->mem); +} + void ngtcp2_rtb_remove_all(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, ngtcp2_conn_stat *cstat) { ngtcp2_rtb_entry *ent; @@ -625,7 +916,7 @@ void ngtcp2_rtb_remove_all(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, rtb_on_remove(rtb, ent, cstat); ngtcp2_ksl_remove(&rtb->ents, &it, &ent->hd.pkt_num); - rtb_on_pkt_lost(rtb, pfrc, ent); + rtb_on_pkt_lost2(rtb, pfrc, ent); } } diff --git a/lib/ngtcp2_rtb.h b/lib/ngtcp2_rtb.h index 4b523e6ef00faeef2d5856d375520c0347785655..c1a12d0278d2e17bc376b60340a9ed4775ba7898 100644 --- a/lib/ngtcp2_rtb.h +++ b/lib/ngtcp2_rtb.h @@ -53,11 +53,30 @@ typedef struct ngtcp2_strm ngtcp2_strm; struct ngtcp2_rst; typedef struct ngtcp2_rst ngtcp2_rst; +typedef enum ngtcp2_frame_chain_binder_flag { + NGTCP2_FRAME_CHAIN_BINDER_FLAG_NONE = 0x00, + /* NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK indicates that an information + which a frame carries has been acknowledged. */ + NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK = 0x01, +} ngtcp2_frame_chain_binder_flag; + +typedef struct ngtcp2_frame_chain_binder { + size_t refcount; + uint32_t flags; +} ngtcp2_frame_chain_binder; + +int ngtcp2_frame_chain_binder_new(ngtcp2_frame_chain_binder **pbinder, + const ngtcp2_mem *mem); + +int ngtcp2_bind_frame_chains(ngtcp2_frame_chain *a, ngtcp2_frame_chain *b, + const ngtcp2_mem *mem); + /* * ngtcp2_frame_chain chains frames in a single packet. */ struct ngtcp2_frame_chain { ngtcp2_frame_chain *next; + ngtcp2_frame_chain_binder *binder; ngtcp2_frame fr; }; @@ -111,6 +130,10 @@ int ngtcp2_frame_chain_crypto_datacnt_new(ngtcp2_frame_chain **pfrc, size_t datacnt, const ngtcp2_mem *mem); +int ngtcp2_frame_chain_new_token_new(ngtcp2_frame_chain **pfrc, + const ngtcp2_vec *token, + const ngtcp2_mem *mem); + /* * ngtcp2_frame_chain_del deallocates |frc|. It also deallocates the * memory pointed by |frc|. @@ -143,6 +166,9 @@ typedef enum { /* NGTCP2_RTB_FLAG_CRYPTO_TIMEOUT_RETRANSMITTED indicates that the CRYPTO frames have been retransmitted. */ NGTCP2_RTB_FLAG_CRYPTO_TIMEOUT_RETRANSMITTED = 0x08, + /* NGTCP2_RTB_FLAG_LOST_RETRANSMITTED indicates that the entry has + been marked lost and scheduled to retransmit. */ + NGTCP2_RTB_FLAG_LOST_RETRANSMITTED = 0x10, } ngtcp2_rtb_flag; struct ngtcp2_rtb_entry; @@ -164,6 +190,7 @@ struct ngtcp2_rtb_entry { /* ts is the time point when a packet included in this entry is sent to a peer. */ ngtcp2_tstamp ts; + ngtcp2_tstamp lost_ts; /* pktlen is the length of QUIC packet */ size_t pktlen; struct { @@ -290,9 +317,9 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr, * some frames might be prepended to |*pfrc| and the caller should * handle them. |pto| is PTO. */ -void ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, - ngtcp2_conn_stat *cstat, ngtcp2_duration pto, - ngtcp2_tstamp ts); +int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc, + ngtcp2_conn_stat *cstat, ngtcp2_conn *conn, + ngtcp2_duration pto, ngtcp2_tstamp ts); /* * ngtcp2_rtb_remove_all removes all packets from |rtb| and prepends diff --git a/lib/ngtcp2_strm.c b/lib/ngtcp2_strm.c index 214683de9d31e30e1db9e5a66dd5f459b18d4bb9..2a0c0c918982323dd8439a74f512e58d56d8c7e3 100644 --- a/lib/ngtcp2_strm.c +++ b/lib/ngtcp2_strm.c @@ -178,6 +178,163 @@ int ngtcp2_strm_streamfrq_push(ngtcp2_strm *strm, ngtcp2_frame_chain *frc) { frc); } +static int strm_streamfrq_unacked_pop(ngtcp2_strm *strm, + ngtcp2_frame_chain **pfrc) { + ngtcp2_frame_chain *frc, *nfrc; + ngtcp2_stream *fr, *nfr; + uint64_t offset, end_offset; + size_t idx, end_idx; + uint64_t base_offset, end_base_offset; + ngtcp2_range gap; + ngtcp2_vec *v; + int rv; + ngtcp2_ksl_it it; + + *pfrc = NULL; + + assert(strm->tx.streamfrq); + assert(ngtcp2_ksl_len(strm->tx.streamfrq)); + + for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it);) { + frc = ngtcp2_ksl_it_get(&it); + fr = &frc->fr.stream; + + ngtcp2_ksl_remove(strm->tx.streamfrq, &it, &fr->offset); + + idx = 0; + offset = fr->offset; + base_offset = 0; + + gap = ngtcp2_strm_get_unacked_range_after(strm, offset); + if (gap.begin < offset) { + gap.begin = offset; + } + + for (; idx < fr->datacnt && offset < gap.begin; ++idx) { + v = &fr->data[idx]; + if (offset + v->len > gap.begin) { + base_offset = gap.begin - offset; + break; + } + + offset += v->len; + } + + if (idx == fr->datacnt) { + if (fr->fin) { + if (strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED) { + ngtcp2_frame_chain_del(frc, strm->mem); + assert(ngtcp2_ksl_len(strm->tx.streamfrq) == 0); + return 0; + } + + rv = ngtcp2_frame_chain_new(&nfrc, strm->mem); + if (rv != 0) { + ngtcp2_frame_chain_del(frc, strm->mem); + return rv; + } + + nfr = &nfrc->fr.stream; + nfr->fin = 1; + nfr->stream_id = fr->stream_id; + nfr->offset = fr->offset + ngtcp2_vec_len(fr->data, fr->datacnt); + nfr->datacnt = 0; + + *pfrc = nfrc; + } + ngtcp2_frame_chain_del(frc, strm->mem); + continue; + } + + assert(gap.begin == offset + base_offset); + + end_idx = idx; + end_offset = offset; + end_base_offset = 0; + + for (; end_idx < fr->datacnt; ++end_idx) { + v = &fr->data[end_idx]; + if (end_offset + v->len > gap.end) { + end_base_offset = gap.end - end_offset; + break; + } + + end_offset += v->len; + } + + if (fr->offset == offset && base_offset == 0 && fr->datacnt == end_idx) { + *pfrc = frc; + return 0; + } + + if (fr->datacnt == end_idx) { + memmove(fr->data, fr->data + idx, sizeof(fr->data[0]) * (end_idx - idx)); + + assert(fr->data[0].len > base_offset); + + fr->offset = offset + base_offset; + fr->datacnt = end_idx - idx; + fr->data[0].base += base_offset; + fr->data[0].len -= (size_t)base_offset; + + *pfrc = frc; + return 0; + } + + rv = ngtcp2_frame_chain_stream_datacnt_new(&nfrc, fr->datacnt - end_idx, + strm->mem); + if (rv != 0) { + ngtcp2_frame_chain_del(frc, strm->mem); + return rv; + } + + nfr = &nfrc->fr.stream; + memcpy(nfr->data, fr->data + end_idx, + sizeof(nfr->data[0]) * (fr->datacnt - end_idx)); + + assert(nfr->data[0].len > end_base_offset); + + nfr->flags = 0; + nfr->fin = fr->fin; + nfr->stream_id = fr->stream_id; + nfr->offset = end_offset + end_base_offset; + nfr->datacnt = fr->datacnt - end_idx; + nfr->data[0].base += end_base_offset; + nfr->data[0].len -= (size_t)end_base_offset; + + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_del(nfrc, strm->mem); + ngtcp2_frame_chain_del(frc, strm->mem); + return rv; + } + + if (end_base_offset) { + ++end_idx; + } + + memmove(fr->data, fr->data + idx, sizeof(fr->data[0]) * (end_idx - idx)); + + assert(fr->data[0].len > base_offset); + + fr->fin = 0; + fr->offset = offset + base_offset; + fr->datacnt = end_idx - idx; + if (end_base_offset) { + assert(fr->data[fr->datacnt - 1].len > end_base_offset); + fr->data[fr->datacnt - 1].len = (size_t)end_base_offset; + } + fr->data[0].base += base_offset; + fr->data[0].len -= (size_t)base_offset; + + *pfrc = frc; + return 0; + } + + return 0; +} + int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, size_t left) { ngtcp2_stream *fr, *nfr; @@ -188,18 +345,23 @@ int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, ngtcp2_vec a[NGTCP2_MAX_STREAM_DATACNT]; ngtcp2_vec b[NGTCP2_MAX_STREAM_DATACNT]; size_t acnt, bcnt; - ngtcp2_ksl_it it; - uint64_t old_offset; + uint64_t unacked_offset; if (strm->tx.streamfrq == NULL || ngtcp2_ksl_len(strm->tx.streamfrq) == 0) { *pfrc = NULL; return 0; } - it = ngtcp2_ksl_begin(strm->tx.streamfrq); - frc = ngtcp2_ksl_it_get(&it); - fr = &frc->fr.stream; + rv = strm_streamfrq_unacked_pop(strm, &frc); + if (rv != 0) { + return rv; + } + if (frc == NULL) { + *pfrc = NULL; + return 0; + } + fr = &frc->fr.stream; datalen = ngtcp2_vec_len(fr->data, fr->datacnt); if (left == 0) { @@ -210,8 +372,6 @@ int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, } } - ngtcp2_ksl_remove(strm->tx.streamfrq, NULL, &fr->offset); - if (datalen > left) { ngtcp2_vec_copy(a, fr->data, fr->datacnt); acnt = fr->datacnt; @@ -272,18 +432,26 @@ int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, acnt = fr->datacnt; for (; left && ngtcp2_ksl_len(strm->tx.streamfrq);) { - it = ngtcp2_ksl_begin(strm->tx.streamfrq); - nfrc = ngtcp2_ksl_it_get(&it); - nfr = &nfrc->fr.stream; + unacked_offset = ngtcp2_strm_streamfrq_unacked_offset(strm); + if (unacked_offset != fr->offset + datalen) { + assert(fr->offset + datalen < unacked_offset); + break; + } - if (nfr->offset != fr->offset + datalen) { - assert(fr->offset + datalen < nfr->offset); + rv = strm_streamfrq_unacked_pop(strm, &nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_del(frc, strm->mem); + return rv; + } + if (nfrc == NULL) { break; } + nfr = &nfrc->fr.stream; + if (nfr->fin && nfr->datacnt == 0) { fr->fin = 1; - ngtcp2_ksl_remove(strm->tx.streamfrq, NULL, &nfr->offset); ngtcp2_frame_chain_del(nfrc, strm->mem); break; } @@ -291,6 +459,13 @@ int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, nmerged = ngtcp2_vec_merge(a, &acnt, nfr->data, &nfr->datacnt, left, NGTCP2_MAX_STREAM_DATACNT); if (nmerged == 0) { + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_del(nfrc, strm->mem); + ngtcp2_frame_chain_del(frc, strm->mem); + return rv; + } break; } @@ -299,15 +474,18 @@ int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, if (nfr->datacnt == 0) { fr->fin = nfr->fin; - ngtcp2_ksl_remove(strm->tx.streamfrq, NULL, &nfr->offset); ngtcp2_frame_chain_del(nfrc, strm->mem); continue; } - old_offset = nfr->offset; nfr->offset += nmerged; - ngtcp2_ksl_update_key(strm->tx.streamfrq, &old_offset, &nfr->offset); + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + ngtcp2_frame_chain_del(nfrc, strm->mem); + ngtcp2_frame_chain_del(frc, strm->mem); + return rv; + } break; } @@ -341,6 +519,40 @@ int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, return 0; } +uint64_t ngtcp2_strm_streamfrq_unacked_offset(ngtcp2_strm *strm) { + ngtcp2_frame_chain *frc; + ngtcp2_stream *fr; + ngtcp2_range gap; + ngtcp2_ksl_it it; + size_t datalen; + + assert(strm->tx.streamfrq); + assert(ngtcp2_ksl_len(strm->tx.streamfrq)); + + for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + frc = ngtcp2_ksl_it_get(&it); + fr = &frc->fr.stream; + + gap = ngtcp2_strm_get_unacked_range_after(strm, fr->offset); + + datalen = ngtcp2_vec_len(fr->data, fr->datacnt); + + if (gap.begin <= fr->offset) { + return fr->offset; + } + if (gap.begin < fr->offset + datalen) { + return gap.begin; + } + if (fr->offset + datalen == gap.begin && fr->fin && + !(strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED)) { + return fr->offset + datalen; + } + } + + return (uint64_t)-1; +} + ngtcp2_frame_chain *ngtcp2_strm_streamfrq_top(ngtcp2_strm *strm) { ngtcp2_ksl_it it; diff --git a/lib/ngtcp2_strm.h b/lib/ngtcp2_strm.h index 999c37a63f3318fc6a3aca6633e25e51174c0f1a..f376ab8878071783440285113020dbd9302919dd 100644 --- a/lib/ngtcp2_strm.h +++ b/lib/ngtcp2_strm.h @@ -64,6 +64,7 @@ typedef enum { /* NGTCP2_STRM_FLAG_RST_ACKED indicates that the outgoing RST_STREAM is acknowledged by peer. */ NGTCP2_STRM_FLAG_RST_ACKED = 0x20, + NGTCP2_STRM_FLAG_FIN_ACKED = 0x40, } ngtcp2_strm_flags; struct ngtcp2_strm; @@ -207,6 +208,8 @@ int ngtcp2_strm_streamfrq_push(ngtcp2_strm *strm, ngtcp2_frame_chain *frc); int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, size_t left); +uint64_t ngtcp2_strm_streamfrq_unacked_offset(ngtcp2_strm *strm); + /* * ngtcp2_strm_streamfrq_top returns the first ngtcp2_frame_chain. * The queue must not be empty. diff --git a/tests/main.c b/tests/main.c index 8b86739485b79ae83cb255fe1e1052f069a34bec..5ffc4c7f92e67e8358fe88dd9966d7e84e8dd57c 100644 --- a/tests/main.c +++ b/tests/main.c @@ -269,6 +269,10 @@ int main() { !CU_add_test(pSuite, "vec_merge", test_ngtcp2_vec_merge) || !CU_add_test(pSuite, "strm_streamfrq_pop", test_ngtcp2_strm_streamfrq_pop) || + !CU_add_test(pSuite, "strm_streamfrq_unacked_offset", + test_ngtcp2_strm_streamfrq_unacked_offset) || + !CU_add_test(pSuite, "strm_streamfrq_unacked_pop", + test_ngtcp2_strm_streamfrq_unacked_pop) || !CU_add_test(pSuite, "pv_add_entry", test_ngtcp2_pv_add_entry) || !CU_add_test(pSuite, "pv_validate", test_ngtcp2_pv_validate) || !CU_add_test(pSuite, "check_invalid_stateless_reset_token", diff --git a/tests/ngtcp2_strm_test.c b/tests/ngtcp2_strm_test.c index e4a7011f0857a18a6c32b5115a0b878b61e20cb7..21f0ecd1886372761271cfc16bf86be2b0c85502 100644 --- a/tests/ngtcp2_strm_test.c +++ b/tests/ngtcp2_strm_test.c @@ -28,6 +28,7 @@ #include "ngtcp2_strm.h" #include "ngtcp2_test_helper.h" +#include "ngtcp2_vec.h" static uint8_t nulldata[1024]; @@ -290,3 +291,245 @@ void test_ngtcp2_strm_streamfrq_pop(void) { ngtcp2_frame_chain_del(frc, mem); ngtcp2_strm_free(&strm); } + +void test_ngtcp2_strm_streamfrq_unacked_offset(void) { + ngtcp2_strm strm; + ngtcp2_frame_chain *frc; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_vec *data; + + /* Everything acknowledged including fin */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_FIN_ACKED, 0, 0, NULL, mem); + + ngtcp2_frame_chain_new(&frc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 0; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 17; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_frame_chain_new(&frc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 1; + frc->fr.stream.offset = 443; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 971; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 443 + 971); + + CU_ASSERT((uint64_t)-1 == ngtcp2_strm_streamfrq_unacked_offset(&strm)); + + ngtcp2_strm_free(&strm); + + /* Everything acknowledged but fin */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, mem); + + ngtcp2_frame_chain_new(&frc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 0; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 17; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_frame_chain_new(&frc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 1; + frc->fr.stream.offset = 443; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 971; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 443 + 971); + + CU_ASSERT(443 + 971 == ngtcp2_strm_streamfrq_unacked_offset(&strm)); + + ngtcp2_strm_free(&strm); + + /* Unacked gap starts in the middle of stream to resend */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, mem); + + ngtcp2_frame_chain_new(&frc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 0; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 971; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 443); + + CU_ASSERT(443 == ngtcp2_strm_streamfrq_unacked_offset(&strm)); + + ngtcp2_strm_free(&strm); + + /* Unacked gap starts after stream to resend */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, mem); + + ngtcp2_frame_chain_new(&frc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 0; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 971; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 971); + + CU_ASSERT((uint64_t)-1 == ngtcp2_strm_streamfrq_unacked_offset(&strm)); + + ngtcp2_strm_free(&strm); + + /* Unacked gap and stream overlap and gap starts before stream */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, mem); + + ngtcp2_frame_chain_new(&frc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 977; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 971; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 971); + + CU_ASSERT(977 == ngtcp2_strm_streamfrq_unacked_offset(&strm)); + + ngtcp2_strm_free(&strm); +} + +void test_ngtcp2_strm_streamfrq_unacked_pop(void) { + ngtcp2_strm strm; + ngtcp2_frame_chain *frc; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_vec *data; + int rv; + + /* Everything acknowledged including fin */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_FIN_ACKED, 0, 0, NULL, mem); + + ngtcp2_frame_chain_new(&frc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 307; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 149; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_frame_chain_new(&frc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 1; + frc->fr.stream.offset = 457; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 307; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 764); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 1024); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == frc); + + ngtcp2_strm_free(&strm); + + /* Everything acknowledged but fin */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, mem); + + ngtcp2_frame_chain_new(&frc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 307; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 149; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_frame_chain_new(&frc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 1; + frc->fr.stream.offset = 457; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 307; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 764); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 1024); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == frc->fr.stream.fin); + CU_ASSERT(764 == frc->fr.stream.offset); + CU_ASSERT(0 == ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt)); + + ngtcp2_frame_chain_del(frc, mem); + ngtcp2_strm_free(&strm); + + /* Remove leading acknowledged data */ + setup_strm_streamfrq_fixture(&strm, mem); + + ngtcp2_strm_ack_data(&strm, 0, 12); + + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 43); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == frc->fr.stream.fin); + CU_ASSERT(12 == frc->fr.stream.offset); + CU_ASSERT(1 == frc->fr.stream.datacnt); + CU_ASSERT(43 == ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt)); + + ngtcp2_frame_chain_del(frc, mem); + ngtcp2_strm_free(&strm); + + /* Creating a gap of acknowledged data */ + setup_strm_streamfrq_fixture(&strm, mem); + + ngtcp2_strm_ack_data(&strm, 32, 1); + + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 43); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == frc->fr.stream.fin); + CU_ASSERT(0 == frc->fr.stream.offset); + CU_ASSERT(2 == frc->fr.stream.datacnt); + CU_ASSERT(32 == ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt)); + + ngtcp2_frame_chain_del(frc, mem); + ngtcp2_strm_free(&strm); +} diff --git a/tests/ngtcp2_strm_test.h b/tests/ngtcp2_strm_test.h index abc9ebbbe0a867f62b1ddb63661504b73896452c..f94c4238b76a3dbf46f49999528aab5e91c1c7fc 100644 --- a/tests/ngtcp2_strm_test.h +++ b/tests/ngtcp2_strm_test.h @@ -30,5 +30,7 @@ #endif /* HAVE_CONFIG_H */ void test_ngtcp2_strm_streamfrq_pop(void); +void test_ngtcp2_strm_streamfrq_unacked_offset(void); +void test_ngtcp2_strm_streamfrq_unacked_pop(void); #endif /* NGTCP2_STRM_TEST_H */