1package daemonmux
2
3// Shared semantic converters.
4//
5// Conversion mirrors the port, domain, and protocol types losslessly and
6// validates every narrowing numeric cast before it happens. Wire uint32
7// taxonomy fields never truncate into a smaller semantic enum: values whose
8// high bits would alias a legitimate code are refused with errConvertRange
9// before any cast. Identity lengths are exact (16-byte connection,
10// incarnation, and lifecycle values); text fields enforce UTF-8,
11// control/bidi, and byte bounds.
12//
13// The daemonmux codec deliberately imports no brokerwire symbol: it reuses
14// the generated shared wire messages and the ports/domain semantic values
15// directly, so the two conversations stay independently evolvable.
16
17import (
18 "github.com/bnema/vev/internal/adapters/protoconv"
19 "github.com/bnema/vev/internal/domain"
20 "github.com/bnema/vev/internal/ports"
21 "github.com/bnema/vev/internal/protocol"
22 "github.com/bnema/vev/internal/protocol/wire"
23)
24
25// muxEnum8 maps shared narrowing failures to the local range sentinel.
26func muxEnum8[T ~uint8](value uint32) (T, error) {
27 v, err := protoconv.Uint8[T](value)
28 if err != nil {
29 return 0, errConvertRange
30 }
31 return v, nil
32}
33
34func startModeToWire(mode ports.BrokerDaemonStartMode) (uint32, error) {
35 v, err := protoconv.BrokerStartModeToWire(mode)
36 if err != nil {
37 return 0, errConvertRange
38 }
39 return v, nil
40}
41
42func startModeFromWire(value uint32) (ports.BrokerDaemonStartMode, error) {
43 v, err := protoconv.BrokerStartModeFromWire(value)
44 if err != nil {
45 return 0, errConvertRange
46 }
47 return v, nil
48}
49
50func physicalToWire(id PhysicalStreamID) uint64 { return uint64(id) }
51
52func physicalFromWire(value uint64) (PhysicalStreamID, error) {
53 id := PhysicalStreamID(value)
54 if err := id.Validate(); err != nil {
55 return 0, errConvertRange
56 }
57 return id, nil
58}
59
60func refToWire(ref StreamRef) *wire.MuxStreamRef {
61 return &wire.MuxStreamRef{
62 PhysicalStreamId: physicalToWire(ref.Physical),
63 BrokerEpoch: uint64(ref.Epoch),
64 ConnectionId: append([]byte(nil), ref.Connection[:]...),
65 ClientStreamId: uint64(ref.Client),
66 }
67}
68
69func refFromWire(message *wire.MuxStreamRef) (StreamRef, error) {
70 var ref StreamRef
71 if message == nil {
72 return ref, errConvertRange
73 }
74 physical, err := physicalFromWire(message.GetPhysicalStreamId())
75 if err != nil {
76 return StreamRef{}, err
77 }
78 if message.GetBrokerEpoch() == 0 {
79 return StreamRef{}, errConvertRange
80 }
81 raw := message.GetConnectionId()
82 if len(raw) != len(ref.Connection) {
83 return StreamRef{}, errConvertRange
84 }
85 copy(ref.Connection[:], raw)
86 if err := ref.Connection.Validate(); err != nil {
87 return StreamRef{}, errConvertRange
88 }
89 client := ports.BrokerStreamID(message.GetClientStreamId())
90 if err := client.Validate(); err != nil {
91 return StreamRef{}, errConvertRange
92 }
93 return StreamRef{
94 Physical: physical,
95 Epoch: ports.BrokerEpoch(message.GetBrokerEpoch()),
96 Connection: ref.Connection,
97 Client: client,
98 }, nil
99}
100
101func registrationToWire(v domain.RemoteRegistration) *wire.RemoteRegistration {
102 return protoconv.BrokerRegistrationToWire(v)
103}
104func registrationFromWire(v *wire.RemoteRegistration) (domain.RemoteRegistration, error) {
105 out, err := protoconv.BrokerRegistrationFromWire(v)
106 if err != nil {
107 return domain.RemoteRegistration{}, errConvertRange
108 }
109 return out, nil
110}
111
112func exactTargetToWire(v protocol.ExactSessionTarget) *wire.ExactTarget {
113 return protoconv.BrokerExactTargetToWire(v)
114}
115
116func exactTargetFromWire(v *wire.ExactTarget) (protocol.ExactSessionTarget, error) {
117 out, err := protoconv.BrokerExactTargetFromWire(v)
118 if err != nil {
119 return protocol.ExactSessionTarget{}, errConvertRange
120 }
121 return out, nil
122}
123
124func policyToWire(v ports.BrokerPolicy) *wire.BrokerWirePolicy {
125 return protoconv.BrokerPolicyToWire(v)
126}
127
128func policyFromWire(v *wire.BrokerWirePolicy) (ports.BrokerPolicy, error) {
129 out, err := protoconv.BrokerPolicyFromWire(v)
130 if err != nil {
131 return ports.BrokerPolicy{}, mapBrokerConversionError(err)
132 }
133 return out, nil
134}
135
136func mapBrokerConversionError(err error) error {
137 switch err {
138 case protoconv.ErrOutOfRange:
139 return errConvertRange
140 case protoconv.ErrTooLarge:
141 return ErrTooLarge
142 default:
143 return ErrInvalidMessage
144 }
145}
146
147func validateDisplayText(v string, max int) error {
148 return mapOptionalBrokerError(protoconv.BrokerDisplayText(v, max))
149}
150
151func mapOptionalBrokerError(err error) error {
152 if err == nil {
153 return nil
154 }
155 return mapBrokerConversionError(err)
156}
157
158func errorDetailToWire(detail ErrorDetail) *wire.BrokerErrorDetail {
159 return &wire.BrokerErrorDetail{
160 Code: uint32(detail.Code),
161 Text: detail.Text,
162 AdmissionCode: detail.AdmissionCode,
163 FailureKind: uint32(detail.FailureKind),
164 }
165}
166
167func errorDetailFromWire(message *wire.BrokerErrorDetail) (ErrorDetail, error) {
168 var detail ErrorDetail
169 if message == nil {
170 return detail, errConvertRange
171 }
172 code, err := muxEnum8[ports.BrokerErrorCode](message.GetCode())
173 if err != nil {
174 return ErrorDetail{}, err
175 }
176 if err := code.Validate(); err != nil {
177 return ErrorDetail{}, ErrInvalidMessage
178 }
179 detail.Code = code
180 if err := validateDisplayText(message.GetText(), ports.BrokerMaxErrorBytes); err != nil {
181 return ErrorDetail{}, err
182 }
183 detail.Text = message.GetText()
184 admission := message.GetAdmissionCode()
185 if admission > MaxAdmissionCode {
186 return ErrorDetail{}, errConvertRange
187 }
188 detail.AdmissionCode = admission
189 kind, err := muxEnum8[domain.RemoteFailureKind](message.GetFailureKind())
190 if err != nil {
191 return ErrorDetail{}, err
192 }
193 if kind > MaxFailureKind {
194 return ErrorDetail{}, ErrInvalidMessage
195 }
196 detail.FailureKind = kind
197 failure := ports.BrokerError{Code: code, Text: detail.Text}
198 if err := failure.Validate(); err != nil {
199 return ErrorDetail{}, ErrInvalidMessage
200 }
201 return detail, nil
202}
203
204// checkEnvEntries keeps the local error taxonomy at the adapter boundary.
205func checkEnvEntries(env []string) error {
206 return mapOptionalBrokerError(protoconv.BrokerEnvEntries(env))
207}