detached c5e603f

improve(dgram): supersede stale UDP output

commit
c5e603fcf6475a70ed717e0528d3bda4dffd0930
author
brice · · 2026-07-09 16:52 UTC
parent
5d4ffc4
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +185 -53
M internal/adapters/dgram/transport.go +110 -37
@@ -172,18 +172,20 @@ type Transport struct {
172172
173173 writeMu sync.Mutex
174174
175- deliverMu sync.Mutex
176- deliverCond *sync.Cond
177- nextRecvSeq uint64
178- recvBuf map[uint64]ports.Frame
175+ deliverMu sync.Mutex
176+ deliverCond *sync.Cond
177+ nextRecvSeq uint64
178+ recvBuf map[uint64]ports.Frame
179+ recvSkipThrough uint64
179180 }
180181
181182 type pending struct {
182- frame ports.Frame
183- first time.Time
184- last time.Time
185- attempts int
186- retransmitted bool
183+ frame ports.Frame
184+ first time.Time
185+ last time.Time
186+ attempts int
187+ retransmitted bool
188+ outputSkipThrough uint64
187189 }
188190
189191 func NewTransport(pc net.PacketConn, peer net.Addr, key []byte, sendDir, recvDir uint32) (*Transport, error) {
@@ -245,13 +247,12 @@ func NewTransportWithOptions(pc net.PacketConn, peer net.Addr, key []byte, sendD
245247 func (t *Transport) DatagramTransport() {}
246248
247249 func (t *Transport) Send(f ports.Frame) error {
248- // MsgOutput supersession is intentionally deferred: this reliable stream uses
249- // strict sequence delivery, so deleting an older pending output without a
250- // receive-side skip/ack signal would stall later frames behind the missing
251- // sequence. Add an explicit droppable/skip protocol before replacing pending
252- // outputs.
253250 reliable := true
254251 t.mu.Lock()
252+ outputSkipThrough := uint64(0)
253+ if f.Type == ports.MsgOutput {
254+ outputSkipThrough = t.supersedePendingOutputLocked()
255+ }
255256 var pendingWaitStarted time.Time
256257 for reliable && len(t.pending) >= t.maxPending && !t.closed {
257258 if t.linkState != ports.LinkStateConnected {
@@ -289,10 +290,10 @@ func (t *Transport) Send(f ports.Frame) error {
289290 seq := t.seq
290291 if reliable {
291292 now := t.clock.Now()
292- t.pending[seq] = &pending{frame: f, first: now, last: now}
293+ t.pending[seq] = &pending{frame: f, first: now, last: now, outputSkipThrough: outputSkipThrough}
293294 }
294295 t.mu.Unlock()
295- if err := t.sendData(seq, reliable, f); err != nil {
296+ if err := t.sendData(seq, reliable, f, outputSkipThrough); err != nil {
296297 if reliable {
297298 t.mu.Lock()
298299 if _, ok := t.pending[seq]; ok {
@@ -306,6 +307,34 @@ func (t *Transport) Send(f ports.Frame) error {
306307 return nil
307308 }
308309
310+func (t *Transport) supersedePendingOutputLocked() uint64 {
311+ var barrier uint64
312+ for seq, p := range t.pending {
313+ if p != nil && p.frame.Type != ports.MsgOutput && seq > barrier {
314+ barrier = seq
315+ }
316+ }
317+ var newest uint64
318+ for seq, p := range t.pending {
319+ if p == nil || p.frame.Type != ports.MsgOutput || seq <= barrier {
320+ continue
321+ }
322+ if seq > newest {
323+ newest = seq
324+ }
325+ }
326+ if newest == 0 {
327+ return 0
328+ }
329+ for seq, p := range t.pending {
330+ if p != nil && p.frame.Type == ports.MsgOutput && seq > barrier && seq <= newest {
331+ delete(t.pending, seq)
332+ }
333+ }
334+ t.notifySendWaitersLocked()
335+ return newest
336+}
337+
309338 func (t *Transport) Recv() (ports.Frame, error) {
310339 select {
311340 case f := <-t.in:
@@ -387,8 +416,8 @@ func (t *Transport) unregisterProbe(id uint64) {
387416 t.mu.Unlock()
388417 }
389418
390-func (t *Transport) sendData(seq uint64, reliable bool, f ports.Frame) error {
391- p := encodeData(seq, reliable, f)
419+func (t *Transport) sendData(seq uint64, reliable bool, f ports.Frame, outputSkipThrough uint64) error {
420+ p := encodeData(seq, reliable, f, outputSkipThrough)
392421 return t.sendPayload(p)
393422 }
394423 func (t *Transport) sendAck(seq uint64) {
@@ -547,12 +576,12 @@ func (t *Transport) handleRecord(p []byte) {
547576 }
548577 t.mu.Unlock()
549578 case recData:
550- seq, reliable, f, ok := decodeData(p)
579+ seq, reliable, f, outputSkipThrough, ok := decodeData(p)
551580 if !ok {
552581 return
553582 }
554583 if reliable {
555- ackSeq, ack, queued := t.enqueueReliable(seq, f)
584+ ackSeq, ack, queued := t.enqueueReliable(seq, f, outputSkipThrough)
556585 if ack {
557586 t.sendAck(ackSeq)
558587 }
@@ -565,9 +594,12 @@ func (t *Transport) handleRecord(p []byte) {
565594 }
566595 }
567596
568-func (t *Transport) enqueueReliable(seq uint64, f ports.Frame) (ackSeq uint64, ack bool, queued bool) {
597+func (t *Transport) enqueueReliable(seq uint64, f ports.Frame, outputSkipThrough uint64) (ackSeq uint64, ack bool, queued bool) {
569598 t.deliverMu.Lock()
570599 defer t.deliverMu.Unlock()
600+ if f.Type == ports.MsgOutput && outputSkipThrough > t.recvSkipThrough {
601+ t.recvSkipThrough = outputSkipThrough
602+ }
571603 if seq < t.nextRecvSeq {
572604 return t.highestContiguousRecvLocked(), true, false
573605 }
@@ -584,10 +616,15 @@ func (t *Transport) enqueueReliable(seq uint64, f ports.Frame) (ackSeq uint64, a
584616 func (t *Transport) highestContiguousRecvLocked() uint64 {
585617 seq := t.nextRecvSeq
586618 for {
587- if _, ok := t.recvBuf[seq]; !ok {
588- break
619+ if _, ok := t.recvBuf[seq]; ok {
620+ seq++
621+ continue
622+ }
623+ if seq <= t.recvSkipThrough {
624+ seq++
625+ continue
589626 }
590- seq++
627+ break
591628 }
592629 return seq - 1
593630 }
@@ -602,6 +639,10 @@ func (t *Transport) deliveryLoop() {
602639 for {
603640 t.deliverMu.Lock()
604641 f, ok := t.recvBuf[t.nextRecvSeq]
642+ for !ok && t.nextRecvSeq <= t.recvSkipThrough {
643+ t.nextRecvSeq++
644+ f, ok = t.recvBuf[t.nextRecvSeq]
645+ }
605646 for !ok {
606647 if t.isClosed() {
607648 t.deliverMu.Unlock()
@@ -609,6 +650,16 @@ func (t *Transport) deliveryLoop() {
609650 }
610651 t.deliverCond.Wait()
611652 f, ok = t.recvBuf[t.nextRecvSeq]
653+ for !ok && t.nextRecvSeq <= t.recvSkipThrough {
654+ t.nextRecvSeq++
655+ f, ok = t.recvBuf[t.nextRecvSeq]
656+ }
657+ }
658+ if t.nextRecvSeq <= t.recvSkipThrough && f.Type == ports.MsgOutput {
659+ delete(t.recvBuf, t.nextRecvSeq)
660+ t.nextRecvSeq++
661+ t.deliverMu.Unlock()
662+ continue
612663 }
613664 delete(t.recvBuf, t.nextRecvSeq)
614665 t.nextRecvSeq++
@@ -675,8 +726,9 @@ func (t *Transport) resendLoop() {
675726 func (t *Transport) resendPending() {
676727 now := t.clock.Now()
677728 var resend []struct {
678- seq uint64
679- f ports.Frame
729+ seq uint64
730+ f ports.Frame
731+ outputSkipThrough uint64
680732 }
681733 t.mu.Lock()
682734 seqs := make([]uint64, 0, len(t.pending))
@@ -697,14 +749,15 @@ func (t *Transport) resendPending() {
697749 p.attempts++
698750 p.retransmitted = true
699751 resend = append(resend, struct {
700- seq uint64
701- f ports.Frame
702- }{seq, p.frame})
752+ seq uint64
753+ f ports.Frame
754+ outputSkipThrough uint64
755+ }{seq, p.frame, p.outputSkipThrough})
703756 limit--
704757 }
705758 t.mu.Unlock()
706759 for _, r := range resend {
707- _ = t.sendData(r.seq, true, r.f)
760+ _ = t.sendData(r.seq, true, r.f, r.outputSkipThrough)
708761 }
709762 }
710763
@@ -887,21 +940,41 @@ func (t *Transport) closeWithError(err error) {
887940 t.deliverMu.Unlock()
888941 }
889942
890-func encodeData(seq uint64, reliable bool, f ports.Frame) []byte {
891- b := make([]byte, 12+len(f.Payload))
943+func encodeData(seq uint64, reliable bool, f ports.Frame, outputSkipThrough uint64) []byte {
944+ headerLen := 12
945+ if outputSkipThrough > 0 {
946+ headerLen = 20
947+ }
948+ b := make([]byte, headerLen+len(f.Payload))
892949 b[0] = recData
893950 binary.BigEndian.PutUint64(b[1:9], seq)
894951 if reliable {
895952 b[9] = 1
896953 }
897954 b[10] = byte(f.Type)
898- b[11] = 0
899- copy(b[12:], f.Payload)
955+ if outputSkipThrough > 0 {
956+ b[11] = 1
957+ binary.BigEndian.PutUint64(b[12:20], outputSkipThrough)
958+ }
959+ copy(b[headerLen:], f.Payload)
900960 return b
901961 }
902-func decodeData(b []byte) (uint64, bool, ports.Frame, bool) {
962+func decodeData(b []byte) (uint64, bool, ports.Frame, uint64, bool) {
903963 if len(b) < 12 || b[0] != recData {
904- return 0, false, ports.Frame{}, false
964+ return 0, false, ports.Frame{}, 0, false
965+ }
966+ flags := b[11]
967+ if flags&^byte(1) != 0 {
968+ return 0, false, ports.Frame{}, 0, false
969+ }
970+ headerLen := 12
971+ outputSkipThrough := uint64(0)
972+ if flags&1 != 0 {
973+ if len(b) < 20 {
974+ return 0, false, ports.Frame{}, 0, false
975+ }
976+ headerLen = 20
977+ outputSkipThrough = binary.BigEndian.Uint64(b[12:20])
905978 }
906- return binary.BigEndian.Uint64(b[1:9]), b[9] == 1, ports.Frame{Type: ports.MsgType(b[10]), Payload: append([]byte(nil), b[12:]...)}, true
979+ return binary.BigEndian.Uint64(b[1:9]), b[9] == 1, ports.Frame{Type: ports.MsgType(b[10]), Payload: append([]byte(nil), b[headerLen:]...)}, outputSkipThrough, true
907980 }
@@ -172,18 +172,20 @@ type Transport struct {
172 172
173 writeMu sync.Mutex173 writeMu sync.Mutex
174 174
175- deliverMu sync.Mutex175+ deliverMu sync.Mutex
176- deliverCond *sync.Cond176+ deliverCond *sync.Cond
177- nextRecvSeq uint64177+ nextRecvSeq uint64
178- recvBuf map[uint64]ports.Frame178+ recvBuf map[uint64]ports.Frame
179+ recvSkipThrough uint64
179 }180 }
180 181
181 type pending struct {182 type pending struct {
182- frame ports.Frame183+ frame ports.Frame
183- first time.Time184+ first time.Time
184- last time.Time185+ last time.Time
185- attempts int186+ attempts int
186- retransmitted bool187+ retransmitted bool
188+ outputSkipThrough uint64
187 }189 }
188 190
189 func NewTransport(pc net.PacketConn, peer net.Addr, key []byte, sendDir, recvDir uint32) (*Transport, error) {191 func NewTransport(pc net.PacketConn, peer net.Addr, key []byte, sendDir, recvDir uint32) (*Transport, error) {
@@ -245,13 +247,12 @@ func NewTransportWithOptions(pc net.PacketConn, peer net.Addr, key []byte, sendD
245 func (t *Transport) DatagramTransport() {}247 func (t *Transport) DatagramTransport() {}
246 248
247 func (t *Transport) Send(f ports.Frame) error {249 func (t *Transport) Send(f ports.Frame) error {
248- // MsgOutput supersession is intentionally deferred: this reliable stream uses
249- // strict sequence delivery, so deleting an older pending output without a
250- // receive-side skip/ack signal would stall later frames behind the missing
251- // sequence. Add an explicit droppable/skip protocol before replacing pending
252- // outputs.
253 reliable := true250 reliable := true
254 t.mu.Lock()251 t.mu.Lock()
252+ outputSkipThrough := uint64(0)
253+ if f.Type == ports.MsgOutput {
254+ outputSkipThrough = t.supersedePendingOutputLocked()
255+ }
255 var pendingWaitStarted time.Time256 var pendingWaitStarted time.Time
256 for reliable && len(t.pending) >= t.maxPending && !t.closed {257 for reliable && len(t.pending) >= t.maxPending && !t.closed {
257 if t.linkState != ports.LinkStateConnected {258 if t.linkState != ports.LinkStateConnected {
@@ -289,10 +290,10 @@ func (t *Transport) Send(f ports.Frame) error {
289 seq := t.seq290 seq := t.seq
290 if reliable {291 if reliable {
291 now := t.clock.Now()292 now := t.clock.Now()
292- t.pending[seq] = &pending{frame: f, first: now, last: now}293+ t.pending[seq] = &pending{frame: f, first: now, last: now, outputSkipThrough: outputSkipThrough}
293 }294 }
294 t.mu.Unlock()295 t.mu.Unlock()
295- if err := t.sendData(seq, reliable, f); err != nil {296+ if err := t.sendData(seq, reliable, f, outputSkipThrough); err != nil {
296 if reliable {297 if reliable {
297 t.mu.Lock()298 t.mu.Lock()
298 if _, ok := t.pending[seq]; ok {299 if _, ok := t.pending[seq]; ok {
@@ -306,6 +307,34 @@ func (t *Transport) Send(f ports.Frame) error {
306 return nil307 return nil
307 }308 }
308 309
310+func (t *Transport) supersedePendingOutputLocked() uint64 {
311+ var barrier uint64
312+ for seq, p := range t.pending {
313+ if p != nil && p.frame.Type != ports.MsgOutput && seq > barrier {
314+ barrier = seq
315+ }
316+ }
317+ var newest uint64
318+ for seq, p := range t.pending {
319+ if p == nil || p.frame.Type != ports.MsgOutput || seq <= barrier {
320+ continue
321+ }
322+ if seq > newest {
323+ newest = seq
324+ }
325+ }
326+ if newest == 0 {
327+ return 0
328+ }
329+ for seq, p := range t.pending {
330+ if p != nil && p.frame.Type == ports.MsgOutput && seq > barrier && seq <= newest {
331+ delete(t.pending, seq)
332+ }
333+ }
334+ t.notifySendWaitersLocked()
335+ return newest
336+}
337+
309 func (t *Transport) Recv() (ports.Frame, error) {338 func (t *Transport) Recv() (ports.Frame, error) {
310 select {339 select {
311 case f := <-t.in:340 case f := <-t.in:
@@ -387,8 +416,8 @@ func (t *Transport) unregisterProbe(id uint64) {
387 t.mu.Unlock()416 t.mu.Unlock()
388 }417 }
389 418
390-func (t *Transport) sendData(seq uint64, reliable bool, f ports.Frame) error {419+func (t *Transport) sendData(seq uint64, reliable bool, f ports.Frame, outputSkipThrough uint64) error {
391- p := encodeData(seq, reliable, f)420+ p := encodeData(seq, reliable, f, outputSkipThrough)
392 return t.sendPayload(p)421 return t.sendPayload(p)
393 }422 }
394 func (t *Transport) sendAck(seq uint64) {423 func (t *Transport) sendAck(seq uint64) {
@@ -547,12 +576,12 @@ func (t *Transport) handleRecord(p []byte) {
547 }576 }
548 t.mu.Unlock()577 t.mu.Unlock()
549 case recData:578 case recData:
550- seq, reliable, f, ok := decodeData(p)579+ seq, reliable, f, outputSkipThrough, ok := decodeData(p)
551 if !ok {580 if !ok {
552 return581 return
553 }582 }
554 if reliable {583 if reliable {
555- ackSeq, ack, queued := t.enqueueReliable(seq, f)584+ ackSeq, ack, queued := t.enqueueReliable(seq, f, outputSkipThrough)
556 if ack {585 if ack {
557 t.sendAck(ackSeq)586 t.sendAck(ackSeq)
558 }587 }
@@ -565,9 +594,12 @@ func (t *Transport) handleRecord(p []byte) {
565 }594 }
566 }595 }
567 596
568-func (t *Transport) enqueueReliable(seq uint64, f ports.Frame) (ackSeq uint64, ack bool, queued bool) {597+func (t *Transport) enqueueReliable(seq uint64, f ports.Frame, outputSkipThrough uint64) (ackSeq uint64, ack bool, queued bool) {
569 t.deliverMu.Lock()598 t.deliverMu.Lock()
570 defer t.deliverMu.Unlock()599 defer t.deliverMu.Unlock()
600+ if f.Type == ports.MsgOutput && outputSkipThrough > t.recvSkipThrough {
601+ t.recvSkipThrough = outputSkipThrough
602+ }
571 if seq < t.nextRecvSeq {603 if seq < t.nextRecvSeq {
572 return t.highestContiguousRecvLocked(), true, false604 return t.highestContiguousRecvLocked(), true, false
573 }605 }
@@ -584,10 +616,15 @@ func (t *Transport) enqueueReliable(seq uint64, f ports.Frame) (ackSeq uint64, a
584 func (t *Transport) highestContiguousRecvLocked() uint64 {616 func (t *Transport) highestContiguousRecvLocked() uint64 {
585 seq := t.nextRecvSeq617 seq := t.nextRecvSeq
586 for {618 for {
587- if _, ok := t.recvBuf[seq]; !ok {619+ if _, ok := t.recvBuf[seq]; ok {
588- break620+ seq++
621+ continue
622+ }
623+ if seq <= t.recvSkipThrough {
624+ seq++
625+ continue
589 }626 }
590- seq++627+ break
591 }628 }
592 return seq - 1629 return seq - 1
593 }630 }
@@ -602,6 +639,10 @@ func (t *Transport) deliveryLoop() {
602 for {639 for {
603 t.deliverMu.Lock()640 t.deliverMu.Lock()
604 f, ok := t.recvBuf[t.nextRecvSeq]641 f, ok := t.recvBuf[t.nextRecvSeq]
642+ for !ok && t.nextRecvSeq <= t.recvSkipThrough {
643+ t.nextRecvSeq++
644+ f, ok = t.recvBuf[t.nextRecvSeq]
645+ }
605 for !ok {646 for !ok {
606 if t.isClosed() {647 if t.isClosed() {
607 t.deliverMu.Unlock()648 t.deliverMu.Unlock()
@@ -609,6 +650,16 @@ func (t *Transport) deliveryLoop() {
609 }650 }
610 t.deliverCond.Wait()651 t.deliverCond.Wait()
611 f, ok = t.recvBuf[t.nextRecvSeq]652 f, ok = t.recvBuf[t.nextRecvSeq]
653+ for !ok && t.nextRecvSeq <= t.recvSkipThrough {
654+ t.nextRecvSeq++
655+ f, ok = t.recvBuf[t.nextRecvSeq]
656+ }
657+ }
658+ if t.nextRecvSeq <= t.recvSkipThrough && f.Type == ports.MsgOutput {
659+ delete(t.recvBuf, t.nextRecvSeq)
660+ t.nextRecvSeq++
661+ t.deliverMu.Unlock()
662+ continue
612 }663 }
613 delete(t.recvBuf, t.nextRecvSeq)664 delete(t.recvBuf, t.nextRecvSeq)
614 t.nextRecvSeq++665 t.nextRecvSeq++
@@ -675,8 +726,9 @@ func (t *Transport) resendLoop() {
675 func (t *Transport) resendPending() {726 func (t *Transport) resendPending() {
676 now := t.clock.Now()727 now := t.clock.Now()
677 var resend []struct {728 var resend []struct {
678- seq uint64729+ seq uint64
679- f ports.Frame730+ f ports.Frame
731+ outputSkipThrough uint64
680 }732 }
681 t.mu.Lock()733 t.mu.Lock()
682 seqs := make([]uint64, 0, len(t.pending))734 seqs := make([]uint64, 0, len(t.pending))
@@ -697,14 +749,15 @@ func (t *Transport) resendPending() {
697 p.attempts++749 p.attempts++
698 p.retransmitted = true750 p.retransmitted = true
699 resend = append(resend, struct {751 resend = append(resend, struct {
700- seq uint64752+ seq uint64
701- f ports.Frame753+ f ports.Frame
702- }{seq, p.frame})754+ outputSkipThrough uint64
755+ }{seq, p.frame, p.outputSkipThrough})
703 limit--756 limit--
704 }757 }
705 t.mu.Unlock()758 t.mu.Unlock()
706 for _, r := range resend {759 for _, r := range resend {
707- _ = t.sendData(r.seq, true, r.f)760+ _ = t.sendData(r.seq, true, r.f, r.outputSkipThrough)
708 }761 }
709 }762 }
710 763
@@ -887,21 +940,41 @@ func (t *Transport) closeWithError(err error) {
887 t.deliverMu.Unlock()940 t.deliverMu.Unlock()
888 }941 }
889 942
890-func encodeData(seq uint64, reliable bool, f ports.Frame) []byte {943+func encodeData(seq uint64, reliable bool, f ports.Frame, outputSkipThrough uint64) []byte {
891- b := make([]byte, 12+len(f.Payload))944+ headerLen := 12
945+ if outputSkipThrough > 0 {
946+ headerLen = 20
947+ }
948+ b := make([]byte, headerLen+len(f.Payload))
892 b[0] = recData949 b[0] = recData
893 binary.BigEndian.PutUint64(b[1:9], seq)950 binary.BigEndian.PutUint64(b[1:9], seq)
894 if reliable {951 if reliable {
895 b[9] = 1952 b[9] = 1
896 }953 }
897 b[10] = byte(f.Type)954 b[10] = byte(f.Type)
898- b[11] = 0955+ if outputSkipThrough > 0 {
899- copy(b[12:], f.Payload)956+ b[11] = 1
957+ binary.BigEndian.PutUint64(b[12:20], outputSkipThrough)
958+ }
959+ copy(b[headerLen:], f.Payload)
900 return b960 return b
901 }961 }
902-func decodeData(b []byte) (uint64, bool, ports.Frame, bool) {962+func decodeData(b []byte) (uint64, bool, ports.Frame, uint64, bool) {
903 if len(b) < 12 || b[0] != recData {963 if len(b) < 12 || b[0] != recData {
904- return 0, false, ports.Frame{}, false964+ return 0, false, ports.Frame{}, 0, false
965+ }
966+ flags := b[11]
967+ if flags&^byte(1) != 0 {
968+ return 0, false, ports.Frame{}, 0, false
969+ }
970+ headerLen := 12
971+ outputSkipThrough := uint64(0)
972+ if flags&1 != 0 {
973+ if len(b) < 20 {
974+ return 0, false, ports.Frame{}, 0, false
975+ }
976+ headerLen = 20
977+ outputSkipThrough = binary.BigEndian.Uint64(b[12:20])
905 }978 }
906- return binary.BigEndian.Uint64(b[1:9]), b[9] == 1, ports.Frame{Type: ports.MsgType(b[10]), Payload: append([]byte(nil), b[12:]...)}, true979+ return binary.BigEndian.Uint64(b[1:9]), b[9] == 1, ports.Frame{Type: ports.MsgType(b[10]), Payload: append([]byte(nil), b[headerLen:]...)}, outputSkipThrough, true
907 }980 }
M internal/adapters/dgram/transport_test.go +72 -13
@@ -389,7 +389,7 @@ func TestAuthenticatedPeerChangeUpdatesPeerAndEmitsConnected(t *testing.T) {
389389 t.Fatalf("unauthenticated packet changed peer to %v", a.Peer())
390390 }
391391 c, _ := pdgram.NewCodec(key())
392- rec := encodeData(7, true, ports.Frame{Type: ports.MsgPing})
392+ rec := encodeData(7, true, ports.Frame{Type: ports.MsgPing}, 0)
393393 frags, _ := pdgram.FragmentPayload(9, rec, pdgram.DefaultMTU)
394394 raw, _ := pdgram.MarshalFragment(frags[0])
395395 aPC.in <- packet{c.Seal(2, 99, raw, nil), testAddr("evil")}
@@ -511,11 +511,11 @@ func TestServerWithoutRebindDoesNotHopPorts(t *testing.T) {
511511
512512 func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) {
513513 tr := &Transport{maxRecvBuffer: 1, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)}
514- _, ack, queued := tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("future")})
514+ _, ack, queued := tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("future")}, 0)
515515 if !ack || !queued {
516516 t.Fatalf("future enqueue ack=%v queued=%v, want true/true", ack, queued)
517517 }
518- _, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")})
518+ _, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}, 0)
519519 if !ack || !queued {
520520 t.Fatalf("next enqueue ack=%v queued=%v, want true/true", ack, queued)
521521 }
@@ -524,6 +524,32 @@ func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) {
524524 }
525525 }
526526
527+func TestDataRecordWireEncodingOutputSkip(t *testing.T) {
528+ got := encodeData(5, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("x")}, 3)
529+ want := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 1, 0, 0, 0, 0, 0, 0, 0, 3, 'x'}
530+ if !bytes.Equal(got, want) {
531+ t.Fatalf("encoded data=%v, want %v", got, want)
532+ }
533+ seq, reliable, frame, skip, ok := decodeData(got)
534+ if !ok || seq != 5 || !reliable || frame.Type != ports.MsgOutput || string(frame.Payload) != "x" || skip != 3 {
535+ t.Fatalf("decoded seq=%d reliable=%v frame=%+v skip=%d ok=%v", seq, reliable, frame, skip, ok)
536+ }
537+}
538+
539+func TestDataRecordWireDecodeRejectsTruncatedOutputSkip(t *testing.T) {
540+ truncated := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 1, 0}
541+ if _, _, _, _, ok := decodeData(truncated); ok {
542+ t.Fatal("decodeData accepted truncated output skip header")
543+ }
544+}
545+
546+func TestDataRecordWireDecodeRejectsUnknownFlags(t *testing.T) {
547+ withTrailingGarbage := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 0x80, 'x'}
548+ if _, _, _, _, ok := decodeData(withTrailingGarbage); ok {
549+ t.Fatal("decodeData accepted unknown data-record flags with trailing garbage")
550+ }
551+}
552+
527553 func TestOutputRetransmitsUntilAck(t *testing.T) {
528554 aPC, bPC := newPair()
529555 var dropped atomic.Bool
@@ -551,7 +577,7 @@ func TestOutputRetransmitsUntilAck(t *testing.T) {
551577 }
552578 }
553579
554-func TestReliableDeliveryPreservesSequenceOrder(t *testing.T) {
580+func TestReliableDeliveryPreservesInputSequenceOrder(t *testing.T) {
555581 aPC, bPC := newPair()
556582 a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2)
557583 b, _ := NewTransport(bPC, testAddr("a"), key(), 2, 1)
@@ -559,18 +585,51 @@ func TestReliableDeliveryPreservesSequenceOrder(t *testing.T) {
559585 defer func() { _ = b.Close() }()
560586
561587 for i := range 100 {
562- if err := a.Send(ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}); err != nil {
588+ if err := a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte{byte(i)}}); err != nil {
563589 t.Fatal(err)
564590 }
565591 }
566592 for i := range 100 {
567593 got := recvWithin(t, b, time.Second)
568- if got.Type != ports.MsgOutput || len(got.Payload) != 1 || got.Payload[0] != byte(i) {
594+ if got.Type != ports.MsgInput || len(got.Payload) != 1 || got.Payload[0] != byte(i) {
569595 t.Fatalf("frame %d delivered out of order: %+v", i, got)
570596 }
571597 }
572598 }
573599
600+func TestOutputSupersessionSkipsDroppedOlderOutput(t *testing.T) {
601+ aPC, bPC := newPair()
602+ var sendAttempts atomic.Int32
603+ aPC.drop = func(_ []byte, addr net.Addr) bool {
604+ return addr.String() == "b" && sendAttempts.Add(1) <= 2
605+ }
606+ a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2)
607+ b, _ := NewTransport(bPC, testAddr("a"), key(), 2, 1)
608+ defer func() { _ = a.Close() }()
609+ defer func() { _ = b.Close() }()
610+
611+ for _, payload := range []string{"state1", "state2", "state3"} {
612+ if err := a.Send(ports.Frame{Type: ports.MsgOutput, Payload: []byte(payload)}); err != nil {
613+ t.Fatal(err)
614+ }
615+ }
616+
617+ a.mu.Lock()
618+ pending := len(a.pending)
619+ a.mu.Unlock()
620+ if pending > 1 {
621+ t.Fatalf("pending outputs=%d, want collapsed to newest", pending)
622+ }
623+
624+ got := recvWithin(t, b, time.Second)
625+ if got.Type != ports.MsgOutput || string(got.Payload) != "state3" {
626+ t.Fatalf("got=%+v, want newest output after skipped predecessors", got)
627+ }
628+ if sendAttempts.Load() < 3 {
629+ t.Fatalf("send attempts=%d, want first two output sends dropped before newest arrived", sendAttempts.Load())
630+ }
631+}
632+
574633 func TestAckProcessingContinuesWhenConsumerBackpressured(t *testing.T) {
575634 aPC, bPC := newPair()
576635 a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2)
@@ -719,7 +778,7 @@ func TestCumulativeAckReleasesAllEarlierPendingFrames(t *testing.T) {
719778 func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) {
720779 tr := &Transport{maxRecvBuffer: maxRecvBuffer, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)}
721780
722- ackSeq, ack, queued := tr.enqueueReliable(3, ports.Frame{Type: ports.MsgOutput, Payload: []byte("third")})
781+ ackSeq, ack, queued := tr.enqueueReliable(3, ports.Frame{Type: ports.MsgOutput, Payload: []byte("third")}, 0)
723782 if !ack || !queued {
724783 t.Fatalf("out-of-order enqueue ack=%v queued=%v, want true/true", ack, queued)
725784 }
@@ -727,7 +786,7 @@ func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) {
727786 t.Fatalf("out-of-order ack=%d, want highest contiguous %d", got, want)
728787 }
729788
730- ackSeq, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("first")})
789+ ackSeq, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("first")}, 0)
731790 if !ack || !queued {
732791 t.Fatalf("first enqueue ack=%v queued=%v, want true/true", ack, queued)
733792 }
@@ -735,7 +794,7 @@ func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) {
735794 t.Fatalf("first contiguous ack=%d, want %d", got, want)
736795 }
737796
738- ackSeq, ack, queued = tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("second")})
797+ ackSeq, ack, queued = tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("second")}, 0)
739798 if !ack || !queued {
740799 t.Fatalf("gap fill enqueue ack=%v queued=%v, want true/true", ack, queued)
741800 }
@@ -966,12 +1025,12 @@ func TestReliableRecvBufferBoundedForFarFutureSequences(t *testing.T) {
9661025 tr := &Transport{nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame), maxRecvBuffer: maxRecvBuffer, done: make(chan struct{})}
9671026 tr.deliverCond = sync.NewCond(&tr.deliverMu)
9681027 for i := range maxRecvBuffer + 100 {
969- tr.enqueueReliable(uint64(1000+i), ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}})
1028+ tr.enqueueReliable(uint64(1000+i), ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}, 0)
9701029 }
9711030 if got := len(tr.recvBuf); got != maxRecvBuffer {
9721031 t.Fatalf("recvBuf len=%d, want %d", got, maxRecvBuffer)
9731032 }
974- tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")})
1033+ tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}, 0)
9751034 if _, ok := tr.recvBuf[1]; !ok {
9761035 t.Fatalf("next expected sequence was dropped when far-future buffer was full")
9771036 }
@@ -994,14 +1053,14 @@ func TestReliableFullRecvBufferDoesNotAckDroppedFarFutureFrame(t *testing.T) {
9941053 for i := range maxRecvBuffer {
9951054 tr.recvBuf[uint64(1000+i)] = ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}
9961055 }
997- tr.handleRecord(encodeData(9999, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("dropped")}))
1056+ tr.handleRecord(encodeData(9999, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("dropped")}, 0))
9981057 select {
9991058 case pkt := <-bPC.in:
10001059 t.Fatalf("unexpected ACK packet for dropped frame: %x", pkt.b)
10011060 case <-time.After(25 * time.Millisecond):
10021061 }
10031062
1004- tr.handleRecord(encodeData(1, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}))
1063+ tr.handleRecord(encodeData(1, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}, 0))
10051064 select {
10061065 case <-bPC.in:
10071066 case <-time.After(time.Second):
@@ -389,7 +389,7 @@ func TestAuthenticatedPeerChangeUpdatesPeerAndEmitsConnected(t *testing.T) {
389 t.Fatalf("unauthenticated packet changed peer to %v", a.Peer())389 t.Fatalf("unauthenticated packet changed peer to %v", a.Peer())
390 }390 }
391 c, _ := pdgram.NewCodec(key())391 c, _ := pdgram.NewCodec(key())
392- rec := encodeData(7, true, ports.Frame{Type: ports.MsgPing})392+ rec := encodeData(7, true, ports.Frame{Type: ports.MsgPing}, 0)
393 frags, _ := pdgram.FragmentPayload(9, rec, pdgram.DefaultMTU)393 frags, _ := pdgram.FragmentPayload(9, rec, pdgram.DefaultMTU)
394 raw, _ := pdgram.MarshalFragment(frags[0])394 raw, _ := pdgram.MarshalFragment(frags[0])
395 aPC.in <- packet{c.Seal(2, 99, raw, nil), testAddr("evil")}395 aPC.in <- packet{c.Seal(2, 99, raw, nil), testAddr("evil")}
@@ -511,11 +511,11 @@ func TestServerWithoutRebindDoesNotHopPorts(t *testing.T) {
511 511
512 func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) {512 func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) {
513 tr := &Transport{maxRecvBuffer: 1, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)}513 tr := &Transport{maxRecvBuffer: 1, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)}
514- _, ack, queued := tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("future")})514+ _, ack, queued := tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("future")}, 0)
515 if !ack || !queued {515 if !ack || !queued {
516 t.Fatalf("future enqueue ack=%v queued=%v, want true/true", ack, queued)516 t.Fatalf("future enqueue ack=%v queued=%v, want true/true", ack, queued)
517 }517 }
518- _, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")})518+ _, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}, 0)
519 if !ack || !queued {519 if !ack || !queued {
520 t.Fatalf("next enqueue ack=%v queued=%v, want true/true", ack, queued)520 t.Fatalf("next enqueue ack=%v queued=%v, want true/true", ack, queued)
521 }521 }
@@ -524,6 +524,32 @@ func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) {
524 }524 }
525 }525 }
526 526
527+func TestDataRecordWireEncodingOutputSkip(t *testing.T) {
528+ got := encodeData(5, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("x")}, 3)
529+ want := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 1, 0, 0, 0, 0, 0, 0, 0, 3, 'x'}
530+ if !bytes.Equal(got, want) {
531+ t.Fatalf("encoded data=%v, want %v", got, want)
532+ }
533+ seq, reliable, frame, skip, ok := decodeData(got)
534+ if !ok || seq != 5 || !reliable || frame.Type != ports.MsgOutput || string(frame.Payload) != "x" || skip != 3 {
535+ t.Fatalf("decoded seq=%d reliable=%v frame=%+v skip=%d ok=%v", seq, reliable, frame, skip, ok)
536+ }
537+}
538+
539+func TestDataRecordWireDecodeRejectsTruncatedOutputSkip(t *testing.T) {
540+ truncated := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 1, 0}
541+ if _, _, _, _, ok := decodeData(truncated); ok {
542+ t.Fatal("decodeData accepted truncated output skip header")
543+ }
544+}
545+
546+func TestDataRecordWireDecodeRejectsUnknownFlags(t *testing.T) {
547+ withTrailingGarbage := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 0x80, 'x'}
548+ if _, _, _, _, ok := decodeData(withTrailingGarbage); ok {
549+ t.Fatal("decodeData accepted unknown data-record flags with trailing garbage")
550+ }
551+}
552+
527 func TestOutputRetransmitsUntilAck(t *testing.T) {553 func TestOutputRetransmitsUntilAck(t *testing.T) {
528 aPC, bPC := newPair()554 aPC, bPC := newPair()
529 var dropped atomic.Bool555 var dropped atomic.Bool
@@ -551,7 +577,7 @@ func TestOutputRetransmitsUntilAck(t *testing.T) {
551 }577 }
552 }578 }
553 579
554-func TestReliableDeliveryPreservesSequenceOrder(t *testing.T) {580+func TestReliableDeliveryPreservesInputSequenceOrder(t *testing.T) {
555 aPC, bPC := newPair()581 aPC, bPC := newPair()
556 a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2)582 a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2)
557 b, _ := NewTransport(bPC, testAddr("a"), key(), 2, 1)583 b, _ := NewTransport(bPC, testAddr("a"), key(), 2, 1)
@@ -559,18 +585,51 @@ func TestReliableDeliveryPreservesSequenceOrder(t *testing.T) {
559 defer func() { _ = b.Close() }()585 defer func() { _ = b.Close() }()
560 586
561 for i := range 100 {587 for i := range 100 {
562- if err := a.Send(ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}); err != nil {588+ if err := a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte{byte(i)}}); err != nil {
563 t.Fatal(err)589 t.Fatal(err)
564 }590 }
565 }591 }
566 for i := range 100 {592 for i := range 100 {
567 got := recvWithin(t, b, time.Second)593 got := recvWithin(t, b, time.Second)
568- if got.Type != ports.MsgOutput || len(got.Payload) != 1 || got.Payload[0] != byte(i) {594+ if got.Type != ports.MsgInput || len(got.Payload) != 1 || got.Payload[0] != byte(i) {
569 t.Fatalf("frame %d delivered out of order: %+v", i, got)595 t.Fatalf("frame %d delivered out of order: %+v", i, got)
570 }596 }
571 }597 }
572 }598 }
573 599
600+func TestOutputSupersessionSkipsDroppedOlderOutput(t *testing.T) {
601+ aPC, bPC := newPair()
602+ var sendAttempts atomic.Int32
603+ aPC.drop = func(_ []byte, addr net.Addr) bool {
604+ return addr.String() == "b" && sendAttempts.Add(1) <= 2
605+ }
606+ a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2)
607+ b, _ := NewTransport(bPC, testAddr("a"), key(), 2, 1)
608+ defer func() { _ = a.Close() }()
609+ defer func() { _ = b.Close() }()
610+
611+ for _, payload := range []string{"state1", "state2", "state3"} {
612+ if err := a.Send(ports.Frame{Type: ports.MsgOutput, Payload: []byte(payload)}); err != nil {
613+ t.Fatal(err)
614+ }
615+ }
616+
617+ a.mu.Lock()
618+ pending := len(a.pending)
619+ a.mu.Unlock()
620+ if pending > 1 {
621+ t.Fatalf("pending outputs=%d, want collapsed to newest", pending)
622+ }
623+
624+ got := recvWithin(t, b, time.Second)
625+ if got.Type != ports.MsgOutput || string(got.Payload) != "state3" {
626+ t.Fatalf("got=%+v, want newest output after skipped predecessors", got)
627+ }
628+ if sendAttempts.Load() < 3 {
629+ t.Fatalf("send attempts=%d, want first two output sends dropped before newest arrived", sendAttempts.Load())
630+ }
631+}
632+
574 func TestAckProcessingContinuesWhenConsumerBackpressured(t *testing.T) {633 func TestAckProcessingContinuesWhenConsumerBackpressured(t *testing.T) {
575 aPC, bPC := newPair()634 aPC, bPC := newPair()
576 a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2)635 a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2)
@@ -719,7 +778,7 @@ func TestCumulativeAckReleasesAllEarlierPendingFrames(t *testing.T) {
719 func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) {778 func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) {
720 tr := &Transport{maxRecvBuffer: maxRecvBuffer, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)}779 tr := &Transport{maxRecvBuffer: maxRecvBuffer, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)}
721 780
722- ackSeq, ack, queued := tr.enqueueReliable(3, ports.Frame{Type: ports.MsgOutput, Payload: []byte("third")})781+ ackSeq, ack, queued := tr.enqueueReliable(3, ports.Frame{Type: ports.MsgOutput, Payload: []byte("third")}, 0)
723 if !ack || !queued {782 if !ack || !queued {
724 t.Fatalf("out-of-order enqueue ack=%v queued=%v, want true/true", ack, queued)783 t.Fatalf("out-of-order enqueue ack=%v queued=%v, want true/true", ack, queued)
725 }784 }
@@ -727,7 +786,7 @@ func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) {
727 t.Fatalf("out-of-order ack=%d, want highest contiguous %d", got, want)786 t.Fatalf("out-of-order ack=%d, want highest contiguous %d", got, want)
728 }787 }
729 788
730- ackSeq, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("first")})789+ ackSeq, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("first")}, 0)
731 if !ack || !queued {790 if !ack || !queued {
732 t.Fatalf("first enqueue ack=%v queued=%v, want true/true", ack, queued)791 t.Fatalf("first enqueue ack=%v queued=%v, want true/true", ack, queued)
733 }792 }
@@ -735,7 +794,7 @@ func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) {
735 t.Fatalf("first contiguous ack=%d, want %d", got, want)794 t.Fatalf("first contiguous ack=%d, want %d", got, want)
736 }795 }
737 796
738- ackSeq, ack, queued = tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("second")})797+ ackSeq, ack, queued = tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("second")}, 0)
739 if !ack || !queued {798 if !ack || !queued {
740 t.Fatalf("gap fill enqueue ack=%v queued=%v, want true/true", ack, queued)799 t.Fatalf("gap fill enqueue ack=%v queued=%v, want true/true", ack, queued)
741 }800 }
@@ -966,12 +1025,12 @@ func TestReliableRecvBufferBoundedForFarFutureSequences(t *testing.T) {
966 tr := &Transport{nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame), maxRecvBuffer: maxRecvBuffer, done: make(chan struct{})}1025 tr := &Transport{nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame), maxRecvBuffer: maxRecvBuffer, done: make(chan struct{})}
967 tr.deliverCond = sync.NewCond(&tr.deliverMu)1026 tr.deliverCond = sync.NewCond(&tr.deliverMu)
968 for i := range maxRecvBuffer + 100 {1027 for i := range maxRecvBuffer + 100 {
969- tr.enqueueReliable(uint64(1000+i), ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}})1028+ tr.enqueueReliable(uint64(1000+i), ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}, 0)
970 }1029 }
971 if got := len(tr.recvBuf); got != maxRecvBuffer {1030 if got := len(tr.recvBuf); got != maxRecvBuffer {
972 t.Fatalf("recvBuf len=%d, want %d", got, maxRecvBuffer)1031 t.Fatalf("recvBuf len=%d, want %d", got, maxRecvBuffer)
973 }1032 }
974- tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")})1033+ tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}, 0)
975 if _, ok := tr.recvBuf[1]; !ok {1034 if _, ok := tr.recvBuf[1]; !ok {
976 t.Fatalf("next expected sequence was dropped when far-future buffer was full")1035 t.Fatalf("next expected sequence was dropped when far-future buffer was full")
977 }1036 }
@@ -994,14 +1053,14 @@ func TestReliableFullRecvBufferDoesNotAckDroppedFarFutureFrame(t *testing.T) {
994 for i := range maxRecvBuffer {1053 for i := range maxRecvBuffer {
995 tr.recvBuf[uint64(1000+i)] = ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}1054 tr.recvBuf[uint64(1000+i)] = ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}
996 }1055 }
997- tr.handleRecord(encodeData(9999, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("dropped")}))1056+ tr.handleRecord(encodeData(9999, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("dropped")}, 0))
998 select {1057 select {
999 case pkt := <-bPC.in:1058 case pkt := <-bPC.in:
1000 t.Fatalf("unexpected ACK packet for dropped frame: %x", pkt.b)1059 t.Fatalf("unexpected ACK packet for dropped frame: %x", pkt.b)
1001 case <-time.After(25 * time.Millisecond):1060 case <-time.After(25 * time.Millisecond):
1002 }1061 }
1003 1062
1004- tr.handleRecord(encodeData(1, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}))1063+ tr.handleRecord(encodeData(1, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}, 0))
1005 select {1064 select {
1006 case <-bPC.in:1065 case <-bPC.in:
1007 case <-time.After(time.Second):1066 case <-time.After(time.Second):
M internal/ports/frame.go +1 -1
@@ -1,7 +1,7 @@
11 package ports
22
33 // ProtocolVersion is the current vev IPC wire protocol version.
4-const ProtocolVersion uint16 = 10
4+const ProtocolVersion uint16 = 11
55
66 // MsgType identifies the kind of payload carried by a Frame.
77 type MsgType uint8
@@ -1,7 +1,7 @@
1 package ports1 package ports
2 2
3 // ProtocolVersion is the current vev IPC wire protocol version.3 // ProtocolVersion is the current vev IPC wire protocol version.
4-const ProtocolVersion uint16 = 104+const ProtocolVersion uint16 = 11
5 5
6 // MsgType identifies the kind of payload carried by a Frame.6 // MsgType identifies the kind of payload carried by a Frame.
7 type MsgType uint87 type MsgType uint8
M internal/ports/frame_test.go +2 -2
@@ -3,8 +3,8 @@ package ports
33 import "testing"
44
55 func TestProtocolVersion(t *testing.T) {
6- if ProtocolVersion != 10 {
7- t.Fatalf("ProtocolVersion = %d, want 10", ProtocolVersion)
6+ if ProtocolVersion != 11 {
7+ t.Fatalf("ProtocolVersion = %d, want 11", ProtocolVersion)
88 }
99 }
1010
@@ -3,8 +3,8 @@ package ports
3 import "testing"3 import "testing"
4 4
5 func TestProtocolVersion(t *testing.T) {5 func TestProtocolVersion(t *testing.T) {
6- if ProtocolVersion != 10 {6+ if ProtocolVersion != 11 {
7- t.Fatalf("ProtocolVersion = %d, want 10", ProtocolVersion)7+ t.Fatalf("ProtocolVersion = %d, want 11", ProtocolVersion)
8 }8 }
9 }9 }
10 10

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close