fn output_path()

in neqo-transport/src/connection/mod.rs [2432:2616]


    fn output_path(
        &mut self,
        path: &PathRef,
        now: Instant,
        closing_frame: Option<&ClosingFrame>,
    ) -> Res<SendOption> {
        let mut initial_sent = None;
        let mut packet_tos = None;
        let mut needs_padding = false;
        let grease_quic_bit = self.can_grease_quic_bit();
        let version = self.version();

        // Determine how we are sending packets (PTO, etc..).
        let profile = self.loss_recovery.send_profile(&path.borrow(), now);
        qdebug!("[{self}] output_path send_profile {profile:?}");

        // Frames for different epochs must go in different packets, but then these
        // packets can go in a single datagram
        let mut encoder = Encoder::with_capacity(profile.limit());
        for space in PacketNumberSpace::iter() {
            // Ensure we have tx crypto state for this epoch, or skip it.
            let Some((epoch, tx)) = self.crypto.states.select_tx_mut(self.version, space) else {
                continue;
            };

            let header_start = encoder.len();
            let (pt, mut builder) = Self::build_packet_header(
                &path.borrow(),
                epoch,
                encoder,
                tx,
                &self.address_validation,
                version,
                grease_quic_bit,
            );
            let pn = Self::add_packet_number(
                &mut builder,
                tx,
                self.loss_recovery.largest_acknowledged_pn(space),
            );
            // The builder will set the limit to 0 if there isn't enough space for the header.
            if builder.is_full() {
                encoder = builder.abort();
                break;
            }

            // Configure the limits and padding for this packet.
            let aead_expansion = tx.expansion();
            needs_padding |= builder.set_initial_limit(
                &profile,
                aead_expansion,
                self.paths
                    .primary()
                    .ok_or(Error::InternalError)?
                    .borrow()
                    .pmtud(),
            );
            builder.enable_padding(needs_padding);
            if builder.is_full() {
                encoder = builder.abort();
                break;
            }

            // Add frames to the packet.
            let payload_start = builder.len();
            let (mut tokens, mut ack_eliciting, mut padded) = (Vec::new(), false, false);
            if let Some(close) = closing_frame {
                self.write_closing_frames(close, &mut builder, space, now, path, &mut tokens);
            } else {
                (tokens, ack_eliciting, padded) =
                    self.write_frames(path, space, &profile, &mut builder, header_start != 0, now);
            }
            if builder.packet_empty() {
                // Nothing to include in this packet.
                encoder = builder.abort();
                continue;
            }

            // If we don't have a TOS for this UDP datagram yet (i.e. `tos` is `None`), get it,
            // adding a `RecoveryToken::EcnEct0` to `tokens` in case of loss.
            let tos = packet_tos.get_or_insert_with(|| path.borrow().tos(&mut tokens));
            self.log_packet(
                packet::MetaData::new_out(
                    path,
                    pt,
                    pn,
                    builder.len() + aead_expansion,
                    &builder.as_ref()[payload_start..],
                    *tos,
                ),
                now,
            );

            self.stats.borrow_mut().packets_tx += 1;
            let tx = self
                .crypto
                .states
                .tx_mut(self.version, epoch)
                .ok_or(Error::InternalError)?;
            encoder = builder.build(tx)?;
            self.crypto.states.auto_update()?;

            if ack_eliciting {
                self.idle_timeout.on_packet_sent(now);
            }
            let sent = SentPacket::new(
                pt,
                pn,
                now,
                ack_eliciting,
                tokens,
                encoder.len() - header_start,
            );
            if padded {
                needs_padding = false;
                self.loss_recovery.on_packet_sent(path, sent, now);
            } else if pt == PacketType::Initial && (self.role == Role::Client || ack_eliciting) {
                // Packets containing Initial packets might need padding, and we want to
                // track that padding along with the Initial packet.  So defer tracking.
                initial_sent = Some(sent);
                needs_padding = true;
            } else {
                if pt == PacketType::Handshake && self.role == Role::Client {
                    needs_padding = false;
                }
                self.loss_recovery.on_packet_sent(path, sent, now);
            }

            // Track which packet types are sent with which ECN codepoints. For
            // coalesced packets, this increases the counts for each packet type
            // contained in the coalesced packet. This is per Section 13.4.1 of
            // RFC 9000.
            self.stats.borrow_mut().ecn_tx[pt] += IpTosEcn::from(*tos);
            if space == PacketNumberSpace::Handshake {
                if self.role == Role::Client {
                    // We're sending a Handshake packet, so we can discard Initial keys.
                    self.discard_keys(PacketNumberSpace::Initial, now);
                } else if self.role == Role::Server && self.state == State::Confirmed {
                    // We could discard handshake keys in set_state,
                    // but wait until after sending an ACK.
                    self.discard_keys(PacketNumberSpace::Handshake, now);
                }
            }

            // If the client has more CRYPTO data queued up, do not coalesce if
            // this packet is an Initial. Without this, 0-RTT packets could be
            // coalesced with the first Initial, which some server (e.g., ours)
            // do not support, because they may not save packets they can't
            // decrypt yet.
            if self.role == Role::Client
                && space == PacketNumberSpace::Initial
                && !self.crypto.streams.is_empty(space)
            {
                break;
            }
        }

        if encoder.is_empty() {
            qdebug!("TX blocked, profile={profile:?}");
            Ok(SendOption::No(profile.paced()))
        } else {
            // Perform additional padding for Initial packets as necessary.
            let mut packets: Vec<u8> = encoder.into();
            if let Some(mut initial) = initial_sent.take() {
                if needs_padding && packets.len() < profile.limit() {
                    qdebug!(
                        "[{self}] pad Initial from {} to PLPMTU {}",
                        packets.len(),
                        profile.limit()
                    );
                    initial.track_padding(profile.limit() - packets.len());
                    // These zeros aren't padding frames, they are an invalid all-zero coalesced
                    // packet, which is why we don't increase `frame_tx.padding` count here.
                    packets.resize(profile.limit(), 0);
                }
                self.loss_recovery.on_packet_sent(path, initial, now);
            }
            path.borrow_mut().add_sent(packets.len());
            Ok(SendOption::Yes(path.borrow_mut().datagram(
                packets,
                packet_tos.unwrap_or_default(),
                &mut self.stats.borrow_mut(),
            )))
        }
    }