1package brokerconfig
2
3import (
4 "context"
5 "errors"
6
7 "github.com/bnema/vev/internal/ports"
8)
9
10// Resolver is the immutable ports.BrokerRouteAuthority over one parsed
11// offline configuration. It holds no mutable state and performs no I/O: every
12// Resolve call fences the request against the provisioned registration and
13// returns the configured identity, policy, and opaque route address. A request
14// can therefore never select an address, identity, or policy of its own.
15//
16// A remote request is fenced against its provisioned registration (endpoint,
17// incarnation, and generation) and policy. A local request carries no endpoint
18// and no registration, so it is fenced against the broker-owned local binding:
19// the configured local identity, policy, and Unix daemonmux route. Both paths
20// return authority chosen by the configuration alone, and neither can widen,
21// narrow, or replace the provisioned identity or policy.
22type Resolver struct {
23 byEndpoint map[string]Registration
24 local *LocalBinding
25}
26
27var _ ports.BrokerRouteAuthority = (*Resolver)(nil)
28
29// Resolve fences one open-stream request against the immutable configuration.
30//
31// A remote request must name a configured endpoint, carry that endpoint's
32// exact registration (endpoint, incarnation, and generation), and request a
33// policy compatible with the provisioned policy. A local request must instead
34// carry an empty endpoint and zero registration (ports enforces that) and is
35// fenced against the provisioned local binding: without one it is refused, and
36// with one its requested policy must be exactly compatible with the
37// provisioned local policy. Either way the resolved identity, policy, and
38// opaque route address come from the configuration, never from the request.
39//
40// Unknown, stale, conflicting, or unprovisioned requests are refused with a
41// typed broker error and no dial is attempted; a cancelled context is reported
42// as such so the pool classifies it as cancellation rather than unavailability.
43func (r *Resolver) ResolveDialTarget(ctx context.Context, request ports.BrokerOpenStreamRequest) (ports.BrokerDialTarget, error) {
44 if err := ctx.Err(); err != nil {
45 return ports.BrokerDialTarget{}, err
46 }
47 if request.Local {
48 return r.resolveLocal(request)
49 }
50 provisioned, ok := r.byEndpoint[request.Endpoint]
51 if !ok {
52 return ports.BrokerDialTarget{}, ports.BrokerError{Code: ports.BrokerErrorUnavailable, Text: "unknown endpoint", Cause: errUnknownEndpoint}
53 }
54 if !request.Registration.Equal(provisioned.Registration) {
55 return ports.BrokerDialTarget{}, ports.BrokerError{Code: ports.BrokerErrorUnavailable, Text: "stale registration", Cause: errStaleRegistration}
56 }
57 if !provisioned.Policy.Compatible(request.Policy) {
58 return ports.BrokerDialTarget{}, ports.BrokerError{Code: ports.BrokerErrorConflictingPolicy, Cause: errors.New("brokerconfig: request policy conflicts with the provisioned policy")}
59 }
60 resolved := ports.BrokerDialTarget{
61 Fence: ports.BrokerEndpointFence{Registration: provisioned.Registration}, Policy: provisioned.Policy, Address: provisioned.Route.Address(),
62 StartMode: request.StartMode,
63 ExpectedIdentity: ports.BrokerExpectedIdentity{Identity: provisioned.Identity, Bound: provisioned.Identity != ""},
64 }
65 if err := resolved.Validate(); err != nil {
66 return ports.BrokerDialTarget{}, ports.BrokerError{Code: ports.BrokerErrorIncompatible, Cause: err}
67 }
68 return resolved, nil
69}
70
71// resolveLocal fences one local request against the broker-owned local
72// binding. A configuration that provisions no local route refuses the request
73// as unavailable; a request whose policy is not exactly compatible with the
74// provisioned local policy is refused as a conflicting policy. The resolved
75// identity, policy, and address are the binding's own, so the request supplies
76// none of them.
77func (r *Resolver) resolveLocal(request ports.BrokerOpenStreamRequest) (ports.BrokerDialTarget, error) {
78 if r.local == nil {
79 return ports.BrokerDialTarget{}, ports.BrokerError{Code: ports.BrokerErrorUnavailable, Text: "local route is not provisioned", Cause: errNoLocalRoute}
80 }
81 if !r.local.Policy.Compatible(request.Policy) {
82 return ports.BrokerDialTarget{}, ports.BrokerError{Code: ports.BrokerErrorConflictingPolicy, Cause: errLocalPolicyConflict}
83 }
84 resolved := ports.BrokerDialTarget{
85 Fence: ports.BrokerEndpointFence{Local: true}, Policy: r.local.Policy, Address: r.local.Route.Address(),
86 StartMode: request.StartMode,
87 ExpectedIdentity: ports.BrokerExpectedIdentity{Identity: r.local.Identity, Bound: r.local.Identity != ""},
88 }
89 if err := resolved.Validate(); err != nil {
90 return ports.BrokerDialTarget{}, ports.BrokerError{Code: ports.BrokerErrorIncompatible, Cause: err}
91 }
92 return resolved, nil
93}