net

package
v0.26.13 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package net — the listen address value.

Package net — the completed-call observation record.

Package net — the outbound client configuration.

Package net — range 0.2.11.* (ADR 0029 core/net block).

Package net — the accepted stream connection port.

Package net — the shutdown signal a long-lived handler observes.

Package net — the configuration-friendly duration value.

Package net — declares the sentinel *errs.Error outcomes of the network domain. Each var's name equals its errs.Define Reason in SCREAMING_SNAKE form. Service implementations and the pkg/v1 facades wrap these sentinels; they declare no codes of their own (the ADR 0016 proc precedent).

Package net — compile-time interface assertions, kept out of the production source per KTN-IFACE-ASSERT-PLACEMENT.

Package net — the opaque, redacting TLS identity value.

Package net — the on-disk TLS material description.

Package net — the TLS identity construction parameters.

Package net — the per-group resource ceilings.

Package net — the loop engine's contract: the connection reference, the posted operation, the option kinds a loop group judges, and the ports a loop and its handler implement (ADR 0186 §D4).

Package net — a loop group's reported state: the backend it runs on, how it got there, and its rings' counters.

Package net — the loop engine's configuration value and its backends.

Package net — TLS material parsing helpers shared by NewIdentity.

Package net — the generic handler decorator.

Package net — the received datagram port.

Package net — the server lifecycle phase.

Package net — the outbound authorisation port.

Package net — compile-time interface assertions, kept out of the production source per KTN-IFACE-ASSERT-PLACEMENT.

Package net — the completed outbound response value.

Package net — the Server-Sent Events frame: the value a stream sends and the rules a frame must satisfy to be carried at all. Writing it — the encoder that splits Data into data lines, and the comment frame — is internal/service/net/sse's (ADR 0160 §4).

Package net — the server's reported state.

Package net — the per-phase deadlines.

Package net — the WebSocket protocol's vocabulary (RFC 6455): the opening handshake's constants and the control-frame ceiling. Reading and writing the protocol — the handshake's key check and digest, the frame codec, the close payload, the UTF-8 check — is internal/service/net/websocket's (ADR 0160 §4).

Package net — the WebSocket close code (RFC 6455 §7.4) and the predicates that govern it in both directions. The close payload's wire form is internal/service/net/websocket's (ADR 0160 §4).

Package net — the WebSocket application message (RFC 6455 §5.6). Its UTF-8 rule (§8.1) is checked by the engine on the bytes, as they come off and go onto the wire: internal/service/net/websocket.ValidateText (ADR 0160 §4).

Package net — the WebSocket frame opcode (RFC 6455 §5.2).

Package net — shared sentinel-wrapping helper.

Index

Constants

View Source
const (
	// WSKeyHeader carries the client's 16 random bytes, base64-encoded.
	WSKeyHeader string = "Sec-WebSocket-Key"
	// WSAcceptHeader carries the server's proof it understood the handshake.
	WSAcceptHeader string = "Sec-WebSocket-Accept"
	// WSVersionHeader carries the client's version, and the server's
	// counter-offer when it refuses one.
	WSVersionHeader string = "Sec-WebSocket-Version"
	// WSProtocolHeader carries the subprotocol list, then the single choice.
	WSProtocolHeader string = "Sec-WebSocket-Protocol"
	// WSExtensionsHeader carries the extension offer. This domain negotiates
	// none, so it is never echoed — see the package CLAUDE.md.
	WSExtensionsHeader string = "Sec-WebSocket-Extensions"
	// WSUpgradeToken is the token both Upgrade headers must carry.
	WSUpgradeToken string = "websocket"
)

The handshake headers, spelled once so no caller mistypes one into a silent miss.

View Source
const CodeAlreadyStarted errs.Code = 0x00_02_0B_05 // 0.2.11.5

CodeAlreadyStarted identifies a Start call on a server that is already serving.

View Source
const CodeCallFailed errs.Code = 0x00_02_0B_13 // 0.2.11.19

CodeCallFailed identifies an outbound call that did not produce a response.

View Source
const CodeConnLimitReached errs.Code = 0x00_02_0B_09 // 0.2.11.9

CodeConnLimitReached identifies a connection rejected because the group's concurrency ceiling was saturated.

View Source
const CodeDrainTimeout errs.Code = 0x00_02_0B_0A // 0.2.11.10

CodeDrainTimeout identifies a shutdown whose drain budget expired with connections still in flight; the remainder is closed hard.

View Source
const CodeGroupDuplicate errs.Code = 0x00_02_0B_0C // 0.2.11.12

CodeGroupDuplicate identifies a second declaration of an already-declared listener group name.

View Source
const CodeGroupUnknown errs.Code = 0x00_02_0B_0B // 0.2.11.11

CodeGroupUnknown identifies a lookup for a listener group that was never declared.

View Source
const CodeHandlerAlreadySet errs.Code = 0x00_02_0B_2C // 0.2.11.44

CodeHandlerAlreadySet identifies a second handler declaration on one stream group — Handle, HandleFunc, HandleHTTP or HandleLoop, in any order — which does not replace the first (ADR 0186 §D1).

View Source
const CodeHandlerMissing errs.Code = 0x00_02_0B_07 // 0.2.11.7

CodeHandlerMissing identifies a listener group that was bound without a handler.

View Source
const CodeHandlerPanic errs.Code = 0x00_02_0B_08 // 0.2.11.8

CodeHandlerPanic identifies a panic recovered inside a handler; the connection is closed but the process survives.

View Source
const CodeInvalidAddress errs.Code = 0x00_02_0B_02 // 0.2.11.2

CodeInvalidAddress identifies a listen or dial address that is syntactically unusable (empty host and port, malformed socket path, negative port).

View Source
const CodeInvalidDuration errs.Code = 0x00_02_0B_15 // 0.2.11.21

CodeInvalidDuration identifies a configuration duration that is neither a Go duration literal nor an integer nanosecond count.

View Source
const CodeListenFailed errs.Code = 0x00_02_0B_01 // 0.2.11.1

CodeListenFailed identifies a listener that could not be bound to its address.

View Source
const CodeLoopBackendUnavailable errs.Code = 0x00_02_0B_25 // 0.2.11.37

CodeLoopBackendUnavailable identifies an explicit loop backend the running kernel's probe refused, on a platform that has the mechanic (ADR 0186 §D5).

View Source
const CodeLoopNoDescriptors errs.Code = 0x00_02_0B_26 // 0.2.11.38

CodeLoopNoDescriptors identifies a loop group refused at Start because the effective RLIMIT_NOFILE is zero or unreadable, which leaves no descriptor for a ring's table or a connection (ADR 0186 §D5).

View Source
const CodeLoopOptionUnsupported errs.Code = 0x00_02_0B_24 // 0.2.11.36

CodeLoopOptionUnsupported identifies an option a group cannot honour on its engine: Loop on a group not served by a loop handler, or, on a loop group, Use or an option neither the engine nor the handler honours (ADR 0186 §D1).

View Source
const CodeLoopOutputFull errs.Code = 0x00_02_0B_28 // 0.2.11.40

CodeLoopOutputFull identifies a write refused because the connection's output, or the loop's global output budget, is at its ceiling.

View Source
const CodeLoopPostQueueFull errs.Code = 0x00_02_0B_2A // 0.2.11.42

CodeLoopPostQueueFull identifies a Post refused because the loop's preallocated queue of posted operations is full.

View Source
const CodeLoopSpillFull errs.Code = 0x00_02_0B_29 // 0.2.11.41

CodeLoopSpillFull identifies an unconsumed tail that does not fit the connection's spill, or the loop's global spill budget.

View Source
const CodeLoopStaleRef errs.Code = 0x00_02_0B_27 // 0.2.11.39

CodeLoopStaleRef identifies an operation on a connection reference whose generation has ended: the connection closed, and its slot may already serve another one.

View Source
const CodeLoopTLSUnsupported errs.Code = 0x00_02_0B_23 // 0.2.11.35

CodeLoopTLSUnsupported identifies a loop group given a TLS identity: the loop engine speaks plaintext only, and the SDK owns no TLS stack it could drive without a blocking net.Conn (ADR 0186 §D2).

View Source
const CodeLoopWrongThread errs.Code = 0x00_02_0B_2B // 0.2.11.43

CodeLoopWrongThread identifies a loop operation called from a thread other than the loop's own, detected under the loopdebug build tag.

View Source
const CodeNotStarted errs.Code = 0x00_02_0B_06 // 0.2.11.6

CodeNotStarted identifies an operation that requires a running server on one that has not been started.

View Source
const CodePacketTooLarge errs.Code = 0x00_02_0B_0E // 0.2.11.14

CodePacketTooLarge identifies a datagram longer than the group's accepted size.

View Source
const CodeRequestDenied errs.Code = 0x00_02_0B_11 // 0.2.11.17

CodeRequestDenied identifies an outbound request refused by the transport policy before it left the process.

View Source
const CodeResponseTooLarge errs.Code = 0x00_02_0B_12 // 0.2.11.18

CodeResponseTooLarge identifies a response body that exceeded the configured read ceiling.

View Source
const CodeSSEFieldInvalid errs.Code = 0x00_02_0B_17 // 0.2.11.23

CodeSSEFieldInvalid identifies a Server-Sent Events frame the wire format cannot carry: a line terminator inside an id, an event name or a comment (the format has no escape — a newline SPLITS a value, so an id carrying one would silently become a different id plus a stray field), a negative retry, a retry under one millisecond (the wire field is an integer millisecond count, and truncating to zero would say "reconnect immediately"), or a frame with no field set at all.

View Source
const CodeSSEFlushUnsupported errs.Code = 0x00_02_0B_18 // 0.2.11.24

CodeSSEFlushUnsupported identifies a ResponseWriter that cannot be flushed. Server-Sent Events is a streaming format: without a flush every event sits in the transport buffer until the handler returns, which for an endless stream means the client receives nothing, ever.

View Source
const CodeSSEStreamClosed errs.Code = 0x00_02_0B_19 // 0.2.11.25

CodeSSEStreamClosed identifies a send on a stream that has already ended — the client disconnected, the server began draining, or the handler closed it.

View Source
const CodeSSEStreamMisconfigured errs.Code = 0x00_02_0B_1A // 0.2.11.26

CodeSSEStreamMisconfigured identifies a stream option the domain refuses to interpret rather than guess at: a negative keep-alive interval or a negative per-write budget (ADR 0031).

View Source
const CodeServerClosed errs.Code = 0x00_02_0B_04 // 0.2.11.4

CodeServerClosed identifies work refused because the server already stopped accepting; it is the expected terminal outcome of a graceful shutdown.

View Source
const CodeSocketAdoptFailed errs.Code = 0x00_02_0B_0D // 0.2.11.13

CodeSocketAdoptFailed identifies an inherited socket that could not be adopted from the supervisor.

View Source
const CodeStaticMisconfigured errs.Code = 0x00_02_0B_22 // 0.2.11.34

CodeStaticMisconfigured identifies a static file handler refused at construction: no file tree to serve, a security header value the handler will not send, or a Referrer-Policy token no browser recognises (ADR 0130).

View Source
const CodeTLSHandshakeFailed errs.Code = 0x00_02_0B_10 // 0.2.11.16

CodeTLSHandshakeFailed identifies a TLS or mTLS handshake that did not complete.

View Source
const CodeTLSMaterialInvalid errs.Code = 0x00_02_0B_0F // 0.2.11.15

CodeTLSMaterialInvalid identifies TLS material that is absent, malformed, or yields no usable certificate; it is never downgraded to an empty trust store.

View Source
const CodeTooManyRedirects errs.Code = 0x00_02_0B_14 // 0.2.11.20

CodeTooManyRedirects identifies an outbound call that exceeded its redirect budget.

View Source
const CodeUnsafePath errs.Code = 0x00_02_0B_16 // 0.2.11.22

CodeUnsafePath identifies an outbound path carrying a dot segment, literal or percent-encoded. Such a path is refused before any allowlist pattern is tried, because an anchored pattern like ^/v1/supi/[^/]+$ happily matches "/v1/supi/.." which the upstream then normalises to a different resource.

View Source
const CodeUnsupportedNetwork errs.Code = 0x00_02_0B_03 // 0.2.11.3

CodeUnsupportedNetwork identifies a network name the domain does not serve (anything outside tcp, tcp4, tcp6, udp, udp4, udp6, unix, unixgram, unixpacket).

View Source
const CodeWSConnClosed errs.Code = 0x00_02_0B_20 // 0.2.11.32

CodeWSConnClosed identifies an operation on a connection that has ended — the peer sent Close, the server began draining, the handler closed it, or the socket died. It is the terminal outcome a receive loop runs until.

View Source
const CodeWSConnMisconfigured errs.Code = 0x00_02_0B_21 // 0.2.11.33

CodeWSConnMisconfigured identifies a connection option the domain refuses to interpret rather than guess at: a negative ping interval or write budget, or a non-positive size ceiling (ADR 0031).

View Source
const CodeWSHandshakeFailed errs.Code = 0x00_02_0B_1B // 0.2.11.27

CodeWSHandshakeFailed identifies an HTTP request that is not a valid RFC 6455 opening handshake: wrong method, absent or non-"websocket" Upgrade token, a Connection header without "Upgrade", a Sec-WebSocket-Key that does not decode to sixteen bytes, a Sec-WebSocket-Version other than 13, or an Origin the server's policy refuses. The response is written before this is returned, so the handler has nothing left to answer.

View Source
const CodeWSInvalidPayload errs.Code = 0x00_02_0B_1F // 0.2.11.31

CodeWSInvalidPayload identifies a payload the protocol cannot carry: a text message or close reason that is not valid UTF-8 (§8.1), a close code that must never appear on the wire (1004, 1005, 1006, 1015 and the unallocated ranges — §7.4.2), or a close reason past the control-frame ceiling.

View Source
const CodeWSMessageTooLarge errs.Code = 0x00_02_0B_1E // 0.2.11.30

CodeWSMessageTooLarge identifies a frame or an accumulated message beyond the connection's configured ceiling. The frame's announced length is checked BEFORE any buffer is sized from it, because a 64-bit length field a peer chooses is an out-of-memory condition one allocation away.

View Source
const CodeWSProtocolViolation errs.Code = 0x00_02_0B_1D // 0.2.11.29

CodeWSProtocolViolation identifies a frame RFC 6455 forbids: an unmasked client frame (§5.1), a set reserved bit or reserved opcode (§5.2), a fragmented or oversized control frame (§5.5), a non-minimal length encoding (§5.2), a continuation with no message in progress or a new data frame interrupting one (§5.4), or a malformed close payload (§5.5.1). Each fails the connection.

View Source
const CodeWSUpgradeUnsupported errs.Code = 0x00_02_0B_1C // 0.2.11.28

CodeWSUpgradeUnsupported identifies a ResponseWriter whose connection cannot be taken over — an HTTP/2 request, or a middleware wrapper that hides http.Hijacker. WebSocket is not a response format: it replaces HTTP on the socket, so without the hijack there is nothing to upgrade.

View Source
const SSEContentType string = "text/event-stream"

SSEContentType is the media type an event stream must be served under. A client that receives anything else does not start an EventSource at all.

View Source
const SSELastEventIDHeader string = "Last-Event-ID"

SSELastEventIDHeader is the request header a reconnecting client sends, carrying the id of the last event it processed.

View Source
const SSEMinRetry time.Duration = time.Millisecond

SSEMinRetry is the smallest reconnection hint the wire can carry. The retry field is an integer MILLISECOND count, so anything shorter rounds to zero — and zero does not mean "a very short delay", it means "reconnect at once".

View Source
const WSGUID string = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

WSGUID is the fixed string RFC 6455 §1.3 concatenates to Sec-WebSocket-Key before hashing. It is a constant of the protocol, not a secret and not a parameter: its only job is to make the accept value impossible to produce by echoing the request, so a cache or a naive proxy cannot fake an upgrade.

View Source
const WSKeyLen int = 16

WSKeyLen is how many bytes Sec-WebSocket-Key decodes to (RFC 6455 §4.1). A key of any other length is not a WebSocket handshake, whatever it decodes.

View Source
const WSMaxControlPayload int = 125

WSMaxControlPayload is the ceiling RFC 6455 §5.5 puts on a control frame's payload. It exists so an endpoint can answer a Ping without buffering: a control frame is answered inline, between the fragments of a message, so it must fit in a fixed buffer that is always available.

View Source
const WSVersion string = "13"

WSVersion is the only protocol version this domain speaks (RFC 6455 §4.1). A request naming any other version is refused WITH this value advertised back, which is what lets a client renegotiate instead of guessing.

Variables

View Source
var (
	// ListenFailed is returned when a listener could not be bound.
	ListenFailed = errs.Static(CodeListenFailed, "LISTEN_FAILED",
		"The server could not bind its listening address",
		"core/net: binding a listener failed; the wrap cause is the OS error",
		0,
		69)

	// InvalidAddress is returned for a syntactically unusable address.
	InvalidAddress = errs.Static(CodeInvalidAddress, "INVALID_ADDRESS",
		"The network address is not valid",
		"core/net: the supplied host:port or socket path could not be parsed",
		0,
		64)

	// UnsupportedNetwork is returned for a network name outside the served set.
	UnsupportedNetwork = errs.Static(CodeUnsupportedNetwork, "UNSUPPORTED_NETWORK",
		"The network type is not supported",
		"core/net: network outside {tcp,tcp4,tcp6,udp,udp4,udp6,unix,unixgram,unixpacket}",
		0,
		64)

	// ServerClosed is returned once the server has stopped accepting work.
	ServerClosed = errs.Static(CodeServerClosed, "SERVER_CLOSED",
		"The server is closed",
		"core/net: the server stopped accepting; this is the terminal state of a graceful shutdown",
		0,
		69)

	// AlreadyStarted is returned by Start on a server that is already serving.
	AlreadyStarted = errs.Static(CodeAlreadyStarted, "ALREADY_STARTED",
		"The server is already started",
		"core/net: Start called on a server whose phase is already Serving",
		0,
		70)

	// NotStarted is returned when an operation requires a running server.
	NotStarted = errs.Static(CodeNotStarted, "NOT_STARTED",
		"The server is not started",
		"core/net: the operation requires a running server",
		0,
		70)

	// HandlerMissing is returned when a group was bound without a handler.
	HandlerMissing = errs.Static(CodeHandlerMissing, "HANDLER_MISSING",
		"The listener group has no handler",
		"core/net: a group reached Start with a nil handler",
		0,
		64)

	// HandlerPanic wraps a panic recovered inside a handler.
	HandlerPanic = errs.Static(CodeHandlerPanic, "HANDLER_PANIC",
		"The connection handler failed unexpectedly",
		"core/net: a handler panicked; the connection was closed and the process kept running",
		0,
		70)

	// ConnLimitReached is returned when the group's connection ceiling is full.
	ConnLimitReached = errs.Static(CodeConnLimitReached, "CONN_LIMIT_REACHED",
		"The server is at its connection limit",
		"core/net: the group's concurrency ceiling rejected the connection",
		0,
		75)

	// DrainTimeout is returned when the shutdown drain budget expired.
	DrainTimeout = errs.Static(CodeDrainTimeout, "DRAIN_TIMEOUT",
		"The server did not drain before its deadline",
		"core/net: connections were still in flight when the drain budget expired",
		0,
		75)

	// GroupUnknown is returned for a lookup of an undeclared group.
	GroupUnknown = errs.Static(CodeGroupUnknown, "GROUP_UNKNOWN",
		"The listener group is not declared",
		"core/net: no group is registered under that name",
		0,
		64)

	// GroupDuplicate is returned for a second declaration of one group name.
	GroupDuplicate = errs.Static(CodeGroupDuplicate, "GROUP_DUPLICATE",
		"The listener group is already declared",
		"core/net: a group with that name already exists on this server",
		0,
		64)

	// SocketAdoptFailed is returned when an inherited socket could not be adopted.
	SocketAdoptFailed = errs.Static(CodeSocketAdoptFailed, "SOCKET_ADOPT_FAILED",
		"An inherited socket could not be adopted",
		"core/net: a supervisor-passed descriptor could not be turned into a listener",
		0,
		71)

	// PacketTooLarge names the reason a datagram beyond the group's accepted
	// size is dropped. It is deliberately not returned to anyone: a read loop
	// has no caller, so the drop is reported through
	// StateValue.OversizedPackets. The sentinel stays because it is what gives
	// that count a documented name in the registry.
	PacketTooLarge = errs.Static(CodePacketTooLarge, "PACKET_TOO_LARGE",
		"The datagram is larger than the accepted size",
		"core/net: the received datagram exceeded the group's maximum packet size",
		0,
		65)

	// TLSMaterialInvalid is returned for absent, malformed, or empty TLS material.
	// It is deliberately loud: an unusable bundle must never degrade into a trust
	// store that silently verifies nothing.
	TLSMaterialInvalid = errs.Static(CodeTLSMaterialInvalid, "TLS_MATERIAL_INVALID",
		"The TLS material is missing or invalid",
		"core/net: the certificate, key, or CA bundle yielded no usable material",
		0,
		78)

	// TLSHandshakeFailed is returned when a TLS or mTLS handshake did not complete.
	TLSHandshakeFailed = errs.Static(CodeTLSHandshakeFailed, "TLS_HANDSHAKE_FAILED",
		"The TLS handshake failed",
		"core/net: the TLS or mTLS handshake did not complete",
		0,
		69)

	// RequestDenied is returned when the transport policy refused an outbound call.
	// The public message never names the refused path, so a denial cannot leak the
	// shape of the private API surface through a log line.
	RequestDenied = errs.Static(CodeRequestDenied, "REQUEST_DENIED",
		"The request was refused by policy",
		"core/net: the transport policy refused the outbound request",
		0,
		77)

	// ResponseTooLarge is returned when a response body exceeded the read ceiling.
	ResponseTooLarge = errs.Static(CodeResponseTooLarge, "RESPONSE_TOO_LARGE",
		"The response is larger than the accepted size",
		"core/net: the response body exceeded the configured maximum size",
		0,
		65)

	// CallFailed is returned when an outbound call produced no response.
	CallFailed = errs.Static(CodeCallFailed, "CALL_FAILED",
		"The outbound call failed",
		"core/net: the outbound call did not produce a response",
		0,
		69)

	// TooManyRedirects is returned when a call exceeded its redirect budget.
	TooManyRedirects = errs.Static(CodeTooManyRedirects, "TOO_MANY_REDIRECTS",
		"The outbound call exceeded its redirect budget",
		"core/net: the redirect chain was longer than the configured maximum",
		0,
		69)

	// InvalidDuration is returned for a configuration duration that parses as
	// neither a Go duration literal nor an integer nanosecond count.
	InvalidDuration = errs.Static(CodeInvalidDuration, "INVALID_DURATION",
		"The duration value is not valid",
		"core/net: the value is neither a Go duration literal nor a nanosecond count",
		0,
		78)

	// UnsafePath is returned for a path carrying a dot segment in any form. It is
	// evaluated before the allowlist, because Go normalises dot segments neither
	// in url.URL nor in the transport, so an anchored pattern alone is not enough.
	UnsafePath = errs.Static(CodeUnsafePath, "UNSAFE_PATH",
		"The request path is not in normalised form",
		"core/net: the path carries a literal or percent-encoded dot segment",
		0,
		77)

	// SSEFieldInvalid is returned for a Server-Sent Events frame the wire format
	// cannot carry. The format has no escape mechanism: a newline inside a value
	// SPLITS it rather than being quoted, so an id or an event name carrying one
	// would arrive as a different value plus a stray field. It is refused, never
	// truncated — a silently shortened id is a resume token that points at the
	// wrong place.
	SSEFieldInvalid = errs.Static(CodeSSEFieldInvalid, "SSE_FIELD_INVALID",
		"The event cannot be represented in the event-stream format",
		"core/net: an SSE frame carried a line terminator in a single-line field, an unrepresentable retry, or no field at all",
		0,
		64)

	// SSEFlushUnsupported is returned when the ResponseWriter cannot be flushed.
	// Without a flush the events accumulate in the transport buffer and reach the
	// client only when the handler returns, so an endless stream delivers nothing
	// at all — the failure is total and silent, which is why it is refused at
	// construction rather than discovered in production.
	SSEFlushUnsupported = errs.Static(CodeSSEFlushUnsupported, "SSE_FLUSH_UNSUPPORTED",
		"The response cannot be streamed",
		"core/net: the http.ResponseWriter implements neither Flush nor http.Flusher",
		0,
		70)

	// SSEStreamClosed is returned by a send on a stream that has already ended:
	// the client disconnected, the server began draining, or the handler closed
	// it. It is the terminal outcome a streaming handler loops until.
	SSEStreamClosed = errs.Static(CodeSSEStreamClosed, "SSE_STREAM_CLOSED",
		"The event stream is closed",
		"core/net: the SSE stream ended — peer gone, server draining, or closed by the handler",
		0,
		69)

	// SSEStreamMisconfigured is returned for a stream option the domain refuses
	// to interpret rather than guess at (ADR 0031). A zero keep-alive interval is
	// CLAMPED to the domain default, because a working keep-alive needs no
	// explanation; a negative one is REFUSED, because no value the SDK invented
	// for it would be defensible and "never" already has an explicit spelling.
	SSEStreamMisconfigured = errs.Static(CodeSSEStreamMisconfigured, "SSE_STREAM_MISCONFIGURED",
		"The event stream options are not valid",
		"core/net: a negative keep-alive interval or write budget was supplied",
		0,
		78)

	// WSHandshakeFailed is returned when an HTTP request is not a valid RFC 6455
	// opening handshake. The upgrade is refused with an HTTP status rather than
	// a Close frame, because at that point there is no WebSocket connection to
	// close — the response is still an ordinary HTTP one.
	WSHandshakeFailed = errs.Static(CodeWSHandshakeFailed, "WS_HANDSHAKE_FAILED",
		"The WebSocket handshake is not valid",
		"core/net: the request does not satisfy RFC 6455 §4.2.1, or its origin was refused",
		0,
		65)

	// WSUpgradeUnsupported is returned when the response's connection cannot be
	// hijacked. It is deliberately distinct from a failed handshake: the request
	// was fine and the peer did nothing wrong — this stack cannot take over the
	// socket, which is a deployment fact (HTTP/2, or a middleware that wraps the
	// ResponseWriter without forwarding Unwrap), not a client error.
	WSUpgradeUnsupported = errs.Static(CodeWSUpgradeUnsupported, "WS_UPGRADE_UNSUPPORTED",
		"The connection cannot be upgraded",
		"core/net: the http.ResponseWriter exposes no http.Hijacker, so the socket cannot be taken over",
		0,
		70)

	// WSProtocolViolation is returned for a frame RFC 6455 forbids. Every case it
	// covers FAILS THE CONNECTION rather than being skipped: a peer that sent a
	// frame this endpoint ignored believes it said something, and the two would
	// be desynchronised from that point on.
	WSProtocolViolation = errs.Static(CodeWSProtocolViolation, "WS_PROTOCOL_VIOLATION",
		"The WebSocket frame is not valid",
		"core/net: the frame violates RFC 6455 framing — masking, reserved bits/opcodes, control-frame shape, minimal length, or fragmentation order",
		0,
		65)

	// WSMessageTooLarge is returned when a frame or a reassembled message exceeds
	// the connection's ceiling. The check runs against the ANNOUNCED length,
	// before any buffer is sized from it: allocating from a 64-bit integer the
	// peer chose is a denial of service in one line.
	WSMessageTooLarge = errs.Static(CodeWSMessageTooLarge, "WS_MESSAGE_TOO_LARGE",
		"The WebSocket message is larger than the accepted size",
		"core/net: the frame or the accumulated message exceeded the configured ceiling",
		0,
		65)

	// WSInvalidPayload is returned for a payload the protocol cannot carry: text
	// that is not UTF-8, a close code that must never be sent, or a close reason
	// past the control-frame ceiling. It is one sentinel for both directions on
	// purpose — the rule a peer broke is the rule this endpoint must not break.
	WSInvalidPayload = errs.Static(CodeWSInvalidPayload, "WS_INVALID_PAYLOAD",
		"The WebSocket payload is not valid",
		"core/net: invalid UTF-8, an unsendable close code, or an oversized close reason",
		0,
		65)

	// WSConnClosed is returned by any operation on a connection that has ended.
	// It is the terminal outcome a receive loop runs until, and it carries the
	// close code the peer sent when there was one.
	WSConnClosed = errs.Static(CodeWSConnClosed, "WS_CONN_CLOSED",
		"The WebSocket connection is closed",
		"core/net: the connection ended — peer close, server draining, closed by the handler, or a dead socket",
		0,
		69)

	// WSConnMisconfigured is returned for an option the domain refuses to
	// interpret rather than guess at (ADR 0031). A zero ping interval is CLAMPED
	// to the domain default, because a working heartbeat needs no explanation; a
	// negative one is REFUSED, and "never" has its own spelling.
	WSConnMisconfigured = errs.Static(CodeWSConnMisconfigured, "WS_CONN_MISCONFIGURED",
		"The WebSocket options are not valid",
		"core/net: a negative interval or budget, or a non-positive size ceiling, was supplied",
		0,
		78)

	// StaticMisconfigured is returned when a static file handler is built with
	// nothing it can serve safely: no file tree, a header value carrying a
	// control character, or a Referrer-Policy token no browser knows — which a
	// browser ignores, silently falling back to a weaker default. The "option"
	// field names which (ADR 0130).
	StaticMisconfigured = errs.Static(CodeStaticMisconfigured, "STATIC_MISCONFIGURED",
		"The static file handler configuration is not valid",
		"core/net: a nil file tree, a header value with a control character, or an unknown Referrer-Policy token",
		0,
		78)

	// LoopTLSUnsupported is returned by Start for a loop group given TLS. The loop
	// engine is plaintext only (ADR 0186 §D2); a group that needs TLS is served by
	// Handle or HandleHTTP on the default engine.
	LoopTLSUnsupported = errs.Static(CodeLoopTLSUnsupported, "LOOP_TLS_UNSUPPORTED",
		"The loop engine does not serve TLS",
		"core/net: a loop group was given a TLS identity; the loop engine is plaintext only",
		0,
		78)

	// LoopOptionUnsupported is returned by Start for an option a group cannot honour
	// on its engine: Loop on a group served by Handle, HandleFunc or HandleHTTP, or
	// on a datagram group; Use on a loop group; or an option neither the loop
	// engine nor its handler honours. A group with no handler fails with
	// HandlerMissing instead. The "option" field names it. It is judged at
	// Start, once the handler is known, whatever order the declarations came in.
	LoopOptionUnsupported = errs.Static(CodeLoopOptionUnsupported, "LOOP_OPTION_UNSUPPORTED",
		"The group option is not supported by its engine",
		"core/net: an option the group's engine or its loop handler cannot honour",
		0,
		78)

	// LoopBackendUnavailable is returned by Start for an explicit loop backend the
	// running kernel's probe refused. The "stage" and "reason" fields name the probe
	// step that failed. A GOOS that has no such mechanic at all returns the shared
	// UnsupportedPlatform instead (ADR 0018 §(a)).
	LoopBackendUnavailable = errs.Static(CodeLoopBackendUnavailable, "LOOP_BACKEND_UNAVAILABLE",
		"The requested loop backend is not available on this kernel",
		"core/net: the kernel probe refused the requested loop backend",
		0,
		69)

	// LoopNoDescriptors is returned by Start for a loop group when the effective
	// RLIMIT_NOFILE soft limit is zero or cannot be read: no ring table and no
	// connection could hold a descriptor. It is checked before the ring count is
	// derived, so it is never a division by zero.
	LoopNoDescriptors = errs.Static(CodeLoopNoDescriptors, "LOOP_NO_DESCRIPTORS",
		"The process has no file descriptor available for the loop",
		"core/net: RLIMIT_NOFILE is zero or unreadable; the loop has no descriptor to use",
		0,
		71)

	// LoopStaleRef is returned by a loop operation on a reference whose connection
	// has closed. The operation does nothing: the slot may already name another
	// connection, and the generation is what tells the two apart.
	LoopStaleRef = errs.Static(CodeLoopStaleRef, "LOOP_STALE_REF",
		"The connection is closed",
		"core/net: the connection reference's generation has ended",
		0,
		70)

	// LoopOutputFull is returned by Write when the bytes would pass the connection's
	// output ceiling or the loop's global output budget. Nothing is copied; the
	// caller waits for OnWritable or closes.
	LoopOutputFull = errs.Static(CodeLoopOutputFull, "LOOP_OUTPUT_FULL",
		"The connection cannot take more output now",
		"core/net: the connection's output or the loop's output budget is at its ceiling",
		0,
		75)

	// LoopSpillFull is the error OnClose receives for a connection whose unconsumed
	// input passed its spill ceiling or the loop's global spill budget. The loop
	// closes it rather than read further.
	LoopSpillFull = errs.Static(CodeLoopSpillFull, "LOOP_SPILL_FULL",
		"The connection sent more than it may hold unread",
		"core/net: the unconsumed input passed the spill ceiling",
		0,
		75)

	// LoopPostQueueFull is returned by Post when the loop's preallocated queue is
	// full. The operation is not queued and will not run.
	LoopPostQueueFull = errs.Static(CodeLoopPostQueueFull, "LOOP_POST_QUEUE_FULL",
		"The loop cannot take more posted work now",
		"core/net: the loop's post queue is full",
		0,
		75)

	// LoopWrongThread is returned under the loopdebug build tag by Write, Close or
	// SetDeadline called from a thread other than the loop's own. Without the tag
	// the check is not compiled; the call is a contract violation either way.
	LoopWrongThread = errs.Static(CodeLoopWrongThread, "LOOP_WRONG_THREAD",
		"The operation was called outside its loop",
		"core/net: a loop-thread operation was called from another thread",
		0,
		70)

	// HandlerAlreadySet is returned by Start for a stream group whose handler was
	// declared twice — by Handle, HandleFunc, HandleHTTP or HandleLoop, in any
	// order. The second declaration does not replace the first; the "group" field
	// names the group.
	HandlerAlreadySet = errs.Static(CodeHandlerAlreadySet, "HANDLER_ALREADY_SET",
		"The listener group already has a handler",
		"core/net: a second handler declaration on one stream group",
		0,
		64)
)

Functions

func Chain

func Chain[H any](h H, middlewares ...Middleware[H]) H

Chain applies middlewares to a handler, outermost first.

Chain(h, a, b, c) yields a(b(c(h))): the first middleware listed is the first to see a connection. That ordering matches how the list reads on the page, which is the only ordering a reader will guess correctly.

func DrainSignal

func DrainSignal(ctx context.Context) <-chan struct{}

DrainSignal returns the drain channel carried by ctx, or nil when the server serving this request publishes none.

A nil channel is the right absence: receiving from it blocks forever, so a select that watches it alongside other cases simply never fires that case. A caller therefore needs no nil check.

func WithDrainSignal

func WithDrainSignal(ctx context.Context, draining <-chan struct{}) context.Context

WithDrainSignal returns ctx carrying the channel a server closes when it begins draining.

A request context is deliberately NOT what carries this. Cancelling it would tell every in-flight handler to abandon the response it is halfway through, which is the opposite of a graceful drain — the drain exists so those responses finish. This signal is additive: a handler that ignores it behaves exactly as before, and a handler that holds a connection open indefinitely — an event stream, a long poll — gets the one piece of information it cannot otherwise have, namely that finishing now is the cooperative thing to do.

The channel is closed, never sent on, so every observer sees it and a late observer sees it immediately.

Types

type AddressValue

type AddressValue struct {
	// Network is the socket family: tcp, tcp4, tcp6, udp, udp4, udp6, unix,
	// unixgram or unixpacket.
	Network string `json:"network"`
	// Addr is the bind target: "host:port" for the IP families, a filesystem
	// path for the unix families.
	Addr string `json:"addr"`
}

AddressValue names one socket to bind. It is a value type so a group's listen set is comparable and copyable, and so a configuration file can express it directly.

func (AddressValue) String

func (a AddressValue) String() string

String renders the address as "network://addr" for logs and error fields.

type CallHook

type CallHook func(call CallValue)

CallHook receives one CallValue per completed outbound call. It is a function port rather than an interface because it is a single-method contract with no state, and a func keeps the domain free of a dependency on the logger or the metrics package: the consumer wires it to whichever it uses.

A CallHook MUST be safe for concurrent use and MUST NOT block — it runs on the calling goroutine, so a slow hook slows every request.

type CallValue

type CallValue struct {
	// Method is the uppercase HTTP method.
	Method string
	// Host is the authority the call was sent to.
	Host string
	// Path is the request path.
	Path string
	// Status is the HTTP status code, or zero when the call produced no response.
	Status int
	// Bytes is the number of response body bytes read.
	Bytes int64
	// Duration is the wall-clock time from request start to response headers.
	// It deliberately excludes the body: the record is emitted once the body is
	// done with, but this field measures the upstream's time to first answer,
	// which is the one a slow upstream and a large payload do not share.
	Duration time.Duration
	// Err is what ended the call: a policy refusal, a transport failure, the
	// response ceiling, or a failed close. Nil when the body was read in full.
	Err error
}

CallValue records one completed outbound call for observation, so a caller can log, meter or trace every egress without wrapping each call site by hand.

It is handed to a CallHook once the response body is FINISHED WITH — read to the end, failed, or closed — not when the headers arrive. A body's size is only known after it has been read, and an over-sized body or a failed close happens later still, so a record emitted at the headers could carry neither: it reported every call as a clean success of zero bytes. A refused request and an unreachable upstream are still reported immediately, because in neither case is there a body to wait for.

type ClientConfig

type ClientConfig struct {
	// BaseURL is the origin every relative path resolves against.
	BaseURL string `json:"base_url"`
	// DialTimeout bounds establishing the TCP connection.
	DialTimeout DurationValue `json:"dial_timeout"`
	// HandshakeTimeout bounds the TLS handshake.
	HandshakeTimeout DurationValue `json:"handshake_timeout"`
	// ResponseTimeout bounds the wait for response headers after the request
	// has been written. It is separate from TotalTimeout so a slow upstream is
	// distinguishable from a large body.
	ResponseTimeout DurationValue `json:"response_timeout"`
	// TotalTimeout bounds the whole call, body included.
	TotalTimeout DurationValue `json:"total_timeout"`
	// MaxResponseSize caps the response body in bytes. A body of exactly this
	// size is admitted; only one past it fails the call, and the body is never
	// truncated.
	MaxResponseSize int64 `json:"max_response_size"`
	// MaxRedirects caps the redirect chain. Every hop is re-authorised by the
	// policy, and a hop to another origin is refused unless RedirectOrigins
	// names it, so a redirect cannot walk out of the allowed surface.
	MaxRedirects int `json:"max_redirects"`
	// RedirectOrigins names the origins, each written scheme://host[:port], a
	// redirect may lead to besides the origin of the request that started the
	// chain. Empty — the default — refuses with RequestDenied every redirect
	// that changes the scheme, the host or the port, https to http included:
	// the built-in policies judge the method and the path, so a hop that keeps
	// the path and swaps the origin would otherwise pass them. A listed origin
	// is followed, and receives none of the DefaultHeaders. An entry that is
	// not a bare http or https origin fails New with InvalidAddress.
	RedirectOrigins []string `json:"redirect_origins"`
	// DefaultHeaders are added to every request that does not already set them
	// and is sent to the origin of BaseURL — or, with no BaseURL, to whatever
	// origin the request names. They are never sent to another origin: a
	// request built for one does not receive them, and a redirect that leaves
	// the origin of the request that started the chain is sent without them.
	DefaultHeaders map[string]string `json:"default_headers"`
}

ClientConfig describes an outbound client. Durations use DurationValue so a configuration file can say "5s" rather than a nanosecond count.

It sits in core beside LimitsValue and TimeoutsValue, which carry the same kind of knobs for the inbound half. Keeping both faces' configuration in one layer is what lets a consumer decode a whole network stanza against the contract package alone, without importing an implementation to name the type of a field it is setting.

type Conn

type Conn interface {
	stdnet.Conn
	// ID returns the monotonic identifier assigned at accept time, for
	// correlating log lines belonging to one connection.
	ID() uint64
	// Group returns the name of the listener group that accepted this
	// connection, so one handler serving several groups can tell them apart.
	Group() string
	// Buffer returns a connection-scoped scratch buffer sized by the group's
	// read-buffer limit. It is recycled with the connection, which is what lets
	// a handler read without allocating per connection.
	Buffer() []byte
}

Conn is one accepted stream connection.

It embeds stdlib net.Conn, so a handler reads and writes it exactly as it would any socket and every io helper keeps working — io.Copy, bufio, encoding wrappers, crypto/tls. Everything the domain adds is additive, which is what keeps "plug a handler in" a four-line exercise.

A Conn is pooled and recycled once ServeConn returns. A handler MUST NOT retain it, nor the slice returned by Buffer, past that point — the same contract the logger's builder already carries.

IFACE-PLUGIN: the concrete type stays unexported in internal/service/net/server.

type ConnHandler

type ConnHandler interface {
	ServeConn(ctx context.Context, c Conn) error
}

ConnHandler serves one accepted stream connection.

Returning an error does not kill the server: the connection is closed and the error is logged and metered. That is deliberate — one malformed peer must never be able to take down the process.

Implementations MUST be safe for concurrent use: the server runs one goroutine per connection and they all share the handler.

type ConnHandlerFunc

type ConnHandlerFunc func(ctx context.Context, c Conn) error

ConnHandlerFunc adapts a plain function to ConnHandler.

func (ConnHandlerFunc) ServeConn

func (f ConnHandlerFunc) ServeConn(ctx context.Context, c Conn) error

ServeConn implements ConnHandler by calling f.

type DurationValue

type DurationValue time.Duration

DurationValue is a time.Duration that round-trips through JSON as either a Go duration string ("30s", "1m30s") or a raw nanosecond count. The SDK's config loader (ADR 0028) decodes through a JSON round-trip, and encoding/json renders a bare time.Duration as nanoseconds — which would force an operator to write 30000000000 in a YAML file to mean thirty seconds. This type accepts both and always emits the readable form.

The mixed receivers are required by encoding/json and are not a style slip: MarshalJSON must take a value so a non-pointer struct field marshals, and UnmarshalJSON must take a pointer so it can assign. The standard library's time.Time carries the same asymmetry for the same reason.

PROMOTION CANDIDATE: this type is domain-neutral and stdlib-only, so it belongs in internal/kernel or internal/core/app/config the moment a second domain needs it. It is declared here because the repo's bar for a shared primitive is two real consumers arising from an actual duplication, and today there is one.

func (DurationValue) Duration

func (d DurationValue) Duration() time.Duration

Duration returns the value as a plain time.Duration.

func (DurationValue) MarshalJSON

func (d DurationValue) MarshalJSON() (encoded []byte, err error)

MarshalJSON emits the readable duration string so a written-back configuration file stays legible.

func (DurationValue) String

func (d DurationValue) String() string

String renders the canonical Go duration form, e.g. "1m30s".

func (*DurationValue) UnmarshalJSON

func (d *DurationValue) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts a quoted Go duration string ("30s") or a bare number of nanoseconds, so both a hand-written config file and a machine-generated one decode.

type IdentityFileParams

type IdentityFileParams struct {
	// CertFile is the PEM leaf certificate followed by any intermediates.
	// Supplying it without KeyFile is always a configuration slip, never an
	// intention, and is refused: an mTLS client that silently degrades to plain
	// TLS is a security hole that reports nothing.
	CertFile string `json:"cert_file"`
	// KeyFile is the PEM private key matching CertFile.
	KeyFile string `json:"key_file"`
	// RootsFile is the PEM CA bundle used to verify the peer. Leaving it empty
	// selects the platform trust store, which is right for a public endpoint and
	// wrong for a private PKI.
	RootsFile string `json:"roots_file"`
	// ClientCAFile is the PEM CA bundle a server accepts client certificates
	// from. Required whenever RequireClientCert is set.
	ClientCAFile string `json:"client_ca_file"`
	// ServerName overrides the name verified against the peer certificate.
	//
	// This is far more often required than its "optional" typing suggests. Any
	// deployment reached through a port forward or a tunnel connects to
	// 127.0.0.1 while the peer certificate names the real service, and the
	// handshake then fails every single time until ServerName is set. Treat it
	// as mandatory whenever the dial address is not the certificate's name.
	ServerName string `json:"server_name"`
	// MinVersion is the lowest accepted TLS version. Zero selects TLS 1.3;
	// anything below TLS 1.2 is refused.
	MinVersion uint16 `json:"min_version"`
	// NextProtos is the ALPN protocol list, e.g. {"h2", "http/1.1"}.
	NextProtos []string `json:"next_protos"`
	// RequireClientCert turns a server identity into mutual TLS.
	RequireClientCert bool `json:"require_client_cert"`
}

IdentityFileParams names TLS material to read from the filesystem. Every path is optional on its own; the combination is validated once the bytes are in hand, by the same core constructor that validates in-memory material.

It is the disk-sourced twin of IdentityParams and lives beside it on purpose: the two describe one domain concept — the material an identity is built from — and splitting them across layers would let the rules that govern them drift apart, which is exactly what this domain refuses for TLS.

type IdentityParams

type IdentityParams struct {
	// CertPEM is the PEM-encoded leaf certificate followed by any intermediates.
	// Paired with KeyPEM; supplying one without the other is an error.
	CertPEM []byte
	// KeyPEM is the PEM-encoded private key matching CertPEM.
	KeyPEM []byte
	// RootsPEM is the PEM-encoded CA bundle used to verify the peer. When empty
	// the platform trust store is used, which is the right default for a public
	// endpoint and the wrong one for a private PKI.
	RootsPEM []byte
	// ClientCAPEM is the PEM-encoded CA bundle a server accepts client
	// certificates from. It is meaningless on a client identity.
	ClientCAPEM []byte
	// ServerName overrides the name verified against the peer certificate. It is
	// required when connecting to a host by IP whose certificate names a DNS name.
	ServerName string
	// MinVersion is the lowest accepted TLS version. Zero selects TLS 1.3;
	// anything below TLS 1.2 is refused outright.
	MinVersion uint16
	// NextProtos is the ALPN protocol list, e.g. {"h2", "http/1.1"}.
	NextProtos []string
	// RequireClientCert turns a server identity into mutual TLS: the peer must
	// present a certificate that chains to ClientCAPEM.
	RequireClientCert bool
}

IdentityParams carries in-memory TLS material. Every field is optional on its own; the combination is what is validated by NewIdentity. Sourcing material from memory rather than from disk is what lets a consumer pull certificates out of a secret manager without ever writing them to a filesystem.

type IdentityValue

type IdentityValue struct {
	// contains filtered or unexported fields
}

IdentityValue is an opaque TLS identity: a certificate chain, the trust anchors used to verify the peer, and the negotiated-version policy. Its String and GoString output is always "<redacted>" so an accidental %v, %s or %#v can never spill key material into a log line. The only way out is ClientConfig or ServerConfig, which mint a fresh *tls.Config per call.

The zero value is valid and means "no identity" — IsZero reports it.

func NewIdentityValue

func NewIdentityValue(p IdentityParams) (id IdentityValue, err error)

NewIdentityValue validates in-memory TLS material and returns the opaque identity. It fails loudly where the standard library is silent: a CA bundle that yields no usable certificate is TLSMaterialInvalid, never an empty pool that would verify nothing.

func (IdentityValue) ClientConfig

func (i IdentityValue) ClientConfig() *tls.Config

ClientConfig returns a fresh *tls.Config for dialling a peer. Each call yields a new value so a caller mutating the result cannot affect any other user of the same identity.

"Fresh" covers the slices and the trust pool, not only the outer struct. A new *tls.Config whose Certificates and RootCAs still point at the identity's own would leave cfg.Certificates[0] = other and cfg.RootCAs.AddCert(evil) reaching every other configuration the identity has ever minted, including ones already in use — which is the opposite of what an opaque, immutable identity is for. Who pays that cost, and how often, is the CALLER's choice and not a property of this method: net/client calls it once inside New and net/server once per listener, but service/app/mail calls it per connection — which for SMTP is per message — deliberately, because StartTLS takes the config and a transport that reused one would hand every send a *tls.Config another send could still be mutating. Three slice clones are nothing beside an SMTP round trip; the sentence that used to sit here said "never per request", which was true of the two net callers and false of the third.

func (IdentityValue) GoString

func (i IdentityValue) GoString() string

GoString implements fmt.GoStringer so %#v stays redacted too — fmt bypasses String for Go-syntax formatting and would otherwise dump the unexported fields.

func (IdentityValue) IsZero

func (i IdentityValue) IsZero() bool

IsZero reports whether the identity carries no material at all, which is the zero value a caller gets when no TLS was configured.

func (IdentityValue) ServerConfig

func (i IdentityValue) ServerConfig() *tls.Config

ServerConfig returns a fresh *tls.Config for accepting connections, switching to mutual TLS when the identity requires a client certificate.

func (IdentityValue) String

func (i IdentityValue) String() string

String implements fmt.Stringer and always redacts so key material never reaches a log line through %v or %s.

type LimitsValue

type LimitsValue struct {
	// MaxConns caps concurrent connections in the group. Beyond it, a new
	// connection is accepted and immediately closed with ConnLimitReached, which
	// is kinder than letting the accept queue silently overflow.
	MaxConns int `json:"max_conns"`
	// ReadBufferSize sizes the per-connection scratch buffer returned by
	// Conn.Buffer.
	ReadBufferSize int `json:"read_buffer_size"`
	// MaxPacketSize is the largest datagram accepted. A larger one is DROPPED
	// and counted in StateValue.OversizedPackets, never delivered truncated:
	// the kernel discards the tail, so the prefix that survives reads exactly
	// like a complete message and the handler cannot tell the difference.
	MaxPacketSize int `json:"max_packet_size"`
	// Backlog is the listen backlog depth where the platform honours it.
	Backlog int `json:"backlog"`
	// Shards is the number of independent listeners to open on one address with
	// SO_REUSEPORT, each with its own accept loop, to remove accept contention.
	// Zero selects the platform default.
	Shards int `json:"shards"`
	// BatchSize is how many datagrams to read per syscall. Zero selects the
	// default; one disables batching.
	BatchSize int `json:"batch_size"`
}

LimitsValue bounds what one listener group may consume. Every field is optional; zero means "use the domain default", never "unbounded" — an unset ceiling must not be the dangerous one.

type ListenerStateValue

type ListenerStateValue struct {
	// Group is the listener group's name.
	Group string `json:"group"`
	// Address is the address actually bound, which for a port of zero is the
	// one the kernel chose rather than the one requested.
	Address string `json:"address"`
	// Network is the socket family.
	Network string `json:"network"`
	// Shards is how many listeners were actually opened on this address.
	Shards int `json:"shards"`
	// Degraded reports that a requested optimisation was unavailable and the
	// server fell back. It is surfaced rather than logged once at startup,
	// because a silent fallback is indistinguishable from a working one and
	// nobody re-reads yesterday's logs to find out.
	Degraded bool `json:"degraded"`
	// DegradedReason names the fallback in plain words when Degraded is set.
	DegradedReason string `json:"degraded_reason,omitempty"`
	// Adopted reports a listener inherited from a supervisor rather than bound
	// by this process.
	Adopted bool `json:"adopted"`
}

ListenerStateValue reports one bound listener.

It carries the address actually bound rather than the one requested, and it reports any fallback explicitly, so a caller never has to infer either.

type LoopBackend added in v0.26.13

type LoopBackend uint8

LoopBackend names the mechanic a loop group runs on (ADR 0186 §D5). The zero value is LoopBackendAuto.

const (
	// LoopBackendAuto takes the best backend the platform's probe reaches:
	// io_uring, then epoll on Linux, the portable backend elsewhere.
	LoopBackendAuto LoopBackend = iota
	// LoopBackendIOURing is io_uring, on linux/amd64, arm64, riscv64 and loong64,
	// after the kernel's staged probe.
	LoopBackendIOURing
	// LoopBackendEpoll is epoll, on every Linux architecture.
	LoopBackendEpoll
	// LoopBackendPortable is the default engine's accept path and goroutines,
	// serialised onto one handler goroutine, on every platform.
	LoopBackendPortable
)

The loop backends. The zero value is LoopBackendAuto.

func (LoopBackend) String added in v0.26.13

func (b LoopBackend) String() string

String renders the backend as State and the start line spell it.

type LoopConnRef added in v0.26.8

type LoopConnRef struct {
	// Ring is the ring, inside its loop, that holds the connection.
	Ring uint32
	// Slot is the connection's index in the ring's table.
	Slot uint32
	// Gen is the slot's generation. It is never zero for a live connection, so
	// the zero LoopConnRef is stale on every loop.
	Gen uint32
}

LoopConnRef names one connection of a loop: the ring it lives on, its slot in that ring's table and the generation of that slot. It is a comparable value with no behaviour, so a handler can key a map with it and hold it without holding anything of the engine.

A slot is reused only once every operation of its previous generation has completed, and the generation then moves on, so an operation on a reference whose connection closed does nothing and returns LoopStaleRef — it can never reach the connection that took the slot since. The zero value names no connection.

type LoopHandler added in v0.26.8

type LoopHandler interface {
	// OnOpen is called once per accepted connection, before any OnData for it.
	// Pressure(ref) already answers for this connection.
	OnOpen(io LoopIO, ref LoopConnRef)
	// OnData hands the handler the bytes received on ref and returns how many of
	// them it consumed, from 0 to len(buf). buf stays valid until the next
	// OnBatchEnd returns, never later, and the handler must not write to it.
	// The unconsumed tail is kept in the connection's spill and handed back
	// first, followed by the next bytes, on the next OnData; a tail past the
	// spill ceiling closes the connection with LoopSpillFull. A value outside
	// 0 to len(buf) is treated as len(buf).
	OnData(io LoopIO, ref LoopConnRef, buf []byte) int
	// OnTimeout is called when the deadline set by SetDeadline on ref expires.
	// The connection stays open: closing it is the handler's choice.
	OnTimeout(io LoopIO, ref LoopConnRef)
	// OnWritable is called when ref's pending output falls below the loop's
	// low-water mark after a Write was refused with LoopOutputFull or left output
	// pending past the mark, so a producer that paused can resume.
	OnWritable(io LoopIO, ref LoopConnRef)
	// OnBatchEnd is called once per loop iteration, after the OnData calls of
	// every ring of the loop, and before any buffer handed to them returns to the
	// kernel. It is where a handler flushes work it batched — one database
	// commit for every request of the batch, say — before writing the answers.
	OnBatchEnd(io LoopIO)
	// OnClose is called once per connection, last. err is nil when the peer
	// closed cleanly or the handler called Close, and otherwise names why the
	// loop closed it — LoopSpillFull, an idle timeout, the drain, a socket
	// error. ref is stale once OnClose returns.
	OnClose(io LoopIO, ref LoopConnRef, err error)
}

LoopHandler is the port a consumer implements to be served by a loop group (ADR 0186 §D4).

One thread calls it: every callback of one loop runs on that loop's thread, one at a time, so a handler needs no lock for state it keeps per loop. It MUST NOT block: a blocking callback stalls every connection of its loop. A callback receives the LoopIO of the loop that calls it and the reference of the connection concerned; neither is valid past the loop's life, and a reference past its connection's OnClose is stale.

IFACE-PLUGIN: the engines live in internal/service/net/loop and internal/service/net/server; this port is what a consumer writes.

type LoopIO added in v0.26.8

type LoopIO interface {
	// Write copies p into ref's output and returns. Writes on one reference leave
	// in the order they were made, and p may be reused once Write returns. It is
	// refused with LoopOutputFull, copying nothing, when p would pass the
	// connection's output ceiling or the loop's global output budget, and with
	// LoopStaleRef when ref's connection has closed.
	Write(ref LoopConnRef, p []byte) error
	// Close closes ref's connection once the output already written has left,
	// and calls OnClose. A second Close, or a Close on a stale reference, returns
	// LoopStaleRef and does nothing.
	Close(ref LoopConnRef) error
	// SetDeadline arms ref's timer to fire OnTimeout after d, on the loop's
	// coarse timer wheel: it fires no earlier than d and at most one wheel tick
	// late. A d of zero or less disarms it. Arming again replaces the previous
	// deadline.
	SetDeadline(ref LoopConnRef, d time.Duration) error
	// Pressure reports whether ref's connection was accepted, or has since come,
	// under the loop's soft pressure: the group serves more connections than its
	// soft bound. It is a primitive; what a protocol makes of it — HTTP/1.1
	// answers Connection: close — is the protocol adapter's. A stale reference
	// reports false.
	Pressure(ref LoopConnRef) bool
	// Post queues op to run once on the loop's thread with arg, and wakes the
	// loop. It is the one operation any goroutine may call. The queue is
	// preallocated and holds no closure: op is a plain function and arg an
	// integer the caller gives meaning to. Each posted operation runs exactly
	// once, in the order posted; a full queue refuses with LoopPostQueueFull, and
	// a loop that has stopped with ServerClosed.
	Post(op PostFunc, arg uintptr) error
}

LoopIO is what a loop offers its handler: the operations on its connections (ADR 0186 §D4). Each loop implements it with a pointer of its own, handed to every callback.

Write, Close and SetDeadline belong to the loop's thread: they are called from a callback, never from another goroutine, which is what lets them take no lock. Under the loopdebug build tag a call from another thread is refused with LoopWrongThread. Post is the one operation another goroutine may call. Every error is a preallocated sentinel; none of the calls allocates when p belongs to the caller's heap or mapping.

IFACE-PLUGIN: the concrete loops stay unexported in internal/service/net/loop and internal/service/net/server.

type LoopOptionHonourer added in v0.26.8

type LoopOptionHonourer interface {
	// Honours reports whether the handler applies the option of kind k itself.
	Honours(k OptionKind) bool
}

LoopOptionHonourer is the optional capability of a LoopHandler that honours group options the loop engine does not: a protocol adapter that applies a header deadline, say. At Start, server refuses with LoopOptionUnsupported every option set on a loop group that neither the engine nor the handler honours (ADR 0186 §D1). A handler without it honours nothing of its own.

type LoopRingStateValue added in v0.26.13

type LoopRingStateValue struct {
	// Loop is the loop's index in its group.
	Loop int `json:"loop"`
	// Ring is the ring's index in its loop.
	Ring int `json:"ring"`
	// Conns is the number of connections the ring holds now.
	Conns int64 `json:"conns"`
	// Accepted is the number of connections the ring accepted.
	Accepted uint64 `json:"accepted"`
	// Closed is the number of connections the ring closed.
	Closed uint64 `json:"closed"`
	// Batches is the number of loop iterations that reaped this ring.
	Batches uint64 `json:"batches"`
	// Completions is the number of completions or events the ring reaped.
	Completions uint64 `json:"completions"`
	// BufferStarved is the number of receives that found no provided buffer
	// (ENOBUFS) and waited for one to come back.
	BufferStarved uint64 `json:"buffer_starved"`
	// EnterRetries is the number of times submitting to the kernel was retried
	// after it was busy — the completion queue full, typically.
	EnterRetries uint64 `json:"enter_retries"`
	// AcceptPauses is the number of times the ring stopped accepting: the
	// descriptor reserve reached, or the process out of descriptors.
	AcceptPauses uint64 `json:"accept_pauses"`
	// Evicted is the number of idle connections closed to admit others.
	Evicted uint64 `json:"evicted"`
	// WatchdogFires is the number of times the liveness watchdog saw a stall it
	// could observe and acted on it.
	WatchdogFires uint64 `json:"watchdog_fires"`
	// Draining reports a ring the watchdog judged sick: it accepts nothing new
	// and closes its connections as they fall idle.
	Draining bool `json:"draining"`
}

LoopRingStateValue reports one ring of one loop: counters the loop refreshes at the end of each batch, read without stopping it (ADR 0186 §D8).

type LoopStateValue added in v0.26.13

type LoopStateValue struct {
	// Group is the loop group's name.
	Group string `json:"group"`
	// Backend is the backend in use.
	Backend LoopBackend `json:"backend"`
	// ProbeStage names the last stage of the kernel probe the backend passed,
	// or the stage that failed when the group fell back.
	ProbeStage string `json:"probe_stage"`
	// Degraded reports that the group runs on a backend other than the best one
	// it asked for: auto fell back from io_uring, say.
	Degraded bool `json:"degraded"`
	// DegradedReason names the fallback and its cause when Degraded is set.
	DegradedReason string `json:"degraded_reason,omitempty"`
	// Loops is the number of loops running.
	Loops int `json:"loops"`
	// Rings is the number of rings per loop.
	Rings int `json:"rings"`
	// RingsSource says where Rings came from: "explicit", "derived" from the
	// connection bounds and the descriptor limit, or "single" when no bound was
	// set or the backend runs one ring.
	RingsSource string `json:"rings_source"`
	// RLimitNoFile is the effective RLIMIT_NOFILE soft limit read at Start. The
	// SDK never raises it; the Go runtime already lifts the soft limit to the
	// hard one.
	RLimitNoFile uint64 `json:"rlimit_nofile"`
	// GOMAXPROCS is runtime.GOMAXPROCS(0) read at Start. The SDK never sets it.
	GOMAXPROCS int `json:"gomaxprocs"`
	// BatchP99 is the 99th percentile of one loop iteration's duration, over the
	// loops' recent batches.
	BatchP99 DurationValue `json:"batch_p99"`
	// RingStates reports every ring of every loop, loop by loop.
	RingStates []LoopRingStateValue `json:"ring_states"`
}

LoopStateValue reports one loop group: the backend it runs on, how it got there, how it is sized, and its rings' counters (ADR 0186 §D8). A fallback is reported here and by Degraded, never silently.

type LoopValue added in v0.26.13

type LoopValue struct {
	// Backend is the mechanic to run on. LoopBackendAuto takes the best one the
	// platform's probe reaches and reports any fallback in State; an explicit
	// backend the platform cannot run is refused at Start.
	Backend LoopBackend `json:"backend"`
	// Loops is the number of loops, each one goroutine locked to its OS thread
	// on a native backend. Zero selects one. The SDK never sets GOMAXPROCS:
	// sizing the process against its loops is the consumer's policy, and State
	// reports both.
	Loops int `json:"loops"`
	// Rings is the number of descriptor shards inside one loop, each with its
	// own table of at most min(RLIMIT_NOFILE, IORING_MAX_FIXED_FILES) entries:
	// what lets io_uring serve more connections than RLIMIT_NOFILE. Zero
	// derives it, ⌈max(MaxConns, SoftConns) / min(RLIMIT_NOFILE,
	// IORING_MAX_FIXED_FILES)⌉, and one when both are zero; that is the only
	// formula, and State says where the value came from. epoll and the portable
	// backend run one ring whatever it says.
	Rings int `json:"rings"`
	// CQBatch bounds the completions or events one loop iteration reaps before
	// it calls OnBatchEnd. Zero selects the domain default.
	CQBatch int `json:"cq_batch"`
	// SpillCap is the most unconsumed input, in bytes, one connection may hold
	// between OnData calls. Past it the connection is closed with
	// LoopSpillFull. Zero selects the domain default.
	SpillCap int `json:"spill_cap"`
	// OutputCap is the most written and unsent output, in bytes, one
	// connection may hold. A Write past it is refused with LoopOutputFull. Zero
	// selects the domain default.
	OutputCap int `json:"output_cap"`
	// SoftConns is the soft connection bound: a connection accepted while the
	// group serves more is flagged under pressure (LoopIO.Pressure), so a
	// protocol can ask its peer to leave. It refuses nothing; MaxConns does.
	// Zero sets no soft bound.
	SoftConns int `json:"soft_conns"`
	// EvictIdle is how long a connection must have been idle before the loop
	// may close it to admit another under hard pressure — the process out of
	// descriptors, or MaxConns reached. A connection with unread bytes is
	// never evicted. Zero evicts nothing.
	EvictIdle DurationValue `json:"evict_idle"`
}

LoopValue configures a loop group's engine (ADR 0186 §D5). It is a value of its own, passed by the Loop group option, so LimitsValue and TimeoutsValue keep the meanings they publish for every engine. Every field is optional: zero selects the domain default, never "unbounded".

type Middleware

type Middleware[H any] func(next H) H

Middleware decorates a handler with another of the same type.

It is generic over the handler type so stream and datagram handlers share one shape and one Chain helper, instead of the domain carrying two near-identical middleware types that would inevitably drift.

type OptionKind added in v0.26.8

type OptionKind uint8

OptionKind names a group option, for the one question a loop group asks of its options at Start: is this one honoured (LoopOptionHonourer)? It names options, not their values.

const (
	// OptionNone names no option; it is the zero value.
	OptionNone OptionKind = iota
	// OptionTLS is TLS. A loop group always refuses it, with LoopTLSUnsupported.
	OptionTLS
	// OptionMiddleware is Use. A loop group always refuses it: there is no
	// ConnHandler to decorate.
	OptionMiddleware
	// OptionReadTimeout is ReadTimeout.
	OptionReadTimeout
	// OptionWriteTimeout is WriteTimeout.
	OptionWriteTimeout
	// OptionIdleTimeout is IdleTimeout. The loop engine honours it.
	OptionIdleTimeout
	// OptionReadHeaderTimeout is ReadHeaderTimeout, which only a protocol adapter
	// can honour.
	OptionReadHeaderTimeout
	// OptionMaxHeaderBytes is MaxHeaderBytes, which only a protocol adapter can
	// honour.
	OptionMaxHeaderBytes
	// OptionHandshakeTimeout is HandshakeTimeout.
	OptionHandshakeTimeout
	// OptionReadBufferSize is ReadBufferSize.
	OptionReadBufferSize
	// OptionMaxPacketSize is MaxPacketSize.
	OptionMaxPacketSize
	// OptionBatchSize is BatchSize.
	OptionBatchSize
	// OptionShards is Shards.
	OptionShards
	// OptionMaxConns is MaxConns. The loop engine honours it, refusing with
	// ConnLimitReached.
	OptionMaxConns
	// OptionAdopt is Adopt. The loop engine honours it.
	OptionAdopt
	// OptionPreHandshake is PreHandshake.
	OptionPreHandshake
)

The group options a loop group judges at Start, by kind.

func (OptionKind) String added in v0.26.8

func (k OptionKind) String() string

String renders the option's name, as its constructor spells it.

type Packet

type Packet interface {
	// Data returns the datagram payload.
	Data() []byte
	// From returns the sender's address.
	From() stdnet.Addr
	// To returns the local address the datagram arrived on.
	To() stdnet.Addr
	// ID returns the monotonic identifier assigned at read time.
	ID() uint64
	// Group returns the name of the listener group that received it.
	Group() string
	// Reply writes a datagram back to the sender. It exists so the common case
	// needs no reference to the underlying connection.
	Reply(b []byte) (n int, err error)
}

Packet is one received datagram.

Data is valid only until ServePacket returns: the buffer is recycled straight back into the read batch, which is what makes batched datagram reading allocation-free. A handler that needs the payload afterwards must copy it.

IFACE-PLUGIN: the concrete type stays unexported in internal/service/net/server.

type PacketHandler

type PacketHandler interface {
	ServePacket(ctx context.Context, p Packet) error
}

PacketHandler serves one received datagram.

Returning an error does not kill the server: it is logged and metered, and reading continues. A datagram service must survive a malformed packet.

Implementations MUST be safe for concurrent use.

type PacketHandlerFunc

type PacketHandlerFunc func(ctx context.Context, p Packet) error

PacketHandlerFunc adapts a plain function to PacketHandler.

func (PacketHandlerFunc) ServePacket

func (f PacketHandlerFunc) ServePacket(ctx context.Context, p Packet) error

ServePacket implements PacketHandler by calling f.

type Phase

type Phase uint8

Phase is where a server sits in its lifecycle. It is reported by State so an operator — or a readiness probe — can tell "not yet listening" apart from "listening" and from "draining", which a single boolean cannot express.

const (
	// PhaseNew is a constructed server that has not bound anything yet.
	PhaseNew Phase = iota
	// PhaseStarting is binding listeners; some may already be up.
	PhaseStarting
	// PhaseServing is bound and accepting.
	PhaseServing
	// PhaseDraining has stopped accepting and is waiting for in-flight work.
	PhaseDraining
	// PhaseStopped has released every listener.
	PhaseStopped
)

The lifecycle phases, in the order a server passes through them.

func (Phase) String

func (p Phase) String() string

String renders the phase for logs and status output.

type Policy

type Policy interface {
	Allow(req RequestValue) error
}

Policy authorises an outbound request before it leaves the process.

The contract that makes this worth a port: implementations are evaluated inside the RoundTripper, underneath every call site. A caller cannot construct a request that skips the check, so "this client is read-only" becomes a property of the code rather than a convention the next contributor must remember. Allow returns nil to permit the request and a typed error to refuse it; the refusal is reported as RequestDenied without echoing the path, so a denial cannot leak the private API surface through a log line.

Implementations MUST be safe for concurrent use.

IFACE-PLUGIN: concrete policies live in internal/service/net/client; their types stay unexported behind their constructors.

type PolicyFunc

type PolicyFunc func(req RequestValue) error

PolicyFunc adapts a plain function to the Policy interface.

func (PolicyFunc) Allow

func (f PolicyFunc) Allow(req RequestValue) error

Allow implements Policy by calling f.

type PostFunc added in v0.26.8

type PostFunc func(io LoopIO, arg uintptr)

PostFunc is an operation posted to a loop with LoopIO.Post. It runs once, on the loop's thread, with the loop's LoopIO and the integer it was posted with. It is a plain function rather than a closure so the queue holds no pointer the poster built: a top-level function and an index into the caller's own table post without allocating.

type RequestValue

type RequestValue struct {
	// Method is the uppercase HTTP method, e.g. "GET".
	Method string
	// Scheme is the URL scheme, e.g. "https".
	Scheme string
	// Host is the authority, host[:port]. It is carried because a policy that
	// sees only the path cannot defend against a redirect that keeps the path
	// and swaps the origin.
	Host string
	// EscapedPath is the percent-encoded path exactly as it will be sent. It is
	// never the decoded form — see the type comment.
	EscapedPath string
	// RawQuery is the encoded query string, without the leading "?".
	RawQuery string
}

RequestValue describes an outbound request to the transport policy.

Two field choices are load-bearing and were paid for in a real integration:

  • The path is the ESCAPED form, the one that goes on the wire. url.URL.Path is already percent-DECODED, so a policy that judges it accepts "%2e%2e" — which the upstream then reinterprets as "..". Judging the decoded form makes a correctly written allowlist bypassable, so this type does not carry it.
  • RawQuery is present because a policy often needs to require a parameter, not just permit a path. A read-only policy that cannot see the query cannot, for instance, insist that a listing call passes the filter that makes its result correct.

The value is immutable by construction: a policy receives a copy and cannot mutate the request it is judging.

type ResponseValue

type ResponseValue struct {
	// Status is the HTTP status code. It is set even when the call also returns
	// an error, so a caller can tolerate a specific status deliberately.
	Status int
	// Header is the response header set.
	Header http.Header
	// Body is the fully-read response body, already bounded by the client's
	// size cap. It is nil for a status that carries no body, such as 204.
	Body []byte
}

ResponseValue is a fully-read outbound response.

The client returns this rather than an *http.Response on purpose. A returned *http.Response makes the caller responsible for closing the body and for reading it under a size cap, and a single forgotten Close leaks a connection. It also makes an accurate byte count impossible: the size of a body is only known once it has been read, so an observation hook firing at header time can never report it. Reading and closing inside the client fixes both.

type SSEEventValue

type SSEEventValue struct {
	// ID is the frame's "id" field. A client stores the last non-empty id it
	// saw and sends it back as Last-Event-ID when it reconnects, so this is the
	// resume token — and it is the CALLER's to mint, because only the caller
	// knows what it means. Empty emits no id field, which leaves the client's
	// stored id untouched; that is the format's own behaviour, not an omission.
	// An id carrying U+0000 is refused with SSEFieldInvalid, as one carrying a
	// line terminator is: a client ignores such an id field (WHATWG HTML,
	// "process the field"), so its stored resume token would silently stay the
	// previous one.
	ID string `json:"id"`
	// Name is the frame's "event" field — the event type the client dispatches
	// under. Empty means the default type, "message".
	Name string `json:"event"`
	// Data is the payload. Every line terminator it carries (LF, CR or CRLF)
	// becomes a separate "data:" line, which the client rejoins with "\n". A
	// payload that mixed terminators therefore arrives normalised to LF: the
	// value is preserved, its byte spelling is not.
	Data string `json:"data"`
	// Retry is the reconnection delay hint, emitted as an integer millisecond
	// count. Zero emits no retry field.
	Retry DurationValue `json:"retry"`
}

SSEEventValue is one Server-Sent Events frame.

The format has no escape mechanism. A line terminator inside a value is not quoted, it SPLITS the value: that is exactly what makes a multi-line payload expressible — Data is emitted as one "data:" line per line and the client rejoins them with "\n" — and exactly what makes a line terminator inside ID or Name unrepresentable. Those are refused rather than truncated, because a silently shortened id is a resume token pointing at the wrong place.

func (SSEEventValue) IsZero

func (e SSEEventValue) IsZero() bool

IsZero reports whether the frame carries no field at all.

func (SSEEventValue) Validate

func (e SSEEventValue) Validate() error

Validate reports whether the frame can be carried by the wire format.

It is separate from the encoder (internal/service/net/sse.AppendEvent) so a caller can check a frame before committing to send it, and so the encoder can validate everything BEFORE it appends anything — a half-written frame on a stream is unrecoverable, since the peer has already read it.

type StateValue

type StateValue struct {
	// Phase is where the server sits in its lifecycle.
	Phase Phase `json:"phase"`
	// Listeners describes every bound listener, including any degradation.
	Listeners []ListenerStateValue `json:"listeners"`
	// ActiveConns is the number of connections currently being served.
	ActiveConns int64 `json:"active_conns"`
	// TotalConns is the number accepted since the server started.
	TotalConns uint64 `json:"total_conns"`
	// RejectedConns is the number turned away by the connection ceiling.
	RejectedConns uint64 `json:"rejected_conns"`
	// OversizedPackets is the number of datagrams dropped for passing their
	// group's MaxPacketSize. They are counted rather than delivered because the
	// kernel has already discarded the tail, and the prefix that survives is
	// indistinguishable from a complete message to the handler receiving it. A
	// read loop has nobody to return an error to, so this counter is the report.
	OversizedPackets uint64 `json:"oversized_packets"`
	// AcceptBackoffs is the number of times an accept loop waited after Accept
	// failed with anything but its listener closing — the process out of file
	// descriptors, typically, with connections still queued. Retrying at once
	// would spin a core per listener while the condition lasts; each wait is
	// 5 ms, doubling to 1 s, and the next accepted connection starts the curve
	// over. A count that keeps rising is a server that cannot accept.
	AcceptBackoffs uint64 `json:"accept_backoffs"`
	// Loops reports every loop group (ADR 0186 §D8). It is empty while the
	// server serves none.
	Loops []LoopStateValue `json:"loops,omitempty"`
}

StateValue is a snapshot of the server's lifecycle and listeners.

It is a value, so a caller can hold it, log it, or compare it without racing the server that produced it.

func (StateValue) Degraded

func (s StateValue) Degraded() bool

Degraded reports whether any listener or loop group fell back from a requested optimisation, so a caller can assert "no silent degradation" in one call instead of walking the slices.

type TimeoutsValue

type TimeoutsValue struct {
	// Read bounds one read from the peer.
	Read DurationValue `json:"read"`
	// Write bounds one write to the peer.
	Write DurationValue `json:"write"`
	// Idle bounds how long a connection may sit with no traffic at all.
	Idle DurationValue `json:"idle"`
	// Handshake bounds the TLS handshake, which is the phase a hostile peer can
	// most cheaply stall. Unlike the others, zero here does NOT mean unbounded:
	// the negotiation happens before any handler exists to notice it, so it
	// falls back to the domain's default instead.
	Handshake DurationValue `json:"handshake"`
}

TimeoutsValue bounds each phase of a connection separately.

Per-phase deadlines rather than one overall budget: a slow reader, a slow writer and an idle keep-alive connection are three different problems with three different right answers, and a single timeout cannot tell them apart.

Every field is a PER-OPERATION bound, refreshed each time, not a budget for the connection's whole life. The distinction is the whole point: a peer sending one byte per second violates a per-read bound at once and stays inside a lifetime bound until the lifetime runs out.

type WSCloseCode

type WSCloseCode uint16

WSCloseCode is the status code a Close frame carries (RFC 6455 §7.4).

const (
	// WSCloseNormal is a completed purpose, on either side.
	WSCloseNormal WSCloseCode = 1000
	// WSCloseGoingAway is a server shutting down or a browser navigating away.
	// It is what a drain sends.
	WSCloseGoingAway WSCloseCode = 1001
	// WSCloseProtocolError is a frame the RFC forbids.
	WSCloseProtocolError WSCloseCode = 1002
	// WSCloseUnsupportedData is a well-formed frame carrying data this endpoint
	// cannot accept — binary to a text-only peer, for instance.
	WSCloseUnsupportedData WSCloseCode = 1003
	// WSCloseNoStatus means the peer's Close frame carried no code. It is
	// reported to the application and never sent.
	WSCloseNoStatus WSCloseCode = 1005
	// WSCloseAbnormal means the connection died without a Close frame. It is
	// reported to the application and never sent.
	WSCloseAbnormal WSCloseCode = 1006
	// WSCloseInvalidPayload is a text frame that is not valid UTF-8, or a close
	// reason that is not.
	WSCloseInvalidPayload WSCloseCode = 1007
	// WSClosePolicyViolation is the generic refusal when no other code fits.
	WSClosePolicyViolation WSCloseCode = 1008
	// WSCloseTooLarge is a message beyond what this endpoint accepts.
	WSCloseTooLarge WSCloseCode = 1009
	// WSCloseExtensionRequired is a client giving up because the server did not
	// negotiate an extension it needs. A server never sends it.
	WSCloseExtensionRequired WSCloseCode = 1010
	// WSCloseInternalError is an unexpected condition on this side.
	WSCloseInternalError WSCloseCode = 1011
	// WSCloseTLSHandshake means the TLS handshake failed. It is reported to the
	// application and never sent — there is no connection to send it on.
	WSCloseTLSHandshake WSCloseCode = 1015
)

The close codes RFC 6455 §7.4.1 defines. Three of them — WSCloseNoStatus, WSCloseAbnormal and WSCloseTLSHandshake — are values an application may OBSERVE but that MUST NOT appear on the wire: they describe the absence of a close frame, so sending one would be a statement contradicted by its own delivery.

func (WSCloseCode) Echoable

func (c WSCloseCode) Echoable() WSCloseCode

Echoable returns the code to send back when answering this one.

It exists because §5.5.1 says an endpoint SHOULD echo the peer's status code, and the one code a peer can leave us holding — WSCloseNoStatus, for a Close with no payload — is a code §7.4.1 forbids on the wire. Echoing blindly is therefore a protocol error waiting for the first well-behaved peer that closes without saying why.

func (WSCloseCode) Sendable

func (c WSCloseCode) Sendable() bool

Sendable reports whether the code may appear in a Close frame on the wire.

It is deliberately a property of the CODE rather than a check inside the sender, because the same question is asked twice from opposite directions: a peer that sends 1006 is committing a protocol error, and so is this endpoint if it ever does. One predicate, both directions, no chance of the two drifting apart.

type WSMessageValue

type WSMessageValue struct {
	// Binary reports whether the payload is opaque bytes (opcode 0x2) rather
	// than UTF-8 text (opcode 0x1). The zero value is text, which is the
	// opcode a zero-valued frame would carry anyway.
	Binary bool `json:"binary"`
	// Data is the reassembled payload. For a text message it is valid UTF-8,
	// which the engine verifies on both the receiving and the sending side.
	Data []byte `json:"data"`
}

WSMessageValue is one complete WebSocket application message, reassembled from however many frames carried it.

A message is the unit an application reasons about; a frame is the unit the transport reasons about. Handing the application frames would make fragmentation — which is the sender's private choice of chunk size, not a semantic boundary — visible in every consumer.

func (WSMessageValue) OpCode

func (m WSMessageValue) OpCode() WSOpCode

OpCode returns the frame opcode this message is carried under.

type WSOpCode

type WSOpCode byte

WSOpCode is a frame's four-bit type field (RFC 6455 §5.2).

const (
	// WSContinuation continues the message the previous frame began.
	WSContinuation WSOpCode = 0x0
	// WSText carries a UTF-8 payload, validated on both sides.
	WSText WSOpCode = 0x1
	// WSBinary carries opaque bytes.
	WSBinary WSOpCode = 0x2
	// WSClose begins or completes the closing handshake.
	WSClose WSOpCode = 0x8
	// WSPing asks the peer to answer with a Pong carrying the same payload.
	WSPing WSOpCode = 0x9
	// WSPong answers a Ping, or is sent unsolicited as a one-way heartbeat.
	WSPong WSOpCode = 0xA
)

The opcodes RFC 6455 defines. Everything else in the four-bit space is reserved, and a reserved opcode fails the connection rather than being skipped: an endpoint that ignored it would be desynchronised from a peer that believes the frame meant something.

func (WSOpCode) Defined

func (o WSOpCode) Defined() bool

Defined reports whether RFC 6455 assigns this opcode a meaning.

func (WSOpCode) IsControl

func (o WSOpCode) IsControl() bool

IsControl reports whether the opcode names a control frame.

Control frames are the ones that may be injected BETWEEN the fragments of a message, so the distinction decides which fragmentation rules apply.

func (WSOpCode) String

func (o WSOpCode) String() string

String renders the opcode for a log line or an error field.

Jump to

Keyboard shortcuts

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