package tcp import ( "log/slog" "github.com/soypat/lneto" "github.com/soypat/lneto/internal" ) const ( // this must be at least 2 for buffer to work. minBufferSize = 2 ) // ringTx is a ring buffer with retransmission queue functionality added. // // | acked(free) | sent | unsent | free | // 0 freeEnd=first.off last.end==unsent.off freeStart=unsent.end Size() type ringTx struct { // rawbuf contains the ring buffer of ordered bytes. It should be the size of the window. rawbuf []byte // unsentOff is the offset of start of unsent data in rawbuf. unsentoff int // unsentend is the offset of end of unsent data in rawbuf. If zero then unsent buffer is empty. unsentend int // sentoff is the offset of start of sent data in rawbuf. sentoff int // sentend is the offset of end of sent data in rawbuf. If zero then sent buffer is empty. sentend int slist sentlist // seq Value // always empty ring. emptyRing ringidx iss Value } // ringidx represents packet data inside RingTx. // Part of the retransmission queue required by RFC 9293 §3.4, §3.10.8. type ringidx struct { // off is data start offset of packet data inside buf. Follows [internal.Ring] semantics. off int // end is the ringed data end offset, non-inclusive. Follows [internal.Ring] semantics. end int // seq is the sequence number of the first byte in the packet. seq Value // size is the size of the packet in bytes. size Size } // Reset resets the RingTx's internal state to use buf as the main ring buffer and creates or reuses // the packet ring buffer. func (rtx *ringTx) Reset(buf []byte, maxqueuedPackets int, iss Value) error { buf = buf[:len(buf):len(buf)] // safely omit capacity section. if maxqueuedPackets <= 0 { return lneto.ErrInvalidConfig } else if len(buf) < minBufferSize || len(buf) < maxqueuedPackets { return lneto.ErrShortBuffer } *rtx = ringTx{ rawbuf: buf, slist: rtx.slist, } rtx.slist.Reset(maxqueuedPackets, iss) rtx.iss = iss return nil } // ResetOrReuse is identical to a call to [ringTx.Reset] with the additional detail that // the zero value of buf (nil) and maxQueuedPackets (0) will selectively reuse existing data buffer and/or packet index buffer. func (rtx *ringTx) ResetOrReuse(buf []byte, maxQueuedPackets int, ack Value) error { if buf == nil { buf = rtx.rawbuf } if maxQueuedPackets == 0 { maxQueuedPackets = cap(rtx.slist.pkts) } return rtx.Reset(buf, maxQueuedPackets, ack) } // Size returns the total storage space of the transmission buffer. func (rtx *ringTx) Size() int { return len(rtx.rawbuf) } // Free returns the total available space for Write calls. func (rtx *ringTx) Free() int { r := rtx.sentAndUnsentBuffer() return r.Free() } // BufferedUnsent returns the amount of written but unsent bytes. func (rtx *ringTx) BufferedUnsent() int { r, _ := rtx.unsentRing() return r.Buffered() } // BufferedSent returns the total amount of bytes sent but not acked. func (rtx *ringTx) BufferedSent() int { r, _ := rtx.sentRing() return r.Buffered() } // Write writes data to the underlying unsent data ring buffer. func (rtx *ringTx) Write(b []byte) (n int, err error) { unsent, lim := rtx.unsentRing() if rtx.sentend == 0 { n, err = unsent.Write(b) // catches case where limit matches with end when both buffers empty } else { n, err = unsent.WriteLimited(b, lim) } if err != nil { return 0, err } rtx.unsentend = unsent.End return n, err } // MakePacket reads from the unsent data ring buffer and generates a new packet segment. // It fails if the sent packet queue is full. func (rtx *ringTx) MakePacket(b []byte, currentSeq Value) (int, error) { free := rtx.slist.Free() if free == 0 { return 0, lneto.ErrBufferFull } endSeq, ok := rtx.sentEndSeq() if ok && currentSeq.LessThan(endSeq) { // maybe retransmit. Look for exact match. for i := range rtx.slist.pkts { pkt := &rtx.slist.pkts[i] if pkt.seq == currentSeq { // This packet to be retransmit. data := rtx.ring(pkt.off, pkt.end) return data.Read(b) } } internal.LogAttrs(nil, slog.LevelError, "txqueue:seq 0 if !isPartial { 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() { if !sl.Oldest().isRecvd() { return // No packets to remove. } off := 0 for i := 0; i < len(sl.pkts); i++ { if sl.pkts[i].isRecvd() { continue } else { sl.pkts[off] = sl.pkts[i] off++ } } sl.pkts = sl.pkts[:off] } // addEnd adds two integers together and wraps the value around the ring's buffer size. // Result of addEnd will never be 0 unless arguments are (0,0). func addEnd(a, b int, size int) int { result := a + b if result > size { result -= size } return result } func addOff(a, b int, size int) int { result := a + b if result >= size { result -= size } return result }