net

package
v0.26.49 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 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 CodeEngineUnsupported errs.Code = 0x00_02_0B_38 // 0.2.11.56

CodeEngineUnsupported identifies an Engine a group cannot run on: one its handler's type has no such engine for, one another option contradicts, or a value outside the engine constants (ADR 0187).

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 CodeGroupNameConflict errs.Code = 0x00_02_0B_3A // 0.2.11.58

CodeGroupNameConflict identifies a Name option that contradicts the name the group was declared with, or names nothing (ADR 0187).

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 CodeHTTP1BodyTooLarge errs.Code = 0x00_02_0B_34 // 0.2.11.52

CodeHTTP1BodyTooLarge identifies a Content-Length past the profile's body bound.

View Source
const CodeHTTP1ContentLengthInvalid errs.Code = 0x00_02_0B_2F // 0.2.11.47

CodeHTTP1ContentLengthInvalid identifies a Content-Length the profile cannot frame by: empty, not decimal digits, too long, or present twice, equal or not.

View Source
const CodeHTTP1ExpectRefused errs.Code = 0x00_02_0B_31 // 0.2.11.49

CodeHTTP1ExpectRefused identifies a request carrying an Expect field: the profile sends no interim response, so 100-continue cannot be met.

View Source
const CodeHTTP1FieldDuplicated errs.Code = 0x00_02_0B_30 // 0.2.11.48

CodeHTTP1FieldDuplicated identifies a second Authorization or Host field in one request.

View Source
const CodeHTTP1HeadTooLarge errs.Code = 0x00_02_0B_32 // 0.2.11.50

CodeHTTP1HeadTooLarge identifies a request head past the profile's head bound or its field-line count.

View Source
const CodeHTTP1MethodUnsupported errs.Code = 0x00_02_0B_35 // 0.2.11.53

CodeHTTP1MethodUnsupported identifies a CONNECT request: the profile opens no tunnel.

View Source
const CodeHTTP1Misconfigured errs.Code = 0x00_02_0B_37 // 0.2.11.55

CodeHTTP1Misconfigured identifies profile limits the codec refuses to interpret: a negative bound, or one past its maximum.

View Source
const CodeHTTP1RequestMalformed errs.Code = 0x00_02_0B_2D // 0.2.11.45

CodeHTTP1RequestMalformed identifies a request the restricted HTTP/1.1 profile refuses for its syntax: a bare CR or LF, a NUL or other control byte in a value, obs-fold, whitespace before a colon, a name outside tchar, or a request line that is not method, target and HTTP/1.1 (ADR 0186 §D6).

View Source
const CodeHTTP1ResponseInvalid errs.Code = 0x00_02_0B_36 // 0.2.11.54

CodeHTTP1ResponseInvalid identifies a response the profile's writer refuses to put on the wire: a status outside 200 to 999, a body on a 204 or a 304, a field name that is not a token or that the codec writes itself, or a field value with a control byte.

View Source
const CodeHTTP1TargetTooLong errs.Code = 0x00_02_0B_33 // 0.2.11.51

CodeHTTP1TargetTooLong identifies a request-target past the profile's target bound.

View Source
const CodeHTTP1TransferEncoding errs.Code = 0x00_02_0B_2E // 0.2.11.46

CodeHTTP1TransferEncoding identifies a request carrying a Transfer-Encoding field, chunked or not: the profile frames by Content-Length alone, so it carries no chunked body and no trailer.

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 CodeHandlerAmbiguous errs.Code = 0x00_02_0B_40 // 0.2.11.64

CodeHandlerAmbiguous identifies a handler the simple API cannot assign to one engine: it implements both interfaces of its entry and no Engine chooses.

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 CodeHandlerUnsupported errs.Code = 0x00_02_0B_41 // 0.2.11.65

CodeHandlerUnsupported identifies a handler no engine of the simple API's entry serves.

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 CodeLoopCapabilityRefused errs.Code = 0x00_02_0B_3C // 0.2.11.60

CodeLoopCapabilityRefused identifies a LoopValue.Capabilities entry the loop cannot opt into: an engine, a class B capability or an unknown token (ADR 0189).

View Source
const CodeLoopGroupStalled errs.Code = 0x00_02_0B_3B // 0.2.11.59

CodeLoopGroupStalled identifies a loop group every ring of which the watchdog drained while the server was serving (ADR 0188).

View Source
const CodeLoopMiddlewareInvalid errs.Code = 0x00_02_0B_3D // 0.2.11.61

CodeLoopMiddlewareInvalid identifies a loop middleware whose layer does not forward every callback to the handler it wraps.

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, BatchReplies on a stream group (ADR 0192), or, on a loop group, Use or an option neither the engine nor the handler honours (ADR 0186 §D1).

View Source
const CodeLoopOutboundFull errs.Code = 0x00_02_0B_3F // 0.2.11.63

CodeLoopOutboundFull identifies a job refused because the lane's queue to its worker is full.

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 TLS it cannot take: a raw loop handler, the loop engine speaking plaintext only (ADR 0186 §D2), or a contradiction of the http1 codec's TLS path (ADR 0198).

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 CodePacketOptionUnsupported errs.Code = 0x00_02_0B_43 // 0.2.11.67

CodePacketOptionUnsupported identifies an option a datagram group cannot honour.

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 CodePlaintextRequired errs.Code = 0x00_02_0B_42 // 0.2.11.66

CodePlaintextRequired identifies an HTTP group given neither TLS nor Plaintext.

View Source
const CodePlaintextWithTLS errs.Code = 0x00_02_0B_39 // 0.2.11.57

CodePlaintextWithTLS identifies a group given both Plaintext and TLS: the group cannot both refuse and serve TLS (ADR 0187).

View Source
const CodeProtocolsRefused errs.Code = 0x00_02_0B_3E // 0.2.11.62

CodeProtocolsRefused identifies a Protocols option, or a TLS identity's NextProtos, that an HTTP group cannot serve.

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 EnvDisable string = "KITSUNIUM_NET_DISABLE"

EnvDisable is the variable that switches engines off, a comma-separated list of capabilities the operator may switch off.

View Source
const EnvEngine string = "KITSUNIUM_NET_ENGINE"

EnvEngine is the variable that caps every group's engine at one capability of the lattice: min(code, environment), never above the code's choice.

View Source
const HTTP1DefaultBody int64 = 1 << 20

HTTP1DefaultBody is the Content-Length bound a zero HTTP1LimitsValue.Body selects: one mebibyte.

View Source
const HTTP1DefaultHead int = 8192

HTTP1DefaultHead is the head bound a zero HTTP1LimitsValue.Head selects: the request line, every field line and the blank line that ends them, in bytes.

View Source
const HTTP1DefaultTarget int = 2048

HTTP1DefaultTarget is the request-target bound a zero HTTP1LimitsValue.Target selects, in bytes.

View Source
const HTTP1MaxAccessFields int = 8

HTTP1MaxAccessFields is how many request fields an access-log link may allow: it sizes HTTP1AccessValue.Fields, so a record is a value and never a slice that grows.

View Source
const HTTP1MaxBody int64 = 1 << 30

HTTP1MaxBody is the largest Content-Length bound HTTP1LimitsValue.Body may ask for: one gibibyte, which ten decimal digits always hold.

View Source
const HTTP1MaxContentLengthDigits int = 10

HTTP1MaxContentLengthDigits is how many digits a Content-Length may carry, leading zeros included. Ten digits hold HTTP1MaxBody and stay far from the int64 limit, so the value is accumulated with no overflow to check: a longer field is refused before it is read.

View Source
const HTTP1MaxFields int = 64

HTTP1MaxFields is how many field lines one request of the restricted HTTP/1.1 profile may carry. It sizes HTTP1RequestValue.Fields, so a request is framed into a fixed array and never into a slice that grows: one more line is refused (431), never stored elsewhere.

View Source
const HTTP1MaxHead int = 65536

HTTP1MaxHead is the largest head bound HTTP1LimitsValue.Head may ask for.

View Source
const HTTP1MaxSubject int = 128

HTTP1MaxSubject is the longest subject an HTTP1AuthValue holds, in bytes. It sizes the value's array, so a subject is copied into the request and never aliases the bytes it was read from; a longer one is refused.

View Source
const LoopsAuto int = -1

LoopsAuto, set as LoopValue.Loops, runs one loop per GOMAXPROCS but one, at least one, read once at Start: a snapshot that a later change of GOMAXPROCS does not move. The P left over runs the rest of the process (ADR 0199). It is a sentinel, never the zero value, which is one loop (ADR 0187 §D9); State reports the count and "auto" as its source.

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 that
	// cannot take it: a raw loop handler — the loop engine is plaintext only (ADR
	// 0186 §D2) — or, for the http1 codec, which takes TLS on the portable backend
	// (ADR 0198), a zero identity, an explicit native backend or a loop
	// middleware; the "option" field names it.
	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)

	// LoopOutboundFull is returned by LoopOutbound.Send when the lane's
	// preallocated queue to its worker is full (R3). The job is not queued; the
	// refusal is counted in LoopStateValue.OutboundRefused.
	LoopOutboundFull = errs.Static(CodeLoopOutboundFull, "LOOP_OUTBOUND_FULL",
		"The server cannot take more offloaded work now",
		"core/net: the lane's queue to its worker 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)

	// HTTP1RequestMalformed is the refusal of a request whose syntax the profile
	// does not accept. It is answered 400 with Connection: close, and the
	// connection is closed: no handler sees the request.
	HTTP1RequestMalformed = errs.Static(CodeHTTP1RequestMalformed, "HTTP1_REQUEST_MALFORMED",
		"The request is malformed",
		"core/net: a bare CR or LF, a NUL or control byte, obs-fold, whitespace before a colon, a non-token name, or a bad request line",
		400,
		65)

	// HTTP1TransferEncoding is the refusal of any Transfer-Encoding. A
	// request that carries one alongside a Content-Length is the classic smuggling
	// vector (RFC 9112 §11.2), so the profile never chooses between them.
	HTTP1TransferEncoding = errs.Static(CodeHTTP1TransferEncoding, "HTTP1_TRANSFER_ENCODING",
		"Transfer-Encoding is not supported",
		"core/net: a Transfer-Encoding field, chunked or not; the profile frames by Content-Length alone",
		400,
		65)

	// HTTP1ContentLengthInvalid is the refusal of a Content-Length the profile
	// cannot frame by. A second one is refused even when it repeats the first:
	// two parsers disagreeing on which one counts is how a body becomes a request.
	HTTP1ContentLengthInvalid = errs.Static(CodeHTTP1ContentLengthInvalid, "HTTP1_CONTENT_LENGTH_INVALID",
		"The Content-Length is not valid",
		"core/net: an empty, non-digit, over-long or repeated Content-Length",
		400,
		65)

	// HTTP1FieldDuplicated is the refusal of a second Authorization or Host. The
	// profile hands a handler one of each, and picking one of two would let a
	// front end and this server judge different credentials or origins.
	HTTP1FieldDuplicated = errs.Static(CodeHTTP1FieldDuplicated, "HTTP1_FIELD_DUPLICATED",
		"A request field is repeated",
		"core/net: a second Authorization or Host field",
		400,
		65)

	// HTTP1ExpectRefused is the refusal of an Expect field, answered 417
	// (RFC 9110 §10.1.1).
	HTTP1ExpectRefused = errs.Static(CodeHTTP1ExpectRefused, "HTTP1_EXPECT_REFUSED",
		"The expectation cannot be met",
		"core/net: an Expect field; the profile sends no 100 Continue",
		417,
		65)

	// HTTP1HeadTooLarge is the refusal of a head past the profile's bound, in
	// bytes or in field lines, answered 431.
	HTTP1HeadTooLarge = errs.Static(CodeHTTP1HeadTooLarge, "HTTP1_HEAD_TOO_LARGE",
		"The request header is too large",
		"core/net: the head did not end within its byte bound, or carried more than HTTP1MaxFields field lines",
		431,
		65)

	// HTTP1TargetTooLong is the refusal of a request-target past the profile's
	// bound, answered 414.
	HTTP1TargetTooLong = errs.Static(CodeHTTP1TargetTooLong, "HTTP1_TARGET_TOO_LONG",
		"The request target is too long",
		"core/net: the request-target is longer than the target bound",
		414,
		65)

	// HTTP1BodyTooLarge is the refusal of a Content-Length past the profile's
	// body bound, answered 413 before a byte of the body is read.
	HTTP1BodyTooLarge = errs.Static(CodeHTTP1BodyTooLarge, "HTTP1_BODY_TOO_LARGE",
		"The request content is too large",
		"core/net: the declared Content-Length is past the body bound",
		413,
		65)

	// HTTP1MethodUnsupported is the refusal of CONNECT, answered 501: a tunnel is
	// outside the profile (ADR 0186 §D6).
	HTTP1MethodUnsupported = errs.Static(CodeHTTP1MethodUnsupported, "HTTP1_METHOD_UNSUPPORTED",
		"The request method is not supported",
		"core/net: a CONNECT request",
		501,
		65)

	// HTTP1ResponseInvalid is returned by the profile's writer for a response it
	// will not put on the wire. Nothing is appended: a half-written response on a
	// kept-alive connection would frame every response after it wrongly.
	HTTP1ResponseInvalid = errs.Static(CodeHTTP1ResponseInvalid, "HTTP1_RESPONSE_INVALID",
		"The response is not valid",
		"core/net: a status outside 200-999, a body on 204 or 304, a non-token or codec-owned field name, or a control byte in a field value",
		0,
		64)

	// HTTP1Misconfigured is returned for profile limits the codec refuses to
	// interpret rather than guess at (ADR 0031): a zero bound is the default, a
	// negative one, or one past its maximum, is refused. The "option" field names
	// which.
	HTTP1Misconfigured = errs.Static(CodeHTTP1Misconfigured, "HTTP1_MISCONFIGURED",
		"The HTTP/1.1 profile limits are not valid",
		"core/net: a negative bound, a head past HTTP1MaxHead, a target past the head, or a body past HTTP1MaxBody",
		0,
		78)

	// EngineUnsupported is returned by Start for a group whose Engine cannot serve
	// it: EngineGoroutine on a group served by HandleLoop or given Loop, EngineLoop
	// on a group served by Handle, HandleFunc or HandleHTTP or on a datagram group,
	// or a value outside the engine constants. The "engine" field names the value.
	// It is refused, never replaced by another engine (ADR 0187).
	EngineUnsupported = errs.Static(CodeEngineUnsupported, "ENGINE_UNSUPPORTED",
		"The group's engine cannot serve its handler",
		"core/net: the Engine option names an engine the group's handler or options cannot run on",
		0,
		78)

	// PlaintextWithTLS is returned by Start for a group given both Plaintext and
	// TLS, in either order. The group is never served in plaintext nor over TLS by
	// default: the contradiction is the caller's to settle (ADR 0187).
	PlaintextWithTLS = errs.Static(CodePlaintextWithTLS, "PLAINTEXT_WITH_TLS",
		"The group cannot be both plaintext and TLS",
		"core/net: a group was given both Plaintext and TLS",
		0,
		78)

	// HandlerAmbiguous is returned by Start for a simple-API group whose handler
	// implements both interfaces of its entry — http.Handler and HTTP1Handler for
	// HTTP, ConnHandler and LoopHandler for TCP — with no Engine to choose
	// (ADR 0197). The "entry" and "type" fields name them.
	HandlerAmbiguous = errs.Static(CodeHandlerAmbiguous, "HANDLER_AMBIGUOUS",
		"The handler could run on two engines; choose one with Engine",
		"core/net: the handler implements both interfaces of its entry and no Engine chooses",
		0,
		64)

	// HandlerUnsupported is returned by Start for a simple-API group whose handler
	// no engine of its entry serves: a PacketHandler given to HTTP, a LoopHandler
	// given to UDP, an HTTP handler given to TCP (its "serve" field names HTTP),
	// any other type (ADR 0197). The "entry" and "type" fields name them, on
	// every one of these refusals.
	HandlerUnsupported = errs.Static(CodeHandlerUnsupported, "HANDLER_UNSUPPORTED",
		"The handler cannot be served by this entry point",
		"core/net: no engine of the entry serves the handler's type",
		0,
		64)

	// PlaintextRequired is returned by Start for a group that serves HTTP —
	// net/http's or the http1 codec — given neither TLS nor Plaintext: a group
	// without TLS says so (ADR 0187 §D7, ADR 0197). The "group" field names it.
	PlaintextRequired = errs.Static(CodePlaintextRequired, "PLAINTEXT_REQUIRED",
		"The HTTP group needs TLS or Plaintext",
		"core/net: an HTTP group without TLS must say Plaintext",
		0,
		78)

	// PacketOptionUnsupported is returned by Start for a datagram group given an
	// option the datagram engine does not apply — anything but MaxPacketSize,
	// BatchSize, BatchReplies, Adopt, Name, Engine and Plaintext — refused rather
	// than dropped
	// (ADR 0187 §D4, ADR 0197). The "option" field names it.
	PacketOptionUnsupported = errs.Static(CodePacketOptionUnsupported, "PACKET_OPTION_UNSUPPORTED",
		"The datagram group cannot honour one of its options",
		"core/net: the datagram group was given an option it cannot honour",
		0,
		78)

	// GroupNameConflict is returned by Start for a group given a Name that is empty
	// or differs from the non-empty name it was declared with. The "group" and
	// "name" fields carry both.
	GroupNameConflict = errs.Static(CodeGroupNameConflict, "GROUP_NAME_CONFLICT",
		"The group's name is not consistent",
		"core/net: the Name option contradicts the group's declared name, or is empty",
		0,
		64)

	// LoopGroupStalled is returned by Serve once every ring of a loop group has been
	// drained by its watchdog while the server was serving, and by the server's
	// liveness check once that has lasted the stall grace (ADR 0188). The "group"
	// field names it. A process restarted with the same environment stalls the
	// same way: the runbook sets KITSUNIUM_NET_DISABLE.
	LoopGroupStalled = errs.Static(CodeLoopGroupStalled, "LOOP_GROUP_STALLED",
		"A loop group stopped accepting connections",
		"core/net: every ring of the loop group was drained by its watchdog; set KITSUNIUM_NET_DISABLE before restarting",
		0,
		69)

	// LoopMiddlewareInvalid is returned by Start when a loop middleware's layer
	// does not forward every callback to the handler it wraps: it does not
	// implement LoopLayered, its Next is not the handler it was given, or one of
	// the eight callbacks sent through a probe chain did not reach the handler.
	// The "group" and "callback" fields name it.
	LoopMiddlewareInvalid = errs.Static(CodeLoopMiddlewareInvalid, "LOOP_MIDDLEWARE_INVALID",
		"A loop middleware does not forward every callback",
		"core/net: a loop middleware's layer does not forward every callback to the next handler",
		0,
		78)

	// ProtocolsRefused is returned by Start for an HTTP group whose Protocols
	// option is empty, holds an empty identifier, repeats or names an unknown
	// one, asks for h2
	// without TLS, or contradicts its identity's NextProtos — naming an HTTP
	// version the group does not serve, or none it does — and for a group that
	// serves no http.Handler given Protocols. The "reason" field names it
	// (ADR 0195).
	ProtocolsRefused = errs.Static(CodeProtocolsRefused, "PROTOCOLS_REFUSED",
		"The group's HTTP protocols are misconfigured",
		"core/net: the group's HTTP protocols cannot be served as configured",
		0,
		78)

	// LoopCapabilityRefused is returned by Start for a loop group whose
	// LoopValue.Capabilities names something it cannot opt into: an engine, a
	// class B capability, which stays off until BENCH.md shows its threshold, or a
	// token outside the registry. The "capability" field names it. It is refused,
	// never ignored (ADR 0189).
	LoopCapabilityRefused = errs.Static(CodeLoopCapabilityRefused, "LOOP_CAPABILITY_REFUSED",
		"The loop group asks for a capability it cannot opt into",
		"core/net: LoopValue.Capabilities names an engine, a class B capability or an unknown token",
		0,
		78)
)

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 Capability added in v0.26.20

type Capability string

Capability names one engine of the lattice the operator's switches restrict (ADR 0188), iouring > epoll > portable > goroutine, or one optional kernel capability a loop group probes (ADR 0189). Its values are the tokens KITSUNIUM_NET_ENGINE and KITSUNIUM_NET_DISABLE take; these blocks are the registry they are parsed against, and any other token is unknown. KITSUNIUM_NET_ENGINE takes an engine only.

const (
	// CapabilityIOURing is io_uring, the top of the lattice. Switched off, or capped
	// below, no loop group calls io_uring_setup.
	CapabilityIOURing Capability = "iouring"
	// CapabilityEpoll is epoll, below io_uring. Switched off, a loop group that would
	// have fallen back to it runs on the portable backend.
	CapabilityEpoll Capability = "epoll"
)

The engines an operator may switch off with KITSUNIUM_NET_DISABLE (ADR 0188).

const (
	// CapabilityPortable is the portable loop backend, the loop family's floor on
	// every platform, and what an unknown token caps the engine at.
	CapabilityPortable Capability = "portable"
	// CapabilityGoroutine is the goroutine engine, the bottom of the lattice. A loop
	// handler capped at it runs on the portable backend, the lowest engine that can
	// serve one.
	CapabilityGoroutine Capability = "goroutine"
)

The floors of the lattice, which KITSUNIUM_NET_ENGINE may name and nothing switches off (ADR 0188).

const (
	// CapabilityURingMultishotRecv is io_uring's multishot receive. Switched off,
	// every receive is single-shot and re-armed after each completion.
	CapabilityURingMultishotRecv Capability = "uring-multishot-recv"
	// CapabilityURingPbuf is io_uring's provided-buffer ring. The io_uring backend
	// receives into nothing else: switched off, io_uring is never set up and the group
	// runs on epoll.
	CapabilityURingPbuf Capability = "uring-pbuf"
	// CapabilityURingPbufInc is the provided-buffer ring consumed incrementally
	// (IOU_PBUF_RING_INC, 6.12): a buffer serves recv after recv, each from where the
	// last stopped (ADR 0204). Switched off, or refused by the kernel, each recv takes
	// a whole buffer.
	CapabilityURingPbufInc Capability = "uring-pbuf-inc"
	// CapabilityURingDirect is io_uring's direct descriptors, chosen when the release
	// check passes (a kernel from 6.13). Switched off, connections live in the process
	// table and the release check does not run.
	CapabilityURingDirect Capability = "uring-direct"
	// CapabilityEpollExclusive is EPOLLEXCLUSIVE on every loop's listener
	// registration. Switched off, every loop's epoll is woken by each connection.
	CapabilityEpollExclusive Capability = "epoll-exclusive"
	// CapabilityTCPNoDelay is TCP_NODELAY on the listeners a loop group accepts on,
	// inherited by every accepted socket. Switched off, it is not set.
	CapabilityTCPNoDelay Capability = "tcp-nodelay"
)

The class A-kernel capabilities, on once probed, which KITSUNIUM_NET_DISABLE may switch off (ADR 0189).

const (
	// CapabilityURingDeferTaskrun is IORING_SETUP_DEFER_TASKRUN with SINGLE_ISSUER on
	// the ring a loop's thread waits on; the other rings keep COOP_TASKRUN.
	CapabilityURingDeferTaskrun Capability = "uring-defer-taskrun"
	// CapabilityURingNoSQArray is IORING_SETUP_NO_SQARRAY: the SQ indexes the SQEs
	// directly, with no indirection array.
	CapabilityURingNoSQArray Capability = "uring-no-sqarray"
	// CapabilityURingRingFDs registers each ring's descriptor with its thread
	// (IORING_REGISTER_RING_FDS), at most 16 a thread, and unregisters it before the
	// ring closes.
	CapabilityURingRingFDs Capability = "uring-ring-fds"
	// CapabilityURingSQRewind is IORING_SETUP_SQ_REWIND with NO_SQARRAY: every
	// submission is filled from SQE 0. It needs the kernel 7.0 lane before it may
	// leave class A-code.
	CapabilityURingSQRewind Capability = "uring-sq-rewind"
	// CapabilityURingRecvBundle is io_uring's receive bundle (IORING_RECVSEND_BUNDLE,
	// 6.10): one completion fills as many provided buffers as the bytes waiting need
	// (ADR 0205). Opted into, on only where a probe recv fills two buffers — not with
	// incremental consumption before 6.15; otherwise each completion fills one buffer.
	CapabilityURingRecvBundle Capability = "uring-recv-bundle"
)

The class A-code capabilities, opt-in, which KITSUNIUM_NET_DISABLE may switch off (ADR 0189).

const (
	// CapabilityURingSendZC is io_uring's zero-copy send.
	CapabilityURingSendZC Capability = "uring-send-zc"
	// CapabilityURingBundle is io_uring's send bundles; the receive bundle is
	// CapabilityURingRecvBundle's (ADR 0205).
	CapabilityURingBundle Capability = "uring-bundle"
	// CapabilityBusyPoll is busy polling of the network device's queue.
	CapabilityBusyPoll Capability = "busy-poll"
	// CapabilityURingZCRX is io_uring's zero-copy receive.
	CapabilityURingZCRX Capability = "uring-zcrx"
	// CapabilityReuseportCBPF is a classic BPF program steering SO_REUSEPORT
	// connections to their loop.
	CapabilityReuseportCBPF Capability = "reuseport-cbpf"
	// CapabilityTCPDeferAccept is TCP_DEFER_ACCEPT on the listeners.
	CapabilityTCPDeferAccept Capability = "tcp-defer-accept"
	// CapabilityURingMinTimeout is io_uring's minimum wait (min_wait_usec), only where
	// the kernel advertises IORING_FEAT_MIN_TIMEOUT.
	CapabilityURingMinTimeout Capability = "uring-min-timeout"
)

The class B capabilities, declared, not built, which KITSUNIUM_NET_DISABLE may name (ADR 0189).

type CapabilityValue added in v0.26.28

type CapabilityValue struct {
	// Name is the capability.
	Name Capability `json:"name"`
	// Class is "a-kernel", "a-code" or "b".
	Class string `json:"class"`
	// Status is "on" (probed and in use), "available" (probed, not asked for),
	// "absent" (probed, the kernel lacks it), "missing" (a class A-code
	// capability the code opted into and the kernel lacks: a fault),
	// "disabled" (switched off by KITSUNIUM_NET_DISABLE or an unknown token,
	// never probed) or "unprobed" (the backend the group runs on has no use for
	// it).
	Status string `json:"status"`
	// Reason names the probe stage and the kernel's answer, or the switch.
	Reason string `json:"reason,omitempty"`
}

CapabilityValue reports one optional kernel capability of a loop group: what its probe found and whether the group runs with it (ADR 0189). A capability the operator switched off is never probed.

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 DatagramStateValue added in v0.26.31

type DatagramStateValue struct {
	// TruncatedPackets is the number of messages dropped because the kernel cut
	// them: a coalesced message past its buffer (MSG_TRUNC under UDP GRO), or a
	// message whose control data did not fit (MSG_CTRUNC). Neither can be split
	// into whole datagrams, so none of what survives is delivered.
	TruncatedPackets uint64 `json:"truncated_packets"`
	// GSOOversized is the number of GSO sends the kernel refused with EMSGSIZE:
	// a sizing bug, counted, which leaves GSO on.
	GSOOversized uint64 `json:"gso_oversized"`
	// ReplyFailures is the number of replies a BatchReplies group queued during
	// a batch that did not leave: Reply had already reported them sent.
	ReplyFailures uint64 `json:"reply_failures"`
}

DatagramStateValue counts what the batched datagram paths drop (ADR 0192): the messages a read cut short, and the replies a batch queued that did not leave. Each is a drop nobody above a read loop could be told of, so the counter is the report.

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 EngineValue added in v0.26.18

type EngineValue uint8

EngineValue names the engine family a group runs on, passed by Engine (ADR 0187): the goroutine engine or the loop engine. The zero value, EngineAuto, lets the handler's type choose. The backend inside the loop family is LoopValue.Backend's.

const (
	// EngineAuto lets the group's handler type choose its engine (ADR 0187): a loop
	// handler runs on the loop engine, every other handler on the goroutine engine.
	EngineAuto EngineValue = iota
	// EngineGoroutine is the default engine: one goroutine per connection on the
	// runtime's netpoller. It serves Handle, HandleFunc and HandleHTTP.
	EngineGoroutine
	// EngineLoop is the loop engine of ADR 0186. It serves HandleLoop; Loop picks its
	// backend.
	EngineLoop
)

The engine families a group may name with Engine. The zero value is EngineAuto.

func (EngineValue) String added in v0.26.18

func (e EngineValue) String() string

String renders the engine as an error field spells it.

type FallbacksValue added in v0.26.20

type FallbacksValue struct {
	// Count is the number of fallbacks since the server was built.
	Count uint64 `json:"count"`
	// LastGroup names the group of the last fallback.
	LastGroup string `json:"last_group,omitempty"`
	// LastReason is the last fallback's reason.
	LastReason string `json:"last_reason,omitempty"`
	// LastAt is when the last fallback happened; zero when none did.
	LastAt time.Time `json:"last_at"`
}

FallbacksValue counts the times a group started on a lower engine than the one its code asked for, because the better one failed, and keeps the last one (ADR 0188). An override by the environment is not a fallback.

type FaultValue added in v0.26.20

type FaultValue struct {
	// Source says what reported it: "listener", "loop", "ring", "stall",
	// "capability", "gso" or "environment".
	Source string `json:"source"`
	// Group names the group concerned; empty for the environment.
	Group string `json:"group,omitempty"`
	// Reason names the fault and, where one exists, the runbook's switch.
	Reason string `json:"reason"`
}

FaultValue reports one thing the server runs without, or cannot do, now: a degraded listener or loop group, a drained ring, a stalled loop group, or an environment token the registry does not know (ADR 0188).

type HTTP1AccessLogger added in v0.26.30

type HTTP1AccessLogger interface {
	// LogHTTP1 receives one request's record.
	LogHTTP1(rec HTTP1AccessValue)
}

HTTP1AccessLogger receives the http1 chain's access-log records. Every slice of rec aliases the request or the response and is valid only while LogHTTP1 runs: a logger that keeps a byte copies it. It runs on the lane's thread, once per request, and must not block it: a logger that writes to a file or a socket queues the record.

IFACE-PLUGIN: what a consumer writes; the link that calls it is internal/service/net/http1's.

type HTTP1AccessValue added in v0.26.30

type HTTP1AccessValue struct {
	// Lane is the request's lane.
	Lane int
	// Route is the route a router link matched, zero for none.
	Route int
	// Method is the request method.
	Method []byte
	// Path is the request's canonical path, without its query.
	Path []byte
	// Status is the answer's status code, 200 when the handler left it zero;
	// zero for an answer still deferred.
	Status int
	// BodyLen is the length of the answer's body.
	BodyLen int
	// Authenticated reports whether an authenticating link verified the request.
	Authenticated bool
	// Deferred reports whether the answer was deferred to the batch's end.
	Deferred bool
	// NFields is how many entries of Fields the record filled.
	NFields int
	// Fields holds the allowed request fields the request carried, in the order
	// the link was given their names.
	Fields [HTTP1MaxAccessFields]HTTP1FieldValue
}

HTTP1AccessValue is one request's access-log record, as the http1 chain's logging link hands it to an HTTP1AccessLogger: what the request asked, how it was routed and answered, and the request fields the link allows, by name. It never holds the Authorization field, the query or the body: the link refuses to allow a credential field, and the record has no slot for the rest. Every slice aliases the request or the response.

type HTTP1AuthValue added in v0.26.30

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

HTTP1AuthValue is what an authenticating link of the http1 chain established about a request: a subject of at most HTTP1MaxSubject bytes, held in an array, and 64 bits of claims the application gives meaning to. It is a value, so copying a request copies it, and it never aliases the bytes the credentials were read from. The zero value is no subject and no claim.

func (*HTTP1AuthValue) Claims added in v0.26.30

func (a *HTTP1AuthValue) Claims() uint64

Claims returns the claims.

func (*HTTP1AuthValue) Reset added in v0.26.30

func (a *HTTP1AuthValue) Reset()

Reset clears the subject and the claims.

func (*HTTP1AuthValue) SetClaims added in v0.26.30

func (a *HTTP1AuthValue) SetClaims(c uint64)

SetClaims sets the claims.

func (*HTTP1AuthValue) SetSubject added in v0.26.30

func (a *HTTP1AuthValue) SetSubject(b []byte) bool

SetSubject copies b into the value and reports true, or leaves the value as it is and reports false when b is longer than HTTP1MaxSubject.

func (*HTTP1AuthValue) Subject added in v0.26.30

func (a *HTTP1AuthValue) Subject() []byte

Subject returns the subject, aliasing the value: valid while the value is, and empty when none was set.

type HTTP1Batcher added in v0.26.17

type HTTP1Batcher interface {
	// EndBatch ends the batch of the lane whose index is lane: the Lane every
	// request of the batch carried.
	EndBatch(lane int)
}

HTTP1Batcher is the optional capability of an HTTP1Handler served on a loop group: the end of each batch of the loop's requests.

The loop adapter calls EndBatch once per loop iteration that served at least one request, on the loop's thread, after every request of the batch went through ServeHTTP1 and before any answer of the batch is written. A handler runs the work it batched there — one transaction for every request, say — and completes each response it deferred (HTTP1ResponseValue.Defer). Every byte the batch's requests aliased is still valid while EndBatch runs, and none is once it returns. Like every callback of the loop, it must not block the loop's thread longer than the batch's own work.

IFACE-PLUGIN: what a consumer writes beside HTTP1Handler; the adapter that calls it is internal/service/net/http1's.

type HTTP1BearerVerifier added in v0.26.30

type HTTP1BearerVerifier interface {
	// VerifyBearer reports whether token, the credentials after "Bearer ", is
	// valid, and fills auth when it is.
	VerifyBearer(token []byte, auth *HTTP1AuthValue) bool
}

HTTP1BearerVerifier verifies a Bearer token for the http1 chain's authenticating link. VerifyBearer reports whether token is valid and, when it is, sets the subject and the claims on auth, which belongs to the request: the link copies nothing back, and nothing the verifier keeps may alias token, whose bytes the connection reuses once the batch ends. It runs on the lane's thread, once per request the link judges, and must not block it.

IFACE-PLUGIN: what a consumer writes; the link that calls it is internal/service/net/http1's.

type HTTP1FieldValue added in v0.26.14

type HTTP1FieldValue struct {
	// Name is the field name, validated as an RFC 9110 token.
	Name []byte
	// Value is the field value, without its leading and trailing spaces and
	// tabs; it holds no NUL, CR, LF or other control byte but the tab.
	Value []byte
}

HTTP1FieldValue is one field line of a request, as it arrived: its name, in the case the client wrote it, and its value with the surrounding whitespace removed. Both slices alias the bytes the request was framed from.

type HTTP1Handler added in v0.26.14

type HTTP1Handler interface {
	ServeHTTP1(req *HTTP1RequestValue, res *HTTP1ResponseValue)
}

HTTP1Handler answers one request of the restricted HTTP/1.1 profile.

It sees only requests the codec framed and accepted: a refusal is answered and the connection closed before any handler runs, and no handler can turn one into an answer of its own. It fills res — the status, extra field lines and the body — and the codec writes the framing: the status line, Content-Length and Connection: close.

req is valid until ServeHTTP1 returns, and so is every byte it aliases unless the caller says longer: on the loop engine those bytes stay valid until the loop's batch ends, HTTP1Batcher.EndBatch included. A handler that keeps one past that copies it. On the loop engine a handler runs on the loop's thread and must not block it.

type HTTP1HandlerFunc added in v0.26.14

type HTTP1HandlerFunc func(req *HTTP1RequestValue, res *HTTP1ResponseValue)

HTTP1HandlerFunc adapts a plain function to HTTP1Handler.

func (HTTP1HandlerFunc) ServeHTTP1 added in v0.26.14

func (f HTTP1HandlerFunc) ServeHTTP1(req *HTTP1RequestValue, res *HTTP1ResponseValue)

ServeHTTP1 implements HTTP1Handler by calling f.

type HTTP1LimitsValue added in v0.26.14

type HTTP1LimitsValue struct {
	// Head bounds the request line, the field lines and the blank line that
	// ends them, in bytes: zero selects HTTP1DefaultHead, at most HTTP1MaxHead.
	Head int `json:"head"`
	// Target bounds the request-target, in bytes: zero selects
	// HTTP1DefaultTarget, and it may not exceed Head.
	Target int `json:"target"`
	// Body bounds the Content-Length a request may declare: zero selects
	// HTTP1DefaultBody, at most HTTP1MaxBody.
	Body int64 `json:"body"`
}

HTTP1LimitsValue bounds what one request of the restricted HTTP/1.1 profile may carry. Zero means the profile's default, never unbounded; a negative field, or one past its maximum, is refused when the codec is built.

type HTTP1RequestValue added in v0.26.14

type HTTP1RequestValue struct {
	// Method is the request method, a token.
	Method []byte
	// Target is the request-target exactly as sent, still percent-encoded.
	Target []byte
	// Host is the Host field's value; HasHost says whether it was present.
	Host []byte
	// Authorization is the Authorization field's value; HasAuthorization says
	// whether it was present.
	Authorization []byte
	// Body is the request content, Content-Length bytes after the head. It is
	// empty until the whole body has arrived.
	Body []byte
	// ContentLength is the declared body length, zero when the field is absent.
	ContentLength int64
	// HeadLen is the length of the head: the request line, the field lines and
	// the blank line. It is zero until the head is complete.
	HeadLen int
	// Consumed is HeadLen plus ContentLength: the bytes this request takes from
	// the stream, whatever its method or route. The next pipelined request
	// starts there. It is set as soon as the head is complete, before the body
	// arrives, so an early answer knows how much it has to drain.
	Consumed int
	// NFields is how many entries of Fields the request filled.
	NFields int
	// Fields holds the request's field lines in the order they arrived,
	// Host, Authorization and Content-Length included.
	Fields [HTTP1MaxFields]HTTP1FieldValue
	// HasHost reports whether the request carried a Host field.
	HasHost bool
	// HasAuthorization reports whether the request carried an Authorization
	// field.
	HasAuthorization bool
	// Close reports whether the client asked to close the connection after
	// this request: a Connection field holding the close token.
	Close bool
	// Lane is the index of the lane that framed the request, from 0 to the
	// group's lane count less one — its LoopIO.Loop — set by the loop adapter
	// before the handler runs. One lane is one thread or one goroutine, so a
	// handler finds its per-lane state in a slice sized at LoopStarter.OnStart,
	// with no lock. Parse leaves it as it is; outside the loop adapter it is
	// what the caller set.
	Lane int
	// Path is the request's canonical path, the one target every link of a
	// chain routes and judges on, computed once by the codec: the target's
	// path up to its query, an absolute-form target reduced to its path once
	// its authority matched Host. It is still percent-encoded, and holds no
	// dot-segment, no empty segment, no backslash and no encoded slash, dot,
	// backslash or NUL: the codec refuses a target that would (400). An
	// asterisk-form target, OPTIONS only, is "*".
	Path []byte
	// Query is the target's query, after its "?" and without it; empty when
	// the target has none.
	Query []byte
	// Route is the identifier of the route a router link matched, zero for
	// none. Parse resets it to zero; only a link sets it.
	Route int
	// Auth is what an authenticating link established: the subject and the
	// claims, copied by value into the request, so nothing of them aliases
	// bytes the connection reuses. Parse resets it; Authenticated says
	// whether a link set it.
	Auth HTTP1AuthValue
	// Authenticated reports whether an authenticating link verified the
	// request's credentials and filled Auth. Parse resets it to false.
	Authenticated bool
}

HTTP1RequestValue is one request of the restricted HTTP/1.1 profile, framed by the codec (internal/service/net/http1).

It is not named RequestValue: that name is the outbound request the Policy port judges, and the two never meet. The profile's prefix is the domain's way of naming a protocol's value, as SSEEventValue and WSMessageValue do.

Every slice aliases the bytes the codec read: the value is valid while those bytes are, which on the loop engine is until OnBatchEnd returns. A handler that keeps a byte past that copies it. Fields is an array, so a value framed into a reused HTTP1RequestValue allocates nothing. Auth is a value, not a slice: what a link established survives the bytes it was read from.

A link of a chain may read every field and set Route, Auth and Authenticated; writing Consumed, HeadLen or Body changes nothing, since the adapter frames with what Parse returned, never with what a link left.

func (*HTTP1RequestValue) Field added in v0.26.14

func (r *HTTP1RequestValue) Field(name string) ([]byte, bool)

Field returns the value of the first field named name and true, or nil and false when the request carries none. Names compare case-insensitively on A–Z only, so no locale or Unicode folding can make two names equal.

type HTTP1ResponseValue added in v0.26.14

type HTTP1ResponseValue struct {
	// Status is the status code, 200 to 999; zero means 200. The profile
	// sends no interim response.
	Status int
	// Header holds the response's extra field lines, each written as
	// "Name: value\r\n" by the codec's writer. It never holds Content-Length or
	// Connection, which the codec writes.
	Header []byte
	// Body is the response content.
	Body []byte
	// Close asks the codec to close the connection after this response.
	Close bool
	// Defer, set by the handler, holds the answer back until the end of the
	// loop's batch, for work it batches — one database transaction for every
	// request of the batch, say. The response value then stays valid, at the
	// same address, until HTTP1Batcher.EndBatch returns, and the handler
	// completes it there: it fills it and clears Defer. An answer still
	// deferred when EndBatch returns is answered 500 and its connection
	// closed. Only the loop adapter reads it; AppendResponse does not.
	Defer bool
	// Pending, set by the handler, hands the answer to the lane's worker
	// (HTTP1Worker, R3): the loop adapter sends Job to it and writes the
	// answer the worker fills, from the loop's thread, once it comes back.
	// Answers after it on the same connection wait for it, in order. A queue
	// to the worker that is full answers 503 with Retry-After at once, the
	// connection kept; a connection that closes first drops the answer. Pending
	// with Defer, or with no worker, is answered 500 and the connection closed.
	// Only the loop adapter reads it.
	Pending bool
	// Job is the integer a pending answer's worker is given: the handler's own
	// index of what the work needs, which it copied out of the request, since
	// the request's bytes do not outlive the batch.
	Job uint64
}

HTTP1ResponseValue is the answer an HTTP1Handler gives to one request.

The handler sets the status, appends any field lines to Header with the codec's writer and the content to Body. The codec writes the status line, the Content-Length computed from Body and Connection: close when it is needed: the handler never spells the response's framing, so it cannot get it wrong.

type HTTP1Worker added in v0.26.33

type HTTP1Worker interface {
	// WorkHTTP1 fills res, the answer to the pending request of lane whose Job
	// was job.
	WorkHTTP1(lane int, job uint64, res *HTTP1ResponseValue)
}

HTTP1Worker is the optional capability of an HTTP1Handler served on a loop group that answers off the loop's thread (R3): a response the handler marks Pending is handed to the lane's worker goroutine, which calls WorkHTTP1 with the response's Job and a response value of its own to fill; the loop adapter writes it, from the loop's thread, once WorkHTTP1 returns. WorkHTTP1 may block. It reads nothing of the request, whose bytes do not outlive the batch: the handler copied what the work needs and named it by job.

IFACE-PLUGIN: what a consumer writes beside HTTP1Handler; the adapter that calls it is internal/service/net/http1's.

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"`
	// Exclusive reports that no other socket can bind the listener's address
	// while it lives: SO_EXCLUSIVEADDRUSE on Windows, set on a unicast TCP or
	// UDP socket this process binds and read back, never set, on an adopted
	// one. It is false on every other platform, which has no such option.
	Exclusive bool `json:"exclusive"`
	// BindWait is how long the bind waited for its address to come free: on
	// Windows, a restarted server's address stays taken until the previous
	// process's connections end, and the bind is retried, bounded. Zero when
	// the first attempt bound.
	BindWait DurationValue `json:"bind_wait"`
}

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
	// Loop returns the index of the loop this LoopIO belongs to, from 0 to the
	// group's lane count less one, fixed for the loop's life: its lane. A
	// handler keeps its per-lane state — a scratch, a batch, a buffer — in a
	// slice indexed by it and sized at LoopStarter.OnStart, so a callback finds
	// its own with no lock and no map. The portable backend runs LoopValue.Loops
	// lanes too, one handler goroutine each. Any goroutine may call it.
	Loop() int
}

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 LoopJobValue added in v0.26.33

type LoopJobValue struct {
	// Ref is the connection the job answers.
	Ref LoopConnRef
	// Slot is the sender's index of the job's state.
	Slot uint32
	// Gen is the generation of that slot when the job was sent.
	Gen uint32
	// Arg is the sender's own argument.
	Arg uint64
}

LoopJobValue is one job a lane sends its worker through LoopOutbound (R3): the connection it answers, by its full reference, the slot the sender keeps it in with that slot's own generation, and an integer the sender gives meaning to. It is a value with no pointer, so the queue that carries it holds nothing the garbage collector must scan, and a completion that comes back after its connection closed finds a stale reference, never another connection.

type LoopLayer added in v0.26.29

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

LoopLayer is the base of a loop middleware's layer: it forwards the eight callbacks — OnOpen, OnData, OnTimeout, OnWritable, OnBatchEnd, OnClose, Honours and OnStart — to the handler it wraps, and Next returns it. A layer embeds it, built by NewLoopLayer, and overrides the callbacks it acts on; an override forwards the callback in turn, through the embedded LoopLayer, unless the layer answers it itself — a refused connection's bytes, say. Honours answers for the wrapped handler, which a LoopOptionHonourer answers and any other handler does not; OnStart reaches a LoopStarter and stops at any other handler.

func NewLoopLayer added in v0.26.29

func NewLoopLayer(next LoopHandler) LoopLayer

NewLoopLayer returns the base of a loop middleware's layer over next: the LoopLayer a layer embeds, forwarding every callback to next.

func (LoopLayer) Honours added in v0.26.29

func (l LoopLayer) Honours(k OptionKind) bool

Honours reports whether the wrapped handler honours the option of kind k: its own Honours when it is a LoopOptionHonourer, false otherwise.

func (LoopLayer) Next added in v0.26.29

func (l LoopLayer) Next() LoopHandler

Next returns the handler the layer wraps.

IFACE-PLUGIN: the wrapped handler is whatever the next layer, or the consumer, wrote.

func (LoopLayer) OnBatchEnd added in v0.26.29

func (l LoopLayer) OnBatchEnd(io LoopIO)

OnBatchEnd forwards to the wrapped handler.

func (LoopLayer) OnClose added in v0.26.29

func (l LoopLayer) OnClose(io LoopIO, ref LoopConnRef, err error)

OnClose forwards to the wrapped handler.

func (LoopLayer) OnData added in v0.26.29

func (l LoopLayer) OnData(io LoopIO, ref LoopConnRef, buf []byte) int

OnData forwards to the wrapped handler and returns what it consumed.

func (LoopLayer) OnOpen added in v0.26.29

func (l LoopLayer) OnOpen(io LoopIO, ref LoopConnRef)

OnOpen forwards to the wrapped handler.

func (LoopLayer) OnStart added in v0.26.29

func (l LoopLayer) OnStart(lanes int)

OnStart forwards to the wrapped handler when it is a LoopStarter.

func (LoopLayer) OnTimeout added in v0.26.29

func (l LoopLayer) OnTimeout(io LoopIO, ref LoopConnRef)

OnTimeout forwards to the wrapped handler.

func (LoopLayer) OnWritable added in v0.26.29

func (l LoopLayer) OnWritable(io LoopIO, ref LoopConnRef)

OnWritable forwards to the wrapped handler.

type LoopLayered added in v0.26.29

type LoopLayered interface {
	// Next returns the handler the layer wraps.
	Next() LoopHandler
}

LoopLayered is what every layer a loop middleware returns implements: Next is the handler the layer wraps. A layer embeds LoopLayer, which forwards the eight callbacks — the six of LoopHandler, Honours and OnStart — to Next, and overrides the ones it acts on, forwarding them in turn. At Start the server walks the chain by Next and sends each callback through a probe chain; a layer that does not implement LoopLayered, or does not forward a callback, fails Start with LoopMiddlewareInvalid. The chain honours an option when one of its layers or the handler does.

type LoopOffloader added in v0.26.33

type LoopOffloader interface {
	// Worker returns the worker the group's lanes offload to; nil offloads
	// nothing.
	Worker() LoopWorker
	// OnOutbound gives the offloader its lanes' outbounds, once.
	OnOutbound(v LoopOutboundValue)
	// OnDone is called on the lane's thread with each job its worker ran.
	OnDone(io LoopIO, job LoopJobValue)
	// Pending reports, on the lane's thread, whether ref, a connection of lane,
	// waits for an answer its worker has not given back: a draining loop keeps
	// it open until it has, and eviction passes it by.
	Pending(lane int, ref LoopConnRef) bool
}

LoopOffloader is the optional capability of a LoopHandler that offloads blocking work (R3). At Start the server walks the chain by Next for the first offloader whose Worker is not nil, starts one worker goroutine per lane, and calls OnOutbound once, after LoopStarter.OnStart and before any other callback. Each job a lane sends comes back through OnDone, on that lane's thread, once Work has returned; a job whose connection closed meanwhile comes back too, so the offloader frees what it kept for it. A draining loop keeps open every connection Pending names. The workers stop after the loop engine has: on Shutdown once the drain is over, queued jobs then dropped.

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 LoopOutbound added in v0.26.33

type LoopOutbound interface {
	// Send queues job for the lane's worker, or refuses with LoopOutboundFull.
	Send(io LoopIO, job LoopJobValue) error
}

LoopOutbound is one lane's queue to its worker (R3), preallocated (LoopValue.Outbound). Send is called on the lane's thread only, with the lane's LoopIO, and never blocks: it queues the job and wakes the worker, or refuses with LoopOutboundFull, queueing nothing.

IFACE-PLUGIN: the queue is internal/service/net/server's.

type LoopOutboundValue added in v0.26.33

type LoopOutboundValue struct {
	// Lanes are the lanes' outbounds, by lane.
	Lanes []LoopOutbound
	// OutputCap is LoopValue.OutputCap, its default applied.
	OutputCap int
}

LoopOutboundValue is what a loop group gives its offloader at Start, once, after LoopStarter.OnStart and before any other callback: one outbound per lane, indexed by LoopIO.Loop, and the output ceiling of one connection (LoopValue.OutputCap, its default applied), which bounds what an offloader holds back for a connection while one of its answers is pending.

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 LoopStarter added in v0.26.29

type LoopStarter interface {
	// OnStart is called once, before any other callback, with the number of
	// lanes: every LoopIO.Loop the group's callbacks see is from 0 to lanes
	// less one.
	OnStart(lanes int)
}

LoopStarter is the optional capability of a LoopHandler that keeps state per lane: OnStart tells it how many lanes the group runs, once, before any other callback, so it sizes a slice indexed by LoopIO.Loop and no callback grows one. A lane is one loop thread on a native backend, one handler goroutine on the portable backend; its count is LoopValue.Loops, zero meaning one, on every backend, a fallback from a native backend to the portable one included.

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"`
	// LoopsSource says where Loops came from: "explicit", "auto" — LoopsAuto,
	// GOMAXPROCS read at Start — or "single" when Loops was zero.
	LoopsSource string `json:"loops_source"`
	// 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"`
	// OutboundRefused counts the jobs a full queue to a worker refused
	// (LoopOutboundFull), every lane's: for the http1 adapter, the 503s it
	// answered for them. Zero when the group offloads nothing.
	OutboundRefused uint64 `json:"outbound_refused"`
	// RingStates reports every ring of every loop, loop by loop.
	RingStates []LoopRingStateValue `json:"ring_states"`
	// Capabilities reports every optional kernel capability the group's backend
	// probed or was told not to, the server's socket probes merged in
	// (ADR 0189).
	Capabilities []CapabilityValue `json:"capabilities,omitempty"`
}

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; LoopsAuto selects GOMAXPROCS less
	// one, at least one, read once at Start. The SDK never sets GOMAXPROCS:
	// sizing the process against its loops is the consumer's policy, and State
	// reports both. A native loop yields to the Go scheduler every 5 ms of
	// serving while GOMAXPROCS exceeds the group's loops (ADR 0199).
	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"`
	// Capabilities opts the group into kernel capabilities of class A-code
	// (ADR 0189). Each is probed and used where the kernel has it, reported
	// "missing" and as a fault where it does not, and never used once
	// KITSUNIUM_NET_DISABLE names it. An engine, a class B capability or an
	// unknown token is refused at Start with LoopCapabilityRefused. Nil asks
	// for none; the class A-kernel capabilities need no opt-in.
	Capabilities []Capability `json:"capabilities,omitempty"`
	// Outbound is how many jobs one lane's queue to its worker holds (R3),
	// when the group's handler offloads (LoopOffloader). A Send past it is
	// refused with LoopOutboundFull and counted in LoopStateValue. Zero
	// selects 256.
	Outbound int `json:"outbound"`
	// BatchWait bounds how long an io_uring loop holds its wait for
	// BatchWaitMin completions while it is loaded: its previous batch reaped
	// at least four, or two receives carrying bytes at least come within the
	// bound at the rate they arrive. A loaded loop so serves larger batches for fewer
	// wakeups (ADR 0199, ADR 0202). It
	// bounds each grouped wait, not a request's latency: a request read over
	// several batches may meet one at each. Zero selects 250 µs,
	// a negative value turns the grouped wait off, and anything past the 10 ms
	// tick is the tick. epoll and the portable backend ignore it.
	BatchWait DurationValue `json:"batch_wait"`
	// BatchWaitMin is how many completions the grouped wait waits for before
	// BatchWait runs out. Zero selects 32; past CQBatch it is CQBatch.
	BatchWaitMin int `json:"batch_wait_min"`
}

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 LoopWorker added in v0.26.33

type LoopWorker interface {
	// Work runs job, sent by the lane whose index is lane.
	Work(lane int, job LoopJobValue)
}

LoopWorker runs a lane's offloaded jobs (R3): Work is called on the lane's worker goroutine, one per lane, off the loop's thread, one job at a time in the order the lane sent them, and may block. When it returns, the job goes back to the lane, whose offloader's OnDone sees it on the loop's thread.

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
	// OptionPlaintext is Plaintext. Every engine honours it; with TLS it is refused
	// with PlaintextWithTLS.
	OptionPlaintext
	// OptionName is Name. Every engine honours it.
	OptionName
	// OptionEngine is Engine. Every engine honours it, or refuses the group with
	// EngineUnsupported.
	OptionEngine
	// OptionLoopMiddleware is UseLoop. The loop engine honours it; every group no
	// loop handler serves refuses it with LoopOptionUnsupported.
	OptionLoopMiddleware
	// OptionBatchReplies is BatchReplies. A datagram group honours it; a stream
	// group, goroutine or loop, refuses it with LoopOptionUnsupported (ADR 0192).
	OptionBatchReplies
	// OptionProtocols is Protocols. An HTTP group served by net/http honours
	// it, and so does the http1 codec given TLS when it names "http/1.1"
	// alone; any other list for that codec is refused with ProtocolsRefused.
	// Any other loop group — the http1 codec without TLS included — refuses
	// it with LoopOptionUnsupported unless its handler or a layer honours it
	// (LoopOptionHonourer), and any other group with ProtocolsRefused.
	OptionProtocols
)

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 OverrideValue added in v0.26.20

type OverrideValue struct {
	// Source is "environment" when KITSUNIUM_NET_ENGINE or KITSUNIUM_NET_DISABLE
	// is set to a non-empty value, and empty otherwise.
	Source string `json:"source,omitempty"`
	// Raw is what was read, each variable set as NAME=value, separated by a
	// space, in the order EnvEngine, EnvDisable.
	Raw string `json:"raw,omitempty"`
	// Applied is the highest engine the environment lets a group run on, after
	// both variables; empty when nothing is set. A group runs on
	// min(its code's choice, Applied), skipping what Disabled names.
	Applied Capability `json:"applied,omitempty"`
	// Disabled lists the engines KITSUNIUM_NET_DISABLE switched off.
	Disabled []Capability `json:"disabled,omitempty"`
}

OverrideValue reports what the operator's environment did to the engines, read once, before any probe (ADR 0188). The zero value is "no override".

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 PlatformValue added in v0.26.20

type PlatformValue struct {
	// GOOS is runtime.GOOS.
	GOOS string `json:"goos"`
	// GOARCH is runtime.GOARCH.
	GOARCH string `json:"goarch"`
	// Kernel is the operating system's release as the platform reports it: the
	// uname release on Linux, kern.osrelease on darwin and the BSDs,
	// major.minor.build as GetVersion reports it on Windows; empty when it cannot
	// be read, and on illumos and Solaris, where the standard library reads none.
	Kernel string `json:"kernel,omitempty"`
	// Go is runtime.Version(), the toolchain the binary was built with.
	Go string `json:"go"`
}

PlatformValue names what the server runs on, so a report says which kernel a probe ran against (ADR 0188).

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"`
	// Datagrams counts what the batched datagram paths drop (ADR 0192).
	Datagrams DatagramStateValue `json:"datagrams"`
	// Loops reports every loop group (ADR 0186 §D8). It is empty while the
	// server serves none.
	Loops []LoopStateValue `json:"loops,omitempty"`
	// Override reports what KITSUNIUM_NET_ENGINE and KITSUNIUM_NET_DISABLE did
	// (ADR 0188).
	Override OverrideValue `json:"override"`
	// Faults lists everything the server runs without now, the degradations of
	// Listeners and Loops included; Degraded is true when it is not empty.
	Faults []FaultValue `json:"faults,omitempty"`
	// Fallbacks counts the engine fallbacks and keeps the last one.
	Fallbacks FallbacksValue `json:"fallbacks"`
	// Platform names the operating system, its release and the toolchain.
	Platform PlatformValue `json:"platform"`
}

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 the server runs with any fault — a listener or loop group that fell back from a requested optimisation, a drained ring, a stalled loop group, an unknown environment token — so a caller can assert "no silent degradation" in one call. It reads Faults, and still the Degraded flags of Listeners and Loops for a value built by hand (ADR 0188).

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