1// Protobuf semantic converters.
2//
3// This file owns every semantic protocol value <-> generated wire envelope
4// conversion used by client.go/server.go dispatch. The actual field-by-field
5// conversion logic lives in internal/adapters/protoconv, the single
6// canonical implementation shared with brokerwire; the wrappers below adapt
7// protoconv's shared ErrOutOfRange (and, for the RemotePreview family,
8// protocol's own validation sentinels) to this package's local
9// errProtoConvertRange, and add the pointer/nil-tolerance policy specific to
10// sessionwire's call sites. Use cases always see uncompressed terminal data.
11package sessionwire
12
13import (
14 "errors"
15
16 renderer "github.com/bnema/vev-vt"
17 "github.com/bnema/vev/internal/adapters/protoconv"
18 "github.com/bnema/vev/internal/domain"
19 "github.com/bnema/vev/internal/protocol"
20 "github.com/bnema/vev/internal/protocol/wire"
21)
22
23var errProtoConvertRange = errors.New("sessionwire: wire value out of semantic range")
24
25func mustUint16(v uint32) (uint16, error) {
26 out, err := protoconv.Uint16[uint16](v)
27 if err != nil {
28 return 0, errProtoConvertRange
29 }
30 return out, nil
31}
32
33func mustUint8(v uint32) (uint8, error) {
34 out, err := protoconv.Uint8[uint8](v)
35 if err != nil {
36 return 0, errProtoConvertRange
37 }
38 return out, nil
39}
40
41// enum8 narrows one wire uint32 into an 8-bit semantic enum. Values whose
42// high bits would otherwise truncate into a valid enum range are refused
43// before any cast, so hostile wire input cannot alias a legitimate value.
44func enum8[T ~uint8](value uint32) (T, error) {
45 out, err := protoconv.Uint8[T](value)
46 if err != nil {
47 return 0, errProtoConvertRange
48 }
49 return out, nil
50}
51
52func mustTabIDString(value string) domain.TabStableID { return domain.TabStableID(value) }
53
54func lifecycleToWire(id domain.SessionLifecycleID) *wire.LifecycleID {
55 return protoconv.LifecycleToWire(id)
56}
57
58func lifecycleFromWire(message *wire.LifecycleID) (domain.SessionLifecycleID, error) {
59 id, err := protoconv.LifecycleFromWire(message)
60 if err != nil {
61 return domain.SessionLifecycleID{}, errProtoConvertRange
62 }
63 return id, nil
64}
65
66func exactTargetToWire(target *protocol.ExactSessionTarget) *wire.ExactTarget {
67 if target == nil {
68 return nil
69 }
70 return &wire.ExactTarget{LifecycleId: lifecycleToWire(target.LifecycleID), SessionName: target.SessionName}
71}
72
73func exactTargetFromWire(message *wire.ExactTarget) (*protocol.ExactSessionTarget, error) {
74 if message == nil {
75 return nil, nil
76 }
77 lifecycle, err := lifecycleFromWire(message.GetLifecycleId())
78 if err != nil {
79 return nil, err
80 }
81 return &protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: message.GetSessionName()}, nil
82}
83
84func rgbToWire(color renderer.RGB) *wire.RGB {
85 return protoconv.RGBToWire(color)
86}
87
88func rgbFromWire(message *wire.RGB) (renderer.RGB, error) {
89 color, err := protoconv.RGBFromWire(message)
90 if err != nil {
91 return renderer.RGB{}, errProtoConvertRange
92 }
93 return color, nil
94}
95
96func registrationToWire(registration domain.RemoteRegistration) *wire.RemoteRegistration {
97 incarnation := append([]byte(nil), registration.Incarnation[:]...)
98 return &wire.RemoteRegistration{Endpoint: registration.Endpoint, Incarnation: incarnation, Generation: uint64(registration.Generation)}
99}
100
101func registrationFromWire(message *wire.RemoteRegistration) (domain.RemoteRegistration, error) {
102 var registration domain.RemoteRegistration
103 if message == nil {
104 return registration, nil
105 }
106 registration.Endpoint = message.GetEndpoint()
107 if len(message.GetIncarnation()) != len(registration.Incarnation) {
108 return domain.RemoteRegistration{}, errProtoConvertRange
109 }
110 copy(registration.Incarnation[:], message.GetIncarnation())
111 registration.Generation = domain.RemoteGeneration(message.GetGeneration())
112 return registration, nil
113}