UDP preserves datagram boundaries but does not guarantee delivery, ordering, or deduplication. QUIC uses UDP to carry its own encrypted packets, acknowledgments, and multi-stream mechanisms; it provides reliable streams and can also carry unreliable datagrams via extensions. When choosing between them, first clarify whether the business requires messages or byte streams, whether expiration and discarding are allowed, and how success is confirmed, rather than equating UDP with "fast" and QUIC with "never disconnecting."
Question
Core Understanding
How large is a single UDP send?
UDP length, IP length, and path MTU impose separate constraints.
Why does QUIC change packet numbers on retransmission?
Packet numbers identify a single transmission; stream offsets identify data positions.
Does packet loss block all streams?
Streams are ordered internally; across streams, there is no unified byte gap like in TCP.
Is 0-RTT truly zero-wait for the first time?
State recovery is required, it may be rejected, and there is a replay risk.
Why might it continue after changing IP?
CID, path validation, and migration rules work together.
UDP: Responsibilities Beyond the 8-Byte Header
The UDP header consists of four 16-bit fields: Source Port, Destination Port, Length, and Checksum. The Length field includes the 8-byte UDP header; the maximum value for standard fields is 65535. However, in IPv4 without options, it is also constrained by the total IP length, making the maximum UDP data 65535−20−8=65507 bytes. This protocol upper bound is far larger than what common paths can directly carry, so it is not recommended for applications to send datagrams this large. RFC 768: UDP
When MTU=1500 and there are no options/extension headers, IPv4 UDP data is at most 1472 bytes, and IPv6 is 1452 bytes; tunneling and QUIC's own overhead will further reduce this. If a UDP datagram is fragmented by IP, it typically requires successful reassembly of all fragments to deliver the datagram; losing one fragment may result in the loss of the entire datagram.
The zero checksum field in IPv4 UDP indicates that no checksum is provided; if the calculated result is actually zero, it should be sent as all 1s on the wire. IPv6 UDP requires a checksum by default, with specific exceptions for tunnels and jumbograms having independent rules; exceptions should not be treated as the default for ordinary applications. The checksum covers the pseudo-header and the UDP message but does not provide sender authentication.
Applications must also handle loss, out-of-order delivery, duplication, size probing, and congestion control. UDP connect can set local default peer and filtering socket behaviors, but it does not perform a TCP-style remote handshake; a successful send does not prove the peer received the data. Real-time services often prefer to discard already expired data, but sending load should still be controlled. RFC 8085: Guidelines for UDP Applications
QUIC's Four-Layer Objects
Object
Identifier/Boundary
Function
UDP Datagram
UDP Length
A single datagram carrier on the path; can carry multiple QUIC packets
QUIC Packet
Packet Number, etc.
Unit of transmission for encryption protection, acknowledgment, and loss detection
Frame
Frame Type and specific fields
Semantics such as STREAM, ACK, CRYPTO, flow control, or path validation
Stream
Stream ID and Byte Offset
Independent ordered byte streams available to the application
One stream can span many packets, and one packet can also carry frames from multiple streams. After packet loss, the information needed for retransmission may be packaged into another packet and re-fragmented; therefore, it is not simply re-sending the old packet intact.
Long Header, Short Header, and Visible Information
QUIC v1 uses corresponding long headers for Initial, Handshake, and 0-RTT, and a short header for 1-RTT; Retry and Version Negotiation have special structures, so a fixed byte table cannot cover all types. Long headers carry information such as Version and Connection ID length; the purpose CID length in short headers depends on the connection context and cannot be fully parsed from an isolated packet grabbed at random. RFC 9000: Packet Formats
Packet Numbers on the wire are encoded with only the necessary lower bits and are recovered by the receiver in context. Some header fields are header-proTECTED and cannot be analyzed directly as plaintext markers before unprotecting. The QUIC Bit rules in the v1 baseline also have subsequent negotiated extensions, so the bit must be interpreted in conjunction with negotiation results. RFC 9287: QUIC Bit Extension
The Spin Bit is an optional measurement signal; endpoints may not provide it, and observation is affected by loss, reordering, and path conditions. It is not a universal clock that strictly "flips once per RTT" per endpoint based on a timer, nor does it reveal application request latency.
Is it the information or the original packet that is retransmitted?
First, only deliver packet 11; stream 4 already has deliverable data, while stream 0 remains empty. Then deliver packet 12, and finally send the late packet 10: the set of acknowledged packets increases, but the amount of unique stream data no longer increases. These two numbering spaces cannot be understood as merged.
Preparing the visual
Is it the information or the original packet that is retransmitted?
New packet numbers can carry data within the same stream interval. Receiver deduplication is based on stream offset, while packet acknowledgment is based on packet number; a gap in one stream does not prevent delivery in another.
{"id":"quic-packet-stream-identity","title":"Is it the information or the original packet that is retransmitted?","summary":"New packet numbers can carry data within the same stream interval. Receiver deduplication is based on stream offset, while packet acknowledgment is based on packet number; a gap in one stream does not prevent delivery in another.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"p10\" data-i18n=\"p10\"></button><button id=\"p11\" data-i18n=\"p11\"></button><button id=\"p12\" data-i18n=\"p12\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><section class=\"panel\"><h3 data-i18n=\"streams\"></h3><div id=\"streams\"></div></section><div class=\"metrics\"><div><span data-i18n=\"acked\"></span><strong id=\"acked\"></strong></div><div><span data-i18n=\"unique\"></span><strong id=\"unique\"></strong></div></div><p id=\"mapping\" class=\"message\">PN10 → S0 [0,100)\nPN11 → S4 [0,100)\nPN12 → S0 [0,100)</p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#streams{display:flex;gap:8px}.metrics strong{font-size:18px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let got=new Set;function draw(){const a=got.has(10)||got.has(12),b=got.has(11);cells('streams',[{text:'S0 [0,'+(a?100:0)+')',cls:a?'on':'empty'},{text:'S4 [0,'+(b?100:0)+')',cls:b?'on':'empty'}]);set('acked',fmt(got));set('unique',100*(a+b));$('#unique').dataset.value=100*(a+b);}for(let n of [10,11,12])$('#p'+n).onclick=()=>{got.add(n);draw()};$('#reset').onclick=()=>{got=new Set;draw()};draw();","audio":false,"strings":{"acked":"Acknowledged packet numbers","heading":"Is it the information or the original packet that is retransmitted?","intro":"Send data for two streams, delivering original or retransmitted packets arbitrarily. Stream offsets and packet numbers are displayed separately.","p10":"Deliver packet 10","p11":"Deliver packet 11","p12":"Deliver retransmitted packet 12","reset":"Reset","scope":"Only 1-RTT STREAM data; packets 10/12 both carry stream 0's [0,100), packet 11 carries stream 4's [0,100). Congestion, keys, flow control, and application protocol dependencies are not simulated.","scopeLabel":"Model Scope and Conventions","streams":"Two independent stream prefixes","unique":"Unique stream data (B)"}}
Handshake and Encryption Levels
QUIC uses TLS 1.3 handshake messages and key negotiation, but it does not simply wrap TLS records onto the transport stream; CRYPTO frames carry handshake bytes at the corresponding encryption level. Initial, Handshake, 0-RTT, and 1-RTT have different protection and usage conditions. Initial keys can be derived from public information, so Initial packet protection does not equal confidentiality from network observers or completion of server authentication. RFC 9001: TLS and QUIC
The initial handshake can often start corresponding application communication after one round trip, but Retry, certificate size, packet loss, and scheduling will change the actual measured latency. Servers are subject to anti-amplification limits before verifying the client's address, typically sending at most three times the number of bytes received; this may limit sending all handshake data in one go.
The UDP datagram carrying Initial for the client must be extended to at least 1200 bytes, referring to the UDP payload, not exactly 1200 bytes after adding UDP/IP. Under IPv6 without extension headers, this corresponds to at least 1248 bytes for the IP packet, and the real path still needs to be considered. RFC 9000: Datagram Size and Address Validation
0-RTT: Sending Early Does Not Mean Getting All Guarantees Early
When resuming a connection, the client can send early data based on saved TLS resumption state and allowed transport parameters; first-time visitors cannot magically possess these conditions. The server may reject early data; the TLS accept signal determines the result, and QUIC has no REJECTED frame for this purpose. When rejected, the client handles the original early data according to the protocol and application interface; rejection cannot be treated as the business logic having been executed. RFC 9001: 0-RTT
0-RTT has replay risks; applications must judge operation semantics and replay protection in deployment. HTTP method names are not sufficient proof: a misimplemented GET may also have side effects. AEAD's key usage limit restricts cryptographic risks but does not provide business deduplication or limit the number of replays by attackers.
Stream Numbering and Flow Control: Limits are Cumulative Upper Bounds
The least significant bit of the Stream ID indicates the initiating endpoint (0 for client, 1 for server), the next bit indicates the direction (0 for bidirectional, 1 for unidirectional); the remaining high bits are the sequence number for that category. Therefore, IDs for each category increment by 4:
Category
First Batch of IDs
Client-initiated Bidirectional
0, 4, 8, 12
Server-initiated Bidirectional
1, 5, 9, 13
Client-initiated Unidirectional
2, 6, 10, 14
Server-initiated Unidirectional
3, 7, 11, 15
The two sending directions of a bidirectional stream have their own byte offsets. MAX_STREAM_DATA constrains the cumulative sendable range for the corresponding stream direction, MAX_DATA constrains the cumulative usage of corresponding stream data within the connection, and MAX_STREAMS limits the number of streams that can be opened; these are not three interchangeable "buffer sizes". RFC 9000: Streams and Flow Control
For example, if the connection limit is 10000 B, and two streams have already used 4000 and 3000 B respectively, the connection has 3000 B remaining; if the target stream itself only has 1000 B remaining, new data is limited to at most this 1000 B. Retransmitting existing bytes does not consume new flow control quotas again. RESET_STREAM's final size also participates in relevant accounting; you cannot bypass the upper limit by constantly resetting.
Independent streams eliminate the cross-stream transport layer waiting caused by TCP's single ordered byte stream, but gaps within the same stream still need to be filled. Multiple streams share congestion control, connection flow control, CPU, and path; HTTP/3 may also have application-layer dependencies, so "QUIC has no head-of-line blocking at all" is still inaccurate.
Why does retransmission no longer occupy a flow control quota?
Drawing the connection total and the target stream separately reveals where the bottleneck lies. Keeping offsets unchanged and repeatedly retransmitting causes line-rate duplicate bytes to grow, but the flow control balance remains unchanged; this is not free bandwidth, and retransmissions are still subject to other constraints like congestion control.
credit0=min(MSD0−H0,MD−i∑Hi)
Preparing the visual
Why does retransmission no longer occupy a flow control quota?
Quotas are accumulated based on the maximum offset consumed by each stream, not based on line-rate bytes sent repeatedly. New data is constrained by both connection and stream upper limits; retransmitting an existing interval does not increase the offset.
{"id":"quic-cumulative-credit","title":"Why does retransmission no longer occupy a flow control quota?","summary":"Quotas are accumulated based on the maximum offset consumed by each stream, not based on line-rate bytes sent repeatedly. New data is constrained by both connection and stream upper limits; retransmitting an existing interval does not increase the offset.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"s0\" data-i18n=\"s0\"></label><output for=\"s0\" id=\"s0-value\"></output><input id=\"s0\" type=\"range\" min=\"100\" max=\"5000\" value=\"4000\" step=\"100\"></div><div class=\"control\"><label for=\"s4\" data-i18n=\"s4\"></label><output for=\"s4\" id=\"s4-value\"></output><input id=\"s4\" type=\"range\" min=\"0\" max=\"5000\" value=\"3000\" step=\"100\"></div><div class=\"actions\"><button id=\"retry\" data-i18n=\"retry\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><div id=\"used\" class=\"intervals\"></div><div class=\"metrics\"><div><span data-i18n=\"remaining\"></span><strong id=\"remaining\"></strong></div><div><span data-i18n=\"repeated\"></span><strong id=\"repeated\"></strong></div></div><p id=\"equation\" class=\"formula\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let repeated=0;function draw(){const a=+$('#s0').value,b=+$('#s4').value,r=Math.min(5000-a,10000-a-b);set('s0-value',a);set('s4-value',b);intervals('used',[{start:0,end:a,label:'S0: '+a+' / 5000'},{start:0,end:a+b,label:'connection: '+(a+b)+' / 10000',cls:'gold'}],10000);set('remaining',r);set('repeated',repeated);set('equation','credit = min(5000 − '+a+', 10000 − '+a+' − '+b+') = '+r);$('#remaining').dataset.value=r;}$('#retry').onclick=()=>{repeated+=100;draw()};$('#reset').onclick=()=>{repeated=0;draw()};document.querySelectorAll('input').forEach(e=>e.oninput=()=>{repeated=0;draw()});draw();","audio":false,"strings":{"heading":"Why does retransmission no longer occupy a flow control quota?","intro":"Change the highest sent offsets and limits for two streams to view the remaining quota left by the connection and target stream.","remaining":"Stream 0 can send new data (B)","repeated":"Retransmitted / B","reset":"Reset","retry":"Retransmit first 100 B of Stream 0","s0":"Stream 0 consumed offset (B)","s4":"Stream 4 consumed offset (B)","scope":"Two ordinary reliable streams, no RESET_STREAM or hole-punching sends; MAX_DATA 10000, target stream MAX_STREAM_DATA 5000. This only calculates flow control, not congestion or anti-amplification permissions.","scopeLabel":"Model Scope and Conventions"}}
ACK and Loss Detection: Acknowledgment Does Not Equal Mind-Reading
Initial, Handshake, and Application Data are three Packet Number Spaces, where 0-RTT and 1-RTT share the application data space. Packet numbers increment and are not reused within their respective spaces; new packet numbers are used when stream bytes are repackaged for sending. ACK reports acknowledged packet number intervals and does not directly list a list of packets physically confirmed as lost.
If packet 10 is unacknowledged but a larger packet is acknowledged, it may be lost or simply out of order. RFC 9002 infers using packet thresholds and time thresholds, etc.; the recommended packet threshold is 3; the time reference is 9/8×max(latest_rtt,smoothed_rtt) considering clock granularity and other conditions, so that new samples and smoothed estimates jointly constrain the judgment. Judgment also requires context such as acknowledged packets sent later. RFC 9002: Loss Detection
PTO expiration schedules probes; it does not directly declare that all unacknowledged packets are lost. Taking a simplified estimate for the application data space as an example, with SRTT=40 ms, RTTVAR=5 ms, max_ack_delay=25 ms, and granularity 1 ms, the PTO base value is 40+max(20,1)+25=85 ms; the handshake space does not simply copy the same ACK delay, and consecutive PTOs have backoff.
Congestion control determines sending load, and recovery mechanisms determine which information to supplement. RFC 9002 provides an example congestion algorithm based on NewReno; QUIC does not require all implementations to use it; CUBIC, BBR, etc., can be adopted by implementations under protocol constraints.
CID and Migration: New Paths Still Require Verification
The receiver can associate packets from a new address to the connection using its own issued Connection ID. CIDs are not eternal, unchanging pairs of numbers; endpoints can issue, use, and retire multiple CIDs according to rules, and short headers do not carry both source and destination CIDs simultaneously. Deployment choices like zero-length CIDs will limit migration capabilities.
After the client changes networks, protected packets on the new path still require connection authentication and path verification; PATH_CHALLENGE's random data is echoed by PATH_RESPONSE, helping to confirm return path reachability. NAT port rebinding and active migration have different backgrounds, and parameters prohibiting active migration should not be simply understood as rejecting all address changes. RFC 9000: Migration and Verification
Paths may have new RTT, capacity, and MTU; related control states need to be handled according to rules, and business may pause or fail. Matching CIDs alone is not enough to promise "the application sees no interruption"; new paths not supporting UDP, lost key states, or verification timeouts may all prevent the connection from continuing.
Reliable STREAM and Unreliable DATAGRAM
The QUIC DATAGRAM extension preserves application datagram boundaries, does not retransmit lost data like STREAM, and does not use STREAM flow control; it is still subject to connection congestion control and negotiated size limits. Receiving an ACK for the packet carrying it does not mean the application has processed the message. Real-time data can choose expiration and discard semantics based on this, but it cannot be assumed that QUIC encryption guarantees reliable delivery. RFC 9221
HTTP/3, DoQ, etc., define their own application mappings for QUIC. Actual selection also depends on UDP reachability, implementation maturity, observability, and resource consumption; user-space implementations are convenient for evolution but do not automatically guarantee lower CPU usage than kernel TCP.
Local Recalculation: Numbering, Flow Control, and Probe Budgets
frommathimportisclosedefstream_id(index,server=False,unidirectional=False):return(index<<2)|int(server)|(int(unidirectional)<<1)assert[stream_id(i)foriinrange(4)]==[0,4,8,12]assert[stream_id(i,True,True)foriinrange(4)]==[3,7,11,15]assert65535-20-8==65507assert1200+8+40==1248assertmin(10000-4000-3000,1000)==1000assert1200*3==3600srtt,latest,rttvar,ack_delay,granularity=.040,.050,.005,.025,.001loss_delay=max(granularity,9/8*max(srtt,latest))pto=srtt+max(4*rttvar,granularity)+ack_delayassertisclose(loss_delay,.05625)assertisclose(pto,.085)# Packet 10's content retransmitted into packet 14: stream offset unchanged, sending packet number different.
sent={10:(0,1000,500),14:(0,1000,500)}assertsent[10]==sent[14]and10!=14print('UDP/Initial size, stream ID, quota, threshold, and PTO examples passed')
These assertions verify local fields and budgets; they do not implement complete packet protection, handshake, or loss detection state machines; no real QUIC connection or network migration is started.
Continue Reading
TCP: Byte sequence numbers, windows, and recovery comparisons.