2021-01-06 14:00:05 +00:00
|
|
|
/* Copyright (c) 2017 - 2021 LiteSpeed Technologies Inc. See LICENSE. */
|
2017-09-22 21:00:03 +00:00
|
|
|
#ifndef LSQUIC_SEND_CTL_H
|
|
|
|
#define LSQUIC_SEND_CTL_H 1
|
|
|
|
|
|
|
|
#include <sys/queue.h>
|
|
|
|
|
|
|
|
#include "lsquic_types.h"
|
|
|
|
|
|
|
|
#ifndef LSQUIC_SEND_STATS
|
|
|
|
# define LSQUIC_SEND_STATS 1
|
|
|
|
#endif
|
|
|
|
|
|
|
|
TAILQ_HEAD(lsquic_packets_tailq, lsquic_packet_out);
|
|
|
|
|
|
|
|
struct lsquic_packet_out;
|
|
|
|
struct ack_info;
|
|
|
|
struct lsquic_alarmset;
|
|
|
|
struct lsquic_engine_public;
|
|
|
|
struct lsquic_conn_public;
|
2019-09-11 15:27:58 +00:00
|
|
|
struct network_path;
|
2017-09-22 21:00:03 +00:00
|
|
|
struct ver_neg;
|
2019-09-11 15:27:58 +00:00
|
|
|
enum pns;
|
2020-09-15 20:42:13 +00:00
|
|
|
struct to_coal;
|
2017-09-22 21:00:03 +00:00
|
|
|
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
enum buf_packet_type { BPT_HIGHEST_PRIO, BPT_OTHER_PRIO, };
|
|
|
|
|
|
|
|
struct buf_packet_q
|
|
|
|
{
|
|
|
|
struct lsquic_packets_tailq bpq_packets;
|
|
|
|
unsigned bpq_count;
|
|
|
|
};
|
|
|
|
|
2018-03-09 19:17:39 +00:00
|
|
|
enum send_ctl_flags {
|
|
|
|
SC_TCID0 = (1 << 0),
|
|
|
|
SC_NSTP = (1 << 2),
|
|
|
|
SC_PACE = (1 << 3),
|
|
|
|
SC_SCHED_TICK = (1 << 4),
|
|
|
|
SC_BUFFER_STREAM= (1 << 5),
|
|
|
|
SC_WAS_QUIET = (1 << 6),
|
2019-09-11 15:27:58 +00:00
|
|
|
SC_IETF = (1 << 7),
|
|
|
|
#define SCBIT_LOST_ACK_SHIFT 8
|
|
|
|
SC_LOST_ACK_INIT= 1 << 8,
|
|
|
|
SC_LOST_ACK_HSK = SC_LOST_ACK_INIT << PNS_HSK,
|
|
|
|
SC_LOST_ACK_APP = SC_LOST_ACK_INIT << PNS_APP,
|
|
|
|
SC_1RTT_ACKED = 1 << 11,
|
2019-09-12 18:39:50 +00:00
|
|
|
SC_APP_LIMITED = 1 << 12,
|
2019-09-18 15:22:20 +00:00
|
|
|
SC_ECN = 1 << 13,
|
2019-11-22 05:40:05 +00:00
|
|
|
SC_QL_BITS = 1 << 14,
|
2019-12-11 14:38:58 +00:00
|
|
|
SC_SANITY_CHECK = 1 << 15,
|
|
|
|
SC_CIDLEN = 1 << 16, /* sc_cidlen is set */
|
2019-12-30 16:29:05 +00:00
|
|
|
SC_POISON = 1 << 17, /* poisoned packet exists */
|
2020-09-15 20:42:13 +00:00
|
|
|
SC_CLEANUP_BBR = 1 << 18,
|
2020-10-07 13:41:26 +00:00
|
|
|
SC_ACK_RECV_INIT= 1 << 19,
|
|
|
|
SC_ACK_RECV_HSK = SC_ACK_RECV_INIT << PNS_HSK,
|
|
|
|
SC_ACK_RECV_APP = SC_ACK_RECV_INIT << PNS_APP,
|
2020-11-11 12:50:03 +00:00
|
|
|
SC_ROUGH_RTT = 1 << 22,
|
2020-12-09 14:11:03 +00:00
|
|
|
#if LSQUIC_DEVEL
|
|
|
|
SC_DYN_PTHRESH = 1 << 31u, /* dynamic packet threshold enabled */
|
|
|
|
#endif
|
2018-03-09 19:17:39 +00:00
|
|
|
};
|
|
|
|
|
2017-09-22 21:00:03 +00:00
|
|
|
typedef struct lsquic_send_ctl {
|
2018-03-09 19:17:39 +00:00
|
|
|
/* The first section consists of struct members which are used in the
|
|
|
|
* time-critical lsquic_send_ctl_got_ack() in the approximate order
|
|
|
|
* of usage.
|
|
|
|
*/
|
|
|
|
lsquic_senhist_t sc_senhist;
|
|
|
|
enum send_ctl_flags sc_flags;
|
2019-09-11 15:27:58 +00:00
|
|
|
enum ecn sc_ecn;
|
2018-03-09 19:17:39 +00:00
|
|
|
unsigned sc_n_stop_waiting;
|
2019-09-11 15:27:58 +00:00
|
|
|
struct lsquic_packets_tailq sc_unacked_packets[N_PNS];
|
2018-03-09 19:17:39 +00:00
|
|
|
lsquic_packno_t sc_largest_acked_packno;
|
|
|
|
lsquic_time_t sc_largest_acked_sent_time;
|
2019-09-11 15:27:58 +00:00
|
|
|
lsquic_time_t sc_last_sent_time;
|
|
|
|
lsquic_time_t sc_last_rto_time;
|
2020-05-06 13:35:33 +00:00
|
|
|
int (*sc_can_send)(struct lsquic_send_ctl *);
|
2018-03-09 19:17:39 +00:00
|
|
|
unsigned sc_bytes_unacked_retx;
|
|
|
|
unsigned sc_bytes_scheduled;
|
2020-09-15 20:42:13 +00:00
|
|
|
struct adaptive_cc sc_adaptive_cc;
|
2019-09-11 15:27:58 +00:00
|
|
|
const struct cong_ctl_if *sc_ci;
|
2020-09-15 20:42:13 +00:00
|
|
|
void *sc_cong_ctl;
|
2018-03-09 19:17:39 +00:00
|
|
|
struct lsquic_engine_public *sc_enpub;
|
|
|
|
unsigned sc_bytes_unacked_all;
|
|
|
|
unsigned sc_n_in_flight_all;
|
|
|
|
unsigned sc_n_in_flight_retx;
|
|
|
|
unsigned sc_n_consec_rtos;
|
|
|
|
unsigned sc_n_hsk;
|
|
|
|
unsigned sc_n_tlp;
|
2019-09-11 15:27:58 +00:00
|
|
|
enum quic_ft_bit sc_retx_frames;
|
2018-03-09 19:17:39 +00:00
|
|
|
struct lsquic_alarmset *sc_alset;
|
|
|
|
|
|
|
|
/* Second section: everything else. */
|
2017-09-22 21:00:03 +00:00
|
|
|
struct lsquic_packets_tailq sc_scheduled_packets,
|
2020-10-13 12:20:25 +00:00
|
|
|
sc_0rtt_stash,
|
2017-09-22 21:00:03 +00:00
|
|
|
sc_lost_packets;
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
struct buf_packet_q sc_buffered_packets[BPT_OTHER_PRIO + 1];
|
2017-09-22 21:00:03 +00:00
|
|
|
const struct ver_neg *sc_ver_neg;
|
|
|
|
struct lsquic_conn_public *sc_conn_pub;
|
|
|
|
struct pacer sc_pacer;
|
|
|
|
lsquic_packno_t sc_cur_packno;
|
|
|
|
lsquic_packno_t sc_largest_sent_at_cutback;
|
|
|
|
lsquic_packno_t sc_max_rtt_packno;
|
|
|
|
/* sc_largest_ack2ed is the packet number sent by peer that we acked and
|
|
|
|
* we know that our ACK was received by peer. This is used to determine
|
|
|
|
* the receive history cutoff point for the purposes of generating ACK
|
|
|
|
* frames in the absense of STOP_WAITING frames. Used when NSTP option
|
|
|
|
* is set. (The "ack2ed" is odd enough to not be confused with anything
|
|
|
|
* else and it is not insanely long.)
|
|
|
|
*/
|
2019-09-11 15:27:58 +00:00
|
|
|
lsquic_packno_t sc_largest_ack2ed[N_PNS];
|
2019-03-19 20:23:50 +00:00
|
|
|
/* sc_largest_acked is the largest packet number in PNS_APP packet number
|
|
|
|
* space sent by peer for which we generated (not necessarily sent) an ACK.
|
|
|
|
* This information is used to drop stale ACK frames from packets in
|
|
|
|
* buffered queues.
|
|
|
|
*/
|
2020-12-09 14:11:03 +00:00
|
|
|
/* XXX We have both sc_largest_acked_packno and sc_largest_acked. Rename
|
|
|
|
* the latter to make the code more readable.
|
|
|
|
*/
|
2019-03-19 20:23:50 +00:00
|
|
|
lsquic_packno_t sc_largest_acked;
|
2017-09-22 21:00:03 +00:00
|
|
|
lsquic_time_t sc_loss_to;
|
2019-09-11 15:27:58 +00:00
|
|
|
uint64_t sc_ecn_total_acked[N_PNS];
|
|
|
|
uint64_t sc_ecn_ce_cnt[N_PNS];
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
struct
|
|
|
|
{
|
2019-09-11 15:27:58 +00:00
|
|
|
lsquic_stream_id_t stream_id;
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
enum buf_packet_type packet_type;
|
|
|
|
} sc_cached_bpt;
|
2017-09-22 21:00:03 +00:00
|
|
|
unsigned sc_next_limit;
|
|
|
|
unsigned sc_n_scheduled;
|
2019-04-01 15:41:55 +00:00
|
|
|
enum packno_bits sc_max_packno_bits;
|
2017-09-22 21:00:03 +00:00
|
|
|
#if LSQUIC_SEND_STATS
|
|
|
|
struct {
|
|
|
|
unsigned n_total_sent,
|
|
|
|
n_resent,
|
|
|
|
n_delayed;
|
|
|
|
} sc_stats;
|
|
|
|
#endif
|
2019-09-11 15:27:58 +00:00
|
|
|
unsigned char *sc_token;
|
|
|
|
size_t sc_token_sz;
|
|
|
|
unsigned sc_retry_count;
|
|
|
|
unsigned sc_rt_count; /* Count round trips */
|
|
|
|
lsquic_packno_t sc_cur_rt_end;
|
2019-12-30 16:29:05 +00:00
|
|
|
lsquic_packno_t sc_gap;
|
2019-11-22 05:40:05 +00:00
|
|
|
unsigned sc_loss_count; /* Used to set loss bit */
|
|
|
|
unsigned sc_square_count;/* Used to set square bit */
|
2020-12-09 14:11:03 +00:00
|
|
|
unsigned sc_reord_thresh;
|
2019-12-11 14:38:58 +00:00
|
|
|
signed char sc_cidlen; /* For debug purposes */
|
2017-09-22 21:00:03 +00:00
|
|
|
} lsquic_send_ctl_t;
|
|
|
|
|
|
|
|
void
|
|
|
|
lsquic_send_ctl_init (lsquic_send_ctl_t *, struct lsquic_alarmset *,
|
|
|
|
struct lsquic_engine_public *, const struct ver_neg *,
|
2019-09-11 15:27:58 +00:00
|
|
|
struct lsquic_conn_public *, enum send_ctl_flags);
|
2017-09-22 21:00:03 +00:00
|
|
|
|
|
|
|
int
|
2019-09-11 15:27:58 +00:00
|
|
|
lsquic_send_ctl_sent_packet (lsquic_send_ctl_t *, struct lsquic_packet_out *);
|
2017-09-22 21:00:03 +00:00
|
|
|
|
2021-02-10 13:51:11 +00:00
|
|
|
void
|
|
|
|
lsquic_send_ctl_mtu_not_sent (struct lsquic_send_ctl *ctl,
|
|
|
|
struct lsquic_packet_out *);
|
|
|
|
|
2017-09-22 21:00:03 +00:00
|
|
|
int
|
|
|
|
lsquic_send_ctl_got_ack (lsquic_send_ctl_t *, const struct ack_info *,
|
2019-09-11 15:27:58 +00:00
|
|
|
lsquic_time_t, lsquic_time_t);
|
2017-09-22 21:00:03 +00:00
|
|
|
|
|
|
|
lsquic_packno_t
|
|
|
|
lsquic_send_ctl_smallest_unacked (lsquic_send_ctl_t *ctl);
|
|
|
|
|
|
|
|
int
|
|
|
|
lsquic_send_ctl_have_unacked_stream_frames (const lsquic_send_ctl_t *);
|
|
|
|
|
2020-11-24 13:51:36 +00:00
|
|
|
int
|
|
|
|
lsquic_send_ctl_have_unacked_retx_data (const struct lsquic_send_ctl *);
|
|
|
|
|
2017-09-22 21:00:03 +00:00
|
|
|
void
|
|
|
|
lsquic_send_ctl_cleanup (lsquic_send_ctl_t *);
|
|
|
|
|
|
|
|
int
|
|
|
|
lsquic_send_ctl_can_send (lsquic_send_ctl_t *ctl);
|
|
|
|
|
|
|
|
void
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
lsquic_send_ctl_scheduled_one (lsquic_send_ctl_t *, struct lsquic_packet_out *);
|
2017-09-22 21:00:03 +00:00
|
|
|
|
|
|
|
void
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
lsquic_send_ctl_delayed_one (lsquic_send_ctl_t *, struct lsquic_packet_out *);
|
2017-09-22 21:00:03 +00:00
|
|
|
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
struct lsquic_packet_out *
|
2020-09-15 20:42:13 +00:00
|
|
|
lsquic_send_ctl_next_packet_to_send (struct lsquic_send_ctl *,
|
|
|
|
const struct to_coal *);
|
2017-09-22 21:00:03 +00:00
|
|
|
|
2020-06-18 13:45:44 +00:00
|
|
|
int
|
|
|
|
lsquic_send_ctl_next_packet_to_send_predict (struct lsquic_send_ctl *);
|
|
|
|
|
2017-09-22 21:00:03 +00:00
|
|
|
void
|
|
|
|
lsquic_send_ctl_expire_all (lsquic_send_ctl_t *ctl);
|
|
|
|
|
|
|
|
#define lsquic_send_ctl_n_in_flight(ctl) (+(ctl)->sc_n_in_flight)
|
|
|
|
|
|
|
|
#define lsquic_send_ctl_n_scheduled(ctl) (+(ctl)->sc_n_scheduled)
|
|
|
|
|
2019-09-11 15:27:58 +00:00
|
|
|
#define lsquic_send_ctl_largest_ack2ed(ctl, pns) \
|
|
|
|
(+(ctl)->sc_largest_ack2ed[pns])
|
2017-09-22 21:00:03 +00:00
|
|
|
|
|
|
|
void
|
2019-12-11 14:38:58 +00:00
|
|
|
lsquic_send_ctl_do_sanity_check (const struct lsquic_send_ctl *ctl);
|
|
|
|
|
2020-01-30 22:12:47 +00:00
|
|
|
#ifndef NDEBUG
|
2019-12-11 14:38:58 +00:00
|
|
|
#define lsquic_send_ctl_sanity_check(ctl) do { \
|
|
|
|
if ((ctl)->sc_flags & SC_SANITY_CHECK) \
|
|
|
|
lsquic_send_ctl_do_sanity_check(ctl); \
|
|
|
|
} while (0)
|
2020-01-30 22:12:47 +00:00
|
|
|
#else
|
|
|
|
#define lsquic_send_ctl_sanity_check(ctl)
|
|
|
|
#endif
|
2017-09-22 21:00:03 +00:00
|
|
|
|
|
|
|
int
|
|
|
|
lsquic_send_ctl_have_outgoing_stream_frames (const lsquic_send_ctl_t *);
|
|
|
|
|
|
|
|
int
|
|
|
|
lsquic_send_ctl_have_outgoing_retx_frames (const lsquic_send_ctl_t *);
|
|
|
|
|
2019-09-11 15:27:58 +00:00
|
|
|
struct lsquic_packet_out *
|
|
|
|
lsquic_send_ctl_last_scheduled (struct lsquic_send_ctl *, enum packnum_space,
|
|
|
|
const struct network_path *, int);
|
2017-09-22 21:00:03 +00:00
|
|
|
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
struct lsquic_packet_out *
|
2019-09-11 15:27:58 +00:00
|
|
|
lsquic_send_ctl_new_packet_out (lsquic_send_ctl_t *, unsigned,
|
|
|
|
enum packnum_space, const struct network_path *);
|
2017-09-22 21:00:03 +00:00
|
|
|
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
struct lsquic_packet_out *
|
2019-09-11 15:27:58 +00:00
|
|
|
lsquic_send_ctl_get_writeable_packet (lsquic_send_ctl_t *, enum packnum_space,
|
|
|
|
unsigned need_at_least, const struct network_path *, int, int *is_err);
|
2017-09-22 21:00:03 +00:00
|
|
|
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
struct lsquic_packet_out *
|
|
|
|
lsquic_send_ctl_get_packet_for_stream (lsquic_send_ctl_t *,
|
2019-09-11 15:27:58 +00:00
|
|
|
unsigned need_at_least, const struct network_path *,
|
|
|
|
const struct lsquic_stream *);
|
|
|
|
|
|
|
|
struct lsquic_packet_out *
|
|
|
|
lsquic_send_ctl_get_packet_for_crypto (struct lsquic_send_ctl *ctl,
|
|
|
|
unsigned need_at_least, enum packnum_space, const struct network_path *);
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
|
2017-09-22 21:00:03 +00:00
|
|
|
unsigned
|
|
|
|
lsquic_send_ctl_reschedule_packets (lsquic_send_ctl_t *);
|
|
|
|
|
2019-09-11 15:27:58 +00:00
|
|
|
#define lsquic_send_ctl_lost_ack(ctl) \
|
|
|
|
(((ctl)->sc_flags & (SC_LOST_ACK_INIT|SC_LOST_ACK_HSK|SC_LOST_ACK_APP)) \
|
|
|
|
>> SCBIT_LOST_ACK_SHIFT)
|
2017-09-22 21:00:03 +00:00
|
|
|
|
2019-09-11 15:27:58 +00:00
|
|
|
#define lsquic_send_ctl_scheduled_ack(ctl, pns, acked) do { \
|
|
|
|
(ctl)->sc_flags &= ~(SC_LOST_ACK_INIT << pns); \
|
|
|
|
if (PNS_APP == pns) \
|
|
|
|
(ctl)->sc_largest_acked = acked; \
|
2017-09-22 21:00:03 +00:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
void
|
|
|
|
lsquic_send_ctl_set_tcid0 (lsquic_send_ctl_t *, int);
|
|
|
|
|
|
|
|
#define lsquic_send_ctl_turn_nstp_on(ctl) ((ctl)->sc_flags |= SC_NSTP)
|
|
|
|
|
|
|
|
void
|
2019-09-11 15:27:58 +00:00
|
|
|
lsquic_send_ctl_elide_stream_frames (lsquic_send_ctl_t *, lsquic_stream_id_t);
|
2017-09-22 21:00:03 +00:00
|
|
|
|
|
|
|
int
|
|
|
|
lsquic_send_ctl_squeeze_sched (lsquic_send_ctl_t *);
|
|
|
|
|
2018-02-26 21:01:16 +00:00
|
|
|
#define lsquic_send_ctl_maybe_squeeze_sched(ctl) ( \
|
|
|
|
(ctl)->sc_n_scheduled && lsquic_send_ctl_squeeze_sched(ctl) \
|
|
|
|
)
|
|
|
|
|
2017-09-22 21:00:03 +00:00
|
|
|
/* Same return value as for squeezing, but without actual squeezing. */
|
|
|
|
int
|
|
|
|
lsquic_send_ctl_have_delayed_packets (const lsquic_send_ctl_t *ctl);
|
|
|
|
|
|
|
|
void
|
|
|
|
lsquic_send_ctl_reset_packnos (lsquic_send_ctl_t *);
|
|
|
|
|
|
|
|
void
|
2019-09-11 15:27:58 +00:00
|
|
|
lsquic_send_ctl_ack_to_front (struct lsquic_send_ctl *, unsigned n_acks);
|
2017-09-22 21:00:03 +00:00
|
|
|
|
2019-09-11 15:27:58 +00:00
|
|
|
#define lsquic_send_ctl_n_stop_waiting(ctl) \
|
|
|
|
(+(ctl)->sc_n_stop_waiting)
|
2017-09-22 21:00:03 +00:00
|
|
|
|
|
|
|
#define lsquic_send_ctl_n_stop_waiting_reset(ctl) do { \
|
|
|
|
(ctl)->sc_n_stop_waiting = 0; \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
void
|
|
|
|
lsquic_send_ctl_drop_scheduled (lsquic_send_ctl_t *);
|
|
|
|
|
2019-09-11 15:27:58 +00:00
|
|
|
#define lsquic_send_ctl_tick_in(ctl, now) do { \
|
2017-09-22 21:00:03 +00:00
|
|
|
if ((ctl)->sc_flags & SC_PACE) \
|
|
|
|
{ \
|
|
|
|
(ctl)->sc_flags |= SC_SCHED_TICK; \
|
2020-03-12 12:41:53 +00:00
|
|
|
lsquic_pacer_tick_in(&(ctl)->sc_pacer, now); \
|
2017-09-22 21:00:03 +00:00
|
|
|
} \
|
2019-09-12 18:39:50 +00:00
|
|
|
(ctl)->sc_flags &= ~SC_APP_LIMITED; \
|
2017-09-22 21:00:03 +00:00
|
|
|
} while (0)
|
|
|
|
|
2019-09-11 15:27:58 +00:00
|
|
|
#define lsquic_send_ctl_tick_out(ctl) do { \
|
|
|
|
if ((ctl)->sc_flags & SC_PACE) \
|
2020-03-12 12:41:53 +00:00
|
|
|
lsquic_pacer_tick_out(&(ctl)->sc_pacer); \
|
2019-09-11 15:27:58 +00:00
|
|
|
} while (0)
|
|
|
|
|
2018-04-09 13:39:38 +00:00
|
|
|
#define lsquic_send_ctl_next_pacer_time(ctl) ( \
|
|
|
|
((ctl)->sc_flags & SC_PACE) \
|
2020-03-12 12:41:53 +00:00
|
|
|
&& lsquic_pacer_delayed(&(ctl)->sc_pacer) \
|
|
|
|
? lsquic_pacer_next_sched(&(ctl)->sc_pacer) \
|
2018-04-09 13:39:38 +00:00
|
|
|
: 0 )
|
|
|
|
|
2019-04-01 15:41:55 +00:00
|
|
|
enum packno_bits
|
2020-10-07 13:41:26 +00:00
|
|
|
lsquic_send_ctl_packno_bits (struct lsquic_send_ctl *, enum packnum_space);
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
|
|
|
|
int
|
|
|
|
lsquic_send_ctl_schedule_buffered (lsquic_send_ctl_t *, enum buf_packet_type);
|
|
|
|
|
2018-04-09 13:39:38 +00:00
|
|
|
#define lsquic_send_ctl_has_buffered(ctl) ( \
|
|
|
|
TAILQ_FIRST(&(ctl)->sc_buffered_packets[BPT_HIGHEST_PRIO].bpq_packets) \
|
|
|
|
|| TAILQ_FIRST(&(ctl)->sc_buffered_packets[BPT_OTHER_PRIO].bpq_packets ))
|
|
|
|
|
2019-10-15 21:02:21 +00:00
|
|
|
#define lsquic_send_ctl_has_buffered_high(ctl) ( \
|
|
|
|
!TAILQ_EMPTY(&(ctl)->sc_buffered_packets[BPT_HIGHEST_PRIO].bpq_packets))
|
|
|
|
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
#define lsquic_send_ctl_invalidate_bpt_cache(ctl) do { \
|
2019-10-08 12:36:49 +00:00
|
|
|
(ctl)->sc_cached_bpt.stream_id = UINT64_MAX; \
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#ifndef NDEBUG
|
2019-04-01 15:41:55 +00:00
|
|
|
enum packno_bits
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
lsquic_send_ctl_guess_packno_bits (struct lsquic_send_ctl *);
|
|
|
|
|
|
|
|
int
|
|
|
|
lsquic_send_ctl_schedule_stream_packets_immediately (struct lsquic_send_ctl *);
|
|
|
|
|
|
|
|
enum buf_packet_type
|
|
|
|
lsquic_send_ctl_determine_bpt (struct lsquic_send_ctl *,
|
|
|
|
const struct lsquic_stream *);
|
|
|
|
|
2019-04-01 15:41:55 +00:00
|
|
|
enum packno_bits
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
lsquic_send_ctl_calc_packno_bits (struct lsquic_send_ctl *);
|
2019-09-11 15:27:58 +00:00
|
|
|
|
|
|
|
void
|
|
|
|
lsquic_send_ctl_set_max_bpq_count (unsigned);
|
Latest changes
- [API Change] Sendfile-like functionality is gone. The stream no
longer opens files and deals with file descriptors. (Among other
things, this makes the code more portable.) Three writing functions
are provided:
lsquic_stream_write
lsquic_stream_writev
lsquic_stream_writef (NEW)
lsquic_stream_writef() is given an abstract reader that has function
pointers for size() and read() functions which the user can implement.
This is the most flexible way. lsquic_stream_write() and
lsquic_stream_writev() are now both implemented as wrappers around
lsquic_stream_writef().
- [OPTIMIZATION] When writing to stream, be it within or without the
on_write() callback, place data directly into packet buffer,
bypassing auxiliary data structures. This reduces amount of memory
required, for the amount of data that can be written is limited
by the congestion window.
To support writes outside the on_write() callback, we keep N
outgoing packet buffers per connection which can be written to
by any stream. One half of these are reserved for the highest
priority stream(s), the other half for all other streams. This way,
low-priority streams cannot write instead of high-priority streams
and, on the other hand, low-priority streams get a chance to send
their packets out.
The algorithm is as follows:
- When user writes to stream outside of the callback:
- If this is the highest priority stream, place it onto the
reserved N/2 queue or fail.
(The actual size of this queue is dynamic -- MAX(N/2, CWND) --
rather than N/2, allowing high-priority streams to write as
much as can be sent.)
- If the stream is not the highest priority, try to place the
data onto the reserved N/2 queue or fail.
- When tick occurs *and* more packets can be scheduled:
- Transfer packets from the high N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for highest-priority streams,
placing resulting packets directly onto the scheduled queue.
- If more scheduling is allowed:
- Transfer packets from the low N/2 queue to the scheduled
queue.
- If more scheduling is allowed:
- Call on_write callbacks for non-highest-priority streams,
placing resulting packets directly onto the scheduled queue
The number N is currently 20, but it could be varied based on
resource usage.
- If stream is created due to incoming headers, make headers readable
from on_new.
- Outgoing packets are no longer marked non-writeable to prevent placing
more than one STREAM frame from the same stream into a single packet.
This property is maintained via code flow and an explicit check.
Packets for stream data are allocated using a special function.
- STREAM frame elision is cheaper, as we only perform it if a reset
stream has outgoing packets referencing it.
- lsquic_packet_out_t is smaller, as stream_rec elements are now
inside a union.
2017-10-31 13:35:58 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
size_t
|
|
|
|
lsquic_send_ctl_mem_used (const struct lsquic_send_ctl *);
|
|
|
|
|
|
|
|
#define lsquic_send_ctl_set_buffer_stream_packets(ctl, b) do { \
|
|
|
|
(ctl)->sc_flags &= ~SC_BUFFER_STREAM; \
|
|
|
|
(ctl)->sc_flags |= -!!(b) & SC_BUFFER_STREAM; \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
int
|
|
|
|
lsquic_send_ctl_turn_on_fin (struct lsquic_send_ctl *,
|
|
|
|
const struct lsquic_stream *);
|
|
|
|
|
2018-02-26 21:01:16 +00:00
|
|
|
int
|
|
|
|
lsquic_send_ctl_pacer_blocked (struct lsquic_send_ctl *);
|
|
|
|
|
|
|
|
#define lsquic_send_ctl_incr_pack_sz(ctl, packet, delta) do { \
|
2019-09-11 15:27:58 +00:00
|
|
|
(packet)->po_data_sz += (delta); \
|
2018-02-26 21:01:16 +00:00
|
|
|
if ((packet)->po_flags & PO_SCHED) \
|
2019-09-11 15:27:58 +00:00
|
|
|
(ctl)->sc_bytes_scheduled += (delta); \
|
2018-02-26 21:01:16 +00:00
|
|
|
lsquic_send_ctl_sanity_check(ctl); \
|
|
|
|
} while (0)
|
|
|
|
|
2018-04-25 14:58:55 +00:00
|
|
|
int
|
2019-09-11 15:27:58 +00:00
|
|
|
lsquic_send_ctl_sched_is_blocked (struct lsquic_send_ctl *);
|
2018-04-25 14:58:55 +00:00
|
|
|
|
2019-02-18 13:40:51 +00:00
|
|
|
void
|
|
|
|
lsquic_send_ctl_verneg_done (struct lsquic_send_ctl *);
|
|
|
|
|
2019-09-11 15:27:58 +00:00
|
|
|
int
|
2019-11-07 21:19:03 +00:00
|
|
|
lsquic_send_ctl_retry (struct lsquic_send_ctl *, const unsigned char *, size_t);
|
2019-09-11 15:27:58 +00:00
|
|
|
|
|
|
|
int
|
|
|
|
lsquic_send_ctl_set_token (struct lsquic_send_ctl *,
|
|
|
|
const unsigned char *token, size_t token_sz);
|
|
|
|
|
|
|
|
void
|
|
|
|
lsquic_send_ctl_empty_pns (struct lsquic_send_ctl *, enum packnum_space);
|
|
|
|
|
2020-11-11 12:50:03 +00:00
|
|
|
void
|
|
|
|
lsquic_send_ctl_maybe_calc_rough_rtt (struct lsquic_send_ctl *,
|
|
|
|
enum packnum_space);
|
|
|
|
|
2019-09-11 15:27:58 +00:00
|
|
|
void
|
2020-09-15 20:42:13 +00:00
|
|
|
lsquic_send_ctl_repath (struct lsquic_send_ctl *ctl,
|
|
|
|
const struct network_path *old, const struct network_path *new,
|
|
|
|
int keep_path_properties);
|
2019-09-11 15:27:58 +00:00
|
|
|
|
2020-12-17 19:39:51 +00:00
|
|
|
void
|
2021-01-18 18:26:33 +00:00
|
|
|
lsquic_send_ctl_cancel_path_verification (struct lsquic_send_ctl *,
|
2020-12-17 19:39:51 +00:00
|
|
|
const struct network_path *);
|
|
|
|
|
2020-07-29 15:33:52 +00:00
|
|
|
void
|
|
|
|
lsquic_send_ctl_resize (struct lsquic_send_ctl *);
|
|
|
|
|
2019-09-11 15:27:58 +00:00
|
|
|
void
|
|
|
|
lsquic_send_ctl_return_enc_data (struct lsquic_send_ctl *);
|
|
|
|
|
|
|
|
#define lsquic_send_ctl_1rtt_acked(ctl) ((ctl)->sc_flags & SC_1RTT_ACKED)
|
|
|
|
|
2019-09-12 18:39:50 +00:00
|
|
|
void
|
|
|
|
lsquic_send_ctl_maybe_app_limited (struct lsquic_send_ctl *,
|
|
|
|
const struct network_path *);
|
|
|
|
|
2019-11-22 05:40:05 +00:00
|
|
|
#define lsquic_send_ctl_do_ql_bits(ctl) do { \
|
|
|
|
(ctl)->sc_flags |= SC_QL_BITS; \
|
|
|
|
} while (0)
|
|
|
|
|
2019-12-11 14:38:58 +00:00
|
|
|
void
|
|
|
|
lsquic_send_ctl_cidlen_change (struct lsquic_send_ctl *,
|
|
|
|
unsigned orig_cid_len, unsigned new_cid_len);
|
|
|
|
|
2019-12-30 16:29:05 +00:00
|
|
|
void
|
|
|
|
lsquic_send_ctl_begin_optack_detection (struct lsquic_send_ctl *);
|
|
|
|
|
2020-05-06 13:35:33 +00:00
|
|
|
void
|
|
|
|
lsquic_send_ctl_path_validated (struct lsquic_send_ctl *);
|
|
|
|
|
2020-06-18 13:45:44 +00:00
|
|
|
/* Has immediately sendable packets */
|
|
|
|
#define lsquic_send_ctl_has_sendable(ctl_) \
|
|
|
|
(lsquic_send_ctl_n_scheduled(ctl_) > 0 \
|
|
|
|
&& lsquic_send_ctl_next_packet_to_send_predict(ctl_))
|
|
|
|
|
2020-07-29 15:33:52 +00:00
|
|
|
#define lsquic_send_ctl_in_recovery(ctl_) ((ctl_)->sc_largest_acked_packno \
|
|
|
|
&& (ctl_)->sc_largest_acked_packno <= (ctl_)->sc_largest_sent_at_cutback)
|
|
|
|
|
|
|
|
#define send_ctl_in_recovery lsquic_send_ctl_in_recovery
|
|
|
|
|
|
|
|
int
|
|
|
|
lsquic_send_ctl_can_send_probe (const struct lsquic_send_ctl *,
|
|
|
|
const struct network_path *);
|
|
|
|
|
2020-09-02 13:03:19 +00:00
|
|
|
#define lsquic_send_ctl_ecn_turned_on(ctl_) ((ctl_)->sc_ecn != ECN_NOT_ECT)
|
|
|
|
|
|
|
|
void
|
|
|
|
lsquic_send_ctl_disable_ecn (struct lsquic_send_ctl *);
|
2020-08-20 18:02:51 +00:00
|
|
|
|
2020-09-08 15:43:03 +00:00
|
|
|
struct send_ctl_state
|
|
|
|
{
|
|
|
|
struct pacer pacer;
|
|
|
|
struct ack_state ack_state;
|
|
|
|
unsigned buf_counts[BPT_OTHER_PRIO + 1];
|
|
|
|
};
|
|
|
|
|
|
|
|
void
|
|
|
|
lsquic_send_ctl_snapshot (struct lsquic_send_ctl *, struct send_ctl_state *);
|
|
|
|
|
|
|
|
void
|
|
|
|
lsquic_send_ctl_rollback (struct lsquic_send_ctl *, struct send_ctl_state *,
|
|
|
|
const struct iovec *, size_t);
|
|
|
|
|
2020-10-13 12:20:25 +00:00
|
|
|
void
|
|
|
|
lsquic_send_ctl_0rtt_to_1rtt (struct lsquic_send_ctl *);
|
|
|
|
|
|
|
|
void
|
|
|
|
lsquic_send_ctl_stash_0rtt_packets (struct lsquic_send_ctl *);
|
|
|
|
|
2017-09-22 21:00:03 +00:00
|
|
|
#endif
|