1// Private daemonmux QUIC carriage.
2//
3// This file exposes the explicit, mux-specific raw QUIC carriage the daemonmux
4// multiplexer (internal/adapters/daemonmux) runs over. It introduces no
5// transport and no second authentication scheme: it reuses the authenticated
6// bootstrap contracts unchanged - the ephemeral certificate and the exact
7// SHA-256 pin, the single-use 32-byte token, and the single-use nonce - and
8// hands daemonmux the connection's one bidirectional stream as a raw bounded
9// transport (wire.BoundedTransport).
10//
11// One QUIC connection is one daemonmux physical connection and therefore
12// exactly one bidirectional stream: logical daemonmux attachments are
13// multiplexed inside that stream and are never mapped to native QUIC streams.
14// The one-stream guard stays in force for the whole connection lifetime, and
15// the bootstrap credential is consumed exactly once, so one freshly minted
16// Server is the daemon half of exactly one physical connection and
17// DialMuxContext is the broker half of one.
18//
19// The caller-supplied setup context (or its deadline) bounds the whole setup:
20// the credential's wait and expiry, the pinned TLS dial, the bounded auth
21// record exchange, and the daemonmux physical handshake the caller runs
22// afterwards over the returned carriage. A rejected pin, token, or nonce
23// closes the carriage and hands nothing out. A successful carriage detaches
24// from the setup context in the daemonmux EndpointConnector, which owns the
25// physical lifetime from then on.
26//
27// The one-time token and nonce stay in the caller-held Readiness record, never
28// in the dial address: the address is composed by the caller from the resolved
29// target host plus the readiness port, exactly like the existing bootstrap
30// dial. Authorization material is absent from addresses, logs, and errors.
31
32package quic
33
34import (
35 "context"
36 "errors"
37 "time"
38
39 "github.com/bnema/vev/internal/protocol/wire"
40)
41
42// ErrMuxCarriage reports a QUIC setup that produced no raw bounded carriage, or
43// an AcceptMux call on a nil Server. The QUIC transport always implements
44// wire.BoundedTransport, so this is a local construction fault, not a peer
45// outcome; a transport that somehow lacks the capability is closed rather than
46// handed to daemonmux unbounded.
47var ErrMuxCarriage = errors.New("quic: mux carriage is not a bounded transport")
48
49// DialMuxContext establishes one authenticated raw bounded daemonmux carriage.
50// It reuses the bootstrap dial contract unchanged: the TLS handshake pins the
51// ephemeral server's exact SHA-256 certificate fingerprint, and exactly one
52// bounded token-plus-nonce auth record travels inside the encrypted single
53// stream. Nothing is handed out unless the pin matched and the record was sent;
54// a failed pin, refused record, or expired credential closes the carriage and
55// returns an error that carries no bootstrap secret.
56//
57// addr is composed by the caller from the resolved target host and the
58// readiness port. ctx (or timeout, whichever ends first) bounds resolution, the
59// pinned dial, and the auth exchange together; the daemonmux physical handshake
60// that follows shares the caller's setup context through
61// EndpointConnector.Connect, so one setup deadline covers bootstrap, dial,
62// auth, and mux handshake.
63//
64// The returned carriage owns exactly one bidirectional QUIC stream and
65// satisfies the raw framed carriage daemonmux consumes (Send, RecvBounded,
66// Close). Its lifetime is the caller's once returned: the one-stream guard
67// stays in force, so a second native stream fails the whole carriage instead of
68// becoming a logical attachment.
69func DialMuxContext(ctx context.Context, addr string, readiness Readiness, config Config, timeout time.Duration, opts ...Option) (wire.BoundedTransport, error) {
70 transport, err := Dial(ctx, addr, readiness, config, timeout, opts...)
71 if err != nil {
72 return nil, err
73 }
74 return asMuxCarriage(transport)
75}
76
77// AcceptMux waits for one authenticated peer on the bootstrap server and
78// returns its raw bounded carriage for daemonmux. It reuses the bootstrap
79// accept contract unchanged: the peer must present the single-use token and
80// nonce within the auth deadline, the token is consumed atomically, and every
81// failed or expired attempt closes its own carriage without handing anything
82// out. Because the credential is one-time, one Server is the daemon half of
83// exactly one physical daemonmux connection, so a daemon mints a fresh
84// ephemeral server per accepted physical connection.
85//
86// ctx (or the advertised credential lifetime, whichever ends first) bounds the
87// wait. A successful return transfers the carriage to the caller; daemonmux's
88// ServerSupervisor owns the physical lifetime from then on.
89func (s *Server) AcceptMux(ctx context.Context) (wire.BoundedTransport, error) {
90 if s == nil {
91 return nil, ErrMuxCarriage
92 }
93 transport, err := s.Accept(ctx)
94 if err != nil {
95 return nil, err
96 }
97 return asMuxCarriage(transport)
98}
99
100// asMuxCarriage asserts the one raw bounded capability daemonmux consumes. A
101// transport without it is closed rather than handed out unbounded.
102func asMuxCarriage(transport wire.Transport) (wire.BoundedTransport, error) {
103 bounded, ok := transport.(wire.BoundedTransport)
104 if !ok {
105 if transport != nil {
106 _ = transport.Close()
107 }
108 return nil, ErrMuxCarriage
109 }
110 return bounded, nil
111}