/* * Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy * in the file LICENSE in the source distribution or at * https://www.openssl.org/source/license.html */ #include "internal/quic_fifd.h" #include "internal/quic_wire.h" #include "internal/qlog_event_helpers.h" DEFINE_LIST_OF(tx_history, OSSL_ACKM_TX_PKT); int ossl_quic_fifd_init(QUIC_FIFD *fifd, QUIC_CFQ *cfq, OSSL_ACKM *ackm, QUIC_TXPIM *txpim, /* stream_id is UINT64_MAX for the crypto stream */ QUIC_SSTREAM *(*get_sstream_by_id)(uint64_t stream_id, uint32_t pn_space, void *arg), void *get_sstream_by_id_arg, /* stream_id is UINT64_MAX if not applicable */ void (*regen_frame)(uint64_t frame_type, uint64_t stream_id, QUIC_TXPIM_PKT *pkt, void *arg), void *regen_frame_arg, void (*confirm_frame)(uint64_t frame_type, uint64_t stream_id, QUIC_TXPIM_PKT *pkt, void *arg), void *confirm_frame_arg, void (*sstream_updated)(uint64_t stream_id, void *arg), void *sstream_updated_arg, QLOG *qlog) { if (cfq == NULL || ackm == NULL || txpim == NULL || get_sstream_by_id == NULL || regen_frame == NULL) return 0; fifd->cfq = cfq; fifd->ackm = ackm; fifd->txpim = txpim; fifd->get_sstream_by_id = get_sstream_by_id; fifd->get_sstream_by_id_arg = get_sstream_by_id_arg; fifd->regen_frame = regen_frame; fifd->regen_frame_arg = regen_frame_arg; fifd->confirm_frame = confirm_frame; fifd->confirm_frame_arg = confirm_frame_arg; fifd->sstream_updated = sstream_updated; fifd->sstream_updated_arg = sstream_updated_arg; fifd->qlog = qlog; return 1; } void ossl_quic_fifd_cleanup(QUIC_FIFD *fifd) { /* No-op. */ } static void on_acked(void *arg) { QUIC_TXPIM_PKT *pkt = arg; QUIC_FIFD *fifd = pkt->fifd; const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt); size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt); QUIC_SSTREAM *sstream; QUIC_CFQ_ITEM *cfq_item, *cfq_item_next; /* STREAM and CRYPTO stream chunks, FINs and stream FC frames */ for (i = 0; i < num_chunks; ++i) { sstream = fifd->get_sstream_by_id(chunks[i].stream_id, pkt->ackm_pkt.pkt_space, fifd->get_sstream_by_id_arg); if (sstream == NULL) continue; if (chunks[i].end >= chunks[i].start) /* coverity[check_return]: Best effort - we cannot fail here. */ ossl_quic_sstream_mark_acked(sstream, chunks[i].start, chunks[i].end); if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX) ossl_quic_sstream_mark_acked_fin(sstream); if (chunks[i].has_stop_sending && chunks[i].stream_id != UINT64_MAX) fifd->confirm_frame(OSSL_QUIC_FRAME_TYPE_STOP_SENDING, chunks[i].stream_id, pkt, fifd->confirm_frame_arg); if (chunks[i].has_reset_stream && chunks[i].stream_id != UINT64_MAX) fifd->confirm_frame(OSSL_QUIC_FRAME_TYPE_RESET_STREAM, chunks[i].stream_id, pkt, fifd->confirm_frame_arg); if (ossl_quic_sstream_is_totally_acked(sstream)) fifd->sstream_updated(chunks[i].stream_id, fifd->sstream_updated_arg); } /* GCR */ for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) { cfq_item_next = cfq_item->pkt_next; ossl_quic_cfq_release(fifd->cfq, cfq_item); } ossl_quic_txpim_pkt_release(fifd->txpim, pkt); } static void on_lost(void *arg) { QUIC_TXPIM_PKT *pkt = arg; QUIC_FIFD *fifd = pkt->fifd; const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt); size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt); QUIC_SSTREAM *sstream; QUIC_CFQ_ITEM *cfq_item, *cfq_item_next; int sstream_updated; ossl_qlog_event_recovery_packet_lost(fifd->qlog, pkt); /* STREAM and CRYPTO stream chunks, FIN and stream FC frames */ for (i = 0; i < num_chunks; ++i) { sstream = fifd->get_sstream_by_id(chunks[i].stream_id, pkt->ackm_pkt.pkt_space, fifd->get_sstream_by_id_arg); if (sstream == NULL) continue; sstream_updated = 0; if (chunks[i].end >= chunks[i].start) { /* * Note: If the stream is being reset, we do not need to retransmit * old data as this is pointless. In this case this will be handled * by (sstream == NULL) above as the QSM will free the QUIC_SSTREAM * and our call to get_sstream_by_id above will return NULL. */ ossl_quic_sstream_mark_lost(sstream, chunks[i].start, chunks[i].end); sstream_updated = 1; } if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX) { ossl_quic_sstream_mark_lost_fin(sstream); sstream_updated = 1; } if (chunks[i].has_stop_sending && chunks[i].stream_id != UINT64_MAX) fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_STOP_SENDING, chunks[i].stream_id, pkt, fifd->regen_frame_arg); if (chunks[i].has_reset_stream && chunks[i].stream_id != UINT64_MAX) fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_RESET_STREAM, chunks[i].stream_id, pkt, fifd->regen_frame_arg); /* * Inform caller that stream needs an FC frame. * * Note: We could track whether an FC frame was sent originally for the * stream to determine if it really needs to be regenerated or not. * However, if loss has occurred, it's probably better to ensure the * peer has up-to-date flow control data for the stream. Given that * these frames are extremely small, we may as well always send it when * handling loss. */ fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA, chunks[i].stream_id, pkt, fifd->regen_frame_arg); if (sstream_updated && chunks[i].stream_id != UINT64_MAX) fifd->sstream_updated(chunks[i].stream_id, fifd->sstream_updated_arg); } /* GCR */ for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) { cfq_item_next = cfq_item->pkt_next; ossl_quic_cfq_mark_lost(fifd->cfq, cfq_item, UINT32_MAX); } /* Regenerate flag frames */ if (pkt->had_handshake_done_frame) fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE, UINT64_MAX, pkt, fifd->regen_frame_arg); if (pkt->had_max_data_frame) fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_DATA, UINT64_MAX, pkt, fifd->regen_frame_arg); if (pkt->had_max_streams_bidi_frame) fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI, UINT64_MAX, pkt, fifd->regen_frame_arg); if (pkt->had_max_streams_uni_frame) fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI, UINT64_MAX, pkt, fifd->regen_frame_arg); if (pkt->had_ack_frame) /* * We always use the ACK_WITH_ECN frame type to represent the ACK frame * type in our callback; we assume it is the caller's job to decide * whether it wants to send ECN data or not. */ fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN, UINT64_MAX, pkt, fifd->regen_frame_arg); ossl_quic_txpim_pkt_release(fifd->txpim, pkt); } static void on_discarded(void *arg) { QUIC_TXPIM_PKT *pkt = arg; QUIC_FIFD *fifd = pkt->fifd; QUIC_CFQ_ITEM *cfq_item, *cfq_item_next; /* * Don't need to do anything to SSTREAMs for STREAM and CRYPTO streams, as * we assume caller will clean them up. */ /* GCR */ for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) { cfq_item_next = cfq_item->pkt_next; ossl_quic_cfq_release(fifd->cfq, cfq_item); } ossl_quic_txpim_pkt_release(fifd->txpim, pkt); } int ossl_quic_fifd_pkt_commit(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt) { QUIC_CFQ_ITEM *cfq_item; const QUIC_TXPIM_CHUNK *chunks; size_t i, num_chunks; QUIC_SSTREAM *sstream; pkt->fifd = fifd; pkt->ackm_pkt.on_lost = on_lost; pkt->ackm_pkt.on_acked = on_acked; pkt->ackm_pkt.on_discarded = on_discarded; pkt->ackm_pkt.cb_arg = pkt; ossl_list_tx_history_init_elem(&pkt->ackm_pkt); pkt->ackm_pkt.anext = pkt->ackm_pkt.lnext = NULL; /* * Mark the CFQ items which have been added to this packet as having been * transmitted. */ for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item->pkt_next) ossl_quic_cfq_mark_tx(fifd->cfq, cfq_item); /* * Mark the send stream chunks which have been added to the packet as having * been transmitted. */ chunks = ossl_quic_txpim_pkt_get_chunks(pkt); num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt); for (i = 0; i < num_chunks; ++i) { sstream = fifd->get_sstream_by_id(chunks[i].stream_id, pkt->ackm_pkt.pkt_space, fifd->get_sstream_by_id_arg); if (sstream == NULL) continue; if (chunks[i].end >= chunks[i].start && !ossl_quic_sstream_mark_transmitted(sstream, chunks[i].start, chunks[i].end)) return 0; if (chunks[i].has_fin && !ossl_quic_sstream_mark_transmitted_fin(sstream, chunks[i].end + 1)) return 0; } /* Inform the ACKM. */ return ossl_ackm_on_tx_packet(fifd->ackm, &pkt->ackm_pkt); } void ossl_quic_fifd_set_qlog(QUIC_FIFD *fifd, QLOG *qlog) { fifd->qlog = qlog; }