1
0
Fork 0
Univerxel/deps/picoquic/sender.c

3926 lines
160 KiB
C

/*
* Author: Christian Huitema
* Copyright (c) 2017, Private Octopus, Inc.
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "picoquic_internal.h"
#include "picoquic_unified_log.h"
#include "tls_api.h"
#include <stdlib.h>
#include <string.h>
/*
* Sending logic.
*
* Data is sent over streams. This is instantiated by the "Post to stream" command, which
* chains data to the head of stream structure. Data is unchained when it sent for the
* first time.
*
* Data is sent in packets, which contain stream frames and possibly other frames.
* The retransmission logic operates on packets. If a packet is seen as lost, the
* important frames that it contains will have to be retransmitted.
*
* Unacknowledged packets are kept in a chained list. Packets get removed from that
* list during the processing of acknowledgements. Packets are marked lost when a
* sufficiently older packet is acknowledged, or after a timer. Lost packets
* generate new packets, which are queued in the chained list.
*/
static picoquic_stream_head_t* picoquic_find_stream_for_writing(picoquic_cnx_t* cnx,
uint64_t stream_id, int * ret)
{
picoquic_stream_head_t* stream = picoquic_find_stream(cnx, stream_id);
*ret = 0;
if (stream == NULL) {
/* Need to check that the ID is authorized */
/* Check parity */
if (IS_CLIENT_STREAM_ID(stream_id) != cnx->client_mode) {
*ret = PICOQUIC_ERROR_INVALID_STREAM_ID;
}
if (*ret == 0) {
stream = picoquic_create_missing_streams(cnx, stream_id, 0);
if (stream == NULL) {
*ret = PICOQUIC_ERROR_MEMORY;
}
}
}
return stream;
}
int picoquic_set_app_stream_ctx(picoquic_cnx_t* cnx,
uint64_t stream_id, void* app_stream_ctx)
{
int ret = 0;
picoquic_stream_head_t* stream = picoquic_find_stream_for_writing(cnx, stream_id, &ret);
if (ret == 0) {
stream->app_stream_ctx = app_stream_ctx;
}
return ret;
}
int picoquic_mark_active_stream(picoquic_cnx_t* cnx,
uint64_t stream_id, int is_active, void * app_stream_ctx)
{
int ret = 0;
picoquic_stream_head_t* stream = picoquic_find_stream_for_writing(cnx, stream_id, &ret);
if (ret == 0) {
if (is_active) {
/* The call only fails if the stream was closed or reset */
if (!stream->fin_requested && !stream->reset_requested &&
cnx->callback_fn != NULL) {
stream->is_active = 1;
stream->app_stream_ctx = app_stream_ctx;
picoquic_reinsert_by_wake_time(cnx->quic, cnx, picoquic_get_quic_time(cnx->quic));
}
else {
ret = PICOQUIC_ERROR_CANNOT_SET_ACTIVE_STREAM;
}
}
else {
stream->is_active = 0;
}
}
return ret;
}
int picoquic_mark_high_priority_stream(picoquic_cnx_t * cnx, uint64_t stream_id, int is_high_priority)
{
if (is_high_priority) {
cnx->high_priority_stream_id = stream_id;
}
else if (cnx->high_priority_stream_id == stream_id) {
cnx->high_priority_stream_id = (uint64_t)((int64_t)-1);
}
return 0;
}
int picoquic_add_to_stream_with_ctx(picoquic_cnx_t* cnx, uint64_t stream_id,
const uint8_t* data, size_t length, int set_fin, void * app_stream_ctx)
{
int ret = 0;
picoquic_stream_head_t* stream = picoquic_find_stream_for_writing(cnx, stream_id, &ret);
if (ret == 0 && set_fin) {
if (stream->fin_requested) {
/* app error, notified the fin twice*/
if (length > 0) {
ret = -1;
}
} else {
stream->fin_requested = 1;
}
}
/* If our side has sent RST_STREAM or received STOP_SENDING, we should not send anymore data. */
if (ret == 0 && (stream->reset_sent || stream->stop_sending_received)) {
ret = -1;
}
if (ret == 0 && length > 0) {
picoquic_stream_data_node_t* stream_data = (picoquic_stream_data_node_t*)malloc(sizeof(picoquic_stream_data_node_t));
if (stream_data == 0) {
ret = -1;
} else {
stream_data->bytes = (uint8_t*)malloc(length);
if (stream_data->bytes == NULL) {
free(stream_data);
stream_data = NULL;
ret = -1;
} else {
picoquic_stream_data_node_t** pprevious = &stream->send_queue;
picoquic_stream_data_node_t* next = stream->send_queue;
memcpy(stream_data->bytes, data, length);
stream_data->length = length;
stream_data->offset = 0;
stream_data->next_stream_data = NULL;
while (next != NULL) {
pprevious = &next->next_stream_data;
next = next->next_stream_data;
}
*pprevious = stream_data;
}
}
picoquic_reinsert_by_wake_time(cnx->quic, cnx, picoquic_get_quic_time(cnx->quic));
}
if (ret == 0) {
cnx->nb_bytes_queued += length;
stream->is_active = 0;
stream->app_stream_ctx = app_stream_ctx;
}
return ret;
}
int picoquic_add_to_stream(picoquic_cnx_t* cnx, uint64_t stream_id,
const uint8_t* data, size_t length, int set_fin)
{
return picoquic_add_to_stream_with_ctx(cnx, stream_id, data, length, set_fin, NULL);
}
int picoquic_open_flow_control(picoquic_cnx_t* cnx, uint64_t stream_id, uint64_t expected_data_size)
{
int ret = 0;
uint8_t buffer[512];
size_t length = 0;
size_t consumed = 0;
picoquic_stream_head_t* stream = picoquic_find_stream(cnx, stream_id);
if (cnx->cnx_state == picoquic_state_ready){
/* Only send the update in ready state, so that the misc frame is not picked by the
* wrong transport context.
* TODO: find way to queue the update so it is only sent as 0RTT or 1RTT packet.
*/
if (stream == NULL) {
ret = PICOQUIC_ERROR_INVALID_STREAM_ID;
}
else {
uint64_t max_required = stream->consumed_offset + expected_data_size;
uint8_t* bytes_max = buffer + sizeof(buffer);
int more_data = 0;
int is_pure_ack = 1;
if (max_required > stream->maxdata_local) {
uint8_t* bytes_next = picoquic_format_max_stream_data_frame(stream, buffer + consumed, bytes_max, &more_data, &is_pure_ack, max_required);
bytes_next = picoquic_format_max_data_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack, expected_data_size);
if ((length = bytes_next - buffer) > 0) {
ret = picoquic_queue_misc_frame(cnx, buffer, length, is_pure_ack);
}
}
}
}
return ret;
}
void picoquic_reset_stream_ctx(picoquic_cnx_t* cnx, uint64_t stream_id)
{
picoquic_stream_head_t* stream = picoquic_find_stream(cnx, stream_id);
if (stream != NULL) {
stream->app_stream_ctx = NULL;
}
}
int picoquic_reset_stream(picoquic_cnx_t* cnx,
uint64_t stream_id, uint16_t local_stream_error)
{
int ret = 0;
picoquic_stream_head_t* stream = NULL;
stream = picoquic_find_stream(cnx, stream_id);
if (stream == NULL) {
ret = PICOQUIC_ERROR_INVALID_STREAM_ID;
}
else {
stream->app_stream_ctx = NULL;
if (stream->fin_sent) {
ret = PICOQUIC_ERROR_STREAM_ALREADY_CLOSED;
}
else if (!stream->reset_requested) {
stream->local_error = local_stream_error;
stream->reset_requested = 1;
}
}
picoquic_reinsert_by_wake_time(cnx->quic, cnx, picoquic_get_quic_time(cnx->quic));
return ret;
}
uint64_t picoquic_get_next_local_stream_id(picoquic_cnx_t* cnx, int is_unidir)
{
int stream_type_id = (cnx->client_mode ^ 1) | ((is_unidir) ? 2 : 0);
return cnx->next_stream_id[stream_type_id];
}
int picoquic_stop_sending(picoquic_cnx_t* cnx,
uint64_t stream_id, uint16_t local_stream_error)
{
int ret = 0;
picoquic_stream_head_t* stream = NULL;
stream = picoquic_find_stream(cnx, stream_id);
if (stream == NULL) {
ret = PICOQUIC_ERROR_INVALID_STREAM_ID;
}
else {
stream->app_stream_ctx = NULL;
if (stream->reset_received) {
ret = PICOQUIC_ERROR_STREAM_ALREADY_CLOSED;
}
else if (!stream->stop_sending_requested) {
stream->local_stop_error = local_stream_error;
stream->stop_sending_requested = 1;
picoquic_insert_output_stream(cnx, stream);
}
}
picoquic_reinsert_by_wake_time(cnx->quic, cnx, picoquic_get_quic_time(cnx->quic));
return ret;
}
/*
* Manage content padding
*/
size_t picoquic_pad_to_target_length(uint8_t * bytes, size_t length, size_t target)
{
if (length < target) {
memset(bytes + length, 0, target - length);
length = target;
}
return length;
}
size_t picoquic_pad_to_policy(picoquic_cnx_t * cnx, uint8_t * bytes, size_t length, uint32_t max_length)
{
size_t target = cnx->padding_minsize;
if (length > target && cnx->padding_multiple != 0) {
uint32_t delta = (length - target) % cnx->padding_multiple;
if (delta == 0) {
target = length;
}
else {
target = length + cnx->padding_multiple - delta;
}
}
if (target > max_length) {
target = max_length;
}
return picoquic_pad_to_target_length(bytes, length, target);
}
/*
* Packet management
*/
picoquic_packet_t* picoquic_create_packet(picoquic_quic_t * quic)
{
picoquic_packet_t* packet = quic->p_first_packet;
if (packet == NULL) {
packet = (picoquic_packet_t*)malloc(sizeof(picoquic_packet_t));
}
else {
quic->p_first_packet = packet->next_packet;
quic->nb_packets_in_pool--;
}
if (packet != NULL) {
memset(packet, 0, offsetof(struct st_picoquic_packet_t, bytes));
}
return packet;
}
void picoquic_recycle_packet(picoquic_quic_t * quic, picoquic_packet_t* packet)
{
if (packet != NULL) {
if (quic->nb_packets_in_pool >= PICOQUIC_MAX_PACKETS_IN_POOL) {
free(packet);
}
else {
packet->next_packet = quic->p_first_packet;
quic->p_first_packet = packet;
quic->nb_packets_in_pool++;
}
}
}
void picoquic_update_payload_length(
uint8_t* bytes, size_t pnum_index, size_t header_length, size_t packet_length)
{
if ((bytes[0] & 0x80) != 0 && header_length > 6 && packet_length > header_length && packet_length < 0x4000)
{
picoquic_varint_encode_16(bytes + pnum_index - 2, (uint16_t)(packet_length - header_length));
}
}
size_t picoquic_create_packet_header(
picoquic_cnx_t* cnx,
picoquic_packet_type_enum packet_type,
uint64_t sequence_number,
picoquic_connection_id_t * remote_cnxid,
picoquic_connection_id_t * local_cnxid,
size_t header_length,
uint8_t* bytes,
size_t* pn_offset,
size_t* pn_length)
{
size_t length = 0;
picoquic_connection_id_t dest_cnx_id =
(cnx->client_mode && (packet_type == picoquic_packet_initial ||
packet_type == picoquic_packet_0rtt_protected)
&& picoquic_is_connection_id_null(remote_cnxid)) ?
cnx->initial_cnxid : *remote_cnxid;
/* Prepare the packet header */
if (packet_type == picoquic_packet_1rtt_protected) {
/* Create a short packet -- using 32 bit sequence numbers for now */
uint8_t K = (cnx->key_phase_enc) ? 0x04 : 0;
uint8_t C = 0x40; /* set the QUIC bit */
size_t pn_l = 4; /* default packet length to 4 bytes */
if (cnx->do_grease_quic_bit) {
/* we grease the quic bit if both local and remote agreed to do so */
C &= (uint8_t)picoquic_public_random_64();
cnx->quic_bit_greased |= (C == 0);
}
length = 0;
bytes[length++] = (K | C | picoquic_spin_function_table[cnx->spin_policy].spinbit_outgoing(cnx));
length += picoquic_format_connection_id(&bytes[length], PICOQUIC_MAX_PACKET_SIZE - length, dest_cnx_id);
*pn_offset = length;
if (header_length > length && header_length < length + 4) {
pn_l = header_length - length;
}
*pn_length = pn_l;
bytes[0] |= (pn_l - 1);
switch (pn_l) {
case 1:
bytes[length] = (uint8_t)sequence_number;
break;
case 2:
picoformat_16(&bytes[length], (uint16_t)sequence_number);
break;
case 3:
picoformat_24(&bytes[length], (uint32_t)sequence_number);
break;
default:
picoformat_32(&bytes[length], (uint32_t)sequence_number);
break;
}
length += pn_l;
}
else {
/* Create a long packet -- default encode PP=3 */
switch (packet_type) {
case picoquic_packet_initial:
bytes[0] = 0xC3;
break;
case picoquic_packet_0rtt_protected:
bytes[0] = 0xD3;
break;
case picoquic_packet_handshake:
bytes[0] = 0xE3;
break;
case picoquic_packet_retry:
/* Do not set PP in retry header, the bits are later used for ODCIL */
bytes[0] = 0xF0;
break;
default:
bytes[0] = 0xFF; /* Will cause an error... */
break;
}
if (cnx->do_grease_quic_bit) {
bytes[0] &= 0xBF;
}
length = 1;
if ((cnx->cnx_state == picoquic_state_client_init || cnx->cnx_state == picoquic_state_client_init_sent) && packet_type == picoquic_packet_initial) {
picoformat_32(&bytes[length], cnx->proposed_version);
}
else {
picoformat_32(&bytes[length], picoquic_supported_versions[cnx->version_index].version);
}
length += 4;
bytes[length++] = dest_cnx_id.id_len;
length += picoquic_format_connection_id(&bytes[length], PICOQUIC_MAX_PACKET_SIZE - length, dest_cnx_id);
bytes[length++] = local_cnxid->id_len;
length += picoquic_format_connection_id(&bytes[length], PICOQUIC_MAX_PACKET_SIZE - length, *local_cnxid);
/* Special case of packet initial -- encode token as part of header */
if (packet_type == picoquic_packet_initial) {
length += picoquic_varint_encode(&bytes[length], PICOQUIC_MAX_PACKET_SIZE - length, cnx->retry_token_length);
if (cnx->retry_token_length > 0) {
memcpy(&bytes[length], cnx->retry_token, cnx->retry_token_length);
length += cnx->retry_token_length;
}
}
if (packet_type == picoquic_packet_retry) {
/* No payload length and no sequence number for Retry */
*pn_offset = 0;
*pn_length = 0;
} else {
/* Reserve two bytes for payload length */
bytes[length++] = 0;
bytes[length++] = 0;
/* Encode the sequence number */
*pn_offset = length;
*pn_length = 4;
picoformat_32(&bytes[length], (uint32_t) sequence_number);
length += 4;
}
}
return length;
}
size_t picoquic_predict_packet_header_length(
picoquic_cnx_t* cnx,
picoquic_packet_type_enum packet_type)
{
uint32_t header_length = 0;
/* The only purpose of the test below is to appease the static analyzer, so it
* wont complain of possible NULL deref. On windows we could use "__assume(cnx != NULL)
* but the documentation does not say anything about that for GCC and CLANG */
if (cnx == NULL) {
return 0;
}
if (packet_type == picoquic_packet_1rtt_protected) {
/* Predict acceptable length of packet number */
uint8_t pn_l = 4;
int64_t delta = cnx->pkt_ctx[picoquic_packet_context_application].send_sequence;
if (cnx->pkt_ctx[picoquic_packet_context_application].retransmit_oldest != NULL) {
delta -= cnx->pkt_ctx[picoquic_packet_context_application].retransmit_oldest->sequence_number;
}
if (delta < 262144) {
pn_l = 3;
if (cnx->pkt_ctx[picoquic_packet_context_application].send_sequence < 1024) {
pn_l = 2;
if (cnx->pkt_ctx[picoquic_packet_context_application].send_sequence < 16) {
pn_l = 1;
}
}
}
/* Compute length of a short packet header */
header_length = 1 + cnx->path[0]->remote_cnxid.id_len + pn_l;
}
else {
/* Compute length of a long packet header */
header_length = 1 + /* version */ 4 + /* cnx_id length bytes */ 2;
/* add dest-id length */
if (cnx->client_mode && (packet_type == picoquic_packet_initial ||
packet_type == picoquic_packet_0rtt_protected)
&& picoquic_is_connection_id_null(&cnx->path[0]->remote_cnxid)) {
header_length += cnx->initial_cnxid.id_len;
}
else {
header_length += cnx->path[0]->remote_cnxid.id_len;
}
/* add srce-id length */
header_length += cnx->path[0]->p_local_cnxid->cnx_id.id_len;
/* add length of payload length and packet number */
header_length += 2 + 4;
/* add length of tokens for initial packets */
if (packet_type == picoquic_packet_initial) {
uint8_t useless[16];
header_length += (uint32_t)picoquic_varint_encode(useless, 16, cnx->retry_token_length);
header_length += (uint32_t)cnx->retry_token_length;
}
}
return header_length;
}
/*
* Management of packet protection
*/
size_t picoquic_get_checksum_length(picoquic_cnx_t* cnx, picoquic_epoch_enum epoch)
{
size_t ret = 16;
if (cnx->crypto_context[epoch].aead_encrypt != NULL) {
ret = picoquic_aead_get_checksum_length(cnx->crypto_context[epoch].aead_encrypt);
}
else {
DBG_PRINTF("Try getting checksum for empty context, epoch %d", epoch);
}
return ret;
}
static size_t picoquic_protect_packet(picoquic_cnx_t* cnx,
picoquic_packet_type_enum ptype,
uint8_t * bytes,
uint64_t sequence_number,
picoquic_connection_id_t * remote_cnxid,
picoquic_connection_id_t * local_cnxid,
size_t length, size_t header_length,
uint8_t* send_buffer, size_t send_buffer_max,
void * aead_context, void* pn_enc,
picoquic_path_t* path_x, uint64_t current_time)
{
size_t send_length;
size_t h_length;
size_t pn_offset = 0;
size_t sample_offset = 0;
size_t pn_length = 0;
size_t aead_checksum_length = picoquic_aead_get_checksum_length(aead_context);
uint8_t first_mask = 0x0F;
/* Create the packet header just before encrypting the content */
h_length = picoquic_create_packet_header(cnx, ptype,
sequence_number, remote_cnxid, local_cnxid, header_length, send_buffer, &pn_offset, &pn_length);
if (ptype == picoquic_packet_1rtt_protected) {
if (remote_cnxid != &path_x->remote_cnxid) {
/* Packet is sent to a different CID: reset the spin bit and loss bit Q to 0 */
send_buffer[0] &= 0xDF;
path_x->q_square = 0;
}
if (cnx->is_loss_bit_enabled_outgoing) {
first_mask = 0x07;
path_x->q_square++;
if ((path_x->q_square & PICOQUIC_LOSS_BIT_Q_HALF_PERIOD) != 0) {
send_buffer[0] |= 0x10;
}
if (path_x->nb_losses_found > path_x->nb_losses_reported) {
send_buffer[0] |= 0x08;
path_x->nb_losses_reported++;
}
}
else {
first_mask = 0x1F;
}
}
/* Make sure that the payload length is encoded in the header */
/* Using encryption, the "payload" length also includes the encrypted packet length */
picoquic_update_payload_length(send_buffer, pn_offset, h_length - pn_length, length + aead_checksum_length);
/* If fuzzing is required, apply it*/
if (cnx->quic->fuzz_fn != NULL) {
if (h_length == header_length) {
memcpy(bytes, send_buffer, header_length);
}
length = cnx->quic->fuzz_fn(cnx->quic->fuzz_ctx, cnx, bytes,
send_buffer_max - aead_checksum_length, length, header_length);
if (h_length == header_length) {
memcpy(send_buffer, bytes, header_length);
}
}
/* Encrypt the packet */
send_length = picoquic_aead_encrypt_generic(send_buffer + /* header_length */ h_length,
bytes + header_length, length - header_length,
sequence_number, send_buffer, /* header_length */ h_length, aead_context);
send_length += /* header_length */ h_length;
/* if needed, log the segment before header protection is applied */
picoquic_log_outgoing_packet(cnx,
bytes, sequence_number, pn_length, length,
send_buffer, send_length, current_time);
/* Next, encrypt the PN -- The sample is located after the pn_offset */
sample_offset = /* header_length */ pn_offset + 4;
if (pn_offset < sample_offset)
{
/* This is always true, as use pn_length = 4 */
uint8_t mask_bytes[5] = { 0, 0, 0, 0, 0 };
uint8_t pn_l;
picoquic_pn_encrypt(pn_enc, send_buffer + sample_offset, mask_bytes, mask_bytes, 5);
/* Encode the first byte */
pn_l = (send_buffer[0] & 3) + 1;
send_buffer[0] ^= (mask_bytes[0] & first_mask);
/* Packet encoding is 1 to 4 bytes */
for (uint8_t i = 0; i < pn_l; i++) {
send_buffer[pn_offset+i] ^= mask_bytes[i+1];
}
}
return send_length;
}
/* Update the leaky bucket used for pacing.
*/
static void picoquic_update_pacing_bucket(picoquic_path_t * path_x, uint64_t current_time)
{
if (path_x->pacing_bucket_nanosec < -path_x->pacing_packet_time_nanosec) {
path_x->pacing_bucket_nanosec = -path_x->pacing_packet_time_nanosec;
}
if (current_time > path_x->pacing_evaluation_time) {
path_x->pacing_bucket_nanosec += (current_time - path_x->pacing_evaluation_time) * 1000;
path_x->pacing_evaluation_time = current_time;
if (path_x->pacing_bucket_nanosec > path_x->pacing_bucket_max) {
path_x->pacing_bucket_nanosec = path_x->pacing_bucket_max;
}
}
}
/*
* Check pacing to see whether the next transmission is authorized.
* If if is not, update the next wait time to reflect pacing.
*
* In packet train mode, the wait will last until the bucket is completely full, or
* if at least N packets are received.
*/
int picoquic_is_sending_authorized_by_pacing(picoquic_cnx_t * cnx, picoquic_path_t * path_x, uint64_t current_time, uint64_t * next_time)
{
int ret = 1;
picoquic_update_pacing_bucket(path_x, current_time);
if (path_x->pacing_bucket_nanosec < path_x->pacing_packet_time_nanosec) {
uint64_t next_pacing_time;
int64_t bucket_required;
if (cnx->quic->packet_train_mode) {
bucket_required = path_x->pacing_bucket_max;
if (bucket_required > 10 * path_x->pacing_packet_time_nanosec) {
bucket_required = 10 * path_x->pacing_packet_time_nanosec;
}
bucket_required -= path_x->pacing_bucket_nanosec;
}
else {
bucket_required = path_x->pacing_packet_time_nanosec - path_x->pacing_bucket_nanosec;
}
next_pacing_time = current_time + 1 + bucket_required / 1000;
if (next_pacing_time < *next_time) {
*next_time = next_pacing_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
ret = 0;
}
return ret;
}
/* Reset the pacing data after recomputing the pacing rate
*/
void picoquic_update_pacing_rate(picoquic_cnx_t * cnx, picoquic_path_t* path_x, double pacing_rate, uint64_t quantum)
{
double packet_time = (double)path_x->send_mtu / pacing_rate;
double quantum_time = (double)quantum / pacing_rate;
uint64_t rtt_nanosec = path_x->smoothed_rtt * 1000;
path_x->pacing_rate = (uint64_t)pacing_rate;
path_x->pacing_packet_time_nanosec = (uint64_t)(packet_time * 1000000000.0);
if (path_x->pacing_packet_time_nanosec <= 0) {
path_x->pacing_packet_time_nanosec = 1;
path_x->pacing_packet_time_microsec = 1;
}
else {
if ((uint64_t)path_x->pacing_packet_time_nanosec > rtt_nanosec) {
path_x->pacing_packet_time_nanosec = rtt_nanosec;
}
path_x->pacing_packet_time_microsec = (path_x->pacing_packet_time_nanosec + 999ull) / 1000;
}
path_x->pacing_bucket_max = (uint64_t)(quantum_time * 1000000000.0);
if (path_x->pacing_bucket_max <= 0) {
path_x->pacing_bucket_max = 16 * path_x->pacing_packet_time_nanosec;
}
if (path_x->pacing_bucket_nanosec > path_x->pacing_bucket_max) {
path_x->pacing_bucket_nanosec = path_x->pacing_bucket_max;
}
if (cnx->is_pacing_update_requested && path_x == cnx->path[0] &&
cnx->callback_fn != NULL) {
if ((path_x->pacing_rate > cnx->pacing_rate_signalled &&
(path_x->pacing_rate - cnx->pacing_rate_signalled >= cnx->pacing_increase_threshold)) ||
(path_x->pacing_rate < cnx->pacing_rate_signalled &&
(cnx->pacing_rate_signalled - path_x->pacing_rate > cnx->pacing_decrease_threshold))){
(void)cnx->callback_fn(cnx, path_x->pacing_rate, NULL, 0, picoquic_callback_pacing_changed, cnx->callback_ctx, NULL);
cnx->pacing_rate_signalled = path_x->pacing_rate;
}
}
}
/*
* Reset the pacing data after CWIN is updated.
* The max bucket is set to contain at least 2 packets more than 1/8th of the congestion window.
*/
void picoquic_update_pacing_data(picoquic_cnx_t* cnx, picoquic_path_t * path_x, int slow_start)
{
uint64_t rtt_nanosec = path_x->smoothed_rtt * 1000;
if ((path_x->cwin < ((uint64_t)path_x->send_mtu) * 8) || rtt_nanosec <= 1000) {
/* Small windows, should only relie on ACK clocking */
path_x->pacing_bucket_max = rtt_nanosec;
path_x->pacing_packet_time_nanosec = 1;
path_x->pacing_packet_time_microsec = 1;
if (path_x->pacing_bucket_nanosec > path_x->pacing_bucket_max) {
path_x->pacing_bucket_nanosec = path_x->pacing_bucket_max;
}
}
else {
double pacing_rate = ((double)path_x->cwin / (double)rtt_nanosec) * 1000000000.0;
uint64_t quantum = path_x->cwin / 4;
if (quantum < 2ull * path_x->send_mtu) {
quantum = 2ull * path_x->send_mtu;
}
else {
if (slow_start && path_x->smoothed_rtt > 4*PICOQUIC_MAX_BANDWIDTH_TIME_INTERVAL_MAX) {
const uint64_t quantum_min = 0x8000;
if (quantum < quantum_min){
quantum = quantum_min;
}
else {
uint64_t quantum2 = (uint64_t)((pacing_rate * PICOQUIC_MAX_BANDWIDTH_TIME_INTERVAL_MAX) / 1000000.0);
if (quantum2 > quantum_min) {
quantum = quantum2;
}
}
}
else if (quantum > 16ull * path_x->send_mtu) {
quantum = 16ull * path_x->send_mtu;
}
}
if (slow_start) {
pacing_rate *= 1.25;
}
picoquic_update_pacing_rate(cnx, path_x, pacing_rate, quantum);
}
}
/*
* Update the pacing data after sending a packet.
*/
void picoquic_update_pacing_after_send(picoquic_path_t * path_x, uint64_t current_time)
{
picoquic_update_pacing_bucket(path_x, current_time);
path_x->pacing_bucket_nanosec -= path_x->pacing_packet_time_nanosec;
}
/*
* Final steps in packet transmission: queue for retransmission, etc
*/
void picoquic_queue_for_retransmit(picoquic_cnx_t* cnx, picoquic_path_t * path_x, picoquic_packet_t* packet,
size_t length, uint64_t current_time)
{
picoquic_packet_context_enum pc = packet->pc;
/* Manage the double linked packet list for retransmissions */
packet->previous_packet = NULL;
if (cnx->pkt_ctx[pc].retransmit_newest == NULL) {
packet->next_packet = NULL;
cnx->pkt_ctx[pc].retransmit_oldest = packet;
} else {
packet->next_packet = cnx->pkt_ctx[pc].retransmit_newest;
packet->next_packet->previous_packet = packet;
}
cnx->pkt_ctx[pc].retransmit_newest = packet;
if (!packet->is_ack_trap) {
/* Account for bytes in transit, for congestion control */
path_x->bytes_in_transit += length;
/* Update the pacing data */
picoquic_update_pacing_after_send(path_x, current_time);
}
}
picoquic_packet_t* picoquic_dequeue_retransmit_packet(picoquic_cnx_t* cnx, picoquic_packet_t* p, int should_free)
{
size_t dequeued_length = p->length + p->checksum_overhead;
picoquic_packet_context_enum pc = p->pc;
if (p->previous_packet == NULL) {
cnx->pkt_ctx[pc].retransmit_newest = p->next_packet;
}
else {
p->previous_packet->next_packet = p->next_packet;
}
if (p->next_packet == NULL) {
cnx->pkt_ctx[pc].retransmit_oldest = p->previous_packet;
}
else {
#ifdef _DEBUG
if (p->next_packet->pc != pc) {
DBG_PRINTF("Inconsistent PC in queue, %d vs %d\n", p->next_packet->pc, pc);
}
if (p->next_packet->previous_packet != p) {
DBG_PRINTF("Inconsistent chain of packets, pc = %d\n", pc);
}
#endif
p->next_packet->previous_packet = p->previous_packet;
}
/* Account for bytes in transit, for congestion control */
if (p->send_path != NULL && !p->is_ack_trap) {
if (p->send_path->bytes_in_transit > dequeued_length) {
p->send_path->bytes_in_transit -= dequeued_length;
}
else {
p->send_path->bytes_in_transit = 0;
}
}
if (should_free || p->is_ack_trap) {
picoquic_recycle_packet(cnx->quic, p);
p = NULL;
}
else {
p->next_packet = NULL;
/* add this packet to the retransmitted list */
if (cnx->pkt_ctx[pc].retransmitted_oldest == NULL) {
cnx->pkt_ctx[pc].retransmitted_newest = p;
cnx->pkt_ctx[pc].retransmitted_oldest = p;
p->previous_packet = NULL;
}
else {
cnx->pkt_ctx[pc].retransmitted_newest->next_packet = p;
p->previous_packet = cnx->pkt_ctx[pc].retransmitted_newest;
cnx->pkt_ctx[pc].retransmitted_newest = p;
}
}
return p;
}
void picoquic_dequeue_retransmitted_packet(picoquic_cnx_t* cnx, picoquic_packet_t* p)
{
picoquic_packet_context_enum pc = p->pc;
if (p->next_packet == NULL) {
cnx->pkt_ctx[pc].retransmitted_newest = p->previous_packet;
}
else {
p->next_packet->previous_packet = p->previous_packet;
}
if (p->previous_packet == NULL) {
cnx->pkt_ctx[pc].retransmitted_oldest = p->next_packet;
}
else {
#ifdef _DEBUG
if (p->previous_packet->pc != pc) {
DBG_PRINTF("Inconsistent PC in queue, %d vs %d\n", p->previous_packet->pc, pc);
}
if (p->previous_packet->next_packet != p) {
DBG_PRINTF("Inconsistent chain of packets, pc = %d\n", pc);
}
#endif
p->previous_packet->next_packet = p->next_packet;
}
picoquic_recycle_packet(cnx->quic, p);
}
/*
* Inserting holes in the send sequence to trap optimistic ack.
* return 0 if hole was inserted, !0 if packet should be freed.
*/
void picoquic_insert_hole_in_send_sequence_if_needed(picoquic_cnx_t* cnx, uint64_t current_time, uint64_t * next_wake_time)
{
if (cnx->quic->sequence_hole_pseudo_period == 0) {
/* Holing disabled. Set to max value, never worry about it later */
cnx->pkt_ctx[0].next_sequence_hole = (uint64_t)((int64_t)-1);
} else if (cnx->cnx_state == picoquic_state_ready &&
cnx->pkt_ctx[0].retransmit_newest != NULL &&
cnx->pkt_ctx[0].send_sequence >= cnx->pkt_ctx[0].next_sequence_hole) {
if (cnx->pkt_ctx[0].next_sequence_hole != 0 &&
!cnx->pkt_ctx[0].retransmit_newest->is_ack_trap) {
/* Insert a hole in sequence */
picoquic_packet_t* packet = picoquic_create_packet(cnx->quic);
if (packet != NULL) {
packet->is_ack_trap = 1;
packet->pc = picoquic_packet_context_application;
packet->ptype = picoquic_packet_1rtt_protected;
packet->send_path = cnx->path[0];
packet->send_time = current_time;
packet->sequence_number = cnx->pkt_ctx[picoquic_packet_context_application].send_sequence++;
picoquic_queue_for_retransmit(cnx, cnx->path[0], packet, 0, current_time);
*next_wake_time = current_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
/* Simulate local loss on the Q bit square function. */
cnx->path[0]->q_square++;
}
}
/* Predict the next hole*/
cnx->pkt_ctx[0].next_sequence_hole = cnx->pkt_ctx[picoquic_packet_context_application].send_sequence + 3 + picoquic_public_uniform_random(cnx->quic->sequence_hole_pseudo_period);
}
}
/*
* Final steps of encoding and protecting the packet before sending
*/
void picoquic_finalize_and_protect_packet(picoquic_cnx_t *cnx, picoquic_packet_t * packet, int ret,
size_t length, size_t header_length, size_t checksum_overhead,
size_t * send_length, uint8_t * send_buffer, size_t send_buffer_max,
picoquic_connection_id_t * remote_cnxid,
picoquic_connection_id_t * local_cnxid,
picoquic_path_t * path_x, uint64_t current_time)
{
if (length != 0 && length < header_length) {
length = 0;
}
if (ret == 0 && length > 0) {
packet->length = length;
cnx->pkt_ctx[packet->pc].send_sequence++;
path_x->latest_sent_time = current_time;
path_x->path_cid_rotated = 0;
packet->delivered_prior = path_x->delivered_last;
packet->delivered_time_prior = path_x->delivered_time_last;
packet->delivered_sent_prior = path_x->delivered_sent_last;
packet->delivered_app_limited = (cnx->cnx_state < picoquic_state_ready || path_x->delivered_limited_index != 0);
switch (packet->ptype) {
case picoquic_packet_version_negotiation:
/* Packet is not encrypted */
break;
case picoquic_packet_initial:
length = picoquic_protect_packet(cnx, packet->ptype, packet->bytes, packet->sequence_number,
remote_cnxid, local_cnxid,
length, header_length,
send_buffer, send_buffer_max, cnx->crypto_context[picoquic_epoch_initial].aead_encrypt, cnx->crypto_context[picoquic_epoch_initial].pn_enc,
path_x, current_time);
break;
case picoquic_packet_handshake:
length = picoquic_protect_packet(cnx, packet->ptype, packet->bytes, packet->sequence_number,
remote_cnxid, local_cnxid,
length, header_length,
send_buffer, send_buffer_max, cnx->crypto_context[picoquic_epoch_handshake].aead_encrypt, cnx->crypto_context[picoquic_epoch_handshake].pn_enc,
path_x, current_time);
break;
case picoquic_packet_retry:
length = picoquic_protect_packet(cnx, packet->ptype, packet->bytes, packet->sequence_number,
remote_cnxid, local_cnxid,
length, header_length,
send_buffer, send_buffer_max, cnx->crypto_context[picoquic_epoch_0rtt].aead_encrypt, cnx->crypto_context[picoquic_epoch_0rtt].pn_enc,
path_x, current_time);
break;
case picoquic_packet_0rtt_protected:
length = picoquic_protect_packet(cnx, packet->ptype, packet->bytes, packet->sequence_number,
remote_cnxid, local_cnxid,
length, header_length,
send_buffer, send_buffer_max, cnx->crypto_context[picoquic_epoch_0rtt].aead_encrypt, cnx->crypto_context[picoquic_epoch_0rtt].pn_enc,
path_x, current_time);
break;
case picoquic_packet_1rtt_protected:
length = picoquic_protect_packet(cnx, packet->ptype, packet->bytes, packet->sequence_number,
remote_cnxid, local_cnxid,
length, header_length,
send_buffer, send_buffer_max, cnx->crypto_context[picoquic_epoch_1rtt].aead_encrypt, cnx->crypto_context[picoquic_epoch_1rtt].pn_enc,
path_x, current_time);
break;
default:
/* Packet type error. Do nothing at all. */
length = 0;
break;
}
*send_length = length;
if (length > 0) {
packet->checksum_overhead = checksum_overhead;
picoquic_queue_for_retransmit(cnx, path_x, packet, length, current_time);
} else {
*send_length = 0;
}
}
else {
*send_length = 0;
}
}
/*
* If a retransmit is needed, fill the packet with the required
* retransmission. Also, prune the retransmit queue as needed.
*
* TODO: consider that the retransmit timer is per path, from the path on
* which the packet was first sent, but the retransmission may be on
* a different path, with different MTU.
*/
static uint64_t picoquic_current_retransmit_timer(picoquic_cnx_t* cnx, picoquic_packet_context_enum pc)
{
uint64_t rto = cnx->path[0]->retransmit_timer;
rto <<= cnx->pkt_ctx[pc].nb_retransmit;
if (cnx->cnx_state < picoquic_state_ready) {
if (rto > PICOQUIC_INITIAL_MAX_RETRANSMIT_TIMER) {
rto = PICOQUIC_INITIAL_MAX_RETRANSMIT_TIMER;
}
}
else if (rto > PICOQUIC_LARGE_RETRANSMIT_TIMER){
uint64_t alt_rto = PICOQUIC_LARGE_RETRANSMIT_TIMER;
if (cnx->path[0]->rtt_min > PICOQUIC_TARGET_SATELLITE_RTT) {
alt_rto = (cnx->path[0]->smoothed_rtt * 3) >> 1;
}
if (alt_rto < rto) {
rto = alt_rto;
}
}
return rto;
}
static int picoquic_retransmit_needed_by_packet(picoquic_cnx_t* cnx,
picoquic_packet_t* p, uint64_t current_time, uint64_t * next_retransmit_time, int* timer_based)
{
picoquic_packet_context_enum pc = p->pc;
uint64_t retransmit_time;
int64_t delta_seq = cnx->pkt_ctx[pc].highest_acknowledged - p->sequence_number;
int should_retransmit = 0;
int is_timer_based = 0;
if (delta_seq > 0) {
/* By default, we use an RTO */
retransmit_time = p->send_time + cnx->path[0]->retransmit_timer;
/* RACK logic works best when the amount of reordering is not too large */
if (delta_seq < 3) {
/* When just a few ulterior packets are acknowledged, we work from the right edge. */
uint64_t rack_timer_min = cnx->pkt_ctx[pc].highest_acknowledged_time +
cnx->remote_parameters.max_ack_delay + (cnx->path[0]->smoothed_rtt >> 2);
if (retransmit_time > rack_timer_min) {
retransmit_time = rack_timer_min;
}
} else {
/* When enough ulterior packets are acknowledged, we work from the right edge. */
uint64_t rack_timer_min = p->send_time + (cnx->path[0]->smoothed_rtt >> 2);
if (rack_timer_min < cnx->pkt_ctx[pc].latest_time_acknowledged) {
retransmit_time = cnx->pkt_ctx[pc].highest_acknowledged_time;
}
}
}
else
{
/* There has not been any higher packet acknowledged, thus we fall back on timer logic. */
retransmit_time = p->send_time + picoquic_current_retransmit_timer(cnx, pc);
is_timer_based = 1;
}
if (p->ptype == picoquic_packet_0rtt_protected) {
/* Special case for 0RTT packets */
if (cnx->cnx_state != picoquic_state_ready &&
cnx->cnx_state != picoquic_state_client_ready_start) {
/* Set the retransmit time ahead of current time since the connection is not ready */
retransmit_time = current_time + cnx->path[0]->smoothed_rtt + PICOQUIC_RACK_DELAY;
} else if (!cnx->zero_rtt_data_accepted) {
/* Zero RTT data was not accepted by the peer, the packets are considered lost */
retransmit_time = current_time;
}
}
if (current_time >= retransmit_time || (p->is_ack_trap && delta_seq > 0)) {
should_retransmit = 1;
*timer_based = is_timer_based;
if (cnx->quic->sequence_hole_pseudo_period != 0 && pc == picoquic_packet_context_application && !p->is_ack_trap) {
DBG_PRINTF("Retransmit #%d, delta=%d, timer=%d, time=%d, sent: %d, ack_t: %d, s_rtt: %d, rt: %d",
(int)p->sequence_number, (int)delta_seq, is_timer_based, (int)current_time, (int)p->send_time,
(int)cnx->pkt_ctx[pc].latest_time_acknowledged, (int)cnx->path[0]->smoothed_rtt, (int) retransmit_time);
}
}
*next_retransmit_time = retransmit_time;
return should_retransmit;
}
int picoquic_queue_stream_frame_for_retransmit(picoquic_cnx_t* cnx, uint8_t * bytes, size_t length)
{
int ret = 0;
picoquic_misc_frame_header_t* misc = picoquic_create_misc_frame(bytes, length, 0);
if (misc == NULL) {
ret = PICOQUIC_ERROR_MEMORY;
}
else {
misc->next_misc_frame = NULL;
if (cnx->stream_frame_retransmit_queue_last == NULL) {
cnx->stream_frame_retransmit_queue = misc;
cnx->stream_frame_retransmit_queue_last = misc;
}
else {
misc->previous_misc_frame = cnx->stream_frame_retransmit_queue_last;
cnx->stream_frame_retransmit_queue_last->next_misc_frame = misc;
cnx->stream_frame_retransmit_queue_last = misc;
}
}
return ret;
}
int picoquic_copy_before_retransmit(picoquic_packet_t * old_p,
picoquic_cnx_t * cnx,
uint8_t * new_bytes,
size_t send_buffer_max_minus_checksum,
int * packet_is_pure_ack,
int * do_not_detect_spurious,
size_t * length)
{
/* check if this is an ACK only packet */
int ret = 0;
int frame_is_pure_ack = 0;
size_t frame_length = 0;
size_t byte_index = 0; /* Used when parsing the old packet */
if (old_p->is_mtu_probe) {
if (old_p->send_path != NULL) {
/* MTU probe was lost, presumably because of packet too big */
old_p->send_path->mtu_probe_sent = 0;
old_p->send_path->send_mtu_max_tried = old_p->length + old_p->checksum_overhead;
}
/* MTU probes should not be retransmitted */
*packet_is_pure_ack = 1;
*do_not_detect_spurious = 0;
}
else if (old_p->is_ack_trap) {
*packet_is_pure_ack = 1;
*do_not_detect_spurious = 0;
}
else {
/* Copy the relevant bytes from one packet to the next */
byte_index = old_p->offset;
while (ret == 0 && byte_index < old_p->length) {
ret = picoquic_skip_frame(&old_p->bytes[byte_index],
old_p->length - byte_index, &frame_length, &frame_is_pure_ack);
/* Check whether the data was already acked, which may happen in
* case of spurious retransmissions */
if (ret == 0 && frame_is_pure_ack == 0) {
ret = picoquic_check_frame_needs_repeat(cnx, &old_p->bytes[byte_index],
frame_length, &frame_is_pure_ack);
}
/* Prepare retransmission if needed */
if (ret == 0 && !frame_is_pure_ack) {
if (PICOQUIC_IN_RANGE(old_p->bytes[byte_index], picoquic_frame_type_stream_range_min, picoquic_frame_type_stream_range_max)) {
ret = picoquic_queue_stream_frame_for_retransmit(cnx, &old_p->bytes[byte_index], frame_length);
}
else {
if (frame_length > send_buffer_max_minus_checksum - *length &&
(old_p->ptype == picoquic_packet_0rtt_protected || old_p->ptype == picoquic_packet_1rtt_protected)) {
ret = picoquic_queue_misc_frame(cnx, &old_p->bytes[byte_index], frame_length, 0);
}
else {
memcpy(&new_bytes[*length], &old_p->bytes[byte_index], frame_length);
*length += frame_length;
}
}
*packet_is_pure_ack = 0;
}
byte_index += frame_length;
}
}
return ret;
}
int picoquic_retransmit_needed(picoquic_cnx_t* cnx,
picoquic_packet_context_enum pc,
picoquic_path_t * path_x, uint64_t current_time, uint64_t * next_wake_time,
picoquic_packet_t* packet, size_t send_buffer_max, size_t* header_length)
{
picoquic_packet_t* old_p = cnx->pkt_ctx[pc].retransmit_oldest;
size_t length = 0;
/* TODO: while packets are pure ACK, drop them from retransmit queue */
while (old_p != NULL) {
picoquic_path_t * old_path = old_p->send_path; /* should be the path on which the packet was transmitted */
int should_retransmit = 0;
int timer_based_retransmit = 0;
uint64_t next_retransmit_time = *next_wake_time;
uint64_t lost_packet_number = old_p->sequence_number;
picoquic_packet_t* p_next = old_p->previous_packet;
uint8_t * new_bytes = packet->bytes;
int ret = 0;
length = 0;
/* Get the packet type */
should_retransmit = cnx->initial_repeat_needed ||
picoquic_retransmit_needed_by_packet(cnx, old_p, current_time, &next_retransmit_time, &timer_based_retransmit);
if (should_retransmit == 0) {
/*
* Always retransmit in order. If not this one, then nothing.
* But make an exception for 0-RTT packets.
*/
if (old_p->ptype == picoquic_packet_0rtt_protected) {
old_p = p_next;
continue;
}
else {
if (next_retransmit_time < *next_wake_time) {
*next_wake_time = next_retransmit_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
break;
}
} else if (old_p->is_ack_trap){
picoquic_dequeue_retransmit_packet(cnx, old_p, 1);
old_p = p_next;
continue;
} else {
/* check if this is an ACK only packet */
int packet_is_pure_ack = 1;
int do_not_detect_spurious = 1;
int frame_is_pure_ack = 0;
uint8_t* old_bytes = old_p->bytes;
size_t frame_length = 0;
size_t byte_index = 0; /* Used when parsing the old packet */
size_t checksum_length = 0;
/* we'll report it where it got lost */
if (old_path) {
old_path->retrans_count++;
}
*header_length = 0;
if (old_p->ptype == picoquic_packet_0rtt_protected) {
/* Only retransmit as 0-RTT if contains crypto data */
int contains_crypto = 0;
byte_index = old_p->offset;
if (old_p->is_evaluated == 0) {
while (ret == 0 && byte_index < old_p->length) {
if (old_bytes[byte_index] == picoquic_frame_type_crypto_hs) {
contains_crypto = 1;
packet_is_pure_ack = 0;
break;
}
ret = picoquic_skip_frame(&old_p->bytes[byte_index],
old_p->length - byte_index, &frame_length, &frame_is_pure_ack);
byte_index += frame_length;
}
old_p->contains_crypto = contains_crypto;
old_p->is_pure_ack = packet_is_pure_ack;
old_p->is_evaluated = 1;
} else {
contains_crypto = old_p->contains_crypto;
packet_is_pure_ack = old_p->is_pure_ack;
}
if (contains_crypto) {
length = picoquic_predict_packet_header_length(cnx, picoquic_packet_0rtt_protected);
packet->ptype = picoquic_packet_0rtt_protected;
packet->offset = length;
} else if (cnx->cnx_state < picoquic_state_client_ready_start) {
should_retransmit = 0;
} else {
length = picoquic_predict_packet_header_length(cnx, picoquic_packet_1rtt_protected);
packet->ptype = picoquic_packet_1rtt_protected;
packet->offset = length;
}
} else {
length = picoquic_predict_packet_header_length(cnx, old_p->ptype);
packet->ptype = old_p->ptype;
packet->offset = length;
}
if (should_retransmit != 0) {
packet->sequence_number = cnx->pkt_ctx[pc].send_sequence;
packet->send_path = path_x;
packet->pc = pc;
*header_length = length;
switch (packet->ptype) {
case picoquic_packet_1rtt_protected:
checksum_length = picoquic_get_checksum_length(cnx, picoquic_epoch_1rtt);
break;
case picoquic_packet_initial:
checksum_length = picoquic_get_checksum_length(cnx, picoquic_epoch_initial);
break;
case picoquic_packet_handshake:
checksum_length = picoquic_get_checksum_length(cnx, picoquic_epoch_handshake);
break;
case picoquic_packet_0rtt_protected:
checksum_length = picoquic_get_checksum_length(cnx, picoquic_epoch_0rtt);
break;
default:
DBG_PRINTF("Trying to retransmit packet type %d", old_p->ptype);
checksum_length = 0;
break;
}
ret = picoquic_copy_before_retransmit(old_p, cnx,
new_bytes,
send_buffer_max - checksum_length,
&packet_is_pure_ack,
&do_not_detect_spurious,
&length);
if (ret != 0) {
DBG_PRINTF("Copy before retransmit returns %d\n", ret);
}
/* Update the number of bytes in transit and remove old packet from queue */
/* If not pure ack, the packet will be placed in the "retransmitted" queue,
* in order to enable detection of spurious restransmissions */
picoquic_log_packet_lost(cnx, old_p->ptype, old_p->sequence_number,
(timer_based_retransmit == 0) ? "repeat" : "timer",
(old_p->send_path == NULL) ? NULL : &old_p->send_path->remote_cnxid,
old_p->length, current_time);
old_p = picoquic_dequeue_retransmit_packet(cnx, old_p, packet_is_pure_ack & do_not_detect_spurious);
/* If we have a good packet, return it */
if (old_p == NULL || packet_is_pure_ack) {
length = 0;
} else {
if (old_p->send_path != NULL &&
(old_p->length + old_p->checksum_overhead) == old_p->send_path->send_mtu) {
old_p->send_path->nb_mtu_losses++;
if (old_p->send_path->nb_mtu_losses > PICOQUIC_MTU_LOSS_THRESHOLD) {
picoquic_reset_path_mtu(old_p->send_path);
picoquic_log_app_message(cnx,
"Reset path MTU after %d retransmissions, %d MTU losses",
cnx->pkt_ctx[pc].nb_retransmit,
old_p->send_path->nb_mtu_losses);
}
}
if (timer_based_retransmit != 0) {
if (cnx->pkt_ctx[pc].nb_retransmit > 7 && cnx->cnx_state >= picoquic_state_ready) {
/*
* Max retransmission count was exceeded. Disconnect.
*/
DBG_PRINTF("Too many retransmits of packet number %d, disconnect", (int)old_p->sequence_number);
cnx->cnx_state = picoquic_state_disconnected;
if (cnx->callback_fn) {
(void)(cnx->callback_fn)(cnx, 0, NULL, 0, picoquic_callback_close, cnx->callback_ctx, NULL);
}
length = 0;
break;
} else {
if (cnx->pkt_ctx[pc].nb_retransmit == 0 ||
old_p->sequence_number >= cnx->pkt_ctx[pc].retransmit_sequence) {
cnx->pkt_ctx[pc].nb_retransmit++;
cnx->pkt_ctx[pc].latest_retransmit_time = current_time;
cnx->pkt_ctx[pc].retransmit_sequence = packet->sequence_number;
}
}
}
if (old_p->ptype < picoquic_packet_1rtt_protected) {
DBG_PRINTF("Retransmit packet type %d, pc=%d, seq = %llx, is_client = %d\n",
old_p->ptype, old_p->pc,
(unsigned long long)old_p->sequence_number, cnx->client_mode);
}
/* special case for the client initial */
if (old_p->ptype == picoquic_packet_initial && cnx->client_mode) {
length = picoquic_pad_to_target_length(new_bytes, length, send_buffer_max - checksum_length);
}
packet->length = length;
cnx->nb_retransmission_total++;
if (old_path != NULL) {
old_path->nb_losses_found++;
old_path->total_bytes_lost += old_p->length;
if (cnx->congestion_alg != NULL && cnx->cnx_state >= picoquic_state_ready) {
cnx->congestion_alg->alg_notify(cnx, old_path,
(timer_based_retransmit == 0) ? picoquic_congestion_notification_repeat : picoquic_congestion_notification_timeout,
0, 0, 0, lost_packet_number, current_time);
}
}
if (length <= packet->offset) {
length = 0;
if (!packet_is_pure_ack) {
/* Pace down the next retransmission so as to not pile up error upon error */
path_x->pacing_bucket_nanosec -= path_x->pacing_packet_time_nanosec;
}
}
else {
break;
}
}
}
}
/*
* If the loop is continuing, this means that we need to look
* at the next candidate packet.
*/
old_p = p_next;
}
return (int)length;
}
/*
* Returns true if there is nothing to repeat in the retransmission queue
*/
int picoquic_is_cnx_backlog_empty(picoquic_cnx_t* cnx)
{
int backlog_empty = 1;
for (picoquic_packet_context_enum pc = 0;
backlog_empty == 1 && pc < picoquic_nb_packet_context; pc++)
{
picoquic_packet_t* p = cnx->pkt_ctx[pc].retransmit_oldest;
if ((cnx->cnx_state == picoquic_state_ready) && (pc != picoquic_packet_context_application)) {
continue;
}
while (p != NULL && backlog_empty == 1) {
/* check if this is an ACK only packet */
int ret = 0;
int frame_is_pure_ack = 0;
size_t frame_length = 0;
size_t byte_index = 0; /* Used when parsing the old packet */
byte_index = p->offset;
while (ret == 0 && byte_index < p->length) {
ret = picoquic_skip_frame(&p->bytes[byte_index],
p->length - p->offset, &frame_length, &frame_is_pure_ack);
if (!frame_is_pure_ack) {
backlog_empty = 0;
break;
}
byte_index += frame_length;
}
p = p->previous_packet;
}
}
return backlog_empty;
}
/* Decide whether MAX data need to be sent or not */
int picoquic_should_send_max_data(picoquic_cnx_t* cnx)
{
int ret = 0;
if (2 * cnx->data_received > cnx->maxdata_local)
ret = 1;
return ret;
}
/* Compute the next logical probe length */
static size_t picoquic_next_mtu_probe_length(picoquic_cnx_t* cnx, picoquic_path_t * path_x)
{
size_t probe_length;
if (path_x->send_mtu_max_tried == 0) {
if (cnx->remote_parameters.max_packet_size > 0) {
probe_length = cnx->remote_parameters.max_packet_size;
if (cnx->quic->mtu_max > 0 && (int)probe_length > cnx->quic->mtu_max) {
probe_length = cnx->quic->mtu_max;
}
else if (probe_length > PICOQUIC_MAX_PACKET_SIZE) {
probe_length = PICOQUIC_MAX_PACKET_SIZE;
}
if (probe_length < path_x->send_mtu) {
probe_length = path_x->send_mtu;
}
}
else if (cnx->quic->mtu_max > 0) {
probe_length = cnx->quic->mtu_max;
}
else {
probe_length = PICOQUIC_PRACTICAL_MAX_MTU;
}
}
else {
if (path_x->send_mtu_max_tried > 1500) {
probe_length = 1500;
}
else if (path_x->send_mtu_max_tried > 1400) {
probe_length = 1400;
}
else {
probe_length = (path_x->send_mtu + path_x->send_mtu_max_tried) / 2;
}
}
return probe_length;
}
/* Decide whether to send an MTU probe */
picoquic_pmtu_discovery_status_enum picoquic_is_mtu_probe_needed(picoquic_cnx_t* cnx, picoquic_path_t * path_x)
{
int ret = picoquic_pmtu_discovery_not_needed;
if ((cnx->cnx_state == picoquic_state_ready || cnx->cnx_state == picoquic_state_client_ready_start || cnx->cnx_state == picoquic_state_server_false_start)
&& path_x->mtu_probe_sent == 0) {
if (path_x->send_mtu_max_tried == 0 || path_x->send_mtu_max_tried > 1400) {
/* MTU discovery is required if the chances of success are large enough
* and there are enough packets to send to amortize the discovery cost.
* Of course we don't know at this stage how much data will be sent
* on the connection; we take the amount of data queued as a proxy
* for that. */
uint64_t next_probe = picoquic_next_mtu_probe_length(cnx, path_x);
if (next_probe > path_x->send_mtu) {
if (cnx->is_pmtud_required) {
ret = picoquic_pmtu_discovery_required;
}
else {
uint64_t packets_to_send_before = cnx->nb_bytes_queued / path_x->send_mtu;
uint64_t packets_to_send_after = cnx->nb_bytes_queued / next_probe;
uint64_t delta = (packets_to_send_before - packets_to_send_after) * 60;
if (delta > next_probe) {
ret = picoquic_pmtu_discovery_required;
}
else {
ret = picoquic_pmtu_discovery_optional;
}
}
}
}
}
return ret;
}
/* Prepare an MTU probe packet */
size_t picoquic_prepare_mtu_probe(picoquic_cnx_t* cnx,
picoquic_path_t * path_x,
size_t header_length, size_t checksum_length,
uint8_t* bytes, size_t bytes_max)
{
size_t probe_length = picoquic_next_mtu_probe_length(cnx, path_x);
size_t length = header_length;
if (probe_length > bytes_max) {
probe_length = bytes_max;
}
bytes[length++] = picoquic_frame_type_ping;
memset(&bytes[length], 0, probe_length - checksum_length - length);
return probe_length - checksum_length;
}
/* Prepare the next packet to 0-RTT packet to send in the client initial
* state, when 0-RTT is available
*/
int picoquic_prepare_packet_0rtt(picoquic_cnx_t* cnx, picoquic_path_t * path_x, picoquic_packet_t* packet,
uint64_t current_time, uint8_t* send_buffer, size_t send_buffer_max, size_t* send_length,
int padding_required, uint64_t * next_wake_time)
{
int ret = 0;
picoquic_stream_head_t* stream = NULL;
picoquic_packet_type_enum packet_type = picoquic_packet_0rtt_protected;
size_t header_length = 0;
uint8_t* bytes = packet->bytes;
size_t length = 0;
size_t checksum_overhead = picoquic_aead_get_checksum_length(cnx->crypto_context[1].aead_encrypt);
uint8_t* bytes_max;
uint8_t* bytes_next;
int more_data = 0;
int is_pure_ack = 1;
int stream_tried_and_failed = 0;
send_buffer_max = (send_buffer_max > path_x->send_mtu) ? path_x->send_mtu : send_buffer_max;
if (path_x->bytes_in_transit + send_buffer_max > PICOQUIC_DEFAULT_0RTT_WINDOW) {
if (path_x->bytes_in_transit > PICOQUIC_DEFAULT_0RTT_WINDOW) {
send_buffer_max = 0;
}
else {
send_buffer_max = (size_t)PICOQUIC_DEFAULT_0RTT_WINDOW - (size_t)path_x->bytes_in_transit;
}
}
bytes_max = bytes + send_buffer_max - checksum_overhead;
stream = picoquic_find_ready_stream(cnx);
length = picoquic_predict_packet_header_length(cnx, packet_type);
packet->ptype = picoquic_packet_0rtt_protected;
packet->offset = length;
header_length = length;
packet->pc = picoquic_packet_context_application;
packet->sequence_number = cnx->pkt_ctx[picoquic_packet_context_application].send_sequence;
packet->send_time = current_time;
packet->send_path = path_x;
packet->checksum_overhead = checksum_overhead;
bytes_next = bytes + length;
/* Consider sending 0-RTT */
if ((stream == NULL && cnx->first_misc_frame == NULL && padding_required == 0) ||
send_buffer_max < PICOQUIC_MIN_SEGMENT_SIZE) {
length = 0;
} else {
/* If present, send misc frame */
while (cnx->first_misc_frame != NULL) {
uint8_t* bytes_misc = bytes_next;
bytes_next = picoquic_format_first_misc_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
if (bytes_next == bytes_misc) {
break;
}
}
/* Encode the stream frame, or frames */
bytes_next = picoquic_format_available_stream_frames(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack, &stream_tried_and_failed, &ret);
length = bytes_next - bytes;
if (more_data) {
*next_wake_time = current_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
if (stream_tried_and_failed) {
path_x->last_sender_limited_time = current_time;
}
/* Add padding if required */
if (padding_required) {
length = picoquic_pad_to_target_length(bytes, length, send_buffer_max - checksum_overhead);
}
}
picoquic_finalize_and_protect_packet(cnx, packet,
ret, length, header_length, checksum_overhead,
send_length, send_buffer, send_buffer_max,
&path_x->remote_cnxid,
&path_x->p_local_cnxid->cnx_id,
path_x, current_time);
if (length > 0) {
/* Accounting of zero rtt packets sent */
cnx->nb_zero_rtt_sent++;
}
/* the reinsertion by wake up time will happen in the calling function */
return ret;
}
/* Get packet type from epoch */
picoquic_packet_type_enum picoquic_packet_type_from_epoch(int epoch)
{
picoquic_packet_type_enum ptype;
switch (epoch) {
case 0:
ptype = picoquic_packet_initial;
break;
case 1:
ptype = picoquic_packet_0rtt_protected;
break;
case 2:
ptype = picoquic_packet_handshake;
break;
case 3:
ptype = picoquic_packet_1rtt_protected;
break;
default:
ptype = picoquic_packet_error;
break;
}
return ptype;
}
/* Prepare a required repetition or ack in a previous context */
size_t picoquic_prepare_packet_old_context(picoquic_cnx_t* cnx, picoquic_packet_context_enum pc,
picoquic_path_t* path_x, picoquic_packet_t* packet, size_t send_buffer_max, uint64_t current_time,
uint64_t* next_wake_time, size_t* header_length)
{
picoquic_epoch_enum epoch = (pc == picoquic_packet_context_initial) ? picoquic_epoch_initial :
(pc == picoquic_packet_context_application) ? picoquic_epoch_0rtt : picoquic_epoch_handshake;
size_t length = 0;
uint8_t* bytes = packet->bytes;
int more_data = 0;
size_t checksum_overhead = picoquic_get_checksum_length(cnx, epoch);
uint8_t* bytes_max = bytes + send_buffer_max - checksum_overhead;
uint8_t* bytes_next;
*header_length = 0;
send_buffer_max = (send_buffer_max > path_x->send_mtu) ? path_x->send_mtu : send_buffer_max;
length = picoquic_retransmit_needed(cnx, pc, path_x, current_time, next_wake_time, packet, send_buffer_max, header_length);
if (length > 0 && (pc == picoquic_packet_context_handshake || cnx->pkt_ctx[picoquic_packet_context_handshake].retransmit_oldest == NULL ||
cnx->cnx_state == picoquic_state_server_init || cnx->cnx_state == picoquic_state_server_handshake)) {
cnx->initial_repeat_needed = 0;
}
if (length == 0 && cnx->pkt_ctx[pc].ack_needed != 0 &&
pc != picoquic_packet_context_application) {
packet->ptype =
(pc == picoquic_packet_context_initial) ? picoquic_packet_initial :
(pc == picoquic_packet_context_handshake) ? picoquic_packet_handshake :
picoquic_packet_0rtt_protected;
length = picoquic_predict_packet_header_length(cnx, packet->ptype);
packet->offset = length;
*header_length = length;
packet->sequence_number = cnx->pkt_ctx[pc].send_sequence;
packet->send_time = current_time;
packet->send_path = path_x;
}
if (length > 0) {
if (packet->ptype != picoquic_packet_0rtt_protected) {
/* Check whether it makes sens to add an ACK at the end of the retransmission */
bytes_next = picoquic_format_ack_frame(cnx, bytes + length, bytes_max, &more_data, current_time, pc);
length = bytes_next - bytes;
}
packet->length = length;
/* document the send time & overhead */
packet->send_time = current_time;
packet->checksum_overhead = checksum_overhead;
packet->pc = pc;
}
return length;
}
/* Empty the handshake repeat queues when transitioning to the completely ready state */
void picoquic_implicit_handshake_ack(picoquic_cnx_t* cnx, picoquic_packet_context_enum pc, uint64_t current_time)
{
picoquic_packet_t* p = cnx->pkt_ctx[pc].retransmit_oldest;
/* Remove packets from the retransmit queue */
while (p != NULL) {
picoquic_packet_t* p_next = p->next_packet;
picoquic_path_t * old_path = p->send_path;
/* Update the congestion control state for the path, but only for the packets sent
* before the initial timer. */
if (old_path != NULL && cnx->congestion_alg != NULL && p->send_time < cnx->start_time + PICOQUIC_INITIAL_RTT) {
cnx->congestion_alg->alg_notify(cnx, old_path,
picoquic_congestion_notification_acknowledgement,
0, 0, p->length, 0, current_time);
}
/* Update the number of bytes in transit and remove old packet from queue */
/* The packet will not be placed in the "retransmitted" queue */
(void)picoquic_dequeue_retransmit_packet(cnx, p, 1);
p = p_next;
}
}
/* Program a migration to the server preferred address if present */
int picoquic_prepare_server_address_migration(picoquic_cnx_t* cnx)
{
int ret = 0;
if (cnx->remote_parameters.prefered_address.is_defined)
{
int ipv4_received = cnx->remote_parameters.prefered_address.ipv4Port != 0;
int ipv6_received = cnx->remote_parameters.prefered_address.ipv6Port != 0;
/* Add the connection ID to the local stash */
ret = picoquic_enqueue_cnxid_stash(cnx, 1,
cnx->remote_parameters.prefered_address.connection_id.id_len,
cnx->remote_parameters.prefered_address.connection_id.id,
cnx->remote_parameters.prefered_address.statelessResetToken,
NULL);
if (ret != 0) {
ret = picoquic_connection_error(cnx, (uint16_t)ret, picoquic_frame_type_new_connection_id);
}
else if(ipv4_received || ipv6_received) {
struct sockaddr_storage dest_addr;
memset(&dest_addr, 0, sizeof(struct sockaddr_storage));
/* program a migration. */
if (ipv4_received && cnx->path[0]->peer_addr.ss_family == AF_INET) {
/* select IPv4 */
ipv6_received = 0;
}
if (ipv6_received) {
/* configure an IPv6 sockaddr */
struct sockaddr_in6 * d6 = (struct sockaddr_in6 *)&dest_addr;
d6->sin6_family = AF_INET6;
d6->sin6_port = htons(cnx->remote_parameters.prefered_address.ipv6Port);
memcpy(&d6->sin6_addr, cnx->remote_parameters.prefered_address.ipv6Address, 16);
}
else {
/* configure an IPv4 sockaddr */
struct sockaddr_in * d4 = (struct sockaddr_in *)&dest_addr;
d4->sin_family = AF_INET;
d4->sin_port = htons(cnx->remote_parameters.prefered_address.ipv4Port);
memcpy(&d4->sin_addr, cnx->remote_parameters.prefered_address.ipv4Address, 4);
}
/* Only send a probe if not already using that address */
if (picoquic_compare_addr((struct sockaddr *)&dest_addr, (struct sockaddr *)&cnx->path[0]->peer_addr) != 0) {
struct sockaddr* local_addr = NULL;
if (cnx->path[0]->local_addr.ss_family != 0) {
local_addr = (struct sockaddr*) & cnx->path[0]->local_addr;
}
ret = picoquic_probe_new_path_ex(cnx, (struct sockaddr *)&dest_addr, local_addr,
picoquic_get_quic_time(cnx->quic), 1);
}
}
}
return ret;
}
/* Prepare the next packet to send when in one of the client initial states */
int picoquic_prepare_packet_client_init(picoquic_cnx_t* cnx, picoquic_path_t * path_x, picoquic_packet_t* packet,
uint64_t current_time, uint8_t* send_buffer, size_t send_buffer_max, size_t* send_length, uint64_t * next_wake_time,
int * is_initial_sent)
{
int ret = 0;
int tls_ready = 0;
picoquic_packet_type_enum packet_type = 0;
size_t checksum_overhead = 16;
int is_cleartext_mode = 1;
int retransmit_possible = 0;
size_t header_length = 0;
uint8_t* bytes = packet->bytes;
uint8_t* bytes_max;
uint8_t* bytes_next;
size_t length = 0;
int epoch = 0;
int is_pure_ack = 1;
int more_data = 0;
picoquic_packet_context_enum pc = picoquic_packet_context_initial;
if (*next_wake_time > cnx->start_time + PICOQUIC_MICROSEC_HANDSHAKE_MAX) {
*next_wake_time = cnx->start_time + PICOQUIC_MICROSEC_HANDSHAKE_MAX;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
cnx->initial_validated = 1; /* always validated on client */
if (cnx->tls_stream[0].send_queue == NULL) {
if (cnx->crypto_context[1].aead_encrypt != NULL &&
cnx->tls_stream[1].send_queue != NULL) {
epoch = 1;
pc = picoquic_packet_context_application;
} else if (cnx->crypto_context[2].aead_encrypt != NULL &&
cnx->tls_stream[1].send_queue == NULL) {
epoch = 2;
pc = picoquic_packet_context_handshake;
}
}
packet_type = picoquic_packet_type_from_epoch(epoch);
send_buffer_max = (send_buffer_max > path_x->send_mtu) ? path_x->send_mtu : send_buffer_max;
/* Prepare header -- depend on connection state */
switch (cnx->cnx_state) {
case picoquic_state_client_init:
if (cnx->retry_token_length == 0 && cnx->sni != NULL) {
(void)picoquic_get_token(cnx->quic->p_first_token, current_time, cnx->sni, (uint16_t)strlen(cnx->sni),
NULL, 0, &cnx->retry_token, &cnx->retry_token_length, 1);
}
break;
case picoquic_state_client_init_sent:
case picoquic_state_client_init_resent:
retransmit_possible = 1;
break;
case picoquic_state_client_renegotiate:
packet_type = picoquic_packet_initial;
break;
case picoquic_state_client_handshake_start:
retransmit_possible = 1;
break;
case picoquic_state_client_almost_ready:
break;
default:
ret = -1;
break;
}
/* If context is handshake, verify first that there is no need for retransmit or ack
* on initial context */
int force_handshake_padding = 0;
if (ret == 0) {
if (epoch > picoquic_epoch_initial) {
if (cnx->crypto_context[picoquic_epoch_handshake].aead_encrypt != NULL) {
if (cnx->pkt_ctx[picoquic_packet_context_initial].ack_needed) {
/* Apply some ack delay, because handshake from server arrive in trains */
uint64_t ack_delay = cnx->path[0]->smoothed_rtt / 8;
uint64_t ack_time;
if (ack_delay > PICOQUIC_ACK_DELAY_MAX) {
ack_delay = PICOQUIC_ACK_DELAY_MAX;
}
ack_time = cnx->pkt_ctx[picoquic_packet_context_initial].time_oldest_unack_packet_received + ack_delay;
if (ack_time <= current_time) {
force_handshake_padding = 1;
}
else if (ack_time < *next_wake_time) {
*next_wake_time = ack_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
}
else if (!force_handshake_padding && cnx->pkt_ctx[pc].retransmit_newest != NULL) {
/* There is a risk of deadlock if the server is doing DDOS mitigation
* and does not receive the Handshake sent by the client. If more than RTT has elapsed since
* the last handshake packet was sent, force another one to be sent. */
uint64_t rto = picoquic_current_retransmit_timer(cnx, picoquic_packet_context_handshake);
uint64_t repeat_time = cnx->pkt_ctx[pc].retransmit_newest->send_time + rto;
if (repeat_time <= current_time) {
force_handshake_padding = 1;
cnx->pkt_ctx[pc].nb_retransmit++;
}
else if (repeat_time < *next_wake_time) {
*next_wake_time = repeat_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
}
}
else {
length = picoquic_prepare_packet_old_context(cnx, picoquic_packet_context_initial,
path_x, packet, send_buffer_max, current_time, next_wake_time, &header_length);
*is_initial_sent |= (length > 0);
}
}
else {
/* There is a risk of deadlock if the server is doing DDOS mitigation
* and does not repeat an initial or handshake packet that was lost. If more than RTT has elapsed since
* the last initial packet was sent, force another one to be sent. */
uint64_t rto = picoquic_current_retransmit_timer(cnx, picoquic_packet_context_initial);
uint64_t repeat_time = cnx->path[0]->latest_sent_time + rto;
force_handshake_padding = (repeat_time <= current_time);
}
}
if (ret == 0 && epoch > picoquic_epoch_0rtt && length == 0 &&
cnx->crypto_context[picoquic_epoch_0rtt].aead_encrypt != NULL) {
length = picoquic_prepare_packet_old_context(cnx, picoquic_packet_context_application,
path_x, packet, send_buffer_max, current_time, next_wake_time, &header_length);
}
/* If there is nothing to send in previous context, check this one too */
if (length == 0) {
checksum_overhead = picoquic_get_checksum_length(cnx, epoch);
packet->checksum_overhead = checksum_overhead;
bytes_max = bytes + send_buffer_max - checksum_overhead;
packet->pc = pc;
tls_ready = picoquic_is_tls_stream_ready(cnx);
if (ret == 0 && retransmit_possible &&
(length = picoquic_retransmit_needed(cnx, pc, path_x, current_time, next_wake_time, packet, send_buffer_max, &header_length)) > 0) {
/* Check whether it makes sens to add an ACK at the end of the retransmission */
if (epoch != picoquic_epoch_0rtt) {
bytes_next = picoquic_format_ack_frame(cnx, bytes + length, bytes_max, &more_data, current_time, pc);
length = bytes_next - bytes;
}
/* document the send time & overhead */
packet->length = length;
packet->send_time = current_time;
packet->checksum_overhead = checksum_overhead;
}
else if (ret == 0 && is_cleartext_mode && tls_ready == 0
&& cnx->first_misc_frame == NULL && !cnx->pkt_ctx[pc].ack_needed && !force_handshake_padding) {
/* when in a clear text mode, only send packets if there is
* actually something to send, or resend. */
packet->length = 0;
}
else if (ret == 0) {
if (cnx->crypto_context[epoch].aead_encrypt == NULL) {
packet->length = 0;
}
else {
length = picoquic_predict_packet_header_length(cnx, packet_type);
packet->ptype = packet_type;
packet->offset = length;
header_length = length;
packet->sequence_number = cnx->pkt_ctx[pc].send_sequence;
packet->send_time = current_time;
packet->send_path = path_x;
bytes_next = bytes + length;
bytes_max = bytes + send_buffer_max - checksum_overhead;
if ((tls_ready == 0 || path_x->cwin <= path_x->bytes_in_transit)
&& (cnx->cnx_state == picoquic_state_client_almost_ready
|| picoquic_is_ack_needed(cnx, current_time, next_wake_time, pc) == 0)
&& cnx->first_misc_frame == NULL && !force_handshake_padding) {
length = 0;
}
else {
if (epoch != 1 && cnx->pkt_ctx[pc].ack_needed) {
bytes_next = picoquic_format_ack_frame(cnx, bytes_next, bytes_max, &more_data, current_time, pc);
}
/* If present, send misc frame */
while (cnx->first_misc_frame != NULL) {
uint8_t* bytes_misc = bytes_next;
bytes_next = picoquic_format_first_misc_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
if (bytes_next == bytes_misc) {
break;
}
}
length = bytes_next - bytes;
if (ret == 0 && path_x->cwin > path_x->bytes_in_transit) {
/* Encode the crypto handshake frame */
if (tls_ready != 0) {
/* Encode the crypto frame */
bytes_next = picoquic_format_crypto_hs_frame(&cnx->tls_stream[epoch],
bytes_next, bytes_max, &more_data, &is_pure_ack);
length = bytes_next - bytes;
}
if (packet_type == picoquic_packet_initial) {
*is_initial_sent = 1;
if (cnx->crypto_context[1].aead_encrypt == NULL ||
cnx->cnx_state == picoquic_state_client_renegotiate ||
cnx->original_cnxid.id_len != 0) {
/* Pad to minimum packet length. But don't do that if the
* initial packet will be coalesced with 0-RTT packet */
length = picoquic_pad_to_target_length(bytes, length, send_buffer_max - checksum_overhead);
}
}
}
if (length == header_length) {
length = picoquic_pad_to_target_length(bytes, length, length + 8);
}
if (length > header_length && epoch == picoquic_epoch_handshake) {
cnx->pkt_ctx[picoquic_packet_context_initial].ack_needed = 0;
}
/* If TLS packets are sent, progress the state */
if (ret == 0 && tls_ready != 0 &&
cnx->tls_stream[epoch].send_queue == NULL) {
switch (cnx->cnx_state) {
case picoquic_state_client_init:
cnx->cnx_state = picoquic_state_client_init_sent;
break;
case picoquic_state_client_renegotiate:
cnx->cnx_state = picoquic_state_client_init_resent;
break;
case picoquic_state_client_almost_ready:
if (cnx->tls_stream[0].send_queue == NULL &&
cnx->tls_stream[1].send_queue == NULL &&
cnx->tls_stream[2].send_queue == NULL) {
cnx->cnx_state = picoquic_state_client_ready_start;
/* Reset the HS retransmission count, since end of flight counts as acknowledgement */
cnx->pkt_ctx[picoquic_packet_context_handshake].nb_retransmit = 0;
/* Signal the application, because data can now be sent. */
if (cnx->callback_fn != NULL) {
if (cnx->callback_fn(cnx, 0, NULL, 0, picoquic_callback_almost_ready, cnx->callback_ctx, NULL) != 0) {
picoquic_log_app_message(cnx, "Callback almost ready returns error 0x%x", PICOQUIC_TRANSPORT_INTERNAL_ERROR);
picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_INTERNAL_ERROR, 0);
}
}
}
break;
default:
break;
}
}
}
}
}
}
if (ret == 0 && length == 0) {
/* In some circumstances, there is a risk that the handshakes stops because the
* server is performing anti-dos mitigation and the client has nothing to repeat */
if ((packet->ptype == picoquic_packet_initial && cnx->crypto_context[picoquic_epoch_handshake].aead_encrypt == NULL &&
cnx->pkt_ctx[picoquic_packet_context_initial].retransmit_newest == NULL &&
cnx->pkt_ctx[picoquic_packet_context_initial].first_sack_item.end_of_sack_range != UINT64_MAX) ||
(packet->ptype == picoquic_packet_handshake &&
cnx->pkt_ctx[picoquic_packet_context_handshake].retransmit_newest == NULL &&
cnx->pkt_ctx[picoquic_packet_context_handshake].first_sack_item.end_of_sack_range == UINT64_MAX &&
cnx->pkt_ctx[picoquic_packet_context_handshake].send_sequence == 0))
{
uint64_t try_time_next = cnx->path[0]->latest_sent_time + cnx->path[0]->smoothed_rtt;
if (current_time < try_time_next) {
/* schedule a wake time to repeat the probing. */
if (*next_wake_time > try_time_next) {
*next_wake_time = try_time_next;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
}
else {
length = header_length;
packet->offset = length;
if (packet->ptype == picoquic_packet_initial) {
/* Repeat an ACK because it helps. */
bytes_max = bytes + send_buffer_max - checksum_overhead;
bytes_next = picoquic_format_ack_frame(cnx, bytes + length, bytes_max, &more_data, current_time, pc);
length = bytes_next - bytes;
length = picoquic_pad_to_target_length(bytes, length, send_buffer_max - checksum_overhead);
}
else {
length = picoquic_pad_to_target_length(bytes, length, length + 8);
}
}
}
}
if (ret == 0 && length == 0 && cnx->crypto_context[1].aead_encrypt != NULL) {
ret = picoquic_prepare_packet_0rtt(cnx, path_x, packet, current_time, send_buffer, send_buffer_max, send_length,
*is_initial_sent, next_wake_time);
}
else {
if (ret == 0 && more_data) {
*next_wake_time = current_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
if (ret == 0 && *is_initial_sent) {
if (packet->ptype == picoquic_packet_initial) {
if (length > 0 && cnx->crypto_context[1].aead_encrypt == NULL &&
(cnx->crypto_context[2].aead_encrypt == NULL || length + checksum_overhead + PICOQUIC_MIN_SEGMENT_SIZE > send_buffer_max ||
!picoquic_is_tls_stream_ready(cnx))) {
length = picoquic_pad_to_target_length(bytes, length, send_buffer_max - checksum_overhead);
}
}
else if (packet->ptype == picoquic_packet_handshake && length + checksum_overhead < send_buffer_max &&
(cnx->crypto_context[3].aead_encrypt == NULL || length + checksum_overhead + PICOQUIC_MIN_SEGMENT_SIZE > send_buffer_max)) {
length = picoquic_pad_to_target_length(bytes, length, send_buffer_max - checksum_overhead);
}
else if (packet->ptype == picoquic_packet_1rtt_protected) {
length = picoquic_pad_to_target_length(bytes, length, send_buffer_max - checksum_overhead);
}
}
if (length > 0 && packet->ptype == picoquic_packet_handshake && !is_pure_ack) {
/* Sending an ack eliciting handshake packet terminates the use of the initial context */
picoquic_implicit_handshake_ack(cnx, picoquic_packet_context_initial, current_time);
picoquic_crypto_context_free(&cnx->crypto_context[picoquic_epoch_initial]);
}
picoquic_finalize_and_protect_packet(cnx, packet,
ret, length, header_length, checksum_overhead,
send_length, send_buffer, send_buffer_max,
&path_x->remote_cnxid, &path_x->p_local_cnxid->cnx_id, path_x, current_time);
}
return ret;
}
/* Compute the time at which to send the next challenge
*/
uint64_t picoquic_next_challenge_time(picoquic_cnx_t* cnx, picoquic_path_t* path_x)
{
uint64_t next_challenge_time = path_x->challenge_time;
if (path_x->challenge_repeat_count >= 2) {
next_challenge_time += path_x->retransmit_timer << path_x->challenge_repeat_count;
}
else {
next_challenge_time += PICOQUIC_INITIAL_RETRANSMIT_TIMER;
}
return next_challenge_time;
}
/* Prepare the next packet to send when in one the server initial states */
int picoquic_prepare_packet_server_init(picoquic_cnx_t* cnx, picoquic_path_t * path_x, picoquic_packet_t* packet,
uint64_t current_time, uint8_t* send_buffer, size_t send_buffer_max, size_t* send_length, uint64_t * next_wake_time)
{
int ret = 0;
int tls_ready = 0;
picoquic_epoch_enum epoch = picoquic_epoch_initial;
picoquic_packet_type_enum packet_type = picoquic_packet_initial;
picoquic_packet_context_enum pc = picoquic_packet_context_initial;
size_t checksum_overhead = 8;
size_t header_length = 0;
uint8_t* bytes = packet->bytes;
uint8_t* bytes_max;
uint8_t* bytes_next;
size_t length = 0;
int more_data = 0;
int is_pure_ack = 1;
if (*next_wake_time > cnx->start_time + PICOQUIC_MICROSEC_HANDSHAKE_MAX) {
*next_wake_time = cnx->start_time + PICOQUIC_MICROSEC_HANDSHAKE_MAX;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
/* The only purpose of the test below is to appease the static analyzer, so it
* wont complain of possible NULL deref. On windows we could use "__assume(path_x != NULL)"
* but the documentation does not say anything about that for GCC and CLANG */
if (path_x == NULL) {
return PICOQUIC_ERROR_UNEXPECTED_ERROR;
}
if (cnx->crypto_context[picoquic_epoch_handshake].aead_encrypt != NULL &&
cnx->tls_stream[0].send_queue == NULL) {
epoch = picoquic_epoch_handshake;
pc = picoquic_packet_context_handshake;
packet_type = picoquic_packet_handshake;
}
send_buffer_max = (send_buffer_max > path_x->send_mtu) ? path_x->send_mtu : send_buffer_max;
if (!cnx->initial_validated &&
(cnx->initial_data_sent + send_buffer_max) > 3 * cnx->initial_data_received){
/* Sending more data now would break the amplication limit */
*send_length = 0;
return 0;
}
/* If context is handshake, verify first that there is no need for retransmit or ack
* on initial context */
if (pc == picoquic_packet_context_handshake) {
length = picoquic_prepare_packet_old_context(cnx, picoquic_packet_context_initial,
path_x, packet, send_buffer_max, current_time, next_wake_time, &header_length);
}
if (length == 0) {
checksum_overhead = picoquic_get_checksum_length(cnx, epoch);
bytes_max = bytes + send_buffer_max - checksum_overhead;
tls_ready = (cnx->tls_stream[epoch].send_queue != NULL &&
cnx->tls_stream[epoch].send_queue->length > cnx->tls_stream[epoch].send_queue->offset);
length = picoquic_predict_packet_header_length(cnx, packet_type);
packet->ptype = packet_type;
packet->offset = length;
header_length = length;
packet->sequence_number = cnx->pkt_ctx[pc].send_sequence;
packet->send_time = current_time;
packet->send_path = path_x;
packet->pc = pc;
bytes_next = bytes + length;
if ((tls_ready != 0 && path_x->cwin > path_x->bytes_in_transit)
|| cnx->pkt_ctx[pc].ack_needed) {
bytes_next = picoquic_format_ack_frame(cnx, bytes_next, bytes_max, &more_data, current_time, pc);
/* Encode the crypto frame */
bytes_next = picoquic_format_crypto_hs_frame(&cnx->tls_stream[epoch],
bytes_next, bytes_max, &more_data, &is_pure_ack);
length = bytes_next - bytes;
/* progress the state if the epoch data is all sent */
if (ret == 0 && tls_ready != 0 && cnx->tls_stream[epoch].send_queue == NULL) {
if (epoch == picoquic_epoch_handshake && picoquic_tls_client_authentication_activated(cnx->quic) == 0) {
cnx->cnx_state = picoquic_state_server_false_start;
/* On a server that does address validation, send a NEW TOKEN frame */
if (!cnx->client_mode && (cnx->quic->check_token || cnx->quic->provide_token)) {
uint8_t token_buffer[256];
size_t token_size;
picoquic_connection_id_t n_cid = picoquic_null_connection_id;
if (picoquic_prepare_retry_token(cnx->quic, (struct sockaddr *)&cnx->path[0]->peer_addr,
current_time + PICOQUIC_TOKEN_DELAY_LONG, &n_cid, &n_cid, 0,
token_buffer, sizeof(token_buffer), &token_size) == 0) {
if (picoquic_queue_new_token_frame(cnx, token_buffer, token_size) != 0) {
picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_INTERNAL_ERROR, picoquic_frame_type_new_token);
}
}
}
if (cnx->callback_fn != NULL) {
if (cnx->callback_fn(cnx, 0, NULL, 0, picoquic_callback_almost_ready, cnx->callback_ctx, NULL) != 0) {
picoquic_log_app_message(cnx, "Callback almost ready returns error 0x%x", PICOQUIC_TRANSPORT_INTERNAL_ERROR);
picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_INTERNAL_ERROR, 0);
}
}
}
else {
cnx->cnx_state = picoquic_state_server_handshake;
}
}
packet->length = length;
}
else if ((length = picoquic_retransmit_needed(cnx, pc, path_x, current_time, next_wake_time, packet, send_buffer_max, &header_length)) > 0) {
/* Set the new checksum length */
checksum_overhead = picoquic_get_checksum_length(cnx, epoch);
cnx->initial_repeat_needed = 0;
/* Check whether it makes sens to add an ACK at the end of the retransmission */
bytes_max = bytes + send_buffer_max - checksum_overhead;
bytes_next = picoquic_format_ack_frame(cnx, bytes + length, bytes_max, &more_data, current_time, pc);
length = bytes_next - bytes;
packet->length = length;
/* document the send time & overhead */
packet->send_time = current_time;
packet->checksum_overhead = checksum_overhead;
}
else if (cnx->pkt_ctx[pc].ack_needed) {
/* when in a handshake mode, send acks asap. */
length = picoquic_predict_packet_header_length(cnx, packet_type);
bytes_next = bytes + length;
bytes_max = bytes + send_buffer_max - checksum_overhead;
bytes_next = picoquic_format_ack_frame(cnx, bytes_next, bytes_max, &more_data, current_time, pc);
length = bytes_next - bytes;
} else {
length = 0;
packet->length = 0;
}
}
if (ret == 0 && length == 0 && more_data) {
*next_wake_time = current_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
picoquic_finalize_and_protect_packet(cnx, packet,
ret, length, header_length, checksum_overhead,
send_length, send_buffer, send_buffer_max,
&path_x->remote_cnxid, &path_x->p_local_cnxid->cnx_id, path_x, current_time);
/* Account for data sent during handshake */
if (!cnx->initial_validated) {
cnx->initial_data_sent += *send_length;
}
return ret;
}
/* Prepare the next packet to send when in one the closing states */
int picoquic_prepare_packet_closing(picoquic_cnx_t* cnx, picoquic_path_t * path_x, picoquic_packet_t* packet,
uint64_t current_time, uint8_t* send_buffer, size_t send_buffer_max, size_t* send_length, uint64_t * next_wake_time)
{
int ret = 0;
/* TODO: manage multiple streams. */
picoquic_packet_type_enum packet_type = 0;
size_t checksum_overhead = 8;
size_t header_length = 0;
uint8_t* bytes = packet->bytes;
uint8_t* bytes_max;
uint8_t* bytes_next;
int more_data = 0;
size_t length = 0;
int is_pure_ack = 1;
picoquic_packet_context_enum pc = picoquic_packet_context_application;
picoquic_epoch_enum epoch = picoquic_epoch_1rtt;
/* The only purpose of the test below is to appease the static analyzer, so it
* wont complain of possible NULL deref. On windows we could use "__assume(path_x != NULL)"
* but the documentation does not say anything about that for GCC and CLANG */
if (path_x == NULL) {
return PICOQUIC_ERROR_UNEXPECTED_ERROR;
}
send_buffer_max = (send_buffer_max > path_x->send_mtu) ? path_x->send_mtu : send_buffer_max;
/* Prepare header -- depend on connection state */
/* TODO: 0-RTT work. */
switch (cnx->cnx_state) {
case picoquic_state_handshake_failure:
/* TODO: check whether closing can be requested in "initial" mode */
if (cnx->crypto_context[picoquic_epoch_handshake].aead_encrypt != NULL &&
cnx->pkt_ctx[picoquic_packet_context_handshake].first_sack_item.start_of_sack_range != (uint64_t)((int64_t)-1)) {
pc = picoquic_packet_context_handshake;
packet_type = picoquic_packet_handshake;
epoch = picoquic_epoch_handshake;
}
else {
pc = picoquic_packet_context_initial;
packet_type = picoquic_packet_initial;
}
break;
case picoquic_state_handshake_failure_resend:
pc = picoquic_packet_context_handshake;
packet_type = picoquic_packet_handshake;
epoch = picoquic_epoch_handshake;
break;
case picoquic_state_disconnecting:
packet_type = picoquic_packet_1rtt_protected;
break;
case picoquic_state_closing_received:
packet_type = picoquic_packet_1rtt_protected;
break;
case picoquic_state_closing:
packet_type = picoquic_packet_1rtt_protected;
break;
case picoquic_state_draining:
packet_type = picoquic_packet_1rtt_protected;
break;
case picoquic_state_disconnected:
ret = PICOQUIC_ERROR_DISCONNECTED;
break;
default:
ret = -1;
break;
}
/* At this stage, we don't try to retransmit any old packet, whether in
* the current context or in previous contexts. */
checksum_overhead = picoquic_get_checksum_length(cnx, epoch);
packet->pc = pc;
bytes_max = bytes + send_buffer_max - checksum_overhead;
if (ret == 0 && cnx->cnx_state == picoquic_state_closing_received) {
/* Send a closing frame, move to closing state */
uint64_t exit_time = cnx->latest_progress_time + 3 * path_x->retransmit_timer;
length = picoquic_predict_packet_header_length(cnx, packet_type);
bytes_next = bytes + length;
packet->ptype = packet_type;
packet->offset = length;
header_length = length;
packet->sequence_number = cnx->pkt_ctx[pc].send_sequence;
packet->send_time = current_time;
packet->send_path = path_x;
/* Send the disconnect frame */
bytes_next = picoquic_format_connection_close_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
length = bytes_next - bytes;
cnx->cnx_state = picoquic_state_draining;
*next_wake_time = exit_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
} else if (ret == 0 && cnx->cnx_state == picoquic_state_closing) {
/* if more than 3*RTO is elapsed, move to disconnected */
uint64_t exit_time = cnx->latest_progress_time + 3 * path_x->retransmit_timer;
if (current_time >= exit_time) {
cnx->cnx_state = picoquic_state_disconnected;
*next_wake_time = current_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
else if (current_time >= cnx->next_wake_time) {
uint64_t delta_t = path_x->rtt_min;
uint64_t next_time = 0;
if (delta_t * 2 < path_x->retransmit_timer) {
delta_t = path_x->retransmit_timer / 2;
}
/* if more than N packet received, repeat and erase */
if (cnx->pkt_ctx[pc].ack_needed) {
length = picoquic_predict_packet_header_length(
cnx, packet_type);
packet->ptype = packet_type;
packet->offset = length;
header_length = length;
packet->sequence_number = cnx->pkt_ctx[pc].send_sequence;
packet->send_time = current_time;
packet->send_path = path_x;
bytes_next = bytes + length;
/* Resend the disconnect frame */
if (cnx->local_error == 0) {
bytes_next = picoquic_format_application_close_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
} else {
bytes_next = picoquic_format_connection_close_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
}
length = bytes_next - bytes;
cnx->pkt_ctx[pc].ack_needed = 0;
}
next_time = current_time + delta_t;
if (next_time > exit_time) {
next_time = exit_time;
}
*next_wake_time = next_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
} else if (ret == 0 && cnx->cnx_state == picoquic_state_draining) {
/* Nothing is ever sent in the draining state */
/* if more than 3*RTO is elapsed, move to disconnected */
uint64_t exit_time = cnx->latest_progress_time + 3 * path_x->retransmit_timer;
if (current_time >= exit_time) {
cnx->cnx_state = picoquic_state_disconnected;
*next_wake_time = current_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
else {
*next_wake_time = exit_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
length = 0;
} else if (ret == 0 && (cnx->cnx_state == picoquic_state_disconnecting ||
cnx->cnx_state == picoquic_state_handshake_failure ||
cnx->cnx_state == picoquic_state_handshake_failure_resend)) {
length = picoquic_predict_packet_header_length(
cnx, packet_type);
bytes_next = bytes + length;
packet->ptype = packet_type;
packet->offset = length;
header_length = length;
packet->sequence_number = cnx->pkt_ctx[pc].send_sequence;
packet->send_time = current_time;
packet->send_path = path_x;
/* send either app close or connection close, depending on error code */
uint64_t delta_t = path_x->rtt_min;
if (2 * delta_t < path_x->retransmit_timer) {
delta_t = path_x->retransmit_timer / 2;
}
/* add a final ack so receiver gets clean state */
bytes_next = picoquic_format_ack_frame(cnx, bytes_next, bytes_max, &more_data, current_time, pc);
/* Send the disconnect frame */
if (cnx->local_error == 0) {
bytes_next = picoquic_format_application_close_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
}
else {
bytes_next = picoquic_format_connection_close_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
}
length = bytes_next - bytes;
if (cnx->cnx_state == picoquic_state_handshake_failure) {
if (pc == picoquic_packet_context_initial &&
cnx->crypto_context[2].aead_encrypt != NULL) {
cnx->cnx_state = picoquic_state_handshake_failure_resend;
}
else {
cnx->cnx_state = picoquic_state_disconnected;
}
}
else if (cnx->cnx_state == picoquic_state_handshake_failure_resend) {
cnx->cnx_state = picoquic_state_disconnected;
}
else {
cnx->cnx_state = picoquic_state_closing;
}
cnx->latest_progress_time = current_time;
*next_wake_time = current_time + delta_t;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
cnx->pkt_ctx[pc].ack_needed = 0;
if (cnx->callback_fn) {
(void)(cnx->callback_fn)(cnx, 0, NULL, 0, picoquic_callback_close, cnx->callback_ctx, NULL);
}
}
else {
length = 0;
}
if (length > 0 && packet->ptype == picoquic_packet_initial && cnx->client_mode) {
length = picoquic_pad_to_target_length(bytes, length, send_buffer_max - checksum_overhead);
}
picoquic_finalize_and_protect_packet(cnx, packet,
ret, length, header_length, checksum_overhead,
send_length, send_buffer, send_buffer_max,
&path_x->remote_cnxid, &path_x->p_local_cnxid->cnx_id, path_x, current_time);
return ret;
}
/* Create required ID, register, and format the corresponding connection ID frame */
uint8_t * picoquic_format_new_local_id_as_needed(picoquic_cnx_t* cnx, uint8_t* bytes, uint8_t * bytes_max, int * more_data, int * is_pure_ack)
{
while ((cnx->remote_parameters.migration_disabled == 0 || cnx->remote_parameters.prefered_address.is_defined) &&
cnx->local_parameters.migration_disabled == 0 &&
cnx->nb_local_cnxid < (int)(cnx->remote_parameters.active_connection_id_limit) &&
cnx->nb_local_cnxid <= PICOQUIC_NB_PATH_TARGET) {
uint8_t* bytes0 = bytes;
picoquic_local_cnxid_t* l_cid = picoquic_create_local_cnxid(cnx, NULL);
if (l_cid == NULL) {
/* OOPS, memory error */
break;
} else {
bytes = picoquic_format_new_connection_id_frame(cnx, bytes, bytes_max, more_data, is_pure_ack, l_cid);
if (bytes == bytes0) {
/* Oops. Try again next time. */
picoquic_delete_local_cnxid(cnx, l_cid);
cnx->local_cnxid_sequence_next--;
break;
}
}
}
return bytes;
}
void picoquic_ready_state_transition(picoquic_cnx_t* cnx, uint64_t current_time)
{
/* Transition to server ready state.
* The handshake is complete, all the handshake packets are implicitly acknowledged */
cnx->cnx_state = picoquic_state_ready;
cnx->is_handshake_finished = 1;
picoquic_implicit_handshake_ack(cnx, picoquic_packet_context_initial, current_time);
picoquic_implicit_handshake_ack(cnx, picoquic_packet_context_handshake, current_time);
(void)picoquic_register_net_secret(cnx);
picoquic_public_random_seed(cnx->quic);
if (!cnx->client_mode) {
(void)picoquic_queue_handshake_done_frame(cnx);
}
if (cnx->is_half_open){
if (cnx->quic->current_number_half_open > 0) {
cnx->quic->current_number_half_open--;
}
cnx->is_half_open = 0;
if (cnx->quic->current_number_half_open < cnx->quic->max_half_open_before_retry) {
cnx->quic->check_token = cnx->quic->force_check_token;
}
}
/* Remove handshake and initial keys if they are still around */
picoquic_crypto_context_free(&cnx->crypto_context[picoquic_epoch_initial]);
picoquic_crypto_context_free(&cnx->crypto_context[picoquic_epoch_0rtt]);
picoquic_crypto_context_free(&cnx->crypto_context[picoquic_epoch_handshake]);
/* Set the confidentiality limit if not already set */
if (cnx->crypto_epoch_length_max == 0) {
cnx->crypto_epoch_length_max =
picoquic_aead_confidentiality_limit(cnx->crypto_context[picoquic_epoch_1rtt].aead_decrypt);
}
/* Start migration to server preferred address if present */
if (cnx->client_mode) {
(void)picoquic_prepare_server_address_migration(cnx);
}
/* Notify the application */
if (cnx->callback_fn != NULL) {
if (cnx->callback_fn(cnx, 0, NULL, 0, picoquic_callback_ready, cnx->callback_ctx, NULL) != 0) {
picoquic_log_app_message(cnx, "Callback ready returns error 0x%x", PICOQUIC_TRANSPORT_INTERNAL_ERROR);
picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_INTERNAL_ERROR, 0);
}
}
/* Ask for ACK frequency update, or initialize variables if not available */
if (cnx->is_ack_frequency_negotiated) {
cnx->is_ack_frequency_updated = 1;
}
else {
cnx->ack_gap_remote = picoquic_compute_ack_gap(cnx, cnx->path[0]->receive_rate_max);
cnx->ack_delay_remote = picoquic_compute_ack_delay_max(cnx->path[0]->rtt_min, PICOQUIC_ACK_DELAY_MIN);
}
/* Perform a check of the PN decryption key, for sanity */
picoquic_log_pn_dec_trial(cnx);
}
/* Prepare the next packet to send when in one the ready states
* Lots of the same code as in the "ready" case, but we deal here with extra
* complexity because the handshake is not finished.
*/
int picoquic_prepare_packet_almost_ready(picoquic_cnx_t* cnx, picoquic_path_t* path_x, picoquic_packet_t* packet,
uint64_t current_time, uint8_t* send_buffer, size_t send_buffer_max, size_t* send_length, uint64_t* next_wake_time,
int* is_initial_sent)
{
int ret = 0;
picoquic_packet_type_enum packet_type = picoquic_packet_1rtt_protected;
picoquic_packet_context_enum pc = picoquic_packet_context_application;
int tls_ready = 0;
int is_pure_ack = 1;
size_t header_length = 0;
uint8_t* bytes = packet->bytes;
size_t length = 0;
size_t checksum_overhead = picoquic_get_checksum_length(cnx, picoquic_epoch_1rtt);
size_t send_buffer_min_max = (send_buffer_max > path_x->send_mtu) ? path_x->send_mtu : send_buffer_max;
uint8_t* bytes_max = bytes + send_buffer_min_max - checksum_overhead;
uint8_t* bytes_next = NULL;
int more_data = 0;
int stream_tried_and_failed = 0;
int is_challenge_padding_needed = 0;
/* Perform amplification prevention check */
if (!cnx->initial_validated &&
(cnx->initial_data_sent + send_buffer_min_max) > 3 * cnx->initial_data_received) {
*send_length = 0;
return 0;
}
/* Verify first that there is no need for retransmit or ack
* on initial or handshake context. */
if (cnx->crypto_context[picoquic_epoch_initial].aead_encrypt != NULL) {
length = picoquic_prepare_packet_old_context(cnx, picoquic_packet_context_initial,
path_x, packet, send_buffer_min_max, current_time, next_wake_time, &header_length);
}
else {
length = 0;
}
if (length == 0) {
length = picoquic_prepare_packet_old_context(cnx, picoquic_packet_context_handshake,
path_x, packet, send_buffer_min_max, current_time, next_wake_time, &header_length);
if (length > 0) {
checksum_overhead = picoquic_get_checksum_length(cnx, picoquic_epoch_handshake);
bytes_max = bytes + send_buffer_min_max - checksum_overhead;
}
}
else {
checksum_overhead = picoquic_get_checksum_length(cnx, picoquic_epoch_initial);
bytes_max = bytes + send_buffer_min_max - checksum_overhead;
*is_initial_sent = 1;
}
if (length > 0) {
cnx->initial_repeat_needed = 0;
if (cnx->client_mode && *is_initial_sent && send_buffer_min_max < length + checksum_overhead + PICOQUIC_MIN_SEGMENT_SIZE) {
length = picoquic_pad_to_target_length(packet->bytes, length, send_buffer_min_max - checksum_overhead);
}
}
if (length == 0) {
tls_ready = picoquic_is_tls_stream_ready(cnx);
packet->pc = pc;
length = picoquic_predict_packet_header_length(
cnx, packet_type);
packet->ptype = packet_type;
packet->offset = length;
header_length = length;
packet->sequence_number = cnx->pkt_ctx[pc].send_sequence;
packet->send_time = current_time;
packet->send_path = path_x;
bytes_next = bytes + length;
if (path_x->challenge_verified == 0 && path_x->challenge_failed == 0) {
uint64_t next_challenge_time = picoquic_next_challenge_time(cnx, path_x);
if (next_challenge_time <= current_time || path_x->challenge_repeat_count == 0) {
if (path_x->challenge_repeat_count < PICOQUIC_CHALLENGE_REPEAT_MAX) {
int ack_needed = cnx->pkt_ctx[pc].ack_needed;
uint8_t* bytes_challenge = bytes_next;
bytes_next = picoquic_format_path_challenge_frame(bytes_next, bytes_max, &more_data, &is_pure_ack,
path_x->challenge[path_x->challenge_repeat_count]);
if (bytes_next > bytes_challenge) {
path_x->challenge_time = current_time;
path_x->challenge_repeat_count++;
is_challenge_padding_needed = 1;
}
/* add an ACK just to be nice */
if (ack_needed) {
bytes_next = picoquic_format_ack_frame(cnx, bytes_next, bytes_max, &more_data, current_time, pc);
/* Restore the ACK needed flags, because challenges are not reliable. */
cnx->pkt_ctx[pc].ack_needed = ack_needed;
}
}
else {
if (path_x == cnx->path[0]) {
/* TODO: consider alt address. Also, consider other available path. */
DBG_PRINTF("%s\n", "Too many challenge retransmits, disconnect");
picoquic_log_app_message(cnx, "%s", "Too many challenge retransmits, disconnect");
cnx->cnx_state = picoquic_state_disconnected;
if (cnx->callback_fn) {
(void)(cnx->callback_fn)(cnx, 0, NULL, 0, picoquic_callback_close, cnx->callback_ctx, NULL);
}
}
else {
DBG_PRINTF("%s\n", "Too many challenge retransmits, abandon path");
picoquic_log_app_message(cnx, "%s", "Too many challenge retransmits, abandon path");
path_x->challenge_failed = 1;
cnx->path_demotion_needed = 1;
}
}
}
else {
if (next_challenge_time < *next_wake_time) {
*next_wake_time = next_challenge_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
}
}
if (path_x->response_required) {
uint8_t* bytes_response = bytes_next;
if ((bytes_next = picoquic_format_path_response_frame(bytes_response, bytes_max,
&more_data, &is_pure_ack, path_x->challenge_response)) > bytes_response) {
path_x->response_required = 0;
is_challenge_padding_needed = 1;
}
}
length = bytes_next - bytes;
if (cnx->cnx_state != picoquic_state_disconnected && path_x->challenge_verified != 0) {
/* There are no frames yet that would be exempt from pacing control, but if there
* was they should be sent here. */
if (picoquic_is_sending_authorized_by_pacing(cnx, path_x, current_time, next_wake_time)) {
/* Send here the frames that are not exempt from the pacing control,
* but are exempt for congestion control */
if (picoquic_is_ack_needed(cnx, current_time, next_wake_time, pc)) {
bytes_next = picoquic_format_ack_frame(cnx, bytes_next, bytes_max, &more_data,
current_time, pc);
}
length = bytes_next - bytes;
if (path_x->cwin < path_x->bytes_in_transit) {
cnx->cwin_blocked = 1;
if (cnx->congestion_alg != NULL) {
cnx->congestion_alg->alg_notify(cnx, path_x,
picoquic_congestion_notification_cwin_blocked,
0, 0, 0, 0, current_time);
}
}
else {
/* Send here the frames that are subject to both congestion and pacing control.
* this includes the PMTU probes.
* Check whether PMTU discovery is required. The call will return
* three values: not needed at all, optional, or required.
* If required, PMTU discovery takes priority over sending stream data.
*/
picoquic_pmtu_discovery_status_enum pmtu_discovery_needed = picoquic_is_mtu_probe_needed(cnx, path_x);
/* if present, send tls data */
if (tls_ready) {
bytes_next = picoquic_format_crypto_hs_frame(&cnx->tls_stream[picoquic_epoch_1rtt],
bytes_next, bytes_max, &more_data, &is_pure_ack);
}
length = bytes_next - bytes;
if (length > header_length || pmtu_discovery_needed != picoquic_pmtu_discovery_required ||
send_buffer_max <= path_x->send_mtu) {
/* No need or no way to do pmtu discovery */
/* If present, send misc frame */
while (cnx->first_misc_frame != NULL) {
uint8_t* bytes_misc = bytes_next;
bytes_next = picoquic_format_first_misc_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
if (bytes_next == bytes_misc) {
break;
}
}
/* If there are not enough published CID, create and advertise */
if (ret == 0) {
bytes_next = picoquic_format_new_local_id_as_needed(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
}
/* Start of CC controlled frames */
if (ret == 0 && length <= header_length && cnx->first_datagram != NULL) {
bytes_next = picoquic_format_first_datagram_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
}
/* If present, send stream frames queued for retransmission */
if (ret == 0) {
bytes_next = picoquic_format_stream_frames_queued_for_retransmit(cnx, bytes_next, bytes_max,
&more_data, &is_pure_ack);
}
if (cnx->is_ack_frequency_updated && cnx->is_ack_frequency_negotiated) {
bytes_next = picoquic_format_ack_frequency_frame(cnx, bytes_next, bytes_max, &more_data);
}
bytes_next = picoquic_format_available_stream_frames(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack, &stream_tried_and_failed, &ret);
length = bytes_next - bytes;
if (length <= header_length) {
/* Mark the bandwidth estimation as application limited */
path_x->delivered_limited_index = path_x->delivered;
/* Notify the peer if something is blocked */
bytes_next = picoquic_format_blocked_frames(cnx, &bytes[length], bytes_max, &more_data, &is_pure_ack);
length = bytes_next - bytes;
}
if (stream_tried_and_failed) {
path_x->last_sender_limited_time = current_time;
}
} /* end of PMTU not required */
if (ret == 0 && length <= header_length && send_buffer_max > path_x->send_mtu
&& path_x->cwin > path_x->bytes_in_transit&& pmtu_discovery_needed != picoquic_pmtu_discovery_not_needed) {
/* Since there is no data to send, this is an opportunity to send an MTU probe */
length = picoquic_prepare_mtu_probe(cnx, path_x, header_length, checksum_overhead, bytes, send_buffer_max);
packet->length = length;
packet->send_path = path_x;
packet->is_mtu_probe = 1;
path_x->mtu_probe_sent = 1;
is_pure_ack = 0;
}
} /* end of PMTU references */
} /* end of CC */
} /* End of pacing */
if (length <= header_length) {
length = 0;
}
if (cnx->cnx_state != picoquic_state_disconnected) {
/* If necessary, encode and send the keep alive packet!
* We only send keep alive packets when no other data is sent!
*/
if (is_pure_ack == 0)
{
cnx->latest_progress_time = current_time;
}
else if (cnx->keep_alive_interval != 0) {
if (cnx->latest_progress_time + cnx->keep_alive_interval <= current_time && length == 0) {
length = picoquic_predict_packet_header_length(
cnx, packet_type);
packet->ptype = packet_type;
packet->pc = pc;
packet->offset = length;
header_length = length;
packet->sequence_number = cnx->pkt_ctx[pc].send_sequence;
packet->send_path = path_x;
packet->send_time = current_time;
bytes[length++] = picoquic_frame_type_ping;
bytes[length++] = 0;
cnx->latest_progress_time = current_time;
}
else if (cnx->latest_progress_time + cnx->keep_alive_interval < *next_wake_time) {
*next_wake_time = cnx->latest_progress_time + cnx->keep_alive_interval;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
}
}
}
if (ret == 0 && length > header_length) {
if (more_data) {
*next_wake_time = current_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
ret = 0;
}
/* Ensure that all packets are properly padded before being sent. */
if ((*is_initial_sent && cnx->client_mode) || (is_challenge_padding_needed && length < PICOQUIC_ENFORCED_INITIAL_MTU)){
length = picoquic_pad_to_target_length(bytes, length, (uint32_t)(send_buffer_min_max - checksum_overhead));
}
else {
length = picoquic_pad_to_policy(cnx, bytes, length, (uint32_t)(send_buffer_min_max - checksum_overhead));
}
}
picoquic_finalize_and_protect_packet(cnx, packet,
ret, length, header_length, checksum_overhead,
send_length, send_buffer, send_buffer_min_max,
&path_x->remote_cnxid, &path_x->p_local_cnxid->cnx_id, path_x, current_time);
if (*send_length > 0) {
/* Account for data sent during handshake */
if (!cnx->initial_validated) {
cnx->initial_data_sent += *send_length;
}
*next_wake_time = current_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
if (picoquic_cnx_is_still_logging(cnx)) {
picoquic_log_cc_dump(cnx, current_time);
}
}
return ret;
}
/* Prepare the next packet to send when in the ready state */
int picoquic_prepare_packet_ready(picoquic_cnx_t* cnx, picoquic_path_t* path_x, picoquic_packet_t* packet,
uint64_t current_time, uint8_t* send_buffer, size_t send_buffer_max, size_t* send_length, uint64_t* next_wake_time,
int* is_initial_sent)
{
int ret = 0;
picoquic_packet_type_enum packet_type = picoquic_packet_1rtt_protected;
picoquic_packet_context_enum pc = picoquic_packet_context_application;
int is_pure_ack = 1;
size_t header_length = 0;
size_t length = 0;
size_t checksum_overhead = picoquic_get_checksum_length(cnx, picoquic_epoch_1rtt);
size_t send_buffer_min_max = (send_buffer_max > path_x->send_mtu) ? path_x->send_mtu : send_buffer_max;
int split_repeat_queued = 0;
uint8_t* bytes = packet->bytes;
uint8_t* bytes_max = bytes + send_buffer_min_max - checksum_overhead;
uint8_t* bytes_next;
int more_data = 0;
int ack_sent = 0;
int is_challenge_padding_needed = 0;
packet->pc = pc;
/* If there was no packet sent on this path for a long time, rotate the
* CID prior to sending a new packet. The point is to make it harder for
* casual observers to track traffic, especially across NAT resets */
if (cnx->client_mode &&
path_x->challenge_verified &&
!path_x->path_cid_rotated &&
path_x->latest_sent_time + PICOQUIC_CID_REFRESH_DELAY < current_time)
{
/* Ignore renewal failure mode, since this is an optional feature */
(void)picoquic_renew_path_connection_id(cnx, path_x);
path_x->path_cid_rotated = 1;
}
/* If the number of packets sent is larger that the max length of
* a crypto epoch, prepare a key rotation */
if ((cnx->pkt_ctx[picoquic_packet_context_application].send_sequence - cnx->crypto_epoch_sequence >
cnx->crypto_epoch_length_max) &&
cnx->crypto_epoch_sequence <
cnx->pkt_ctx[picoquic_packet_context_application].first_sack_item.end_of_sack_range) {
if (picoquic_start_key_rotation(cnx) != 0) {
picoquic_log_app_message(cnx, "Cannot start key rotation after %"PRIu64" packets",
cnx->pkt_ctx[picoquic_packet_context_application].send_sequence);
}
}
/* The first action is normally to retransmit lost packets. But if retransmit follows an
* MTU drop, the stream frame will be fragmented and a fragment will be queued as a
* misc frame. These fragments should have chance to go out before more retransmit is
* permitted, hence the test here for the misc-frame */
if (cnx->first_misc_frame == NULL &&
(length = picoquic_retransmit_needed(cnx, pc, path_x, current_time, next_wake_time, packet,
send_buffer_min_max, &header_length)) > 0) {
/* Check whether it makes sense to add an ACK at the end of the retransmission */
/* Don't do that if it risks mixing clear text and encrypted ack */
bytes_next = picoquic_format_ack_frame(cnx, bytes + length, bytes_max, &more_data,
current_time, pc);
length = bytes_next - bytes;
/* document the send time & overhead */
is_pure_ack = 0;
packet->send_time = current_time;
packet->checksum_overhead = checksum_overhead;
}
else if (ret == 0) {
length = picoquic_predict_packet_header_length(
cnx, packet_type);
packet->ptype = packet_type;
packet->offset = length;
header_length = length;
packet->sequence_number = cnx->pkt_ctx[pc].send_sequence;
packet->send_time = current_time;
packet->send_path = path_x;
bytes_next = bytes + length;
/* If required, prepare challenge and response frames.
* These frames will be sent immediately, regardless of pacing or flow control.
*/
if (path_x->challenge_verified == 0 && path_x->challenge_failed == 0) {
uint64_t next_challenge_time = picoquic_next_challenge_time(cnx, path_x);
if (next_challenge_time <= current_time || path_x->challenge_repeat_count == 0) {
if (path_x->challenge_repeat_count < PICOQUIC_CHALLENGE_REPEAT_MAX) {
int ack_needed = cnx->pkt_ctx[pc].ack_needed;
/* When blocked, repeat the path challenge or wait */
uint8_t* bytes_challenge = bytes_next;
bytes_next = picoquic_format_path_challenge_frame(bytes_next, bytes_max, &more_data, &is_pure_ack,
path_x->challenge[path_x->challenge_repeat_count]);
if (bytes_next > bytes_challenge) {
path_x->challenge_time = current_time;
path_x->challenge_repeat_count++;
is_challenge_padding_needed = 1;
}
/* add an ACK just to be nice */
if (ack_needed) {
bytes_next = picoquic_format_ack_frame(cnx, bytes_next, bytes_max, &more_data,
current_time, pc);
/* Restore the ACK needed flags, because challenges are not reliable. */
cnx->pkt_ctx[pc].ack_needed = ack_needed;
}
}
else {
if (path_x == cnx->path[0]) {
/* Try to find an alternate path */
for (int i = 1; i < cnx->nb_paths; i++) {
if (cnx->path[i]->challenge_failed == 0) {
cnx->path[0] = cnx->path[i];
cnx->path[i] = path_x;
}
}
}
if (path_x == cnx->path[0]) {
DBG_PRINTF("%s\n", "Too many challenge retransmits, disconnect");
picoquic_log_app_message(cnx, "%s", "Too many challenge retransmits, disconnect");
cnx->cnx_state = picoquic_state_disconnected;
if (cnx->callback_fn) {
(void)(cnx->callback_fn)(cnx, 0, NULL, 0, picoquic_callback_close, cnx->callback_ctx, NULL);
}
}
else {
DBG_PRINTF("%s\n", "Too many challenge retransmits, abandon path");
picoquic_log_app_message(cnx, "%s", "Too many challenge retransmits, abandon path");
path_x->challenge_failed = 1;
}
}
}
else {
if (next_challenge_time < *next_wake_time) {
*next_wake_time = next_challenge_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
}
}
if (path_x->response_required) {
uint8_t* bytes_response = bytes_next;
if ((bytes_next = picoquic_format_path_response_frame(bytes_response, bytes_max,
&more_data, &is_pure_ack, path_x->challenge_response)) > bytes_response) {
path_x->response_required = 0;
is_challenge_padding_needed = 1;
}
}
/* Compute the length before pacing block */
length = bytes_next - bytes;
if (cnx->cnx_state != picoquic_state_disconnected && path_x->challenge_verified != 0) {
/* There are no frames yet that would be exempt from pacing control, but if there
* was they should be sent here. */
if (picoquic_is_sending_authorized_by_pacing(cnx, path_x, current_time, next_wake_time)) {
/* Send here the frames that are not exempt from the pacing control,
* but are exempt for congestion control */
if (picoquic_is_ack_needed(cnx, current_time, next_wake_time, pc)) {
uint8_t* bytes_ack = bytes_next;
bytes_next = picoquic_format_ack_frame(cnx, bytes_next, bytes_max, &more_data,
current_time, pc);
ack_sent = (bytes_next > bytes_ack);
}
/* if necessary, prepare the MAX STREAM frames */
if (ret == 0) {
bytes_next = picoquic_format_max_streams_frame_if_needed(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
}
/* If necessary, encode the max data frame */
if (ret == 0){
if (cnx->is_flow_control_limited) {
if (cnx->data_received + (cnx->local_parameters.initial_max_data / 2) > cnx->maxdata_local) {
bytes_next = picoquic_format_max_data_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack,
cnx->local_parameters.initial_max_data);
}
}
else if (2 * cnx->data_received > cnx->maxdata_local) {
bytes_next = picoquic_format_max_data_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack,
picoquic_cc_increased_window(cnx, cnx->maxdata_local));
}
}
/* If necessary, encode the max stream data frames */
if (ret == 0 && cnx->max_stream_data_needed) {
bytes_next = picoquic_format_required_max_stream_data_frames(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
}
/* If present, send misc frame */
while (cnx->first_misc_frame != NULL) {
uint8_t* bytes_misc = bytes_next;
bytes_next = picoquic_format_first_misc_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
if (bytes_next > bytes_misc) {
split_repeat_queued |=
PICOQUIC_IN_RANGE(*bytes_misc, picoquic_frame_type_stream_range_min, picoquic_frame_type_stream_range_max);
}
else {
break;
}
}
/* Compute the length before entering the CC block */
length = bytes_next - bytes;
if (path_x->cwin < path_x->bytes_in_transit) {
cnx->cwin_blocked = 1;
if (cnx->congestion_alg != NULL) {
cnx->congestion_alg->alg_notify(cnx, path_x,
picoquic_congestion_notification_cwin_blocked,
0, 0, 0, 0, current_time);
}
}
else {
/* Send here the frames that are subject to both congestion and pacing control.
* this includes the PMTU probes.
* Check whether PMTU discovery is required. The call will return
* three values: not needed at all, optional, or required.
* If required, PMTU discovery takes priority over sending stream data.
*/
int datagram_tried_and_failed = 0;
int stream_tried_and_failed = 0;
picoquic_pmtu_discovery_status_enum pmtu_discovery_needed = picoquic_is_mtu_probe_needed(cnx, path_x);
/* if present, send tls data */
if (picoquic_is_tls_stream_ready(cnx)) {
bytes_next = picoquic_format_crypto_hs_frame(&cnx->tls_stream[picoquic_epoch_1rtt],
bytes_next, bytes_max, &more_data, &is_pure_ack);
}
if (length > header_length || pmtu_discovery_needed != picoquic_pmtu_discovery_required ||
send_buffer_max <= path_x->send_mtu) {
/* No need or no way to do path MTU discovery, just go on with formatting packets */
/* If there are not enough local CID published, create and advertise */
if (ret == 0) {
bytes_next = picoquic_format_new_local_id_as_needed(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
}
/* Start of CC controlled frames */
if (ret == 0 && length <= header_length) {
if (cnx->first_datagram != NULL) {
bytes_next = picoquic_format_first_datagram_frame(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack);
}
else {
datagram_tried_and_failed = 1;
}
}
/* If present, send stream frames queued for retransmission */
if (ret == 0) {
bytes_next = picoquic_format_stream_frames_queued_for_retransmit(cnx, bytes_next, bytes_max,
&more_data, &is_pure_ack);
}
if (ret == 0 && cnx->is_ack_frequency_updated && cnx->is_ack_frequency_negotiated) {
bytes_next = picoquic_format_ack_frequency_frame(cnx, bytes_next, bytes_max, &more_data);
}
/* Encode the stream frame, or frames */
if (ret == 0 && !split_repeat_queued && bytes_next + 8 < bytes_max) {
bytes_next = picoquic_format_available_stream_frames(cnx, bytes_next, bytes_max, &more_data, &is_pure_ack, &stream_tried_and_failed, &ret);
}
length = bytes_next - bytes;
if (length <= header_length || is_pure_ack) {
/* Mark the bandwidth estimation as application limited */
path_x->delivered_limited_index = path_x->delivered;
/* Notify the peer if something is blocked */
bytes_next = picoquic_format_blocked_frames(cnx, &bytes[length], bytes_max, &more_data, &is_pure_ack);
length = bytes_next - bytes;
}
if (stream_tried_and_failed && datagram_tried_and_failed) {
path_x->last_sender_limited_time = current_time;
}
else if (length <= header_length && stream_tried_and_failed && !cnx->stream_blocked && !cnx->flow_blocked) {
/* Consider redundant retransmission:
* if the redundant retransmission index is null:
* - if the packet loss rate is large enough compared to BDP, set index to last sent packet.
* - if not, do not perform redundant retransmission.
* if the packet contains a stream frame, if that stream is finished, and if the
* data range has not been acked, and it fits: copy it to the data. Move the index to the previous packet.
*/
/* TODO: write that code! */
}
} /* end of PMTU not required */
if (ret == 0 && length <= header_length && send_buffer_max > path_x->send_mtu
&& path_x->cwin > path_x->bytes_in_transit&& pmtu_discovery_needed != picoquic_pmtu_discovery_not_needed) {
/* Since there is no data to send, this is an opportunity to send an MTU probe */
length = picoquic_prepare_mtu_probe(cnx, path_x, header_length, checksum_overhead, bytes, send_buffer_max);
packet->length = length;
packet->send_path = path_x;
packet->is_mtu_probe = 1;
path_x->mtu_probe_sent = 1;
is_pure_ack = 0;
}
} /* end of CC */
} /* End of pacing */
} /* End of challenge verified */
}
if (length <= header_length) {
length = 0;
}
if (cnx->cnx_state != picoquic_state_disconnected) {
if (length > 0){
cnx->pkt_ctx[pc].ack_of_ack_requested |= !is_pure_ack;
if (!cnx->pkt_ctx[pc].ack_of_ack_requested && ack_sent) {
/* If we have sent many ACKs, add a PING to get an ack of ack */
/* The number 24 is chosen to not break any of the unit tests. If the number is
* too small, the PING mechanism can cause delayed end of the connection, or
* early breakage */
bytes_next = bytes + length;
if (bytes_next < bytes_max &&
cnx->pkt_ctx[pc].highest_acknowledged + 24 < cnx->pkt_ctx[pc].send_sequence &&
path_x == cnx->path[0] &&
cnx->pkt_ctx[pc].highest_acknowledged_time + cnx->path[0]->smoothed_rtt < current_time) {
/* Bundle a Ping with ACK, so as to get trigger an Acknowledgement */
*bytes_next++ = picoquic_frame_type_ping;
cnx->pkt_ctx[pc].ack_of_ack_requested = 1;
is_pure_ack = 0;
length = bytes_next - bytes;
}
}
}
if (is_pure_ack == 0)
{
cnx->latest_progress_time = current_time;
}
else if (cnx->keep_alive_interval != 0) {
/* If necessary, encode and send the keep alive packet.
* We only send keep alive packets when no other data is sent.
*/
if (cnx->latest_progress_time + cnx->keep_alive_interval <= current_time && length == 0) {
length = picoquic_predict_packet_header_length(
cnx, packet_type);
packet->ptype = packet_type;
packet->pc = pc;
packet->offset = length;
header_length = length;
packet->sequence_number = cnx->pkt_ctx[pc].send_sequence;
packet->send_path = path_x;
packet->send_time = current_time;
bytes[length++] = picoquic_frame_type_ping;
bytes[length++] = 0;
cnx->latest_progress_time = current_time;
}
else if (cnx->latest_progress_time + cnx->keep_alive_interval < *next_wake_time) {
*next_wake_time = cnx->latest_progress_time + cnx->keep_alive_interval;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
}
if (more_data) {
*next_wake_time = current_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
ret = 0;
}
}
if (ret == 0 && length > header_length) {
/* Ensure that all packets are properly padded before being sent. */
if ((*is_initial_sent && cnx->client_mode) || (is_challenge_padding_needed && length < PICOQUIC_ENFORCED_INITIAL_MTU)){
length = picoquic_pad_to_target_length(bytes, length, (uint32_t)(send_buffer_min_max - checksum_overhead));
}
else {
length = picoquic_pad_to_policy(cnx, bytes, length, (uint32_t)(send_buffer_min_max - checksum_overhead));
}
}
picoquic_finalize_and_protect_packet(cnx, packet,
ret, length, header_length, checksum_overhead,
send_length, send_buffer, send_buffer_min_max,
&path_x->remote_cnxid, &path_x->p_local_cnxid->cnx_id, path_x, current_time);
if (*send_length > 0) {
*next_wake_time = current_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
if (ret == 0 && picoquic_cnx_is_still_logging(cnx)) {
picoquic_log_cc_dump(cnx, current_time);
}
}
return ret;
}
static int picoquic_check_idle_timer(picoquic_cnx_t* cnx, uint64_t* next_wake_time, uint64_t current_time)
{
int ret = 0;
uint64_t idle_timer = 0;
if (cnx->cnx_state >= picoquic_state_ready) {
uint64_t rto = picoquic_current_retransmit_timer(cnx, picoquic_packet_context_application);
idle_timer = cnx->idle_timeout;
if (idle_timer < 3 * rto) {
idle_timer = 3 * rto;
}
idle_timer += cnx->latest_progress_time;
if (idle_timer < cnx->idle_timeout) {
idle_timer = UINT64_MAX;
}
}
else {
idle_timer = cnx->start_time + PICOQUIC_MICROSEC_HANDSHAKE_MAX;
}
if (current_time >= idle_timer) {
/* Too long silence, break it. */
cnx->cnx_state = picoquic_state_disconnected;
ret = PICOQUIC_ERROR_DISCONNECTED;
if (cnx->callback_fn) {
(void)(cnx->callback_fn)(cnx, 0, NULL, 0, picoquic_callback_close, cnx->callback_ctx, NULL);
}
} else if (idle_timer < *next_wake_time) {
*next_wake_time = idle_timer;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
return ret;
}
/* Prepare next packet to send, or nothing.. */
int picoquic_prepare_segment(picoquic_cnx_t* cnx, picoquic_path_t* path_x, picoquic_packet_t* packet,
uint64_t current_time, uint8_t* send_buffer, size_t send_buffer_max, size_t* send_length,
uint64_t* next_wake_time, int* is_initial_sent)
{
int ret = 0;
/* Reset the blocked indicators */
cnx->cwin_blocked = 0;
cnx->flow_blocked = 0;
cnx->stream_blocked = 0;
/* Prepare header -- depend on connection state */
/* TODO: 0-RTT work. */
switch (cnx->cnx_state) {
case picoquic_state_client_init:
case picoquic_state_client_init_sent:
case picoquic_state_client_init_resent:
case picoquic_state_client_renegotiate:
case picoquic_state_client_handshake_start:
case picoquic_state_client_almost_ready:
ret = picoquic_prepare_packet_client_init(cnx, path_x, packet, current_time, send_buffer, send_buffer_max, send_length, next_wake_time, is_initial_sent);
break;
case picoquic_state_server_almost_ready:
case picoquic_state_server_init:
case picoquic_state_server_handshake:
ret = picoquic_prepare_packet_server_init(cnx, path_x, packet, current_time, send_buffer, send_buffer_max, send_length, next_wake_time);
break;
case picoquic_state_server_false_start:
/*
* Manage the end of false start transition, and if needed start
* preparing packet in ready state.
*/
if (cnx->cnx_state == picoquic_state_server_false_start &&
cnx->crypto_context[3].aead_decrypt != NULL) {
picoquic_ready_state_transition(cnx, current_time);
return picoquic_prepare_packet_ready(cnx, path_x, packet, current_time, send_buffer, send_buffer_max, send_length, next_wake_time, is_initial_sent);
}
/* Else, just fall through to almost ready behavior.
*/
case picoquic_state_client_ready_start:
ret = picoquic_prepare_packet_almost_ready(cnx, path_x, packet, current_time, send_buffer, send_buffer_max, send_length, next_wake_time, is_initial_sent);
break;
case picoquic_state_ready:
ret = picoquic_prepare_packet_ready(cnx, path_x, packet, current_time, send_buffer, send_buffer_max, send_length, next_wake_time, is_initial_sent);
break;
case picoquic_state_handshake_failure:
case picoquic_state_handshake_failure_resend:
case picoquic_state_disconnecting:
case picoquic_state_closing_received:
case picoquic_state_closing:
case picoquic_state_draining:
ret = picoquic_prepare_packet_closing(cnx, path_x, packet, current_time, send_buffer, send_buffer_max, send_length, next_wake_time);
break;
case picoquic_state_disconnected:
ret = PICOQUIC_ERROR_DISCONNECTED;
break;
case picoquic_state_client_retry_received:
DBG_PRINTF("Unexpected connection state: %d\n", cnx->cnx_state);
ret = PICOQUIC_ERROR_UNEXPECTED_STATE;
break;
default:
DBG_PRINTF("Unexpected connection state: %d\n", cnx->cnx_state);
ret = PICOQUIC_ERROR_UNEXPECTED_STATE;
break;
}
return ret;
}
/*
* The version 1 of Quic only supports path migration, not full multipath.
* This code finds whether there is a path being probed that could become the
* default path, or that needs an immediate challenge sent or replied to.
*
* If no other path is suitable, the code returns the default path.
*/
static int picoquic_select_next_path(picoquic_cnx_t * cnx, uint64_t current_time, uint64_t * next_wake_time)
{
int path_id = -1;
/* Select the path */
for (int i = 1; i < cnx->nb_paths; i++) {
if (cnx->path[i]->path_is_demoted) {
continue;
}
else if (cnx->path[i]->challenge_failed) {
picoquic_demote_path(cnx, i, current_time);
continue;
}
else if (cnx->path[i]->challenge_verified) {
/* TODO: selection logic if multiple paths are available! */
/* This path becomes the new default */
picoquic_promote_path_to_default(cnx, i, current_time);
path_id = 0;
break;
}
else if (path_id < 0) {
if (cnx->path[i]->response_required) {
path_id = i;
}
else if (cnx->path[i]->challenge_required) {
uint64_t next_challenge_time = picoquic_next_challenge_time(cnx, cnx->path[i]);
if (cnx->path[i]->challenge_repeat_count == 0 ||
current_time >= next_challenge_time) {
/* will try this path, unless a validated path came in */
path_id = i;
}
else if (next_challenge_time < *next_wake_time) {
*next_wake_time = next_challenge_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
}
}
}
if (path_id < 0) {
path_id = 0;
}
return path_id;
}
/* Prepare next packet to send, or nothing.. */
int picoquic_prepare_packet_ex(picoquic_cnx_t* cnx,
uint64_t current_time, uint8_t* send_buffer, size_t send_buffer_max, size_t* send_length,
struct sockaddr_storage * p_addr_to, struct sockaddr_storage * p_addr_from, int* if_index, size_t* send_msg_size)
{
int ret;
picoquic_packet_t * packet = NULL;
struct sockaddr_storage addr_to_log;
struct sockaddr_storage addr_from_log;
int is_initial_sent = 0;
uint64_t next_wake_time = cnx->latest_progress_time + 2*PICOQUIC_MICROSEC_SILENCE_MAX;
uint64_t initial_next_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
if (cnx->recycle_sooner_needed) {
picoquic_process_sooner_packets(cnx, current_time);
}
memset(&addr_to_log, 0, sizeof(addr_to_log));
memset(&addr_from_log, 0, sizeof(addr_from_log));
*send_length = 0;
ret = picoquic_check_idle_timer(cnx, &next_wake_time, current_time);
if (send_buffer_max < PICOQUIC_ENFORCED_INITIAL_MTU) {
DBG_PRINTF("Invalid buffer size: %zu", send_buffer_max);
ret = -1;
}
if (ret == 0) {
int path_id;
/* Remove delete paths */
if (cnx->path_demotion_needed) {
picoquic_delete_abandoned_paths(cnx, current_time, &next_wake_time);
}
/* Check whether to insert a hole in the sequence of packets */
if (cnx->pkt_ctx[0].send_sequence >= cnx->pkt_ctx[0].next_sequence_hole) {
picoquic_insert_hole_in_send_sequence_if_needed(cnx, current_time, &next_wake_time);
}
/* Select the next path, and the corresponding addresses */
path_id = picoquic_select_next_path(cnx, current_time, &next_wake_time);
picoquic_store_addr(&addr_to_log, (struct sockaddr*) & cnx->path[path_id]->peer_addr);
if (cnx->path[path_id]->local_addr.ss_family != 0) {
picoquic_store_addr(&addr_from_log, (struct sockaddr*) & cnx->path[path_id]->local_addr);
}
if (p_addr_to != NULL) {
picoquic_store_addr(p_addr_to, (struct sockaddr*) & cnx->path[path_id]->peer_addr);
}
if (p_addr_from != NULL) {
picoquic_store_addr(p_addr_from, (struct sockaddr*) & cnx->path[path_id]->local_addr);
}
if (if_index != NULL) {
*if_index = cnx->path[path_id]->if_index_dest;
}
/* Send the available packets */
if (send_msg_size != NULL) {
*send_msg_size = cnx->path[path_id]->send_mtu;
}
initial_next_time = next_wake_time;
while (ret == 0)
{
/* Create a new packet */
size_t packet_size = 0;
size_t packet_max = send_buffer_max - *send_length;
uint8_t* packet_buffer = send_buffer + *send_length;
/* Reset the wake time to the initial value after sending packets */
next_wake_time = initial_next_time;
/* Send the available segments in that packet. */
while (ret == 0)
{
size_t available = packet_max;
size_t segment_length = 0;
if (packet_size > 0) {
packet_max = cnx->path[path_id]->send_mtu;
if (packet_max < packet_size + PICOQUIC_MIN_SEGMENT_SIZE) {
break;
}
else {
available = packet_max - packet_size;
}
}
packet = picoquic_create_packet(cnx->quic);
if (packet == NULL) {
ret = PICOQUIC_ERROR_MEMORY;
break;
}
else {
ret = picoquic_prepare_segment(cnx, cnx->path[path_id], packet, current_time,
packet_buffer + packet_size, available, &segment_length, &next_wake_time, &is_initial_sent);
if (ret == 0) {
packet_size += segment_length;
if (packet->length == 0) {
/* Nothing more to send */
picoquic_recycle_packet(cnx->quic, packet);
break;
}
else if (packet->ptype == picoquic_packet_1rtt_protected) {
/* Cannot coalesce packets after 1 rtt packet */
break;
}
else if (segment_length == 0) {
DBG_PRINTF("Send bug: segment length = %zu, packet length = %zu\n", segment_length, packet->length);
break;
}
}
else {
picoquic_recycle_packet(cnx->quic, packet);
packet = NULL;
if (packet_size != 0) {
ret = 0;
}
break;
}
if (cnx->quic->dont_coalesce_init) {
break;
}
}
}
if (packet_size > 0) {
cnx->nb_packets_sent++;
/* if needed, log that the packet is sent */
picoquic_log_pdu(cnx, 0, current_time,
(struct sockaddr*) & addr_to_log, (struct sockaddr*) & addr_from_log, packet_size);
}
/* Update the wake up time for the connection */
if (packet_size > 0 || cnx->cnx_state == picoquic_state_disconnected) {
next_wake_time = current_time;
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
/* Account for the bytes in the packet. */
*send_length += packet_size;
/* Check whether to keep coalescing multiple packets in the send buffer */
if (send_msg_size == NULL) {
break;
}
else if (packet_size > *send_msg_size) {
/* This can only happen for the first packet in a batch. */
*send_msg_size = packet_size;
}
else if (packet_size != *send_msg_size) {
if (*send_length > 0) {
if (packet_size == 0 && *send_length < 4*(*send_msg_size)) {
if (cnx->path[0]->cwin <= cnx->path[0]->bytes_in_transit) {
cnx->nb_trains_blocked_cwin++;
}
else if (cnx->path[0]->pacing_bucket_nanosec < cnx->path[0]->pacing_packet_time_nanosec){
cnx->nb_trains_blocked_pacing++;
}
else {
cnx->nb_trains_blocked_others++;
}
}
else {
cnx->nb_trains_short++;
}
}
break;
}
else if (*send_length + *send_msg_size > send_buffer_max) {
break;
}
}
if (*send_length > 0) {
cnx->nb_trains_sent++;
}
}
picoquic_reinsert_by_wake_time(cnx->quic, cnx, next_wake_time);
return ret;
}
int picoquic_prepare_packet(picoquic_cnx_t* cnx,
uint64_t current_time, uint8_t* send_buffer, size_t send_buffer_max, size_t* send_length,
struct sockaddr_storage* p_addr_to, struct sockaddr_storage* p_addr_from, int* if_index)
{
return picoquic_prepare_packet_ex(cnx, current_time, send_buffer, send_buffer_max, send_length,
p_addr_to, p_addr_from, if_index, NULL);
}
int picoquic_close(picoquic_cnx_t* cnx, uint16_t reason_code)
{
int ret = 0;
if (cnx->cnx_state == picoquic_state_ready ||
cnx->cnx_state == picoquic_state_server_false_start || cnx->cnx_state == picoquic_state_client_ready_start) {
cnx->cnx_state = picoquic_state_disconnecting;
cnx->application_error = reason_code;
} else if (cnx->cnx_state < picoquic_state_client_ready_start) {
cnx->cnx_state = picoquic_state_handshake_failure;
cnx->application_error = 0;
cnx->local_error = PICOQUIC_TRANSPORT_APPLICATION_ERROR;
} else {
ret = -1;
}
cnx->offending_frame_type = 0;
picoquic_reinsert_by_wake_time(cnx->quic, cnx, picoquic_get_quic_time(cnx->quic));
return ret;
}
/* Quic context level call.
* will send a stateless packet if one is queued, or ask the first connection in
* the wake list to prepare a packet */
int picoquic_prepare_next_packet_ex(picoquic_quic_t* quic,
uint64_t current_time, uint8_t* send_buffer, size_t send_buffer_max, size_t* send_length,
struct sockaddr_storage* p_addr_to, struct sockaddr_storage* p_addr_from, int * if_index,
picoquic_connection_id_t * log_cid, picoquic_cnx_t** p_last_cnx, size_t * send_msg_size)
{
int ret = 0;
picoquic_stateless_packet_t* sp = picoquic_dequeue_stateless_packet(quic);
if (p_last_cnx) {
*p_last_cnx = NULL;
}
if (sp != NULL) {
if (sp->length > send_buffer_max) {
*send_length = 0;
}
else {
memcpy(send_buffer, sp->bytes, sp->length);
*send_length = sp->length;
picoquic_store_addr(p_addr_to, (struct sockaddr*) & sp->addr_to);
picoquic_store_addr(p_addr_from, (struct sockaddr*) & sp->addr_local);
*if_index = sp->if_index_local;
if (log_cid != NULL) {
*log_cid = sp->initial_cid;
}
}
picoquic_delete_stateless_packet(sp);
}
else {
picoquic_cnx_t* cnx = picoquic_get_earliest_cnx_to_wake(quic, current_time);
if (cnx == NULL) {
*send_length = 0;
}
else {
ret = picoquic_prepare_packet_ex(cnx, current_time, send_buffer, send_buffer_max, send_length, p_addr_to, p_addr_from,
if_index, send_msg_size);
if (log_cid != NULL) {
*log_cid = cnx->initial_cnxid;
}
if (ret == PICOQUIC_ERROR_DISCONNECTED) {
ret = 0;
picoquic_log_app_message(cnx, "Closed. Retrans= %d, spurious= %d, max sp gap = %d, max sp delay = %d, dg-coal: %f",
(int)cnx->nb_retransmission_total, (int)cnx->nb_spurious,
(int)cnx->path[0]->max_reorder_gap, (int)cnx->path[0]->max_spurious_rtt,
(cnx->nb_trains_sent > 0) ? ((double)cnx->nb_packets_sent / (double)cnx->nb_trains_sent) : 0.0);
if (quic->F_log != NULL) {
fflush(quic->F_log);
}
if (cnx->f_binlog != NULL) {
fflush(cnx->f_binlog);
}
if (cnx->client_mode) {
/* Do not unilaterally delete the connection context, as it was set by the application */
picoquic_reinsert_by_wake_time(cnx->quic, cnx, UINT64_MAX);
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
}
else {
picoquic_delete_cnx(cnx);
}
}
else {
if (*if_index == -1) {
*if_index = picoquic_get_local_if_index(cnx);
}
if (p_last_cnx) {
*p_last_cnx = cnx;
}
}
}
}
return ret;
}
int picoquic_prepare_next_packet(picoquic_quic_t* quic,
uint64_t current_time, uint8_t* send_buffer, size_t send_buffer_max, size_t* send_length,
struct sockaddr_storage* p_addr_to, struct sockaddr_storage* p_addr_from, int* if_index,
picoquic_connection_id_t* log_cid, picoquic_cnx_t** p_last_cnx)
{
return picoquic_prepare_next_packet_ex(quic, current_time, send_buffer, send_buffer_max, send_length,
p_addr_to, p_addr_from, if_index, log_cid, p_last_cnx, NULL);
}