diff --git a/tcp/handler.go b/tcp/handler.go index 7cffc89..632c26b 100644 --- a/tcp/handler.go +++ b/tcp/handler.go @@ -504,6 +504,19 @@ func (h *Handler) nextSegmentIsRetransmit() bool { return hasSent && h.scb.snd.NXT.LessThan(endSeq) } +// NextSegmentSYN returns syn=true if next outgoing segment is a handshake SYN. +// This method is exported for use by [Policy] implementations to decide handshake-only options (window scale, SACK-permitted, timestamps). +func (h *Handler) NextSegmentSYN() (syn, ack bool) { + state := h.scb.State() + if h.AwaitingSynSend() || h.requeueControl && state == StateSynSent { + return true, false // SYN initial/requeue. + } else if h.requeueControl && state == StateSynRcvd { + return true, true // SYNACK requeue. + } + pending := h.scb.pending[0] + return pending.HasAny(FlagSYN), pending.HasAny(FlagACK) +} + // Write implements [io.Writer] by copying b to a internal buffer to be sent over the network on the next // [Handler.Send] call that can send data to remote peer. Use [Handler.Free] to know the maximum length the argument slice can be before erroring. func (h *Handler) Write(b []byte) (int, error) { diff --git a/tcp/policy.go b/tcp/policy.go index 200438c..580bd96 100644 --- a/tcp/policy.go +++ b/tcp/policy.go @@ -1,5 +1,8 @@ package tcp +// TransmitUnlimited size returned by [Policy.PreTx] to signal no new data transmit limit (no congestion control). +const TransmitUnlimited = ^Size(0) + // Policy observes segment traffic and steers transmit behaviour: RTO, // congestion control and the like (discussion #157). The tcp package holds no // clock, so a Policy needing time carries its own (issue #140). @@ -8,17 +11,20 @@ type Policy interface { // Reset returns the Policy to its pre-connection state. Called on every // (re)open and Abort. Must preserve configuration such as a clock. Reset() + // PreTx is called before writing to a frame. // The outgoing frame options can be set by the Policy and will be respected if Frame offset >5. - // rtxFrom is ignored unless within [snd.UNA, snd.NXT]. Nothing is committed - // until PostTx: a transmit attempt may emit no segment at all. - PreTx(h *Handler, outgoingOpts Frame) (rtxFrom Value, retransmit, holdNew bool) + // rtxFrom is ignored unless within [snd.UNA, snd.NXT] and returned retransmit==true. + // newTransmitLimit sets the maximum number of new bytes to send over the wire (congestion control). + // If not implementing congestion control then newTransmitLimit=[TransmitUnlimited]. + PreTx(h *Handler, outgoingOpts Frame) (newTransmitLimit Size, rtxFrom Value, retransmit bool) + // PostTx called on leaving the transmit path with the fully written frame. + PostTx(h *Handler, outgoing Frame) + // PreRx is called by [Handler] on every incoming segment. // PreRx can choose to drop segment if it returns keep=false. PreRx(h *Handler, incoming Frame) (keep bool) // PostRx is called by [Handler] after accepting an incoming segment. // TODO: congestion control will also want the pre-Recv snd.UNA here. PostRx(h *Handler, prevState State, accepted Frame) - // PostTx called on leaving the transmit path with the fully written frame. - PostTx(h *Handler, outgoing Frame) }