1package daemonmux
2
3import (
4 "errors"
5
6 "github.com/bnema/vev/internal/domain"
7 "github.com/bnema/vev/internal/ports"
8 "github.com/bnema/vev/internal/protocol"
9)
10
11var (
12 // ErrWrongDirection reports a daemonmux message presented to the wrong
13 // directional codec. Direction is a wire property of the envelope oneof:
14 // a client envelope never decodes as daemon output and vice versa.
15 ErrWrongDirection = errors.New("daemonmux: wrong message direction")
16 // ErrInvalidMessage reports a daemonmux message that fails semantic
17 // validation: bad stream identity, text, taxonomy, bound, environment,
18 // policy, or error detail.
19 ErrInvalidMessage = errors.New("daemonmux: invalid message")
20 // ErrTooLarge reports a stateless bound refusal established by limits.go:
21 // an envelope, chunk, environment, policy token, or error text above its
22 // ceiling.
23 ErrTooLarge = errors.New("daemonmux: message exceeds bound")
24 errConvertRange = errors.New("daemonmux: wire value out of semantic range")
25)
26
27// ClientMessage is the closed set of broker-to-daemon daemonmux messages.
28// Every variant converts to exactly one MuxClientEnvelope payload.
29type ClientMessage interface{ muxClientMessage() }
30
31// ServerMessage is the closed set of daemon-to-broker daemonmux messages.
32// Every variant converts to exactly one MuxServerEnvelope payload.
33type ServerMessage interface{ muxServerMessage() }
34
35// PhysicalStreamID identifies one logical stream multiplexed over one
36// physical daemon connection. It is asserted by the opening side and never
37// crosses a physical connection; the engine (not this codec) enforces that a
38// connection's IDs are strictly increasing, so the codec only rejects zero.
39type PhysicalStreamID uint64
40
41// Validate rejects the zero value: a stream is never routed without an
42// explicit physical identity.
43func (id PhysicalStreamID) Validate() error {
44 if id == 0 {
45 return errors.New("daemonmux: physical stream ID is zero")
46 }
47 return nil
48}
49
50// StreamRef is the full identity of one multiplexed logical stream: the
51// mux-local physical stream, the broker epoch and client connection that own
52// it, and the broker client stream it carries. It is carried by MuxOpen,
53// MuxOpened, and MuxRefused only; later frames are routed by the physical
54// stream ID alone.
55type StreamRef struct {
56 Physical PhysicalStreamID
57 Epoch ports.BrokerEpoch
58 Connection ports.BrokerConnectionID
59 Client ports.BrokerStreamID
60}
61
62// Validate enforces the complete reference: a nonzero physical and broker
63// stream, a nonzero epoch, and a nonzero 16-byte connection identity.
64func (r StreamRef) Validate() error {
65 if err := r.Physical.Validate(); err != nil {
66 return err
67 }
68 if r.Epoch == 0 {
69 return errors.New("daemonmux: stream ref has no broker epoch")
70 }
71 if err := r.Connection.Validate(); err != nil {
72 return err
73 }
74 return r.Client.Validate()
75}
76
77// Open asks the daemon to open one independently cancellable logical stream
78// over the physical connection. Env is the per-request session environment
79// and is never inherited from the broker process environment. StartMode is
80// the broker's explicit daemon-start authorization, propagated unchanged so a
81// transport can never widen it into a spawn.
82type Open struct {
83 Ref StreamRef
84 Purpose ports.BrokerStreamPurpose
85 Admission ports.BrokerStreamAdmission
86 Name string
87 Local bool
88 Endpoint string
89 Registration domain.RemoteRegistration
90 Target protocol.ExactSessionTarget
91 Env []string
92 Policy ports.BrokerPolicy
93 StartMode ports.BrokerDaemonStartMode
94}
95
96func (Open) muxClientMessage() {}
97
98// Data carries one opaque stream frame. It travels in both directions over
99// the same wire payload.
100type Data struct {
101 Physical PhysicalStreamID
102 Data []byte
103}
104
105func (Data) muxClientMessage() {}
106func (Data) muxServerMessage() {}
107
108// Close ends one logical stream. It travels in both directions over the same
109// wire payload.
110type Close struct {
111 Physical PhysicalStreamID
112}
113
114func (Close) muxClientMessage() {}
115func (Close) muxServerMessage() {}
116
117// Reset aborts one logical stream. It travels in both directions over the
118// same wire payload. HasError=false means no detail at all: encode refuses a
119// nonzero Error rather than silently dropping it.
120type Reset struct {
121 Physical PhysicalStreamID
122 Error ErrorDetail
123 HasError bool
124}
125
126func (Reset) muxClientMessage() {}
127func (Reset) muxServerMessage() {}
128
129// WindowUpdate returns receive credit for one stream: the receiver consumed
130// Data frames worth Credit credit bytes (payload plus MuxChunkCreditOverhead
131// per frame, see chunkCredit), so the sender may spend that many more. It
132// travels in both directions over the same wire payload.
133type WindowUpdate struct {
134 Physical PhysicalStreamID
135 Credit uint64
136}
137
138func (WindowUpdate) muxClientMessage() {}
139func (WindowUpdate) muxServerMessage() {}
140
141// Opened confirms one logical stream is established.
142type Opened struct {
143 Ref StreamRef
144}
145
146func (Opened) muxServerMessage() {}
147
148// Refused refuses one logical stream open. The typed error is mandatory: a
149// refusal without a reason could not be classified.
150type Refused struct {
151 Ref StreamRef
152 Error ErrorDetail
153}
154
155func (Refused) muxServerMessage() {}
156
157// MaxAdmissionCode is the highest admission-refusal code carried on
158// ErrorDetail. The taxonomy is closed: 0 is none, 1 limit, 2 closed, 3 stale
159// connection or stream, 4 invalid request (mirroring
160// ports.BrokerAdmissionError).
161const MaxAdmissionCode uint32 = 4
162
163// MaxFailureKind bounds ErrorDetail.FailureKind to the domain
164// transport-failure taxonomy. The zero value (RemoteFailureNone) is valid.
165const MaxFailureKind = domain.RemoteFailureInvalidResponse
166
167// ErrorDetail is the typed daemonmux failure carried to the broker: a closed
168// error code plus bounded display text, the admission refusal (if any), and
169// the sanitized transport cause. The underlying diagnostic cause is local-only
170// and never travels on this wire.
171type ErrorDetail struct {
172 Code ports.BrokerErrorCode
173 Text string
174 // AdmissionCode is the closed admission-refusal taxonomy: 0 is none,
175 // 1 limit, 2 closed, 3 stale connection or stream, 4 invalid request.
176 AdmissionCode uint32
177 FailureKind domain.RemoteFailureKind
178}
179
180// validate enforces the transport contract of one ErrorDetail: a closed
181// error code, bounded presentation-safe text, an admission code within the
182// closed refusal taxonomy, and a failure kind within the domain taxonomy.
183func (d ErrorDetail) validate() error {
184 if err := d.Code.Validate(); err != nil {
185 return ErrInvalidMessage
186 }
187 if d.AdmissionCode > MaxAdmissionCode {
188 return ErrInvalidMessage
189 }
190 if d.FailureKind > MaxFailureKind {
191 return ErrInvalidMessage
192 }
193 return validateDisplayText(d.Text, ports.BrokerMaxErrorBytes)
194}