diff --git a/tcp/txqueue.go b/tcp/txqueue.go index d7841bd..46e175a 100644 --- a/tcp/txqueue.go +++ b/tcp/txqueue.go @@ -1,8 +1,11 @@ package tcp import ( + "bytes" "errors" + "fmt" "slices" + "strconv" "github.com/soypat/lneto/internal" ) @@ -149,7 +152,10 @@ func (rtx *ringTx) MakePacket(b []byte, currentSeq Value) (int, error) { // RecvSegment processes an incoming segment and updates the sent packet queue func (rtx *ringTx) RecvACK(ack Value) error { - rtx.slist.RecvAck(ack, rtx.Size()) + err := rtx.slist.RecvAck(ack, rtx.Size()) + if err != nil { + return err + } oldest := rtx.slist.Oldest() newest := rtx.slist.Newest() if oldest == nil { @@ -292,7 +298,13 @@ func (sl *sentlist) AddPacket(datalen, off, bufsize int) *ringidx { return &sl.pkts[len(sl.pkts)-1] } -func (sl *sentlist) RecvAck(ack Value, bufsize int) { +func (sl *sentlist) RecvAck(ack Value, bufsize int) error { + newest := sl.Newest() + if newest == nil { + return errors.New("no packet to ack") + } else if newest.endSeq().LessThan(ack) { + return errors.New("ack of unsent packet") + } // Mark fully acked. for i := 0; i < len(sl.pkts); i++ { pkt := &sl.pkts[i] @@ -308,16 +320,17 @@ func (sl *sentlist) RecvAck(ack Value, bufsize int) { sl.removeRecvd() maybePartial := sl.Oldest() if maybePartial == nil { - return // No more packets, all acked. + return nil // No more packets, all acked. } totalAcked := int32(ack - maybePartial.seq) isPartial := totalAcked > 0 if !isPartial { - return // Not a partial packet ack. + return nil // Not a partial packet ack. } maybePartial.off = addOff(maybePartial.off, int(totalAcked), bufsize) maybePartial.size -= Size(totalAcked) maybePartial.seq += Value(totalAcked) + return nil } func (sl *sentlist) removeRecvd() { @@ -353,3 +366,135 @@ func addOff(a, b int, size int) int { } return result } + +// prints out buffer zones with indices: +// +// 0 32 42 47 +// |---free(32)---|---usnt(10)---|---free(5)---| +func (rtx *ringTx) appendString(b []byte) []byte { + size := rtx.Size() + type zone struct { + name string + start, end int + } + zcontains := func(off int, z *zone) bool { + if z.end == 0 { + return false // Empty + } else if z.end < z.start { + // zone wraps. + } + return off >= z.start && off < z.end + } + + zs := zone{name: "sent", start: rtx.sentoff, end: rtx.sentend} + zu := zone{name: "usnt", start: rtx.unsentoff, end: rtx.unsentend} + bufStart := zs.start + if bufStart == 0 { + bufStart = zu.start + } + bufEnd := zu.end + if bufEnd == 0 { + bufEnd = zs.end + } + zf := zone{name: "free", start: bufEnd, end: bufStart} + getZone := func(off int) *zone { + if zcontains(0, &zs) { + return &zs + } else if zcontains(0, &zu) { + return &zu + } else { + return &zf + } + } + + zones := []*zone{getZone(0)} + for i := 1; i < size; i++ { + z := getZone(i) + if z != zones[len(zones)-1] { + zones = append(zones, z) + } + } + + var wrapZone *zone + for i := range zones { + wraps := zones[i].end != 0 && zones[i].end < zones[i].start + if wraps { + if wrapZone != nil { + panic("illegal to have more than one wrap zone") + } + wrapZone = zones[i] + } + } + // ---- your simple approach starts here ---- + var currentZone *zone + if wrapZone != nil { + currentZone = wrapZone + } else { + currentZone = zones[0] + } + + var lastPrintedZone *zone + var l1, l2 bytes.Buffer + changes := 0 + zoneLen := func(z *zone, sz int) int { + if z.end == 0 { + return 0 + } + if z.end < z.start { + return (sz - z.start) + z.end + } + return z.end - z.start + } + for ib := 0; ib < size; ib++ { + // see if current zone still contains this index + currentContainsIdx := currentZone != nil && zcontains(ib, currentZone) + if !currentContainsIdx { + // find which zone contains this index + for _, z := range zones { + if zcontains(ib, z) { + currentZone = z + currentContainsIdx = true + break + } + } + } + // if still same zone, keep going + if currentZone == lastPrintedZone { + continue + } + + // zone changed + changes++ + if changes > 4 { + panic("found too many zone changes") + } + lastPrintedZone = currentZone + + // build the bottom line segment + seg := "|---" + currentZone.name + "(" + strconv.Itoa(zoneLen(currentZone, size)) + ")---" + l2.WriteString(seg) + + // write the start index aligned to seg width + n, _ := fmt.Fprintf(&l1, "%d", currentZone.start) + for i := 0; i < len(seg)-n; i++ { + l1.WriteByte(' ') + } + } + + // close last zone: print its end index and closing bar + l2.WriteByte('|') + + // if the last zone "ends" at 0 because of wrap, use sz + endIdx := lastPrintedZone.end + if endIdx == 0 { + endIdx = size + } + fmt.Fprintf(&l1, "%d\n", endIdx) + + // write second line under the first + l2.WriteTo(&l1) + l1.WriteByte('\n') + + b = append(b, l1.Bytes()...) + return b +} diff --git a/tcp/txqueue_test.go b/tcp/txqueue_test.go index b41a454..311aba8 100644 --- a/tcp/txqueue_test.go +++ b/tcp/txqueue_test.go @@ -323,7 +323,7 @@ func testQueueSanity(t *testing.T, rtx *ringTx) { sz := rtx.Size() gotSz := free + sent + unsent if gotSz != sz { - t.Error("\n" + rtx.string()) + t.Error("\n" + string(rtx.appendString(nil))) t.Fatalf("want size=%d, got size=%d (free+sent+unsent=%d+%d+%d)", sz, gotSz, free, sent, unsent) } rsent, _ := rtx.sentRing()