framer

package module
v0.1.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 11, 2026 License: MIT Imports: 7 Imported by: 0

README

framer — message boundaries over stream I/O

Go Reference Go Report Card Coverage Status

Languages: English | 简体中文 | 日本語 | Español | Français

Portable message framing for Go. Preserve one-message-per-Read/Write over stream transports.

Scope: message boundary preservation for stream transports.

Overview

Many transports are byte streams (TCP, Unix stream, pipes). A single Read may return a partial application message, or several messages concatenated. framer restores message boundaries: in stream mode, one Read returns exactly one message payload, and one Write emits exactly one framed message.

  • Message boundary preservation for byte streams (TCP, Unix stream, pipes).
  • Pass-through on boundary-preserving transports (UDP, Unix datagram, WebSocket, SCTP).
  • Portable wire format; configurable byte order.

Protocol adaptation

  • BinaryStream (stream transports: TCP, TLS-over-TCP, Unix stream, pipes): adds a length prefix; reads/writes whole messages.
  • SeqPacket (e.g., SCTP, WebSocket): pass-through; the transport already preserves boundaries.
  • Datagram (e.g., UDP, Unix datagram): pass-through; boundary already preserved.
  • For Reader.Read, packet modes are pass-through: WithReadLimit is checked after one receive, so an oversized packet may return n > limit with ErrTooLong; n is the bytes copied from that packet.
  • Packet-output paths retry whole packets only after zero-progress ErrWouldBlock / ErrMore; a fully accepted packet returned with ErrWouldBlock or ErrMore is not replayed, and partial packet writes are reported as io.ErrShortWrite.

Select at construction time via WithProtocol(...) (read/write variants exist) or via transport helpers (see Options).

Wire format

Compact variable-length length prefix, followed by payload bytes. Byte order for the extended length is configurable: WithByteOrder, or per-direction WithReadByteOrder / WithWriteByteOrder.

Frame data format

The framing scheme used by framer is intentionally compact:

  • Header byte H0 + optional extended length bytes.
  • Let L be the payload length in bytes.
    • If 0 ≤ L ≤ 253 (0x00..0xFD): H0 = L. No extra length bytes.
    • If 254 ≤ L ≤ 65535 (0x0000..0xFFFF): H0 = 0xFE and the next 2 bytes encode L as an unsigned 16‑bit integer in the configured byte order.
    • If 65536 ≤ L ≤ 2^56-1: H0 = 0xFF and the next 7 bytes carry L as a 56‑bit integer, laid out in the configured byte order.
      • Big‑endian: bytes [1..7] are the big‑endian lower 56 bits of L.
      • Little‑endian: bytes [1..7] are the little‑endian lower 56 bits of L.

Limits and errors:

  • The maximum supported payload length is 2^56-1; larger values result in framer.ErrTooLong.
  • When a read‑side limit is configured (WithReadLimit), lengths exceeding the limit fail with framer.ErrTooLong.

Installation

Install with go get:

go get code.hybscloud.com/framer
c1, c2 := net.Pipe()
defer c1.Close()
defer c2.Close()

w := framer.NewWriter(c1, framer.WithWriteTCP())
r := framer.NewReader(c2, framer.WithReadTCP())

go func() { _, _ = w.Write([]byte("hello")) }()

buf := make([]byte, 64)
n, err := r.Read(buf)
if err != nil {
	panic(err)
}
fmt.Printf("got: %q\n", buf[:n])

Non-blocking usage

framer defaults to non-blocking mode. In an event-driven loop:

for {
	n, err := r.Read(buf)
	if n > 0 {
		process(buf[:n])
	}
	if err != nil {
		if err == framer.ErrWouldBlock {
			// No data now; wait for readability (epoll, io_uring, etc.)
			continue
		}
		if err == io.EOF {
			break
		}
		log.Fatal(err)
	}
}

Options

  • WithProtocol(proto Protocol): choose BinaryStream, SeqPacket, or Datagram (read/write variants available).
  • Byte order: WithByteOrder, or WithReadByteOrder / WithWriteByteOrder.
  • WithReadLimit(n int): cap maximum message payload size when reading; Reader.Read enforces it post-read in packet modes and may return n > limit with ErrTooLong.
  • WithRetryDelay(d time.Duration): configure zero-progress ErrWouldBlock policy; a negative value returns ErrWouldBlock immediately, zero yields and retries, and a positive value sleeps for d before retrying. If an operation already transferred bytes, it returns the positive count with ErrWouldBlock so the caller can process progress before retrying; related options: WithNonblock() / WithBlock().

Transport helpers (presets):

  • WithReadTCP / WithWriteTCP (BinaryStream, network‑order BigEndian)
  • WithReadUDP / WithWriteUDP (Datagram, BigEndian)
  • WithReadWebSocket / WithWriteWebSocket (SeqPacket, BigEndian)
  • WithReadSCTP / WithWriteSCTP (SeqPacket, BigEndian)
  • WithReadUnix / WithWriteUnix (BinaryStream, BigEndian)
  • WithReadUnixPacket / WithWriteUnixPacket (Datagram, BigEndian)
  • WithReadLocal / WithWriteLocal (BinaryStream, native byte order)

Everything else: see GoDoc: https://pkg.go.dev/code.hybscloud.com/framer

Semantics Contract

Packet mode note (SeqPacket / Datagram)
  • Packet mode preserves transport boundaries and does not split packets.
  • Reader.Read enforces WithReadLimit after one packet read; transfer helpers use one sentinel byte to reject oversized packets before forwarding bytes.
  • Packet-preserving destinations retry the whole packet only after zero-progress ErrWouldBlock / ErrMore; a fully accepted packet returned with ErrWouldBlock or ErrMore is not replayed, and partial packet writes are boundary failures reported as io.ErrShortWrite.
  • Reader.WriteTo to an arbitrary io.Writer is a byte-copy transfer with suffix resume. When the destination is a framer.Writer, it uses the destination algebra: packet writers retry the whole packet after zero progress, and stream writers retry the same in-flight frame.
  • If a packet source returns (n > 0, err), Reader.WriteTo emits the admitted packet before reporting err; write-side suspension keeps that source signal pending across retry.
  • Progress counts are operation-indexed: Reader.Read reports bytes copied into p, Reader.WriteTo reports bytes written to dst, Writer.ReadFrom reports bytes read from src and admitted to the writer state, and Forwarder.ForwardOnce reports progress in its current phase.
Retry discipline
  • ErrWouldBlock is readiness suspension, not failure; aggregate helpers may return a positive count when earlier loop steps made progress before suspension.
  • ErrMore means the same operation has more progress to deliver; it is not io.EOF and not readiness suspension. Process any returned progress, then call the same operation again.
  • Retry Reader.Read after partial stream progress on the same Reader with the same buffer.
  • Retry Writer.Write after BinaryStream suspension on the same Writer with the same message length; BinaryStream header bytes are not included in n. In packet modes, n == len(p) with ErrWouldBlock or ErrMore means the packet was accepted, so do not replay p.
  • Retry Reader.WriteTo on the same Reader and same destination, Writer.ReadFrom on the same Writer, and Forwarder.ForwardOnce on the same Forwarder.
Performance contract
  • Hot paths keep runtime checks minimal for steady-state throughput.
  • Callers are responsible for valid option/buffer usage and operation-specific retry after ErrWouldBlock or ErrMore.
Error taxonomy
Error Meaning Caller action
nil Operation completed successfully Proceed; n reflects full progress
io.EOF End of stream (no more messages) Stop reading; normal termination
io.ErrUnexpectedEOF Stream ended mid-message (header or payload incomplete) Treat as fatal; data corruption or disconnect
io.ErrShortBuffer Destination buffer too small for message payload Retry with larger buffer
io.ErrShortWrite Destination accepted fewer bytes than provided Retry or treat as fatal per context
io.ErrNoProgress Underlying Reader made no progress (n==0, err==nil) on a non-empty buffer Treat as fatal; indicates a broken io.Reader implementation
framer.ErrWouldBlock No progress possible now without waiting Retry later (after poll/event); n may be >0
framer.ErrMore Same operation has more progress to deliver, distinct from EOF and readiness suspension Process returned progress, then call the same operation again
framer.ErrTooLong Message exceeds a configured limit, transfer cap, or wire-format bound Reject message; possibly fatal
framer.ErrInvalidArgument Nil reader/writer or invalid config Fix configuration
Outcome tables

Reader.Read(p []byte) (n int, err error) (BinaryStream mode)

Condition n err
Complete message delivered payload length nil
len(p) < payload length 0 io.ErrShortBuffer
Payload exceeds ReadLimit 0 ErrTooLong
Underlying returns would-block bytes read so far ErrWouldBlock
Underlying returns more bytes read so far ErrMore
EOF at message boundary 0 io.EOF
EOF mid-header or mid-payload bytes read io.ErrUnexpectedEOF

Writer.Write(p []byte) (n int, err error) (BinaryStream mode)

Condition n err
Complete framed message emitted len(p) nil
Payload exceeds max (2^56-1) 0 ErrTooLong
Underlying returns would-block payload bytes written so far ErrWouldBlock
Underlying returns more payload bytes written so far ErrMore

Reader.WriteTo(dst io.Writer) (n int64, err error)

Condition n err
All messages transferred until EOF total payload bytes nil
Underlying reader returns would-block payload bytes written ErrWouldBlock
Underlying reader returns more payload bytes written ErrMore
dst returns would-block payload bytes written ErrWouldBlock
Packet source exceeds ReadLimit before forwarding bytes already written before that packet ErrTooLong
Message exceeds internal buffer (64KiB default) bytes so far ErrTooLong
Stream ended mid-message bytes so far io.ErrUnexpectedEOF

Writer.ReadFrom(src io.Reader) (n int64, err error)

Condition n err
All chunks encoded until src EOF total bytes read from src nil
src returns would-block bytes read from src before the signal ErrWouldBlock
src returns more bytes read from src before the signal ErrMore
Underlying writer returns would-block bytes read from src and admitted before suspension; 0 on pure write-side resume ErrWouldBlock
Underlying writer returns more bytes read from src and admitted before suspension; 0 on pure write-side resume ErrMore

Forwarder.ForwardOnce() (n int, err error)

Condition n err
One message fully forwarded payload bytes (write phase) nil
Packet source returns (n > 0, io.EOF) payload bytes (write phase) nil (next call returns io.EOF)
No more messages 0 io.EOF
Source returns would-block read bytes if no packet was emitted; packet-source n > 0 is emitted first and returns payload bytes (write phase) ErrWouldBlock
Source returns more read bytes if no packet was emitted; packet-source n > 0 is emitted first and returns payload bytes (write phase) ErrMore
Write phase would-block bytes written this call ErrWouldBlock
Write phase more bytes written this call ErrMore
Stream message or required packet read capacity exceeds internal buffer 0 io.ErrShortBuffer
Packet exceeds ReadLimit/default packet transfer cap before forwarding bytes read from the packet, not forwarded ErrTooLong
Stream ended mid-message bytes so far io.ErrUnexpectedEOF
Operation classification
Operation Boundary behavior Use case
Reader.Read Message-preserving: one call = one message Application-level message processing
Writer.Write Message-preserving: one call = one framed message Application-level message sending
Reader.WriteTo Byte transfer to arbitrary writers; known framer destinations preserve packet/frame retry law Efficient bulk transfer with suffix resume
Writer.ReadFrom Chunking: each src chunk becomes one message; packet output is whole-packet retry only after zero progress Efficient bulk encoding; does NOT preserve upstream boundaries
Forwarder.ForwardOnce Message-preserving relay: decode one, re-encode one Message-aware proxying with boundary preservation
Blocking policy

By default, framer is non-blocking (WithNonblock()): ErrWouldBlock is returned immediately.

  • WithBlock() or WithRetryDelay(0): yield (runtime.Gosched) and retry zero-progress would-block
  • WithRetryDelay(d > 0): sleep d and retry zero-progress would-block
  • Negative RetryDelay (default): return zero-progress ErrWouldBlock immediately
  • If a read or write already transferred bytes, framer returns the positive count with ErrWouldBlock; process the progress and retry the same operation as documented above.

No method hides blocking unless explicitly configured.

framer uses code.hybscloud.com/iox control flow signals. ErrWouldBlock and ErrMore are aliases from iox, enabling direct integration with other iox-aware components (iofd, takt).

Fast paths

framer implements stdlib copy fast paths to interoperate with io.Copy-style engines and iox.CopyPolicy:

  • (*Reader).WriteTo(io.Writer): efficiently transfers framed message payloads to dst.

    • Stream (BinaryStream): processes one framed message at a time and writes only the payload bytes to dst. If ReadLimit == 0, an internal default cap (64KiB) is used; messages larger than this cap return framer.ErrTooLong.
    • Packet (SeqPacket/Datagram): pass-through byte transfer; sentinel-cap reads reject oversized packets before forwarding, and n counts bytes written to dst.
    • Semantic write-side errors framer.ErrWouldBlock and framer.ErrMore are propagated unchanged with the progress count reflecting bytes written; packet-source errors returned with bytes are reported after the admitted packet is emitted.
  • (*Writer).ReadFrom(io.Reader): chunk-to-message; each successful Read chunk from src is encoded as a single framed message.

    • This is efficient but does not preserve application message boundaries from src.
    • On boundary-preserving protocols it effectively behaves like pass-through.
    • Semantic errors framer.ErrWouldBlock and framer.ErrMore are propagated unchanged; n counts bytes read from src and admitted to the writer state.

Recommendation: prefer iox.CopyPolicy with a retry-aware policy (e.g., PolicyRetry) in non-blocking loops so ErrWouldBlock / ErrMore are handled explicitly.

Zero-allocation steady state: After initial buffer allocation, Forwarder and WriteTo paths reuse internal buffers. No heap allocations occur per message in steady state.

Note on partial write recovery: When using iox.Copy with non-blocking destinations, partial writes may occur. If the source does not implement io.Seeker, iox.Copy returns iox.ErrNoSeeker to prevent silent data loss. For non-seekable sources (e.g., network sockets), use iox.CopyPolicy with PolicyRetry for write-side semantic errors to ensure all read bytes are written before returning.

Forwarding

  • Wire proxying (byte engines): use iox.CopyPolicy and standard io fast paths (WriterTo/ReaderFrom) when byte-level forwarding is acceptable and higher-level boundaries do not need preservation.
  • Message relay (preserve boundaries): use framer.NewForwarder(dst, src, ...) and call ForwardOnce() in your poll loop. It decodes exactly one framed message from src and re-encodes it as exactly one framed message to dst.
    • Non-blocking semantics: retry the same Forwarder instance after framer.ErrWouldBlock or framer.ErrMore; packet-source (n > 0, err) is emitted before the source signal is reported, and write-side suspension keeps that source signal pending for the later retry on the same Forwarder.
    • Limits: io.ErrShortBuffer when the internal buffer is too small for a stream message or required packet read capacity; framer.ErrTooLong when a packet exceeds WithReadLimit or the default packet transfer cap before forwarding.
    • Zero‑alloc steady state after construction; the internal scratch buffer is reused per message.

Message relay example:

fwd := framer.NewForwarder(dst, src, framer.WithReadTCP(), framer.WithWriteTCP())

for {
	_, err := fwd.ForwardOnce()
	if err != nil {
		if err == framer.ErrWouldBlock {
			continue // wait for src readable or dst writable
		}
		if err == io.EOF {
			break
		}
		log.Fatal(err)
	}
}

License

MIT — see LICENSE.

©2025 Hayabusa Cloud Co., Ltd.

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

Constants

This section is empty.

Variables

View Source
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")
)
View Source
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

func NewPipe(opts ...Option) (reader io.Reader, writer io.Writer)

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

func NewReadWriter(r io.Reader, w io.Writer, opts ...Option) io.ReadWriter

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

func NewReader(r io.Reader, opts ...Option) io.Reader

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.

func NewWriter

func NewWriter(w io.Writer, opts ...Option) io.Writer

NewWriter returns an io.Writer that writes framed messages to w.

The returned dynamic type is *Writer. Callers that need Writer.ReadFrom can type assert the result to *Writer.

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

func NewForwarder(dst io.Writer, src io.Reader, opts ...Option) *Forwarder

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

func (f *Forwarder) ForwardOnce() (n int, err error)

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

func WithByteOrder(order binary.ByteOrder) Option

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

func WithProtocol(proto Protocol) Option

WithProtocol sets both read and write protocol modes.

func WithReadByteOrder

func WithReadByteOrder(order binary.ByteOrder) Option

WithReadByteOrder sets the read-side byte order for BinaryStream length prefixes.

func WithReadLimit

func WithReadLimit(limit int) Option

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

func WithReadProtocol(proto Protocol) Option

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

func WithRetryDelay(d time.Duration) Option

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

func WithWriteByteOrder(order binary.ByteOrder) Option

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

func WithWriteProtocol(proto Protocol) Option

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.
const (
	// BinaryStream selects length-prefixed framing for byte-stream transports.
	BinaryStream Protocol = 1

	// SeqPacket selects pass-through framing for sequenced-packet transports.
	SeqPacket Protocol = 2

	// Datagram selects pass-through framing for datagram transports.
	Datagram Protocol = 3
)

type ReadWriter

type ReadWriter struct {
	*Reader
	*Writer
}

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

func (r *Reader) Read(p []byte) (int, error)

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

func (r *Reader) WriteTo(dst io.Writer) (int64, error)

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

func (w *Writer) ReadFrom(src io.Reader) (int64, error)

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

func (w *Writer) Write(p []byte) (int, error)

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.

Directories

Path Synopsis
internal
bo
Package bo provides native byte order selection.
Package bo provides native byte order selection.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL