Quic loss recovery
include quic_types
include quic_application
include quic_security
include quic_frame
include quic_packet
include quic_locale
module quic_packet_arr(quic_packet) = { instance jdx : unbounded_sequence instance arr : array(jdx,quic_packet) }
instance quic_packet : quic_packet_arr(quic_packet)
QUIC uses separate packet number spaces for each encryption level, except 0-RTT and all generations of 1-RTT keys use the same packet number space. Separate packet number spaces ensures that the acknowledgment of packets sent with one level of encryption will not cause spurious retransmission of packets sent with a different encryption level. Congestion control and round-trip time (RTT) measurement are unified across packet number spaces
QUIC separates transmission order from delivery order: packet numbers indicate transmission order, and delivery order is determined by the stream offsets in STREAM frames.
QUIC's packet number is strictly increasing within a packet number space and directly encodes transmission order. A higher packet number signifies that the packet was sent later, and a lower packet number signifies that the packet was sent earlier.
QUIC starts a loss epoch when a packet is lost. The loss epoch ends when any packet sent after the start of the epoch is acknowledged
function loss_epoch : microseconds
function loss_detection_timer : microseconds
function first_rtt_sample : microseconds
function latest_rtt(C:cid) : microseconds
function smoothed_rtt(C:cid) : microseconds
function rttvar(C:cid) : microseconds
function min_rtt(C:cid) : microseconds
function time_of_last_ack_eliciting_packet(E: quic_packet_type) : microseconds
function largest_acked_packet(E: quic_packet_type) : pkt_num
function local_largest_acked_packet(E: quic_packet_type) : pkt_num
function loss_time(E: quic_packet_type) : microseconds
TODO error: cannot determine an iteration bound for loop over quic_packet.idx function sent_packets(E: quic_packet_type) : quic_packet.arr
function newly_acked_packets(E: quic_packet_type, I:stream_pos) : quic_packet
function newly_acked_packets_end(E: quic_packet_type) : stream_pos
function lost_packets(E: quic_packet_type, I:stream_pos) : quic_packet
function lost_packets_end(E: quic_packet_type) : stream_pos
function pc_lost(E: quic_packet_type, I:stream_pos) : quic_packet
function pc_lost_end(E: quic_packet_type) : stream_pos
function sent_packets(E: quic_packet_type, I:stream_pos) : quic_packet
function sent_packets_end(E: quic_packet_type) : stream_pos
function sent_packets_packet_number(E: quic_packet_type,S: pkt_num) : pkt_num
function sent_packets_time_sent(E: quic_packet_type,S: pkt_num) : microseconds
relation sent_packets_ack_eliciting(E: quic_packet_type,S: pkt_num)
relation sent_packets_in_flight(E: quic_packet_type,S: pkt_num)
function sent_packets_sent_bytes(E: quic_packet_type,S: pkt_num) : stream_pos # frame.arr
function received_packets(E: quic_packet_type, I:stream_pos) : quic_packet
function received_packets_end(E: quic_packet_type) : stream_pos
function received_packets_packet_number(E: quic_packet_type,S: pkt_num) : pkt_num
function received_packets_time_sent(E: quic_packet_type,S: pkt_num) : microseconds
relation received_packets_ack_eliciting(E: quic_packet_type,S: pkt_num)
relation received_packets_in_flight(E: quic_packet_type,S: pkt_num)
function received_packets_sent_bytes(E: quic_packet_type,S: pkt_num) : stream_pos # frame.arr
function packets_to_retransmit(E: quic_packet_type, I:stream_pos) : frame.arr
function packets_to_retransmit_end(E: quic_packet_type) : stream_pos
relation ack_eliciting_packet_in_flight
relation ack_eliciting_packet_in_flight_per_space(E: quic_packet_type)
function sent_time_of_largest_acked(S:pkt_num) : microseconds
function ack_time : microseconds
function kPacketThreshold : stream_pos
relation anti_amplification_limit_reached
function kTimeThreshold : microseconds
function kGranularity : microseconds
function kInitialRtt : milliseconds
function pto_count : microseconds # for avoid to cast
function pto_timeout : microseconds
function pto_backoff : stream_pos
function max_idle_timeout_server : microseconds
function max_idle_timeout_client : microseconds
function max_idle_timeout_used : microseconds
function idle_timeout(C:cid) : microseconds
function timeout : microseconds
relation respect_idle_timeout
relation connection_timeout
function max_ack_delay_tp : microseconds
function local_max_ack_delay_tp : microseconds
function ack_delay_exponent_tp : microseconds
function local_ack_delay_exponent_tp : microseconds
function validation_timeout : microseconds
relation need_sent_ack_eliciting_initial_packet
relation need_sent_ack_eliciting_handshake_packet
relation need_sent_ack_eliciting_application_packet
Sending a single ack-eliciting packet also increases the chances of incurring additional latency when a receiver delays its acknowledgment. QUIC therefore recommends that the minimum congestion window be two packets. While this increases network load, it is considered safe since the sender will still reduce its sending rate exponentially under persistent congestion
function minimum_congestion_window : stream_pos
relation pn_space_discarded
relation is_idle
after init {
need_sent_ack_eliciting_initial_packet := false;
need_sent_ack_eliciting_handshake_packet := false;
need_sent_ack_eliciting_application_packet := false;
loss_detection_timer := 0;
pto_count := 0;
latest_rtt(C) := 0;
smoothed_rtt(C) := 333000; # 333ms ~ kInitialRtt -> from handshake
rttvar(C) := 165000;
min_rtt(C) := 0;
first_rtt_sample := 0;
largest_acked_packet(E) := 0xFFFFFF; # infinity
local_largest_acked_packet(E) := 0xFFFFFF; # infinity
time_of_last_ack_eliciting_packet(E) := 0;
loss_time(E) := 0;
kPacketThreshold := 3;
kTimeThreshold := 1125; #9/8;
kGranularity := 1000; #1ms
max_ack_delay_tp := 25000; # 25ms
local_max_ack_delay_tp := 25000; # 25ms
local_ack_delay_exponent_tp := 3;
ack_delay_exponent_tp := 3;
connection_timeout := false;
respect_idle_timeout := true;
minimum_congestion_window := 2;
ack_eliciting_packet_in_flight := false;
anti_amplification_limit_reached := false;
sent_packets_end(E) := 0;
newly_acked_packets_end(E) := 0;
lost_packets_end(E) := 0;
pc_lost_end(E) := 0;
pn_space_discarded := false;
is_idle := false;
}
import action show_latest_rtt(t:microseconds,c:cid)
import action show_min_rtt(t:microseconds,c:cid)
import action show_smoothed_rtt(t:microseconds,c:cid)
import action show_rttvar(t:microseconds,c:cid)
action pow(x:microseconds, y:microseconds) returns (z:microseconds) = {
<<<
z = pow(x,y);
>>>
}
action abs(x:microseconds) returns (z:microseconds) = {
<<<
z = abs(x);
>>>
}
action get_pto_time_and_space(dst:ip.endpoint, dcid:cid) returns (pto_timeout_res:microseconds, pto_space:quic_packet_type) = {
var max := 4 * rttvar(dcid);
if max < kGranularity {
max := kGranularity;
}
var duration := (smoothed_rtt(dcid) + max) * pow(pto_count,2);
if ~ack_eliciting_packet_in_flight {
if ~peer_completed_address_validation(dst, dcid) {
pto_timeout_res := duration + time_api.c_timer.now_micros;
if established_handshake_keys(dcid) {
pto_space := quic_packet_type.handshake;
}
else {
pto_space := quic_packet_type.initial;
}
};
} else {
var temp_pto_timeout_res : microseconds := 0xFFFFFF; # infinity
var temp_pto_space := quic_packet_type.initial;
var t := time_of_last_ack_eliciting_packet(temp_pto_space) + duration;
if t < temp_pto_timeout_res {
pto_timeout_res := t;
pto_space := temp_pto_space;
};
temp_pto_space := quic_packet_type.handshake;
t := time_of_last_ack_eliciting_packet(temp_pto_space) + duration;
if t < temp_pto_timeout_res {
pto_timeout_res := t;
pto_space := temp_pto_space;
};
temp_pto_space := quic_packet_type.one_rtt;
if peer_completed_address_validation(dst, dcid) {
duration := duration + max_ack_delay_tp * pow(pto_count,2);
t := time_of_last_ack_eliciting_packet(temp_pto_space) + duration;
if t < temp_pto_timeout_res {
pto_timeout_res := t;
pto_space := temp_pto_space;
};
};
};
call show_get_pto_time_and_space(pto_timeout_res, pto_space);
}
action get_loss_time_space returns (time:microseconds, space:quic_packet_type) = {
var inter_time := loss_time(quic_packet_type.initial);
var inter_space := quic_packet_type.initial;
if inter_time = 0 | loss_time(quic_packet_type.handshake) < inter_time {
inter_time := loss_time(quic_packet_type.handshake);
inter_space := quic_packet_type.handshake;
};
if inter_time = 0 | loss_time(quic_packet_type.one_rtt) < inter_time {
inter_time := loss_time(quic_packet_type.one_rtt);
inter_space := quic_packet_type.one_rtt;
};
time := inter_time;
space := inter_space;
}
action peer_completed_address_validation(dst:ip.endpoint, dcid:cid) returns (b:bool) = {
if dst = server.ep {
b := true;
}
b := established_handshake_keys(dcid); # has received Handshake ACK || handshake confirmed
}
action set_loss_detection_timer(dst:ip.endpoint, dcid: cid) = {
var earliest_loss_time : microseconds;
var space : quic_packet_type;
(earliest_loss_time, space) := get_loss_time_space;
call show_get_loss_time_space(earliest_loss_time, space);
if earliest_loss_time ~= 0 {
loss_detection_timer := earliest_loss_time;
} else {
if anti_amplification_limit_reached { # (dst, dcid)
loss_detection_timer := 0;
}
else {
if ~ack_eliciting_packet_in_flight & peer_completed_address_validation(dst, dcid) {
loss_detection_timer := 0;
} else {
var pto_timeout_t : microseconds;
var pto_space : quic_packet_type;
(pto_timeout_t, pto_space) := get_pto_time_and_space(dst,dcid);
loss_detection_timer := pto_timeout_t;
pto_timeout := pto_timeout_t;
}
}
};
call show_loss_detection_timer(loss_detection_timer);
}
action on_loss_detection_timeout(dst:ip.endpoint, dcid: cid) = {
var earliest_loss_time : microseconds;
var pn_space : quic_packet_type;
(earliest_loss_time, pn_space) := get_loss_time_space;
call show_get_loss_time_space(earliest_loss_time, pn_space);
if earliest_loss_time ~= 0 {
detect_and_remove_lost_packets(pn_space,dcid);
if lost_packets_end(pn_space) > 0 {
call on_packets_lost(pn_space);
call set_loss_detection_timer(dst, dcid);
};
} else {
if ~ack_eliciting_packet_in_flight & ~peer_completed_address_validation(dst, dcid) {
if established_handshake_keys(dcid) {
call show_need_sent_ack_eliciting_handshake_packet;
need_sent_ack_eliciting_handshake_packet := true;
} else {
call show_need_sent_ack_eliciting_initial_packet;
need_sent_ack_eliciting_initial_packet := true;
}
} else {
var pto_timeout_t : microseconds;
var pto_space : quic_packet_type;
(pto_timeout_t, pto_space) := get_pto_time_and_space(dst,dcid);
loss_detection_timer := pto_timeout_t;
pto_timeout := pto_timeout_t;
if pn_space = quic_packet_type.initial {
call show_need_sent_ack_eliciting_initial_packet;
need_sent_ack_eliciting_initial_packet := true;
} else if pn_space = quic_packet_type.handshake {
call show_need_sent_ack_eliciting_handshake_packet;
need_sent_ack_eliciting_handshake_packet := true;
} else {
call show_need_sent_ack_eliciting_application_packet;
need_sent_ack_eliciting_application_packet := true;
};
};
pto_count := pto_count + 1;
call set_loss_detection_timer(dst, dcid);
};
}
import action show_need_sent_ack_eliciting_initial_packet
import action show_need_sent_ack_eliciting_handshake_packet
import action show_need_sent_ack_eliciting_application_packet
action detect_and_remove_lost_packets(pn_space:quic_packet_type, dcid:cid) = {
loss_time(pn_space) := 0;
var max := smoothed_rtt(dcid);
if max < latest_rtt(dcid) {
max := latest_rtt(dcid);
};
var loss_delay := kTimeThreshold * max;
if loss_delay < kGranularity {
loss_delay := kGranularity;
};
var tp := trans_params(dcid);
var lost_sent_time := time_api.c_timer.now_micros - loss_delay;
var sent_packets_size := sent_packets_end(pn_space);
sent_packets_end(pn_space) := 0;
lost_packets_end(pn_space) := 0;
var idx : stream_pos := 0;
call show_detect_and_remove_lost_packets(lost_sent_time, loss_delay, sent_packets_size);
while idx < sent_packets_size {
var current_pkt := sent_packets(pn_space,idx);
if current_pkt.seq_num <= largest_acked_packet(pn_space) {
if min_ack_delay.is_set(tp) {
if sent_packets_time_sent(pn_space, current_pkt.seq_num) <= lost_sent_time |
largest_acked_packet(pn_space) >= current_pkt.seq_num + streampos_to_seqnum(ack_out_of_order_val(dcid)) {
lost_packets(pn_space,lost_packets_end(pn_space)) := current_pkt;
lost_packets_end(pn_space) := lost_packets_end(pn_space) + 1;
}
else {
sent_packets(pn_space,sent_packets_end(pn_space)) := current_pkt;
sent_packets_end(pn_space) := sent_packets_end(pn_space) + 1;
if loss_time(pn_space) = 0 {
loss_time(pn_space) := sent_packets_time_sent(pn_space, current_pkt.seq_num) + loss_delay;
}
else {
var min := loss_time(pn_space);
if min > sent_packets_time_sent(pn_space, current_pkt.seq_num) + loss_delay {
loss_time(pn_space) := sent_packets_time_sent(pn_space, current_pkt.seq_num) + loss_delay;
}
}
};
}
else {
if sent_packets_time_sent(pn_space, current_pkt.seq_num) <= lost_sent_time |
largest_acked_packet(pn_space) >= current_pkt.seq_num + streampos_to_seqnum(kPacketThreshold) {
lost_packets(pn_space,lost_packets_end(pn_space)) := current_pkt;
lost_packets_end(pn_space) := lost_packets_end(pn_space) + 1;
} else {
sent_packets(pn_space,sent_packets_end(pn_space)) := current_pkt;
sent_packets_end(pn_space) := sent_packets_end(pn_space) + 1;
if loss_time(pn_space) = 0 {
loss_time(pn_space) := sent_packets_time_sent(pn_space, current_pkt.seq_num) + loss_delay;
}
else {
var min := loss_time(pn_space);
if min > sent_packets_time_sent(pn_space, current_pkt.seq_num) + loss_delay {
loss_time(pn_space) := sent_packets_time_sent(pn_space, current_pkt.seq_num) + loss_delay;
}
}
};
}
};
idx := idx + 1;
};
call show_loss_time_update(loss_time(pn_space), pn_space);
}
import action show_loss_time_update(t:microseconds, pn_space:quic_packet_type)
action on_datagram_received(dst:ip.endpoint, dcid:cid, datagram:stream_data) = {
if anti_amplification_limit_reached { # TODO
call set_loss_detection_timer(dst, dcid);
if loss_detection_timer < time_api.c_timer.now_micros {
loss_detection_timer := pto_timeout;
call show_loss_detection_timeout;
call on_loss_detection_timeout(dst, dcid);
};
};
}
import action show_loss_detection_timeout
action detect_and_remove_acked_packets(largest_acked:pkt_num, pn_space:quic_packet_type) = {
var sent_packets_size := sent_packets_end(pn_space);
sent_packets_end(pn_space) := 0;
newly_acked_packets_end(pn_space) := 0;
var idx : stream_pos := 0;
if sent_packets_size > 0 {
while idx < sent_packets_size {
var current_pkt := sent_packets(pn_space,idx);
if current_pkt.seq_num <= largest_acked {
newly_acked_packets(pn_space,newly_acked_packets_end(pn_space)) := current_pkt;
newly_acked_packets_end(pn_space) := newly_acked_packets_end(pn_space) + 1;
}
else {
sent_packets(pn_space,sent_packets_end(pn_space)) := current_pkt;
sent_packets_end(pn_space) := sent_packets_end(pn_space) + 1;
}
idx := idx + 1;
};
};
call show_detect_and_remove_acked_packets(largest_acked, sent_packets_size, newly_acked_packets_end(pn_space), sent_packets_end(pn_space));
}
action on_ack_received(dst:ip.endpoint, largest_acked:pkt_num, ack_delay:microseconds, pn_space:quic_packet_type) = {
call show_on_ack_received(largest_acked, ack_delay, pn_space);
ack_delay := ack_delay * pow(2, ack_delay_exponent_tp);
call show_on_ack_received(largest_acked, ack_delay, pn_space);
if largest_acked_packet(pn_space) = 0xFFFFFF { # infinity
largest_acked_packet(pn_space) := largest_acked;
} else {
if largest_acked > largest_acked_packet(pn_space) {
largest_acked_packet(pn_space) := largest_acked;
}
}
detect_and_remove_acked_packets(largest_acked, pn_space);
if newly_acked_packets_end(pn_space) > 0 {
var idx : stream_pos:= 0;
var largest_acked_pkt := newly_acked_packets(pn_space, idx);
idx := idx + 1;
while idx < newly_acked_packets_end(pn_space) {
if newly_acked_packets(pn_space, idx).seq_num > largest_acked_pkt.seq_num {
largest_acked_pkt := newly_acked_packets(pn_space, idx);
}
idx := idx + 1;
}
call show_largest_acked_packet(largest_acked_pkt.seq_num, largest_acked, pn_space);
call show_include_ack_eliciting(includes_ack_eliciting(pn_space), pn_space);
if largest_acked_pkt.seq_num = largest_acked & includes_ack_eliciting(pn_space) {
latest_rtt(largest_acked_pkt.dst_cid) := time_api.c_timer.now_micros - sent_packets_time_sent(largest_acked_pkt.ptype,largest_acked_pkt.seq_num);
call show_latest_rtt(latest_rtt(largest_acked_pkt.dst_cid),largest_acked_pkt.dst_cid);
call update_rtt(ack_delay, largest_acked_pkt.dst_cid);
}
detect_and_remove_lost_packets(pn_space,largest_acked_pkt.dst_cid);
call show_detect_and_remove_lost_packets_size(pn_space,lost_packets_end(pn_space));
if lost_packets_end(pn_space) > 0 {
call on_packets_lost(pn_space);
}
call on_packets_acked(pn_space);
if peer_completed_address_validation(dst, largest_acked_pkt.dst_cid) {
pto_count := 0;
}
call set_loss_detection_timer(dst, largest_acked_pkt.dst_cid);
}
}
import action show_detect_and_remove_lost_packets_size(pn_space:quic_packet_type,lost_packets_end:stream_pos)
import action show_on_ack_received(largest_acked:pkt_num, ack_delay:microseconds, pn_space:quic_packet_type)
action on_ack_sent(largest_acked:pkt_num, pn_space:quic_packet_type) returns (ack_delay:microseconds) = {
var ack_sent_time := time_api.c_timer.now_micros;
var ack_delay_non_encoded := received_packets_time_sent(pn_space,largest_acked) - ack_sent_time;
ack_delay := ack_delay_non_encoded / pow(2, local_ack_delay_exponent_tp);
call show_on_ack_sent(largest_acked, ack_delay,ack_sent_time, ack_delay_non_encoded, pn_space, pow(2, local_ack_delay_exponent_tp));
}
import action show_on_ack_sent(largest_acked:pkt_num, ack_delay:microseconds, ack_delasy:microseconds, ack_delay_non_encoded:microseconds, pn_space:quic_packet_type, pow_ack_delay_exponent_tp:microseconds)
action includes_ack_eliciting(pn_space:quic_packet_type) returns (res:bool) = {
var idx : stream_pos := 0;
var res_inter := false;
while idx < newly_acked_packets_end(pn_space) {
if sent_packets_ack_eliciting(newly_acked_packets(pn_space, idx).ptype, newly_acked_packets(pn_space, idx).seq_num) {
res_inter := true;
}
idx := idx + 1;
}
res := res_inter;
}
action update_rtt(ack_delay:microseconds, dcid:cid) = {
call show_update_rtt(latest_rtt(dcid), min_rtt(dcid), smoothed_rtt(dcid), rttvar(dcid), dcid);
if first_rtt_sample = 0 {
min_rtt(dcid) := latest_rtt(dcid);
smoothed_rtt(dcid) := latest_rtt(dcid);
rttvar(dcid) := latest_rtt(dcid) / 2;
first_rtt_sample := time_api.c_timer.now_micros;
} else {
if min_rtt(dcid) > latest_rtt(dcid) {
min_rtt(dcid) := latest_rtt(dcid);
}
if established_handshake_keys(dcid) {
if ack_delay > max_ack_delay_tp {
ack_delay := max_ack_delay_tp;
}
}
var adjusted_rtt := latest_rtt(dcid);
if latest_rtt(dcid) > min_rtt(dcid) + ack_delay {
adjusted_rtt := latest_rtt(dcid) - ack_delay;
}
var abs := abs(smoothed_rtt(dcid) - adjusted_rtt);
rttvar(dcid) := 3 * rttvar(dcid)/4 + abs/4;
smoothed_rtt(dcid) := 7 * smoothed_rtt(dcid)/8 + adjusted_rtt/8;
}
call show_update_rtt(latest_rtt(dcid), min_rtt(dcid), smoothed_rtt(dcid), rttvar(dcid), dcid);
}
import action show_update_rtt(latest_rtt:microseconds, min_rtt:microseconds, smoothed_rtt:microseconds, rttvar:microseconds, dcid:cid)
action on_pn_space_discarded(dst:ip.endpoint, dcid:cid, pn_space:quic_packet_type) = {
if ~ pn_space_discarded {
call remove_from_bytes_in_flight(pn_space);
sent_packets_end(pn_space) := 0;
time_of_last_ack_eliciting_packet(pn_space) := 0;
loss_time(pn_space) := 0;
pto_count := 0;
call show_on_pn_space_discarded(pn_space);
call set_loss_detection_timer(dst, dcid);
pn_space_discarded := true;
}
}
import action show_on_pn_space_discarded(pn_space:quic_packet_type)
action on_packet_sent(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet, sent_bytes:stream_pos) = {
sent_time_of_largest_acked(pkt.seq_num) := time_api.c_timer.now_micros;
sent_packets_end(pkt.ptype) := sent_packets_end(pkt.ptype) + 1;
sent_packets(pkt.ptype,sent_packets_end(pkt.ptype)) := pkt;
sent_packets_packet_number(pkt.ptype,pkt.seq_num) := pkt.seq_num;
sent_packets_time_sent(pkt.ptype,pkt.seq_num) := time_api.c_timer.now_micros;
sent_packets_ack_eliciting(pkt.ptype,pkt.seq_num) := queued_ack_eliciting_pkt(seqnum_to_streampos(pkt.seq_num));
sent_packets_in_flight(pkt.ptype,pkt.seq_num) := true;
sent_packets_sent_bytes(pkt.ptype,pkt.seq_num) := sent_bytes; # TODO
var idx : stream_pos := 0;
ack_eliciting_packet_in_flight := false;
ack_eliciting_packet_in_flight_per_space(pkt.ptype) := false;
while idx < sent_packets_end(pkt.ptype) {
if queued_ack_eliciting_pkt(seqnum_to_streampos(sent_packets(pkt.ptype,idx).seq_num)) {
ack_eliciting_packet_in_flight := true;
ack_eliciting_packet_in_flight_per_space(pkt.ptype) := true;
}
idx := idx + 1;
}
if queued_ack_eliciting_pkt(seqnum_to_streampos(pkt.seq_num)){
time_of_last_ack_eliciting_packet(pkt.ptype) := time_api.c_timer.now_micros;
bytes_in_flight := bytes_in_flight + sent_bytes;
}
call show_seqnum_to_streampos(seqnum_to_streampos(pkt.seq_num));
call show_on_packet_sent(sent_packets_end(pkt.ptype), sent_packets_packet_number(pkt.ptype,pkt.seq_num), sent_packets_time_sent(pkt.ptype,pkt.seq_num), sent_packets_ack_eliciting(pkt.ptype,pkt.seq_num), sent_packets_in_flight(pkt.ptype,pkt.seq_num), sent_packets_sent_bytes(pkt.ptype,pkt.seq_num), ack_eliciting_packet_in_flight);
call show_time_of_last_ack_eliciting_packet(time_of_last_ack_eliciting_packet(pkt.ptype),pkt.ptype);
call set_loss_detection_timer(dst, pkt.dst_cid);
}
action on_packet_received(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet, sent_bytes:stream_pos) = {
received_packets_end(pkt.ptype) := received_packets_end(pkt.ptype) + 1;
received_packets(pkt.ptype,received_packets_end(pkt.ptype)) := pkt;
received_packets_packet_number(pkt.ptype,pkt.seq_num) := pkt.seq_num;
received_packets_time_sent(pkt.ptype,pkt.seq_num) := time_api.c_timer.now_micros;
received_packets_ack_eliciting(pkt.ptype,pkt.seq_num) := queued_ack_eliciting_pkt(seqnum_to_streampos(pkt.seq_num));
received_packets_in_flight(pkt.ptype,pkt.seq_num) := true;
received_packets_sent_bytes(pkt.ptype,pkt.seq_num) := sent_bytes; # TODO
call show_seqnum_to_streampos(seqnum_to_streampos(pkt.seq_num));
call show_on_packet_received(received_packets_end(pkt.ptype), received_packets_packet_number(pkt.ptype,pkt.seq_num), received_packets_time_sent(pkt.ptype,pkt.seq_num), received_packets_ack_eliciting(pkt.ptype,pkt.seq_num), sent_packets_in_flight(pkt.ptype,pkt.seq_num), received_packets_sent_bytes(pkt.ptype,pkt.seq_num));
}
import action show_get_loss_time_space(t:microseconds, s:quic_packet_type)
import action show_detect_and_remove_lost_packets(lost_sent_time:microseconds, loss_delay:microseconds, sent_packets_size:stream_pos)
import action show_detect_and_remove_acked_packets(largest_acked:pkt_num, sent_packets_size:stream_pos, newly_acked_packets_end:stream_pos, sent_packets_end:stream_pos)
import action show_on_packet_sent(sent_packets_end:stream_pos, sent_packets_packet_number:pkt_num, sent_packets_time_sent:microseconds, sent_packets_ack_eliciting:bool, sent_packets_in_flight:bool, sent_packets_sent_bytes:stream_pos, ack_eliciting_packet_in_flight:bool)
import action show_on_packet_received(sent_packets_end:stream_pos, sent_packets_packet_number:pkt_num, sent_packets_time_sent:microseconds, sent_packets_ack_eliciting:bool, sent_packets_in_flight:bool, sent_packets_sent_bytes:stream_pos)
import action show_include_ack_eliciting(res:bool, pn_space:quic_packet_type)
import action show_largest_acked_packet(largest_acked:pkt_num, largest_acked2d:pkt_num, pn_space:quic_packet_type)
import action show_loss_detection_timer(loss_detection_timer:microseconds)
import action show_get_pto_time_and_space(pto_timeout_res:microseconds, pto_space:quic_packet_type)
import action show_time_of_last_ack_eliciting_packet(time_of_last_ack_eliciting_packet:microseconds,ptype:quic_packet_type)