Documentation
¶
Overview ¶
Package framer provides portable message framing over io.Reader and io.Writer.
Semantics and design:
- Protocol adaptation: on stream transports such as TCP, framer adds a compact length prefix and preserves one-message-per-Read/Write. On boundary-preserving transports such as SCTP, UDP, and WebSocket, framer is pass-through.
- Packet-mode limit semantics: Reader.Read checks WithReadLimit after one transport read; oversized packets may return (n > limit, ErrTooLong), where n is the number of bytes copied from that packet. Reader.WriteTo and Forwarder use a sentinel byte to reject oversized packets before forwarding bytes.
- Non-blocking first: ErrWouldBlock and ErrMore are control-flow signals, not failures. Hot paths avoid allocations and return promptly.
- Performance contract: hot paths keep runtime validation minimal; callers are responsible for valid option and buffer usage plus operation-specific retry after ErrWouldBlock or ErrMore.
- io compatibility: Reader, Writer, and ReadWriter implement standard io interfaces and honor io.Writer short-write contracts and io.Reader buffer semantics.
Wire format (stream mode): a 1-byte header followed by optional extended length bytes and then the payload. Let L be payload length in bytes:
- 0 <= L <= 253: header[0] = L (no extended length)
- 254 <= L <= 65535: header[0] = 0xFE; next 2 bytes encode L (configured byte order)
- 65536 <= L <= 2^56-1: header[0] = 0xFF; next 7 bytes encode lower 56 bits of L in the configured byte order
Maximum supported payload is 2^56-1; larger values produce ErrTooLong. A per-reader limit can be set via WithReadLimit.
Index ¶
- Variables
- func NewPipe(opts ...Option) (reader io.Reader, writer io.Writer)
- func NewReadWriter(r io.Reader, w io.Writer, opts ...Option) io.ReadWriter
- func NewReader(r io.Reader, opts ...Option) io.Reader
- func NewWriter(w io.Writer, opts ...Option) io.Writer
- type Forwarder
- type Option
- func WithBlock() Option
- func WithByteOrder(order binary.ByteOrder) Option
- func WithNonblock() Option
- func WithProtocol(proto Protocol) Option
- func WithReadByteOrder(order binary.ByteOrder) Option
- func WithReadLimit(limit int) Option
- func WithReadLocal() Option
- func WithReadProtocol(proto Protocol) Option
- func WithReadSCTP() Option
- func WithReadTCP() Option
- func WithReadUDP() Option
- func WithReadUnix() Option
- func WithReadUnixPacket() Option
- func WithReadWebSocket() Option
- func WithRetryDelay(d time.Duration) Option
- func WithWriteByteOrder(order binary.ByteOrder) Option
- func WithWriteLocal() Option
- func WithWriteProtocol(proto Protocol) Option
- func WithWriteSCTP() Option
- func WithWriteTCP() Option
- func WithWriteUDP() Option
- func WithWriteUnix() Option
- func WithWriteUnixPacket() Option
- func WithWriteWebSocket() Option
- type Options
- type Protocol
- type ReadWriter
- type Reader
- type Writer
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidArgument reports an invalid configuration or nil reader/writer. ErrInvalidArgument = errors.New("framer: invalid argument") // ErrTooLong reports that a message exceeds a configured limit, transfer cap, // or wire-format bound. ErrTooLong = errors.New("framer: message too long") )
var ( // ErrWouldBlock reports that the current non-blocking transport attempt cannot // progress without waiting. // // It is a control-flow signal, not a failure. Aggregate helpers may return a // positive count with ErrWouldBlock when earlier steps in the same call made // progress before the current attempt suspended. // // ErrWouldBlock is an alias for iox.ErrWouldBlock. ErrWouldBlock = iox.ErrWouldBlock // ErrMore reports that the same operation has additional progress to deliver. // // It is not io.EOF and not readiness suspension. Process any returned // progress, then call the same operation again. // // ErrMore is an alias for iox.ErrMore. ErrMore = iox.ErrMore )
Functions ¶
func NewPipe ¶
NewPipe returns a synchronous in-memory framing pipe.
The pipe is backed by io.Pipe. Both returned interfaces refer to the same state-bearing ReadWriter, so callers coordinate reads and writes as one synchronous pipe rather than treating them as independent endpoints.
func NewReadWriter ¶
NewReadWriter returns an io.ReadWriter that reads and writes framed messages.
The returned value shares one state machine for both directions and is intended for coordinated use. Concurrent access requires caller synchronization.
func NewReader ¶
NewReader returns an io.Reader that reads framed messages from r.
The returned dynamic type is *Reader. Callers that need Reader.WriteTo can type assert the result to *Reader.
Types ¶
type Forwarder ¶
type Forwarder struct {
// contains filtered or unexported fields
}
Forwarder relays framed messages from a source to a destination while preserving message boundaries.
Semantics (BinaryStream):
- One call to ForwardOnce processes at most one logical message.
- Two-phase state machine per message: 1) Read a whole framed message payload from src into an internal buffer (non-blocking; may return early with partial progress and ErrWouldBlock or ErrMore). 2) Write that same payload as exactly one framed message to dst (non-blocking; may return early with partial progress and ErrWouldBlock or ErrMore).
- Returns (n, nil) when a whole message payload has been forwarded to dst.
- Returns ErrWouldBlock or ErrMore when the current phase cannot continue without a later retry. The returned n is the current-phase progress and can be zero when that phase accepted no payload bytes.
- Message boundaries are preserved: the destination sees exactly the same payload bytes as the source, encoded as one framed message on stream transports.
Semantics (SeqPacket/Datagram):
- Treats one packet as one message unit per call. Reads one packet from src and writes one packet to dst.
- If the packet source returns (n > 0, err), ForwardOnce forwards that packet before reporting err. A final (n > 0, io.EOF) packet is reported as nil after the packet is forwarded, and the next call reports io.EOF.
- Destination packet writes are atomic: zero-progress ErrWouldBlock or ErrMore keeps the whole packet pending, while a partial packet write fails with io.ErrShortWrite rather than retrying a suffix as a second packet.
- Return counts follow the current ForwardOnce phase documented on Forwarder.ForwardOnce.
Limits and buffer sizing:
- The internal payload buffer is allocated during construction based on read-side limit (WithReadLimit). If ReadLimit is zero, a conservative default (64KiB) is used. There are no heap allocations in the steady-state forwarding path.
- If a stream message or required packet read capacity exceeds the internal buffer capacity, ForwardOnce returns io.ErrShortBuffer. Callers can construct a new Forwarder with a larger ReadLimit to accommodate larger messages.
- If a packet exceeds ReadLimit, or exceeds the default packet transfer cap when ReadLimit is zero, ForwardOnce returns ErrTooLong before forwarding bytes.
Retry rule:
- On ErrWouldBlock or ErrMore, the caller must retry ForwardOnce on the same Forwarder instance. The instance may hold an in-flight write or a pending source signal that must remain ordered with the forwarded packet.
func NewForwarder ¶
NewForwarder returns a Forwarder that relays messages from src to dst.
Options apply to both directions unless a read-side or write-side option is used.
func (*Forwarder) ForwardOnce ¶
ForwardOnce forwards at most one message.
See Forwarder documentation for stream, packet, limit, and retry semantics.
Return value n reflects progress in the current phase:
- During the read phase, n is the number of payload bytes read into the internal buffer in this call.
- During the write phase, n is the number of payload bytes written to dst in this call.
- In packet mode, if the source returns n > 0 with an error, ForwardOnce emits that packet before reporting the source error. If write-side suspension happens first, the source error remains pending for a later ForwardOnce call on the same Forwarder.
type Option ¶
type Option func(*Options)
Option configures Reader, Writer, ReadWriter, and Forwarder construction.
Later options override earlier settings for the same field.
func WithBlock ¶
func WithBlock() Option
WithBlock enables cooperative blocking by yielding and retrying on zero-progress ErrWouldBlock.
func WithByteOrder ¶
WithByteOrder sets both read and write byte order for BinaryStream length prefixes.
func WithNonblock ¶
func WithNonblock() Option
WithNonblock forces non-blocking behavior by returning zero-progress ErrWouldBlock immediately.
func WithProtocol ¶
WithProtocol sets both read and write protocol modes.
func WithReadByteOrder ¶
WithReadByteOrder sets the read-side byte order for BinaryStream length prefixes.
func WithReadLimit ¶
WithReadLimit sets the maximum accepted payload size on the read side.
In SeqPacket and Datagram modes, the limit is checked after a packet read, so an oversized packet may return (n > limit, ErrTooLong). A non-positive limit means no caller-configured limit; transfer helpers may still apply their documented internal buffer cap.
func WithReadLocal ¶
func WithReadLocal() Option
WithReadLocal configures the reader side for local stream transports with BinaryStream and native byte order.
func WithReadProtocol ¶
WithReadProtocol sets the reader-side protocol mode.
func WithReadSCTP ¶
func WithReadSCTP() Option
WithReadSCTP configures the reader side for SCTP with SeqPacket pass-through and BigEndian byte order.
func WithReadTCP ¶
func WithReadTCP() Option
WithReadTCP configures the reader side for TCP with BinaryStream and BigEndian length prefixes.
func WithReadUDP ¶
func WithReadUDP() Option
WithReadUDP configures the reader side for UDP with Datagram pass-through and BigEndian byte order.
func WithReadUnix ¶
func WithReadUnix() Option
WithReadUnix configures the reader side for Unix stream sockets with BinaryStream and BigEndian byte order.
func WithReadUnixPacket ¶
func WithReadUnixPacket() Option
WithReadUnixPacket configures the reader side for Unix packet sockets with Datagram pass-through and BigEndian byte order.
func WithReadWebSocket ¶
func WithReadWebSocket() Option
WithReadWebSocket configures the reader side for WebSocket with SeqPacket pass-through and BigEndian byte order.
func WithRetryDelay ¶
WithRetryDelay sets the wait policy used when the underlying transport returns ErrWouldBlock without transferring bytes.
A negative duration returns ErrWouldBlock immediately. A zero duration yields and retries. A positive duration sleeps for d before retrying. Positive-progress ErrWouldBlock is returned to the caller with its byte count.
func WithWriteByteOrder ¶
WithWriteByteOrder sets the write-side byte order for BinaryStream length prefixes.
func WithWriteLocal ¶
func WithWriteLocal() Option
WithWriteLocal configures the writer side for local stream transports with BinaryStream and native byte order.
func WithWriteProtocol ¶
WithWriteProtocol sets the writer-side protocol mode.
func WithWriteSCTP ¶
func WithWriteSCTP() Option
WithWriteSCTP configures the writer side for SCTP with SeqPacket pass-through and BigEndian byte order.
func WithWriteTCP ¶
func WithWriteTCP() Option
WithWriteTCP configures the writer side for TCP with BinaryStream and BigEndian length prefixes.
func WithWriteUDP ¶
func WithWriteUDP() Option
WithWriteUDP configures the writer side for UDP with Datagram pass-through and BigEndian byte order.
func WithWriteUnix ¶
func WithWriteUnix() Option
WithWriteUnix configures the writer side for Unix stream sockets with BinaryStream and BigEndian byte order.
func WithWriteUnixPacket ¶
func WithWriteUnixPacket() Option
WithWriteUnixPacket configures the writer side for Unix packet sockets with Datagram pass-through and BigEndian byte order.
func WithWriteWebSocket ¶
func WithWriteWebSocket() Option
WithWriteWebSocket configures the writer side for WebSocket with SeqPacket pass-through and BigEndian byte order.
type Options ¶
type Options struct {
// ReadByteOrder controls BinaryStream length-prefix decoding.
ReadByteOrder binary.ByteOrder
// WriteByteOrder controls BinaryStream length-prefix encoding.
WriteByteOrder binary.ByteOrder
// ReadProto selects the reader-side protocol mode.
ReadProto Protocol
// WriteProto selects the writer-side protocol mode.
WriteProto Protocol
// ReadLimit caps the maximum accepted payload size in bytes.
// A non-positive value means no caller-configured read limit.
ReadLimit int
// RetryDelay controls how the framer handles zero-progress ErrWouldBlock
// from the underlying transport:
// - negative: nonblock, return ErrWouldBlock immediately
// - zero: yield (runtime.Gosched) and retry
// - positive: sleep for the duration and retry
//
// If the underlying read or write transfers bytes before returning
// ErrWouldBlock, the operation returns that positive count with ErrWouldBlock
// so callers can process progress before retrying.
RetryDelay time.Duration
}
Options configures framing behavior used by constructors.
Constructors start from package defaults before applying Option values. Callers normally pass Option helpers rather than constructing Options directly; the zero Options value is not a complete configuration because byte order fields may be nil.
type Protocol ¶
type Protocol uint8
Protocol describes the message-boundary behavior expected from the underlying transport.
The framer logic adapts its algorithm based on this setting:
- BinaryStream: boundaries are not preserved, for example TCP; framer adds a length prefix.
- SeqPacket and Datagram: boundaries are preserved; framer is pass-through.
type ReadWriter ¶
ReadWriter groups a Reader and Writer that share one framer state.
ReadWriter is a convenience wrapper for coordinated use. Concurrent access requires caller synchronization.
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader reads framed messages from a stream or packet-preserving transport.
A Reader carries in-flight state after ErrWouldBlock or ErrMore. Retry the interrupted operation on the same Reader instance before switching between Read and WriteTo.
func (*Reader) Read ¶
Read reads one message payload into p.
In SeqPacket and Datagram modes, Read passes through one transport read. In BinaryStream mode, p must fit the whole message payload or Read returns io.ErrShortBuffer without consuming the payload.
In SeqPacket/Datagram mode, WithReadLimit is enforced after one transport read, so an oversized packet may return (n > limit, ErrTooLong); n reports bytes copied from that packet.
If Read returns ErrWouldBlock or ErrMore after partial stream progress, retry Read on the same Reader instance with the same buffer until the message completes or a terminal error is returned.
func (*Reader) WriteTo ¶
WriteTo writes messages from r to dst until the source reaches EOF or returns an error.
Semantics:
- Stream (BinaryStream): transfers one framed message payload at a time from the underlying reader into dst. The payload bytes are written as-is; this method does not attempt to preserve or reconstruct framer wire format on the destination unless dst is itself a framer.Writer. It uses an internal reusable scratch buffer sized by the Reader's ReadLimit; when ReadLimit is zero, a conservative default cap is used (64KiB) and messages exceeding this cap result in ErrTooLong.
- Packet (SeqPacket/Datagram): pass-through byte transfer for arbitrary dst. Partial dst writes resume from an internal byte cursor on the next WriteTo call. When dst is a framer packet Writer, whole-packet retry is used after zero-progress ErrWouldBlock/ErrMore. When dst is a framer stream Writer, same-message frame retry is used so the destination frame length remains stable. ReadLimit is checked post-read with one sentinel byte, so oversized packets are rejected before forwarding.
Non-blocking semantics: if the underlying reader or writer returns iox.ErrWouldBlock or iox.ErrMore, WriteTo returns immediately with the progress count and the same control signal. Retry WriteTo on the same Reader instance and destination to resume the interrupted transfer. Short writes on dst are handled per io.Writer contract. If a packet source returns bytes with an error, that source error is reported after the admitted packet is emitted, even if write-side suspension requires a later WriteTo call.
The returned count is bytes written to dst. A rejected oversized packet does not add to the count.
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer writes framed messages to a stream or packet-preserving transport.
A Writer can carry in-flight state after ErrWouldBlock or ErrMore. Retry an interrupted BinaryStream Write or ReadFrom operation on the same Writer before switching operations. Direct packet-mode Write carries no hidden replay state after the whole packet has been accepted.
func (*Writer) ReadFrom ¶
ReadFrom reads chunks from src and writes each chunk as a framed message.
Semantics:
- Chunk-to-message: each chunk read from src (a successful src.Read call) is encoded as a single framed message and written via w.Write. This is efficient but does not preserve upstream application message boundaries. For protocols that already preserve boundaries (SeqPacket/Datagram), this is effectively pass-through.
Non-blocking semantics: if src.Read or the underlying writer returns iox.ErrWouldBlock or iox.ErrMore, ReadFrom returns immediately with the same control signal. Retry ReadFrom on the same Writer instance to resume the interrupted message. Pure write-side resume calls can return n == 0 because the pending bytes were already read from src and admitted to the Writer.
The returned count is bytes read from src and admitted to this Writer. If src.Read returns n > 0 with an error, the source error is reported after that admitted chunk is emitted. If write-side ErrWouldBlock or ErrMore is observed first, the source signal remains pending for a later ReadFrom call. No heap allocations occur in the steady-state path.
func (*Writer) Write ¶
Write writes p as one message.
In BinaryStream mode, Write emits a length prefix before p. In SeqPacket and Datagram modes, Write passes p through as one transport packet and reports io.ErrShortWrite if the destination accepts only a prefix.
The returned count is bytes accepted from p; BinaryStream header bytes are not included. In BinaryStream mode, if Write returns ErrWouldBlock or ErrMore, retry Write on the same Writer instance with the same message length before starting another message. In packet modes, n == len(p) with ErrWouldBlock or ErrMore means the packet was accepted; do not replay p. If packet-mode Write returns ErrWouldBlock or ErrMore with n == 0, retry with the same packet.