Merge pull request #230 from bnema/refactor/client-session-channel
refactor(client): use typed session connections
24 files · +1746 -363
A internal/adapters/sessionwire/client.go +266 -0
| @@ -0,0 +1,266 @@ | |||
| 1 | + | package sessionwire | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "errors" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/vev/internal/ports" | |
| 8 | + | "github.com/bnema/vev/internal/protocol" | |
| 9 | + | "github.com/bnema/vev/internal/protocol/wire" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | type clientConnection struct{ raw ports.Transport } | |
| 13 | + | ||
| 14 | + | var _ ports.ClientConnection = (*clientConnection)(nil) | |
| 15 | + | ||
| 16 | + | // NewClientConnection wraps one raw client connection incarnation. | |
| 17 | + | func NewClientConnection(raw ports.Transport) ports.ClientConnection { | |
| 18 | + | if raw == nil { | |
| 19 | + | return nil | |
| 20 | + | } | |
| 21 | + | return &clientConnection{raw: raw} | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | func (c *clientConnection) SendClient(message protocol.ClientMessage) error { | |
| 25 | + | frame, err := encodeClient(message) | |
| 26 | + | if err != nil { | |
| 27 | + | return err | |
| 28 | + | } | |
| 29 | + | return c.raw.Send(frame) | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | func (c *clientConnection) ReceiveServer() (protocol.ServerMessage, error) { | |
| 33 | + | frame, err := c.raw.Recv() | |
| 34 | + | if err != nil { | |
| 35 | + | return nil, err | |
| 36 | + | } | |
| 37 | + | message, err := decodeServer(frame) | |
| 38 | + | if err == nil { | |
| 39 | + | return message, nil | |
| 40 | + | } | |
| 41 | + | failure := &protocol.DecodeFailure{Category: protocol.DecodeMalformed, Type: uint8(frame.Type), Err: err} | |
| 42 | + | if errors.Is(err, ErrUnknownMessageType) { | |
| 43 | + | failure.Category = protocol.DecodeUnknownType | |
| 44 | + | } else if errors.Is(err, ErrWrongDirection) { | |
| 45 | + | failure.Category = protocol.DecodeWrongDirection | |
| 46 | + | } | |
| 47 | + | return nil, failure | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | func (c *clientConnection) Capabilities() protocol.ConnectionCapabilities { | |
| 51 | + | return rawCapabilities(c.raw) | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | func (c *clientConnection) LinkState() ports.LinkState { return rawLinkState(c.raw) } | |
| 55 | + | func (c *clientConnection) LinkEvents() <-chan ports.LinkEvent { return rawLinkEvents(c.raw) } | |
| 56 | + | func (c *clientConnection) Close() error { return c.raw.Close() } | |
| 57 | + | ||
| 58 | + | type clientDialer struct{ raw ports.Dialer } | |
| 59 | + | ||
| 60 | + | var _ ports.ClientDialer = (*clientDialer)(nil) | |
| 61 | + | ||
| 62 | + | // NewClientDialer wraps every dialed raw connection in a stable typed adapter. | |
| 63 | + | func NewClientDialer(raw ports.Dialer) ports.ClientDialer { | |
| 64 | + | if raw == nil { | |
| 65 | + | return nil | |
| 66 | + | } | |
| 67 | + | return &clientDialer{raw: raw} | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | func (d *clientDialer) Dial(ctx context.Context) (ports.ClientConnection, error) { | |
| 71 | + | raw, err := d.raw.Dial(ctx) | |
| 72 | + | if err != nil { | |
| 73 | + | return nil, err | |
| 74 | + | } | |
| 75 | + | return NewClientConnection(raw), nil | |
| 76 | + | } | |
| 77 | + | ||
| 78 | + | func decodeServer(frame wire.Frame) (protocol.ServerMessage, error) { | |
| 79 | + | switch frame.Type { | |
| 80 | + | case wire.MsgWelcome: | |
| 81 | + | return wire.UnmarshalWelcome(frame.Payload) | |
| 82 | + | case wire.MsgError: | |
| 83 | + | return wire.UnmarshalErrorMsg(frame.Payload) | |
| 84 | + | case wire.MsgOutput: | |
| 85 | + | return wire.UnmarshalOutput(frame.Payload) | |
| 86 | + | case wire.MsgDetached: | |
| 87 | + | return wire.UnmarshalDetached(frame.Payload) | |
| 88 | + | case wire.MsgPong: | |
| 89 | + | return wire.UnmarshalPong(frame.Payload) | |
| 90 | + | case wire.MsgSessions: | |
| 91 | + | return wire.UnmarshalSessions(frame.Payload) | |
| 92 | + | case wire.MsgCommandResult: | |
| 93 | + | return wire.UnmarshalCommandResult(frame.Payload) | |
| 94 | + | case wire.MsgNavigationAction: | |
| 95 | + | return wire.UnmarshalNavigationDirective(frame.Payload) | |
| 96 | + | case wire.MsgAttachTarget: | |
| 97 | + | return wire.UnmarshalAttachTarget(frame.Payload) | |
| 98 | + | case wire.MsgRemotePreviewResponse: | |
| 99 | + | return wire.UnmarshalRemotePreview(frame.Payload) | |
| 100 | + | case wire.MsgCommittedRouteIdentity: | |
| 101 | + | return wire.UnmarshalCommittedRouteIdentity(frame.Payload) | |
| 102 | + | case wire.MsgNavigateRecentRoute: | |
| 103 | + | return wire.UnmarshalRouteNavigationAction(frame.Payload) | |
| 104 | + | case wire.MsgRouteNavigationFailure: | |
| 105 | + | return wire.UnmarshalRouteNavigationFailure(frame.Payload) | |
| 106 | + | case wire.MsgRoutePosition: | |
| 107 | + | return wire.UnmarshalRoutePosition(frame.Payload) | |
| 108 | + | case wire.MsgSamePeerSwitchFailure: | |
| 109 | + | return wire.UnmarshalSamePeerSwitchFailure(frame.Payload) | |
| 110 | + | case wire.MsgParkedRouteResponse: | |
| 111 | + | return wire.UnmarshalParkedRouteResponse(frame.Payload) | |
| 112 | + | case wire.MsgHello, wire.MsgInput, wire.MsgResize, wire.MsgDetach, wire.MsgPing, | |
| 113 | + | wire.MsgList, wire.MsgKill, wire.MsgTheme, wire.MsgAck, wire.MsgImagePush, | |
| 114 | + | wire.MsgClientNotice, wire.MsgCommand, wire.MsgOutputResetRequest, | |
| 115 | + | wire.MsgRemotePreviewRequest, wire.MsgRouteAttentionSubscription, | |
| 116 | + | wire.MsgSamePeerSwitchRequest, wire.MsgParkedRouteRequest, | |
| 117 | + | wire.MsgRecentRouteSnapshot: | |
| 118 | + | return nil, ErrWrongDirection | |
| 119 | + | default: | |
| 120 | + | return nil, ErrUnknownMessageType | |
| 121 | + | } | |
| 122 | + | } | |
| 123 | + | ||
| 124 | + | func encodeClient(message protocol.ClientMessage) (wire.Frame, error) { | |
| 125 | + | switch m := message.(type) { | |
| 126 | + | case protocol.Hello: | |
| 127 | + | payload := wire.MarshalHello(m) | |
| 128 | + | if payload == nil { | |
| 129 | + | return wire.Frame{}, ErrInvalidMessage | |
| 130 | + | } | |
| 131 | + | return wire.Frame{Type: wire.MsgHello, Payload: payload}, nil | |
| 132 | + | case protocol.Input: | |
| 133 | + | return wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(m)}, nil | |
| 134 | + | case protocol.Resize: | |
| 135 | + | payload, err := wire.MarshalResize(m) | |
| 136 | + | return wire.Frame{Type: wire.MsgResize, Payload: payload}, err | |
| 137 | + | case protocol.Detach: | |
| 138 | + | return wire.Frame{Type: wire.MsgDetach, Payload: wire.MarshalDetach(m)}, nil | |
| 139 | + | case protocol.Ping: | |
| 140 | + | return wire.Frame{Type: wire.MsgPing, Payload: wire.MarshalPing(m)}, nil | |
| 141 | + | case protocol.List: | |
| 142 | + | return wire.Frame{Type: wire.MsgList, Payload: wire.MarshalList(m)}, nil | |
| 143 | + | case protocol.Kill: | |
| 144 | + | return wire.Frame{Type: wire.MsgKill, Payload: wire.MarshalKill(m)}, nil | |
| 145 | + | case protocol.Theme: | |
| 146 | + | return wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(m)}, nil | |
| 147 | + | case protocol.Ack: | |
| 148 | + | payload, err := wire.MarshalAck(m) | |
| 149 | + | return wire.Frame{Type: wire.MsgAck, Payload: payload}, err | |
| 150 | + | case protocol.ImagePush: | |
| 151 | + | return wire.Frame{Type: wire.MsgImagePush, Payload: wire.MarshalImagePush(m)}, nil | |
| 152 | + | case protocol.ClientNotice: | |
| 153 | + | if err := protocol.ValidateClientNotice(m); err != nil { | |
| 154 | + | return wire.Frame{}, err | |
| 155 | + | } | |
| 156 | + | return wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(m)}, nil | |
| 157 | + | case protocol.CommandRequest: | |
| 158 | + | payload, err := wire.MarshalCommandRequest(m) | |
| 159 | + | return wire.Frame{Type: wire.MsgCommand, Payload: payload}, err | |
| 160 | + | case protocol.OutputResetRequest: | |
| 161 | + | return wire.Frame{Type: wire.MsgOutputResetRequest, Payload: wire.MarshalOutputResetRequest(m)}, nil | |
| 162 | + | case protocol.RemotePreviewRequest: | |
| 163 | + | payload := wire.MarshalRemotePreviewRequest(m) | |
| 164 | + | if payload == nil { | |
| 165 | + | return wire.Frame{}, ErrInvalidMessage | |
| 166 | + | } | |
| 167 | + | return wire.Frame{Type: wire.MsgRemotePreviewRequest, Payload: payload}, nil | |
| 168 | + | case protocol.RouteAttentionSubscription: | |
| 169 | + | payload, err := wire.MarshalRouteAttentionSubscription(m) | |
| 170 | + | return wire.Frame{Type: wire.MsgRouteAttentionSubscription, Payload: payload}, err | |
| 171 | + | case protocol.SamePeerSwitchRequest: | |
| 172 | + | payload, err := wire.MarshalSamePeerSwitchRequest(m) | |
| 173 | + | return wire.Frame{Type: wire.MsgSamePeerSwitchRequest, Payload: payload}, err | |
| 174 | + | case protocol.ParkedRouteRequest: | |
| 175 | + | payload := wire.MarshalParkedRouteRequest(m) | |
| 176 | + | if payload == nil { | |
| 177 | + | return wire.Frame{}, ErrInvalidMessage | |
| 178 | + | } | |
| 179 | + | return wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: payload}, nil | |
| 180 | + | case protocol.RecentRouteSnapshot: | |
| 181 | + | payload, err := wire.MarshalRecentRouteSnapshot(m) | |
| 182 | + | return wire.Frame{Type: wire.MsgRecentRouteSnapshot, Payload: payload}, err | |
| 183 | + | case protocol.RouteNavigationFailure: | |
| 184 | + | payload, err := wire.MarshalRouteNavigationFailure(m) | |
| 185 | + | return wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: payload}, err | |
| 186 | + | case *protocol.Hello: | |
| 187 | + | if m != nil { | |
| 188 | + | return encodeClient(*m) | |
| 189 | + | } | |
| 190 | + | case *protocol.Input: | |
| 191 | + | if m != nil { | |
| 192 | + | return encodeClient(*m) | |
| 193 | + | } | |
| 194 | + | case *protocol.Resize: | |
| 195 | + | if m != nil { | |
| 196 | + | return encodeClient(*m) | |
| 197 | + | } | |
| 198 | + | case *protocol.Detach: | |
| 199 | + | if m != nil { | |
| 200 | + | return encodeClient(*m) | |
| 201 | + | } | |
| 202 | + | case *protocol.Ping: | |
| 203 | + | if m != nil { | |
| 204 | + | return encodeClient(*m) | |
| 205 | + | } | |
| 206 | + | case *protocol.List: | |
| 207 | + | if m != nil { | |
| 208 | + | return encodeClient(*m) | |
| 209 | + | } | |
| 210 | + | case *protocol.Kill: | |
| 211 | + | if m != nil { | |
| 212 | + | return encodeClient(*m) | |
| 213 | + | } | |
| 214 | + | case *protocol.Theme: | |
| 215 | + | if m != nil { | |
| 216 | + | return encodeClient(*m) | |
| 217 | + | } | |
| 218 | + | case *protocol.Ack: | |
| 219 | + | if m != nil { | |
| 220 | + | return encodeClient(*m) | |
| 221 | + | } | |
| 222 | + | case *protocol.ImagePush: | |
| 223 | + | if m != nil { | |
| 224 | + | return encodeClient(*m) | |
| 225 | + | } | |
| 226 | + | case *protocol.ClientNotice: | |
| 227 | + | if m != nil { | |
| 228 | + | return encodeClient(*m) | |
| 229 | + | } | |
| 230 | + | case *protocol.CommandRequest: | |
| 231 | + | if m != nil { | |
| 232 | + | return encodeClient(*m) | |
| 233 | + | } | |
| 234 | + | case *protocol.OutputResetRequest: | |
| 235 | + | if m != nil { | |
| 236 | + | return encodeClient(*m) | |
| 237 | + | } | |
| 238 | + | case *protocol.RemotePreviewRequest: | |
| 239 | + | if m != nil { | |
| 240 | + | return encodeClient(*m) | |
| 241 | + | } | |
| 242 | + | case *protocol.RouteAttentionSubscription: | |
| 243 | + | if m != nil { | |
| 244 | + | return encodeClient(*m) | |
| 245 | + | } | |
| 246 | + | case *protocol.SamePeerSwitchRequest: | |
| 247 | + | if m != nil { | |
| 248 | + | return encodeClient(*m) | |
| 249 | + | } | |
| 250 | + | case *protocol.ParkedRouteRequest: | |
| 251 | + | if m != nil { | |
| 252 | + | return encodeClient(*m) | |
| 253 | + | } | |
| 254 | + | case *protocol.RecentRouteSnapshot: | |
| 255 | + | if m != nil { | |
| 256 | + | return encodeClient(*m) | |
| 257 | + | } | |
| 258 | + | case *protocol.RouteNavigationFailure: | |
| 259 | + | if m != nil { | |
| 260 | + | return encodeClient(*m) | |
| 261 | + | } | |
| 262 | + | default: | |
| 263 | + | return wire.Frame{}, ErrWrongDirection | |
| 264 | + | } | |
| 265 | + | return wire.Frame{}, ErrInvalidMessage | |
| 266 | + | } | |
| @@ -0,0 +1,266 @@ | |||||
| 1 | + | package sessionwire | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "errors" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/vev/internal/ports" | |||
| 8 | + | "github.com/bnema/vev/internal/protocol" | |||
| 9 | + | "github.com/bnema/vev/internal/protocol/wire" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | type clientConnection struct{ raw ports.Transport } | |||
| 13 | + | ||||
| 14 | + | var _ ports.ClientConnection = (*clientConnection)(nil) | |||
| 15 | + | ||||
| 16 | + | // NewClientConnection wraps one raw client connection incarnation. | |||
| 17 | + | func NewClientConnection(raw ports.Transport) ports.ClientConnection { | |||
| 18 | + | if raw == nil { | |||
| 19 | + | return nil | |||
| 20 | + | } | |||
| 21 | + | return &clientConnection{raw: raw} | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | func (c *clientConnection) SendClient(message protocol.ClientMessage) error { | |||
| 25 | + | frame, err := encodeClient(message) | |||
| 26 | + | if err != nil { | |||
| 27 | + | return err | |||
| 28 | + | } | |||
| 29 | + | return c.raw.Send(frame) | |||
| 30 | + | } | |||
| 31 | + | ||||
| 32 | + | func (c *clientConnection) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 33 | + | frame, err := c.raw.Recv() | |||
| 34 | + | if err != nil { | |||
| 35 | + | return nil, err | |||
| 36 | + | } | |||
| 37 | + | message, err := decodeServer(frame) | |||
| 38 | + | if err == nil { | |||
| 39 | + | return message, nil | |||
| 40 | + | } | |||
| 41 | + | failure := &protocol.DecodeFailure{Category: protocol.DecodeMalformed, Type: uint8(frame.Type), Err: err} | |||
| 42 | + | if errors.Is(err, ErrUnknownMessageType) { | |||
| 43 | + | failure.Category = protocol.DecodeUnknownType | |||
| 44 | + | } else if errors.Is(err, ErrWrongDirection) { | |||
| 45 | + | failure.Category = protocol.DecodeWrongDirection | |||
| 46 | + | } | |||
| 47 | + | return nil, failure | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | func (c *clientConnection) Capabilities() protocol.ConnectionCapabilities { | |||
| 51 | + | return rawCapabilities(c.raw) | |||
| 52 | + | } | |||
| 53 | + | ||||
| 54 | + | func (c *clientConnection) LinkState() ports.LinkState { return rawLinkState(c.raw) } | |||
| 55 | + | func (c *clientConnection) LinkEvents() <-chan ports.LinkEvent { return rawLinkEvents(c.raw) } | |||
| 56 | + | func (c *clientConnection) Close() error { return c.raw.Close() } | |||
| 57 | + | ||||
| 58 | + | type clientDialer struct{ raw ports.Dialer } | |||
| 59 | + | ||||
| 60 | + | var _ ports.ClientDialer = (*clientDialer)(nil) | |||
| 61 | + | ||||
| 62 | + | // NewClientDialer wraps every dialed raw connection in a stable typed adapter. | |||
| 63 | + | func NewClientDialer(raw ports.Dialer) ports.ClientDialer { | |||
| 64 | + | if raw == nil { | |||
| 65 | + | return nil | |||
| 66 | + | } | |||
| 67 | + | return &clientDialer{raw: raw} | |||
| 68 | + | } | |||
| 69 | + | ||||
| 70 | + | func (d *clientDialer) Dial(ctx context.Context) (ports.ClientConnection, error) { | |||
| 71 | + | raw, err := d.raw.Dial(ctx) | |||
| 72 | + | if err != nil { | |||
| 73 | + | return nil, err | |||
| 74 | + | } | |||
| 75 | + | return NewClientConnection(raw), nil | |||
| 76 | + | } | |||
| 77 | + | ||||
| 78 | + | func decodeServer(frame wire.Frame) (protocol.ServerMessage, error) { | |||
| 79 | + | switch frame.Type { | |||
| 80 | + | case wire.MsgWelcome: | |||
| 81 | + | return wire.UnmarshalWelcome(frame.Payload) | |||
| 82 | + | case wire.MsgError: | |||
| 83 | + | return wire.UnmarshalErrorMsg(frame.Payload) | |||
| 84 | + | case wire.MsgOutput: | |||
| 85 | + | return wire.UnmarshalOutput(frame.Payload) | |||
| 86 | + | case wire.MsgDetached: | |||
| 87 | + | return wire.UnmarshalDetached(frame.Payload) | |||
| 88 | + | case wire.MsgPong: | |||
| 89 | + | return wire.UnmarshalPong(frame.Payload) | |||
| 90 | + | case wire.MsgSessions: | |||
| 91 | + | return wire.UnmarshalSessions(frame.Payload) | |||
| 92 | + | case wire.MsgCommandResult: | |||
| 93 | + | return wire.UnmarshalCommandResult(frame.Payload) | |||
| 94 | + | case wire.MsgNavigationAction: | |||
| 95 | + | return wire.UnmarshalNavigationDirective(frame.Payload) | |||
| 96 | + | case wire.MsgAttachTarget: | |||
| 97 | + | return wire.UnmarshalAttachTarget(frame.Payload) | |||
| 98 | + | case wire.MsgRemotePreviewResponse: | |||
| 99 | + | return wire.UnmarshalRemotePreview(frame.Payload) | |||
| 100 | + | case wire.MsgCommittedRouteIdentity: | |||
| 101 | + | return wire.UnmarshalCommittedRouteIdentity(frame.Payload) | |||
| 102 | + | case wire.MsgNavigateRecentRoute: | |||
| 103 | + | return wire.UnmarshalRouteNavigationAction(frame.Payload) | |||
| 104 | + | case wire.MsgRouteNavigationFailure: | |||
| 105 | + | return wire.UnmarshalRouteNavigationFailure(frame.Payload) | |||
| 106 | + | case wire.MsgRoutePosition: | |||
| 107 | + | return wire.UnmarshalRoutePosition(frame.Payload) | |||
| 108 | + | case wire.MsgSamePeerSwitchFailure: | |||
| 109 | + | return wire.UnmarshalSamePeerSwitchFailure(frame.Payload) | |||
| 110 | + | case wire.MsgParkedRouteResponse: | |||
| 111 | + | return wire.UnmarshalParkedRouteResponse(frame.Payload) | |||
| 112 | + | case wire.MsgHello, wire.MsgInput, wire.MsgResize, wire.MsgDetach, wire.MsgPing, | |||
| 113 | + | wire.MsgList, wire.MsgKill, wire.MsgTheme, wire.MsgAck, wire.MsgImagePush, | |||
| 114 | + | wire.MsgClientNotice, wire.MsgCommand, wire.MsgOutputResetRequest, | |||
| 115 | + | wire.MsgRemotePreviewRequest, wire.MsgRouteAttentionSubscription, | |||
| 116 | + | wire.MsgSamePeerSwitchRequest, wire.MsgParkedRouteRequest, | |||
| 117 | + | wire.MsgRecentRouteSnapshot: | |||
| 118 | + | return nil, ErrWrongDirection | |||
| 119 | + | default: | |||
| 120 | + | return nil, ErrUnknownMessageType | |||
| 121 | + | } | |||
| 122 | + | } | |||
| 123 | + | ||||
| 124 | + | func encodeClient(message protocol.ClientMessage) (wire.Frame, error) { | |||
| 125 | + | switch m := message.(type) { | |||
| 126 | + | case protocol.Hello: | |||
| 127 | + | payload := wire.MarshalHello(m) | |||
| 128 | + | if payload == nil { | |||
| 129 | + | return wire.Frame{}, ErrInvalidMessage | |||
| 130 | + | } | |||
| 131 | + | return wire.Frame{Type: wire.MsgHello, Payload: payload}, nil | |||
| 132 | + | case protocol.Input: | |||
| 133 | + | return wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(m)}, nil | |||
| 134 | + | case protocol.Resize: | |||
| 135 | + | payload, err := wire.MarshalResize(m) | |||
| 136 | + | return wire.Frame{Type: wire.MsgResize, Payload: payload}, err | |||
| 137 | + | case protocol.Detach: | |||
| 138 | + | return wire.Frame{Type: wire.MsgDetach, Payload: wire.MarshalDetach(m)}, nil | |||
| 139 | + | case protocol.Ping: | |||
| 140 | + | return wire.Frame{Type: wire.MsgPing, Payload: wire.MarshalPing(m)}, nil | |||
| 141 | + | case protocol.List: | |||
| 142 | + | return wire.Frame{Type: wire.MsgList, Payload: wire.MarshalList(m)}, nil | |||
| 143 | + | case protocol.Kill: | |||
| 144 | + | return wire.Frame{Type: wire.MsgKill, Payload: wire.MarshalKill(m)}, nil | |||
| 145 | + | case protocol.Theme: | |||
| 146 | + | return wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(m)}, nil | |||
| 147 | + | case protocol.Ack: | |||
| 148 | + | payload, err := wire.MarshalAck(m) | |||
| 149 | + | return wire.Frame{Type: wire.MsgAck, Payload: payload}, err | |||
| 150 | + | case protocol.ImagePush: | |||
| 151 | + | return wire.Frame{Type: wire.MsgImagePush, Payload: wire.MarshalImagePush(m)}, nil | |||
| 152 | + | case protocol.ClientNotice: | |||
| 153 | + | if err := protocol.ValidateClientNotice(m); err != nil { | |||
| 154 | + | return wire.Frame{}, err | |||
| 155 | + | } | |||
| 156 | + | return wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(m)}, nil | |||
| 157 | + | case protocol.CommandRequest: | |||
| 158 | + | payload, err := wire.MarshalCommandRequest(m) | |||
| 159 | + | return wire.Frame{Type: wire.MsgCommand, Payload: payload}, err | |||
| 160 | + | case protocol.OutputResetRequest: | |||
| 161 | + | return wire.Frame{Type: wire.MsgOutputResetRequest, Payload: wire.MarshalOutputResetRequest(m)}, nil | |||
| 162 | + | case protocol.RemotePreviewRequest: | |||
| 163 | + | payload := wire.MarshalRemotePreviewRequest(m) | |||
| 164 | + | if payload == nil { | |||
| 165 | + | return wire.Frame{}, ErrInvalidMessage | |||
| 166 | + | } | |||
| 167 | + | return wire.Frame{Type: wire.MsgRemotePreviewRequest, Payload: payload}, nil | |||
| 168 | + | case protocol.RouteAttentionSubscription: | |||
| 169 | + | payload, err := wire.MarshalRouteAttentionSubscription(m) | |||
| 170 | + | return wire.Frame{Type: wire.MsgRouteAttentionSubscription, Payload: payload}, err | |||
| 171 | + | case protocol.SamePeerSwitchRequest: | |||
| 172 | + | payload, err := wire.MarshalSamePeerSwitchRequest(m) | |||
| 173 | + | return wire.Frame{Type: wire.MsgSamePeerSwitchRequest, Payload: payload}, err | |||
| 174 | + | case protocol.ParkedRouteRequest: | |||
| 175 | + | payload := wire.MarshalParkedRouteRequest(m) | |||
| 176 | + | if payload == nil { | |||
| 177 | + | return wire.Frame{}, ErrInvalidMessage | |||
| 178 | + | } | |||
| 179 | + | return wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: payload}, nil | |||
| 180 | + | case protocol.RecentRouteSnapshot: | |||
| 181 | + | payload, err := wire.MarshalRecentRouteSnapshot(m) | |||
| 182 | + | return wire.Frame{Type: wire.MsgRecentRouteSnapshot, Payload: payload}, err | |||
| 183 | + | case protocol.RouteNavigationFailure: | |||
| 184 | + | payload, err := wire.MarshalRouteNavigationFailure(m) | |||
| 185 | + | return wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: payload}, err | |||
| 186 | + | case *protocol.Hello: | |||
| 187 | + | if m != nil { | |||
| 188 | + | return encodeClient(*m) | |||
| 189 | + | } | |||
| 190 | + | case *protocol.Input: | |||
| 191 | + | if m != nil { | |||
| 192 | + | return encodeClient(*m) | |||
| 193 | + | } | |||
| 194 | + | case *protocol.Resize: | |||
| 195 | + | if m != nil { | |||
| 196 | + | return encodeClient(*m) | |||
| 197 | + | } | |||
| 198 | + | case *protocol.Detach: | |||
| 199 | + | if m != nil { | |||
| 200 | + | return encodeClient(*m) | |||
| 201 | + | } | |||
| 202 | + | case *protocol.Ping: | |||
| 203 | + | if m != nil { | |||
| 204 | + | return encodeClient(*m) | |||
| 205 | + | } | |||
| 206 | + | case *protocol.List: | |||
| 207 | + | if m != nil { | |||
| 208 | + | return encodeClient(*m) | |||
| 209 | + | } | |||
| 210 | + | case *protocol.Kill: | |||
| 211 | + | if m != nil { | |||
| 212 | + | return encodeClient(*m) | |||
| 213 | + | } | |||
| 214 | + | case *protocol.Theme: | |||
| 215 | + | if m != nil { | |||
| 216 | + | return encodeClient(*m) | |||
| 217 | + | } | |||
| 218 | + | case *protocol.Ack: | |||
| 219 | + | if m != nil { | |||
| 220 | + | return encodeClient(*m) | |||
| 221 | + | } | |||
| 222 | + | case *protocol.ImagePush: | |||
| 223 | + | if m != nil { | |||
| 224 | + | return encodeClient(*m) | |||
| 225 | + | } | |||
| 226 | + | case *protocol.ClientNotice: | |||
| 227 | + | if m != nil { | |||
| 228 | + | return encodeClient(*m) | |||
| 229 | + | } | |||
| 230 | + | case *protocol.CommandRequest: | |||
| 231 | + | if m != nil { | |||
| 232 | + | return encodeClient(*m) | |||
| 233 | + | } | |||
| 234 | + | case *protocol.OutputResetRequest: | |||
| 235 | + | if m != nil { | |||
| 236 | + | return encodeClient(*m) | |||
| 237 | + | } | |||
| 238 | + | case *protocol.RemotePreviewRequest: | |||
| 239 | + | if m != nil { | |||
| 240 | + | return encodeClient(*m) | |||
| 241 | + | } | |||
| 242 | + | case *protocol.RouteAttentionSubscription: | |||
| 243 | + | if m != nil { | |||
| 244 | + | return encodeClient(*m) | |||
| 245 | + | } | |||
| 246 | + | case *protocol.SamePeerSwitchRequest: | |||
| 247 | + | if m != nil { | |||
| 248 | + | return encodeClient(*m) | |||
| 249 | + | } | |||
| 250 | + | case *protocol.ParkedRouteRequest: | |||
| 251 | + | if m != nil { | |||
| 252 | + | return encodeClient(*m) | |||
| 253 | + | } | |||
| 254 | + | case *protocol.RecentRouteSnapshot: | |||
| 255 | + | if m != nil { | |||
| 256 | + | return encodeClient(*m) | |||
| 257 | + | } | |||
| 258 | + | case *protocol.RouteNavigationFailure: | |||
| 259 | + | if m != nil { | |||
| 260 | + | return encodeClient(*m) | |||
| 261 | + | } | |||
| 262 | + | default: | |||
| 263 | + | return wire.Frame{}, ErrWrongDirection | |||
| 264 | + | } | |||
| 265 | + | return wire.Frame{}, ErrInvalidMessage | |||
| 266 | + | } | |||
A internal/adapters/sessionwire/client_test.go +140 -0
| @@ -0,0 +1,140 @@ | |||
| 1 | + | package sessionwire | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "errors" | |
| 6 | + | "testing" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/vev/internal/domain" | |
| 9 | + | "github.com/bnema/vev/internal/ports" | |
| 10 | + | "github.com/bnema/vev/internal/protocol" | |
| 11 | + | "github.com/bnema/vev/internal/protocol/wire" | |
| 12 | + | "github.com/stretchr/testify/require" | |
| 13 | + | ) | |
| 14 | + | ||
| 15 | + | func TestClientConnectionEncodesEveryClientMessage(t *testing.T) { | |
| 16 | + | target := testTarget() | |
| 17 | + | exact := protocol.ExactSessionTarget{LifecycleID: target.LifecycleID, SessionName: target.SessionName} | |
| 18 | + | lease := protocol.ParkedRouteLeaseID{1} | |
| 19 | + | tests := []struct { | |
| 20 | + | name string | |
| 21 | + | message protocol.ClientMessage | |
| 22 | + | typeID wire.MsgType | |
| 23 | + | }{ | |
| 24 | + | {name: "hello", message: protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}}, typeID: wire.MsgHello}, | |
| 25 | + | {name: "input", message: protocol.Input{InputSeq: 1, Data: []byte("x")}, typeID: wire.MsgInput}, | |
| 26 | + | {name: "resize", message: protocol.Resize{Size: domain.Size{Cols: 80, Rows: 24}}, typeID: wire.MsgResize}, | |
| 27 | + | {name: "detach", message: protocol.Detach{}, typeID: wire.MsgDetach}, | |
| 28 | + | {name: "ping", message: protocol.Ping{}, typeID: wire.MsgPing}, | |
| 29 | + | {name: "list", message: protocol.List{}, typeID: wire.MsgList}, | |
| 30 | + | {name: "kill", message: protocol.Kill{Name: "work"}, typeID: wire.MsgKill}, | |
| 31 | + | {name: "theme", message: protocol.Theme{TrueColor: true}, typeID: wire.MsgTheme}, | |
| 32 | + | {name: "ack", message: protocol.Ack{Epoch: 1, State: 1}, typeID: wire.MsgAck}, | |
| 33 | + | {name: "image", message: protocol.ImagePush{InputSeq: 1, Mime: "image/png", Data: []byte{1}}, typeID: wire.MsgImagePush}, | |
| 34 | + | {name: "notice", message: protocol.ClientNotice{Action: protocol.ClientNoticeLinkConnected}, typeID: wire.MsgClientNotice}, | |
| 35 | + | {name: "command", message: protocol.CommandRequest{Version: protocol.Version, RequestID: 1, Slug: "list-sessions"}, typeID: wire.MsgCommand}, | |
| 36 | + | {name: "reset", message: protocol.OutputResetRequest{}, typeID: wire.MsgOutputResetRequest}, | |
| 37 | + | {name: "preview", message: protocol.RemotePreviewRequest{Version: protocol.RemotePreviewSchemaVersion, Target: target, Width: 1, Height: 1}, typeID: wire.MsgRemotePreviewRequest}, | |
| 38 | + | {name: "attention", message: protocol.RouteAttentionSubscription{Targets: []protocol.RouteAttentionTarget{}}, typeID: wire.MsgRouteAttentionSubscription}, | |
| 39 | + | {name: "same peer", message: protocol.SamePeerSwitchRequest{RequestID: 1, Target: exact}, typeID: wire.MsgSamePeerSwitchRequest}, | |
| 40 | + | {name: "parked", message: protocol.ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: protocol.ParkedRoutePrepare}, typeID: wire.MsgParkedRouteRequest}, | |
| 41 | + | {name: "snapshot", message: protocol.RecentRouteSnapshot{}, typeID: wire.MsgRecentRouteSnapshot}, | |
| 42 | + | {name: "route failure", message: protocol.RouteNavigationFailure{Key: 1, Generation: 1, Code: protocol.RouteFailureUnavailable}, typeID: wire.MsgRouteNavigationFailure}, | |
| 43 | + | } | |
| 44 | + | for _, tt := range tests { | |
| 45 | + | t.Run(tt.name, func(t *testing.T) { | |
| 46 | + | raw := &scriptedTransport{} | |
| 47 | + | require.NoError(t, NewClientConnection(raw).SendClient(tt.message)) | |
| 48 | + | require.Equal(t, tt.typeID, raw.sent.Type) | |
| 49 | + | got, err := decodeClient(raw.sent) | |
| 50 | + | require.NoError(t, err) | |
| 51 | + | require.Equal(t, tt.message, got) | |
| 52 | + | }) | |
| 53 | + | } | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | func TestClientConnectionDecodesEveryServerMessage(t *testing.T) { | |
| 57 | + | exact := protocol.ExactSessionTarget{LifecycleID: domain.SessionLifecycleID{1}, SessionName: "work"} | |
| 58 | + | lease := protocol.ParkedRouteLeaseID{1} | |
| 59 | + | tests := []struct { | |
| 60 | + | name string | |
| 61 | + | message protocol.ServerMessage | |
| 62 | + | }{ | |
| 63 | + | {name: "welcome", message: protocol.Welcome{SessionID: "session"}}, | |
| 64 | + | {name: "error", message: protocol.ErrorMsg{Code: protocol.ErrInternal, Text: "error"}}, | |
| 65 | + | {name: "output", message: protocol.Output{Epoch: 1, New: 1, Full: true, Size: domain.Size{Cols: 80, Rows: 24}}}, | |
| 66 | + | {name: "detached", message: protocol.Detached{Reason: protocol.ReasonDetach}}, | |
| 67 | + | {name: "pong", message: protocol.Pong{}}, | |
| 68 | + | {name: "sessions", message: protocol.Sessions{Sessions: []protocol.SessionInfo{}}}, | |
| 69 | + | {name: "command result", message: protocol.CommandResult{RequestID: 1, OK: true}}, | |
| 70 | + | {name: "navigation", message: protocol.NavigationDirective{Action: protocol.NavigationOpenHomePicker, LeaseID: lease}}, | |
| 71 | + | {name: "attach target", message: protocol.AttachTarget{Session: "work", Intent: protocol.IntentAttach}}, | |
| 72 | + | {name: "preview", message: protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: protocol.RemotePreviewUnavailable}}, | |
| 73 | + | {name: "identity", message: protocol.CommittedRouteIdentity{Target: exact}}, | |
| 74 | + | {name: "route action", message: protocol.RouteNavigationAction{SnapshotGeneration: 1, Key: 1, Generation: 1}}, | |
| 75 | + | {name: "route failure", message: protocol.RouteNavigationFailure{Key: 1, Generation: 1, Code: protocol.RouteFailureUnavailable}}, | |
| 76 | + | {name: "route position", message: protocol.RoutePosition{Target: exact, ActiveTabID: "tab-1"}}, | |
| 77 | + | {name: "switch failure", message: protocol.SamePeerSwitchFailure{RequestID: 1, Code: protocol.SamePeerSwitchUnavailable}}, | |
| 78 | + | {name: "parked response", message: protocol.ParkedRouteResponse{RequestID: 1, Status: protocol.ParkedRouteReady}}, | |
| 79 | + | } | |
| 80 | + | for _, tt := range tests { | |
| 81 | + | t.Run(tt.name, func(t *testing.T) { | |
| 82 | + | frame, err := encodeServer(tt.message) | |
| 83 | + | require.NoError(t, err) | |
| 84 | + | got, err := NewClientConnection(&scriptedTransport{recv: frame}).ReceiveServer() | |
| 85 | + | require.NoError(t, err) | |
| 86 | + | require.Equal(t, tt.message, got) | |
| 87 | + | }) | |
| 88 | + | } | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | func TestClientConnectionClassifiesFailuresAndPreservesCapabilities(t *testing.T) { | |
| 92 | + | validError := wire.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrInternal, Text: "error"}) | |
| 93 | + | for _, tt := range []struct { | |
| 94 | + | name string | |
| 95 | + | frame wire.Frame | |
| 96 | + | category protocol.DecodeCategory | |
| 97 | + | }{ | |
| 98 | + | {name: "wrong direction", frame: wire.Frame{Type: wire.MsgInput}, category: protocol.DecodeWrongDirection}, | |
| 99 | + | {name: "truncated payload", frame: wire.Frame{Type: wire.MsgError, Payload: validError[:1]}, category: protocol.DecodeMalformed}, | |
| 100 | + | {name: "trailing garbage", frame: wire.Frame{Type: wire.MsgError, Payload: append(append([]byte(nil), validError...), 0xff)}, category: protocol.DecodeMalformed}, | |
| 101 | + | } { | |
| 102 | + | t.Run(tt.name, func(t *testing.T) { | |
| 103 | + | _, err := NewClientConnection(&scriptedTransport{recv: tt.frame}).ReceiveServer() | |
| 104 | + | var failure *protocol.DecodeFailure | |
| 105 | + | require.ErrorAs(t, err, &failure) | |
| 106 | + | require.Equal(t, tt.category, failure.Category) | |
| 107 | + | }) | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | events := make(chan ports.LinkEvent, 1) | |
| 111 | + | raw := &capableTransport{events: events} | |
| 112 | + | connection := NewClientConnection(raw) | |
| 113 | + | require.Equal(t, uint8(1), connection.Capabilities().PreferredOutputWindow) | |
| 114 | + | require.True(t, connection.Capabilities().LinkState) | |
| 115 | + | require.Equal(t, ports.LinkStateDegraded, connection.LinkState()) | |
| 116 | + | require.Equal(t, (<-chan ports.LinkEvent)(events), connection.LinkEvents()) | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | func TestClientDialerWrapsEachConnectionAndPreservesErrors(t *testing.T) { | |
| 120 | + | dialErr := errors.New("dial failed") | |
| 121 | + | dialer := NewClientDialer(&scriptedDialer{err: dialErr}) | |
| 122 | + | _, err := dialer.Dial(context.Background()) | |
| 123 | + | require.ErrorIs(t, err, dialErr) | |
| 124 | + | ||
| 125 | + | raw := &scriptedTransport{} | |
| 126 | + | dialer = NewClientDialer(&scriptedDialer{transport: raw}) | |
| 127 | + | connection, err := dialer.Dial(context.Background()) | |
| 128 | + | require.NoError(t, err) | |
| 129 | + | require.NoError(t, connection.SendClient(protocol.Ping{})) | |
| 130 | + | require.Equal(t, wire.MsgPing, raw.sent.Type) | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | type scriptedDialer struct { | |
| 134 | + | transport ports.Transport | |
| 135 | + | err error | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | func (d *scriptedDialer) Dial(context.Context) (ports.Transport, error) { | |
| 139 | + | return d.transport, d.err | |
| 140 | + | } | |
| @@ -0,0 +1,140 @@ | |||||
| 1 | + | package sessionwire | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "errors" | |||
| 6 | + | "testing" | |||
| 7 | + | ||||
| 8 | + | "github.com/bnema/vev/internal/domain" | |||
| 9 | + | "github.com/bnema/vev/internal/ports" | |||
| 10 | + | "github.com/bnema/vev/internal/protocol" | |||
| 11 | + | "github.com/bnema/vev/internal/protocol/wire" | |||
| 12 | + | "github.com/stretchr/testify/require" | |||
| 13 | + | ) | |||
| 14 | + | ||||
| 15 | + | func TestClientConnectionEncodesEveryClientMessage(t *testing.T) { | |||
| 16 | + | target := testTarget() | |||
| 17 | + | exact := protocol.ExactSessionTarget{LifecycleID: target.LifecycleID, SessionName: target.SessionName} | |||
| 18 | + | lease := protocol.ParkedRouteLeaseID{1} | |||
| 19 | + | tests := []struct { | |||
| 20 | + | name string | |||
| 21 | + | message protocol.ClientMessage | |||
| 22 | + | typeID wire.MsgType | |||
| 23 | + | }{ | |||
| 24 | + | {name: "hello", message: protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}}, typeID: wire.MsgHello}, | |||
| 25 | + | {name: "input", message: protocol.Input{InputSeq: 1, Data: []byte("x")}, typeID: wire.MsgInput}, | |||
| 26 | + | {name: "resize", message: protocol.Resize{Size: domain.Size{Cols: 80, Rows: 24}}, typeID: wire.MsgResize}, | |||
| 27 | + | {name: "detach", message: protocol.Detach{}, typeID: wire.MsgDetach}, | |||
| 28 | + | {name: "ping", message: protocol.Ping{}, typeID: wire.MsgPing}, | |||
| 29 | + | {name: "list", message: protocol.List{}, typeID: wire.MsgList}, | |||
| 30 | + | {name: "kill", message: protocol.Kill{Name: "work"}, typeID: wire.MsgKill}, | |||
| 31 | + | {name: "theme", message: protocol.Theme{TrueColor: true}, typeID: wire.MsgTheme}, | |||
| 32 | + | {name: "ack", message: protocol.Ack{Epoch: 1, State: 1}, typeID: wire.MsgAck}, | |||
| 33 | + | {name: "image", message: protocol.ImagePush{InputSeq: 1, Mime: "image/png", Data: []byte{1}}, typeID: wire.MsgImagePush}, | |||
| 34 | + | {name: "notice", message: protocol.ClientNotice{Action: protocol.ClientNoticeLinkConnected}, typeID: wire.MsgClientNotice}, | |||
| 35 | + | {name: "command", message: protocol.CommandRequest{Version: protocol.Version, RequestID: 1, Slug: "list-sessions"}, typeID: wire.MsgCommand}, | |||
| 36 | + | {name: "reset", message: protocol.OutputResetRequest{}, typeID: wire.MsgOutputResetRequest}, | |||
| 37 | + | {name: "preview", message: protocol.RemotePreviewRequest{Version: protocol.RemotePreviewSchemaVersion, Target: target, Width: 1, Height: 1}, typeID: wire.MsgRemotePreviewRequest}, | |||
| 38 | + | {name: "attention", message: protocol.RouteAttentionSubscription{Targets: []protocol.RouteAttentionTarget{}}, typeID: wire.MsgRouteAttentionSubscription}, | |||
| 39 | + | {name: "same peer", message: protocol.SamePeerSwitchRequest{RequestID: 1, Target: exact}, typeID: wire.MsgSamePeerSwitchRequest}, | |||
| 40 | + | {name: "parked", message: protocol.ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: protocol.ParkedRoutePrepare}, typeID: wire.MsgParkedRouteRequest}, | |||
| 41 | + | {name: "snapshot", message: protocol.RecentRouteSnapshot{}, typeID: wire.MsgRecentRouteSnapshot}, | |||
| 42 | + | {name: "route failure", message: protocol.RouteNavigationFailure{Key: 1, Generation: 1, Code: protocol.RouteFailureUnavailable}, typeID: wire.MsgRouteNavigationFailure}, | |||
| 43 | + | } | |||
| 44 | + | for _, tt := range tests { | |||
| 45 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 46 | + | raw := &scriptedTransport{} | |||
| 47 | + | require.NoError(t, NewClientConnection(raw).SendClient(tt.message)) | |||
| 48 | + | require.Equal(t, tt.typeID, raw.sent.Type) | |||
| 49 | + | got, err := decodeClient(raw.sent) | |||
| 50 | + | require.NoError(t, err) | |||
| 51 | + | require.Equal(t, tt.message, got) | |||
| 52 | + | }) | |||
| 53 | + | } | |||
| 54 | + | } | |||
| 55 | + | ||||
| 56 | + | func TestClientConnectionDecodesEveryServerMessage(t *testing.T) { | |||
| 57 | + | exact := protocol.ExactSessionTarget{LifecycleID: domain.SessionLifecycleID{1}, SessionName: "work"} | |||
| 58 | + | lease := protocol.ParkedRouteLeaseID{1} | |||
| 59 | + | tests := []struct { | |||
| 60 | + | name string | |||
| 61 | + | message protocol.ServerMessage | |||
| 62 | + | }{ | |||
| 63 | + | {name: "welcome", message: protocol.Welcome{SessionID: "session"}}, | |||
| 64 | + | {name: "error", message: protocol.ErrorMsg{Code: protocol.ErrInternal, Text: "error"}}, | |||
| 65 | + | {name: "output", message: protocol.Output{Epoch: 1, New: 1, Full: true, Size: domain.Size{Cols: 80, Rows: 24}}}, | |||
| 66 | + | {name: "detached", message: protocol.Detached{Reason: protocol.ReasonDetach}}, | |||
| 67 | + | {name: "pong", message: protocol.Pong{}}, | |||
| 68 | + | {name: "sessions", message: protocol.Sessions{Sessions: []protocol.SessionInfo{}}}, | |||
| 69 | + | {name: "command result", message: protocol.CommandResult{RequestID: 1, OK: true}}, | |||
| 70 | + | {name: "navigation", message: protocol.NavigationDirective{Action: protocol.NavigationOpenHomePicker, LeaseID: lease}}, | |||
| 71 | + | {name: "attach target", message: protocol.AttachTarget{Session: "work", Intent: protocol.IntentAttach}}, | |||
| 72 | + | {name: "preview", message: protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: protocol.RemotePreviewUnavailable}}, | |||
| 73 | + | {name: "identity", message: protocol.CommittedRouteIdentity{Target: exact}}, | |||
| 74 | + | {name: "route action", message: protocol.RouteNavigationAction{SnapshotGeneration: 1, Key: 1, Generation: 1}}, | |||
| 75 | + | {name: "route failure", message: protocol.RouteNavigationFailure{Key: 1, Generation: 1, Code: protocol.RouteFailureUnavailable}}, | |||
| 76 | + | {name: "route position", message: protocol.RoutePosition{Target: exact, ActiveTabID: "tab-1"}}, | |||
| 77 | + | {name: "switch failure", message: protocol.SamePeerSwitchFailure{RequestID: 1, Code: protocol.SamePeerSwitchUnavailable}}, | |||
| 78 | + | {name: "parked response", message: protocol.ParkedRouteResponse{RequestID: 1, Status: protocol.ParkedRouteReady}}, | |||
| 79 | + | } | |||
| 80 | + | for _, tt := range tests { | |||
| 81 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 82 | + | frame, err := encodeServer(tt.message) | |||
| 83 | + | require.NoError(t, err) | |||
| 84 | + | got, err := NewClientConnection(&scriptedTransport{recv: frame}).ReceiveServer() | |||
| 85 | + | require.NoError(t, err) | |||
| 86 | + | require.Equal(t, tt.message, got) | |||
| 87 | + | }) | |||
| 88 | + | } | |||
| 89 | + | } | |||
| 90 | + | ||||
| 91 | + | func TestClientConnectionClassifiesFailuresAndPreservesCapabilities(t *testing.T) { | |||
| 92 | + | validError := wire.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrInternal, Text: "error"}) | |||
| 93 | + | for _, tt := range []struct { | |||
| 94 | + | name string | |||
| 95 | + | frame wire.Frame | |||
| 96 | + | category protocol.DecodeCategory | |||
| 97 | + | }{ | |||
| 98 | + | {name: "wrong direction", frame: wire.Frame{Type: wire.MsgInput}, category: protocol.DecodeWrongDirection}, | |||
| 99 | + | {name: "truncated payload", frame: wire.Frame{Type: wire.MsgError, Payload: validError[:1]}, category: protocol.DecodeMalformed}, | |||
| 100 | + | {name: "trailing garbage", frame: wire.Frame{Type: wire.MsgError, Payload: append(append([]byte(nil), validError...), 0xff)}, category: protocol.DecodeMalformed}, | |||
| 101 | + | } { | |||
| 102 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 103 | + | _, err := NewClientConnection(&scriptedTransport{recv: tt.frame}).ReceiveServer() | |||
| 104 | + | var failure *protocol.DecodeFailure | |||
| 105 | + | require.ErrorAs(t, err, &failure) | |||
| 106 | + | require.Equal(t, tt.category, failure.Category) | |||
| 107 | + | }) | |||
| 108 | + | } | |||
| 109 | + | ||||
| 110 | + | events := make(chan ports.LinkEvent, 1) | |||
| 111 | + | raw := &capableTransport{events: events} | |||
| 112 | + | connection := NewClientConnection(raw) | |||
| 113 | + | require.Equal(t, uint8(1), connection.Capabilities().PreferredOutputWindow) | |||
| 114 | + | require.True(t, connection.Capabilities().LinkState) | |||
| 115 | + | require.Equal(t, ports.LinkStateDegraded, connection.LinkState()) | |||
| 116 | + | require.Equal(t, (<-chan ports.LinkEvent)(events), connection.LinkEvents()) | |||
| 117 | + | } | |||
| 118 | + | ||||
| 119 | + | func TestClientDialerWrapsEachConnectionAndPreservesErrors(t *testing.T) { | |||
| 120 | + | dialErr := errors.New("dial failed") | |||
| 121 | + | dialer := NewClientDialer(&scriptedDialer{err: dialErr}) | |||
| 122 | + | _, err := dialer.Dial(context.Background()) | |||
| 123 | + | require.ErrorIs(t, err, dialErr) | |||
| 124 | + | ||||
| 125 | + | raw := &scriptedTransport{} | |||
| 126 | + | dialer = NewClientDialer(&scriptedDialer{transport: raw}) | |||
| 127 | + | connection, err := dialer.Dial(context.Background()) | |||
| 128 | + | require.NoError(t, err) | |||
| 129 | + | require.NoError(t, connection.SendClient(protocol.Ping{})) | |||
| 130 | + | require.Equal(t, wire.MsgPing, raw.sent.Type) | |||
| 131 | + | } | |||
| 132 | + | ||||
| 133 | + | type scriptedDialer struct { | |||
| 134 | + | transport ports.Transport | |||
| 135 | + | err error | |||
| 136 | + | } | |||
| 137 | + | ||||
| 138 | + | func (d *scriptedDialer) Dial(context.Context) (ports.Transport, error) { | |||
| 139 | + | return d.transport, d.err | |||
| 140 | + | } | |||
M internal/adapters/sessionwire/server.go +15 -8
| @@ -132,10 +132,17 @@ func encodeOutput(output protocol.Output) (wire.Frame, error) { | |||
| 132 | 132 | } | |
| 133 | 133 | ||
| 134 | 134 | func (c *serverConnection) Capabilities() protocol.ConnectionCapabilities { | |
| 135 | - | _, datagram := c.raw.(ports.DatagramTransport) | |
| 136 | - | _, async := c.raw.(ports.AsyncTransport) | |
| 137 | - | _, synchronous := c.raw.(ports.OwnedSynchronousTransport) | |
| 138 | - | _, linkState := c.raw.(ports.LinkStateReporter) | |
| 135 | + | return rawCapabilities(c.raw) | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | func (c *serverConnection) LinkState() ports.LinkState { return rawLinkState(c.raw) } | |
| 139 | + | func (c *serverConnection) LinkEvents() <-chan ports.LinkEvent { return rawLinkEvents(c.raw) } | |
| 140 | + | ||
| 141 | + | func rawCapabilities(raw ports.Transport) protocol.ConnectionCapabilities { | |
| 142 | + | _, datagram := raw.(ports.DatagramTransport) | |
| 143 | + | _, async := raw.(ports.AsyncTransport) | |
| 144 | + | _, synchronous := raw.(ports.OwnedSynchronousTransport) | |
| 145 | + | _, linkState := raw.(ports.LinkStateReporter) | |
| 139 | 146 | window := uint8(protocol.MaxOutputWindow) | |
| 140 | 147 | if datagram { | |
| 141 | 148 | window = 1 | |
| @@ -149,15 +156,15 @@ func (c *serverConnection) Capabilities() protocol.ConnectionCapabilities { | |||
| 149 | 156 | } | |
| 150 | 157 | } | |
| 151 | 158 | ||
| 152 | - | func (c *serverConnection) LinkState() ports.LinkState { | |
| 153 | - | if reporter, ok := c.raw.(ports.LinkStateReporter); ok { | |
| 159 | + | func rawLinkState(raw ports.Transport) ports.LinkState { | |
| 160 | + | if reporter, ok := raw.(ports.LinkStateReporter); ok { | |
| 154 | 161 | return reporter.LinkState() | |
| 155 | 162 | } | |
| 156 | 163 | return ports.LinkStateConnected | |
| 157 | 164 | } | |
| 158 | 165 | ||
| 159 | - | func (c *serverConnection) LinkEvents() <-chan ports.LinkEvent { | |
| 160 | - | if reporter, ok := c.raw.(ports.LinkStateReporter); ok { | |
| 166 | + | func rawLinkEvents(raw ports.Transport) <-chan ports.LinkEvent { | |
| 167 | + | if reporter, ok := raw.(ports.LinkStateReporter); ok { | |
| 161 | 168 | return reporter.LinkEvents() | |
| 162 | 169 | } | |
| 163 | 170 | return nil | |
| @@ -132,10 +132,17 @@ func encodeOutput(output protocol.Output) (wire.Frame, error) { | |||||
| 132 | } | 132 | } | ||
| 133 | 133 | ||||
| 134 | func (c *serverConnection) Capabilities() protocol.ConnectionCapabilities { | 134 | func (c *serverConnection) Capabilities() protocol.ConnectionCapabilities { | ||
| 135 | - | _, datagram := c.raw.(ports.DatagramTransport) | 135 | + | return rawCapabilities(c.raw) |
| 136 | - | _, async := c.raw.(ports.AsyncTransport) | 136 | + | } |
| 137 | - | _, synchronous := c.raw.(ports.OwnedSynchronousTransport) | 137 | + | |
| 138 | - | _, linkState := c.raw.(ports.LinkStateReporter) | 138 | + | func (c *serverConnection) LinkState() ports.LinkState { return rawLinkState(c.raw) } |
| 139 | + | func (c *serverConnection) LinkEvents() <-chan ports.LinkEvent { return rawLinkEvents(c.raw) } | |||
| 140 | + | ||||
| 141 | + | func rawCapabilities(raw ports.Transport) protocol.ConnectionCapabilities { | |||
| 142 | + | _, datagram := raw.(ports.DatagramTransport) | |||
| 143 | + | _, async := raw.(ports.AsyncTransport) | |||
| 144 | + | _, synchronous := raw.(ports.OwnedSynchronousTransport) | |||
| 145 | + | _, linkState := raw.(ports.LinkStateReporter) | |||
| 139 | window := uint8(protocol.MaxOutputWindow) | 146 | window := uint8(protocol.MaxOutputWindow) | ||
| 140 | if datagram { | 147 | if datagram { | ||
| 141 | window = 1 | 148 | window = 1 | ||
| @@ -149,15 +156,15 @@ func (c *serverConnection) Capabilities() protocol.ConnectionCapabilities { | |||||
| 149 | } | 156 | } | ||
| 150 | } | 157 | } | ||
| 151 | 158 | ||||
| 152 | - | func (c *serverConnection) LinkState() ports.LinkState { | 159 | + | func rawLinkState(raw ports.Transport) ports.LinkState { |
| 153 | - | if reporter, ok := c.raw.(ports.LinkStateReporter); ok { | 160 | + | if reporter, ok := raw.(ports.LinkStateReporter); ok { |
| 154 | return reporter.LinkState() | 161 | return reporter.LinkState() | ||
| 155 | } | 162 | } | ||
| 156 | return ports.LinkStateConnected | 163 | return ports.LinkStateConnected | ||
| 157 | } | 164 | } | ||
| 158 | 165 | ||||
| 159 | - | func (c *serverConnection) LinkEvents() <-chan ports.LinkEvent { | 166 | + | func rawLinkEvents(raw ports.Transport) <-chan ports.LinkEvent { |
| 160 | - | if reporter, ok := c.raw.(ports.LinkStateReporter); ok { | 167 | + | if reporter, ok := raw.(ports.LinkStateReporter); ok { |
| 161 | return reporter.LinkEvents() | 168 | return reporter.LinkEvents() | ||
| 162 | } | 169 | } | ||
| 163 | return nil | 170 | return nil | ||
M internal/app/run.go +4 -4
| @@ -914,7 +914,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||
| 914 | 914 | } | |
| 915 | 915 | return policy | |
| 916 | 916 | } | |
| 917 | - | handoff := func(target protocol.AttachTarget) (ports.Dialer, client.AttachRequest, error) { | |
| 917 | + | handoff := func(target protocol.AttachTarget) (ports.ClientDialer, client.AttachRequest, error) { | |
| 918 | 918 | if err := validateRemoteAttachHandoff(target); err != nil { | |
| 919 | 919 | return nil, client.AttachRequest{}, fmt.Errorf("vev: invalid remote attach handoff: %w", err) | |
| 920 | 920 | } | |
| @@ -938,7 +938,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||
| 938 | 938 | if selection != nil { | |
| 939 | 939 | displayOrigin = selection.DisplayOrigin | |
| 940 | 940 | } | |
| 941 | - | return dialer, client.AttachRequest{ | |
| 941 | + | return sessionwire.NewClientDialer(dialer), client.AttachRequest{ | |
| 942 | 942 | Intent: target.Intent, | |
| 943 | 943 | SessionName: target.Session, | |
| 944 | 944 | Remote: true, | |
| @@ -962,7 +962,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||
| 962 | 962 | return dialErr | |
| 963 | 963 | } | |
| 964 | 964 | err = runClient(ctx, client.Dependencies{ | |
| 965 | - | Dialer: dialer, | |
| 965 | + | Dialer: sessionwire.NewClientDialer(dialer), | |
| 966 | 966 | Terminal: term.New(), | |
| 967 | 967 | Clock: clock.New(), | |
| 968 | 968 | Clipboard: deps.clipboard, | |
| @@ -988,7 +988,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||
| 988 | 988 | log.Info("attaching to local session", "intent", intent, "name", name) | |
| 989 | 989 | } | |
| 990 | 990 | err = runClient(ctx, client.Dependencies{ | |
| 991 | - | Dialer: localDialer(), | |
| 991 | + | Dialer: sessionwire.NewClientDialer(localDialer()), | |
| 992 | 992 | Terminal: term.New(), | |
| 993 | 993 | Clock: clock.New(), | |
| 994 | 994 | Logger: log, | |
| @@ -914,7 +914,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||||
| 914 | } | 914 | } | ||
| 915 | return policy | 915 | return policy | ||
| 916 | } | 916 | } | ||
| 917 | - | handoff := func(target protocol.AttachTarget) (ports.Dialer, client.AttachRequest, error) { | 917 | + | handoff := func(target protocol.AttachTarget) (ports.ClientDialer, client.AttachRequest, error) { |
| 918 | if err := validateRemoteAttachHandoff(target); err != nil { | 918 | if err := validateRemoteAttachHandoff(target); err != nil { | ||
| 919 | return nil, client.AttachRequest{}, fmt.Errorf("vev: invalid remote attach handoff: %w", err) | 919 | return nil, client.AttachRequest{}, fmt.Errorf("vev: invalid remote attach handoff: %w", err) | ||
| 920 | } | 920 | } | ||
| @@ -938,7 +938,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||||
| 938 | if selection != nil { | 938 | if selection != nil { | ||
| 939 | displayOrigin = selection.DisplayOrigin | 939 | displayOrigin = selection.DisplayOrigin | ||
| 940 | } | 940 | } | ||
| 941 | - | return dialer, client.AttachRequest{ | 941 | + | return sessionwire.NewClientDialer(dialer), client.AttachRequest{ |
| 942 | Intent: target.Intent, | 942 | Intent: target.Intent, | ||
| 943 | SessionName: target.Session, | 943 | SessionName: target.Session, | ||
| 944 | Remote: true, | 944 | Remote: true, | ||
| @@ -962,7 +962,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||||
| 962 | return dialErr | 962 | return dialErr | ||
| 963 | } | 963 | } | ||
| 964 | err = runClient(ctx, client.Dependencies{ | 964 | err = runClient(ctx, client.Dependencies{ | ||
| 965 | - | Dialer: dialer, | 965 | + | Dialer: sessionwire.NewClientDialer(dialer), |
| 966 | Terminal: term.New(), | 966 | Terminal: term.New(), | ||
| 967 | Clock: clock.New(), | 967 | Clock: clock.New(), | ||
| 968 | Clipboard: deps.clipboard, | 968 | Clipboard: deps.clipboard, | ||
| @@ -988,7 +988,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||||
| 988 | log.Info("attaching to local session", "intent", intent, "name", name) | 988 | log.Info("attaching to local session", "intent", intent, "name", name) | ||
| 989 | } | 989 | } | ||
| 990 | err = runClient(ctx, client.Dependencies{ | 990 | err = runClient(ctx, client.Dependencies{ | ||
| 991 | - | Dialer: localDialer(), | 991 | + | Dialer: sessionwire.NewClientDialer(localDialer()), |
| 992 | Terminal: term.New(), | 992 | Terminal: term.New(), | ||
| 993 | Clock: clock.New(), | 993 | Clock: clock.New(), | ||
| 994 | Logger: log, | 994 | Logger: log, | ||
M internal/app/run_test.go +16 -14
| @@ -6,6 +6,7 @@ import ( | |||
| 6 | 6 | "context" | |
| 7 | 7 | "encoding/json" | |
| 8 | 8 | "errors" | |
| 9 | + | "fmt" | |
| 9 | 10 | "io" | |
| 10 | 11 | "log/slog" | |
| 11 | 12 | "net" | |
| @@ -18,6 +19,7 @@ import ( | |||
| 18 | 19 | ||
| 19 | 20 | "github.com/bnema/vev/internal/adapters/lifecycle" | |
| 20 | 21 | remoteadapter "github.com/bnema/vev/internal/adapters/remote" | |
| 22 | + | "github.com/bnema/vev/internal/adapters/sessionwire" | |
| 21 | 23 | "github.com/bnema/vev/internal/domain" | |
| 22 | 24 | "github.com/bnema/vev/internal/persist" | |
| 23 | 25 | "github.com/bnema/vev/internal/ports" | |
| @@ -575,7 +577,7 @@ func TestAttachTargetsCreateCommonSessionRequest(t *testing.T) { | |||
| 575 | 577 | t.Run(tt.name, func(t *testing.T) { | |
| 576 | 578 | cmd, err := parseArgs([]string{"attach", tt.arg}) | |
| 577 | 579 | require.NoError(t, err) | |
| 578 | - | connection, err := client.NewSessionConnection(portsmocks.NewMockTransport(t), client.SessionTarget{ | |
| 580 | + | connection, err := client.NewSessionConnection(sessionwire.NewClientConnection(portsmocks.NewMockTransport(t)), client.SessionTarget{ | |
| 579 | 581 | Intent: cmd.intent, | |
| 580 | 582 | SessionName: cmd.name, | |
| 581 | 583 | }) | |
| @@ -807,7 +809,13 @@ func TestDetachedLocalHelloIncludesTrueColor(t *testing.T) { | |||
| 807 | 809 | type namedDialer struct{ name string } | |
| 808 | 810 | ||
| 809 | 811 | func (d namedDialer) Dial(context.Context) (ports.Transport, error) { | |
| 810 | - | return nil, errors.New("not used") | |
| 812 | + | return nil, fmt.Errorf("not used: %s", d.name) | |
| 813 | + | } | |
| 814 | + | ||
| 815 | + | func requireNamedClientDialer(t *testing.T, ctx context.Context, dialer ports.ClientDialer, name string) { | |
| 816 | + | t.Helper() | |
| 817 | + | _, err := dialer.Dial(ctx) | |
| 818 | + | require.EqualError(t, err, "not used: "+name) | |
| 811 | 819 | } | |
| 812 | 820 | ||
| 813 | 821 | // fakeClipboardReader is a distinguishable ports.ClipboardReader used only to | |
| @@ -838,12 +846,9 @@ func TestRunAttachWithDepsSelectsRemoteTransport(t *testing.T) { | |||
| 838 | 846 | remoteDialerFactory: factory, | |
| 839 | 847 | selectedRemoteTransport: tt.selectedTransport, | |
| 840 | 848 | clipboard: clip, | |
| 841 | - | runClient: func(_ context.Context, deps client.Dependencies, request client.AttachRequest) error { | |
| 842 | - | nd, ok := deps.Dialer.(namedDialer) | |
| 843 | - | if !ok { | |
| 844 | - | t.Fatalf("dialer type = %T, want namedDialer", deps.Dialer) | |
| 845 | - | } | |
| 846 | - | gotDialer = nd.name | |
| 849 | + | runClient: func(ctx context.Context, deps client.Dependencies, request client.AttachRequest) error { | |
| 850 | + | requireNamedClientDialer(t, ctx, deps.Dialer, "remote") | |
| 851 | + | gotDialer = "remote" | |
| 847 | 852 | gotRemote = deps.Remote | |
| 848 | 853 | if !request.Remote || request.EnvironmentPolicy != protocol.EnvironmentPolicyClientOwned { | |
| 849 | 854 | t.Fatal("direct CLI remote attach must preserve client-owned environment policy") | |
| @@ -1064,12 +1069,9 @@ func TestRunAttachWithDepsBuildsLocalDialer(t *testing.T) { | |||
| 1064 | 1069 | localDialer: func() ports.Dialer { return namedDialer{name: "local"} }, | |
| 1065 | 1070 | remoteDialerFactory: factory, | |
| 1066 | 1071 | clipboard: &fakeClipboardReader{}, // must NOT reach runClient for a local attach | |
| 1067 | - | runClient: func(_ context.Context, deps client.Dependencies, request client.AttachRequest) error { | |
| 1068 | - | nd, ok := deps.Dialer.(namedDialer) | |
| 1069 | - | if !ok { | |
| 1070 | - | t.Fatalf("dialer type = %T, want namedDialer", deps.Dialer) | |
| 1071 | - | } | |
| 1072 | - | gotDialer = nd.name | |
| 1072 | + | runClient: func(ctx context.Context, deps client.Dependencies, request client.AttachRequest) error { | |
| 1073 | + | requireNamedClientDialer(t, ctx, deps.Dialer, "local") | |
| 1074 | + | gotDialer = "local" | |
| 1073 | 1075 | gotRemote = deps.Remote | |
| 1074 | 1076 | if request.Remote { | |
| 1075 | 1077 | t.Fatal("local carriage metadata must not be in the attach request") | |
| @@ -6,6 +6,7 @@ import ( | |||||
| 6 | "context" | 6 | "context" | ||
| 7 | "encoding/json" | 7 | "encoding/json" | ||
| 8 | "errors" | 8 | "errors" | ||
| 9 | + | "fmt" | |||
| 9 | "io" | 10 | "io" | ||
| 10 | "log/slog" | 11 | "log/slog" | ||
| 11 | "net" | 12 | "net" | ||
| @@ -18,6 +19,7 @@ import ( | |||||
| 18 | 19 | ||||
| 19 | "github.com/bnema/vev/internal/adapters/lifecycle" | 20 | "github.com/bnema/vev/internal/adapters/lifecycle" | ||
| 20 | remoteadapter "github.com/bnema/vev/internal/adapters/remote" | 21 | remoteadapter "github.com/bnema/vev/internal/adapters/remote" | ||
| 22 | + | "github.com/bnema/vev/internal/adapters/sessionwire" | |||
| 21 | "github.com/bnema/vev/internal/domain" | 23 | "github.com/bnema/vev/internal/domain" | ||
| 22 | "github.com/bnema/vev/internal/persist" | 24 | "github.com/bnema/vev/internal/persist" | ||
| 23 | "github.com/bnema/vev/internal/ports" | 25 | "github.com/bnema/vev/internal/ports" | ||
| @@ -575,7 +577,7 @@ func TestAttachTargetsCreateCommonSessionRequest(t *testing.T) { | |||||
| 575 | t.Run(tt.name, func(t *testing.T) { | 577 | t.Run(tt.name, func(t *testing.T) { | ||
| 576 | cmd, err := parseArgs([]string{"attach", tt.arg}) | 578 | cmd, err := parseArgs([]string{"attach", tt.arg}) | ||
| 577 | require.NoError(t, err) | 579 | require.NoError(t, err) | ||
| 578 | - | connection, err := client.NewSessionConnection(portsmocks.NewMockTransport(t), client.SessionTarget{ | 580 | + | connection, err := client.NewSessionConnection(sessionwire.NewClientConnection(portsmocks.NewMockTransport(t)), client.SessionTarget{ |
| 579 | Intent: cmd.intent, | 581 | Intent: cmd.intent, | ||
| 580 | SessionName: cmd.name, | 582 | SessionName: cmd.name, | ||
| 581 | }) | 583 | }) | ||
| @@ -807,7 +809,13 @@ func TestDetachedLocalHelloIncludesTrueColor(t *testing.T) { | |||||
| 807 | type namedDialer struct{ name string } | 809 | type namedDialer struct{ name string } | ||
| 808 | 810 | ||||
| 809 | func (d namedDialer) Dial(context.Context) (ports.Transport, error) { | 811 | func (d namedDialer) Dial(context.Context) (ports.Transport, error) { | ||
| 810 | - | return nil, errors.New("not used") | 812 | + | return nil, fmt.Errorf("not used: %s", d.name) |
| 813 | + | } | |||
| 814 | + | ||||
| 815 | + | func requireNamedClientDialer(t *testing.T, ctx context.Context, dialer ports.ClientDialer, name string) { | |||
| 816 | + | t.Helper() | |||
| 817 | + | _, err := dialer.Dial(ctx) | |||
| 818 | + | require.EqualError(t, err, "not used: "+name) | |||
| 811 | } | 819 | } | ||
| 812 | 820 | ||||
| 813 | // fakeClipboardReader is a distinguishable ports.ClipboardReader used only to | 821 | // fakeClipboardReader is a distinguishable ports.ClipboardReader used only to | ||
| @@ -838,12 +846,9 @@ func TestRunAttachWithDepsSelectsRemoteTransport(t *testing.T) { | |||||
| 838 | remoteDialerFactory: factory, | 846 | remoteDialerFactory: factory, | ||
| 839 | selectedRemoteTransport: tt.selectedTransport, | 847 | selectedRemoteTransport: tt.selectedTransport, | ||
| 840 | clipboard: clip, | 848 | clipboard: clip, | ||
| 841 | - | runClient: func(_ context.Context, deps client.Dependencies, request client.AttachRequest) error { | 849 | + | runClient: func(ctx context.Context, deps client.Dependencies, request client.AttachRequest) error { |
| 842 | - | nd, ok := deps.Dialer.(namedDialer) | 850 | + | requireNamedClientDialer(t, ctx, deps.Dialer, "remote") |
| 843 | - | if !ok { | 851 | + | gotDialer = "remote" |
| 844 | - | t.Fatalf("dialer type = %T, want namedDialer", deps.Dialer) | |||
| 845 | - | } | |||
| 846 | - | gotDialer = nd.name | |||
| 847 | gotRemote = deps.Remote | 852 | gotRemote = deps.Remote | ||
| 848 | if !request.Remote || request.EnvironmentPolicy != protocol.EnvironmentPolicyClientOwned { | 853 | if !request.Remote || request.EnvironmentPolicy != protocol.EnvironmentPolicyClientOwned { | ||
| 849 | t.Fatal("direct CLI remote attach must preserve client-owned environment policy") | 854 | t.Fatal("direct CLI remote attach must preserve client-owned environment policy") | ||
| @@ -1064,12 +1069,9 @@ func TestRunAttachWithDepsBuildsLocalDialer(t *testing.T) { | |||||
| 1064 | localDialer: func() ports.Dialer { return namedDialer{name: "local"} }, | 1069 | localDialer: func() ports.Dialer { return namedDialer{name: "local"} }, | ||
| 1065 | remoteDialerFactory: factory, | 1070 | remoteDialerFactory: factory, | ||
| 1066 | clipboard: &fakeClipboardReader{}, // must NOT reach runClient for a local attach | 1071 | clipboard: &fakeClipboardReader{}, // must NOT reach runClient for a local attach | ||
| 1067 | - | runClient: func(_ context.Context, deps client.Dependencies, request client.AttachRequest) error { | 1072 | + | runClient: func(ctx context.Context, deps client.Dependencies, request client.AttachRequest) error { |
| 1068 | - | nd, ok := deps.Dialer.(namedDialer) | 1073 | + | requireNamedClientDialer(t, ctx, deps.Dialer, "local") |
| 1069 | - | if !ok { | 1074 | + | gotDialer = "local" |
| 1070 | - | t.Fatalf("dialer type = %T, want namedDialer", deps.Dialer) | |||
| 1071 | - | } | |||
| 1072 | - | gotDialer = nd.name | |||
| 1073 | gotRemote = deps.Remote | 1075 | gotRemote = deps.Remote | ||
| 1074 | if request.Remote { | 1076 | if request.Remote { | ||
| 1075 | t.Fatal("local carriage metadata must not be in the attach request") | 1077 | t.Fatal("local carriage metadata must not be in the attach request") | ||
M internal/app/transport_observability_test.go +2 -4
| @@ -236,13 +236,11 @@ func TestRunAttachPropagatesOneObserverToRemoteTransportFactory(t *testing.T) { | |||
| 236 | 236 | t.Cleanup(func() { newRemoteDialerFactoryWithRuntimeObserver = originalFactory }) | |
| 237 | 237 | ||
| 238 | 238 | originalRunClient := runClientWithDeps | |
| 239 | - | runClientWithDeps = func(_ context.Context, deps client.Dependencies, _ client.AttachRequest) error { | |
| 239 | + | runClientWithDeps = func(ctx context.Context, deps client.Dependencies, _ client.AttachRequest) error { | |
| 240 | 240 | if deps.RuntimeObserver != observer { | |
| 241 | 241 | t.Fatalf("client transport observer = %v, want process observer %v", deps.RuntimeObserver, observer) | |
| 242 | 242 | } | |
| 243 | - | if deps.Dialer != (namedDialer{name: "remote"}) { | |
| 244 | - | t.Fatalf("remote transport dialer = %v, want configured factory dialer", deps.Dialer) | |
| 245 | - | } | |
| 243 | + | requireNamedClientDialer(t, ctx, deps.Dialer, "remote") | |
| 246 | 244 | return nil | |
| 247 | 245 | } | |
| 248 | 246 | t.Cleanup(func() { runClientWithDeps = originalRunClient }) | |
| @@ -236,13 +236,11 @@ func TestRunAttachPropagatesOneObserverToRemoteTransportFactory(t *testing.T) { | |||||
| 236 | t.Cleanup(func() { newRemoteDialerFactoryWithRuntimeObserver = originalFactory }) | 236 | t.Cleanup(func() { newRemoteDialerFactoryWithRuntimeObserver = originalFactory }) | ||
| 237 | 237 | ||||
| 238 | originalRunClient := runClientWithDeps | 238 | originalRunClient := runClientWithDeps | ||
| 239 | - | runClientWithDeps = func(_ context.Context, deps client.Dependencies, _ client.AttachRequest) error { | 239 | + | runClientWithDeps = func(ctx context.Context, deps client.Dependencies, _ client.AttachRequest) error { |
| 240 | if deps.RuntimeObserver != observer { | 240 | if deps.RuntimeObserver != observer { | ||
| 241 | t.Fatalf("client transport observer = %v, want process observer %v", deps.RuntimeObserver, observer) | 241 | t.Fatalf("client transport observer = %v, want process observer %v", deps.RuntimeObserver, observer) | ||
| 242 | } | 242 | } | ||
| 243 | - | if deps.Dialer != (namedDialer{name: "remote"}) { | 243 | + | requireNamedClientDialer(t, ctx, deps.Dialer, "remote") |
| 244 | - | t.Fatalf("remote transport dialer = %v, want configured factory dialer", deps.Dialer) | |||
| 245 | - | } | |||
| 246 | return nil | 244 | return nil | ||
| 247 | } | 245 | } | ||
| 248 | t.Cleanup(func() { runClientWithDeps = originalRunClient }) | 246 | t.Cleanup(func() { runClientWithDeps = originalRunClient }) | ||
M internal/ports/mocks/session_mock.go +402 -0
| @@ -5,11 +5,413 @@ | |||
| 5 | 5 | package portsmocks | |
| 6 | 6 | ||
| 7 | 7 | import ( | |
| 8 | + | "context" | |
| 9 | + | ||
| 8 | 10 | "github.com/bnema/vev/internal/ports" | |
| 9 | 11 | "github.com/bnema/vev/internal/protocol" | |
| 10 | 12 | mock "github.com/stretchr/testify/mock" | |
| 11 | 13 | ) | |
| 12 | 14 | ||
| 15 | + | // NewMockClientConnection creates a new instance of MockClientConnection. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 16 | + | // The first argument is typically a *testing.T value. | |
| 17 | + | func NewMockClientConnection(t interface { | |
| 18 | + | mock.TestingT | |
| 19 | + | Cleanup(func()) | |
| 20 | + | }) *MockClientConnection { | |
| 21 | + | mock := &MockClientConnection{} | |
| 22 | + | mock.Mock.Test(t) | |
| 23 | + | ||
| 24 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 25 | + | ||
| 26 | + | return mock | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | // MockClientConnection is an autogenerated mock type for the ClientConnection type | |
| 30 | + | type MockClientConnection struct { | |
| 31 | + | mock.Mock | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | type MockClientConnection_Expecter struct { | |
| 35 | + | mock *mock.Mock | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | func (_m *MockClientConnection) EXPECT() *MockClientConnection_Expecter { | |
| 39 | + | return &MockClientConnection_Expecter{mock: &_m.Mock} | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | // Capabilities provides a mock function for the type MockClientConnection | |
| 43 | + | func (_mock *MockClientConnection) Capabilities() protocol.ConnectionCapabilities { | |
| 44 | + | ret := _mock.Called() | |
| 45 | + | ||
| 46 | + | if len(ret) == 0 { | |
| 47 | + | panic("no return value specified for Capabilities") | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | var r0 protocol.ConnectionCapabilities | |
| 51 | + | if returnFunc, ok := ret.Get(0).(func() protocol.ConnectionCapabilities); ok { | |
| 52 | + | r0 = returnFunc() | |
| 53 | + | } else { | |
| 54 | + | r0 = ret.Get(0).(protocol.ConnectionCapabilities) | |
| 55 | + | } | |
| 56 | + | return r0 | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | // MockClientConnection_Capabilities_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Capabilities' | |
| 60 | + | type MockClientConnection_Capabilities_Call struct { | |
| 61 | + | *mock.Call | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | // Capabilities is a helper method to define mock.On call | |
| 65 | + | func (_e *MockClientConnection_Expecter) Capabilities() *MockClientConnection_Capabilities_Call { | |
| 66 | + | return &MockClientConnection_Capabilities_Call{Call: _e.mock.On("Capabilities")} | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | func (_c *MockClientConnection_Capabilities_Call) Run(run func()) *MockClientConnection_Capabilities_Call { | |
| 70 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 71 | + | run() | |
| 72 | + | }) | |
| 73 | + | return _c | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | func (_c *MockClientConnection_Capabilities_Call) Return(connectionCapabilities protocol.ConnectionCapabilities) *MockClientConnection_Capabilities_Call { | |
| 77 | + | _c.Call.Return(connectionCapabilities) | |
| 78 | + | return _c | |
| 79 | + | } | |
| 80 | + | ||
| 81 | + | func (_c *MockClientConnection_Capabilities_Call) RunAndReturn(run func() protocol.ConnectionCapabilities) *MockClientConnection_Capabilities_Call { | |
| 82 | + | _c.Call.Return(run) | |
| 83 | + | return _c | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | // Close provides a mock function for the type MockClientConnection | |
| 87 | + | func (_mock *MockClientConnection) Close() error { | |
| 88 | + | ret := _mock.Called() | |
| 89 | + | ||
| 90 | + | if len(ret) == 0 { | |
| 91 | + | panic("no return value specified for Close") | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | var r0 error | |
| 95 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |
| 96 | + | r0 = returnFunc() | |
| 97 | + | } else { | |
| 98 | + | r0 = ret.Error(0) | |
| 99 | + | } | |
| 100 | + | return r0 | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | // MockClientConnection_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |
| 104 | + | type MockClientConnection_Close_Call struct { | |
| 105 | + | *mock.Call | |
| 106 | + | } | |
| 107 | + | ||
| 108 | + | // Close is a helper method to define mock.On call | |
| 109 | + | func (_e *MockClientConnection_Expecter) Close() *MockClientConnection_Close_Call { | |
| 110 | + | return &MockClientConnection_Close_Call{Call: _e.mock.On("Close")} | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | func (_c *MockClientConnection_Close_Call) Run(run func()) *MockClientConnection_Close_Call { | |
| 114 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 115 | + | run() | |
| 116 | + | }) | |
| 117 | + | return _c | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | func (_c *MockClientConnection_Close_Call) Return(err error) *MockClientConnection_Close_Call { | |
| 121 | + | _c.Call.Return(err) | |
| 122 | + | return _c | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | func (_c *MockClientConnection_Close_Call) RunAndReturn(run func() error) *MockClientConnection_Close_Call { | |
| 126 | + | _c.Call.Return(run) | |
| 127 | + | return _c | |
| 128 | + | } | |
| 129 | + | ||
| 130 | + | // LinkEvents provides a mock function for the type MockClientConnection | |
| 131 | + | func (_mock *MockClientConnection) LinkEvents() <-chan ports.LinkEvent { | |
| 132 | + | ret := _mock.Called() | |
| 133 | + | ||
| 134 | + | if len(ret) == 0 { | |
| 135 | + | panic("no return value specified for LinkEvents") | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | var r0 <-chan ports.LinkEvent | |
| 139 | + | if returnFunc, ok := ret.Get(0).(func() <-chan ports.LinkEvent); ok { | |
| 140 | + | r0 = returnFunc() | |
| 141 | + | } else { | |
| 142 | + | if ret.Get(0) != nil { | |
| 143 | + | r0 = ret.Get(0).(<-chan ports.LinkEvent) | |
| 144 | + | } | |
| 145 | + | } | |
| 146 | + | return r0 | |
| 147 | + | } | |
| 148 | + | ||
| 149 | + | // MockClientConnection_LinkEvents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LinkEvents' | |
| 150 | + | type MockClientConnection_LinkEvents_Call struct { | |
| 151 | + | *mock.Call | |
| 152 | + | } | |
| 153 | + | ||
| 154 | + | // LinkEvents is a helper method to define mock.On call | |
| 155 | + | func (_e *MockClientConnection_Expecter) LinkEvents() *MockClientConnection_LinkEvents_Call { | |
| 156 | + | return &MockClientConnection_LinkEvents_Call{Call: _e.mock.On("LinkEvents")} | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | func (_c *MockClientConnection_LinkEvents_Call) Run(run func()) *MockClientConnection_LinkEvents_Call { | |
| 160 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 161 | + | run() | |
| 162 | + | }) | |
| 163 | + | return _c | |
| 164 | + | } | |
| 165 | + | ||
| 166 | + | func (_c *MockClientConnection_LinkEvents_Call) Return(linkEventCh <-chan ports.LinkEvent) *MockClientConnection_LinkEvents_Call { | |
| 167 | + | _c.Call.Return(linkEventCh) | |
| 168 | + | return _c | |
| 169 | + | } | |
| 170 | + | ||
| 171 | + | func (_c *MockClientConnection_LinkEvents_Call) RunAndReturn(run func() <-chan ports.LinkEvent) *MockClientConnection_LinkEvents_Call { | |
| 172 | + | _c.Call.Return(run) | |
| 173 | + | return _c | |
| 174 | + | } | |
| 175 | + | ||
| 176 | + | // LinkState provides a mock function for the type MockClientConnection | |
| 177 | + | func (_mock *MockClientConnection) LinkState() ports.LinkState { | |
| 178 | + | ret := _mock.Called() | |
| 179 | + | ||
| 180 | + | if len(ret) == 0 { | |
| 181 | + | panic("no return value specified for LinkState") | |
| 182 | + | } | |
| 183 | + | ||
| 184 | + | var r0 ports.LinkState | |
| 185 | + | if returnFunc, ok := ret.Get(0).(func() ports.LinkState); ok { | |
| 186 | + | r0 = returnFunc() | |
| 187 | + | } else { | |
| 188 | + | r0 = ret.Get(0).(ports.LinkState) | |
| 189 | + | } | |
| 190 | + | return r0 | |
| 191 | + | } | |
| 192 | + | ||
| 193 | + | // MockClientConnection_LinkState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LinkState' | |
| 194 | + | type MockClientConnection_LinkState_Call struct { | |
| 195 | + | *mock.Call | |
| 196 | + | } | |
| 197 | + | ||
| 198 | + | // LinkState is a helper method to define mock.On call | |
| 199 | + | func (_e *MockClientConnection_Expecter) LinkState() *MockClientConnection_LinkState_Call { | |
| 200 | + | return &MockClientConnection_LinkState_Call{Call: _e.mock.On("LinkState")} | |
| 201 | + | } | |
| 202 | + | ||
| 203 | + | func (_c *MockClientConnection_LinkState_Call) Run(run func()) *MockClientConnection_LinkState_Call { | |
| 204 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 205 | + | run() | |
| 206 | + | }) | |
| 207 | + | return _c | |
| 208 | + | } | |
| 209 | + | ||
| 210 | + | func (_c *MockClientConnection_LinkState_Call) Return(linkState ports.LinkState) *MockClientConnection_LinkState_Call { | |
| 211 | + | _c.Call.Return(linkState) | |
| 212 | + | return _c | |
| 213 | + | } | |
| 214 | + | ||
| 215 | + | func (_c *MockClientConnection_LinkState_Call) RunAndReturn(run func() ports.LinkState) *MockClientConnection_LinkState_Call { | |
| 216 | + | _c.Call.Return(run) | |
| 217 | + | return _c | |
| 218 | + | } | |
| 219 | + | ||
| 220 | + | // ReceiveServer provides a mock function for the type MockClientConnection | |
| 221 | + | func (_mock *MockClientConnection) ReceiveServer() (protocol.ServerMessage, error) { | |
| 222 | + | ret := _mock.Called() | |
| 223 | + | ||
| 224 | + | if len(ret) == 0 { | |
| 225 | + | panic("no return value specified for ReceiveServer") | |
| 226 | + | } | |
| 227 | + | ||
| 228 | + | var r0 protocol.ServerMessage | |
| 229 | + | var r1 error | |
| 230 | + | if returnFunc, ok := ret.Get(0).(func() (protocol.ServerMessage, error)); ok { | |
| 231 | + | return returnFunc() | |
| 232 | + | } | |
| 233 | + | if returnFunc, ok := ret.Get(0).(func() protocol.ServerMessage); ok { | |
| 234 | + | r0 = returnFunc() | |
| 235 | + | } else { | |
| 236 | + | if ret.Get(0) != nil { | |
| 237 | + | r0 = ret.Get(0).(protocol.ServerMessage) | |
| 238 | + | } | |
| 239 | + | } | |
| 240 | + | if returnFunc, ok := ret.Get(1).(func() error); ok { | |
| 241 | + | r1 = returnFunc() | |
| 242 | + | } else { | |
| 243 | + | r1 = ret.Error(1) | |
| 244 | + | } | |
| 245 | + | return r0, r1 | |
| 246 | + | } | |
| 247 | + | ||
| 248 | + | // MockClientConnection_ReceiveServer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReceiveServer' | |
| 249 | + | type MockClientConnection_ReceiveServer_Call struct { | |
| 250 | + | *mock.Call | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | // ReceiveServer is a helper method to define mock.On call | |
| 254 | + | func (_e *MockClientConnection_Expecter) ReceiveServer() *MockClientConnection_ReceiveServer_Call { | |
| 255 | + | return &MockClientConnection_ReceiveServer_Call{Call: _e.mock.On("ReceiveServer")} | |
| 256 | + | } | |
| 257 | + | ||
| 258 | + | func (_c *MockClientConnection_ReceiveServer_Call) Run(run func()) *MockClientConnection_ReceiveServer_Call { | |
| 259 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 260 | + | run() | |
| 261 | + | }) | |
| 262 | + | return _c | |
| 263 | + | } | |
| 264 | + | ||
| 265 | + | func (_c *MockClientConnection_ReceiveServer_Call) Return(serverMessage protocol.ServerMessage, err error) *MockClientConnection_ReceiveServer_Call { | |
| 266 | + | _c.Call.Return(serverMessage, err) | |
| 267 | + | return _c | |
| 268 | + | } | |
| 269 | + | ||
| 270 | + | func (_c *MockClientConnection_ReceiveServer_Call) RunAndReturn(run func() (protocol.ServerMessage, error)) *MockClientConnection_ReceiveServer_Call { | |
| 271 | + | _c.Call.Return(run) | |
| 272 | + | return _c | |
| 273 | + | } | |
| 274 | + | ||
| 275 | + | // SendClient provides a mock function for the type MockClientConnection | |
| 276 | + | func (_mock *MockClientConnection) SendClient(clientMessage protocol.ClientMessage) error { | |
| 277 | + | ret := _mock.Called(clientMessage) | |
| 278 | + | ||
| 279 | + | if len(ret) == 0 { | |
| 280 | + | panic("no return value specified for SendClient") | |
| 281 | + | } | |
| 282 | + | ||
| 283 | + | var r0 error | |
| 284 | + | if returnFunc, ok := ret.Get(0).(func(protocol.ClientMessage) error); ok { | |
| 285 | + | r0 = returnFunc(clientMessage) | |
| 286 | + | } else { | |
| 287 | + | r0 = ret.Error(0) | |
| 288 | + | } | |
| 289 | + | return r0 | |
| 290 | + | } | |
| 291 | + | ||
| 292 | + | // MockClientConnection_SendClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendClient' | |
| 293 | + | type MockClientConnection_SendClient_Call struct { | |
| 294 | + | *mock.Call | |
| 295 | + | } | |
| 296 | + | ||
| 297 | + | // SendClient is a helper method to define mock.On call | |
| 298 | + | // - clientMessage protocol.ClientMessage | |
| 299 | + | func (_e *MockClientConnection_Expecter) SendClient(clientMessage any) *MockClientConnection_SendClient_Call { | |
| 300 | + | return &MockClientConnection_SendClient_Call{Call: _e.mock.On("SendClient", clientMessage)} | |
| 301 | + | } | |
| 302 | + | ||
| 303 | + | func (_c *MockClientConnection_SendClient_Call) Run(run func(clientMessage protocol.ClientMessage)) *MockClientConnection_SendClient_Call { | |
| 304 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 305 | + | var arg0 protocol.ClientMessage | |
| 306 | + | if args[0] != nil { | |
| 307 | + | arg0 = args[0].(protocol.ClientMessage) | |
| 308 | + | } | |
| 309 | + | run( | |
| 310 | + | arg0, | |
| 311 | + | ) | |
| 312 | + | }) | |
| 313 | + | return _c | |
| 314 | + | } | |
| 315 | + | ||
| 316 | + | func (_c *MockClientConnection_SendClient_Call) Return(err error) *MockClientConnection_SendClient_Call { | |
| 317 | + | _c.Call.Return(err) | |
| 318 | + | return _c | |
| 319 | + | } | |
| 320 | + | ||
| 321 | + | func (_c *MockClientConnection_SendClient_Call) RunAndReturn(run func(clientMessage protocol.ClientMessage) error) *MockClientConnection_SendClient_Call { | |
| 322 | + | _c.Call.Return(run) | |
| 323 | + | return _c | |
| 324 | + | } | |
| 325 | + | ||
| 326 | + | // NewMockClientDialer creates a new instance of MockClientDialer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 327 | + | // The first argument is typically a *testing.T value. | |
| 328 | + | func NewMockClientDialer(t interface { | |
| 329 | + | mock.TestingT | |
| 330 | + | Cleanup(func()) | |
| 331 | + | }) *MockClientDialer { | |
| 332 | + | mock := &MockClientDialer{} | |
| 333 | + | mock.Mock.Test(t) | |
| 334 | + | ||
| 335 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 336 | + | ||
| 337 | + | return mock | |
| 338 | + | } | |
| 339 | + | ||
| 340 | + | // MockClientDialer is an autogenerated mock type for the ClientDialer type | |
| 341 | + | type MockClientDialer struct { | |
| 342 | + | mock.Mock | |
| 343 | + | } | |
| 344 | + | ||
| 345 | + | type MockClientDialer_Expecter struct { | |
| 346 | + | mock *mock.Mock | |
| 347 | + | } | |
| 348 | + | ||
| 349 | + | func (_m *MockClientDialer) EXPECT() *MockClientDialer_Expecter { | |
| 350 | + | return &MockClientDialer_Expecter{mock: &_m.Mock} | |
| 351 | + | } | |
| 352 | + | ||
| 353 | + | // Dial provides a mock function for the type MockClientDialer | |
| 354 | + | func (_mock *MockClientDialer) Dial(context1 context.Context) (ports.ClientConnection, error) { | |
| 355 | + | ret := _mock.Called(context1) | |
| 356 | + | ||
| 357 | + | if len(ret) == 0 { | |
| 358 | + | panic("no return value specified for Dial") | |
| 359 | + | } | |
| 360 | + | ||
| 361 | + | var r0 ports.ClientConnection | |
| 362 | + | var r1 error | |
| 363 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) (ports.ClientConnection, error)); ok { | |
| 364 | + | return returnFunc(context1) | |
| 365 | + | } | |
| 366 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) ports.ClientConnection); ok { | |
| 367 | + | r0 = returnFunc(context1) | |
| 368 | + | } else { | |
| 369 | + | if ret.Get(0) != nil { | |
| 370 | + | r0 = ret.Get(0).(ports.ClientConnection) | |
| 371 | + | } | |
| 372 | + | } | |
| 373 | + | if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { | |
| 374 | + | r1 = returnFunc(context1) | |
| 375 | + | } else { | |
| 376 | + | r1 = ret.Error(1) | |
| 377 | + | } | |
| 378 | + | return r0, r1 | |
| 379 | + | } | |
| 380 | + | ||
| 381 | + | // MockClientDialer_Dial_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Dial' | |
| 382 | + | type MockClientDialer_Dial_Call struct { | |
| 383 | + | *mock.Call | |
| 384 | + | } | |
| 385 | + | ||
| 386 | + | // Dial is a helper method to define mock.On call | |
| 387 | + | // - context1 context.Context | |
| 388 | + | func (_e *MockClientDialer_Expecter) Dial(context1 any) *MockClientDialer_Dial_Call { | |
| 389 | + | return &MockClientDialer_Dial_Call{Call: _e.mock.On("Dial", context1)} | |
| 390 | + | } | |
| 391 | + | ||
| 392 | + | func (_c *MockClientDialer_Dial_Call) Run(run func(context1 context.Context)) *MockClientDialer_Dial_Call { | |
| 393 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 394 | + | var arg0 context.Context | |
| 395 | + | if args[0] != nil { | |
| 396 | + | arg0 = args[0].(context.Context) | |
| 397 | + | } | |
| 398 | + | run( | |
| 399 | + | arg0, | |
| 400 | + | ) | |
| 401 | + | }) | |
| 402 | + | return _c | |
| 403 | + | } | |
| 404 | + | ||
| 405 | + | func (_c *MockClientDialer_Dial_Call) Return(clientConnection ports.ClientConnection, err error) *MockClientDialer_Dial_Call { | |
| 406 | + | _c.Call.Return(clientConnection, err) | |
| 407 | + | return _c | |
| 408 | + | } | |
| 409 | + | ||
| 410 | + | func (_c *MockClientDialer_Dial_Call) RunAndReturn(run func(context1 context.Context) (ports.ClientConnection, error)) *MockClientDialer_Dial_Call { | |
| 411 | + | _c.Call.Return(run) | |
| 412 | + | return _c | |
| 413 | + | } | |
| 414 | + | ||
| 13 | 415 | // NewMockServerConnection creates a new instance of MockServerConnection. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 14 | 416 | // The first argument is typically a *testing.T value. | |
| 15 | 417 | func NewMockServerConnection(t interface { | |
| @@ -5,11 +5,413 @@ | |||||
| 5 | package portsmocks | 5 | package portsmocks | ||
| 6 | 6 | ||||
| 7 | import ( | 7 | import ( | ||
| 8 | + | "context" | |||
| 9 | + | ||||
| 8 | "github.com/bnema/vev/internal/ports" | 10 | "github.com/bnema/vev/internal/ports" | ||
| 9 | "github.com/bnema/vev/internal/protocol" | 11 | "github.com/bnema/vev/internal/protocol" | ||
| 10 | mock "github.com/stretchr/testify/mock" | 12 | mock "github.com/stretchr/testify/mock" | ||
| 11 | ) | 13 | ) | ||
| 12 | 14 | ||||
| 15 | + | // NewMockClientConnection creates a new instance of MockClientConnection. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 16 | + | // The first argument is typically a *testing.T value. | |||
| 17 | + | func NewMockClientConnection(t interface { | |||
| 18 | + | mock.TestingT | |||
| 19 | + | Cleanup(func()) | |||
| 20 | + | }) *MockClientConnection { | |||
| 21 | + | mock := &MockClientConnection{} | |||
| 22 | + | mock.Mock.Test(t) | |||
| 23 | + | ||||
| 24 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |||
| 25 | + | ||||
| 26 | + | return mock | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | // MockClientConnection is an autogenerated mock type for the ClientConnection type | |||
| 30 | + | type MockClientConnection struct { | |||
| 31 | + | mock.Mock | |||
| 32 | + | } | |||
| 33 | + | ||||
| 34 | + | type MockClientConnection_Expecter struct { | |||
| 35 | + | mock *mock.Mock | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | func (_m *MockClientConnection) EXPECT() *MockClientConnection_Expecter { | |||
| 39 | + | return &MockClientConnection_Expecter{mock: &_m.Mock} | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | // Capabilities provides a mock function for the type MockClientConnection | |||
| 43 | + | func (_mock *MockClientConnection) Capabilities() protocol.ConnectionCapabilities { | |||
| 44 | + | ret := _mock.Called() | |||
| 45 | + | ||||
| 46 | + | if len(ret) == 0 { | |||
| 47 | + | panic("no return value specified for Capabilities") | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | var r0 protocol.ConnectionCapabilities | |||
| 51 | + | if returnFunc, ok := ret.Get(0).(func() protocol.ConnectionCapabilities); ok { | |||
| 52 | + | r0 = returnFunc() | |||
| 53 | + | } else { | |||
| 54 | + | r0 = ret.Get(0).(protocol.ConnectionCapabilities) | |||
| 55 | + | } | |||
| 56 | + | return r0 | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | // MockClientConnection_Capabilities_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Capabilities' | |||
| 60 | + | type MockClientConnection_Capabilities_Call struct { | |||
| 61 | + | *mock.Call | |||
| 62 | + | } | |||
| 63 | + | ||||
| 64 | + | // Capabilities is a helper method to define mock.On call | |||
| 65 | + | func (_e *MockClientConnection_Expecter) Capabilities() *MockClientConnection_Capabilities_Call { | |||
| 66 | + | return &MockClientConnection_Capabilities_Call{Call: _e.mock.On("Capabilities")} | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | func (_c *MockClientConnection_Capabilities_Call) Run(run func()) *MockClientConnection_Capabilities_Call { | |||
| 70 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 71 | + | run() | |||
| 72 | + | }) | |||
| 73 | + | return _c | |||
| 74 | + | } | |||
| 75 | + | ||||
| 76 | + | func (_c *MockClientConnection_Capabilities_Call) Return(connectionCapabilities protocol.ConnectionCapabilities) *MockClientConnection_Capabilities_Call { | |||
| 77 | + | _c.Call.Return(connectionCapabilities) | |||
| 78 | + | return _c | |||
| 79 | + | } | |||
| 80 | + | ||||
| 81 | + | func (_c *MockClientConnection_Capabilities_Call) RunAndReturn(run func() protocol.ConnectionCapabilities) *MockClientConnection_Capabilities_Call { | |||
| 82 | + | _c.Call.Return(run) | |||
| 83 | + | return _c | |||
| 84 | + | } | |||
| 85 | + | ||||
| 86 | + | // Close provides a mock function for the type MockClientConnection | |||
| 87 | + | func (_mock *MockClientConnection) Close() error { | |||
| 88 | + | ret := _mock.Called() | |||
| 89 | + | ||||
| 90 | + | if len(ret) == 0 { | |||
| 91 | + | panic("no return value specified for Close") | |||
| 92 | + | } | |||
| 93 | + | ||||
| 94 | + | var r0 error | |||
| 95 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |||
| 96 | + | r0 = returnFunc() | |||
| 97 | + | } else { | |||
| 98 | + | r0 = ret.Error(0) | |||
| 99 | + | } | |||
| 100 | + | return r0 | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | // MockClientConnection_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |||
| 104 | + | type MockClientConnection_Close_Call struct { | |||
| 105 | + | *mock.Call | |||
| 106 | + | } | |||
| 107 | + | ||||
| 108 | + | // Close is a helper method to define mock.On call | |||
| 109 | + | func (_e *MockClientConnection_Expecter) Close() *MockClientConnection_Close_Call { | |||
| 110 | + | return &MockClientConnection_Close_Call{Call: _e.mock.On("Close")} | |||
| 111 | + | } | |||
| 112 | + | ||||
| 113 | + | func (_c *MockClientConnection_Close_Call) Run(run func()) *MockClientConnection_Close_Call { | |||
| 114 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 115 | + | run() | |||
| 116 | + | }) | |||
| 117 | + | return _c | |||
| 118 | + | } | |||
| 119 | + | ||||
| 120 | + | func (_c *MockClientConnection_Close_Call) Return(err error) *MockClientConnection_Close_Call { | |||
| 121 | + | _c.Call.Return(err) | |||
| 122 | + | return _c | |||
| 123 | + | } | |||
| 124 | + | ||||
| 125 | + | func (_c *MockClientConnection_Close_Call) RunAndReturn(run func() error) *MockClientConnection_Close_Call { | |||
| 126 | + | _c.Call.Return(run) | |||
| 127 | + | return _c | |||
| 128 | + | } | |||
| 129 | + | ||||
| 130 | + | // LinkEvents provides a mock function for the type MockClientConnection | |||
| 131 | + | func (_mock *MockClientConnection) LinkEvents() <-chan ports.LinkEvent { | |||
| 132 | + | ret := _mock.Called() | |||
| 133 | + | ||||
| 134 | + | if len(ret) == 0 { | |||
| 135 | + | panic("no return value specified for LinkEvents") | |||
| 136 | + | } | |||
| 137 | + | ||||
| 138 | + | var r0 <-chan ports.LinkEvent | |||
| 139 | + | if returnFunc, ok := ret.Get(0).(func() <-chan ports.LinkEvent); ok { | |||
| 140 | + | r0 = returnFunc() | |||
| 141 | + | } else { | |||
| 142 | + | if ret.Get(0) != nil { | |||
| 143 | + | r0 = ret.Get(0).(<-chan ports.LinkEvent) | |||
| 144 | + | } | |||
| 145 | + | } | |||
| 146 | + | return r0 | |||
| 147 | + | } | |||
| 148 | + | ||||
| 149 | + | // MockClientConnection_LinkEvents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LinkEvents' | |||
| 150 | + | type MockClientConnection_LinkEvents_Call struct { | |||
| 151 | + | *mock.Call | |||
| 152 | + | } | |||
| 153 | + | ||||
| 154 | + | // LinkEvents is a helper method to define mock.On call | |||
| 155 | + | func (_e *MockClientConnection_Expecter) LinkEvents() *MockClientConnection_LinkEvents_Call { | |||
| 156 | + | return &MockClientConnection_LinkEvents_Call{Call: _e.mock.On("LinkEvents")} | |||
| 157 | + | } | |||
| 158 | + | ||||
| 159 | + | func (_c *MockClientConnection_LinkEvents_Call) Run(run func()) *MockClientConnection_LinkEvents_Call { | |||
| 160 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 161 | + | run() | |||
| 162 | + | }) | |||
| 163 | + | return _c | |||
| 164 | + | } | |||
| 165 | + | ||||
| 166 | + | func (_c *MockClientConnection_LinkEvents_Call) Return(linkEventCh <-chan ports.LinkEvent) *MockClientConnection_LinkEvents_Call { | |||
| 167 | + | _c.Call.Return(linkEventCh) | |||
| 168 | + | return _c | |||
| 169 | + | } | |||
| 170 | + | ||||
| 171 | + | func (_c *MockClientConnection_LinkEvents_Call) RunAndReturn(run func() <-chan ports.LinkEvent) *MockClientConnection_LinkEvents_Call { | |||
| 172 | + | _c.Call.Return(run) | |||
| 173 | + | return _c | |||
| 174 | + | } | |||
| 175 | + | ||||
| 176 | + | // LinkState provides a mock function for the type MockClientConnection | |||
| 177 | + | func (_mock *MockClientConnection) LinkState() ports.LinkState { | |||
| 178 | + | ret := _mock.Called() | |||
| 179 | + | ||||
| 180 | + | if len(ret) == 0 { | |||
| 181 | + | panic("no return value specified for LinkState") | |||
| 182 | + | } | |||
| 183 | + | ||||
| 184 | + | var r0 ports.LinkState | |||
| 185 | + | if returnFunc, ok := ret.Get(0).(func() ports.LinkState); ok { | |||
| 186 | + | r0 = returnFunc() | |||
| 187 | + | } else { | |||
| 188 | + | r0 = ret.Get(0).(ports.LinkState) | |||
| 189 | + | } | |||
| 190 | + | return r0 | |||
| 191 | + | } | |||
| 192 | + | ||||
| 193 | + | // MockClientConnection_LinkState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LinkState' | |||
| 194 | + | type MockClientConnection_LinkState_Call struct { | |||
| 195 | + | *mock.Call | |||
| 196 | + | } | |||
| 197 | + | ||||
| 198 | + | // LinkState is a helper method to define mock.On call | |||
| 199 | + | func (_e *MockClientConnection_Expecter) LinkState() *MockClientConnection_LinkState_Call { | |||
| 200 | + | return &MockClientConnection_LinkState_Call{Call: _e.mock.On("LinkState")} | |||
| 201 | + | } | |||
| 202 | + | ||||
| 203 | + | func (_c *MockClientConnection_LinkState_Call) Run(run func()) *MockClientConnection_LinkState_Call { | |||
| 204 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 205 | + | run() | |||
| 206 | + | }) | |||
| 207 | + | return _c | |||
| 208 | + | } | |||
| 209 | + | ||||
| 210 | + | func (_c *MockClientConnection_LinkState_Call) Return(linkState ports.LinkState) *MockClientConnection_LinkState_Call { | |||
| 211 | + | _c.Call.Return(linkState) | |||
| 212 | + | return _c | |||
| 213 | + | } | |||
| 214 | + | ||||
| 215 | + | func (_c *MockClientConnection_LinkState_Call) RunAndReturn(run func() ports.LinkState) *MockClientConnection_LinkState_Call { | |||
| 216 | + | _c.Call.Return(run) | |||
| 217 | + | return _c | |||
| 218 | + | } | |||
| 219 | + | ||||
| 220 | + | // ReceiveServer provides a mock function for the type MockClientConnection | |||
| 221 | + | func (_mock *MockClientConnection) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 222 | + | ret := _mock.Called() | |||
| 223 | + | ||||
| 224 | + | if len(ret) == 0 { | |||
| 225 | + | panic("no return value specified for ReceiveServer") | |||
| 226 | + | } | |||
| 227 | + | ||||
| 228 | + | var r0 protocol.ServerMessage | |||
| 229 | + | var r1 error | |||
| 230 | + | if returnFunc, ok := ret.Get(0).(func() (protocol.ServerMessage, error)); ok { | |||
| 231 | + | return returnFunc() | |||
| 232 | + | } | |||
| 233 | + | if returnFunc, ok := ret.Get(0).(func() protocol.ServerMessage); ok { | |||
| 234 | + | r0 = returnFunc() | |||
| 235 | + | } else { | |||
| 236 | + | if ret.Get(0) != nil { | |||
| 237 | + | r0 = ret.Get(0).(protocol.ServerMessage) | |||
| 238 | + | } | |||
| 239 | + | } | |||
| 240 | + | if returnFunc, ok := ret.Get(1).(func() error); ok { | |||
| 241 | + | r1 = returnFunc() | |||
| 242 | + | } else { | |||
| 243 | + | r1 = ret.Error(1) | |||
| 244 | + | } | |||
| 245 | + | return r0, r1 | |||
| 246 | + | } | |||
| 247 | + | ||||
| 248 | + | // MockClientConnection_ReceiveServer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReceiveServer' | |||
| 249 | + | type MockClientConnection_ReceiveServer_Call struct { | |||
| 250 | + | *mock.Call | |||
| 251 | + | } | |||
| 252 | + | ||||
| 253 | + | // ReceiveServer is a helper method to define mock.On call | |||
| 254 | + | func (_e *MockClientConnection_Expecter) ReceiveServer() *MockClientConnection_ReceiveServer_Call { | |||
| 255 | + | return &MockClientConnection_ReceiveServer_Call{Call: _e.mock.On("ReceiveServer")} | |||
| 256 | + | } | |||
| 257 | + | ||||
| 258 | + | func (_c *MockClientConnection_ReceiveServer_Call) Run(run func()) *MockClientConnection_ReceiveServer_Call { | |||
| 259 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 260 | + | run() | |||
| 261 | + | }) | |||
| 262 | + | return _c | |||
| 263 | + | } | |||
| 264 | + | ||||
| 265 | + | func (_c *MockClientConnection_ReceiveServer_Call) Return(serverMessage protocol.ServerMessage, err error) *MockClientConnection_ReceiveServer_Call { | |||
| 266 | + | _c.Call.Return(serverMessage, err) | |||
| 267 | + | return _c | |||
| 268 | + | } | |||
| 269 | + | ||||
| 270 | + | func (_c *MockClientConnection_ReceiveServer_Call) RunAndReturn(run func() (protocol.ServerMessage, error)) *MockClientConnection_ReceiveServer_Call { | |||
| 271 | + | _c.Call.Return(run) | |||
| 272 | + | return _c | |||
| 273 | + | } | |||
| 274 | + | ||||
| 275 | + | // SendClient provides a mock function for the type MockClientConnection | |||
| 276 | + | func (_mock *MockClientConnection) SendClient(clientMessage protocol.ClientMessage) error { | |||
| 277 | + | ret := _mock.Called(clientMessage) | |||
| 278 | + | ||||
| 279 | + | if len(ret) == 0 { | |||
| 280 | + | panic("no return value specified for SendClient") | |||
| 281 | + | } | |||
| 282 | + | ||||
| 283 | + | var r0 error | |||
| 284 | + | if returnFunc, ok := ret.Get(0).(func(protocol.ClientMessage) error); ok { | |||
| 285 | + | r0 = returnFunc(clientMessage) | |||
| 286 | + | } else { | |||
| 287 | + | r0 = ret.Error(0) | |||
| 288 | + | } | |||
| 289 | + | return r0 | |||
| 290 | + | } | |||
| 291 | + | ||||
| 292 | + | // MockClientConnection_SendClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendClient' | |||
| 293 | + | type MockClientConnection_SendClient_Call struct { | |||
| 294 | + | *mock.Call | |||
| 295 | + | } | |||
| 296 | + | ||||
| 297 | + | // SendClient is a helper method to define mock.On call | |||
| 298 | + | // - clientMessage protocol.ClientMessage | |||
| 299 | + | func (_e *MockClientConnection_Expecter) SendClient(clientMessage any) *MockClientConnection_SendClient_Call { | |||
| 300 | + | return &MockClientConnection_SendClient_Call{Call: _e.mock.On("SendClient", clientMessage)} | |||
| 301 | + | } | |||
| 302 | + | ||||
| 303 | + | func (_c *MockClientConnection_SendClient_Call) Run(run func(clientMessage protocol.ClientMessage)) *MockClientConnection_SendClient_Call { | |||
| 304 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 305 | + | var arg0 protocol.ClientMessage | |||
| 306 | + | if args[0] != nil { | |||
| 307 | + | arg0 = args[0].(protocol.ClientMessage) | |||
| 308 | + | } | |||
| 309 | + | run( | |||
| 310 | + | arg0, | |||
| 311 | + | ) | |||
| 312 | + | }) | |||
| 313 | + | return _c | |||
| 314 | + | } | |||
| 315 | + | ||||
| 316 | + | func (_c *MockClientConnection_SendClient_Call) Return(err error) *MockClientConnection_SendClient_Call { | |||
| 317 | + | _c.Call.Return(err) | |||
| 318 | + | return _c | |||
| 319 | + | } | |||
| 320 | + | ||||
| 321 | + | func (_c *MockClientConnection_SendClient_Call) RunAndReturn(run func(clientMessage protocol.ClientMessage) error) *MockClientConnection_SendClient_Call { | |||
| 322 | + | _c.Call.Return(run) | |||
| 323 | + | return _c | |||
| 324 | + | } | |||
| 325 | + | ||||
| 326 | + | // NewMockClientDialer creates a new instance of MockClientDialer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 327 | + | // The first argument is typically a *testing.T value. | |||
| 328 | + | func NewMockClientDialer(t interface { | |||
| 329 | + | mock.TestingT | |||
| 330 | + | Cleanup(func()) | |||
| 331 | + | }) *MockClientDialer { | |||
| 332 | + | mock := &MockClientDialer{} | |||
| 333 | + | mock.Mock.Test(t) | |||
| 334 | + | ||||
| 335 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |||
| 336 | + | ||||
| 337 | + | return mock | |||
| 338 | + | } | |||
| 339 | + | ||||
| 340 | + | // MockClientDialer is an autogenerated mock type for the ClientDialer type | |||
| 341 | + | type MockClientDialer struct { | |||
| 342 | + | mock.Mock | |||
| 343 | + | } | |||
| 344 | + | ||||
| 345 | + | type MockClientDialer_Expecter struct { | |||
| 346 | + | mock *mock.Mock | |||
| 347 | + | } | |||
| 348 | + | ||||
| 349 | + | func (_m *MockClientDialer) EXPECT() *MockClientDialer_Expecter { | |||
| 350 | + | return &MockClientDialer_Expecter{mock: &_m.Mock} | |||
| 351 | + | } | |||
| 352 | + | ||||
| 353 | + | // Dial provides a mock function for the type MockClientDialer | |||
| 354 | + | func (_mock *MockClientDialer) Dial(context1 context.Context) (ports.ClientConnection, error) { | |||
| 355 | + | ret := _mock.Called(context1) | |||
| 356 | + | ||||
| 357 | + | if len(ret) == 0 { | |||
| 358 | + | panic("no return value specified for Dial") | |||
| 359 | + | } | |||
| 360 | + | ||||
| 361 | + | var r0 ports.ClientConnection | |||
| 362 | + | var r1 error | |||
| 363 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) (ports.ClientConnection, error)); ok { | |||
| 364 | + | return returnFunc(context1) | |||
| 365 | + | } | |||
| 366 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) ports.ClientConnection); ok { | |||
| 367 | + | r0 = returnFunc(context1) | |||
| 368 | + | } else { | |||
| 369 | + | if ret.Get(0) != nil { | |||
| 370 | + | r0 = ret.Get(0).(ports.ClientConnection) | |||
| 371 | + | } | |||
| 372 | + | } | |||
| 373 | + | if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { | |||
| 374 | + | r1 = returnFunc(context1) | |||
| 375 | + | } else { | |||
| 376 | + | r1 = ret.Error(1) | |||
| 377 | + | } | |||
| 378 | + | return r0, r1 | |||
| 379 | + | } | |||
| 380 | + | ||||
| 381 | + | // MockClientDialer_Dial_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Dial' | |||
| 382 | + | type MockClientDialer_Dial_Call struct { | |||
| 383 | + | *mock.Call | |||
| 384 | + | } | |||
| 385 | + | ||||
| 386 | + | // Dial is a helper method to define mock.On call | |||
| 387 | + | // - context1 context.Context | |||
| 388 | + | func (_e *MockClientDialer_Expecter) Dial(context1 any) *MockClientDialer_Dial_Call { | |||
| 389 | + | return &MockClientDialer_Dial_Call{Call: _e.mock.On("Dial", context1)} | |||
| 390 | + | } | |||
| 391 | + | ||||
| 392 | + | func (_c *MockClientDialer_Dial_Call) Run(run func(context1 context.Context)) *MockClientDialer_Dial_Call { | |||
| 393 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 394 | + | var arg0 context.Context | |||
| 395 | + | if args[0] != nil { | |||
| 396 | + | arg0 = args[0].(context.Context) | |||
| 397 | + | } | |||
| 398 | + | run( | |||
| 399 | + | arg0, | |||
| 400 | + | ) | |||
| 401 | + | }) | |||
| 402 | + | return _c | |||
| 403 | + | } | |||
| 404 | + | ||||
| 405 | + | func (_c *MockClientDialer_Dial_Call) Return(clientConnection ports.ClientConnection, err error) *MockClientDialer_Dial_Call { | |||
| 406 | + | _c.Call.Return(clientConnection, err) | |||
| 407 | + | return _c | |||
| 408 | + | } | |||
| 409 | + | ||||
| 410 | + | func (_c *MockClientDialer_Dial_Call) RunAndReturn(run func(context1 context.Context) (ports.ClientConnection, error)) *MockClientDialer_Dial_Call { | |||
| 411 | + | _c.Call.Return(run) | |||
| 412 | + | return _c | |||
| 413 | + | } | |||
| 414 | + | ||||
| 13 | // NewMockServerConnection creates a new instance of MockServerConnection. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | 415 | // NewMockServerConnection creates a new instance of MockServerConnection. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | ||
| 14 | // The first argument is typically a *testing.T value. | 416 | // The first argument is typically a *testing.T value. | ||
| 15 | func NewMockServerConnection(t interface { | 417 | func NewMockServerConnection(t interface { | ||
M internal/ports/session.go +20 -1
| @@ -1,6 +1,25 @@ | |||
| 1 | 1 | package ports | |
| 2 | 2 | ||
| 3 | - | import "github.com/bnema/vev/internal/protocol" | |
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/vev/internal/protocol" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // ClientConnection is one typed client-side session channel. | |
| 10 | + | type ClientConnection interface { | |
| 11 | + | SendClient(protocol.ClientMessage) error | |
| 12 | + | ReceiveServer() (protocol.ServerMessage, error) | |
| 13 | + | Capabilities() protocol.ConnectionCapabilities | |
| 14 | + | LinkState() LinkState | |
| 15 | + | LinkEvents() <-chan LinkEvent | |
| 16 | + | Close() error | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | // ClientDialer establishes typed client-side session connections. | |
| 20 | + | type ClientDialer interface { | |
| 21 | + | Dial(context.Context) (ClientConnection, error) | |
| 22 | + | } | |
| 4 | 23 | ||
| 5 | 24 | // ServerConnection is one typed client/daemon session channel. Close must be | |
| 6 | 25 | // concurrent-safe and unblock ReceiveClient and every send mode. | |
| @@ -1,6 +1,25 @@ | |||||
| 1 | package ports | 1 | package ports | ||
| 2 | 2 | ||||
| 3 | - | import "github.com/bnema/vev/internal/protocol" | 3 | + | import ( |
| 4 | + | "context" | |||
| 5 | + | ||||
| 6 | + | "github.com/bnema/vev/internal/protocol" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | // ClientConnection is one typed client-side session channel. | |||
| 10 | + | type ClientConnection interface { | |||
| 11 | + | SendClient(protocol.ClientMessage) error | |||
| 12 | + | ReceiveServer() (protocol.ServerMessage, error) | |||
| 13 | + | Capabilities() protocol.ConnectionCapabilities | |||
| 14 | + | LinkState() LinkState | |||
| 15 | + | LinkEvents() <-chan LinkEvent | |||
| 16 | + | Close() error | |||
| 17 | + | } | |||
| 18 | + | ||||
| 19 | + | // ClientDialer establishes typed client-side session connections. | |||
| 20 | + | type ClientDialer interface { | |||
| 21 | + | Dial(context.Context) (ClientConnection, error) | |||
| 22 | + | } | |||
| 4 | 23 | ||||
| 5 | // ServerConnection is one typed client/daemon session channel. Close must be | 24 | // ServerConnection is one typed client/daemon session channel. Close must be | ||
| 6 | // concurrent-safe and unblock ReceiveClient and every send mode. | 25 | // concurrent-safe and unblock ReceiveClient and every send mode. | ||
M internal/usecase/client/ack_queue_test.go +11 -9
| @@ -7,6 +7,8 @@ import ( | |||
| 7 | 7 | "testing" | |
| 8 | 8 | "time" | |
| 9 | 9 | ||
| 10 | + | "github.com/bnema/vev/internal/domain" | |
| 11 | + | "github.com/bnema/vev/internal/protocol" | |
| 10 | 12 | "github.com/bnema/vev/internal/protocol/wire" | |
| 11 | 13 | "github.com/stretchr/testify/require" | |
| 12 | 14 | ) | |
| @@ -18,9 +20,9 @@ func (t *ackRecordingTransport) Recv() (wire.Frame, error) { return wire.Frame{} | |||
| 18 | 20 | func (t *ackRecordingTransport) Close() error { return nil } | |
| 19 | 21 | ||
| 20 | 22 | func TestCumulativeAckBypassesFullNormalSendQueue(t *testing.T) { | |
| 21 | - | normal := make(chan wire.Frame, sendQueueDepth) | |
| 23 | + | normal := make(chan protocol.ClientMessage, sendQueueDepth) | |
| 22 | 24 | for range cap(normal) { | |
| 23 | - | normal <- wire.Frame{Type: wire.MsgInput} | |
| 25 | + | normal <- protocol.Input{} | |
| 24 | 26 | } | |
| 25 | 27 | const epoch = 7 | |
| 26 | 28 | acks := newCumulativeAckQueue() | |
| @@ -45,8 +47,8 @@ func TestCumulativeAckBypassesFullNormalSendQueue(t *testing.T) { | |||
| 45 | 47 | func TestSamePeerSwitchControlBypassesHeldInput(t *testing.T) { | |
| 46 | 48 | ctx, cancel := context.WithCancel(context.Background()) | |
| 47 | 49 | defer cancel() | |
| 48 | - | normal := make(chan wire.Frame, 1) | |
| 49 | - | control := make(chan wire.Frame, 1) | |
| 50 | + | normal := make(chan protocol.ClientMessage, 1) | |
| 51 | + | control := make(chan protocol.ClientMessage, 1) | |
| 50 | 52 | gate := newSamePeerInputGate() | |
| 51 | 53 | gate.setPaused(true) | |
| 52 | 54 | held := make(chan struct{}) | |
| @@ -56,9 +58,9 @@ func TestSamePeerSwitchControlBypassesHeldInput(t *testing.T) { | |||
| 56 | 58 | errs := make(chan error, 1) | |
| 57 | 59 | go runSender(ctx, cancel, transport, control, nil, normal, gate, acks, errs, slog.Default()) | |
| 58 | 60 | ||
| 59 | - | normal <- wire.Frame{Type: wire.MsgInput, Payload: []byte("held")} | |
| 61 | + | normal <- protocol.Input{Data: []byte("held")} | |
| 60 | 62 | awaitSenderSignal(t, held) | |
| 61 | - | control <- wire.Frame{Type: wire.MsgSamePeerSwitchRequest, Payload: []byte("switch")} | |
| 63 | + | control <- protocol.SamePeerSwitchRequest{RequestID: 1, Target: protocol.ExactSessionTarget{LifecycleID: domain.SessionLifecycleID{1}, SessionName: "work"}} | |
| 62 | 64 | if got := awaitSenderFrame(t, transport.sent); got.Type != wire.MsgSamePeerSwitchRequest { | |
| 63 | 65 | t.Fatalf("first frame = %d, want same-peer switch request", got.Type) | |
| 64 | 66 | } | |
| @@ -76,14 +78,14 @@ func TestSamePeerSwitchControlBypassesHeldInput(t *testing.T) { | |||
| 76 | 78 | func TestSenderBarrierLeavesPausedInputQueued(t *testing.T) { | |
| 77 | 79 | ctx, cancel := context.WithCancel(context.Background()) | |
| 78 | 80 | defer cancel() | |
| 79 | - | normal := make(chan wire.Frame, 1) | |
| 81 | + | normal := make(chan protocol.ClientMessage, 1) | |
| 80 | 82 | barriers := make(chan chan struct{}) | |
| 81 | 83 | gate := newSamePeerInputGate() | |
| 82 | 84 | gate.setPaused(true) | |
| 83 | 85 | transport := &ackRecordingTransport{sent: make(chan wire.Frame, 1)} | |
| 84 | 86 | go runSender(ctx, cancel, transport, nil, barriers, normal, gate, newCumulativeAckQueue(), make(chan error, 1), slog.Default()) | |
| 85 | 87 | ||
| 86 | - | normal <- wire.Frame{Type: wire.MsgInput, Payload: []byte("held")} | |
| 88 | + | normal <- protocol.Input{Data: []byte("held")} | |
| 87 | 89 | barrierDone := make(chan struct{}) | |
| 88 | 90 | barriers <- barrierDone | |
| 89 | 91 | awaitSenderSignal(t, barrierDone) | |
| @@ -106,7 +108,7 @@ func TestSenderBarrierFlushesPendingAck(t *testing.T) { | |||
| 106 | 108 | acks.offer(3, 9) | |
| 107 | 109 | transport := &ackRecordingTransport{sent: make(chan wire.Frame, 1)} | |
| 108 | 110 | barriers := make(chan chan struct{}) | |
| 109 | - | go runSender(ctx, cancel, transport, nil, barriers, make(chan wire.Frame, 1), newSamePeerInputGate(), acks, make(chan error, 1), slog.Default()) | |
| 111 | + | go runSender(ctx, cancel, transport, nil, barriers, make(chan protocol.ClientMessage, 1), newSamePeerInputGate(), acks, make(chan error, 1), slog.Default()) | |
| 110 | 112 | ||
| 111 | 113 | done := make(chan struct{}) | |
| 112 | 114 | barriers <- done | |
| @@ -7,6 +7,8 @@ import ( | |||||
| 7 | "testing" | 7 | "testing" | ||
| 8 | "time" | 8 | "time" | ||
| 9 | 9 | ||||
| 10 | + | "github.com/bnema/vev/internal/domain" | |||
| 11 | + | "github.com/bnema/vev/internal/protocol" | |||
| 10 | "github.com/bnema/vev/internal/protocol/wire" | 12 | "github.com/bnema/vev/internal/protocol/wire" | ||
| 11 | "github.com/stretchr/testify/require" | 13 | "github.com/stretchr/testify/require" | ||
| 12 | ) | 14 | ) | ||
| @@ -18,9 +20,9 @@ func (t *ackRecordingTransport) Recv() (wire.Frame, error) { return wire.Frame{} | |||||
| 18 | func (t *ackRecordingTransport) Close() error { return nil } | 20 | func (t *ackRecordingTransport) Close() error { return nil } | ||
| 19 | 21 | ||||
| 20 | func TestCumulativeAckBypassesFullNormalSendQueue(t *testing.T) { | 22 | func TestCumulativeAckBypassesFullNormalSendQueue(t *testing.T) { | ||
| 21 | - | normal := make(chan wire.Frame, sendQueueDepth) | 23 | + | normal := make(chan protocol.ClientMessage, sendQueueDepth) |
| 22 | for range cap(normal) { | 24 | for range cap(normal) { | ||
| 23 | - | normal <- wire.Frame{Type: wire.MsgInput} | 25 | + | normal <- protocol.Input{} |
| 24 | } | 26 | } | ||
| 25 | const epoch = 7 | 27 | const epoch = 7 | ||
| 26 | acks := newCumulativeAckQueue() | 28 | acks := newCumulativeAckQueue() | ||
| @@ -45,8 +47,8 @@ func TestCumulativeAckBypassesFullNormalSendQueue(t *testing.T) { | |||||
| 45 | func TestSamePeerSwitchControlBypassesHeldInput(t *testing.T) { | 47 | func TestSamePeerSwitchControlBypassesHeldInput(t *testing.T) { | ||
| 46 | ctx, cancel := context.WithCancel(context.Background()) | 48 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 47 | defer cancel() | 49 | defer cancel() | ||
| 48 | - | normal := make(chan wire.Frame, 1) | 50 | + | normal := make(chan protocol.ClientMessage, 1) |
| 49 | - | control := make(chan wire.Frame, 1) | 51 | + | control := make(chan protocol.ClientMessage, 1) |
| 50 | gate := newSamePeerInputGate() | 52 | gate := newSamePeerInputGate() | ||
| 51 | gate.setPaused(true) | 53 | gate.setPaused(true) | ||
| 52 | held := make(chan struct{}) | 54 | held := make(chan struct{}) | ||
| @@ -56,9 +58,9 @@ func TestSamePeerSwitchControlBypassesHeldInput(t *testing.T) { | |||||
| 56 | errs := make(chan error, 1) | 58 | errs := make(chan error, 1) | ||
| 57 | go runSender(ctx, cancel, transport, control, nil, normal, gate, acks, errs, slog.Default()) | 59 | go runSender(ctx, cancel, transport, control, nil, normal, gate, acks, errs, slog.Default()) | ||
| 58 | 60 | ||||
| 59 | - | normal <- wire.Frame{Type: wire.MsgInput, Payload: []byte("held")} | 61 | + | normal <- protocol.Input{Data: []byte("held")} |
| 60 | awaitSenderSignal(t, held) | 62 | awaitSenderSignal(t, held) | ||
| 61 | - | control <- wire.Frame{Type: wire.MsgSamePeerSwitchRequest, Payload: []byte("switch")} | 63 | + | control <- protocol.SamePeerSwitchRequest{RequestID: 1, Target: protocol.ExactSessionTarget{LifecycleID: domain.SessionLifecycleID{1}, SessionName: "work"}} |
| 62 | if got := awaitSenderFrame(t, transport.sent); got.Type != wire.MsgSamePeerSwitchRequest { | 64 | if got := awaitSenderFrame(t, transport.sent); got.Type != wire.MsgSamePeerSwitchRequest { | ||
| 63 | t.Fatalf("first frame = %d, want same-peer switch request", got.Type) | 65 | t.Fatalf("first frame = %d, want same-peer switch request", got.Type) | ||
| 64 | } | 66 | } | ||
| @@ -76,14 +78,14 @@ func TestSamePeerSwitchControlBypassesHeldInput(t *testing.T) { | |||||
| 76 | func TestSenderBarrierLeavesPausedInputQueued(t *testing.T) { | 78 | func TestSenderBarrierLeavesPausedInputQueued(t *testing.T) { | ||
| 77 | ctx, cancel := context.WithCancel(context.Background()) | 79 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 78 | defer cancel() | 80 | defer cancel() | ||
| 79 | - | normal := make(chan wire.Frame, 1) | 81 | + | normal := make(chan protocol.ClientMessage, 1) |
| 80 | barriers := make(chan chan struct{}) | 82 | barriers := make(chan chan struct{}) | ||
| 81 | gate := newSamePeerInputGate() | 83 | gate := newSamePeerInputGate() | ||
| 82 | gate.setPaused(true) | 84 | gate.setPaused(true) | ||
| 83 | transport := &ackRecordingTransport{sent: make(chan wire.Frame, 1)} | 85 | transport := &ackRecordingTransport{sent: make(chan wire.Frame, 1)} | ||
| 84 | go runSender(ctx, cancel, transport, nil, barriers, normal, gate, newCumulativeAckQueue(), make(chan error, 1), slog.Default()) | 86 | go runSender(ctx, cancel, transport, nil, barriers, normal, gate, newCumulativeAckQueue(), make(chan error, 1), slog.Default()) | ||
| 85 | 87 | ||||
| 86 | - | normal <- wire.Frame{Type: wire.MsgInput, Payload: []byte("held")} | 88 | + | normal <- protocol.Input{Data: []byte("held")} |
| 87 | barrierDone := make(chan struct{}) | 89 | barrierDone := make(chan struct{}) | ||
| 88 | barriers <- barrierDone | 90 | barriers <- barrierDone | ||
| 89 | awaitSenderSignal(t, barrierDone) | 91 | awaitSenderSignal(t, barrierDone) | ||
| @@ -106,7 +108,7 @@ func TestSenderBarrierFlushesPendingAck(t *testing.T) { | |||||
| 106 | acks.offer(3, 9) | 108 | acks.offer(3, 9) | ||
| 107 | transport := &ackRecordingTransport{sent: make(chan wire.Frame, 1)} | 109 | transport := &ackRecordingTransport{sent: make(chan wire.Frame, 1)} | ||
| 108 | barriers := make(chan chan struct{}) | 110 | barriers := make(chan chan struct{}) | ||
| 109 | - | go runSender(ctx, cancel, transport, nil, barriers, make(chan wire.Frame, 1), newSamePeerInputGate(), acks, make(chan error, 1), slog.Default()) | 111 | + | go runSender(ctx, cancel, transport, nil, barriers, make(chan protocol.ClientMessage, 1), newSamePeerInputGate(), acks, make(chan error, 1), slog.Default()) |
| 110 | 112 | ||||
| 111 | done := make(chan struct{}) | 113 | done := make(chan struct{}) | ||
| 112 | barriers <- done | 114 | barriers <- done | ||
M internal/usecase/client/client.go +139 -199
| @@ -24,7 +24,6 @@ import ( | |||
| 24 | 24 | "github.com/bnema/vev/internal/domain" | |
| 25 | 25 | "github.com/bnema/vev/internal/ports" | |
| 26 | 26 | "github.com/bnema/vev/internal/protocol" | |
| 27 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 28 | 27 | "github.com/bnema/vev/internal/usecase/theme" | |
| 29 | 28 | ) | |
| 30 | 29 | ||
| @@ -152,10 +151,10 @@ const ( | |||
| 152 | 151 | ) | |
| 153 | 152 | ||
| 154 | 153 | // Dependencies supplies the collaborators required by a Runner. | |
| 155 | - | type AttachHandoffFunc func(protocol.AttachTarget) (ports.Dialer, AttachRequest, error) | |
| 154 | + | type AttachHandoffFunc func(protocol.AttachTarget) (ports.ClientDialer, AttachRequest, error) | |
| 156 | 155 | ||
| 157 | 156 | type Dependencies struct { | |
| 158 | - | Dialer ports.Dialer | |
| 157 | + | Dialer ports.ClientDialer | |
| 159 | 158 | Terminal ports.Terminal | |
| 160 | 159 | Clock ports.Clock | |
| 161 | 160 | // DisableCapabilityProbe skips active outer-terminal discovery for headless | |
| @@ -198,13 +197,13 @@ type AttachRequest struct { | |||
| 198 | 197 | // attachRoute captures the dialer, request, and resume token needed to | |
| 199 | 198 | // restore a previous attachment route after a navigation handoff. | |
| 200 | 199 | type attachRoute struct { | |
| 201 | - | dialer ports.Dialer | |
| 200 | + | dialer ports.ClientDialer | |
| 202 | 201 | request AttachRequest | |
| 203 | 202 | resumeToken uint64 | |
| 204 | 203 | } | |
| 205 | 204 | ||
| 206 | 205 | type Runner struct { | |
| 207 | - | dialer ports.Dialer | |
| 206 | + | dialer ports.ClientDialer | |
| 208 | 207 | term ports.Terminal | |
| 209 | 208 | clock ports.Clock | |
| 210 | 209 | clipboard ports.ClipboardReader | |
| @@ -649,8 +648,8 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error) | |||
| 649 | 648 | } | |
| 650 | 649 | ||
| 651 | 650 | var linkEvents <-chan ports.LinkEvent | |
| 652 | - | if reporter, ok := connection.Transport().(ports.LinkStateReporter); ok { | |
| 653 | - | linkEvents = reporter.LinkEvents() | |
| 651 | + | if connection.Connection().Capabilities().LinkState { | |
| 652 | + | linkEvents = connection.Connection().LinkEvents() | |
| 654 | 653 | } | |
| 655 | 654 | result := (&attachAttempt{ | |
| 656 | 655 | runner: r, | |
| @@ -803,7 +802,7 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error) | |||
| 803 | 802 | } | |
| 804 | 803 | } | |
| 805 | 804 | var ( | |
| 806 | - | nextDialer ports.Dialer | |
| 805 | + | nextDialer ports.ClientDialer | |
| 807 | 806 | nextRequest AttachRequest | |
| 808 | 807 | handoffErr error | |
| 809 | 808 | ) | |
| @@ -1058,10 +1057,10 @@ func sleepReconnectWithResizeEvents(ctx context.Context, clk ports.Clock, d time | |||
| 1058 | 1057 | ||
| 1059 | 1058 | type attachAttempt struct { | |
| 1060 | 1059 | runner *Runner | |
| 1061 | - | dialer ports.Dialer | |
| 1060 | + | dialer ports.ClientDialer | |
| 1062 | 1061 | connection *SessionConnection | |
| 1063 | 1062 | remote bool | |
| 1064 | - | transport ports.Transport | |
| 1063 | + | transport ports.ClientConnection | |
| 1065 | 1064 | handshakeCtx context.Context | |
| 1066 | 1065 | handshakeTimedOut <-chan struct{} | |
| 1067 | 1066 | finishHandshake func() | |
| @@ -1125,17 +1124,18 @@ func DetectTrueColor(termEnv, colorTerm string) bool { | |||
| 1125 | 1124 | return ports.DetectTrueColor(termEnv, colorTerm) | |
| 1126 | 1125 | } | |
| 1127 | 1126 | ||
| 1128 | - | func requestedOutputWindow(transport ports.Transport) uint8 { | |
| 1129 | - | if _, ok := transport.(ports.DatagramTransport); ok { | |
| 1130 | - | return 1 | |
| 1127 | + | func requestedOutputWindow(connection ports.ClientConnection) uint8 { | |
| 1128 | + | window := connection.Capabilities().PreferredOutputWindow | |
| 1129 | + | if window == 0 { | |
| 1130 | + | return 8 | |
| 1131 | 1131 | } | |
| 1132 | - | return 8 | |
| 1132 | + | return window | |
| 1133 | 1133 | } | |
| 1134 | 1134 | ||
| 1135 | 1135 | func (a *attachAttempt) run(ctx context.Context) attachResult { | |
| 1136 | 1136 | transport := a.transport | |
| 1137 | 1137 | if a.connection != nil { | |
| 1138 | - | transport = a.connection.Transport() | |
| 1138 | + | transport = a.connection.Connection() | |
| 1139 | 1139 | } | |
| 1140 | 1140 | request := a.request | |
| 1141 | 1141 | term := a.runner.term | |
| @@ -1244,18 +1244,18 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1244 | 1244 | Remote: remote, | |
| 1245 | 1245 | } | |
| 1246 | 1246 | if err := sendHandshake(func() error { | |
| 1247 | - | return transport.Send(wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(hello)}) | |
| 1247 | + | return transport.SendClient(hello) | |
| 1248 | 1248 | }); err != nil { | |
| 1249 | 1249 | return handshakeFailure("sending hello", err) | |
| 1250 | 1250 | } | |
| 1251 | 1251 | ms.helloSent = true | |
| 1252 | 1252 | ||
| 1253 | 1253 | // 2. Await Welcome or a typed rejection. | |
| 1254 | - | var reply wire.Frame | |
| 1254 | + | var reply protocol.ServerMessage | |
| 1255 | 1255 | if err := boundedHandshakeOperationWithTransition(handshakeCtx, transport, func() error { | |
| 1256 | - | var recvErr error | |
| 1257 | - | reply, recvErr = transport.Recv() | |
| 1258 | - | return recvErr | |
| 1256 | + | var receiveErr error | |
| 1257 | + | reply, receiveErr = transport.ReceiveServer() | |
| 1258 | + | return receiveErr | |
| 1259 | 1259 | }, transition); err != nil { | |
| 1260 | 1260 | return handshakeFailure("awaiting welcome", err) | |
| 1261 | 1261 | } | |
| @@ -1271,12 +1271,9 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1271 | 1271 | err: err, | |
| 1272 | 1272 | } | |
| 1273 | 1273 | } | |
| 1274 | - | switch reply.Type { | |
| 1275 | - | case wire.MsgWelcome: | |
| 1276 | - | welcome, derr := wire.UnmarshalWelcome(reply.Payload) | |
| 1277 | - | if derr != nil { | |
| 1278 | - | return result(false, fmt.Errorf("vev: decoding welcome: %w", derr)) | |
| 1279 | - | } | |
| 1274 | + | switch message := reply.(type) { | |
| 1275 | + | case protocol.Welcome: | |
| 1276 | + | welcome := message | |
| 1280 | 1277 | resumeToken = welcome.ResumeToken | |
| 1281 | 1278 | name = welcome.SessionName | |
| 1282 | 1279 | committedIdentity = cloneCommittedIdentity(welcome.CommittedIdentity) | |
| @@ -1287,14 +1284,10 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1287 | 1284 | remember() | |
| 1288 | 1285 | } | |
| 1289 | 1286 | } | |
| 1290 | - | case wire.MsgError: | |
| 1291 | - | em, derr := wire.UnmarshalErrorMsg(reply.Payload) | |
| 1292 | - | if derr != nil { | |
| 1293 | - | return result(false, fmt.Errorf("vev: decoding error reply: %w", derr)) | |
| 1294 | - | } | |
| 1295 | - | return result(false, &ProtocolError{Code: em.Code, Text: em.Text}) | |
| 1287 | + | case protocol.ErrorMsg: | |
| 1288 | + | return result(false, &ProtocolError{Code: message.Code, Text: message.Text}) | |
| 1296 | 1289 | default: | |
| 1297 | - | return result(false, fmt.Errorf("vev: unexpected reply type %d before welcome", reply.Type)) | |
| 1290 | + | return result(false, fmt.Errorf("vev: unexpected %T before welcome", reply)) | |
| 1298 | 1291 | } | |
| 1299 | 1292 | if err := handshakeCtx.Err(); err != nil { | |
| 1300 | 1293 | return handshakeFailure("validating welcome", err) | |
| @@ -1320,30 +1313,20 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1320 | 1313 | return welcomedResult(fmt.Errorf("vev: committing route identity: %w", commitErr)) | |
| 1321 | 1314 | } | |
| 1322 | 1315 | } | |
| 1323 | - | snapshotPayload, err := wire.MarshalRecentRouteSnapshot(a.runner.ledger.snapshot()) | |
| 1324 | - | if err != nil { | |
| 1325 | - | return welcomedResult(fmt.Errorf("vev: encoding route snapshot: %w", err)) | |
| 1326 | - | } | |
| 1327 | - | attentionPayload, err := wire.MarshalRouteAttentionSubscription(a.runner.ledger.attentionSubscription()) | |
| 1328 | - | if err != nil { | |
| 1329 | - | return welcomedResult(fmt.Errorf("vev: encoding route attention subscription: %w", err)) | |
| 1330 | - | } | |
| 1316 | + | snapshot := a.runner.ledger.snapshot() | |
| 1317 | + | attention := a.runner.ledger.attentionSubscription() | |
| 1331 | 1318 | if err := sendHandshake(func() error { | |
| 1332 | - | if err := transport.Send(wire.Frame{Type: wire.MsgRecentRouteSnapshot, Payload: snapshotPayload}); err != nil { | |
| 1319 | + | if err := transport.SendClient(snapshot); err != nil { | |
| 1333 | 1320 | return err | |
| 1334 | 1321 | } | |
| 1335 | - | return transport.Send(wire.Frame{Type: wire.MsgRouteAttentionSubscription, Payload: attentionPayload}) | |
| 1322 | + | return transport.SendClient(attention) | |
| 1336 | 1323 | }); err != nil { | |
| 1337 | 1324 | return welcomedResult(fmt.Errorf("vev: publishing route snapshot: %w", err)) | |
| 1338 | 1325 | } | |
| 1339 | 1326 | } | |
| 1340 | 1327 | if a.runner.routeFailure != nil { | |
| 1341 | - | payload, err := wire.MarshalRouteNavigationFailure(*a.runner.routeFailure) | |
| 1342 | - | if err != nil { | |
| 1343 | - | return welcomedResult(fmt.Errorf("vev: encoding route navigation failure: %w", err)) | |
| 1344 | - | } | |
| 1345 | 1328 | if err := sendHandshake(func() error { | |
| 1346 | - | return transport.Send(wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: payload}) | |
| 1329 | + | return transport.SendClient(*a.runner.routeFailure) | |
| 1347 | 1330 | }); err != nil { | |
| 1348 | 1331 | return welcomedResult(fmt.Errorf("vev: publishing route navigation failure: %w", err)) | |
| 1349 | 1332 | } | |
| @@ -1370,8 +1353,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1370 | 1353 | input.start() | |
| 1371 | 1354 | defer input.stop() | |
| 1372 | 1355 | } | |
| 1373 | - | sendCh := make(chan wire.Frame, sendQueueDepth) | |
| 1374 | - | controlCh := make(chan wire.Frame, 1) | |
| 1356 | + | sendCh := make(chan protocol.ClientMessage, sendQueueDepth) | |
| 1357 | + | controlCh := make(chan protocol.ClientMessage, 1) | |
| 1375 | 1358 | barrierCh := make(chan chan struct{}) | |
| 1376 | 1359 | inputGate := newSamePeerInputGate() | |
| 1377 | 1360 | ackQueue := newCumulativeAckQueue() | |
| @@ -1394,18 +1377,18 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1394 | 1377 | } | |
| 1395 | 1378 | } | |
| 1396 | 1379 | publish := func(snapshot protocol.Theme) error { | |
| 1397 | - | frame := wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(snapshot)} | |
| 1380 | + | message := protocol.ClientMessage(snapshot) | |
| 1398 | 1381 | // The initial cleared snapshot is sent before the sender exists. This | |
| 1399 | 1382 | // keeps the generation publication ordered before the first query and | |
| 1400 | 1383 | // avoids leaving a queued Theme behind an immediate detach. | |
| 1401 | 1384 | if !senderStarted { | |
| 1402 | - | if err := sendHandshake(func() error { return transport.Send(frame) }); err != nil { | |
| 1385 | + | if err := sendHandshake(func() error { return transport.SendClient(message) }); err != nil { | |
| 1403 | 1386 | return fmt.Errorf("sending initial theme: %w", err) | |
| 1404 | 1387 | } | |
| 1405 | 1388 | return nil | |
| 1406 | 1389 | } | |
| 1407 | 1390 | select { | |
| 1408 | - | case sendCh <- frame: | |
| 1391 | + | case sendCh <- message: | |
| 1409 | 1392 | return nil | |
| 1410 | 1393 | case <-loopCtx.Done(): | |
| 1411 | 1394 | return context.Canceled | |
| @@ -1504,11 +1487,11 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1504 | 1487 | // Before the asynchronous sender starts, preserve transport ordering by | |
| 1505 | 1488 | // requesting the reset synchronously after the initial Theme publication. | |
| 1506 | 1489 | resetErr := sendHandshake(func() error { | |
| 1507 | - | payload, err := wire.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight}) | |
| 1508 | - | if err != nil { | |
| 1509 | - | return err | |
| 1490 | + | message := protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight} | |
| 1491 | + | if err := protocol.ValidateGeometry(message.Geometry()); err != nil { | |
| 1492 | + | return fmt.Errorf("validating terminal size for reconnect reconciliation: %w", err) | |
| 1510 | 1493 | } | |
| 1511 | - | return transport.Send(wire.Frame{Type: wire.MsgResize, Payload: payload}) | |
| 1494 | + | return transport.SendClient(message) | |
| 1512 | 1495 | }) | |
| 1513 | 1496 | if resetErr != nil { | |
| 1514 | 1497 | resetErr = handshakeContextError(ctx, handshakeTimedOut, resetErr) | |
| @@ -1630,12 +1613,12 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1630 | 1613 | return fmt.Errorf("reading terminal geometry for parked route: %w", err) | |
| 1631 | 1614 | } | |
| 1632 | 1615 | geometry = geometry.NormalizePixels() | |
| 1633 | - | payload, err := wire.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight}) | |
| 1634 | - | if err != nil { | |
| 1635 | - | return fmt.Errorf("encoding terminal size for parked route: %w", err) | |
| 1616 | + | message := protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight} | |
| 1617 | + | if err := protocol.ValidateGeometry(message.Geometry()); err != nil { | |
| 1618 | + | return fmt.Errorf("validating terminal size for parked route: %w", err) | |
| 1636 | 1619 | } | |
| 1637 | 1620 | select { | |
| 1638 | - | case controlCh <- wire.Frame{Type: wire.MsgResize, Payload: payload}: | |
| 1621 | + | case controlCh <- message: | |
| 1639 | 1622 | return nil | |
| 1640 | 1623 | case <-loopCtx.Done(): | |
| 1641 | 1624 | return context.Canceled | |
| @@ -1647,12 +1630,11 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1647 | 1630 | } | |
| 1648 | 1631 | nextParkedRequestID++ | |
| 1649 | 1632 | request := protocol.ParkedRouteRequest{RequestID: nextParkedRequestID, LeaseID: leaseID, Action: action, Target: target} | |
| 1650 | - | payload := wire.MarshalParkedRouteRequest(request) | |
| 1651 | - | if payload == nil { | |
| 1633 | + | if protocol.ValidateParkedRouteRequest(request) != nil { | |
| 1652 | 1634 | return errors.New("vev: invalid parked-route request") | |
| 1653 | 1635 | } | |
| 1654 | 1636 | select { | |
| 1655 | - | case controlCh <- wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: payload}: | |
| 1637 | + | case controlCh <- request: | |
| 1656 | 1638 | parkedPending = &parkedRoutePending{ | |
| 1657 | 1639 | requestID: request.RequestID, action: action, leaseID: leaseID, | |
| 1658 | 1640 | timer: clk.NewTimer(protocol.HandshakeTimeout), | |
| @@ -1727,12 +1709,12 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1727 | 1709 | return fmt.Errorf("reading terminal geometry for reconnect reconciliation: %w", serr) | |
| 1728 | 1710 | } | |
| 1729 | 1711 | geometry = geometry.NormalizePixels() | |
| 1730 | - | payload, err := wire.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight}) | |
| 1731 | - | if err != nil { | |
| 1732 | - | return fmt.Errorf("encoding terminal size for reconnect reconciliation: %w", err) | |
| 1712 | + | message := protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight} | |
| 1713 | + | if err := protocol.ValidateGeometry(message.Geometry()); err != nil { | |
| 1714 | + | return fmt.Errorf("validating terminal size for reconnect reconciliation: %w", err) | |
| 1733 | 1715 | } | |
| 1734 | 1716 | select { | |
| 1735 | - | case sendCh <- wire.Frame{Type: wire.MsgResize, Payload: payload}: | |
| 1717 | + | case sendCh <- message: | |
| 1736 | 1718 | awaitingReconnectReset = true | |
| 1737 | 1719 | return nil | |
| 1738 | 1720 | case <-loopCtx.Done(): | |
| @@ -1743,20 +1725,13 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1743 | 1725 | if a.runner.ledger == nil { | |
| 1744 | 1726 | return errors.New("vev: route ledger unavailable") | |
| 1745 | 1727 | } | |
| 1746 | - | snapshotPayload, err := wire.MarshalRecentRouteSnapshot(a.runner.ledger.snapshot()) | |
| 1747 | - | if err != nil { | |
| 1748 | - | return err | |
| 1749 | - | } | |
| 1750 | - | attentionPayload, err := wire.MarshalRouteAttentionSubscription(a.runner.ledger.attentionSubscription()) | |
| 1751 | - | if err != nil { | |
| 1752 | - | return err | |
| 1728 | + | messages := []protocol.ClientMessage{ | |
| 1729 | + | a.runner.ledger.snapshot(), | |
| 1730 | + | a.runner.ledger.attentionSubscription(), | |
| 1753 | 1731 | } | |
| 1754 | - | for _, frame := range []wire.Frame{ | |
| 1755 | - | {Type: wire.MsgRecentRouteSnapshot, Payload: snapshotPayload}, | |
| 1756 | - | {Type: wire.MsgRouteAttentionSubscription, Payload: attentionPayload}, | |
| 1757 | - | } { | |
| 1732 | + | for _, message := range messages { | |
| 1758 | 1733 | select { | |
| 1759 | - | case sendCh <- frame: | |
| 1734 | + | case sendCh <- message: | |
| 1760 | 1735 | case <-loopCtx.Done(): | |
| 1761 | 1736 | return context.Canceled | |
| 1762 | 1737 | } | |
| @@ -1764,16 +1739,12 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1764 | 1739 | return nil | |
| 1765 | 1740 | } | |
| 1766 | 1741 | sendRouteFailure := func(action protocol.RouteNavigationAction, code protocol.RouteFailureCode) error { | |
| 1767 | - | payload, err := wire.MarshalRouteNavigationFailure(protocol.RouteNavigationFailure{ | |
| 1768 | - | Key: action.Key, | |
| 1769 | - | Generation: action.Generation, | |
| 1770 | - | Code: code, | |
| 1771 | - | }) | |
| 1772 | - | if err != nil { | |
| 1773 | - | return err | |
| 1742 | + | failure := protocol.RouteNavigationFailure{Key: action.Key, Generation: action.Generation, Code: code} | |
| 1743 | + | if failure.Code.Validate() != nil || failure.Key == 0 || failure.Generation == 0 { | |
| 1744 | + | return protocol.ErrInvalidRouteWire | |
| 1774 | 1745 | } | |
| 1775 | 1746 | select { | |
| 1776 | - | case sendCh <- wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: payload}: | |
| 1747 | + | case sendCh <- failure: | |
| 1777 | 1748 | return nil | |
| 1778 | 1749 | case <-loopCtx.Done(): | |
| 1779 | 1750 | return context.Canceled | |
| @@ -1828,7 +1799,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1828 | 1799 | return welcomedResult(fmt.Errorf("dismissing reconnect toast: %w", err)) | |
| 1829 | 1800 | } | |
| 1830 | 1801 | select { | |
| 1831 | - | case sendCh <- wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(protocol.ClientNotice{Action: protocol.ClientNoticeLinkConnected})}: | |
| 1802 | + | case sendCh <- protocol.ClientNotice{Action: protocol.ClientNoticeLinkConnected}: | |
| 1832 | 1803 | case <-loopCtx.Done(): | |
| 1833 | 1804 | return welcomedResult(nil) | |
| 1834 | 1805 | } | |
| @@ -1837,7 +1808,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1837 | 1808 | if ev.State == ports.LinkStateDegraded { | |
| 1838 | 1809 | log.Warn("UDP link degraded") | |
| 1839 | 1810 | select { | |
| 1840 | - | case sendCh <- wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(protocol.ClientNotice{Action: protocol.ClientNoticeLinkDegraded})}: | |
| 1811 | + | case sendCh <- protocol.ClientNotice{Action: protocol.ClientNoticeLinkDegraded}: | |
| 1841 | 1812 | case <-loopCtx.Done(): | |
| 1842 | 1813 | return welcomedResult(nil) | |
| 1843 | 1814 | } | |
| @@ -1884,12 +1855,9 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1884 | 1855 | } | |
| 1885 | 1856 | return welcomedResult(fmt.Errorf("vev: receiving from daemon: %w", r.err)) | |
| 1886 | 1857 | } | |
| 1887 | - | switch r.frame.Type { | |
| 1888 | - | case wire.MsgOutput: | |
| 1889 | - | o, derr := wire.UnmarshalOutput(r.frame.Payload) | |
| 1890 | - | if derr != nil { | |
| 1891 | - | return welcomedResult(fmt.Errorf("vev: decoding output: %w", derr)) | |
| 1892 | - | } | |
| 1858 | + | switch message := r.message.(type) { | |
| 1859 | + | case protocol.Output: | |
| 1860 | + | o := message | |
| 1893 | 1861 | nextState, accepted := outputState.next(o) | |
| 1894 | 1862 | if !accepted { | |
| 1895 | 1863 | // A discarded frame was never written and must never be ACKed. | |
| @@ -1897,7 +1865,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1897 | 1865 | // repeated gaps while that reset is in flight. | |
| 1898 | 1866 | if !outputResetRequested { | |
| 1899 | 1867 | select { | |
| 1900 | - | case sendCh <- wire.Frame{Type: wire.MsgOutputResetRequest, Payload: wire.MarshalOutputResetRequest(protocol.OutputResetRequest{})}: | |
| 1868 | + | case sendCh <- protocol.OutputResetRequest{}: | |
| 1901 | 1869 | outputResetRequested = true | |
| 1902 | 1870 | case <-loopCtx.Done(): | |
| 1903 | 1871 | return loopCanceledResult() | |
| @@ -1947,11 +1915,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1947 | 1915 | log.Debug("received first output") | |
| 1948 | 1916 | endHandshake() | |
| 1949 | 1917 | } | |
| 1950 | - | case wire.MsgAttachTarget: | |
| 1951 | - | target, derr := wire.UnmarshalAttachTarget(r.frame.Payload) | |
| 1952 | - | if derr != nil { | |
| 1953 | - | return welcomedResult(fmt.Errorf("vev: decoding attach target: %w", derr)) | |
| 1954 | - | } | |
| 1918 | + | case protocol.AttachTarget: | |
| 1919 | + | target := message | |
| 1955 | 1920 | if a.returnAttachTargets { | |
| 1956 | 1921 | handoff := welcomedResult(nil) | |
| 1957 | 1922 | handoff.target = &target | |
| @@ -1967,18 +1932,18 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1967 | 1932 | } | |
| 1968 | 1933 | nextSamePeerRequestID++ | |
| 1969 | 1934 | request := a.runner.ledger.samePeerHandoff(request, target) | |
| 1970 | - | payload, err := wire.MarshalSamePeerSwitchRequest(protocol.SamePeerSwitchRequest{ | |
| 1935 | + | switchRequest := protocol.SamePeerSwitchRequest{ | |
| 1971 | 1936 | RequestID: nextSamePeerRequestID, | |
| 1972 | 1937 | Target: *target.ExactTarget, | |
| 1973 | 1938 | PreferredTabID: request.PreferredTabID, | |
| 1974 | - | }) | |
| 1975 | - | if err != nil { | |
| 1976 | - | return welcomedResult(fmt.Errorf("vev: encoding same-peer switch: %w", err)) | |
| 1939 | + | } | |
| 1940 | + | if switchRequest.Validate() != nil { | |
| 1941 | + | return welcomedResult(errors.New("vev: invalid same-peer switch")) | |
| 1977 | 1942 | } | |
| 1978 | 1943 | samePeerSwitch = &samePeerSwitchPending{requestID: nextSamePeerRequestID, target: *target.ExactTarget} | |
| 1979 | 1944 | inputGate.setPaused(true) | |
| 1980 | 1945 | select { | |
| 1981 | - | case controlCh <- wire.Frame{Type: wire.MsgSamePeerSwitchRequest, Payload: payload}: | |
| 1946 | + | case controlCh <- switchRequest: | |
| 1982 | 1947 | continue | |
| 1983 | 1948 | case <-loopCtx.Done(): | |
| 1984 | 1949 | return loopCanceledResult() | |
| @@ -1987,11 +1952,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1987 | 1952 | handoff := welcomedResult(nil) | |
| 1988 | 1953 | handoff.target = &target | |
| 1989 | 1954 | return handoff | |
| 1990 | - | case wire.MsgParkedRouteResponse: | |
| 1991 | - | response, derr := wire.UnmarshalParkedRouteResponse(r.frame.Payload) | |
| 1992 | - | if derr != nil { | |
| 1993 | - | return welcomedResult(fmt.Errorf("vev: decoding parked-route response: %w", derr)) | |
| 1994 | - | } | |
| 1955 | + | case protocol.ParkedRouteResponse: | |
| 1956 | + | response := message | |
| 1995 | 1957 | if parkedPending == nil || response.RequestID != parkedPending.requestID { | |
| 1996 | 1958 | return welcomedResult(errors.New("vev: unexpected parked-route response")) | |
| 1997 | 1959 | } | |
| @@ -2034,12 +1996,9 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 2034 | 1996 | return fallback | |
| 2035 | 1997 | } | |
| 2036 | 1998 | } | |
| 2037 | - | case wire.MsgNavigationAction: | |
| 2038 | - | directive, derr := wire.UnmarshalNavigationDirective(r.frame.Payload) | |
| 2039 | - | if derr != nil { | |
| 2040 | - | return welcomedResult(fmt.Errorf("vev: decoding navigation action: %w", derr)) | |
| 2041 | - | } | |
| 2042 | - | _, datagramRoute := transport.(ports.DatagramTransport) | |
| 1999 | + | case protocol.NavigationDirective: | |
| 2000 | + | directive := message | |
| 2001 | + | datagramRoute := transport.Capabilities().PreferredOutputWindow == 1 | |
| 2043 | 2002 | if directive.Action == protocol.NavigationOpenHomePicker && a.openHomePicker != nil && datagramRoute { | |
| 2044 | 2003 | stopForeground() | |
| 2045 | 2004 | if err := flushSender(); err != nil { | |
| @@ -2053,11 +2012,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 2053 | 2012 | navigation := welcomedResult(nil) | |
| 2054 | 2013 | navigation.action = directive.Action | |
| 2055 | 2014 | return navigation | |
| 2056 | - | case wire.MsgNavigateRecentRoute: | |
| 2057 | - | action, derr := wire.UnmarshalRouteNavigationAction(r.frame.Payload) | |
| 2058 | - | if derr != nil { | |
| 2059 | - | return welcomedResult(fmt.Errorf("vev: decoding route navigation action: %w", derr)) | |
| 2060 | - | } | |
| 2015 | + | case protocol.RouteNavigationAction: | |
| 2016 | + | action := message | |
| 2061 | 2017 | if a.runner.ledger == nil { | |
| 2062 | 2018 | if ferr := sendRouteFailure(action, protocol.RouteFailureUnavailable); ferr != nil { | |
| 2063 | 2019 | return welcomedResult(ferr) | |
| @@ -2077,11 +2033,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 2077 | 2033 | navigation := welcomedResult(nil) | |
| 2078 | 2034 | navigation.routeAction = &action | |
| 2079 | 2035 | return navigation | |
| 2080 | - | case wire.MsgCommittedRouteIdentity: | |
| 2081 | - | identity, derr := wire.UnmarshalCommittedRouteIdentity(r.frame.Payload) | |
| 2082 | - | if derr != nil { | |
| 2083 | - | return welcomedResult(fmt.Errorf("vev: decoding committed route identity: %w", derr)) | |
| 2084 | - | } | |
| 2036 | + | case protocol.CommittedRouteIdentity: | |
| 2037 | + | identity := message | |
| 2085 | 2038 | if samePeerSwitch != nil && identity.Target != samePeerSwitch.target { | |
| 2086 | 2039 | return welcomedResult(errors.New("vev: same-peer switch committed an unexpected target")) | |
| 2087 | 2040 | } | |
| @@ -2100,11 +2053,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 2100 | 2053 | samePeerSwitch = nil | |
| 2101 | 2054 | inputGate.setPaused(false) | |
| 2102 | 2055 | } | |
| 2103 | - | case wire.MsgSamePeerSwitchFailure: | |
| 2104 | - | failure, derr := wire.UnmarshalSamePeerSwitchFailure(r.frame.Payload) | |
| 2105 | - | if derr != nil { | |
| 2106 | - | return welcomedResult(fmt.Errorf("vev: decoding same-peer switch failure: %w", derr)) | |
| 2107 | - | } | |
| 2056 | + | case protocol.SamePeerSwitchFailure: | |
| 2057 | + | failure := message | |
| 2108 | 2058 | if samePeerSwitch != nil && failure.RequestID == samePeerSwitch.requestID { | |
| 2109 | 2059 | samePeerSwitch = nil | |
| 2110 | 2060 | inputGate.setPaused(false) | |
| @@ -2112,7 +2062,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 2112 | 2062 | transitionWaitingFull = true | |
| 2113 | 2063 | if !outputResetRequested { | |
| 2114 | 2064 | select { | |
| 2115 | - | case sendCh <- wire.Frame{Type: wire.MsgOutputResetRequest, Payload: wire.MarshalOutputResetRequest(protocol.OutputResetRequest{})}: | |
| 2065 | + | case sendCh <- protocol.OutputResetRequest{}: | |
| 2116 | 2066 | outputResetRequested = true | |
| 2117 | 2067 | case <-loopCtx.Done(): | |
| 2118 | 2068 | return loopCanceledResult() | |
| @@ -2122,11 +2072,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 2122 | 2072 | transition.stop() | |
| 2123 | 2073 | } | |
| 2124 | 2074 | } | |
| 2125 | - | case wire.MsgRoutePosition: | |
| 2126 | - | position, derr := wire.UnmarshalRoutePosition(r.frame.Payload) | |
| 2127 | - | if derr != nil { | |
| 2128 | - | return welcomedResult(fmt.Errorf("vev: decoding route position: %w", derr)) | |
| 2129 | - | } | |
| 2075 | + | case protocol.RoutePosition: | |
| 2076 | + | position := message | |
| 2130 | 2077 | if a.runner.ledger == nil { | |
| 2131 | 2078 | return welcomedResult(errors.New("vev: route ledger unavailable")) | |
| 2132 | 2079 | } | |
| @@ -2134,17 +2081,11 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 2134 | 2081 | return welcomedResult(fmt.Errorf("vev: remembering route position: %w", derr)) | |
| 2135 | 2082 | } | |
| 2136 | 2083 | routePosition = cloneRoutePosition(&position) | |
| 2137 | - | case wire.MsgRouteNavigationFailure: | |
| 2138 | - | failure, derr := wire.UnmarshalRouteNavigationFailure(r.frame.Payload) | |
| 2139 | - | if derr != nil { | |
| 2140 | - | return welcomedResult(fmt.Errorf("vev: decoding route navigation failure: %w", derr)) | |
| 2141 | - | } | |
| 2084 | + | case protocol.RouteNavigationFailure: | |
| 2085 | + | failure := message | |
| 2142 | 2086 | log.Warn("route navigation rejected", "key", failure.Key, "generation", failure.Generation, "code", failure.Code) | |
| 2143 | - | case wire.MsgDetached: | |
| 2144 | - | d, derr := wire.UnmarshalDetached(r.frame.Payload) | |
| 2145 | - | if derr != nil { | |
| 2146 | - | return welcomedResult(fmt.Errorf("vev: decoding detached: %w", derr)) | |
| 2147 | - | } | |
| 2087 | + | case protocol.Detached: | |
| 2088 | + | d := message | |
| 2148 | 2089 | ms.detached = true | |
| 2149 | 2090 | if d.Reason == protocol.ReasonDetach { | |
| 2150 | 2091 | log.Info("detached cleanly", "reason", d.Reason) | |
| @@ -2152,34 +2093,43 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 2152 | 2093 | log.Warn("detached by daemon", "reason", d.Reason) | |
| 2153 | 2094 | } | |
| 2154 | 2095 | return welcomedResult(detachedResult(d.Reason)) | |
| 2155 | - | case wire.MsgPong: | |
| 2096 | + | case protocol.Pong: | |
| 2156 | 2097 | // Liveness reply; nothing to do. | |
| 2157 | - | default: | |
| 2158 | - | // Unknown/out-of-band message types are ignored so a newer | |
| 2159 | - | // daemon can add server->client messages without breaking us. | |
| 2160 | 2098 | } | |
| 2161 | 2099 | } | |
| 2162 | 2100 | } | |
| 2163 | 2101 | } | |
| 2164 | 2102 | ||
| 2165 | - | // recvResult carries one framed message (or a read error) from the receive | |
| 2166 | - | // pump to the main loop. | |
| 2103 | + | // recvResult carries one typed message (or a read error) from the receive pump. | |
| 2167 | 2104 | type recvResult struct { | |
| 2168 | - | frame wire.Frame | |
| 2169 | - | err error | |
| 2105 | + | message protocol.ServerMessage | |
| 2106 | + | err error | |
| 2170 | 2107 | } | |
| 2171 | 2108 | ||
| 2172 | - | // runRecv reads frames until an error, forwarding each to the main loop. | |
| 2109 | + | // runRecv reads typed messages until an error, forwarding each to the main loop. | |
| 2173 | 2110 | // It exits on the first error or when the loop context is cancelled. | |
| 2174 | - | func runRecv(ctx context.Context, transport ports.Transport, out chan<- recvResult, failed chan<- struct{}, log *slog.Logger) { | |
| 2111 | + | func runRecv(ctx context.Context, transport ports.ClientConnection, out chan<- recvResult, failed chan<- struct{}, log *slog.Logger) { | |
| 2175 | 2112 | defer log.Debug("receive pump exited") | |
| 2176 | 2113 | for { | |
| 2177 | - | f, err := transport.Recv() | |
| 2114 | + | message, err := transport.ReceiveServer() | |
| 2115 | + | if err != nil { | |
| 2116 | + | var failure *protocol.DecodeFailure | |
| 2117 | + | if errors.As(err, &failure) && (failure.Category == protocol.DecodeUnknownType || failure.Category == protocol.DecodeWrongDirection) { | |
| 2118 | + | log.Debug("ignoring rejected server message", | |
| 2119 | + | "category", failure.Category, | |
| 2120 | + | "kind", failure.Kind, | |
| 2121 | + | "type", failure.Type, | |
| 2122 | + | "version", failure.Version, | |
| 2123 | + | "request", failure.RequestID, | |
| 2124 | + | ) | |
| 2125 | + | continue | |
| 2126 | + | } | |
| 2127 | + | } | |
| 2178 | 2128 | if err != nil && failed != nil { | |
| 2179 | 2129 | close(failed) | |
| 2180 | 2130 | } | |
| 2181 | 2131 | select { | |
| 2182 | - | case out <- recvResult{frame: f, err: err}: | |
| 2132 | + | case out <- recvResult{message: message, err: err}: | |
| 2183 | 2133 | case <-ctx.Done(): | |
| 2184 | 2134 | return | |
| 2185 | 2135 | } | |
| @@ -2189,9 +2139,6 @@ func runRecv(ctx context.Context, transport ports.Transport, out chan<- recvResu | |||
| 2189 | 2139 | } | |
| 2190 | 2140 | } | |
| 2191 | 2141 | ||
| 2192 | - | // runSender is the sole caller of transport.Send: serialising the input and | |
| 2193 | - | // resize pumps through one goroutine keeps the transport's single-writer | |
| 2194 | - | // contract intact. A send failure cancels the loop and is surfaced once. | |
| 2195 | 2142 | type cumulativeAckQueue struct { | |
| 2196 | 2143 | mu sync.Mutex | |
| 2197 | 2144 | latestEpoch uint64 | |
| @@ -2226,12 +2173,15 @@ func (q *cumulativeAckQueue) take() (uint64, uint64) { | |||
| 2226 | 2173 | return epoch, state | |
| 2227 | 2174 | } | |
| 2228 | 2175 | ||
| 2229 | - | // runSender preserves normal-frame order while allowing switch control and | |
| 2230 | - | // output ACKs to make progress when raw input is held for a same-peer switch. | |
| 2231 | - | func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.Transport, control <-chan wire.Frame, barriers <-chan chan struct{}, in <-chan wire.Frame, inputGate *samePeerInputGate, acks *cumulativeAckQueue, errCh chan<- error, log *slog.Logger) { | |
| 2176 | + | // runSender is the sole caller of SendClient: serialising the input and resize | |
| 2177 | + | // pumps through one goroutine keeps the connection's single-writer contract. | |
| 2178 | + | // It preserves normal-frame order while allowing switch control and output ACKs | |
| 2179 | + | // to make progress when raw input is held for a same-peer switch. A send failure | |
| 2180 | + | // cancels the loop and is surfaced once. | |
| 2181 | + | func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.ClientConnection, control <-chan protocol.ClientMessage, barriers <-chan chan struct{}, in <-chan protocol.ClientMessage, inputGate *samePeerInputGate, acks *cumulativeAckQueue, errCh chan<- error, log *slog.Logger) { | |
| 2232 | 2182 | defer log.Debug("sender pump exited") | |
| 2233 | - | send := func(f wire.Frame) bool { | |
| 2234 | - | if err := transport.Send(f); err != nil { | |
| 2183 | + | send := func(message protocol.ClientMessage) bool { | |
| 2184 | + | if err := transport.SendClient(message); err != nil { | |
| 2235 | 2185 | select { | |
| 2236 | 2186 | case errCh <- err: | |
| 2237 | 2187 | default: | |
| @@ -2246,16 +2196,7 @@ func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.T | |||
| 2246 | 2196 | if state == 0 { | |
| 2247 | 2197 | return true | |
| 2248 | 2198 | } | |
| 2249 | - | payload, err := wire.MarshalAck(protocol.Ack{Epoch: epoch, State: state}) | |
| 2250 | - | if err != nil { | |
| 2251 | - | select { | |
| 2252 | - | case errCh <- fmt.Errorf("encoding output ACK: %w", err): | |
| 2253 | - | default: | |
| 2254 | - | } | |
| 2255 | - | cancel() | |
| 2256 | - | return false | |
| 2257 | - | } | |
| 2258 | - | return send(wire.Frame{Type: wire.MsgAck, Payload: payload}) | |
| 2199 | + | return send(protocol.Ack{Epoch: epoch, State: state}) | |
| 2259 | 2200 | } | |
| 2260 | 2201 | flushPending := func() bool { | |
| 2261 | 2202 | for { | |
| @@ -2287,12 +2228,12 @@ func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.T | |||
| 2287 | 2228 | close(done) | |
| 2288 | 2229 | return true | |
| 2289 | 2230 | } | |
| 2290 | - | var heldInput *wire.Frame | |
| 2231 | + | var heldInput protocol.ClientMessage | |
| 2291 | 2232 | for { | |
| 2292 | 2233 | if heldInput != nil { | |
| 2293 | 2234 | paused, changed := inputGate.snapshot() | |
| 2294 | 2235 | if !paused { | |
| 2295 | - | if !send(*heldInput) { | |
| 2236 | + | if !send(heldInput) { | |
| 2296 | 2237 | return | |
| 2297 | 2238 | } | |
| 2298 | 2239 | heldInput = nil | |
| @@ -2348,23 +2289,23 @@ func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.T | |||
| 2348 | 2289 | if !completeBarrier(done) { | |
| 2349 | 2290 | return | |
| 2350 | 2291 | } | |
| 2351 | - | case frame := <-in: | |
| 2292 | + | case message := <-in: | |
| 2352 | 2293 | select { | |
| 2353 | 2294 | case <-ctx.Done(): | |
| 2354 | 2295 | return | |
| 2355 | 2296 | default: | |
| 2356 | 2297 | } | |
| 2357 | - | if frame.Type == wire.MsgInput || frame.Type == wire.MsgImagePush { | |
| 2298 | + | switch message.(type) { | |
| 2299 | + | case protocol.Input, protocol.ImagePush: | |
| 2358 | 2300 | if paused, _ := inputGate.snapshot(); paused { | |
| 2359 | - | held := frame | |
| 2360 | - | heldInput = &held | |
| 2301 | + | heldInput = message | |
| 2361 | 2302 | if inputGate != nil && inputGate.afterInputHeld != nil { | |
| 2362 | 2303 | inputGate.afterInputHeld() | |
| 2363 | 2304 | } | |
| 2364 | 2305 | continue | |
| 2365 | 2306 | } | |
| 2366 | 2307 | } | |
| 2367 | - | if !send(frame) { | |
| 2308 | + | if !send(message) { | |
| 2368 | 2309 | return | |
| 2369 | 2310 | } | |
| 2370 | 2311 | case <-ctx.Done(): | |
| @@ -2726,7 +2667,7 @@ type stdinPump struct { | |||
| 2726 | 2667 | in io.Reader // compatibility for isolated scanner tests | |
| 2727 | 2668 | input *terminalInputPump | |
| 2728 | 2669 | consumer uint64 | |
| 2729 | - | out chan<- wire.Frame | |
| 2670 | + | out chan<- protocol.ClientMessage | |
| 2730 | 2671 | clock ports.Clock | |
| 2731 | 2672 | clipboard ports.ClipboardReader | |
| 2732 | 2673 | logger *slog.Logger | |
| @@ -2747,13 +2688,13 @@ func (p *stdinPump) run() { | |||
| 2747 | 2688 | var undeliveredMu sync.Mutex | |
| 2748 | 2689 | var undelivered []byte | |
| 2749 | 2690 | sendOK.Store(true) | |
| 2750 | - | send := func(frame wire.Frame) bool { | |
| 2691 | + | send := func(message protocol.ClientMessage) bool { | |
| 2751 | 2692 | if !sendOK.Load() { | |
| 2752 | 2693 | return false | |
| 2753 | 2694 | } | |
| 2754 | 2695 | return p.sendLease.send(func() bool { | |
| 2755 | 2696 | select { | |
| 2756 | - | case p.out <- frame: | |
| 2697 | + | case p.out <- message: | |
| 2757 | 2698 | if p.afterInputDelivered != nil { | |
| 2758 | 2699 | p.afterInputDelivered() | |
| 2759 | 2700 | } | |
| @@ -2786,7 +2727,7 @@ func (p *stdinPump) run() { | |||
| 2786 | 2727 | return | |
| 2787 | 2728 | } | |
| 2788 | 2729 | inputSeq++ | |
| 2789 | - | if !send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{InputSeq: inputSeq, Data: append([]byte(nil), data...)})}) { | |
| 2730 | + | if !send(protocol.Input{InputSeq: inputSeq, Data: append([]byte(nil), data...)}) { | |
| 2790 | 2731 | undeliveredMu.Lock() | |
| 2791 | 2732 | undelivered = append(undelivered, data...) | |
| 2792 | 2733 | undeliveredMu.Unlock() | |
| @@ -2800,11 +2741,11 @@ func (p *stdinPump) run() { | |||
| 2800 | 2741 | reader: p.clipboard, | |
| 2801 | 2742 | log: p.logger, | |
| 2802 | 2743 | sendNotice: func(action uint8) { | |
| 2803 | - | send(wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(protocol.ClientNotice{Action: action})}) | |
| 2744 | + | send(protocol.ClientNotice{Action: action}) | |
| 2804 | 2745 | }, | |
| 2805 | 2746 | sendImage: func(mime string, data []byte) { | |
| 2806 | 2747 | inputSeq++ | |
| 2807 | - | send(wire.Frame{Type: wire.MsgImagePush, Payload: wire.MarshalImagePush(protocol.ImagePush{InputSeq: inputSeq, Mime: mime, Data: data})}) | |
| 2748 | + | send(protocol.ImagePush{InputSeq: inputSeq, Mime: mime, Data: data}) | |
| 2808 | 2749 | }, | |
| 2809 | 2750 | next: coalescer.Scan, | |
| 2810 | 2751 | } | |
| @@ -2946,7 +2887,7 @@ func (p *stdinPump) run() { | |||
| 2946 | 2887 | disarmMarkerDeadline() | |
| 2947 | 2888 | markers.flush(sink) | |
| 2948 | 2889 | select { | |
| 2949 | - | case p.out <- wire.Frame{Type: wire.MsgDetach, Payload: wire.MarshalDetach(protocol.Detach{})}: | |
| 2890 | + | case p.out <- protocol.Detach{}: | |
| 2950 | 2891 | case <-p.ctx.Done(): | |
| 2951 | 2892 | } | |
| 2952 | 2893 | p.cancel() | |
| @@ -2967,7 +2908,7 @@ func (p *stdinPump) run() { | |||
| 2967 | 2908 | // runResize forwards coalesced terminal resize events to the daemon. It | |
| 2968 | 2909 | // tolerates an already-closed resize channel (which the terminal adapter | |
| 2969 | 2910 | // hands back when restore ran before ResizeEvents was first called). | |
| 2970 | - | func runResize(ctx context.Context, events <-chan domain.Geometry, out chan<- wire.Frame, sendLease *foregroundSendLease, log *slog.Logger) { | |
| 2911 | + | func runResize(ctx context.Context, events <-chan domain.Geometry, out chan<- protocol.ClientMessage, sendLease *foregroundSendLease, log *slog.Logger) { | |
| 2971 | 2912 | defer log.Debug("resize pump exited") | |
| 2972 | 2913 | for { | |
| 2973 | 2914 | select { | |
| @@ -2976,15 +2917,14 @@ func runResize(ctx context.Context, events <-chan domain.Geometry, out chan<- wi | |||
| 2976 | 2917 | return | |
| 2977 | 2918 | } | |
| 2978 | 2919 | geometry = geometry.NormalizePixels() | |
| 2979 | - | payload, err := wire.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight}) | |
| 2980 | - | if err != nil { | |
| 2981 | - | log.Error("encoding terminal resize", "error", err) | |
| 2920 | + | message := protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight} | |
| 2921 | + | if err := protocol.ValidateGeometry(message.Geometry()); err != nil { | |
| 2922 | + | log.Error("validating terminal resize", "error", err) | |
| 2982 | 2923 | continue | |
| 2983 | 2924 | } | |
| 2984 | - | frame := wire.Frame{Type: wire.MsgResize, Payload: payload} | |
| 2985 | 2925 | if !sendLease.send(func() bool { | |
| 2986 | 2926 | select { | |
| 2987 | - | case out <- frame: | |
| 2927 | + | case out <- message: | |
| 2988 | 2928 | return true | |
| 2989 | 2929 | case <-ctx.Done(): | |
| 2990 | 2930 | return false | |
| @@ -24,7 +24,6 @@ import ( | |||||
| 24 | "github.com/bnema/vev/internal/domain" | 24 | "github.com/bnema/vev/internal/domain" | ||
| 25 | "github.com/bnema/vev/internal/ports" | 25 | "github.com/bnema/vev/internal/ports" | ||
| 26 | "github.com/bnema/vev/internal/protocol" | 26 | "github.com/bnema/vev/internal/protocol" | ||
| 27 | - | "github.com/bnema/vev/internal/protocol/wire" | |||
| 28 | "github.com/bnema/vev/internal/usecase/theme" | 27 | "github.com/bnema/vev/internal/usecase/theme" | ||
| 29 | ) | 28 | ) | ||
| 30 | 29 | ||||
| @@ -152,10 +151,10 @@ const ( | |||||
| 152 | ) | 151 | ) | ||
| 153 | 152 | ||||
| 154 | // Dependencies supplies the collaborators required by a Runner. | 153 | // Dependencies supplies the collaborators required by a Runner. | ||
| 155 | - | type AttachHandoffFunc func(protocol.AttachTarget) (ports.Dialer, AttachRequest, error) | 154 | + | type AttachHandoffFunc func(protocol.AttachTarget) (ports.ClientDialer, AttachRequest, error) |
| 156 | 155 | ||||
| 157 | type Dependencies struct { | 156 | type Dependencies struct { | ||
| 158 | - | Dialer ports.Dialer | 157 | + | Dialer ports.ClientDialer |
| 159 | Terminal ports.Terminal | 158 | Terminal ports.Terminal | ||
| 160 | Clock ports.Clock | 159 | Clock ports.Clock | ||
| 161 | // DisableCapabilityProbe skips active outer-terminal discovery for headless | 160 | // DisableCapabilityProbe skips active outer-terminal discovery for headless | ||
| @@ -198,13 +197,13 @@ type AttachRequest struct { | |||||
| 198 | // attachRoute captures the dialer, request, and resume token needed to | 197 | // attachRoute captures the dialer, request, and resume token needed to | ||
| 199 | // restore a previous attachment route after a navigation handoff. | 198 | // restore a previous attachment route after a navigation handoff. | ||
| 200 | type attachRoute struct { | 199 | type attachRoute struct { | ||
| 201 | - | dialer ports.Dialer | 200 | + | dialer ports.ClientDialer |
| 202 | request AttachRequest | 201 | request AttachRequest | ||
| 203 | resumeToken uint64 | 202 | resumeToken uint64 | ||
| 204 | } | 203 | } | ||
| 205 | 204 | ||||
| 206 | type Runner struct { | 205 | type Runner struct { | ||
| 207 | - | dialer ports.Dialer | 206 | + | dialer ports.ClientDialer |
| 208 | term ports.Terminal | 207 | term ports.Terminal | ||
| 209 | clock ports.Clock | 208 | clock ports.Clock | ||
| 210 | clipboard ports.ClipboardReader | 209 | clipboard ports.ClipboardReader | ||
| @@ -649,8 +648,8 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error) | |||||
| 649 | } | 648 | } | ||
| 650 | 649 | ||||
| 651 | var linkEvents <-chan ports.LinkEvent | 650 | var linkEvents <-chan ports.LinkEvent | ||
| 652 | - | if reporter, ok := connection.Transport().(ports.LinkStateReporter); ok { | 651 | + | if connection.Connection().Capabilities().LinkState { |
| 653 | - | linkEvents = reporter.LinkEvents() | 652 | + | linkEvents = connection.Connection().LinkEvents() |
| 654 | } | 653 | } | ||
| 655 | result := (&attachAttempt{ | 654 | result := (&attachAttempt{ | ||
| 656 | runner: r, | 655 | runner: r, | ||
| @@ -803,7 +802,7 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error) | |||||
| 803 | } | 802 | } | ||
| 804 | } | 803 | } | ||
| 805 | var ( | 804 | var ( | ||
| 806 | - | nextDialer ports.Dialer | 805 | + | nextDialer ports.ClientDialer |
| 807 | nextRequest AttachRequest | 806 | nextRequest AttachRequest | ||
| 808 | handoffErr error | 807 | handoffErr error | ||
| 809 | ) | 808 | ) | ||
| @@ -1058,10 +1057,10 @@ func sleepReconnectWithResizeEvents(ctx context.Context, clk ports.Clock, d time | |||||
| 1058 | 1057 | ||||
| 1059 | type attachAttempt struct { | 1058 | type attachAttempt struct { | ||
| 1060 | runner *Runner | 1059 | runner *Runner | ||
| 1061 | - | dialer ports.Dialer | 1060 | + | dialer ports.ClientDialer |
| 1062 | connection *SessionConnection | 1061 | connection *SessionConnection | ||
| 1063 | remote bool | 1062 | remote bool | ||
| 1064 | - | transport ports.Transport | 1063 | + | transport ports.ClientConnection |
| 1065 | handshakeCtx context.Context | 1064 | handshakeCtx context.Context | ||
| 1066 | handshakeTimedOut <-chan struct{} | 1065 | handshakeTimedOut <-chan struct{} | ||
| 1067 | finishHandshake func() | 1066 | finishHandshake func() | ||
| @@ -1125,17 +1124,18 @@ func DetectTrueColor(termEnv, colorTerm string) bool { | |||||
| 1125 | return ports.DetectTrueColor(termEnv, colorTerm) | 1124 | return ports.DetectTrueColor(termEnv, colorTerm) | ||
| 1126 | } | 1125 | } | ||
| 1127 | 1126 | ||||
| 1128 | - | func requestedOutputWindow(transport ports.Transport) uint8 { | 1127 | + | func requestedOutputWindow(connection ports.ClientConnection) uint8 { |
| 1129 | - | if _, ok := transport.(ports.DatagramTransport); ok { | 1128 | + | window := connection.Capabilities().PreferredOutputWindow |
| 1130 | - | return 1 | 1129 | + | if window == 0 { |
| 1130 | + | return 8 | |||
| 1131 | } | 1131 | } | ||
| 1132 | - | return 8 | 1132 | + | return window |
| 1133 | } | 1133 | } | ||
| 1134 | 1134 | ||||
| 1135 | func (a *attachAttempt) run(ctx context.Context) attachResult { | 1135 | func (a *attachAttempt) run(ctx context.Context) attachResult { | ||
| 1136 | transport := a.transport | 1136 | transport := a.transport | ||
| 1137 | if a.connection != nil { | 1137 | if a.connection != nil { | ||
| 1138 | - | transport = a.connection.Transport() | 1138 | + | transport = a.connection.Connection() |
| 1139 | } | 1139 | } | ||
| 1140 | request := a.request | 1140 | request := a.request | ||
| 1141 | term := a.runner.term | 1141 | term := a.runner.term | ||
| @@ -1244,18 +1244,18 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1244 | Remote: remote, | 1244 | Remote: remote, | ||
| 1245 | } | 1245 | } | ||
| 1246 | if err := sendHandshake(func() error { | 1246 | if err := sendHandshake(func() error { | ||
| 1247 | - | return transport.Send(wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(hello)}) | 1247 | + | return transport.SendClient(hello) |
| 1248 | }); err != nil { | 1248 | }); err != nil { | ||
| 1249 | return handshakeFailure("sending hello", err) | 1249 | return handshakeFailure("sending hello", err) | ||
| 1250 | } | 1250 | } | ||
| 1251 | ms.helloSent = true | 1251 | ms.helloSent = true | ||
| 1252 | 1252 | ||||
| 1253 | // 2. Await Welcome or a typed rejection. | 1253 | // 2. Await Welcome or a typed rejection. | ||
| 1254 | - | var reply wire.Frame | 1254 | + | var reply protocol.ServerMessage |
| 1255 | if err := boundedHandshakeOperationWithTransition(handshakeCtx, transport, func() error { | 1255 | if err := boundedHandshakeOperationWithTransition(handshakeCtx, transport, func() error { | ||
| 1256 | - | var recvErr error | 1256 | + | var receiveErr error |
| 1257 | - | reply, recvErr = transport.Recv() | 1257 | + | reply, receiveErr = transport.ReceiveServer() |
| 1258 | - | return recvErr | 1258 | + | return receiveErr |
| 1259 | }, transition); err != nil { | 1259 | }, transition); err != nil { | ||
| 1260 | return handshakeFailure("awaiting welcome", err) | 1260 | return handshakeFailure("awaiting welcome", err) | ||
| 1261 | } | 1261 | } | ||
| @@ -1271,12 +1271,9 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1271 | err: err, | 1271 | err: err, | ||
| 1272 | } | 1272 | } | ||
| 1273 | } | 1273 | } | ||
| 1274 | - | switch reply.Type { | 1274 | + | switch message := reply.(type) { |
| 1275 | - | case wire.MsgWelcome: | 1275 | + | case protocol.Welcome: |
| 1276 | - | welcome, derr := wire.UnmarshalWelcome(reply.Payload) | 1276 | + | welcome := message |
| 1277 | - | if derr != nil { | |||
| 1278 | - | return result(false, fmt.Errorf("vev: decoding welcome: %w", derr)) | |||
| 1279 | - | } | |||
| 1280 | resumeToken = welcome.ResumeToken | 1277 | resumeToken = welcome.ResumeToken | ||
| 1281 | name = welcome.SessionName | 1278 | name = welcome.SessionName | ||
| 1282 | committedIdentity = cloneCommittedIdentity(welcome.CommittedIdentity) | 1279 | committedIdentity = cloneCommittedIdentity(welcome.CommittedIdentity) | ||
| @@ -1287,14 +1284,10 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1287 | remember() | 1284 | remember() | ||
| 1288 | } | 1285 | } | ||
| 1289 | } | 1286 | } | ||
| 1290 | - | case wire.MsgError: | 1287 | + | case protocol.ErrorMsg: |
| 1291 | - | em, derr := wire.UnmarshalErrorMsg(reply.Payload) | 1288 | + | return result(false, &ProtocolError{Code: message.Code, Text: message.Text}) |
| 1292 | - | if derr != nil { | |||
| 1293 | - | return result(false, fmt.Errorf("vev: decoding error reply: %w", derr)) | |||
| 1294 | - | } | |||
| 1295 | - | return result(false, &ProtocolError{Code: em.Code, Text: em.Text}) | |||
| 1296 | default: | 1289 | default: | ||
| 1297 | - | return result(false, fmt.Errorf("vev: unexpected reply type %d before welcome", reply.Type)) | 1290 | + | return result(false, fmt.Errorf("vev: unexpected %T before welcome", reply)) |
| 1298 | } | 1291 | } | ||
| 1299 | if err := handshakeCtx.Err(); err != nil { | 1292 | if err := handshakeCtx.Err(); err != nil { | ||
| 1300 | return handshakeFailure("validating welcome", err) | 1293 | return handshakeFailure("validating welcome", err) | ||
| @@ -1320,30 +1313,20 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1320 | return welcomedResult(fmt.Errorf("vev: committing route identity: %w", commitErr)) | 1313 | return welcomedResult(fmt.Errorf("vev: committing route identity: %w", commitErr)) | ||
| 1321 | } | 1314 | } | ||
| 1322 | } | 1315 | } | ||
| 1323 | - | snapshotPayload, err := wire.MarshalRecentRouteSnapshot(a.runner.ledger.snapshot()) | 1316 | + | snapshot := a.runner.ledger.snapshot() |
| 1324 | - | if err != nil { | 1317 | + | attention := a.runner.ledger.attentionSubscription() |
| 1325 | - | return welcomedResult(fmt.Errorf("vev: encoding route snapshot: %w", err)) | |||
| 1326 | - | } | |||
| 1327 | - | attentionPayload, err := wire.MarshalRouteAttentionSubscription(a.runner.ledger.attentionSubscription()) | |||
| 1328 | - | if err != nil { | |||
| 1329 | - | return welcomedResult(fmt.Errorf("vev: encoding route attention subscription: %w", err)) | |||
| 1330 | - | } | |||
| 1331 | if err := sendHandshake(func() error { | 1318 | if err := sendHandshake(func() error { | ||
| 1332 | - | if err := transport.Send(wire.Frame{Type: wire.MsgRecentRouteSnapshot, Payload: snapshotPayload}); err != nil { | 1319 | + | if err := transport.SendClient(snapshot); err != nil { |
| 1333 | return err | 1320 | return err | ||
| 1334 | } | 1321 | } | ||
| 1335 | - | return transport.Send(wire.Frame{Type: wire.MsgRouteAttentionSubscription, Payload: attentionPayload}) | 1322 | + | return transport.SendClient(attention) |
| 1336 | }); err != nil { | 1323 | }); err != nil { | ||
| 1337 | return welcomedResult(fmt.Errorf("vev: publishing route snapshot: %w", err)) | 1324 | return welcomedResult(fmt.Errorf("vev: publishing route snapshot: %w", err)) | ||
| 1338 | } | 1325 | } | ||
| 1339 | } | 1326 | } | ||
| 1340 | if a.runner.routeFailure != nil { | 1327 | if a.runner.routeFailure != nil { | ||
| 1341 | - | payload, err := wire.MarshalRouteNavigationFailure(*a.runner.routeFailure) | |||
| 1342 | - | if err != nil { | |||
| 1343 | - | return welcomedResult(fmt.Errorf("vev: encoding route navigation failure: %w", err)) | |||
| 1344 | - | } | |||
| 1345 | if err := sendHandshake(func() error { | 1328 | if err := sendHandshake(func() error { | ||
| 1346 | - | return transport.Send(wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: payload}) | 1329 | + | return transport.SendClient(*a.runner.routeFailure) |
| 1347 | }); err != nil { | 1330 | }); err != nil { | ||
| 1348 | return welcomedResult(fmt.Errorf("vev: publishing route navigation failure: %w", err)) | 1331 | return welcomedResult(fmt.Errorf("vev: publishing route navigation failure: %w", err)) | ||
| 1349 | } | 1332 | } | ||
| @@ -1370,8 +1353,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1370 | input.start() | 1353 | input.start() | ||
| 1371 | defer input.stop() | 1354 | defer input.stop() | ||
| 1372 | } | 1355 | } | ||
| 1373 | - | sendCh := make(chan wire.Frame, sendQueueDepth) | 1356 | + | sendCh := make(chan protocol.ClientMessage, sendQueueDepth) |
| 1374 | - | controlCh := make(chan wire.Frame, 1) | 1357 | + | controlCh := make(chan protocol.ClientMessage, 1) |
| 1375 | barrierCh := make(chan chan struct{}) | 1358 | barrierCh := make(chan chan struct{}) | ||
| 1376 | inputGate := newSamePeerInputGate() | 1359 | inputGate := newSamePeerInputGate() | ||
| 1377 | ackQueue := newCumulativeAckQueue() | 1360 | ackQueue := newCumulativeAckQueue() | ||
| @@ -1394,18 +1377,18 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1394 | } | 1377 | } | ||
| 1395 | } | 1378 | } | ||
| 1396 | publish := func(snapshot protocol.Theme) error { | 1379 | publish := func(snapshot protocol.Theme) error { | ||
| 1397 | - | frame := wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(snapshot)} | 1380 | + | message := protocol.ClientMessage(snapshot) |
| 1398 | // The initial cleared snapshot is sent before the sender exists. This | 1381 | // The initial cleared snapshot is sent before the sender exists. This | ||
| 1399 | // keeps the generation publication ordered before the first query and | 1382 | // keeps the generation publication ordered before the first query and | ||
| 1400 | // avoids leaving a queued Theme behind an immediate detach. | 1383 | // avoids leaving a queued Theme behind an immediate detach. | ||
| 1401 | if !senderStarted { | 1384 | if !senderStarted { | ||
| 1402 | - | if err := sendHandshake(func() error { return transport.Send(frame) }); err != nil { | 1385 | + | if err := sendHandshake(func() error { return transport.SendClient(message) }); err != nil { |
| 1403 | return fmt.Errorf("sending initial theme: %w", err) | 1386 | return fmt.Errorf("sending initial theme: %w", err) | ||
| 1404 | } | 1387 | } | ||
| 1405 | return nil | 1388 | return nil | ||
| 1406 | } | 1389 | } | ||
| 1407 | select { | 1390 | select { | ||
| 1408 | - | case sendCh <- frame: | 1391 | + | case sendCh <- message: |
| 1409 | return nil | 1392 | return nil | ||
| 1410 | case <-loopCtx.Done(): | 1393 | case <-loopCtx.Done(): | ||
| 1411 | return context.Canceled | 1394 | return context.Canceled | ||
| @@ -1504,11 +1487,11 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1504 | // Before the asynchronous sender starts, preserve transport ordering by | 1487 | // Before the asynchronous sender starts, preserve transport ordering by | ||
| 1505 | // requesting the reset synchronously after the initial Theme publication. | 1488 | // requesting the reset synchronously after the initial Theme publication. | ||
| 1506 | resetErr := sendHandshake(func() error { | 1489 | resetErr := sendHandshake(func() error { | ||
| 1507 | - | payload, err := wire.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight}) | 1490 | + | message := protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight} |
| 1508 | - | if err != nil { | 1491 | + | if err := protocol.ValidateGeometry(message.Geometry()); err != nil { |
| 1509 | - | return err | 1492 | + | return fmt.Errorf("validating terminal size for reconnect reconciliation: %w", err) |
| 1510 | } | 1493 | } | ||
| 1511 | - | return transport.Send(wire.Frame{Type: wire.MsgResize, Payload: payload}) | 1494 | + | return transport.SendClient(message) |
| 1512 | }) | 1495 | }) | ||
| 1513 | if resetErr != nil { | 1496 | if resetErr != nil { | ||
| 1514 | resetErr = handshakeContextError(ctx, handshakeTimedOut, resetErr) | 1497 | resetErr = handshakeContextError(ctx, handshakeTimedOut, resetErr) | ||
| @@ -1630,12 +1613,12 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1630 | return fmt.Errorf("reading terminal geometry for parked route: %w", err) | 1613 | return fmt.Errorf("reading terminal geometry for parked route: %w", err) | ||
| 1631 | } | 1614 | } | ||
| 1632 | geometry = geometry.NormalizePixels() | 1615 | geometry = geometry.NormalizePixels() | ||
| 1633 | - | payload, err := wire.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight}) | 1616 | + | message := protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight} |
| 1634 | - | if err != nil { | 1617 | + | if err := protocol.ValidateGeometry(message.Geometry()); err != nil { |
| 1635 | - | return fmt.Errorf("encoding terminal size for parked route: %w", err) | 1618 | + | return fmt.Errorf("validating terminal size for parked route: %w", err) |
| 1636 | } | 1619 | } | ||
| 1637 | select { | 1620 | select { | ||
| 1638 | - | case controlCh <- wire.Frame{Type: wire.MsgResize, Payload: payload}: | 1621 | + | case controlCh <- message: |
| 1639 | return nil | 1622 | return nil | ||
| 1640 | case <-loopCtx.Done(): | 1623 | case <-loopCtx.Done(): | ||
| 1641 | return context.Canceled | 1624 | return context.Canceled | ||
| @@ -1647,12 +1630,11 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1647 | } | 1630 | } | ||
| 1648 | nextParkedRequestID++ | 1631 | nextParkedRequestID++ | ||
| 1649 | request := protocol.ParkedRouteRequest{RequestID: nextParkedRequestID, LeaseID: leaseID, Action: action, Target: target} | 1632 | request := protocol.ParkedRouteRequest{RequestID: nextParkedRequestID, LeaseID: leaseID, Action: action, Target: target} | ||
| 1650 | - | payload := wire.MarshalParkedRouteRequest(request) | 1633 | + | if protocol.ValidateParkedRouteRequest(request) != nil { |
| 1651 | - | if payload == nil { | |||
| 1652 | return errors.New("vev: invalid parked-route request") | 1634 | return errors.New("vev: invalid parked-route request") | ||
| 1653 | } | 1635 | } | ||
| 1654 | select { | 1636 | select { | ||
| 1655 | - | case controlCh <- wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: payload}: | 1637 | + | case controlCh <- request: |
| 1656 | parkedPending = &parkedRoutePending{ | 1638 | parkedPending = &parkedRoutePending{ | ||
| 1657 | requestID: request.RequestID, action: action, leaseID: leaseID, | 1639 | requestID: request.RequestID, action: action, leaseID: leaseID, | ||
| 1658 | timer: clk.NewTimer(protocol.HandshakeTimeout), | 1640 | timer: clk.NewTimer(protocol.HandshakeTimeout), | ||
| @@ -1727,12 +1709,12 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1727 | return fmt.Errorf("reading terminal geometry for reconnect reconciliation: %w", serr) | 1709 | return fmt.Errorf("reading terminal geometry for reconnect reconciliation: %w", serr) | ||
| 1728 | } | 1710 | } | ||
| 1729 | geometry = geometry.NormalizePixels() | 1711 | geometry = geometry.NormalizePixels() | ||
| 1730 | - | payload, err := wire.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight}) | 1712 | + | message := protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight} |
| 1731 | - | if err != nil { | 1713 | + | if err := protocol.ValidateGeometry(message.Geometry()); err != nil { |
| 1732 | - | return fmt.Errorf("encoding terminal size for reconnect reconciliation: %w", err) | 1714 | + | return fmt.Errorf("validating terminal size for reconnect reconciliation: %w", err) |
| 1733 | } | 1715 | } | ||
| 1734 | select { | 1716 | select { | ||
| 1735 | - | case sendCh <- wire.Frame{Type: wire.MsgResize, Payload: payload}: | 1717 | + | case sendCh <- message: |
| 1736 | awaitingReconnectReset = true | 1718 | awaitingReconnectReset = true | ||
| 1737 | return nil | 1719 | return nil | ||
| 1738 | case <-loopCtx.Done(): | 1720 | case <-loopCtx.Done(): | ||
| @@ -1743,20 +1725,13 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1743 | if a.runner.ledger == nil { | 1725 | if a.runner.ledger == nil { | ||
| 1744 | return errors.New("vev: route ledger unavailable") | 1726 | return errors.New("vev: route ledger unavailable") | ||
| 1745 | } | 1727 | } | ||
| 1746 | - | snapshotPayload, err := wire.MarshalRecentRouteSnapshot(a.runner.ledger.snapshot()) | 1728 | + | messages := []protocol.ClientMessage{ |
| 1747 | - | if err != nil { | 1729 | + | a.runner.ledger.snapshot(), |
| 1748 | - | return err | 1730 | + | a.runner.ledger.attentionSubscription(), |
| 1749 | - | } | |||
| 1750 | - | attentionPayload, err := wire.MarshalRouteAttentionSubscription(a.runner.ledger.attentionSubscription()) | |||
| 1751 | - | if err != nil { | |||
| 1752 | - | return err | |||
| 1753 | } | 1731 | } | ||
| 1754 | - | for _, frame := range []wire.Frame{ | 1732 | + | for _, message := range messages { |
| 1755 | - | {Type: wire.MsgRecentRouteSnapshot, Payload: snapshotPayload}, | |||
| 1756 | - | {Type: wire.MsgRouteAttentionSubscription, Payload: attentionPayload}, | |||
| 1757 | - | } { | |||
| 1758 | select { | 1733 | select { | ||
| 1759 | - | case sendCh <- frame: | 1734 | + | case sendCh <- message: |
| 1760 | case <-loopCtx.Done(): | 1735 | case <-loopCtx.Done(): | ||
| 1761 | return context.Canceled | 1736 | return context.Canceled | ||
| 1762 | } | 1737 | } | ||
| @@ -1764,16 +1739,12 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1764 | return nil | 1739 | return nil | ||
| 1765 | } | 1740 | } | ||
| 1766 | sendRouteFailure := func(action protocol.RouteNavigationAction, code protocol.RouteFailureCode) error { | 1741 | sendRouteFailure := func(action protocol.RouteNavigationAction, code protocol.RouteFailureCode) error { | ||
| 1767 | - | payload, err := wire.MarshalRouteNavigationFailure(protocol.RouteNavigationFailure{ | 1742 | + | failure := protocol.RouteNavigationFailure{Key: action.Key, Generation: action.Generation, Code: code} |
| 1768 | - | Key: action.Key, | 1743 | + | if failure.Code.Validate() != nil || failure.Key == 0 || failure.Generation == 0 { |
| 1769 | - | Generation: action.Generation, | 1744 | + | return protocol.ErrInvalidRouteWire |
| 1770 | - | Code: code, | |||
| 1771 | - | }) | |||
| 1772 | - | if err != nil { | |||
| 1773 | - | return err | |||
| 1774 | } | 1745 | } | ||
| 1775 | select { | 1746 | select { | ||
| 1776 | - | case sendCh <- wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: payload}: | 1747 | + | case sendCh <- failure: |
| 1777 | return nil | 1748 | return nil | ||
| 1778 | case <-loopCtx.Done(): | 1749 | case <-loopCtx.Done(): | ||
| 1779 | return context.Canceled | 1750 | return context.Canceled | ||
| @@ -1828,7 +1799,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1828 | return welcomedResult(fmt.Errorf("dismissing reconnect toast: %w", err)) | 1799 | return welcomedResult(fmt.Errorf("dismissing reconnect toast: %w", err)) | ||
| 1829 | } | 1800 | } | ||
| 1830 | select { | 1801 | select { | ||
| 1831 | - | case sendCh <- wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(protocol.ClientNotice{Action: protocol.ClientNoticeLinkConnected})}: | 1802 | + | case sendCh <- protocol.ClientNotice{Action: protocol.ClientNoticeLinkConnected}: |
| 1832 | case <-loopCtx.Done(): | 1803 | case <-loopCtx.Done(): | ||
| 1833 | return welcomedResult(nil) | 1804 | return welcomedResult(nil) | ||
| 1834 | } | 1805 | } | ||
| @@ -1837,7 +1808,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1837 | if ev.State == ports.LinkStateDegraded { | 1808 | if ev.State == ports.LinkStateDegraded { | ||
| 1838 | log.Warn("UDP link degraded") | 1809 | log.Warn("UDP link degraded") | ||
| 1839 | select { | 1810 | select { | ||
| 1840 | - | case sendCh <- wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(protocol.ClientNotice{Action: protocol.ClientNoticeLinkDegraded})}: | 1811 | + | case sendCh <- protocol.ClientNotice{Action: protocol.ClientNoticeLinkDegraded}: |
| 1841 | case <-loopCtx.Done(): | 1812 | case <-loopCtx.Done(): | ||
| 1842 | return welcomedResult(nil) | 1813 | return welcomedResult(nil) | ||
| 1843 | } | 1814 | } | ||
| @@ -1884,12 +1855,9 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1884 | } | 1855 | } | ||
| 1885 | return welcomedResult(fmt.Errorf("vev: receiving from daemon: %w", r.err)) | 1856 | return welcomedResult(fmt.Errorf("vev: receiving from daemon: %w", r.err)) | ||
| 1886 | } | 1857 | } | ||
| 1887 | - | switch r.frame.Type { | 1858 | + | switch message := r.message.(type) { |
| 1888 | - | case wire.MsgOutput: | 1859 | + | case protocol.Output: |
| 1889 | - | o, derr := wire.UnmarshalOutput(r.frame.Payload) | 1860 | + | o := message |
| 1890 | - | if derr != nil { | |||
| 1891 | - | return welcomedResult(fmt.Errorf("vev: decoding output: %w", derr)) | |||
| 1892 | - | } | |||
| 1893 | nextState, accepted := outputState.next(o) | 1861 | nextState, accepted := outputState.next(o) | ||
| 1894 | if !accepted { | 1862 | if !accepted { | ||
| 1895 | // A discarded frame was never written and must never be ACKed. | 1863 | // A discarded frame was never written and must never be ACKed. | ||
| @@ -1897,7 +1865,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1897 | // repeated gaps while that reset is in flight. | 1865 | // repeated gaps while that reset is in flight. | ||
| 1898 | if !outputResetRequested { | 1866 | if !outputResetRequested { | ||
| 1899 | select { | 1867 | select { | ||
| 1900 | - | case sendCh <- wire.Frame{Type: wire.MsgOutputResetRequest, Payload: wire.MarshalOutputResetRequest(protocol.OutputResetRequest{})}: | 1868 | + | case sendCh <- protocol.OutputResetRequest{}: |
| 1901 | outputResetRequested = true | 1869 | outputResetRequested = true | ||
| 1902 | case <-loopCtx.Done(): | 1870 | case <-loopCtx.Done(): | ||
| 1903 | return loopCanceledResult() | 1871 | return loopCanceledResult() | ||
| @@ -1947,11 +1915,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1947 | log.Debug("received first output") | 1915 | log.Debug("received first output") | ||
| 1948 | endHandshake() | 1916 | endHandshake() | ||
| 1949 | } | 1917 | } | ||
| 1950 | - | case wire.MsgAttachTarget: | 1918 | + | case protocol.AttachTarget: |
| 1951 | - | target, derr := wire.UnmarshalAttachTarget(r.frame.Payload) | 1919 | + | target := message |
| 1952 | - | if derr != nil { | |||
| 1953 | - | return welcomedResult(fmt.Errorf("vev: decoding attach target: %w", derr)) | |||
| 1954 | - | } | |||
| 1955 | if a.returnAttachTargets { | 1920 | if a.returnAttachTargets { | ||
| 1956 | handoff := welcomedResult(nil) | 1921 | handoff := welcomedResult(nil) | ||
| 1957 | handoff.target = &target | 1922 | handoff.target = &target | ||
| @@ -1967,18 +1932,18 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1967 | } | 1932 | } | ||
| 1968 | nextSamePeerRequestID++ | 1933 | nextSamePeerRequestID++ | ||
| 1969 | request := a.runner.ledger.samePeerHandoff(request, target) | 1934 | request := a.runner.ledger.samePeerHandoff(request, target) | ||
| 1970 | - | payload, err := wire.MarshalSamePeerSwitchRequest(protocol.SamePeerSwitchRequest{ | 1935 | + | switchRequest := protocol.SamePeerSwitchRequest{ |
| 1971 | RequestID: nextSamePeerRequestID, | 1936 | RequestID: nextSamePeerRequestID, | ||
| 1972 | Target: *target.ExactTarget, | 1937 | Target: *target.ExactTarget, | ||
| 1973 | PreferredTabID: request.PreferredTabID, | 1938 | PreferredTabID: request.PreferredTabID, | ||
| 1974 | - | }) | 1939 | + | } |
| 1975 | - | if err != nil { | 1940 | + | if switchRequest.Validate() != nil { |
| 1976 | - | return welcomedResult(fmt.Errorf("vev: encoding same-peer switch: %w", err)) | 1941 | + | return welcomedResult(errors.New("vev: invalid same-peer switch")) |
| 1977 | } | 1942 | } | ||
| 1978 | samePeerSwitch = &samePeerSwitchPending{requestID: nextSamePeerRequestID, target: *target.ExactTarget} | 1943 | samePeerSwitch = &samePeerSwitchPending{requestID: nextSamePeerRequestID, target: *target.ExactTarget} | ||
| 1979 | inputGate.setPaused(true) | 1944 | inputGate.setPaused(true) | ||
| 1980 | select { | 1945 | select { | ||
| 1981 | - | case controlCh <- wire.Frame{Type: wire.MsgSamePeerSwitchRequest, Payload: payload}: | 1946 | + | case controlCh <- switchRequest: |
| 1982 | continue | 1947 | continue | ||
| 1983 | case <-loopCtx.Done(): | 1948 | case <-loopCtx.Done(): | ||
| 1984 | return loopCanceledResult() | 1949 | return loopCanceledResult() | ||
| @@ -1987,11 +1952,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1987 | handoff := welcomedResult(nil) | 1952 | handoff := welcomedResult(nil) | ||
| 1988 | handoff.target = &target | 1953 | handoff.target = &target | ||
| 1989 | return handoff | 1954 | return handoff | ||
| 1990 | - | case wire.MsgParkedRouteResponse: | 1955 | + | case protocol.ParkedRouteResponse: |
| 1991 | - | response, derr := wire.UnmarshalParkedRouteResponse(r.frame.Payload) | 1956 | + | response := message |
| 1992 | - | if derr != nil { | |||
| 1993 | - | return welcomedResult(fmt.Errorf("vev: decoding parked-route response: %w", derr)) | |||
| 1994 | - | } | |||
| 1995 | if parkedPending == nil || response.RequestID != parkedPending.requestID { | 1957 | if parkedPending == nil || response.RequestID != parkedPending.requestID { | ||
| 1996 | return welcomedResult(errors.New("vev: unexpected parked-route response")) | 1958 | return welcomedResult(errors.New("vev: unexpected parked-route response")) | ||
| 1997 | } | 1959 | } | ||
| @@ -2034,12 +1996,9 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 2034 | return fallback | 1996 | return fallback | ||
| 2035 | } | 1997 | } | ||
| 2036 | } | 1998 | } | ||
| 2037 | - | case wire.MsgNavigationAction: | 1999 | + | case protocol.NavigationDirective: |
| 2038 | - | directive, derr := wire.UnmarshalNavigationDirective(r.frame.Payload) | 2000 | + | directive := message |
| 2039 | - | if derr != nil { | 2001 | + | datagramRoute := transport.Capabilities().PreferredOutputWindow == 1 |
| 2040 | - | return welcomedResult(fmt.Errorf("vev: decoding navigation action: %w", derr)) | |||
| 2041 | - | } | |||
| 2042 | - | _, datagramRoute := transport.(ports.DatagramTransport) | |||
| 2043 | if directive.Action == protocol.NavigationOpenHomePicker && a.openHomePicker != nil && datagramRoute { | 2002 | if directive.Action == protocol.NavigationOpenHomePicker && a.openHomePicker != nil && datagramRoute { | ||
| 2044 | stopForeground() | 2003 | stopForeground() | ||
| 2045 | if err := flushSender(); err != nil { | 2004 | if err := flushSender(); err != nil { | ||
| @@ -2053,11 +2012,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 2053 | navigation := welcomedResult(nil) | 2012 | navigation := welcomedResult(nil) | ||
| 2054 | navigation.action = directive.Action | 2013 | navigation.action = directive.Action | ||
| 2055 | return navigation | 2014 | return navigation | ||
| 2056 | - | case wire.MsgNavigateRecentRoute: | 2015 | + | case protocol.RouteNavigationAction: |
| 2057 | - | action, derr := wire.UnmarshalRouteNavigationAction(r.frame.Payload) | 2016 | + | action := message |
| 2058 | - | if derr != nil { | |||
| 2059 | - | return welcomedResult(fmt.Errorf("vev: decoding route navigation action: %w", derr)) | |||
| 2060 | - | } | |||
| 2061 | if a.runner.ledger == nil { | 2017 | if a.runner.ledger == nil { | ||
| 2062 | if ferr := sendRouteFailure(action, protocol.RouteFailureUnavailable); ferr != nil { | 2018 | if ferr := sendRouteFailure(action, protocol.RouteFailureUnavailable); ferr != nil { | ||
| 2063 | return welcomedResult(ferr) | 2019 | return welcomedResult(ferr) | ||
| @@ -2077,11 +2033,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 2077 | navigation := welcomedResult(nil) | 2033 | navigation := welcomedResult(nil) | ||
| 2078 | navigation.routeAction = &action | 2034 | navigation.routeAction = &action | ||
| 2079 | return navigation | 2035 | return navigation | ||
| 2080 | - | case wire.MsgCommittedRouteIdentity: | 2036 | + | case protocol.CommittedRouteIdentity: |
| 2081 | - | identity, derr := wire.UnmarshalCommittedRouteIdentity(r.frame.Payload) | 2037 | + | identity := message |
| 2082 | - | if derr != nil { | |||
| 2083 | - | return welcomedResult(fmt.Errorf("vev: decoding committed route identity: %w", derr)) | |||
| 2084 | - | } | |||
| 2085 | if samePeerSwitch != nil && identity.Target != samePeerSwitch.target { | 2038 | if samePeerSwitch != nil && identity.Target != samePeerSwitch.target { | ||
| 2086 | return welcomedResult(errors.New("vev: same-peer switch committed an unexpected target")) | 2039 | return welcomedResult(errors.New("vev: same-peer switch committed an unexpected target")) | ||
| 2087 | } | 2040 | } | ||
| @@ -2100,11 +2053,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 2100 | samePeerSwitch = nil | 2053 | samePeerSwitch = nil | ||
| 2101 | inputGate.setPaused(false) | 2054 | inputGate.setPaused(false) | ||
| 2102 | } | 2055 | } | ||
| 2103 | - | case wire.MsgSamePeerSwitchFailure: | 2056 | + | case protocol.SamePeerSwitchFailure: |
| 2104 | - | failure, derr := wire.UnmarshalSamePeerSwitchFailure(r.frame.Payload) | 2057 | + | failure := message |
| 2105 | - | if derr != nil { | |||
| 2106 | - | return welcomedResult(fmt.Errorf("vev: decoding same-peer switch failure: %w", derr)) | |||
| 2107 | - | } | |||
| 2108 | if samePeerSwitch != nil && failure.RequestID == samePeerSwitch.requestID { | 2058 | if samePeerSwitch != nil && failure.RequestID == samePeerSwitch.requestID { | ||
| 2109 | samePeerSwitch = nil | 2059 | samePeerSwitch = nil | ||
| 2110 | inputGate.setPaused(false) | 2060 | inputGate.setPaused(false) | ||
| @@ -2112,7 +2062,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 2112 | transitionWaitingFull = true | 2062 | transitionWaitingFull = true | ||
| 2113 | if !outputResetRequested { | 2063 | if !outputResetRequested { | ||
| 2114 | select { | 2064 | select { | ||
| 2115 | - | case sendCh <- wire.Frame{Type: wire.MsgOutputResetRequest, Payload: wire.MarshalOutputResetRequest(protocol.OutputResetRequest{})}: | 2065 | + | case sendCh <- protocol.OutputResetRequest{}: |
| 2116 | outputResetRequested = true | 2066 | outputResetRequested = true | ||
| 2117 | case <-loopCtx.Done(): | 2067 | case <-loopCtx.Done(): | ||
| 2118 | return loopCanceledResult() | 2068 | return loopCanceledResult() | ||
| @@ -2122,11 +2072,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 2122 | transition.stop() | 2072 | transition.stop() | ||
| 2123 | } | 2073 | } | ||
| 2124 | } | 2074 | } | ||
| 2125 | - | case wire.MsgRoutePosition: | 2075 | + | case protocol.RoutePosition: |
| 2126 | - | position, derr := wire.UnmarshalRoutePosition(r.frame.Payload) | 2076 | + | position := message |
| 2127 | - | if derr != nil { | |||
| 2128 | - | return welcomedResult(fmt.Errorf("vev: decoding route position: %w", derr)) | |||
| 2129 | - | } | |||
| 2130 | if a.runner.ledger == nil { | 2077 | if a.runner.ledger == nil { | ||
| 2131 | return welcomedResult(errors.New("vev: route ledger unavailable")) | 2078 | return welcomedResult(errors.New("vev: route ledger unavailable")) | ||
| 2132 | } | 2079 | } | ||
| @@ -2134,17 +2081,11 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 2134 | return welcomedResult(fmt.Errorf("vev: remembering route position: %w", derr)) | 2081 | return welcomedResult(fmt.Errorf("vev: remembering route position: %w", derr)) | ||
| 2135 | } | 2082 | } | ||
| 2136 | routePosition = cloneRoutePosition(&position) | 2083 | routePosition = cloneRoutePosition(&position) | ||
| 2137 | - | case wire.MsgRouteNavigationFailure: | 2084 | + | case protocol.RouteNavigationFailure: |
| 2138 | - | failure, derr := wire.UnmarshalRouteNavigationFailure(r.frame.Payload) | 2085 | + | failure := message |
| 2139 | - | if derr != nil { | |||
| 2140 | - | return welcomedResult(fmt.Errorf("vev: decoding route navigation failure: %w", derr)) | |||
| 2141 | - | } | |||
| 2142 | log.Warn("route navigation rejected", "key", failure.Key, "generation", failure.Generation, "code", failure.Code) | 2086 | log.Warn("route navigation rejected", "key", failure.Key, "generation", failure.Generation, "code", failure.Code) | ||
| 2143 | - | case wire.MsgDetached: | 2087 | + | case protocol.Detached: |
| 2144 | - | d, derr := wire.UnmarshalDetached(r.frame.Payload) | 2088 | + | d := message |
| 2145 | - | if derr != nil { | |||
| 2146 | - | return welcomedResult(fmt.Errorf("vev: decoding detached: %w", derr)) | |||
| 2147 | - | } | |||
| 2148 | ms.detached = true | 2089 | ms.detached = true | ||
| 2149 | if d.Reason == protocol.ReasonDetach { | 2090 | if d.Reason == protocol.ReasonDetach { | ||
| 2150 | log.Info("detached cleanly", "reason", d.Reason) | 2091 | log.Info("detached cleanly", "reason", d.Reason) | ||
| @@ -2152,34 +2093,43 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 2152 | log.Warn("detached by daemon", "reason", d.Reason) | 2093 | log.Warn("detached by daemon", "reason", d.Reason) | ||
| 2153 | } | 2094 | } | ||
| 2154 | return welcomedResult(detachedResult(d.Reason)) | 2095 | return welcomedResult(detachedResult(d.Reason)) | ||
| 2155 | - | case wire.MsgPong: | 2096 | + | case protocol.Pong: |
| 2156 | // Liveness reply; nothing to do. | 2097 | // Liveness reply; nothing to do. | ||
| 2157 | - | default: | |||
| 2158 | - | // Unknown/out-of-band message types are ignored so a newer | |||
| 2159 | - | // daemon can add server->client messages without breaking us. | |||
| 2160 | } | 2098 | } | ||
| 2161 | } | 2099 | } | ||
| 2162 | } | 2100 | } | ||
| 2163 | } | 2101 | } | ||
| 2164 | 2102 | ||||
| 2165 | - | // recvResult carries one framed message (or a read error) from the receive | 2103 | + | // recvResult carries one typed message (or a read error) from the receive pump. |
| 2166 | - | // pump to the main loop. | |||
| 2167 | type recvResult struct { | 2104 | type recvResult struct { | ||
| 2168 | - | frame wire.Frame | 2105 | + | message protocol.ServerMessage |
| 2169 | - | err error | 2106 | + | err error |
| 2170 | } | 2107 | } | ||
| 2171 | 2108 | ||||
| 2172 | - | // runRecv reads frames until an error, forwarding each to the main loop. | 2109 | + | // runRecv reads typed messages until an error, forwarding each to the main loop. |
| 2173 | // It exits on the first error or when the loop context is cancelled. | 2110 | // It exits on the first error or when the loop context is cancelled. | ||
| 2174 | - | func runRecv(ctx context.Context, transport ports.Transport, out chan<- recvResult, failed chan<- struct{}, log *slog.Logger) { | 2111 | + | func runRecv(ctx context.Context, transport ports.ClientConnection, out chan<- recvResult, failed chan<- struct{}, log *slog.Logger) { |
| 2175 | defer log.Debug("receive pump exited") | 2112 | defer log.Debug("receive pump exited") | ||
| 2176 | for { | 2113 | for { | ||
| 2177 | - | f, err := transport.Recv() | 2114 | + | message, err := transport.ReceiveServer() |
| 2115 | + | if err != nil { | |||
| 2116 | + | var failure *protocol.DecodeFailure | |||
| 2117 | + | if errors.As(err, &failure) && (failure.Category == protocol.DecodeUnknownType || failure.Category == protocol.DecodeWrongDirection) { | |||
| 2118 | + | log.Debug("ignoring rejected server message", | |||
| 2119 | + | "category", failure.Category, | |||
| 2120 | + | "kind", failure.Kind, | |||
| 2121 | + | "type", failure.Type, | |||
| 2122 | + | "version", failure.Version, | |||
| 2123 | + | "request", failure.RequestID, | |||
| 2124 | + | ) | |||
| 2125 | + | continue | |||
| 2126 | + | } | |||
| 2127 | + | } | |||
| 2178 | if err != nil && failed != nil { | 2128 | if err != nil && failed != nil { | ||
| 2179 | close(failed) | 2129 | close(failed) | ||
| 2180 | } | 2130 | } | ||
| 2181 | select { | 2131 | select { | ||
| 2182 | - | case out <- recvResult{frame: f, err: err}: | 2132 | + | case out <- recvResult{message: message, err: err}: |
| 2183 | case <-ctx.Done(): | 2133 | case <-ctx.Done(): | ||
| 2184 | return | 2134 | return | ||
| 2185 | } | 2135 | } | ||
| @@ -2189,9 +2139,6 @@ func runRecv(ctx context.Context, transport ports.Transport, out chan<- recvResu | |||||
| 2189 | } | 2139 | } | ||
| 2190 | } | 2140 | } | ||
| 2191 | 2141 | ||||
| 2192 | - | // runSender is the sole caller of transport.Send: serialising the input and | |||
| 2193 | - | // resize pumps through one goroutine keeps the transport's single-writer | |||
| 2194 | - | // contract intact. A send failure cancels the loop and is surfaced once. | |||
| 2195 | type cumulativeAckQueue struct { | 2142 | type cumulativeAckQueue struct { | ||
| 2196 | mu sync.Mutex | 2143 | mu sync.Mutex | ||
| 2197 | latestEpoch uint64 | 2144 | latestEpoch uint64 | ||
| @@ -2226,12 +2173,15 @@ func (q *cumulativeAckQueue) take() (uint64, uint64) { | |||||
| 2226 | return epoch, state | 2173 | return epoch, state | ||
| 2227 | } | 2174 | } | ||
| 2228 | 2175 | ||||
| 2229 | - | // runSender preserves normal-frame order while allowing switch control and | 2176 | + | // runSender is the sole caller of SendClient: serialising the input and resize |
| 2230 | - | // output ACKs to make progress when raw input is held for a same-peer switch. | 2177 | + | // pumps through one goroutine keeps the connection's single-writer contract. |
| 2231 | - | func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.Transport, control <-chan wire.Frame, barriers <-chan chan struct{}, in <-chan wire.Frame, inputGate *samePeerInputGate, acks *cumulativeAckQueue, errCh chan<- error, log *slog.Logger) { | 2178 | + | // It preserves normal-frame order while allowing switch control and output ACKs |
| 2179 | + | // to make progress when raw input is held for a same-peer switch. A send failure | |||
| 2180 | + | // cancels the loop and is surfaced once. | |||
| 2181 | + | func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.ClientConnection, control <-chan protocol.ClientMessage, barriers <-chan chan struct{}, in <-chan protocol.ClientMessage, inputGate *samePeerInputGate, acks *cumulativeAckQueue, errCh chan<- error, log *slog.Logger) { | |||
| 2232 | defer log.Debug("sender pump exited") | 2182 | defer log.Debug("sender pump exited") | ||
| 2233 | - | send := func(f wire.Frame) bool { | 2183 | + | send := func(message protocol.ClientMessage) bool { |
| 2234 | - | if err := transport.Send(f); err != nil { | 2184 | + | if err := transport.SendClient(message); err != nil { |
| 2235 | select { | 2185 | select { | ||
| 2236 | case errCh <- err: | 2186 | case errCh <- err: | ||
| 2237 | default: | 2187 | default: | ||
| @@ -2246,16 +2196,7 @@ func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.T | |||||
| 2246 | if state == 0 { | 2196 | if state == 0 { | ||
| 2247 | return true | 2197 | return true | ||
| 2248 | } | 2198 | } | ||
| 2249 | - | payload, err := wire.MarshalAck(protocol.Ack{Epoch: epoch, State: state}) | 2199 | + | return send(protocol.Ack{Epoch: epoch, State: state}) |
| 2250 | - | if err != nil { | |||
| 2251 | - | select { | |||
| 2252 | - | case errCh <- fmt.Errorf("encoding output ACK: %w", err): | |||
| 2253 | - | default: | |||
| 2254 | - | } | |||
| 2255 | - | cancel() | |||
| 2256 | - | return false | |||
| 2257 | - | } | |||
| 2258 | - | return send(wire.Frame{Type: wire.MsgAck, Payload: payload}) | |||
| 2259 | } | 2200 | } | ||
| 2260 | flushPending := func() bool { | 2201 | flushPending := func() bool { | ||
| 2261 | for { | 2202 | for { | ||
| @@ -2287,12 +2228,12 @@ func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.T | |||||
| 2287 | close(done) | 2228 | close(done) | ||
| 2288 | return true | 2229 | return true | ||
| 2289 | } | 2230 | } | ||
| 2290 | - | var heldInput *wire.Frame | 2231 | + | var heldInput protocol.ClientMessage |
| 2291 | for { | 2232 | for { | ||
| 2292 | if heldInput != nil { | 2233 | if heldInput != nil { | ||
| 2293 | paused, changed := inputGate.snapshot() | 2234 | paused, changed := inputGate.snapshot() | ||
| 2294 | if !paused { | 2235 | if !paused { | ||
| 2295 | - | if !send(*heldInput) { | 2236 | + | if !send(heldInput) { |
| 2296 | return | 2237 | return | ||
| 2297 | } | 2238 | } | ||
| 2298 | heldInput = nil | 2239 | heldInput = nil | ||
| @@ -2348,23 +2289,23 @@ func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.T | |||||
| 2348 | if !completeBarrier(done) { | 2289 | if !completeBarrier(done) { | ||
| 2349 | return | 2290 | return | ||
| 2350 | } | 2291 | } | ||
| 2351 | - | case frame := <-in: | 2292 | + | case message := <-in: |
| 2352 | select { | 2293 | select { | ||
| 2353 | case <-ctx.Done(): | 2294 | case <-ctx.Done(): | ||
| 2354 | return | 2295 | return | ||
| 2355 | default: | 2296 | default: | ||
| 2356 | } | 2297 | } | ||
| 2357 | - | if frame.Type == wire.MsgInput || frame.Type == wire.MsgImagePush { | 2298 | + | switch message.(type) { |
| 2299 | + | case protocol.Input, protocol.ImagePush: | |||
| 2358 | if paused, _ := inputGate.snapshot(); paused { | 2300 | if paused, _ := inputGate.snapshot(); paused { | ||
| 2359 | - | held := frame | 2301 | + | heldInput = message |
| 2360 | - | heldInput = &held | |||
| 2361 | if inputGate != nil && inputGate.afterInputHeld != nil { | 2302 | if inputGate != nil && inputGate.afterInputHeld != nil { | ||
| 2362 | inputGate.afterInputHeld() | 2303 | inputGate.afterInputHeld() | ||
| 2363 | } | 2304 | } | ||
| 2364 | continue | 2305 | continue | ||
| 2365 | } | 2306 | } | ||
| 2366 | } | 2307 | } | ||
| 2367 | - | if !send(frame) { | 2308 | + | if !send(message) { |
| 2368 | return | 2309 | return | ||
| 2369 | } | 2310 | } | ||
| 2370 | case <-ctx.Done(): | 2311 | case <-ctx.Done(): | ||
| @@ -2726,7 +2667,7 @@ type stdinPump struct { | |||||
| 2726 | in io.Reader // compatibility for isolated scanner tests | 2667 | in io.Reader // compatibility for isolated scanner tests | ||
| 2727 | input *terminalInputPump | 2668 | input *terminalInputPump | ||
| 2728 | consumer uint64 | 2669 | consumer uint64 | ||
| 2729 | - | out chan<- wire.Frame | 2670 | + | out chan<- protocol.ClientMessage |
| 2730 | clock ports.Clock | 2671 | clock ports.Clock | ||
| 2731 | clipboard ports.ClipboardReader | 2672 | clipboard ports.ClipboardReader | ||
| 2732 | logger *slog.Logger | 2673 | logger *slog.Logger | ||
| @@ -2747,13 +2688,13 @@ func (p *stdinPump) run() { | |||||
| 2747 | var undeliveredMu sync.Mutex | 2688 | var undeliveredMu sync.Mutex | ||
| 2748 | var undelivered []byte | 2689 | var undelivered []byte | ||
| 2749 | sendOK.Store(true) | 2690 | sendOK.Store(true) | ||
| 2750 | - | send := func(frame wire.Frame) bool { | 2691 | + | send := func(message protocol.ClientMessage) bool { |
| 2751 | if !sendOK.Load() { | 2692 | if !sendOK.Load() { | ||
| 2752 | return false | 2693 | return false | ||
| 2753 | } | 2694 | } | ||
| 2754 | return p.sendLease.send(func() bool { | 2695 | return p.sendLease.send(func() bool { | ||
| 2755 | select { | 2696 | select { | ||
| 2756 | - | case p.out <- frame: | 2697 | + | case p.out <- message: |
| 2757 | if p.afterInputDelivered != nil { | 2698 | if p.afterInputDelivered != nil { | ||
| 2758 | p.afterInputDelivered() | 2699 | p.afterInputDelivered() | ||
| 2759 | } | 2700 | } | ||
| @@ -2786,7 +2727,7 @@ func (p *stdinPump) run() { | |||||
| 2786 | return | 2727 | return | ||
| 2787 | } | 2728 | } | ||
| 2788 | inputSeq++ | 2729 | inputSeq++ | ||
| 2789 | - | if !send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{InputSeq: inputSeq, Data: append([]byte(nil), data...)})}) { | 2730 | + | if !send(protocol.Input{InputSeq: inputSeq, Data: append([]byte(nil), data...)}) { |
| 2790 | undeliveredMu.Lock() | 2731 | undeliveredMu.Lock() | ||
| 2791 | undelivered = append(undelivered, data...) | 2732 | undelivered = append(undelivered, data...) | ||
| 2792 | undeliveredMu.Unlock() | 2733 | undeliveredMu.Unlock() | ||
| @@ -2800,11 +2741,11 @@ func (p *stdinPump) run() { | |||||
| 2800 | reader: p.clipboard, | 2741 | reader: p.clipboard, | ||
| 2801 | log: p.logger, | 2742 | log: p.logger, | ||
| 2802 | sendNotice: func(action uint8) { | 2743 | sendNotice: func(action uint8) { | ||
| 2803 | - | send(wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(protocol.ClientNotice{Action: action})}) | 2744 | + | send(protocol.ClientNotice{Action: action}) |
| 2804 | }, | 2745 | }, | ||
| 2805 | sendImage: func(mime string, data []byte) { | 2746 | sendImage: func(mime string, data []byte) { | ||
| 2806 | inputSeq++ | 2747 | inputSeq++ | ||
| 2807 | - | send(wire.Frame{Type: wire.MsgImagePush, Payload: wire.MarshalImagePush(protocol.ImagePush{InputSeq: inputSeq, Mime: mime, Data: data})}) | 2748 | + | send(protocol.ImagePush{InputSeq: inputSeq, Mime: mime, Data: data}) |
| 2808 | }, | 2749 | }, | ||
| 2809 | next: coalescer.Scan, | 2750 | next: coalescer.Scan, | ||
| 2810 | } | 2751 | } | ||
| @@ -2946,7 +2887,7 @@ func (p *stdinPump) run() { | |||||
| 2946 | disarmMarkerDeadline() | 2887 | disarmMarkerDeadline() | ||
| 2947 | markers.flush(sink) | 2888 | markers.flush(sink) | ||
| 2948 | select { | 2889 | select { | ||
| 2949 | - | case p.out <- wire.Frame{Type: wire.MsgDetach, Payload: wire.MarshalDetach(protocol.Detach{})}: | 2890 | + | case p.out <- protocol.Detach{}: |
| 2950 | case <-p.ctx.Done(): | 2891 | case <-p.ctx.Done(): | ||
| 2951 | } | 2892 | } | ||
| 2952 | p.cancel() | 2893 | p.cancel() | ||
| @@ -2967,7 +2908,7 @@ func (p *stdinPump) run() { | |||||
| 2967 | // runResize forwards coalesced terminal resize events to the daemon. It | 2908 | // runResize forwards coalesced terminal resize events to the daemon. It | ||
| 2968 | // tolerates an already-closed resize channel (which the terminal adapter | 2909 | // tolerates an already-closed resize channel (which the terminal adapter | ||
| 2969 | // hands back when restore ran before ResizeEvents was first called). | 2910 | // hands back when restore ran before ResizeEvents was first called). | ||
| 2970 | - | func runResize(ctx context.Context, events <-chan domain.Geometry, out chan<- wire.Frame, sendLease *foregroundSendLease, log *slog.Logger) { | 2911 | + | func runResize(ctx context.Context, events <-chan domain.Geometry, out chan<- protocol.ClientMessage, sendLease *foregroundSendLease, log *slog.Logger) { |
| 2971 | defer log.Debug("resize pump exited") | 2912 | defer log.Debug("resize pump exited") | ||
| 2972 | for { | 2913 | for { | ||
| 2973 | select { | 2914 | select { | ||
| @@ -2976,15 +2917,14 @@ func runResize(ctx context.Context, events <-chan domain.Geometry, out chan<- wi | |||||
| 2976 | return | 2917 | return | ||
| 2977 | } | 2918 | } | ||
| 2978 | geometry = geometry.NormalizePixels() | 2919 | geometry = geometry.NormalizePixels() | ||
| 2979 | - | payload, err := wire.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight}) | 2920 | + | message := protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight} |
| 2980 | - | if err != nil { | 2921 | + | if err := protocol.ValidateGeometry(message.Geometry()); err != nil { |
| 2981 | - | log.Error("encoding terminal resize", "error", err) | 2922 | + | log.Error("validating terminal resize", "error", err) |
| 2982 | continue | 2923 | continue | ||
| 2983 | } | 2924 | } | ||
| 2984 | - | frame := wire.Frame{Type: wire.MsgResize, Payload: payload} | |||
| 2985 | if !sendLease.send(func() bool { | 2925 | if !sendLease.send(func() bool { | ||
| 2986 | select { | 2926 | select { | ||
| 2987 | - | case out <- frame: | 2927 | + | case out <- message: |
| 2988 | return true | 2928 | return true | ||
| 2989 | case <-ctx.Done(): | 2929 | case <-ctx.Done(): | ||
| 2990 | return false | 2930 | return false | ||
M internal/usecase/client/client_test.go +38 -38
| @@ -130,7 +130,7 @@ func (markedDatagramTransport) DatagramTransport() {} | |||
| 130 | 130 | // scriptRecv wires a MockTransport's Recv to yield the given items in order, | |
| 131 | 131 | // then block forever (simulating a live but idle connection). It returns a | |
| 132 | 132 | // cleanup that unblocks the parked reader. | |
| 133 | - | func scriptRecv(tr *portsmocks.MockTransport, items ...recvItem) func() { | |
| 133 | + | func scriptRecv(tr *mockClientConnection, items ...recvItem) func() { | |
| 134 | 134 | ch := make(chan recvItem, len(items)) | |
| 135 | 135 | for _, it := range items { | |
| 136 | 136 | ch <- it | |
| @@ -242,15 +242,15 @@ func isType(typ wire.MsgType) any { | |||
| 242 | 242 | // transportDialer adapts one already-open test transport to the Runner API. | |
| 243 | 243 | type transportDialer struct{ transport ports.Transport } | |
| 244 | 244 | ||
| 245 | - | func (d transportDialer) Dial(context.Context) (ports.Transport, error) { | |
| 246 | - | return d.transport, nil | |
| 245 | + | func (d transportDialer) Dial(context.Context) (ports.ClientConnection, error) { | |
| 246 | + | return &rawClientConnection{raw: d.transport}, nil | |
| 247 | 247 | } | |
| 248 | 248 | ||
| 249 | 249 | func runTestClient(ctx context.Context, deps client.Dependencies, request client.AttachRequest) error { | |
| 250 | 250 | return client.NewRunner(deps).Run(ctx, request) | |
| 251 | 251 | } | |
| 252 | 252 | ||
| 253 | - | func testDependencies(dialer ports.Dialer, terminal ports.Terminal, clock ports.Clock, clipboard ports.ClipboardReader, observer ports.SerializedRuntimeObserver) client.Dependencies { | |
| 253 | + | func testDependencies(dialer ports.ClientDialer, terminal ports.Terminal, clock ports.Clock, clipboard ports.ClipboardReader, observer ports.SerializedRuntimeObserver) client.Dependencies { | |
| 254 | 254 | return client.Dependencies{ | |
| 255 | 255 | Dialer: dialer, | |
| 256 | 256 | Terminal: terminal, | |
| @@ -286,7 +286,7 @@ func newHandshakeClock(t *testing.T, timerCount int) (*portsmocks.MockClock, <-c | |||
| 286 | 286 | func TestRunBoundsBlockedDial(t *testing.T) { | |
| 287 | 287 | clk, createdTimers := newHandshakeClock(t, 1) | |
| 288 | 288 | started := make(chan struct{}) | |
| 289 | - | dialer := portsmocks.NewMockDialer(t) | |
| 289 | + | dialer := newMockClientDialer(t) | |
| 290 | 290 | dialer.EXPECT().Dial(mock.Anything).RunAndReturn(func(ctx context.Context) (ports.Transport, error) { | |
| 291 | 291 | close(started) | |
| 292 | 292 | <-ctx.Done() | |
| @@ -330,7 +330,7 @@ func TestRunBoundsPreWelcomeOperations(t *testing.T) { | |||
| 330 | 330 | closed := make(chan struct{}) | |
| 331 | 331 | var closeOnce sync.Once | |
| 332 | 332 | operationExited := make(chan struct{}) | |
| 333 | - | tr := portsmocks.NewMockTransport(t) | |
| 333 | + | tr := newMockClientConnection(t) | |
| 334 | 334 | tr.EXPECT().Close().Run(func() { closeOnce.Do(func() { close(closed) }) }).Return(nil).Maybe() | |
| 335 | 335 | ||
| 336 | 336 | block := func() error { | |
| @@ -349,7 +349,7 @@ func TestRunBoundsPreWelcomeOperations(t *testing.T) { | |||
| 349 | 349 | return wire.Frame{}, block() | |
| 350 | 350 | }).Once() | |
| 351 | 351 | } | |
| 352 | - | dialer := portsmocks.NewMockDialer(t) | |
| 352 | + | dialer := newMockClientDialer(t) | |
| 353 | 353 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | |
| 354 | 354 | ||
| 355 | 355 | errCh := make(chan error, 1) | |
| @@ -401,7 +401,7 @@ func TestAttachHelloIncludesTrueColor(t *testing.T) { | |||
| 401 | 401 | defer in.unblock() | |
| 402 | 402 | ||
| 403 | 403 | gotHello := make(chan protocol.Hello, 1) | |
| 404 | - | tr := portsmocks.NewMockTransport(t) | |
| 404 | + | tr := newMockClientConnection(t) | |
| 405 | 405 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 406 | 406 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 407 | 407 | tr.EXPECT().Send(isType(wire.MsgHello)).RunAndReturn(func(f wire.Frame) error { | |
| @@ -616,7 +616,7 @@ func TestAcceptedHomeActionUsesCapturedRouteAfterRequestMetadataRebase(t *testin | |||
| 616 | 616 | localDialer := &sequenceDialer{trs: []ports.Transport{local1, local2}} | |
| 617 | 617 | remoteDialer := &sequenceDialer{trs: []ports.Transport{remote}} | |
| 618 | 618 | deps := testDependencies(localDialer, term, realClock{}, nil, nil) | |
| 619 | - | deps.AttachHandoff = func(protocol.AttachTarget) (ports.Dialer, client.AttachRequest, error) { | |
| 619 | + | deps.AttachHandoff = func(protocol.AttachTarget) (ports.ClientDialer, client.AttachRequest, error) { | |
| 620 | 620 | target := protocol.ExactSessionTarget{LifecycleID: remoteLifecycle, SessionName: "work"} | |
| 621 | 621 | return remoteDialer, client.AttachRequest{ | |
| 622 | 622 | Intent: protocol.IntentAttach, SessionName: "work", Remote: true, | |
| @@ -650,9 +650,9 @@ func hybridLocalBootstrap(lifecycle domain.SessionLifecycleID, target domain.Rem | |||
| 650 | 650 | }} | |
| 651 | 651 | } | |
| 652 | 652 | ||
| 653 | - | func hybridPickerDependencies(localDialer ports.Dialer, term ports.Terminal, clock ports.Clock, remoteByEndpoint map[string]ports.Dialer) client.Dependencies { | |
| 653 | + | func hybridPickerDependencies(localDialer ports.ClientDialer, term ports.Terminal, clock ports.Clock, remoteByEndpoint map[string]ports.ClientDialer) client.Dependencies { | |
| 654 | 654 | deps := testDependencies(localDialer, term, clock, nil, nil) | |
| 655 | - | deps.AttachHandoff = func(target protocol.AttachTarget) (ports.Dialer, client.AttachRequest, error) { | |
| 655 | + | deps.AttachHandoff = func(target protocol.AttachTarget) (ports.ClientDialer, client.AttachRequest, error) { | |
| 656 | 656 | if target.RemoteTarget == nil { | |
| 657 | 657 | return nil, client.AttachRequest{}, errors.New("hybrid picker target is not remote") | |
| 658 | 658 | } | |
| @@ -743,7 +743,7 @@ func TestHybridPickerSameHostSwitchReusesRemoteTransport(t *testing.T) { | |||
| 743 | 743 | } | |
| 744 | 744 | localDialer := &sequenceDialer{trs: []ports.Transport{localInitial, localPicker}} | |
| 745 | 745 | remoteDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: remote}}} | |
| 746 | - | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.Dialer{"remote": remoteDialer}) | |
| 746 | + | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.ClientDialer{"remote": remoteDialer}) | |
| 747 | 747 | ||
| 748 | 748 | err := runTestClient(context.Background(), deps, client.AttachRequest{ | |
| 749 | 749 | Intent: protocol.IntentAttach, SessionName: "local", Origin: protocol.RouteOriginLocal, OriginKey: "local", | |
| @@ -826,7 +826,7 @@ func TestHybridPickerExpiredSwitchFallsBackToNewDial(t *testing.T) { | |||
| 826 | 826 | remoteDialer := &sequenceDialer{trs: []ports.Transport{ | |
| 827 | 827 | markedDatagramTransport{Transport: sourceRemote}, targetRemote, | |
| 828 | 828 | }} | |
| 829 | - | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.Dialer{"remote": remoteDialer}) | |
| 829 | + | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.ClientDialer{"remote": remoteDialer}) | |
| 830 | 830 | ||
| 831 | 831 | require.NoError(t, runTestClient(context.Background(), deps, client.AttachRequest{ | |
| 832 | 832 | Intent: protocol.IntentAttach, SessionName: "local", Origin: protocol.RouteOriginLocal, OriginKey: "local", | |
| @@ -890,7 +890,7 @@ func TestHybridPickerPrepareResponseTimeoutClosesRetainedTransport(t *testing.T) | |||
| 890 | 890 | ||
| 891 | 891 | localDialer := &sequenceDialer{trs: []ports.Transport{localInitial}} | |
| 892 | 892 | remoteDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: remote}}} | |
| 893 | - | deps := hybridPickerDependencies(localDialer, term, clock, map[string]ports.Dialer{"remote": remoteDialer}) | |
| 893 | + | deps := hybridPickerDependencies(localDialer, term, clock, map[string]ports.ClientDialer{"remote": remoteDialer}) | |
| 894 | 894 | ||
| 895 | 895 | ctx, cancel := context.WithCancel(context.Background()) | |
| 896 | 896 | defer cancel() | |
| @@ -967,7 +967,7 @@ func TestHybridPickerBackResumesRetainedRemoteTransport(t *testing.T) { | |||
| 967 | 967 | }} | |
| 968 | 968 | localDialer := &sequenceDialer{trs: []ports.Transport{localInitial, localPicker}} | |
| 969 | 969 | remoteDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: remote}}} | |
| 970 | - | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.Dialer{"remote": remoteDialer}) | |
| 970 | + | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.ClientDialer{"remote": remoteDialer}) | |
| 971 | 971 | ||
| 972 | 972 | require.NoError(t, runTestClient(context.Background(), deps, client.AttachRequest{ | |
| 973 | 973 | Intent: protocol.IntentAttach, SessionName: "local", Origin: protocol.RouteOriginLocal, OriginKey: "local", | |
| @@ -1023,7 +1023,7 @@ func TestHybridPickerDifferentHostFallsBackToNewRemoteDial(t *testing.T) { | |||
| 1023 | 1023 | localDialer := &sequenceDialer{trs: []ports.Transport{localInitial, localPicker}} | |
| 1024 | 1024 | sourceDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: sourceRemote}}} | |
| 1025 | 1025 | targetDialer := &sequenceDialer{trs: []ports.Transport{targetRemote}} | |
| 1026 | - | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.Dialer{ | |
| 1026 | + | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.ClientDialer{ | |
| 1027 | 1027 | sourceTarget.Endpoint: sourceDialer, | |
| 1028 | 1028 | targetTarget.Endpoint: targetDialer, | |
| 1029 | 1029 | }) | |
| @@ -1109,7 +1109,7 @@ func TestRouteNavigationPreservesRemoteHomePickerAcrossLocalReturn(t *testing.T) | |||
| 1109 | 1109 | remoteDialer := &sequenceDialer{trs: []ports.Transport{remote1, remote2, remote3}} | |
| 1110 | 1110 | ||
| 1111 | 1111 | deps := testDependencies(localDialer, term, realClock{}, nil, nil) | |
| 1112 | - | deps.AttachHandoff = func(target protocol.AttachTarget) (ports.Dialer, client.AttachRequest, error) { | |
| 1112 | + | deps.AttachHandoff = func(target protocol.AttachTarget) (ports.ClientDialer, client.AttachRequest, error) { | |
| 1113 | 1113 | require.Equal(t, remoteHandoff, target) | |
| 1114 | 1114 | return remoteDialer, client.AttachRequest{ | |
| 1115 | 1115 | Intent: protocol.IntentAttach, SessionName: target.Session, Remote: true, | |
| @@ -1170,7 +1170,7 @@ func TestAttachTargetHandoffReturnsValidatedTargetAndClosesTransport(t *testing. | |||
| 1170 | 1170 | defer in.unblock() | |
| 1171 | 1171 | ||
| 1172 | 1172 | target := protocol.AttachTarget{Endpoint: "remote.example", Session: "work", Intent: protocol.IntentAttach} | |
| 1173 | - | tr := portsmocks.NewMockTransport(t) | |
| 1173 | + | tr := newMockClientConnection(t) | |
| 1174 | 1174 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1175 | 1175 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| 1176 | 1176 | unblock := scriptRecv(tr, | |
| @@ -1197,7 +1197,7 @@ func TestAttachHelloIncludesCompleteLocalEnvironment(t *testing.T) { | |||
| 1197 | 1197 | defer in.unblock() | |
| 1198 | 1198 | ||
| 1199 | 1199 | gotHello := make(chan protocol.Hello, 1) | |
| 1200 | - | tr := portsmocks.NewMockTransport(t) | |
| 1200 | + | tr := newMockClientConnection(t) | |
| 1201 | 1201 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1202 | 1202 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1203 | 1203 | tr.EXPECT().Send(isType(wire.MsgHello)).RunAndReturn(func(f wire.Frame) error { | |
| @@ -1226,7 +1226,7 @@ func TestAttachHelloRequestsSingleOutputForDatagramTransport(t *testing.T) { | |||
| 1226 | 1226 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | |
| 1227 | 1227 | defer in.unblock() | |
| 1228 | 1228 | ||
| 1229 | - | tr := portsmocks.NewMockTransport(t) | |
| 1229 | + | tr := newMockClientConnection(t) | |
| 1230 | 1230 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1231 | 1231 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1232 | 1232 | tr.EXPECT().Send(isType(wire.MsgHello)).RunAndReturn(func(f wire.Frame) error { | |
| @@ -1252,7 +1252,7 @@ func TestAttachHappyPath(t *testing.T) { | |||
| 1252 | 1252 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | |
| 1253 | 1253 | defer in.unblock() | |
| 1254 | 1254 | ||
| 1255 | - | tr := portsmocks.NewMockTransport(t) | |
| 1255 | + | tr := newMockClientConnection(t) | |
| 1256 | 1256 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1257 | 1257 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1258 | 1258 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| @@ -1275,7 +1275,7 @@ func TestAttachVersionMismatch(t *testing.T) { | |||
| 1275 | 1275 | tm.EXPECT().Geometry().Return(domain.Geometry{Size: domain.Size{Cols: 80, Rows: 24}}, nil).Once() | |
| 1276 | 1276 | // EnterRaw must NOT be called on the error-before-welcome path. | |
| 1277 | 1277 | ||
| 1278 | - | tr := portsmocks.NewMockTransport(t) | |
| 1278 | + | tr := newMockClientConnection(t) | |
| 1279 | 1279 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| 1280 | 1280 | tr.EXPECT().Recv().Return( | |
| 1281 | 1281 | frameOf(wire.MsgError, wire.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "version mismatch"})), | |
| @@ -1298,7 +1298,7 @@ func TestAttachRestoredOnRecvErrorMidStream(t *testing.T) { | |||
| 1298 | 1298 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | |
| 1299 | 1299 | defer in.unblock() | |
| 1300 | 1300 | ||
| 1301 | - | tr := portsmocks.NewMockTransport(t) | |
| 1301 | + | tr := newMockClientConnection(t) | |
| 1302 | 1302 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1303 | 1303 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1304 | 1304 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| @@ -1322,7 +1322,7 @@ func TestAttachDaemonVanishedOnEOF(t *testing.T) { | |||
| 1322 | 1322 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | |
| 1323 | 1323 | defer in.unblock() | |
| 1324 | 1324 | ||
| 1325 | - | tr := portsmocks.NewMockTransport(t) | |
| 1325 | + | tr := newMockClientConnection(t) | |
| 1326 | 1326 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1327 | 1327 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1328 | 1328 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| @@ -1356,7 +1356,7 @@ func TestAttachStdinForwardsSGRMouseReportAsSingleFrame(t *testing.T) { | |||
| 1356 | 1356 | ||
| 1357 | 1357 | gotInput := make(chan []byte, 2) | |
| 1358 | 1358 | allowDetach := make(chan struct{}) | |
| 1359 | - | tr := portsmocks.NewMockTransport(t) | |
| 1359 | + | tr := newMockClientConnection(t) | |
| 1360 | 1360 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1361 | 1361 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1362 | 1362 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| @@ -1418,7 +1418,7 @@ func TestAttachStdinCoalescesSplitBracketedPaste(t *testing.T) { | |||
| 1418 | 1418 | ||
| 1419 | 1419 | gotInput := make(chan []byte, 1) | |
| 1420 | 1420 | allowDetach := make(chan struct{}) | |
| 1421 | - | tr := portsmocks.NewMockTransport(t) | |
| 1421 | + | tr := newMockClientConnection(t) | |
| 1422 | 1422 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1423 | 1423 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1424 | 1424 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| @@ -1473,7 +1473,7 @@ func TestAttachForwardsResize(t *testing.T) { | |||
| 1473 | 1473 | firstRecv := make(chan struct{}) | |
| 1474 | 1474 | var firstRecvOnce sync.Once | |
| 1475 | 1475 | ||
| 1476 | - | tr := portsmocks.NewMockTransport(t) | |
| 1476 | + | tr := newMockClientConnection(t) | |
| 1477 | 1477 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1478 | 1478 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1479 | 1479 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| @@ -1534,7 +1534,7 @@ func TestRunRestoresRawModeAfterAttachError(t *testing.T) { | |||
| 1534 | 1534 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | |
| 1535 | 1535 | defer in.unblock() | |
| 1536 | 1536 | ||
| 1537 | - | tr := portsmocks.NewMockTransport(t) | |
| 1537 | + | tr := newMockClientConnection(t) | |
| 1538 | 1538 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1539 | 1539 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1540 | 1540 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| @@ -1545,7 +1545,7 @@ func TestRunRestoresRawModeAfterAttachError(t *testing.T) { | |||
| 1545 | 1545 | ) | |
| 1546 | 1546 | defer unblock() | |
| 1547 | 1547 | tr.EXPECT().Close().Return(nil).Once() | |
| 1548 | - | d := portsmocks.NewMockDialer(t) | |
| 1548 | + | d := newMockClientDialer(t) | |
| 1549 | 1549 | d.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | |
| 1550 | 1550 | ||
| 1551 | 1551 | err := runTestClient(context.Background(), testDependencies(d, tm, realClock{}, nil, nil), client.AttachRequest{Intent: protocol.IntentEphemeral, SessionName: "", Remote: false}) | |
| @@ -1558,14 +1558,14 @@ func TestRunDoesNotEnterRawBeforePreWelcomeError(t *testing.T) { | |||
| 1558 | 1558 | tm.EXPECT().Geometry().Return(domain.Geometry{Size: domain.Size{Cols: 80, Rows: 24}}, nil).Once() | |
| 1559 | 1559 | // EnterRaw must NOT be called when the daemon rejects Hello before Welcome. | |
| 1560 | 1560 | ||
| 1561 | - | tr := portsmocks.NewMockTransport(t) | |
| 1561 | + | tr := newMockClientConnection(t) | |
| 1562 | 1562 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| 1563 | 1563 | tr.EXPECT().Recv().Return( | |
| 1564 | 1564 | frameOf(wire.MsgError, wire.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "version mismatch"})), | |
| 1565 | 1565 | nil, | |
| 1566 | 1566 | ).Once() | |
| 1567 | 1567 | tr.EXPECT().Close().Return(nil).Once() | |
| 1568 | - | d := portsmocks.NewMockDialer(t) | |
| 1568 | + | d := newMockClientDialer(t) | |
| 1569 | 1569 | d.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | |
| 1570 | 1570 | ||
| 1571 | 1571 | err := runTestClient(context.Background(), testDependencies(d, tm, realClock{}, nil, nil), client.AttachRequest{Intent: protocol.IntentAttach, SessionName: "main", Remote: false}) | |
| @@ -1577,7 +1577,7 @@ func TestRunDoesNotEnterRawBeforePreWelcomeError(t *testing.T) { | |||
| 1577 | 1577 | ||
| 1578 | 1578 | func TestRunPhaseASingleAttempt(t *testing.T) { | |
| 1579 | 1579 | dialErr := errors.New("dial failed") | |
| 1580 | - | d := portsmocks.NewMockDialer(t) | |
| 1580 | + | d := newMockClientDialer(t) | |
| 1581 | 1581 | d.EXPECT().Dial(mock.Anything).Return(nil, dialErr).Once() | |
| 1582 | 1582 | tm := portsmocks.NewMockTerminal(t) | |
| 1583 | 1583 | ||
| @@ -1591,7 +1591,7 @@ type sequenceDialer struct { | |||
| 1591 | 1591 | calls atomic.Int32 | |
| 1592 | 1592 | } | |
| 1593 | 1593 | ||
| 1594 | - | func (d *sequenceDialer) Dial(context.Context) (ports.Transport, error) { | |
| 1594 | + | func (d *sequenceDialer) Dial(context.Context) (ports.ClientConnection, error) { | |
| 1595 | 1595 | i := int(d.calls.Add(1)) - 1 | |
| 1596 | 1596 | if i < len(d.errs) && d.errs[i] != nil { | |
| 1597 | 1597 | return nil, d.errs[i] | |
| @@ -1599,7 +1599,7 @@ func (d *sequenceDialer) Dial(context.Context) (ports.Transport, error) { | |||
| 1599 | 1599 | if i >= len(d.trs) { | |
| 1600 | 1600 | return nil, io.EOF | |
| 1601 | 1601 | } | |
| 1602 | - | return d.trs[i], nil | |
| 1602 | + | return &rawClientConnection{raw: d.trs[i]}, nil | |
| 1603 | 1603 | } | |
| 1604 | 1604 | ||
| 1605 | 1605 | type recordingTransport struct { | |
| @@ -1939,7 +1939,7 @@ func TestAttachRememberRemoteHost(t *testing.T) { | |||
| 1939 | 1939 | return nil | |
| 1940 | 1940 | } | |
| 1941 | 1941 | ||
| 1942 | - | tr := portsmocks.NewMockTransport(t) | |
| 1942 | + | tr := newMockClientConnection(t) | |
| 1943 | 1943 | if !tt.preWelcomeReject { | |
| 1944 | 1944 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 1945 | 1945 | } | |
| @@ -2033,7 +2033,7 @@ func TestAttachRememberRemoteHostDoesNotBlockAfterWelcome(t *testing.T) { | |||
| 2033 | 2033 | releaseLearner := make(chan struct{}) | |
| 2034 | 2034 | learnerDone := make(chan struct{}) | |
| 2035 | 2035 | ||
| 2036 | - | tr := portsmocks.NewMockTransport(t) | |
| 2036 | + | tr := newMockClientConnection(t) | |
| 2037 | 2037 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 2038 | 2038 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| 2039 | 2039 | unblock := scriptRecv(tr, | |
| @@ -2114,7 +2114,7 @@ func TestRunRestoresTerminalBeforeWaitingForLearner(t *testing.T) { | |||
| 2114 | 2114 | releaseLearner := make(chan struct{}) | |
| 2115 | 2115 | learnerDone := make(chan struct{}) | |
| 2116 | 2116 | ||
| 2117 | - | tr := portsmocks.NewMockTransport(t) | |
| 2117 | + | tr := newMockClientConnection(t) | |
| 2118 | 2118 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 2119 | 2119 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| 2120 | 2120 | unblock := scriptRecv(tr, | |
| @@ -2211,7 +2211,7 @@ func TestRunReturnsWhenRemoteHostLearnerStalls(t *testing.T) { | |||
| 2211 | 2211 | return realClock{}.NewTimer(d) | |
| 2212 | 2212 | }).Maybe() | |
| 2213 | 2213 | ||
| 2214 | - | tr := portsmocks.NewMockTransport(t) | |
| 2214 | + | tr := newMockClientConnection(t) | |
| 2215 | 2215 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 2216 | 2216 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| 2217 | 2217 | unblock := scriptRecv(tr, | |
| @@ -130,7 +130,7 @@ func (markedDatagramTransport) DatagramTransport() {} | |||||
| 130 | // scriptRecv wires a MockTransport's Recv to yield the given items in order, | 130 | // scriptRecv wires a MockTransport's Recv to yield the given items in order, | ||
| 131 | // then block forever (simulating a live but idle connection). It returns a | 131 | // then block forever (simulating a live but idle connection). It returns a | ||
| 132 | // cleanup that unblocks the parked reader. | 132 | // cleanup that unblocks the parked reader. | ||
| 133 | - | func scriptRecv(tr *portsmocks.MockTransport, items ...recvItem) func() { | 133 | + | func scriptRecv(tr *mockClientConnection, items ...recvItem) func() { |
| 134 | ch := make(chan recvItem, len(items)) | 134 | ch := make(chan recvItem, len(items)) | ||
| 135 | for _, it := range items { | 135 | for _, it := range items { | ||
| 136 | ch <- it | 136 | ch <- it | ||
| @@ -242,15 +242,15 @@ func isType(typ wire.MsgType) any { | |||||
| 242 | // transportDialer adapts one already-open test transport to the Runner API. | 242 | // transportDialer adapts one already-open test transport to the Runner API. | ||
| 243 | type transportDialer struct{ transport ports.Transport } | 243 | type transportDialer struct{ transport ports.Transport } | ||
| 244 | 244 | ||||
| 245 | - | func (d transportDialer) Dial(context.Context) (ports.Transport, error) { | 245 | + | func (d transportDialer) Dial(context.Context) (ports.ClientConnection, error) { |
| 246 | - | return d.transport, nil | 246 | + | return &rawClientConnection{raw: d.transport}, nil |
| 247 | } | 247 | } | ||
| 248 | 248 | ||||
| 249 | func runTestClient(ctx context.Context, deps client.Dependencies, request client.AttachRequest) error { | 249 | func runTestClient(ctx context.Context, deps client.Dependencies, request client.AttachRequest) error { | ||
| 250 | return client.NewRunner(deps).Run(ctx, request) | 250 | return client.NewRunner(deps).Run(ctx, request) | ||
| 251 | } | 251 | } | ||
| 252 | 252 | ||||
| 253 | - | func testDependencies(dialer ports.Dialer, terminal ports.Terminal, clock ports.Clock, clipboard ports.ClipboardReader, observer ports.SerializedRuntimeObserver) client.Dependencies { | 253 | + | func testDependencies(dialer ports.ClientDialer, terminal ports.Terminal, clock ports.Clock, clipboard ports.ClipboardReader, observer ports.SerializedRuntimeObserver) client.Dependencies { |
| 254 | return client.Dependencies{ | 254 | return client.Dependencies{ | ||
| 255 | Dialer: dialer, | 255 | Dialer: dialer, | ||
| 256 | Terminal: terminal, | 256 | Terminal: terminal, | ||
| @@ -286,7 +286,7 @@ func newHandshakeClock(t *testing.T, timerCount int) (*portsmocks.MockClock, <-c | |||||
| 286 | func TestRunBoundsBlockedDial(t *testing.T) { | 286 | func TestRunBoundsBlockedDial(t *testing.T) { | ||
| 287 | clk, createdTimers := newHandshakeClock(t, 1) | 287 | clk, createdTimers := newHandshakeClock(t, 1) | ||
| 288 | started := make(chan struct{}) | 288 | started := make(chan struct{}) | ||
| 289 | - | dialer := portsmocks.NewMockDialer(t) | 289 | + | dialer := newMockClientDialer(t) |
| 290 | dialer.EXPECT().Dial(mock.Anything).RunAndReturn(func(ctx context.Context) (ports.Transport, error) { | 290 | dialer.EXPECT().Dial(mock.Anything).RunAndReturn(func(ctx context.Context) (ports.Transport, error) { | ||
| 291 | close(started) | 291 | close(started) | ||
| 292 | <-ctx.Done() | 292 | <-ctx.Done() | ||
| @@ -330,7 +330,7 @@ func TestRunBoundsPreWelcomeOperations(t *testing.T) { | |||||
| 330 | closed := make(chan struct{}) | 330 | closed := make(chan struct{}) | ||
| 331 | var closeOnce sync.Once | 331 | var closeOnce sync.Once | ||
| 332 | operationExited := make(chan struct{}) | 332 | operationExited := make(chan struct{}) | ||
| 333 | - | tr := portsmocks.NewMockTransport(t) | 333 | + | tr := newMockClientConnection(t) |
| 334 | tr.EXPECT().Close().Run(func() { closeOnce.Do(func() { close(closed) }) }).Return(nil).Maybe() | 334 | tr.EXPECT().Close().Run(func() { closeOnce.Do(func() { close(closed) }) }).Return(nil).Maybe() | ||
| 335 | 335 | ||||
| 336 | block := func() error { | 336 | block := func() error { | ||
| @@ -349,7 +349,7 @@ func TestRunBoundsPreWelcomeOperations(t *testing.T) { | |||||
| 349 | return wire.Frame{}, block() | 349 | return wire.Frame{}, block() | ||
| 350 | }).Once() | 350 | }).Once() | ||
| 351 | } | 351 | } | ||
| 352 | - | dialer := portsmocks.NewMockDialer(t) | 352 | + | dialer := newMockClientDialer(t) |
| 353 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | 353 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | ||
| 354 | 354 | ||||
| 355 | errCh := make(chan error, 1) | 355 | errCh := make(chan error, 1) | ||
| @@ -401,7 +401,7 @@ func TestAttachHelloIncludesTrueColor(t *testing.T) { | |||||
| 401 | defer in.unblock() | 401 | defer in.unblock() | ||
| 402 | 402 | ||||
| 403 | gotHello := make(chan protocol.Hello, 1) | 403 | gotHello := make(chan protocol.Hello, 1) | ||
| 404 | - | tr := portsmocks.NewMockTransport(t) | 404 | + | tr := newMockClientConnection(t) |
| 405 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 405 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 406 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 406 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 407 | tr.EXPECT().Send(isType(wire.MsgHello)).RunAndReturn(func(f wire.Frame) error { | 407 | tr.EXPECT().Send(isType(wire.MsgHello)).RunAndReturn(func(f wire.Frame) error { | ||
| @@ -616,7 +616,7 @@ func TestAcceptedHomeActionUsesCapturedRouteAfterRequestMetadataRebase(t *testin | |||||
| 616 | localDialer := &sequenceDialer{trs: []ports.Transport{local1, local2}} | 616 | localDialer := &sequenceDialer{trs: []ports.Transport{local1, local2}} | ||
| 617 | remoteDialer := &sequenceDialer{trs: []ports.Transport{remote}} | 617 | remoteDialer := &sequenceDialer{trs: []ports.Transport{remote}} | ||
| 618 | deps := testDependencies(localDialer, term, realClock{}, nil, nil) | 618 | deps := testDependencies(localDialer, term, realClock{}, nil, nil) | ||
| 619 | - | deps.AttachHandoff = func(protocol.AttachTarget) (ports.Dialer, client.AttachRequest, error) { | 619 | + | deps.AttachHandoff = func(protocol.AttachTarget) (ports.ClientDialer, client.AttachRequest, error) { |
| 620 | target := protocol.ExactSessionTarget{LifecycleID: remoteLifecycle, SessionName: "work"} | 620 | target := protocol.ExactSessionTarget{LifecycleID: remoteLifecycle, SessionName: "work"} | ||
| 621 | return remoteDialer, client.AttachRequest{ | 621 | return remoteDialer, client.AttachRequest{ | ||
| 622 | Intent: protocol.IntentAttach, SessionName: "work", Remote: true, | 622 | Intent: protocol.IntentAttach, SessionName: "work", Remote: true, | ||
| @@ -650,9 +650,9 @@ func hybridLocalBootstrap(lifecycle domain.SessionLifecycleID, target domain.Rem | |||||
| 650 | }} | 650 | }} | ||
| 651 | } | 651 | } | ||
| 652 | 652 | ||||
| 653 | - | func hybridPickerDependencies(localDialer ports.Dialer, term ports.Terminal, clock ports.Clock, remoteByEndpoint map[string]ports.Dialer) client.Dependencies { | 653 | + | func hybridPickerDependencies(localDialer ports.ClientDialer, term ports.Terminal, clock ports.Clock, remoteByEndpoint map[string]ports.ClientDialer) client.Dependencies { |
| 654 | deps := testDependencies(localDialer, term, clock, nil, nil) | 654 | deps := testDependencies(localDialer, term, clock, nil, nil) | ||
| 655 | - | deps.AttachHandoff = func(target protocol.AttachTarget) (ports.Dialer, client.AttachRequest, error) { | 655 | + | deps.AttachHandoff = func(target protocol.AttachTarget) (ports.ClientDialer, client.AttachRequest, error) { |
| 656 | if target.RemoteTarget == nil { | 656 | if target.RemoteTarget == nil { | ||
| 657 | return nil, client.AttachRequest{}, errors.New("hybrid picker target is not remote") | 657 | return nil, client.AttachRequest{}, errors.New("hybrid picker target is not remote") | ||
| 658 | } | 658 | } | ||
| @@ -743,7 +743,7 @@ func TestHybridPickerSameHostSwitchReusesRemoteTransport(t *testing.T) { | |||||
| 743 | } | 743 | } | ||
| 744 | localDialer := &sequenceDialer{trs: []ports.Transport{localInitial, localPicker}} | 744 | localDialer := &sequenceDialer{trs: []ports.Transport{localInitial, localPicker}} | ||
| 745 | remoteDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: remote}}} | 745 | remoteDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: remote}}} | ||
| 746 | - | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.Dialer{"remote": remoteDialer}) | 746 | + | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.ClientDialer{"remote": remoteDialer}) |
| 747 | 747 | ||||
| 748 | err := runTestClient(context.Background(), deps, client.AttachRequest{ | 748 | err := runTestClient(context.Background(), deps, client.AttachRequest{ | ||
| 749 | Intent: protocol.IntentAttach, SessionName: "local", Origin: protocol.RouteOriginLocal, OriginKey: "local", | 749 | Intent: protocol.IntentAttach, SessionName: "local", Origin: protocol.RouteOriginLocal, OriginKey: "local", | ||
| @@ -826,7 +826,7 @@ func TestHybridPickerExpiredSwitchFallsBackToNewDial(t *testing.T) { | |||||
| 826 | remoteDialer := &sequenceDialer{trs: []ports.Transport{ | 826 | remoteDialer := &sequenceDialer{trs: []ports.Transport{ | ||
| 827 | markedDatagramTransport{Transport: sourceRemote}, targetRemote, | 827 | markedDatagramTransport{Transport: sourceRemote}, targetRemote, | ||
| 828 | }} | 828 | }} | ||
| 829 | - | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.Dialer{"remote": remoteDialer}) | 829 | + | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.ClientDialer{"remote": remoteDialer}) |
| 830 | 830 | ||||
| 831 | require.NoError(t, runTestClient(context.Background(), deps, client.AttachRequest{ | 831 | require.NoError(t, runTestClient(context.Background(), deps, client.AttachRequest{ | ||
| 832 | Intent: protocol.IntentAttach, SessionName: "local", Origin: protocol.RouteOriginLocal, OriginKey: "local", | 832 | Intent: protocol.IntentAttach, SessionName: "local", Origin: protocol.RouteOriginLocal, OriginKey: "local", | ||
| @@ -890,7 +890,7 @@ func TestHybridPickerPrepareResponseTimeoutClosesRetainedTransport(t *testing.T) | |||||
| 890 | 890 | ||||
| 891 | localDialer := &sequenceDialer{trs: []ports.Transport{localInitial}} | 891 | localDialer := &sequenceDialer{trs: []ports.Transport{localInitial}} | ||
| 892 | remoteDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: remote}}} | 892 | remoteDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: remote}}} | ||
| 893 | - | deps := hybridPickerDependencies(localDialer, term, clock, map[string]ports.Dialer{"remote": remoteDialer}) | 893 | + | deps := hybridPickerDependencies(localDialer, term, clock, map[string]ports.ClientDialer{"remote": remoteDialer}) |
| 894 | 894 | ||||
| 895 | ctx, cancel := context.WithCancel(context.Background()) | 895 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 896 | defer cancel() | 896 | defer cancel() | ||
| @@ -967,7 +967,7 @@ func TestHybridPickerBackResumesRetainedRemoteTransport(t *testing.T) { | |||||
| 967 | }} | 967 | }} | ||
| 968 | localDialer := &sequenceDialer{trs: []ports.Transport{localInitial, localPicker}} | 968 | localDialer := &sequenceDialer{trs: []ports.Transport{localInitial, localPicker}} | ||
| 969 | remoteDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: remote}}} | 969 | remoteDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: remote}}} | ||
| 970 | - | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.Dialer{"remote": remoteDialer}) | 970 | + | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.ClientDialer{"remote": remoteDialer}) |
| 971 | 971 | ||||
| 972 | require.NoError(t, runTestClient(context.Background(), deps, client.AttachRequest{ | 972 | require.NoError(t, runTestClient(context.Background(), deps, client.AttachRequest{ | ||
| 973 | Intent: protocol.IntentAttach, SessionName: "local", Origin: protocol.RouteOriginLocal, OriginKey: "local", | 973 | Intent: protocol.IntentAttach, SessionName: "local", Origin: protocol.RouteOriginLocal, OriginKey: "local", | ||
| @@ -1023,7 +1023,7 @@ func TestHybridPickerDifferentHostFallsBackToNewRemoteDial(t *testing.T) { | |||||
| 1023 | localDialer := &sequenceDialer{trs: []ports.Transport{localInitial, localPicker}} | 1023 | localDialer := &sequenceDialer{trs: []ports.Transport{localInitial, localPicker}} | ||
| 1024 | sourceDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: sourceRemote}}} | 1024 | sourceDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: sourceRemote}}} | ||
| 1025 | targetDialer := &sequenceDialer{trs: []ports.Transport{targetRemote}} | 1025 | targetDialer := &sequenceDialer{trs: []ports.Transport{targetRemote}} | ||
| 1026 | - | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.Dialer{ | 1026 | + | deps := hybridPickerDependencies(localDialer, term, realClock{}, map[string]ports.ClientDialer{ |
| 1027 | sourceTarget.Endpoint: sourceDialer, | 1027 | sourceTarget.Endpoint: sourceDialer, | ||
| 1028 | targetTarget.Endpoint: targetDialer, | 1028 | targetTarget.Endpoint: targetDialer, | ||
| 1029 | }) | 1029 | }) | ||
| @@ -1109,7 +1109,7 @@ func TestRouteNavigationPreservesRemoteHomePickerAcrossLocalReturn(t *testing.T) | |||||
| 1109 | remoteDialer := &sequenceDialer{trs: []ports.Transport{remote1, remote2, remote3}} | 1109 | remoteDialer := &sequenceDialer{trs: []ports.Transport{remote1, remote2, remote3}} | ||
| 1110 | 1110 | ||||
| 1111 | deps := testDependencies(localDialer, term, realClock{}, nil, nil) | 1111 | deps := testDependencies(localDialer, term, realClock{}, nil, nil) | ||
| 1112 | - | deps.AttachHandoff = func(target protocol.AttachTarget) (ports.Dialer, client.AttachRequest, error) { | 1112 | + | deps.AttachHandoff = func(target protocol.AttachTarget) (ports.ClientDialer, client.AttachRequest, error) { |
| 1113 | require.Equal(t, remoteHandoff, target) | 1113 | require.Equal(t, remoteHandoff, target) | ||
| 1114 | return remoteDialer, client.AttachRequest{ | 1114 | return remoteDialer, client.AttachRequest{ | ||
| 1115 | Intent: protocol.IntentAttach, SessionName: target.Session, Remote: true, | 1115 | Intent: protocol.IntentAttach, SessionName: target.Session, Remote: true, | ||
| @@ -1170,7 +1170,7 @@ func TestAttachTargetHandoffReturnsValidatedTargetAndClosesTransport(t *testing. | |||||
| 1170 | defer in.unblock() | 1170 | defer in.unblock() | ||
| 1171 | 1171 | ||||
| 1172 | target := protocol.AttachTarget{Endpoint: "remote.example", Session: "work", Intent: protocol.IntentAttach} | 1172 | target := protocol.AttachTarget{Endpoint: "remote.example", Session: "work", Intent: protocol.IntentAttach} | ||
| 1173 | - | tr := portsmocks.NewMockTransport(t) | 1173 | + | tr := newMockClientConnection(t) |
| 1174 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1174 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1175 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 1175 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| 1176 | unblock := scriptRecv(tr, | 1176 | unblock := scriptRecv(tr, | ||
| @@ -1197,7 +1197,7 @@ func TestAttachHelloIncludesCompleteLocalEnvironment(t *testing.T) { | |||||
| 1197 | defer in.unblock() | 1197 | defer in.unblock() | ||
| 1198 | 1198 | ||||
| 1199 | gotHello := make(chan protocol.Hello, 1) | 1199 | gotHello := make(chan protocol.Hello, 1) | ||
| 1200 | - | tr := portsmocks.NewMockTransport(t) | 1200 | + | tr := newMockClientConnection(t) |
| 1201 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1201 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1202 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1202 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1203 | tr.EXPECT().Send(isType(wire.MsgHello)).RunAndReturn(func(f wire.Frame) error { | 1203 | tr.EXPECT().Send(isType(wire.MsgHello)).RunAndReturn(func(f wire.Frame) error { | ||
| @@ -1226,7 +1226,7 @@ func TestAttachHelloRequestsSingleOutputForDatagramTransport(t *testing.T) { | |||||
| 1226 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | 1226 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | ||
| 1227 | defer in.unblock() | 1227 | defer in.unblock() | ||
| 1228 | 1228 | ||||
| 1229 | - | tr := portsmocks.NewMockTransport(t) | 1229 | + | tr := newMockClientConnection(t) |
| 1230 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1230 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1231 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1231 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1232 | tr.EXPECT().Send(isType(wire.MsgHello)).RunAndReturn(func(f wire.Frame) error { | 1232 | tr.EXPECT().Send(isType(wire.MsgHello)).RunAndReturn(func(f wire.Frame) error { | ||
| @@ -1252,7 +1252,7 @@ func TestAttachHappyPath(t *testing.T) { | |||||
| 1252 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | 1252 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | ||
| 1253 | defer in.unblock() | 1253 | defer in.unblock() | ||
| 1254 | 1254 | ||||
| 1255 | - | tr := portsmocks.NewMockTransport(t) | 1255 | + | tr := newMockClientConnection(t) |
| 1256 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1256 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1257 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1257 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1258 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 1258 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| @@ -1275,7 +1275,7 @@ func TestAttachVersionMismatch(t *testing.T) { | |||||
| 1275 | tm.EXPECT().Geometry().Return(domain.Geometry{Size: domain.Size{Cols: 80, Rows: 24}}, nil).Once() | 1275 | tm.EXPECT().Geometry().Return(domain.Geometry{Size: domain.Size{Cols: 80, Rows: 24}}, nil).Once() | ||
| 1276 | // EnterRaw must NOT be called on the error-before-welcome path. | 1276 | // EnterRaw must NOT be called on the error-before-welcome path. | ||
| 1277 | 1277 | ||||
| 1278 | - | tr := portsmocks.NewMockTransport(t) | 1278 | + | tr := newMockClientConnection(t) |
| 1279 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 1279 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| 1280 | tr.EXPECT().Recv().Return( | 1280 | tr.EXPECT().Recv().Return( | ||
| 1281 | frameOf(wire.MsgError, wire.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "version mismatch"})), | 1281 | frameOf(wire.MsgError, wire.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "version mismatch"})), | ||
| @@ -1298,7 +1298,7 @@ func TestAttachRestoredOnRecvErrorMidStream(t *testing.T) { | |||||
| 1298 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | 1298 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | ||
| 1299 | defer in.unblock() | 1299 | defer in.unblock() | ||
| 1300 | 1300 | ||||
| 1301 | - | tr := portsmocks.NewMockTransport(t) | 1301 | + | tr := newMockClientConnection(t) |
| 1302 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1302 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1303 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1303 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1304 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 1304 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| @@ -1322,7 +1322,7 @@ func TestAttachDaemonVanishedOnEOF(t *testing.T) { | |||||
| 1322 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | 1322 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | ||
| 1323 | defer in.unblock() | 1323 | defer in.unblock() | ||
| 1324 | 1324 | ||||
| 1325 | - | tr := portsmocks.NewMockTransport(t) | 1325 | + | tr := newMockClientConnection(t) |
| 1326 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1326 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1327 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1327 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1328 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 1328 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| @@ -1356,7 +1356,7 @@ func TestAttachStdinForwardsSGRMouseReportAsSingleFrame(t *testing.T) { | |||||
| 1356 | 1356 | ||||
| 1357 | gotInput := make(chan []byte, 2) | 1357 | gotInput := make(chan []byte, 2) | ||
| 1358 | allowDetach := make(chan struct{}) | 1358 | allowDetach := make(chan struct{}) | ||
| 1359 | - | tr := portsmocks.NewMockTransport(t) | 1359 | + | tr := newMockClientConnection(t) |
| 1360 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1360 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1361 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1361 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1362 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 1362 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| @@ -1418,7 +1418,7 @@ func TestAttachStdinCoalescesSplitBracketedPaste(t *testing.T) { | |||||
| 1418 | 1418 | ||||
| 1419 | gotInput := make(chan []byte, 1) | 1419 | gotInput := make(chan []byte, 1) | ||
| 1420 | allowDetach := make(chan struct{}) | 1420 | allowDetach := make(chan struct{}) | ||
| 1421 | - | tr := portsmocks.NewMockTransport(t) | 1421 | + | tr := newMockClientConnection(t) |
| 1422 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1422 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1423 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1423 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1424 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 1424 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| @@ -1473,7 +1473,7 @@ func TestAttachForwardsResize(t *testing.T) { | |||||
| 1473 | firstRecv := make(chan struct{}) | 1473 | firstRecv := make(chan struct{}) | ||
| 1474 | var firstRecvOnce sync.Once | 1474 | var firstRecvOnce sync.Once | ||
| 1475 | 1475 | ||||
| 1476 | - | tr := portsmocks.NewMockTransport(t) | 1476 | + | tr := newMockClientConnection(t) |
| 1477 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1477 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1478 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1478 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1479 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 1479 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| @@ -1534,7 +1534,7 @@ func TestRunRestoresRawModeAfterAttachError(t *testing.T) { | |||||
| 1534 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | 1534 | tm, in := newHappyTerminal(t, &out, &restoreCount, resizeCh) | ||
| 1535 | defer in.unblock() | 1535 | defer in.unblock() | ||
| 1536 | 1536 | ||||
| 1537 | - | tr := portsmocks.NewMockTransport(t) | 1537 | + | tr := newMockClientConnection(t) |
| 1538 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1538 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1539 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1539 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1540 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 1540 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| @@ -1545,7 +1545,7 @@ func TestRunRestoresRawModeAfterAttachError(t *testing.T) { | |||||
| 1545 | ) | 1545 | ) | ||
| 1546 | defer unblock() | 1546 | defer unblock() | ||
| 1547 | tr.EXPECT().Close().Return(nil).Once() | 1547 | tr.EXPECT().Close().Return(nil).Once() | ||
| 1548 | - | d := portsmocks.NewMockDialer(t) | 1548 | + | d := newMockClientDialer(t) |
| 1549 | d.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | 1549 | d.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | ||
| 1550 | 1550 | ||||
| 1551 | err := runTestClient(context.Background(), testDependencies(d, tm, realClock{}, nil, nil), client.AttachRequest{Intent: protocol.IntentEphemeral, SessionName: "", Remote: false}) | 1551 | err := runTestClient(context.Background(), testDependencies(d, tm, realClock{}, nil, nil), client.AttachRequest{Intent: protocol.IntentEphemeral, SessionName: "", Remote: false}) | ||
| @@ -1558,14 +1558,14 @@ func TestRunDoesNotEnterRawBeforePreWelcomeError(t *testing.T) { | |||||
| 1558 | tm.EXPECT().Geometry().Return(domain.Geometry{Size: domain.Size{Cols: 80, Rows: 24}}, nil).Once() | 1558 | tm.EXPECT().Geometry().Return(domain.Geometry{Size: domain.Size{Cols: 80, Rows: 24}}, nil).Once() | ||
| 1559 | // EnterRaw must NOT be called when the daemon rejects Hello before Welcome. | 1559 | // EnterRaw must NOT be called when the daemon rejects Hello before Welcome. | ||
| 1560 | 1560 | ||||
| 1561 | - | tr := portsmocks.NewMockTransport(t) | 1561 | + | tr := newMockClientConnection(t) |
| 1562 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 1562 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| 1563 | tr.EXPECT().Recv().Return( | 1563 | tr.EXPECT().Recv().Return( | ||
| 1564 | frameOf(wire.MsgError, wire.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "version mismatch"})), | 1564 | frameOf(wire.MsgError, wire.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "version mismatch"})), | ||
| 1565 | nil, | 1565 | nil, | ||
| 1566 | ).Once() | 1566 | ).Once() | ||
| 1567 | tr.EXPECT().Close().Return(nil).Once() | 1567 | tr.EXPECT().Close().Return(nil).Once() | ||
| 1568 | - | d := portsmocks.NewMockDialer(t) | 1568 | + | d := newMockClientDialer(t) |
| 1569 | d.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | 1569 | d.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | ||
| 1570 | 1570 | ||||
| 1571 | err := runTestClient(context.Background(), testDependencies(d, tm, realClock{}, nil, nil), client.AttachRequest{Intent: protocol.IntentAttach, SessionName: "main", Remote: false}) | 1571 | err := runTestClient(context.Background(), testDependencies(d, tm, realClock{}, nil, nil), client.AttachRequest{Intent: protocol.IntentAttach, SessionName: "main", Remote: false}) | ||
| @@ -1577,7 +1577,7 @@ func TestRunDoesNotEnterRawBeforePreWelcomeError(t *testing.T) { | |||||
| 1577 | 1577 | ||||
| 1578 | func TestRunPhaseASingleAttempt(t *testing.T) { | 1578 | func TestRunPhaseASingleAttempt(t *testing.T) { | ||
| 1579 | dialErr := errors.New("dial failed") | 1579 | dialErr := errors.New("dial failed") | ||
| 1580 | - | d := portsmocks.NewMockDialer(t) | 1580 | + | d := newMockClientDialer(t) |
| 1581 | d.EXPECT().Dial(mock.Anything).Return(nil, dialErr).Once() | 1581 | d.EXPECT().Dial(mock.Anything).Return(nil, dialErr).Once() | ||
| 1582 | tm := portsmocks.NewMockTerminal(t) | 1582 | tm := portsmocks.NewMockTerminal(t) | ||
| 1583 | 1583 | ||||
| @@ -1591,7 +1591,7 @@ type sequenceDialer struct { | |||||
| 1591 | calls atomic.Int32 | 1591 | calls atomic.Int32 | ||
| 1592 | } | 1592 | } | ||
| 1593 | 1593 | ||||
| 1594 | - | func (d *sequenceDialer) Dial(context.Context) (ports.Transport, error) { | 1594 | + | func (d *sequenceDialer) Dial(context.Context) (ports.ClientConnection, error) { |
| 1595 | i := int(d.calls.Add(1)) - 1 | 1595 | i := int(d.calls.Add(1)) - 1 | ||
| 1596 | if i < len(d.errs) && d.errs[i] != nil { | 1596 | if i < len(d.errs) && d.errs[i] != nil { | ||
| 1597 | return nil, d.errs[i] | 1597 | return nil, d.errs[i] | ||
| @@ -1599,7 +1599,7 @@ func (d *sequenceDialer) Dial(context.Context) (ports.Transport, error) { | |||||
| 1599 | if i >= len(d.trs) { | 1599 | if i >= len(d.trs) { | ||
| 1600 | return nil, io.EOF | 1600 | return nil, io.EOF | ||
| 1601 | } | 1601 | } | ||
| 1602 | - | return d.trs[i], nil | 1602 | + | return &rawClientConnection{raw: d.trs[i]}, nil |
| 1603 | } | 1603 | } | ||
| 1604 | 1604 | ||||
| 1605 | type recordingTransport struct { | 1605 | type recordingTransport struct { | ||
| @@ -1939,7 +1939,7 @@ func TestAttachRememberRemoteHost(t *testing.T) { | |||||
| 1939 | return nil | 1939 | return nil | ||
| 1940 | } | 1940 | } | ||
| 1941 | 1941 | ||||
| 1942 | - | tr := portsmocks.NewMockTransport(t) | 1942 | + | tr := newMockClientConnection(t) |
| 1943 | if !tt.preWelcomeReject { | 1943 | if !tt.preWelcomeReject { | ||
| 1944 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 1944 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 1945 | } | 1945 | } | ||
| @@ -2033,7 +2033,7 @@ func TestAttachRememberRemoteHostDoesNotBlockAfterWelcome(t *testing.T) { | |||||
| 2033 | releaseLearner := make(chan struct{}) | 2033 | releaseLearner := make(chan struct{}) | ||
| 2034 | learnerDone := make(chan struct{}) | 2034 | learnerDone := make(chan struct{}) | ||
| 2035 | 2035 | ||||
| 2036 | - | tr := portsmocks.NewMockTransport(t) | 2036 | + | tr := newMockClientConnection(t) |
| 2037 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 2037 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 2038 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 2038 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| 2039 | unblock := scriptRecv(tr, | 2039 | unblock := scriptRecv(tr, | ||
| @@ -2114,7 +2114,7 @@ func TestRunRestoresTerminalBeforeWaitingForLearner(t *testing.T) { | |||||
| 2114 | releaseLearner := make(chan struct{}) | 2114 | releaseLearner := make(chan struct{}) | ||
| 2115 | learnerDone := make(chan struct{}) | 2115 | learnerDone := make(chan struct{}) | ||
| 2116 | 2116 | ||||
| 2117 | - | tr := portsmocks.NewMockTransport(t) | 2117 | + | tr := newMockClientConnection(t) |
| 2118 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 2118 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 2119 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 2119 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| 2120 | unblock := scriptRecv(tr, | 2120 | unblock := scriptRecv(tr, | ||
| @@ -2211,7 +2211,7 @@ func TestRunReturnsWhenRemoteHostLearnerStalls(t *testing.T) { | |||||
| 2211 | return realClock{}.NewTimer(d) | 2211 | return realClock{}.NewTimer(d) | ||
| 2212 | }).Maybe() | 2212 | }).Maybe() | ||
| 2213 | 2213 | ||||
| 2214 | - | tr := portsmocks.NewMockTransport(t) | 2214 | + | tr := newMockClientConnection(t) |
| 2215 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 2215 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 2216 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 2216 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| 2217 | unblock := scriptRecv(tr, | 2217 | unblock := scriptRecv(tr, | ||
M internal/usecase/client/clipboard_intercept_test.go +4 -4
| @@ -44,7 +44,7 @@ func runRemoteClipboardTest(t *testing.T, remote bool, clip ports.ClipboardReade | |||
| 44 | 44 | gotImage = make(chan protocol.ImagePush, 1) | |
| 45 | 45 | allowDetach := make(chan struct{}) | |
| 46 | 46 | ||
| 47 | - | tr := portsmocks.NewMockTransport(t) | |
| 47 | + | tr := newMockClientConnection(t) | |
| 48 | 48 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 49 | 49 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| 50 | 50 | tr.EXPECT().Send(isType(wire.MsgResize)).Return(nil).Maybe() | |
| @@ -88,7 +88,7 @@ func runRemoteClipboardTest(t *testing.T, remote bool, clip ports.ClipboardReade | |||
| 88 | 88 | }).Maybe() | |
| 89 | 89 | tr.EXPECT().Close().Return(nil).Once() | |
| 90 | 90 | ||
| 91 | - | d := portsmocks.NewMockDialer(t) | |
| 91 | + | d := newMockClientDialer(t) | |
| 92 | 92 | d.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | |
| 93 | 93 | ||
| 94 | 94 | err := runTestClient(context.Background(), testDependencies(d, tm, realClock{}, clip, nil), client.AttachRequest{Intent: protocol.IntentEphemeral, Remote: remote}) | |
| @@ -157,8 +157,8 @@ func (*clipboardToastLifecycleTransport) Close() error { return n | |||
| 157 | 157 | ||
| 158 | 158 | type clipboardToastLifecycleDialer struct{ transport ports.Transport } | |
| 159 | 159 | ||
| 160 | - | func (d clipboardToastLifecycleDialer) Dial(context.Context) (ports.Transport, error) { | |
| 161 | - | return d.transport, nil | |
| 160 | + | func (d clipboardToastLifecycleDialer) Dial(context.Context) (ports.ClientConnection, error) { | |
| 161 | + | return &rawClientConnection{raw: d.transport}, nil | |
| 162 | 162 | } | |
| 163 | 163 | ||
| 164 | 164 | func TestRunRemoteClipboardFailureNotifiesDaemonAndWritesOutputVerbatim(t *testing.T) { | |
| @@ -44,7 +44,7 @@ func runRemoteClipboardTest(t *testing.T, remote bool, clip ports.ClipboardReade | |||||
| 44 | gotImage = make(chan protocol.ImagePush, 1) | 44 | gotImage = make(chan protocol.ImagePush, 1) | ||
| 45 | allowDetach := make(chan struct{}) | 45 | allowDetach := make(chan struct{}) | ||
| 46 | 46 | ||||
| 47 | - | tr := portsmocks.NewMockTransport(t) | 47 | + | tr := newMockClientConnection(t) |
| 48 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 48 | tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 49 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 49 | tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| 50 | tr.EXPECT().Send(isType(wire.MsgResize)).Return(nil).Maybe() | 50 | tr.EXPECT().Send(isType(wire.MsgResize)).Return(nil).Maybe() | ||
| @@ -88,7 +88,7 @@ func runRemoteClipboardTest(t *testing.T, remote bool, clip ports.ClipboardReade | |||||
| 88 | }).Maybe() | 88 | }).Maybe() | ||
| 89 | tr.EXPECT().Close().Return(nil).Once() | 89 | tr.EXPECT().Close().Return(nil).Once() | ||
| 90 | 90 | ||||
| 91 | - | d := portsmocks.NewMockDialer(t) | 91 | + | d := newMockClientDialer(t) |
| 92 | d.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | 92 | d.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | ||
| 93 | 93 | ||||
| 94 | err := runTestClient(context.Background(), testDependencies(d, tm, realClock{}, clip, nil), client.AttachRequest{Intent: protocol.IntentEphemeral, Remote: remote}) | 94 | err := runTestClient(context.Background(), testDependencies(d, tm, realClock{}, clip, nil), client.AttachRequest{Intent: protocol.IntentEphemeral, Remote: remote}) | ||
| @@ -157,8 +157,8 @@ func (*clipboardToastLifecycleTransport) Close() error { return n | |||||
| 157 | 157 | ||||
| 158 | type clipboardToastLifecycleDialer struct{ transport ports.Transport } | 158 | type clipboardToastLifecycleDialer struct{ transport ports.Transport } | ||
| 159 | 159 | ||||
| 160 | - | func (d clipboardToastLifecycleDialer) Dial(context.Context) (ports.Transport, error) { | 160 | + | func (d clipboardToastLifecycleDialer) Dial(context.Context) (ports.ClientConnection, error) { |
| 161 | - | return d.transport, nil | 161 | + | return &rawClientConnection{raw: d.transport}, nil |
| 162 | } | 162 | } | ||
| 163 | 163 | ||||
| 164 | func TestRunRemoteClipboardFailureNotifiesDaemonAndWritesOutputVerbatim(t *testing.T) { | 164 | func TestRunRemoteClipboardFailureNotifiesDaemonAndWritesOutputVerbatim(t *testing.T) { | ||
M internal/usecase/client/handshake.go +5 -9
| @@ -63,7 +63,7 @@ func newHandshakeContext(parent context.Context, clock ports.Clock) (context.Con | |||
| 63 | 63 | ||
| 64 | 64 | // watchHandshakeTransport closes a transport when the handshake context ends. | |
| 65 | 65 | // Transport.Close is required to interrupt blocked Send and Recv operations. | |
| 66 | - | func watchHandshakeTransport(ctx context.Context, transport ports.Transport) func() { | |
| 66 | + | func watchHandshakeTransport(ctx context.Context, transport ports.ClientConnection) func() { | |
| 67 | 67 | stop := make(chan struct{}) | |
| 68 | 68 | done := make(chan struct{}) | |
| 69 | 69 | go func() { | |
| @@ -83,11 +83,7 @@ func watchHandshakeTransport(ctx context.Context, transport ports.Transport) fun | |||
| 83 | 83 | } | |
| 84 | 84 | } | |
| 85 | 85 | ||
| 86 | - | func boundedHandshakeOperation(ctx context.Context, transport ports.Transport, operation func() error) error { | |
| 87 | - | return boundedHandshakeOperationWithTransition(ctx, transport, operation, nil) | |
| 88 | - | } | |
| 89 | - | ||
| 90 | - | func boundedHandshakeOperationWithTransition(ctx context.Context, transport ports.Transport, operation func() error, transition *transitionUI) error { | |
| 86 | + | func boundedHandshakeOperationWithTransition(ctx context.Context, transport ports.ClientConnection, operation func() error, transition *transitionUI) error { | |
| 91 | 87 | if err := ctx.Err(); err != nil { | |
| 92 | 88 | _ = transport.Close() | |
| 93 | 89 | return err | |
| @@ -116,16 +112,16 @@ func boundedHandshakeOperationWithTransition(ctx context.Context, transport port | |||
| 116 | 112 | } | |
| 117 | 113 | } | |
| 118 | 114 | ||
| 119 | - | func boundedDial(ctx context.Context, dialer ports.Dialer) (ports.Transport, error) { | |
| 115 | + | func boundedDial(ctx context.Context, dialer ports.ClientDialer) (ports.ClientConnection, error) { | |
| 120 | 116 | return boundedDialWithTransition(ctx, dialer, nil) | |
| 121 | 117 | } | |
| 122 | 118 | ||
| 123 | - | func boundedDialWithTransition(ctx context.Context, dialer ports.Dialer, transition *transitionUI) (ports.Transport, error) { | |
| 119 | + | func boundedDialWithTransition(ctx context.Context, dialer ports.ClientDialer, transition *transitionUI) (ports.ClientConnection, error) { | |
| 124 | 120 | // The dial context only bounds startup. A successful carriage outlives the | |
| 125 | 121 | // handshake context; canceling that context after Welcome must not kill SSH. | |
| 126 | 122 | dialCtx, cancel := context.WithCancel(context.WithoutCancel(ctx)) | |
| 127 | 123 | type dialResult struct { | |
| 128 | - | transport ports.Transport | |
| 124 | + | transport ports.ClientConnection | |
| 129 | 125 | err error | |
| 130 | 126 | } | |
| 131 | 127 | result := make(chan dialResult, 1) | |
| @@ -63,7 +63,7 @@ func newHandshakeContext(parent context.Context, clock ports.Clock) (context.Con | |||||
| 63 | 63 | ||||
| 64 | // watchHandshakeTransport closes a transport when the handshake context ends. | 64 | // watchHandshakeTransport closes a transport when the handshake context ends. | ||
| 65 | // Transport.Close is required to interrupt blocked Send and Recv operations. | 65 | // Transport.Close is required to interrupt blocked Send and Recv operations. | ||
| 66 | - | func watchHandshakeTransport(ctx context.Context, transport ports.Transport) func() { | 66 | + | func watchHandshakeTransport(ctx context.Context, transport ports.ClientConnection) func() { |
| 67 | stop := make(chan struct{}) | 67 | stop := make(chan struct{}) | ||
| 68 | done := make(chan struct{}) | 68 | done := make(chan struct{}) | ||
| 69 | go func() { | 69 | go func() { | ||
| @@ -83,11 +83,7 @@ func watchHandshakeTransport(ctx context.Context, transport ports.Transport) fun | |||||
| 83 | } | 83 | } | ||
| 84 | } | 84 | } | ||
| 85 | 85 | ||||
| 86 | - | func boundedHandshakeOperation(ctx context.Context, transport ports.Transport, operation func() error) error { | 86 | + | func boundedHandshakeOperationWithTransition(ctx context.Context, transport ports.ClientConnection, operation func() error, transition *transitionUI) error { |
| 87 | - | return boundedHandshakeOperationWithTransition(ctx, transport, operation, nil) | |||
| 88 | - | } | |||
| 89 | - | ||||
| 90 | - | func boundedHandshakeOperationWithTransition(ctx context.Context, transport ports.Transport, operation func() error, transition *transitionUI) error { | |||
| 91 | if err := ctx.Err(); err != nil { | 87 | if err := ctx.Err(); err != nil { | ||
| 92 | _ = transport.Close() | 88 | _ = transport.Close() | ||
| 93 | return err | 89 | return err | ||
| @@ -116,16 +112,16 @@ func boundedHandshakeOperationWithTransition(ctx context.Context, transport port | |||||
| 116 | } | 112 | } | ||
| 117 | } | 113 | } | ||
| 118 | 114 | ||||
| 119 | - | func boundedDial(ctx context.Context, dialer ports.Dialer) (ports.Transport, error) { | 115 | + | func boundedDial(ctx context.Context, dialer ports.ClientDialer) (ports.ClientConnection, error) { |
| 120 | return boundedDialWithTransition(ctx, dialer, nil) | 116 | return boundedDialWithTransition(ctx, dialer, nil) | ||
| 121 | } | 117 | } | ||
| 122 | 118 | ||||
| 123 | - | func boundedDialWithTransition(ctx context.Context, dialer ports.Dialer, transition *transitionUI) (ports.Transport, error) { | 119 | + | func boundedDialWithTransition(ctx context.Context, dialer ports.ClientDialer, transition *transitionUI) (ports.ClientConnection, error) { |
| 124 | // The dial context only bounds startup. A successful carriage outlives the | 120 | // The dial context only bounds startup. A successful carriage outlives the | ||
| 125 | // handshake context; canceling that context after Welcome must not kill SSH. | 121 | // handshake context; canceling that context after Welcome must not kill SSH. | ||
| 126 | dialCtx, cancel := context.WithCancel(context.WithoutCancel(ctx)) | 122 | dialCtx, cancel := context.WithCancel(context.WithoutCancel(ctx)) | ||
| 127 | type dialResult struct { | 123 | type dialResult struct { | ||
| 128 | - | transport ports.Transport | 124 | + | transport ports.ClientConnection |
| 129 | err error | 125 | err error | ||
| 130 | } | 126 | } | ||
| 131 | result := make(chan dialResult, 1) | 127 | result := make(chan dialResult, 1) | ||
M internal/usecase/client/handshake_test.go +1 -1
| @@ -14,7 +14,7 @@ type failedContextDialer struct { | |||
| 14 | 14 | ctx context.Context | |
| 15 | 15 | } | |
| 16 | 16 | ||
| 17 | - | func (d *failedContextDialer) Dial(ctx context.Context) (ports.Transport, error) { | |
| 17 | + | func (d *failedContextDialer) Dial(ctx context.Context) (ports.ClientConnection, error) { | |
| 18 | 18 | d.ctx = ctx | |
| 19 | 19 | return nil, errors.New("dial failed") | |
| 20 | 20 | } | |
| @@ -14,7 +14,7 @@ type failedContextDialer struct { | |||||
| 14 | ctx context.Context | 14 | ctx context.Context | ||
| 15 | } | 15 | } | ||
| 16 | 16 | ||||
| 17 | - | func (d *failedContextDialer) Dial(ctx context.Context) (ports.Transport, error) { | 17 | + | func (d *failedContextDialer) Dial(ctx context.Context) (ports.ClientConnection, error) { |
| 18 | d.ctx = ctx | 18 | d.ctx = ctx | ||
| 19 | return nil, errors.New("dial failed") | 19 | return nil, errors.New("dial failed") | ||
| 20 | } | 20 | } | ||
M internal/usecase/client/palette_attach_generation_test.go +1 -1
| @@ -438,7 +438,7 @@ func TestStdinPumpSameReadSchemeAndOldMarkerCannotAdvanceReplacement(t *testing. | |||
| 438 | 438 | // notification and publish its replacement generation before the scanner | |
| 439 | 439 | // can deliver the old marker from the same terminal Read. | |
| 440 | 440 | events := make(chan paletteGenerationEvent) | |
| 441 | - | out := make(chan wire.Frame, 1) | |
| 441 | + | out := make(chan protocol.ClientMessage, 1) | |
| 442 | 442 | schemeHandled := make(chan struct{}) | |
| 443 | 443 | pump := &stdinPump{ | |
| 444 | 444 | ctx: ctx, | |
| @@ -438,7 +438,7 @@ func TestStdinPumpSameReadSchemeAndOldMarkerCannotAdvanceReplacement(t *testing. | |||||
| 438 | // notification and publish its replacement generation before the scanner | 438 | // notification and publish its replacement generation before the scanner | ||
| 439 | // can deliver the old marker from the same terminal Read. | 439 | // can deliver the old marker from the same terminal Read. | ||
| 440 | events := make(chan paletteGenerationEvent) | 440 | events := make(chan paletteGenerationEvent) | ||
| 441 | - | out := make(chan wire.Frame, 1) | 441 | + | out := make(chan protocol.ClientMessage, 1) |
| 442 | schemeHandled := make(chan struct{}) | 442 | schemeHandled := make(chan struct{}) | ||
| 443 | pump := &stdinPump{ | 443 | pump := &stdinPump{ | ||
| 444 | ctx: ctx, | 444 | ctx: ctx, | ||
M internal/usecase/client/reconnect_toast_test.go +13 -13
| @@ -152,13 +152,13 @@ type reconnectToastSequenceDialer struct { | |||
| 152 | 152 | calls int | |
| 153 | 153 | } | |
| 154 | 154 | ||
| 155 | - | func (d *reconnectToastSequenceDialer) Dial(context.Context) (ports.Transport, error) { | |
| 155 | + | func (d *reconnectToastSequenceDialer) Dial(context.Context) (ports.ClientConnection, error) { | |
| 156 | 156 | if d.calls >= len(d.transports) { | |
| 157 | 157 | return nil, io.EOF | |
| 158 | 158 | } | |
| 159 | 159 | tr := d.transports[d.calls] | |
| 160 | 160 | d.calls++ | |
| 161 | - | return tr, nil | |
| 161 | + | return &rawClientConnection{raw: tr}, nil | |
| 162 | 162 | } | |
| 163 | 163 | ||
| 164 | 164 | type reconnectToastRecordingTransport struct { | |
| @@ -323,7 +323,7 @@ func newReconnectHandshakeClock(t *testing.T) *portsmocks.MockClock { | |||
| 323 | 323 | return clk | |
| 324 | 324 | } | |
| 325 | 325 | ||
| 326 | - | func newReconnectAttachAttempt(term ports.Terminal, transport ports.Transport, clock ports.Clock, request AttachRequest, resumeToken uint64, state *terminalThemeState, linkEvents <-chan ports.LinkEvent, ms *milestones) *attachAttempt { | |
| 326 | + | func newReconnectAttachAttempt(term ports.Terminal, transport ports.ClientConnection, clock ports.Clock, request AttachRequest, resumeToken uint64, state *terminalThemeState, linkEvents <-chan ports.LinkEvent, ms *milestones) *attachAttempt { | |
| 327 | 327 | rawEntered := false | |
| 328 | 328 | enterRaw := func() error { | |
| 329 | 329 | if rawEntered { | |
| @@ -1430,7 +1430,7 @@ func TestReconnectRetrySleepFailuresPreserveDialError(t *testing.T) { | |||
| 1430 | 1430 | reconnectToastRecv{frame: reconnectToastWelcome(44)}, | |
| 1431 | 1431 | reconnectToastRecv{err: io.EOF}, | |
| 1432 | 1432 | ) | |
| 1433 | - | dialer := portsmocks.NewMockDialer(t) | |
| 1433 | + | dialer := newMockClientDialer(t) | |
| 1434 | 1434 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | |
| 1435 | 1435 | dialer.EXPECT().Dial(mock.Anything).Run(func(context.Context) { armed.Store(true) }).Return(nil, dialErr).Once() | |
| 1436 | 1436 | ||
| @@ -1484,12 +1484,12 @@ func (w *reconnectStatusClearFailWriter) Write(p []byte) (int, error) { | |||
| 1484 | 1484 | func TestReconnectCancellationPreservesContextWhenStatusClearFails(t *testing.T) { | |
| 1485 | 1485 | tests := []struct { | |
| 1486 | 1486 | name string | |
| 1487 | - | configure func(*portsmocks.MockDialer, ports.Transport) | |
| 1487 | + | configure func(*mockClientDialer, ports.Transport) | |
| 1488 | 1488 | sleep func(context.CancelFunc) bool | |
| 1489 | 1489 | }{ | |
| 1490 | 1490 | { | |
| 1491 | 1491 | name: "after attach failure", | |
| 1492 | - | configure: func(dialer *portsmocks.MockDialer, tr ports.Transport) { | |
| 1492 | + | configure: func(dialer *mockClientDialer, tr ports.Transport) { | |
| 1493 | 1493 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | |
| 1494 | 1494 | }, | |
| 1495 | 1495 | sleep: func(cancel context.CancelFunc) bool { | |
| @@ -1499,7 +1499,7 @@ func TestReconnectCancellationPreservesContextWhenStatusClearFails(t *testing.T) | |||
| 1499 | 1499 | }, | |
| 1500 | 1500 | { | |
| 1501 | 1501 | name: "after reconnect dial failure", | |
| 1502 | - | configure: func(dialer *portsmocks.MockDialer, tr ports.Transport) { | |
| 1502 | + | configure: func(dialer *mockClientDialer, tr ports.Transport) { | |
| 1503 | 1503 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | |
| 1504 | 1504 | dialer.EXPECT().Dial(mock.Anything).Return(nil, errors.New("redial failed")).Once() | |
| 1505 | 1505 | }, | |
| @@ -1538,7 +1538,7 @@ func TestReconnectCancellationPreservesContextWhenStatusClearFails(t *testing.T) | |||
| 1538 | 1538 | reconnectToastRecv{frame: reconnectToastWelcome(11)}, | |
| 1539 | 1539 | reconnectToastRecv{err: io.EOF}, | |
| 1540 | 1540 | ) | |
| 1541 | - | dialer := portsmocks.NewMockDialer(t) | |
| 1541 | + | dialer := newMockClientDialer(t) | |
| 1542 | 1542 | tt.configure(dialer, tr) | |
| 1543 | 1543 | ||
| 1544 | 1544 | err := NewRunner(Dependencies{Dialer: dialer, Terminal: term.term, Clock: newReconnectHandshakeClock(t), DisableCapabilityProbe: true, Logger: slog.New(slog.DiscardHandler)}).Run(ctx, AttachRequest{Intent: protocol.IntentAttach, SessionName: "main"}) | |
| @@ -1640,13 +1640,13 @@ func TestRemoteEphemeralReconnectUsesAssignedSessionName(t *testing.T) { | |||
| 1640 | 1640 | func TestRemoteReconnectToastLifecycle(t *testing.T) { | |
| 1641 | 1641 | tests := []struct { | |
| 1642 | 1642 | name string | |
| 1643 | - | configure func(t *testing.T, dialer *portsmocks.MockDialer) []*reconnectToastRecordingTransport | |
| 1643 | + | configure func(t *testing.T, dialer *mockClientDialer) []*reconnectToastRecordingTransport | |
| 1644 | 1644 | sleep func(cancel context.CancelFunc) bool | |
| 1645 | 1645 | wantErr func(t *testing.T, err error) | |
| 1646 | 1646 | }{ | |
| 1647 | 1647 | { | |
| 1648 | 1648 | name: "clears on successful reconnect", | |
| 1649 | - | configure: func(t *testing.T, dialer *portsmocks.MockDialer) []*reconnectToastRecordingTransport { | |
| 1649 | + | configure: func(t *testing.T, dialer *mockClientDialer) []*reconnectToastRecordingTransport { | |
| 1650 | 1650 | tr1 := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(11)}, reconnectToastRecv{err: io.EOF}) | |
| 1651 | 1651 | tr2 := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(22)}, reconnectToastRecv{frame: reconnectToastDetach(protocol.ReasonDetach)}) | |
| 1652 | 1652 | dialer.EXPECT().Dial(mock.Anything).Return(tr1, nil).Once() | |
| @@ -1658,7 +1658,7 @@ func TestRemoteReconnectToastLifecycle(t *testing.T) { | |||
| 1658 | 1658 | }, | |
| 1659 | 1659 | { | |
| 1660 | 1660 | name: "clears on cancellation", | |
| 1661 | - | configure: func(t *testing.T, dialer *portsmocks.MockDialer) []*reconnectToastRecordingTransport { | |
| 1661 | + | configure: func(t *testing.T, dialer *mockClientDialer) []*reconnectToastRecordingTransport { | |
| 1662 | 1662 | tr := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(11)}, reconnectToastRecv{err: io.EOF}) | |
| 1663 | 1663 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | |
| 1664 | 1664 | return []*reconnectToastRecordingTransport{tr} | |
| @@ -1671,7 +1671,7 @@ func TestRemoteReconnectToastLifecycle(t *testing.T) { | |||
| 1671 | 1671 | }, | |
| 1672 | 1672 | { | |
| 1673 | 1673 | name: "clears on final exit", | |
| 1674 | - | configure: func(t *testing.T, dialer *portsmocks.MockDialer) []*reconnectToastRecordingTransport { | |
| 1674 | + | configure: func(t *testing.T, dialer *mockClientDialer) []*reconnectToastRecordingTransport { | |
| 1675 | 1675 | tr1 := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(11)}, reconnectToastRecv{err: io.EOF}) | |
| 1676 | 1676 | tr2 := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(22)}, reconnectToastRecv{frame: reconnectToastDetach(protocol.ReasonSessionKilled)}) | |
| 1677 | 1677 | dialer.EXPECT().Dial(mock.Anything).Return(tr1, nil).Once() | |
| @@ -1702,7 +1702,7 @@ func TestRemoteReconnectToastLifecycle(t *testing.T) { | |||
| 1702 | 1702 | ||
| 1703 | 1703 | term := newReconnectToastTerminalHarness(t) | |
| 1704 | 1704 | defer term.closeInput() | |
| 1705 | - | dialer := portsmocks.NewMockDialer(t) | |
| 1705 | + | dialer := newMockClientDialer(t) | |
| 1706 | 1706 | transports := tt.configure(t, dialer) | |
| 1707 | 1707 | ||
| 1708 | 1708 | err := NewRunner(Dependencies{Dialer: dialer, Terminal: term.term, Clock: newReconnectHandshakeClock(t), DisableCapabilityProbe: true, Logger: slog.New(slog.DiscardHandler)}).Run(ctx, AttachRequest{Intent: protocol.IntentAttach, SessionName: "main", Remote: true}) | |
| @@ -152,13 +152,13 @@ type reconnectToastSequenceDialer struct { | |||||
| 152 | calls int | 152 | calls int | ||
| 153 | } | 153 | } | ||
| 154 | 154 | ||||
| 155 | - | func (d *reconnectToastSequenceDialer) Dial(context.Context) (ports.Transport, error) { | 155 | + | func (d *reconnectToastSequenceDialer) Dial(context.Context) (ports.ClientConnection, error) { |
| 156 | if d.calls >= len(d.transports) { | 156 | if d.calls >= len(d.transports) { | ||
| 157 | return nil, io.EOF | 157 | return nil, io.EOF | ||
| 158 | } | 158 | } | ||
| 159 | tr := d.transports[d.calls] | 159 | tr := d.transports[d.calls] | ||
| 160 | d.calls++ | 160 | d.calls++ | ||
| 161 | - | return tr, nil | 161 | + | return &rawClientConnection{raw: tr}, nil |
| 162 | } | 162 | } | ||
| 163 | 163 | ||||
| 164 | type reconnectToastRecordingTransport struct { | 164 | type reconnectToastRecordingTransport struct { | ||
| @@ -323,7 +323,7 @@ func newReconnectHandshakeClock(t *testing.T) *portsmocks.MockClock { | |||||
| 323 | return clk | 323 | return clk | ||
| 324 | } | 324 | } | ||
| 325 | 325 | ||||
| 326 | - | func newReconnectAttachAttempt(term ports.Terminal, transport ports.Transport, clock ports.Clock, request AttachRequest, resumeToken uint64, state *terminalThemeState, linkEvents <-chan ports.LinkEvent, ms *milestones) *attachAttempt { | 326 | + | func newReconnectAttachAttempt(term ports.Terminal, transport ports.ClientConnection, clock ports.Clock, request AttachRequest, resumeToken uint64, state *terminalThemeState, linkEvents <-chan ports.LinkEvent, ms *milestones) *attachAttempt { |
| 327 | rawEntered := false | 327 | rawEntered := false | ||
| 328 | enterRaw := func() error { | 328 | enterRaw := func() error { | ||
| 329 | if rawEntered { | 329 | if rawEntered { | ||
| @@ -1430,7 +1430,7 @@ func TestReconnectRetrySleepFailuresPreserveDialError(t *testing.T) { | |||||
| 1430 | reconnectToastRecv{frame: reconnectToastWelcome(44)}, | 1430 | reconnectToastRecv{frame: reconnectToastWelcome(44)}, | ||
| 1431 | reconnectToastRecv{err: io.EOF}, | 1431 | reconnectToastRecv{err: io.EOF}, | ||
| 1432 | ) | 1432 | ) | ||
| 1433 | - | dialer := portsmocks.NewMockDialer(t) | 1433 | + | dialer := newMockClientDialer(t) |
| 1434 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | 1434 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | ||
| 1435 | dialer.EXPECT().Dial(mock.Anything).Run(func(context.Context) { armed.Store(true) }).Return(nil, dialErr).Once() | 1435 | dialer.EXPECT().Dial(mock.Anything).Run(func(context.Context) { armed.Store(true) }).Return(nil, dialErr).Once() | ||
| 1436 | 1436 | ||||
| @@ -1484,12 +1484,12 @@ func (w *reconnectStatusClearFailWriter) Write(p []byte) (int, error) { | |||||
| 1484 | func TestReconnectCancellationPreservesContextWhenStatusClearFails(t *testing.T) { | 1484 | func TestReconnectCancellationPreservesContextWhenStatusClearFails(t *testing.T) { | ||
| 1485 | tests := []struct { | 1485 | tests := []struct { | ||
| 1486 | name string | 1486 | name string | ||
| 1487 | - | configure func(*portsmocks.MockDialer, ports.Transport) | 1487 | + | configure func(*mockClientDialer, ports.Transport) |
| 1488 | sleep func(context.CancelFunc) bool | 1488 | sleep func(context.CancelFunc) bool | ||
| 1489 | }{ | 1489 | }{ | ||
| 1490 | { | 1490 | { | ||
| 1491 | name: "after attach failure", | 1491 | name: "after attach failure", | ||
| 1492 | - | configure: func(dialer *portsmocks.MockDialer, tr ports.Transport) { | 1492 | + | configure: func(dialer *mockClientDialer, tr ports.Transport) { |
| 1493 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | 1493 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | ||
| 1494 | }, | 1494 | }, | ||
| 1495 | sleep: func(cancel context.CancelFunc) bool { | 1495 | sleep: func(cancel context.CancelFunc) bool { | ||
| @@ -1499,7 +1499,7 @@ func TestReconnectCancellationPreservesContextWhenStatusClearFails(t *testing.T) | |||||
| 1499 | }, | 1499 | }, | ||
| 1500 | { | 1500 | { | ||
| 1501 | name: "after reconnect dial failure", | 1501 | name: "after reconnect dial failure", | ||
| 1502 | - | configure: func(dialer *portsmocks.MockDialer, tr ports.Transport) { | 1502 | + | configure: func(dialer *mockClientDialer, tr ports.Transport) { |
| 1503 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | 1503 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | ||
| 1504 | dialer.EXPECT().Dial(mock.Anything).Return(nil, errors.New("redial failed")).Once() | 1504 | dialer.EXPECT().Dial(mock.Anything).Return(nil, errors.New("redial failed")).Once() | ||
| 1505 | }, | 1505 | }, | ||
| @@ -1538,7 +1538,7 @@ func TestReconnectCancellationPreservesContextWhenStatusClearFails(t *testing.T) | |||||
| 1538 | reconnectToastRecv{frame: reconnectToastWelcome(11)}, | 1538 | reconnectToastRecv{frame: reconnectToastWelcome(11)}, | ||
| 1539 | reconnectToastRecv{err: io.EOF}, | 1539 | reconnectToastRecv{err: io.EOF}, | ||
| 1540 | ) | 1540 | ) | ||
| 1541 | - | dialer := portsmocks.NewMockDialer(t) | 1541 | + | dialer := newMockClientDialer(t) |
| 1542 | tt.configure(dialer, tr) | 1542 | tt.configure(dialer, tr) | ||
| 1543 | 1543 | ||||
| 1544 | err := NewRunner(Dependencies{Dialer: dialer, Terminal: term.term, Clock: newReconnectHandshakeClock(t), DisableCapabilityProbe: true, Logger: slog.New(slog.DiscardHandler)}).Run(ctx, AttachRequest{Intent: protocol.IntentAttach, SessionName: "main"}) | 1544 | err := NewRunner(Dependencies{Dialer: dialer, Terminal: term.term, Clock: newReconnectHandshakeClock(t), DisableCapabilityProbe: true, Logger: slog.New(slog.DiscardHandler)}).Run(ctx, AttachRequest{Intent: protocol.IntentAttach, SessionName: "main"}) | ||
| @@ -1640,13 +1640,13 @@ func TestRemoteEphemeralReconnectUsesAssignedSessionName(t *testing.T) { | |||||
| 1640 | func TestRemoteReconnectToastLifecycle(t *testing.T) { | 1640 | func TestRemoteReconnectToastLifecycle(t *testing.T) { | ||
| 1641 | tests := []struct { | 1641 | tests := []struct { | ||
| 1642 | name string | 1642 | name string | ||
| 1643 | - | configure func(t *testing.T, dialer *portsmocks.MockDialer) []*reconnectToastRecordingTransport | 1643 | + | configure func(t *testing.T, dialer *mockClientDialer) []*reconnectToastRecordingTransport |
| 1644 | sleep func(cancel context.CancelFunc) bool | 1644 | sleep func(cancel context.CancelFunc) bool | ||
| 1645 | wantErr func(t *testing.T, err error) | 1645 | wantErr func(t *testing.T, err error) | ||
| 1646 | }{ | 1646 | }{ | ||
| 1647 | { | 1647 | { | ||
| 1648 | name: "clears on successful reconnect", | 1648 | name: "clears on successful reconnect", | ||
| 1649 | - | configure: func(t *testing.T, dialer *portsmocks.MockDialer) []*reconnectToastRecordingTransport { | 1649 | + | configure: func(t *testing.T, dialer *mockClientDialer) []*reconnectToastRecordingTransport { |
| 1650 | tr1 := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(11)}, reconnectToastRecv{err: io.EOF}) | 1650 | tr1 := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(11)}, reconnectToastRecv{err: io.EOF}) | ||
| 1651 | tr2 := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(22)}, reconnectToastRecv{frame: reconnectToastDetach(protocol.ReasonDetach)}) | 1651 | tr2 := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(22)}, reconnectToastRecv{frame: reconnectToastDetach(protocol.ReasonDetach)}) | ||
| 1652 | dialer.EXPECT().Dial(mock.Anything).Return(tr1, nil).Once() | 1652 | dialer.EXPECT().Dial(mock.Anything).Return(tr1, nil).Once() | ||
| @@ -1658,7 +1658,7 @@ func TestRemoteReconnectToastLifecycle(t *testing.T) { | |||||
| 1658 | }, | 1658 | }, | ||
| 1659 | { | 1659 | { | ||
| 1660 | name: "clears on cancellation", | 1660 | name: "clears on cancellation", | ||
| 1661 | - | configure: func(t *testing.T, dialer *portsmocks.MockDialer) []*reconnectToastRecordingTransport { | 1661 | + | configure: func(t *testing.T, dialer *mockClientDialer) []*reconnectToastRecordingTransport { |
| 1662 | tr := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(11)}, reconnectToastRecv{err: io.EOF}) | 1662 | tr := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(11)}, reconnectToastRecv{err: io.EOF}) | ||
| 1663 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | 1663 | dialer.EXPECT().Dial(mock.Anything).Return(tr, nil).Once() | ||
| 1664 | return []*reconnectToastRecordingTransport{tr} | 1664 | return []*reconnectToastRecordingTransport{tr} | ||
| @@ -1671,7 +1671,7 @@ func TestRemoteReconnectToastLifecycle(t *testing.T) { | |||||
| 1671 | }, | 1671 | }, | ||
| 1672 | { | 1672 | { | ||
| 1673 | name: "clears on final exit", | 1673 | name: "clears on final exit", | ||
| 1674 | - | configure: func(t *testing.T, dialer *portsmocks.MockDialer) []*reconnectToastRecordingTransport { | 1674 | + | configure: func(t *testing.T, dialer *mockClientDialer) []*reconnectToastRecordingTransport { |
| 1675 | tr1 := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(11)}, reconnectToastRecv{err: io.EOF}) | 1675 | tr1 := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(11)}, reconnectToastRecv{err: io.EOF}) | ||
| 1676 | tr2 := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(22)}, reconnectToastRecv{frame: reconnectToastDetach(protocol.ReasonSessionKilled)}) | 1676 | tr2 := newReconnectToastRecordingTransport(reconnectToastRecv{frame: reconnectToastWelcome(22)}, reconnectToastRecv{frame: reconnectToastDetach(protocol.ReasonSessionKilled)}) | ||
| 1677 | dialer.EXPECT().Dial(mock.Anything).Return(tr1, nil).Once() | 1677 | dialer.EXPECT().Dial(mock.Anything).Return(tr1, nil).Once() | ||
| @@ -1702,7 +1702,7 @@ func TestRemoteReconnectToastLifecycle(t *testing.T) { | |||||
| 1702 | 1702 | ||||
| 1703 | term := newReconnectToastTerminalHarness(t) | 1703 | term := newReconnectToastTerminalHarness(t) | ||
| 1704 | defer term.closeInput() | 1704 | defer term.closeInput() | ||
| 1705 | - | dialer := portsmocks.NewMockDialer(t) | 1705 | + | dialer := newMockClientDialer(t) |
| 1706 | transports := tt.configure(t, dialer) | 1706 | transports := tt.configure(t, dialer) | ||
| 1707 | 1707 | ||||
| 1708 | err := NewRunner(Dependencies{Dialer: dialer, Terminal: term.term, Clock: newReconnectHandshakeClock(t), DisableCapabilityProbe: true, Logger: slog.New(slog.DiscardHandler)}).Run(ctx, AttachRequest{Intent: protocol.IntentAttach, SessionName: "main", Remote: true}) | 1708 | err := NewRunner(Dependencies{Dialer: dialer, Terminal: term.term, Clock: newReconnectHandshakeClock(t), DisableCapabilityProbe: true, Logger: slog.New(slog.DiscardHandler)}).Run(ctx, AttachRequest{Intent: protocol.IntentAttach, SessionName: "main", Remote: true}) | ||
M internal/usecase/client/remote_attach_validation_test.go +1 -2
| @@ -7,7 +7,6 @@ import ( | |||
| 7 | 7 | "github.com/stretchr/testify/require" | |
| 8 | 8 | ||
| 9 | 9 | "github.com/bnema/vev/internal/domain" | |
| 10 | - | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |
| 11 | 10 | "github.com/bnema/vev/internal/protocol" | |
| 12 | 11 | ) | |
| 13 | 12 | ||
| @@ -24,7 +23,7 @@ func TestMalformedAttachRequestsNeverDial(t *testing.T) { | |||
| 24 | 23 | for _, tt := range tests { | |
| 25 | 24 | t.Run(tt.name, func(t *testing.T) { | |
| 26 | 25 | // No EXPECT: any Dial call fails the test. | |
| 27 | - | dialer := portsmocks.NewMockDialer(t) | |
| 26 | + | dialer := newMockClientDialer(t) | |
| 28 | 27 | err := NewRunner(Dependencies{Dialer: dialer}).Run(context.Background(), tt.request) | |
| 29 | 28 | require.Error(t, err) | |
| 30 | 29 | }) | |
| @@ -7,7 +7,6 @@ import ( | |||||
| 7 | "github.com/stretchr/testify/require" | 7 | "github.com/stretchr/testify/require" | ||
| 8 | 8 | ||||
| 9 | "github.com/bnema/vev/internal/domain" | 9 | "github.com/bnema/vev/internal/domain" | ||
| 10 | - | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |||
| 11 | "github.com/bnema/vev/internal/protocol" | 10 | "github.com/bnema/vev/internal/protocol" | ||
| 12 | ) | 11 | ) | ||
| 13 | 12 | ||||
| @@ -24,7 +23,7 @@ func TestMalformedAttachRequestsNeverDial(t *testing.T) { | |||||
| 24 | for _, tt := range tests { | 23 | for _, tt := range tests { | ||
| 25 | t.Run(tt.name, func(t *testing.T) { | 24 | t.Run(tt.name, func(t *testing.T) { | ||
| 26 | // No EXPECT: any Dial call fails the test. | 25 | // No EXPECT: any Dial call fails the test. | ||
| 27 | - | dialer := portsmocks.NewMockDialer(t) | 26 | + | dialer := newMockClientDialer(t) |
| 28 | err := NewRunner(Dependencies{Dialer: dialer}).Run(context.Background(), tt.request) | 27 | err := NewRunner(Dependencies{Dialer: dialer}).Run(context.Background(), tt.request) | ||
| 29 | require.Error(t, err) | 28 | require.Error(t, err) | ||
| 30 | }) | 29 | }) | ||
M internal/usecase/client/routes.go +4 -4
| @@ -62,7 +62,7 @@ type routeCandidate struct { | |||
| 62 | 62 | originKey string | |
| 63 | 63 | target protocol.ExactSessionTarget | |
| 64 | 64 | presentation routePresentation | |
| 65 | - | dialer ports.Dialer | |
| 65 | + | dialer ports.ClientDialer | |
| 66 | 66 | request AttachRequest | |
| 67 | 67 | resumeToken uint64 | |
| 68 | 68 | home bool | |
| @@ -74,7 +74,7 @@ type routeRecord struct { | |||
| 74 | 74 | originKey string | |
| 75 | 75 | target protocol.ExactSessionTarget | |
| 76 | 76 | presentation routePresentation | |
| 77 | - | dialer ports.Dialer | |
| 77 | + | dialer ports.ClientDialer | |
| 78 | 78 | request AttachRequest | |
| 79 | 79 | resumeToken uint64 | |
| 80 | 80 | home bool | |
| @@ -115,7 +115,7 @@ func cloneRoutePosition(position *protocol.RoutePosition) *protocol.RoutePositio | |||
| 115 | 115 | return &clone | |
| 116 | 116 | } | |
| 117 | 117 | ||
| 118 | - | func routeCandidateForAttach(request AttachRequest, identity protocol.CommittedRouteIdentity, dialer ports.Dialer, resumeToken uint64) routeCandidate { | |
| 118 | + | func routeCandidateForAttach(request AttachRequest, identity protocol.CommittedRouteIdentity, dialer ports.ClientDialer, resumeToken uint64) routeCandidate { | |
| 119 | 119 | origin := normalizeRouteOrigin(request.Origin, request.Remote) | |
| 120 | 120 | originKey := normalizeRouteOriginKey(request.OriginKey, origin) | |
| 121 | 121 | kind := protocol.RouteKindLocal | |
| @@ -628,7 +628,7 @@ type routeTransitionPlan struct { | |||
| 628 | 628 | type routeTransitionCommit struct { | |
| 629 | 629 | identity protocol.CommittedRouteIdentity | |
| 630 | 630 | presentation routePresentation | |
| 631 | - | dialer ports.Dialer | |
| 631 | + | dialer ports.ClientDialer | |
| 632 | 632 | resumeToken uint64 | |
| 633 | 633 | } | |
| 634 | 634 | ||
| @@ -62,7 +62,7 @@ type routeCandidate struct { | |||||
| 62 | originKey string | 62 | originKey string | ||
| 63 | target protocol.ExactSessionTarget | 63 | target protocol.ExactSessionTarget | ||
| 64 | presentation routePresentation | 64 | presentation routePresentation | ||
| 65 | - | dialer ports.Dialer | 65 | + | dialer ports.ClientDialer |
| 66 | request AttachRequest | 66 | request AttachRequest | ||
| 67 | resumeToken uint64 | 67 | resumeToken uint64 | ||
| 68 | home bool | 68 | home bool | ||
| @@ -74,7 +74,7 @@ type routeRecord struct { | |||||
| 74 | originKey string | 74 | originKey string | ||
| 75 | target protocol.ExactSessionTarget | 75 | target protocol.ExactSessionTarget | ||
| 76 | presentation routePresentation | 76 | presentation routePresentation | ||
| 77 | - | dialer ports.Dialer | 77 | + | dialer ports.ClientDialer |
| 78 | request AttachRequest | 78 | request AttachRequest | ||
| 79 | resumeToken uint64 | 79 | resumeToken uint64 | ||
| 80 | home bool | 80 | home bool | ||
| @@ -115,7 +115,7 @@ func cloneRoutePosition(position *protocol.RoutePosition) *protocol.RoutePositio | |||||
| 115 | return &clone | 115 | return &clone | ||
| 116 | } | 116 | } | ||
| 117 | 117 | ||||
| 118 | - | func routeCandidateForAttach(request AttachRequest, identity protocol.CommittedRouteIdentity, dialer ports.Dialer, resumeToken uint64) routeCandidate { | 118 | + | func routeCandidateForAttach(request AttachRequest, identity protocol.CommittedRouteIdentity, dialer ports.ClientDialer, resumeToken uint64) routeCandidate { |
| 119 | origin := normalizeRouteOrigin(request.Origin, request.Remote) | 119 | origin := normalizeRouteOrigin(request.Origin, request.Remote) | ||
| 120 | originKey := normalizeRouteOriginKey(request.OriginKey, origin) | 120 | originKey := normalizeRouteOriginKey(request.OriginKey, origin) | ||
| 121 | kind := protocol.RouteKindLocal | 121 | kind := protocol.RouteKindLocal | ||
| @@ -628,7 +628,7 @@ type routeTransitionPlan struct { | |||||
| 628 | type routeTransitionCommit struct { | 628 | type routeTransitionCommit struct { | ||
| 629 | identity protocol.CommittedRouteIdentity | 629 | identity protocol.CommittedRouteIdentity | ||
| 630 | presentation routePresentation | 630 | presentation routePresentation | ||
| 631 | - | dialer ports.Dialer | 631 | + | dialer ports.ClientDialer |
| 632 | resumeToken uint64 | 632 | resumeToken uint64 | ||
| 633 | } | 633 | } | ||
| 634 | 634 | ||||
M internal/usecase/client/session_connection.go +16 -17
| @@ -10,7 +10,7 @@ import ( | |||
| 10 | 10 | ) | |
| 11 | 11 | ||
| 12 | 12 | var ( | |
| 13 | - | errNilSessionTransport = errors.New("client: nil session transport") | |
| 13 | + | errNilSessionConnection = errors.New("client: nil session connection") | |
| 14 | 14 | ErrEphemeralSessionName = errors.New("client: ephemeral sessions cannot have a name") | |
| 15 | 15 | ) | |
| 16 | 16 | ||
| @@ -38,31 +38,30 @@ func (t SessionTarget) validate() error { | |||
| 38 | 38 | return domain.ValidateSessionName(t.SessionName) | |
| 39 | 39 | } | |
| 40 | 40 | ||
| 41 | - | // SessionConnection owns one already-open transport and its daemon-facing | |
| 42 | - | // session target. Local sockets and remote carriages use this same owner. | |
| 41 | + | // SessionConnection owns one already-open typed connection and its | |
| 42 | + | // daemon-facing session target. Local and remote carriages use this owner. | |
| 43 | 43 | type SessionConnection struct { | |
| 44 | - | transport ports.Transport | |
| 45 | - | target SessionTarget | |
| 44 | + | connection ports.ClientConnection | |
| 45 | + | target SessionTarget | |
| 46 | 46 | } | |
| 47 | 47 | ||
| 48 | - | // NewSessionConnection creates a connection for an already-selected transport. | |
| 49 | - | // Target validation happens before the connection is used for a Hello. | |
| 50 | - | func NewSessionConnection(transport ports.Transport, target SessionTarget) (*SessionConnection, error) { | |
| 51 | - | if transport == nil { | |
| 52 | - | return nil, errNilSessionTransport | |
| 48 | + | // NewSessionConnection binds an already-selected typed connection to a target. | |
| 49 | + | func NewSessionConnection(connection ports.ClientConnection, target SessionTarget) (*SessionConnection, error) { | |
| 50 | + | if connection == nil { | |
| 51 | + | return nil, errNilSessionConnection | |
| 53 | 52 | } | |
| 54 | 53 | if err := target.validate(); err != nil { | |
| 55 | 54 | return nil, fmt.Errorf("invalid session target: %w", err) | |
| 56 | 55 | } | |
| 57 | - | return &SessionConnection{transport: transport, target: target}, nil | |
| 56 | + | return &SessionConnection{connection: connection, target: target}, nil | |
| 58 | 57 | } | |
| 59 | 58 | ||
| 60 | - | // Transport returns the owned carriage for this connection. | |
| 61 | - | func (c *SessionConnection) Transport() ports.Transport { | |
| 59 | + | // Connection returns the owned typed session connection. | |
| 60 | + | func (c *SessionConnection) Connection() ports.ClientConnection { | |
| 62 | 61 | if c == nil { | |
| 63 | 62 | return nil | |
| 64 | 63 | } | |
| 65 | - | return c.transport | |
| 64 | + | return c.connection | |
| 66 | 65 | } | |
| 67 | 66 | ||
| 68 | 67 | // AttachRequest returns the common request shape used after transport | |
| @@ -88,10 +87,10 @@ func cloneAttachRequest(request AttachRequest) AttachRequest { | |||
| 88 | 87 | return request | |
| 89 | 88 | } | |
| 90 | 89 | ||
| 91 | - | // Close releases the owned transport. | |
| 90 | + | // Close releases the owned connection. | |
| 92 | 91 | func (c *SessionConnection) Close() error { | |
| 93 | - | if c == nil || c.transport == nil { | |
| 92 | + | if c == nil || c.connection == nil { | |
| 94 | 93 | return nil | |
| 95 | 94 | } | |
| 96 | - | return c.transport.Close() | |
| 95 | + | return c.connection.Close() | |
| 97 | 96 | } | |
| @@ -10,7 +10,7 @@ import ( | |||||
| 10 | ) | 10 | ) | ||
| 11 | 11 | ||||
| 12 | var ( | 12 | var ( | ||
| 13 | - | errNilSessionTransport = errors.New("client: nil session transport") | 13 | + | errNilSessionConnection = errors.New("client: nil session connection") |
| 14 | ErrEphemeralSessionName = errors.New("client: ephemeral sessions cannot have a name") | 14 | ErrEphemeralSessionName = errors.New("client: ephemeral sessions cannot have a name") | ||
| 15 | ) | 15 | ) | ||
| 16 | 16 | ||||
| @@ -38,31 +38,30 @@ func (t SessionTarget) validate() error { | |||||
| 38 | return domain.ValidateSessionName(t.SessionName) | 38 | return domain.ValidateSessionName(t.SessionName) | ||
| 39 | } | 39 | } | ||
| 40 | 40 | ||||
| 41 | - | // SessionConnection owns one already-open transport and its daemon-facing | 41 | + | // SessionConnection owns one already-open typed connection and its |
| 42 | - | // session target. Local sockets and remote carriages use this same owner. | 42 | + | // daemon-facing session target. Local and remote carriages use this owner. |
| 43 | type SessionConnection struct { | 43 | type SessionConnection struct { | ||
| 44 | - | transport ports.Transport | 44 | + | connection ports.ClientConnection |
| 45 | - | target SessionTarget | 45 | + | target SessionTarget |
| 46 | } | 46 | } | ||
| 47 | 47 | ||||
| 48 | - | // NewSessionConnection creates a connection for an already-selected transport. | 48 | + | // NewSessionConnection binds an already-selected typed connection to a target. |
| 49 | - | // Target validation happens before the connection is used for a Hello. | 49 | + | func NewSessionConnection(connection ports.ClientConnection, target SessionTarget) (*SessionConnection, error) { |
| 50 | - | func NewSessionConnection(transport ports.Transport, target SessionTarget) (*SessionConnection, error) { | 50 | + | if connection == nil { |
| 51 | - | if transport == nil { | 51 | + | return nil, errNilSessionConnection |
| 52 | - | return nil, errNilSessionTransport | |||
| 53 | } | 52 | } | ||
| 54 | if err := target.validate(); err != nil { | 53 | if err := target.validate(); err != nil { | ||
| 55 | return nil, fmt.Errorf("invalid session target: %w", err) | 54 | return nil, fmt.Errorf("invalid session target: %w", err) | ||
| 56 | } | 55 | } | ||
| 57 | - | return &SessionConnection{transport: transport, target: target}, nil | 56 | + | return &SessionConnection{connection: connection, target: target}, nil |
| 58 | } | 57 | } | ||
| 59 | 58 | ||||
| 60 | - | // Transport returns the owned carriage for this connection. | 59 | + | // Connection returns the owned typed session connection. |
| 61 | - | func (c *SessionConnection) Transport() ports.Transport { | 60 | + | func (c *SessionConnection) Connection() ports.ClientConnection { |
| 62 | if c == nil { | 61 | if c == nil { | ||
| 63 | return nil | 62 | return nil | ||
| 64 | } | 63 | } | ||
| 65 | - | return c.transport | 64 | + | return c.connection |
| 66 | } | 65 | } | ||
| 67 | 66 | ||||
| 68 | // AttachRequest returns the common request shape used after transport | 67 | // AttachRequest returns the common request shape used after transport | ||
| @@ -88,10 +87,10 @@ func cloneAttachRequest(request AttachRequest) AttachRequest { | |||||
| 88 | return request | 87 | return request | ||
| 89 | } | 88 | } | ||
| 90 | 89 | ||||
| 91 | - | // Close releases the owned transport. | 90 | + | // Close releases the owned connection. |
| 92 | func (c *SessionConnection) Close() error { | 91 | func (c *SessionConnection) Close() error { | ||
| 93 | - | if c == nil || c.transport == nil { | 92 | + | if c == nil || c.connection == nil { |
| 94 | return nil | 93 | return nil | ||
| 95 | } | 94 | } | ||
| 96 | - | return c.transport.Close() | 95 | + | return c.connection.Close() |
| 97 | } | 96 | } | ||
M internal/usecase/client/session_connection_test.go +5 -6
| @@ -3,7 +3,6 @@ package client_test | |||
| 3 | 3 | import ( | |
| 4 | 4 | "testing" | |
| 5 | 5 | ||
| 6 | - | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |
| 7 | 6 | "github.com/bnema/vev/internal/protocol" | |
| 8 | 7 | "github.com/bnema/vev/internal/usecase/client" | |
| 9 | 8 | "github.com/stretchr/testify/require" | |
| @@ -11,8 +10,8 @@ import ( | |||
| 11 | 10 | ||
| 12 | 11 | func TestSessionConnectionUsesOneAttachShapeForLocalAndRemoteTransports(t *testing.T) { | |
| 13 | 12 | target := client.SessionTarget{Intent: protocol.IntentAttach, SessionName: "work"} | |
| 14 | - | local := portsmocks.NewMockTransport(t) | |
| 15 | - | remote := portsmocks.NewMockTransport(t) | |
| 13 | + | local := newMockClientConnection(t) | |
| 14 | + | remote := newMockClientConnection(t) | |
| 16 | 15 | ||
| 17 | 16 | localConnection, err := client.NewSessionConnection(local, target) | |
| 18 | 17 | require.NoError(t, err) | |
| @@ -21,8 +20,8 @@ func TestSessionConnectionUsesOneAttachShapeForLocalAndRemoteTransports(t *testi | |||
| 21 | 20 | ||
| 22 | 21 | require.Equal(t, localConnection.AttachRequest(), remoteConnection.AttachRequest()) | |
| 23 | 22 | require.Equal(t, client.AttachRequest{Intent: protocol.IntentAttach, SessionName: "work"}, localConnection.AttachRequest()) | |
| 24 | - | require.Same(t, local, localConnection.Transport()) | |
| 25 | - | require.Same(t, remote, remoteConnection.Transport()) | |
| 23 | + | require.Same(t, local, localConnection.Connection()) | |
| 24 | + | require.Same(t, remote, remoteConnection.Connection()) | |
| 26 | 25 | } | |
| 27 | 26 | ||
| 28 | 27 | func TestSessionTargetValidation(t *testing.T) { | |
| @@ -43,7 +42,7 @@ func TestSessionTargetValidation(t *testing.T) { | |||
| 43 | 42 | } | |
| 44 | 43 | for _, tt := range tests { | |
| 45 | 44 | t.Run(tt.name, func(t *testing.T) { | |
| 46 | - | transport := portsmocks.NewMockTransport(t) | |
| 45 | + | transport := newMockClientConnection(t) | |
| 47 | 46 | _, err := client.NewSessionConnection(transport, tt.target) | |
| 48 | 47 | if tt.wantErr { | |
| 49 | 48 | require.Error(t, err) | |
| @@ -3,7 +3,6 @@ package client_test | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "testing" | 4 | "testing" | ||
| 5 | 5 | ||||
| 6 | - | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |||
| 7 | "github.com/bnema/vev/internal/protocol" | 6 | "github.com/bnema/vev/internal/protocol" | ||
| 8 | "github.com/bnema/vev/internal/usecase/client" | 7 | "github.com/bnema/vev/internal/usecase/client" | ||
| 9 | "github.com/stretchr/testify/require" | 8 | "github.com/stretchr/testify/require" | ||
| @@ -11,8 +10,8 @@ import ( | |||||
| 11 | 10 | ||||
| 12 | func TestSessionConnectionUsesOneAttachShapeForLocalAndRemoteTransports(t *testing.T) { | 11 | func TestSessionConnectionUsesOneAttachShapeForLocalAndRemoteTransports(t *testing.T) { | ||
| 13 | target := client.SessionTarget{Intent: protocol.IntentAttach, SessionName: "work"} | 12 | target := client.SessionTarget{Intent: protocol.IntentAttach, SessionName: "work"} | ||
| 14 | - | local := portsmocks.NewMockTransport(t) | 13 | + | local := newMockClientConnection(t) |
| 15 | - | remote := portsmocks.NewMockTransport(t) | 14 | + | remote := newMockClientConnection(t) |
| 16 | 15 | ||||
| 17 | localConnection, err := client.NewSessionConnection(local, target) | 16 | localConnection, err := client.NewSessionConnection(local, target) | ||
| 18 | require.NoError(t, err) | 17 | require.NoError(t, err) | ||
| @@ -21,8 +20,8 @@ func TestSessionConnectionUsesOneAttachShapeForLocalAndRemoteTransports(t *testi | |||||
| 21 | 20 | ||||
| 22 | require.Equal(t, localConnection.AttachRequest(), remoteConnection.AttachRequest()) | 21 | require.Equal(t, localConnection.AttachRequest(), remoteConnection.AttachRequest()) | ||
| 23 | require.Equal(t, client.AttachRequest{Intent: protocol.IntentAttach, SessionName: "work"}, localConnection.AttachRequest()) | 22 | require.Equal(t, client.AttachRequest{Intent: protocol.IntentAttach, SessionName: "work"}, localConnection.AttachRequest()) | ||
| 24 | - | require.Same(t, local, localConnection.Transport()) | 23 | + | require.Same(t, local, localConnection.Connection()) |
| 25 | - | require.Same(t, remote, remoteConnection.Transport()) | 24 | + | require.Same(t, remote, remoteConnection.Connection()) |
| 26 | } | 25 | } | ||
| 27 | 26 | ||||
| 28 | func TestSessionTargetValidation(t *testing.T) { | 27 | func TestSessionTargetValidation(t *testing.T) { | ||
| @@ -43,7 +42,7 @@ func TestSessionTargetValidation(t *testing.T) { | |||||
| 43 | } | 42 | } | ||
| 44 | for _, tt := range tests { | 43 | for _, tt := range tests { | ||
| 45 | t.Run(tt.name, func(t *testing.T) { | 44 | t.Run(tt.name, func(t *testing.T) { | ||
| 46 | - | transport := portsmocks.NewMockTransport(t) | 45 | + | transport := newMockClientConnection(t) |
| 47 | _, err := client.NewSessionConnection(transport, tt.target) | 46 | _, err := client.NewSessionConnection(transport, tt.target) | ||
| 48 | if tt.wantErr { | 47 | if tt.wantErr { | ||
| 49 | require.Error(t, err) | 48 | require.Error(t, err) | ||
M internal/usecase/client/terminal_input_pump_test.go +30 -27
| @@ -13,6 +13,7 @@ import ( | |||
| 13 | 13 | ||
| 14 | 14 | "github.com/bnema/vev/internal/domain" | |
| 15 | 15 | "github.com/bnema/vev/internal/ports" | |
| 16 | + | "github.com/bnema/vev/internal/protocol" | |
| 16 | 17 | "github.com/bnema/vev/internal/protocol/wire" | |
| 17 | 18 | ) | |
| 18 | 19 | ||
| @@ -82,10 +83,12 @@ func requireSignal(t *testing.T, ch <-chan struct{}, message string) { | |||
| 82 | 83 | } | |
| 83 | 84 | } | |
| 84 | 85 | ||
| 85 | - | func requireFrame(t *testing.T, ch <-chan wire.Frame, message string) wire.Frame { | |
| 86 | + | func requireFrame(t *testing.T, ch <-chan protocol.ClientMessage, message string) wire.Frame { | |
| 86 | 87 | t.Helper() | |
| 87 | 88 | select { | |
| 88 | - | case frame := <-ch: | |
| 89 | + | case message := <-ch: | |
| 90 | + | frame, err := testClientFrame(message) | |
| 91 | + | require.NoError(t, err) | |
| 89 | 92 | return frame | |
| 90 | 93 | case <-time.After(time.Second): | |
| 91 | 94 | t.Fatal(message) | |
| @@ -182,7 +185,7 @@ func TestTerminalInputPumpCancellationHandoffPreservesQueuedBytes(t *testing.T) | |||
| 182 | 185 | go func() { | |
| 183 | 186 | (&stdinPump{ | |
| 184 | 187 | ctx: cancelledCtx, cancel: cancelCancelled, input: input, | |
| 185 | - | out: make(chan wire.Frame, 1), clock: newAttachPaletteClock(), | |
| 188 | + | out: make(chan protocol.ClientMessage, 1), clock: newAttachPaletteClock(), | |
| 186 | 189 | logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent), | |
| 187 | 190 | activeGeneration: &activeGeneration, | |
| 188 | 191 | }).run() | |
| @@ -192,7 +195,7 @@ func TestTerminalInputPumpCancellationHandoffPreservesQueuedBytes(t *testing.T) | |||
| 192 | 195 | ||
| 193 | 196 | replacementCtx, cancelReplacement := context.WithCancel(context.Background()) | |
| 194 | 197 | defer cancelReplacement() | |
| 195 | - | out := make(chan wire.Frame, len(want)) | |
| 198 | + | out := make(chan protocol.ClientMessage, len(want)) | |
| 196 | 199 | replacementDone := make(chan struct{}) | |
| 197 | 200 | go func() { | |
| 198 | 201 | (&stdinPump{ | |
| @@ -206,9 +209,9 @@ func TestTerminalInputPumpCancellationHandoffPreservesQueuedBytes(t *testing.T) | |||
| 206 | 209 | ||
| 207 | 210 | var got []byte | |
| 208 | 211 | for len(got) < len(want) { | |
| 209 | - | frame := <-out | |
| 210 | - | input, err := wire.UnmarshalInput(frame.Payload) | |
| 211 | - | require.NoError(t, err) | |
| 212 | + | message := <-out | |
| 213 | + | input, ok := message.(protocol.Input) | |
| 214 | + | require.True(t, ok) | |
| 212 | 215 | got = append(got, input.Data...) | |
| 213 | 216 | } | |
| 214 | 217 | require.Equal(t, want, got, "handoff must preserve every queued byte in order") | |
| @@ -230,7 +233,7 @@ func TestTerminalInputPumpCancellationAfterTakeRequeuesRawRead(t *testing.T) { | |||
| 230 | 233 | go func() { | |
| 231 | 234 | (&stdinPump{ | |
| 232 | 235 | ctx: ctx, cancel: cancel, input: input, consumer: consumer, | |
| 233 | - | out: make(chan wire.Frame, 1), clock: newAttachPaletteClock(), | |
| 236 | + | out: make(chan protocol.ClientMessage, 1), clock: newAttachPaletteClock(), | |
| 234 | 237 | logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent), | |
| 235 | 238 | afterInputTake: func() { | |
| 236 | 239 | close(taken) | |
| @@ -264,7 +267,7 @@ func TestTerminalInputPumpCancellationPreservesStandaloneEscapeForReplacement(t | |||
| 264 | 267 | oldDone := make(chan struct{}) | |
| 265 | 268 | go func() { | |
| 266 | 269 | (&stdinPump{ | |
| 267 | - | ctx: oldCtx, cancel: cancelOld, input: input, out: make(chan wire.Frame, 1), | |
| 270 | + | ctx: oldCtx, cancel: cancelOld, input: input, out: make(chan protocol.ClientMessage, 1), | |
| 268 | 271 | clock: oldClock, logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent), | |
| 269 | 272 | activeGeneration: &activeGeneration, | |
| 270 | 273 | }).run() | |
| @@ -279,7 +282,7 @@ func TestTerminalInputPumpCancellationPreservesStandaloneEscapeForReplacement(t | |||
| 279 | 282 | newCtx, cancelNew := context.WithCancel(context.Background()) | |
| 280 | 283 | defer cancelNew() | |
| 281 | 284 | newClock := newAttachPaletteClock() | |
| 282 | - | out := make(chan wire.Frame, 1) | |
| 285 | + | out := make(chan protocol.ClientMessage, 1) | |
| 283 | 286 | newDone := make(chan struct{}) | |
| 284 | 287 | go func() { | |
| 285 | 288 | (&stdinPump{ | |
| @@ -291,9 +294,9 @@ func TestTerminalInputPumpCancellationPreservesStandaloneEscapeForReplacement(t | |||
| 291 | 294 | }() | |
| 292 | 295 | (<-newClock.timers).fire() // marker ambiguity deadline | |
| 293 | 296 | (<-newClock.timers).fire() // paste coalescer's lone-ESC deadline | |
| 294 | - | frame := <-out | |
| 295 | - | got, err := wire.UnmarshalInput(frame.Payload) | |
| 296 | - | require.NoError(t, err) | |
| 297 | + | message := <-out | |
| 298 | + | got, ok := message.(protocol.Input) | |
| 299 | + | require.True(t, ok) | |
| 297 | 300 | require.Equal(t, []byte("\x1b"), got.Data) | |
| 298 | 301 | ||
| 299 | 302 | cancelNew() | |
| @@ -307,7 +310,7 @@ func TestTerminalInputPumpCancellationAfterInputBeforeOSCPreservesOnlyUndelivere | |||
| 307 | 310 | ctx, cancel := context.WithCancel(context.Background()) | |
| 308 | 311 | delivered := make(chan struct{}) | |
| 309 | 312 | done := make(chan struct{}) | |
| 310 | - | out := make(chan wire.Frame, 1) | |
| 313 | + | out := make(chan protocol.ClientMessage, 1) | |
| 311 | 314 | go func() { | |
| 312 | 315 | (&stdinPump{ | |
| 313 | 316 | ctx: ctx, cancel: cancel, input: input, out: out, clock: newAttachPaletteClock(), | |
| @@ -328,7 +331,7 @@ func TestTerminalInputPumpCancellationAfterInputBeforeOSCPreservesOnlyUndelivere | |||
| 328 | 331 | requireSignal(t, done, "timed out waiting for cancelled stdin pump") | |
| 329 | 332 | ||
| 330 | 333 | replacementCtx, cancelReplacement := context.WithCancel(context.Background()) | |
| 331 | - | replacementOut := make(chan wire.Frame, 1) | |
| 334 | + | replacementOut := make(chan protocol.ClientMessage, 1) | |
| 332 | 335 | replacementDone := make(chan struct{}) | |
| 333 | 336 | go func() { | |
| 334 | 337 | (&stdinPump{ | |
| @@ -355,7 +358,7 @@ func TestTerminalInputPumpCancellationPreservesCoalescerHeldSuffix(t *testing.T) | |||
| 355 | 358 | input.enqueue(terminalReadResult{data: raw}, 1) | |
| 356 | 359 | var activeGeneration atomic.Uint64 | |
| 357 | 360 | ctx, cancel := context.WithCancel(context.Background()) | |
| 358 | - | out := make(chan wire.Frame, 1) | |
| 361 | + | out := make(chan protocol.ClientMessage, 1) | |
| 359 | 362 | done := make(chan struct{}) | |
| 360 | 363 | go func() { | |
| 361 | 364 | (&stdinPump{ | |
| @@ -374,7 +377,7 @@ func TestTerminalInputPumpCancellationPreservesCoalescerHeldSuffix(t *testing.T) | |||
| 374 | 377 | ||
| 375 | 378 | replacementCtx, cancelReplacement := context.WithCancel(context.Background()) | |
| 376 | 379 | replacementClock := newAttachPaletteClock() | |
| 377 | - | replacementOut := make(chan wire.Frame, 1) | |
| 380 | + | replacementOut := make(chan protocol.ClientMessage, 1) | |
| 378 | 381 | replacementDone := make(chan struct{}) | |
| 379 | 382 | go func() { | |
| 380 | 383 | (&stdinPump{ | |
| @@ -425,7 +428,7 @@ func TestTerminalInputPumpCancellationAfterDeliveredCallbackDoesNotReplay(t *tes | |||
| 425 | 428 | ctx, cancel := context.WithCancel(context.Background()) | |
| 426 | 429 | delivered := make(chan struct{}) | |
| 427 | 430 | done := make(chan struct{}) | |
| 428 | - | out := make(chan wire.Frame, 1) | |
| 431 | + | out := make(chan protocol.ClientMessage, 1) | |
| 429 | 432 | go func() { | |
| 430 | 433 | (&stdinPump{ | |
| 431 | 434 | ctx: ctx, cancel: cancel, input: input, out: out, clock: newAttachPaletteClock(), | |
| @@ -438,15 +441,15 @@ func TestTerminalInputPumpCancellationAfterDeliveredCallbackDoesNotReplay(t *tes | |||
| 438 | 441 | }).run() | |
| 439 | 442 | close(done) | |
| 440 | 443 | }() | |
| 441 | - | frame := <-out | |
| 442 | - | got, err := wire.UnmarshalInput(frame.Payload) | |
| 443 | - | require.NoError(t, err) | |
| 444 | + | message := <-out | |
| 445 | + | got, ok := message.(protocol.Input) | |
| 446 | + | require.True(t, ok) | |
| 444 | 447 | require.Equal(t, []byte("x"), got.Data) | |
| 445 | 448 | <-delivered | |
| 446 | 449 | <-done | |
| 447 | 450 | ||
| 448 | 451 | consumer := input.claim() | |
| 449 | - | _, ok := input.take(context.Background(), consumer) | |
| 452 | + | _, ok = input.take(context.Background(), consumer) | |
| 450 | 453 | require.False(t, ok, "a callback already delivered to the sender must not replay after reconnect") | |
| 451 | 454 | input.revoke(consumer) | |
| 452 | 455 | } | |
| @@ -464,7 +467,7 @@ func TestTerminalInputPumpReusesOneReaderAcrossCancelledAttempts(t *testing.T) { | |||
| 464 | 467 | go func() { | |
| 465 | 468 | (&stdinPump{ | |
| 466 | 469 | ctx: firstCtx, cancel: cancelFirst, input: input, | |
| 467 | - | out: make(chan wire.Frame), clock: newAttachPaletteClock(), | |
| 470 | + | out: make(chan protocol.ClientMessage), clock: newAttachPaletteClock(), | |
| 468 | 471 | logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent), | |
| 469 | 472 | activeGeneration: &activeGeneration, | |
| 470 | 473 | }).run() | |
| @@ -475,7 +478,7 @@ func TestTerminalInputPumpReusesOneReaderAcrossCancelledAttempts(t *testing.T) { | |||
| 475 | 478 | ||
| 476 | 479 | secondCtx, cancelSecond := context.WithCancel(context.Background()) | |
| 477 | 480 | defer cancelSecond() | |
| 478 | - | out := make(chan wire.Frame, 1) | |
| 481 | + | out := make(chan protocol.ClientMessage, 1) | |
| 479 | 482 | secondDone := make(chan struct{}) | |
| 480 | 483 | go func() { | |
| 481 | 484 | (&stdinPump{ | |
| @@ -488,9 +491,9 @@ func TestTerminalInputPumpReusesOneReaderAcrossCancelledAttempts(t *testing.T) { | |||
| 488 | 491 | }() | |
| 489 | 492 | ||
| 490 | 493 | reader.chunks <- []byte("x") | |
| 491 | - | frame := <-out | |
| 492 | - | got, err := wire.UnmarshalInput(frame.Payload) | |
| 493 | - | require.NoError(t, err) | |
| 494 | + | message := <-out | |
| 495 | + | got, ok := message.(protocol.Input) | |
| 496 | + | require.True(t, ok) | |
| 494 | 497 | require.Equal(t, []byte("x"), got.Data) | |
| 495 | 498 | require.Equal(t, 1, reader.maxActive(), "cancelled attempts must not leave competing reads") | |
| 496 | 499 | ||
| @@ -13,6 +13,7 @@ import ( | |||||
| 13 | 13 | ||||
| 14 | "github.com/bnema/vev/internal/domain" | 14 | "github.com/bnema/vev/internal/domain" | ||
| 15 | "github.com/bnema/vev/internal/ports" | 15 | "github.com/bnema/vev/internal/ports" | ||
| 16 | + | "github.com/bnema/vev/internal/protocol" | |||
| 16 | "github.com/bnema/vev/internal/protocol/wire" | 17 | "github.com/bnema/vev/internal/protocol/wire" | ||
| 17 | ) | 18 | ) | ||
| 18 | 19 | ||||
| @@ -82,10 +83,12 @@ func requireSignal(t *testing.T, ch <-chan struct{}, message string) { | |||||
| 82 | } | 83 | } | ||
| 83 | } | 84 | } | ||
| 84 | 85 | ||||
| 85 | - | func requireFrame(t *testing.T, ch <-chan wire.Frame, message string) wire.Frame { | 86 | + | func requireFrame(t *testing.T, ch <-chan protocol.ClientMessage, message string) wire.Frame { |
| 86 | t.Helper() | 87 | t.Helper() | ||
| 87 | select { | 88 | select { | ||
| 88 | - | case frame := <-ch: | 89 | + | case message := <-ch: |
| 90 | + | frame, err := testClientFrame(message) | |||
| 91 | + | require.NoError(t, err) | |||
| 89 | return frame | 92 | return frame | ||
| 90 | case <-time.After(time.Second): | 93 | case <-time.After(time.Second): | ||
| 91 | t.Fatal(message) | 94 | t.Fatal(message) | ||
| @@ -182,7 +185,7 @@ func TestTerminalInputPumpCancellationHandoffPreservesQueuedBytes(t *testing.T) | |||||
| 182 | go func() { | 185 | go func() { | ||
| 183 | (&stdinPump{ | 186 | (&stdinPump{ | ||
| 184 | ctx: cancelledCtx, cancel: cancelCancelled, input: input, | 187 | ctx: cancelledCtx, cancel: cancelCancelled, input: input, | ||
| 185 | - | out: make(chan wire.Frame, 1), clock: newAttachPaletteClock(), | 188 | + | out: make(chan protocol.ClientMessage, 1), clock: newAttachPaletteClock(), |
| 186 | logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent), | 189 | logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent), | ||
| 187 | activeGeneration: &activeGeneration, | 190 | activeGeneration: &activeGeneration, | ||
| 188 | }).run() | 191 | }).run() | ||
| @@ -192,7 +195,7 @@ func TestTerminalInputPumpCancellationHandoffPreservesQueuedBytes(t *testing.T) | |||||
| 192 | 195 | ||||
| 193 | replacementCtx, cancelReplacement := context.WithCancel(context.Background()) | 196 | replacementCtx, cancelReplacement := context.WithCancel(context.Background()) | ||
| 194 | defer cancelReplacement() | 197 | defer cancelReplacement() | ||
| 195 | - | out := make(chan wire.Frame, len(want)) | 198 | + | out := make(chan protocol.ClientMessage, len(want)) |
| 196 | replacementDone := make(chan struct{}) | 199 | replacementDone := make(chan struct{}) | ||
| 197 | go func() { | 200 | go func() { | ||
| 198 | (&stdinPump{ | 201 | (&stdinPump{ | ||
| @@ -206,9 +209,9 @@ func TestTerminalInputPumpCancellationHandoffPreservesQueuedBytes(t *testing.T) | |||||
| 206 | 209 | ||||
| 207 | var got []byte | 210 | var got []byte | ||
| 208 | for len(got) < len(want) { | 211 | for len(got) < len(want) { | ||
| 209 | - | frame := <-out | 212 | + | message := <-out |
| 210 | - | input, err := wire.UnmarshalInput(frame.Payload) | 213 | + | input, ok := message.(protocol.Input) |
| 211 | - | require.NoError(t, err) | 214 | + | require.True(t, ok) |
| 212 | got = append(got, input.Data...) | 215 | got = append(got, input.Data...) | ||
| 213 | } | 216 | } | ||
| 214 | require.Equal(t, want, got, "handoff must preserve every queued byte in order") | 217 | require.Equal(t, want, got, "handoff must preserve every queued byte in order") | ||
| @@ -230,7 +233,7 @@ func TestTerminalInputPumpCancellationAfterTakeRequeuesRawRead(t *testing.T) { | |||||
| 230 | go func() { | 233 | go func() { | ||
| 231 | (&stdinPump{ | 234 | (&stdinPump{ | ||
| 232 | ctx: ctx, cancel: cancel, input: input, consumer: consumer, | 235 | ctx: ctx, cancel: cancel, input: input, consumer: consumer, | ||
| 233 | - | out: make(chan wire.Frame, 1), clock: newAttachPaletteClock(), | 236 | + | out: make(chan protocol.ClientMessage, 1), clock: newAttachPaletteClock(), |
| 234 | logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent), | 237 | logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent), | ||
| 235 | afterInputTake: func() { | 238 | afterInputTake: func() { | ||
| 236 | close(taken) | 239 | close(taken) | ||
| @@ -264,7 +267,7 @@ func TestTerminalInputPumpCancellationPreservesStandaloneEscapeForReplacement(t | |||||
| 264 | oldDone := make(chan struct{}) | 267 | oldDone := make(chan struct{}) | ||
| 265 | go func() { | 268 | go func() { | ||
| 266 | (&stdinPump{ | 269 | (&stdinPump{ | ||
| 267 | - | ctx: oldCtx, cancel: cancelOld, input: input, out: make(chan wire.Frame, 1), | 270 | + | ctx: oldCtx, cancel: cancelOld, input: input, out: make(chan protocol.ClientMessage, 1), |
| 268 | clock: oldClock, logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent), | 271 | clock: oldClock, logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent), | ||
| 269 | activeGeneration: &activeGeneration, | 272 | activeGeneration: &activeGeneration, | ||
| 270 | }).run() | 273 | }).run() | ||
| @@ -279,7 +282,7 @@ func TestTerminalInputPumpCancellationPreservesStandaloneEscapeForReplacement(t | |||||
| 279 | newCtx, cancelNew := context.WithCancel(context.Background()) | 282 | newCtx, cancelNew := context.WithCancel(context.Background()) | ||
| 280 | defer cancelNew() | 283 | defer cancelNew() | ||
| 281 | newClock := newAttachPaletteClock() | 284 | newClock := newAttachPaletteClock() | ||
| 282 | - | out := make(chan wire.Frame, 1) | 285 | + | out := make(chan protocol.ClientMessage, 1) |
| 283 | newDone := make(chan struct{}) | 286 | newDone := make(chan struct{}) | ||
| 284 | go func() { | 287 | go func() { | ||
| 285 | (&stdinPump{ | 288 | (&stdinPump{ | ||
| @@ -291,9 +294,9 @@ func TestTerminalInputPumpCancellationPreservesStandaloneEscapeForReplacement(t | |||||
| 291 | }() | 294 | }() | ||
| 292 | (<-newClock.timers).fire() // marker ambiguity deadline | 295 | (<-newClock.timers).fire() // marker ambiguity deadline | ||
| 293 | (<-newClock.timers).fire() // paste coalescer's lone-ESC deadline | 296 | (<-newClock.timers).fire() // paste coalescer's lone-ESC deadline | ||
| 294 | - | frame := <-out | 297 | + | message := <-out |
| 295 | - | got, err := wire.UnmarshalInput(frame.Payload) | 298 | + | got, ok := message.(protocol.Input) |
| 296 | - | require.NoError(t, err) | 299 | + | require.True(t, ok) |
| 297 | require.Equal(t, []byte("\x1b"), got.Data) | 300 | require.Equal(t, []byte("\x1b"), got.Data) | ||
| 298 | 301 | ||||
| 299 | cancelNew() | 302 | cancelNew() | ||
| @@ -307,7 +310,7 @@ func TestTerminalInputPumpCancellationAfterInputBeforeOSCPreservesOnlyUndelivere | |||||
| 307 | ctx, cancel := context.WithCancel(context.Background()) | 310 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 308 | delivered := make(chan struct{}) | 311 | delivered := make(chan struct{}) | ||
| 309 | done := make(chan struct{}) | 312 | done := make(chan struct{}) | ||
| 310 | - | out := make(chan wire.Frame, 1) | 313 | + | out := make(chan protocol.ClientMessage, 1) |
| 311 | go func() { | 314 | go func() { | ||
| 312 | (&stdinPump{ | 315 | (&stdinPump{ | ||
| 313 | ctx: ctx, cancel: cancel, input: input, out: out, clock: newAttachPaletteClock(), | 316 | ctx: ctx, cancel: cancel, input: input, out: out, clock: newAttachPaletteClock(), | ||
| @@ -328,7 +331,7 @@ func TestTerminalInputPumpCancellationAfterInputBeforeOSCPreservesOnlyUndelivere | |||||
| 328 | requireSignal(t, done, "timed out waiting for cancelled stdin pump") | 331 | requireSignal(t, done, "timed out waiting for cancelled stdin pump") | ||
| 329 | 332 | ||||
| 330 | replacementCtx, cancelReplacement := context.WithCancel(context.Background()) | 333 | replacementCtx, cancelReplacement := context.WithCancel(context.Background()) | ||
| 331 | - | replacementOut := make(chan wire.Frame, 1) | 334 | + | replacementOut := make(chan protocol.ClientMessage, 1) |
| 332 | replacementDone := make(chan struct{}) | 335 | replacementDone := make(chan struct{}) | ||
| 333 | go func() { | 336 | go func() { | ||
| 334 | (&stdinPump{ | 337 | (&stdinPump{ | ||
| @@ -355,7 +358,7 @@ func TestTerminalInputPumpCancellationPreservesCoalescerHeldSuffix(t *testing.T) | |||||
| 355 | input.enqueue(terminalReadResult{data: raw}, 1) | 358 | input.enqueue(terminalReadResult{data: raw}, 1) | ||
| 356 | var activeGeneration atomic.Uint64 | 359 | var activeGeneration atomic.Uint64 | ||
| 357 | ctx, cancel := context.WithCancel(context.Background()) | 360 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 358 | - | out := make(chan wire.Frame, 1) | 361 | + | out := make(chan protocol.ClientMessage, 1) |
| 359 | done := make(chan struct{}) | 362 | done := make(chan struct{}) | ||
| 360 | go func() { | 363 | go func() { | ||
| 361 | (&stdinPump{ | 364 | (&stdinPump{ | ||
| @@ -374,7 +377,7 @@ func TestTerminalInputPumpCancellationPreservesCoalescerHeldSuffix(t *testing.T) | |||||
| 374 | 377 | ||||
| 375 | replacementCtx, cancelReplacement := context.WithCancel(context.Background()) | 378 | replacementCtx, cancelReplacement := context.WithCancel(context.Background()) | ||
| 376 | replacementClock := newAttachPaletteClock() | 379 | replacementClock := newAttachPaletteClock() | ||
| 377 | - | replacementOut := make(chan wire.Frame, 1) | 380 | + | replacementOut := make(chan protocol.ClientMessage, 1) |
| 378 | replacementDone := make(chan struct{}) | 381 | replacementDone := make(chan struct{}) | ||
| 379 | go func() { | 382 | go func() { | ||
| 380 | (&stdinPump{ | 383 | (&stdinPump{ | ||
| @@ -425,7 +428,7 @@ func TestTerminalInputPumpCancellationAfterDeliveredCallbackDoesNotReplay(t *tes | |||||
| 425 | ctx, cancel := context.WithCancel(context.Background()) | 428 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 426 | delivered := make(chan struct{}) | 429 | delivered := make(chan struct{}) | ||
| 427 | done := make(chan struct{}) | 430 | done := make(chan struct{}) | ||
| 428 | - | out := make(chan wire.Frame, 1) | 431 | + | out := make(chan protocol.ClientMessage, 1) |
| 429 | go func() { | 432 | go func() { | ||
| 430 | (&stdinPump{ | 433 | (&stdinPump{ | ||
| 431 | ctx: ctx, cancel: cancel, input: input, out: out, clock: newAttachPaletteClock(), | 434 | ctx: ctx, cancel: cancel, input: input, out: out, clock: newAttachPaletteClock(), | ||
| @@ -438,15 +441,15 @@ func TestTerminalInputPumpCancellationAfterDeliveredCallbackDoesNotReplay(t *tes | |||||
| 438 | }).run() | 441 | }).run() | ||
| 439 | close(done) | 442 | close(done) | ||
| 440 | }() | 443 | }() | ||
| 441 | - | frame := <-out | 444 | + | message := <-out |
| 442 | - | got, err := wire.UnmarshalInput(frame.Payload) | 445 | + | got, ok := message.(protocol.Input) |
| 443 | - | require.NoError(t, err) | 446 | + | require.True(t, ok) |
| 444 | require.Equal(t, []byte("x"), got.Data) | 447 | require.Equal(t, []byte("x"), got.Data) | ||
| 445 | <-delivered | 448 | <-delivered | ||
| 446 | <-done | 449 | <-done | ||
| 447 | 450 | ||||
| 448 | consumer := input.claim() | 451 | consumer := input.claim() | ||
| 449 | - | _, ok := input.take(context.Background(), consumer) | 452 | + | _, ok = input.take(context.Background(), consumer) |
| 450 | require.False(t, ok, "a callback already delivered to the sender must not replay after reconnect") | 453 | require.False(t, ok, "a callback already delivered to the sender must not replay after reconnect") | ||
| 451 | input.revoke(consumer) | 454 | input.revoke(consumer) | ||
| 452 | } | 455 | } | ||
| @@ -464,7 +467,7 @@ func TestTerminalInputPumpReusesOneReaderAcrossCancelledAttempts(t *testing.T) { | |||||
| 464 | go func() { | 467 | go func() { | ||
| 465 | (&stdinPump{ | 468 | (&stdinPump{ | ||
| 466 | ctx: firstCtx, cancel: cancelFirst, input: input, | 469 | ctx: firstCtx, cancel: cancelFirst, input: input, | ||
| 467 | - | out: make(chan wire.Frame), clock: newAttachPaletteClock(), | 470 | + | out: make(chan protocol.ClientMessage), clock: newAttachPaletteClock(), |
| 468 | logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent), | 471 | logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent), | ||
| 469 | activeGeneration: &activeGeneration, | 472 | activeGeneration: &activeGeneration, | ||
| 470 | }).run() | 473 | }).run() | ||
| @@ -475,7 +478,7 @@ func TestTerminalInputPumpReusesOneReaderAcrossCancelledAttempts(t *testing.T) { | |||||
| 475 | 478 | ||||
| 476 | secondCtx, cancelSecond := context.WithCancel(context.Background()) | 479 | secondCtx, cancelSecond := context.WithCancel(context.Background()) | ||
| 477 | defer cancelSecond() | 480 | defer cancelSecond() | ||
| 478 | - | out := make(chan wire.Frame, 1) | 481 | + | out := make(chan protocol.ClientMessage, 1) |
| 479 | secondDone := make(chan struct{}) | 482 | secondDone := make(chan struct{}) | ||
| 480 | go func() { | 483 | go func() { | ||
| 481 | (&stdinPump{ | 484 | (&stdinPump{ | ||
| @@ -488,9 +491,9 @@ func TestTerminalInputPumpReusesOneReaderAcrossCancelledAttempts(t *testing.T) { | |||||
| 488 | }() | 491 | }() | ||
| 489 | 492 | ||||
| 490 | reader.chunks <- []byte("x") | 493 | reader.chunks <- []byte("x") | ||
| 491 | - | frame := <-out | 494 | + | message := <-out |
| 492 | - | got, err := wire.UnmarshalInput(frame.Payload) | 495 | + | got, ok := message.(protocol.Input) |
| 493 | - | require.NoError(t, err) | 496 | + | require.True(t, ok) |
| 494 | require.Equal(t, []byte("x"), got.Data) | 497 | require.Equal(t, []byte("x"), got.Data) | ||
| 495 | require.Equal(t, 1, reader.maxActive(), "cancelled attempts must not leave competing reads") | 498 | require.Equal(t, 1, reader.maxActive(), "cancelled attempts must not leave competing reads") | ||
| 496 | 499 | ||||
M internal/usecase/client/transport_observability_test.go +2 -2
| @@ -46,7 +46,7 @@ func TestBlockingRuntimeObserverDoesNotDelayTerminalFlushOrACK(t *testing.T) { | |||
| 46 | 46 | reporter := ports.NewSerializedRuntimeObserver(observer, 1) | |
| 47 | 47 | defer reporter.Close() | |
| 48 | 48 | acked := make(chan struct{}) | |
| 49 | - | transport := portsmocks.NewMockTransport(t) | |
| 49 | + | transport := newMockClientConnection(t) | |
| 50 | 50 | transport.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| 51 | 51 | transport.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 52 | 52 | transport.EXPECT().Send(isType(wire.MsgAck)).Run(func(wire.Frame) { close(acked) }).Return(nil).Once() | |
| @@ -87,7 +87,7 @@ func TestTerminalFlushBoundaryTransportObservability(t *testing.T) { | |||
| 87 | 87 | observer := &clientRuntimeObserver{} | |
| 88 | 88 | reporter := ports.NewSerializedRuntimeObserver(observer, 64) | |
| 89 | 89 | defer reporter.Close() | |
| 90 | - | transport := portsmocks.NewMockTransport(t) | |
| 90 | + | transport := newMockClientConnection(t) | |
| 91 | 91 | transport.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | |
| 92 | 92 | transport.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | |
| 93 | 93 | unblock := scriptRecv(transport, | |
| @@ -46,7 +46,7 @@ func TestBlockingRuntimeObserverDoesNotDelayTerminalFlushOrACK(t *testing.T) { | |||||
| 46 | reporter := ports.NewSerializedRuntimeObserver(observer, 1) | 46 | reporter := ports.NewSerializedRuntimeObserver(observer, 1) | ||
| 47 | defer reporter.Close() | 47 | defer reporter.Close() | ||
| 48 | acked := make(chan struct{}) | 48 | acked := make(chan struct{}) | ||
| 49 | - | transport := portsmocks.NewMockTransport(t) | 49 | + | transport := newMockClientConnection(t) |
| 50 | transport.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 50 | transport.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| 51 | transport.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 51 | transport.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 52 | transport.EXPECT().Send(isType(wire.MsgAck)).Run(func(wire.Frame) { close(acked) }).Return(nil).Once() | 52 | transport.EXPECT().Send(isType(wire.MsgAck)).Run(func(wire.Frame) { close(acked) }).Return(nil).Once() | ||
| @@ -87,7 +87,7 @@ func TestTerminalFlushBoundaryTransportObservability(t *testing.T) { | |||||
| 87 | observer := &clientRuntimeObserver{} | 87 | observer := &clientRuntimeObserver{} | ||
| 88 | reporter := ports.NewSerializedRuntimeObserver(observer, 64) | 88 | reporter := ports.NewSerializedRuntimeObserver(observer, 64) | ||
| 89 | defer reporter.Close() | 89 | defer reporter.Close() | ||
| 90 | - | transport := portsmocks.NewMockTransport(t) | 90 | + | transport := newMockClientConnection(t) |
| 91 | transport.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | 91 | transport.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once() | ||
| 92 | transport.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | 92 | transport.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe() | ||
| 93 | unblock := scriptRecv(transport, | 93 | unblock := scriptRecv(transport, | ||
A internal/usecase/client/typed_connection_external_test.go +237 -0
| @@ -0,0 +1,237 @@ | |||
| 1 | + | package client_test | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "errors" | |
| 6 | + | ||
| 7 | + | "github.com/stretchr/testify/mock" | |
| 8 | + | ||
| 9 | + | "github.com/bnema/vev/internal/ports" | |
| 10 | + | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |
| 11 | + | "github.com/bnema/vev/internal/protocol" | |
| 12 | + | "github.com/bnema/vev/internal/protocol/wire" | |
| 13 | + | ) | |
| 14 | + | ||
| 15 | + | type mockTestingT interface { | |
| 16 | + | mock.TestingT | |
| 17 | + | Cleanup(func()) | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | type mockClientConnection struct{ *portsmocks.MockTransport } | |
| 21 | + | ||
| 22 | + | func newMockClientConnection(t mockTestingT) *mockClientConnection { | |
| 23 | + | return &mockClientConnection{MockTransport: portsmocks.NewMockTransport(t)} | |
| 24 | + | } | |
| 25 | + | func (c *mockClientConnection) SendClient(m protocol.ClientMessage) error { | |
| 26 | + | f, e := externalClientFrame(m) | |
| 27 | + | if e != nil { | |
| 28 | + | return e | |
| 29 | + | } | |
| 30 | + | return c.Send(f) | |
| 31 | + | } | |
| 32 | + | func (c *mockClientConnection) ReceiveServer() (protocol.ServerMessage, error) { | |
| 33 | + | f, e := c.Recv() | |
| 34 | + | if e != nil { | |
| 35 | + | return nil, e | |
| 36 | + | } | |
| 37 | + | return externalServerMessage(f) | |
| 38 | + | } | |
| 39 | + | func (c *mockClientConnection) Capabilities() protocol.ConnectionCapabilities { | |
| 40 | + | return externalCapabilities(c.MockTransport) | |
| 41 | + | } | |
| 42 | + | func (c *mockClientConnection) LinkState() ports.LinkState { return ports.LinkStateConnected } | |
| 43 | + | func (c *mockClientConnection) LinkEvents() <-chan ports.LinkEvent { return nil } | |
| 44 | + | ||
| 45 | + | type rawClientConnection struct{ raw ports.Transport } | |
| 46 | + | ||
| 47 | + | func (c *rawClientConnection) SendClient(m protocol.ClientMessage) error { | |
| 48 | + | f, e := externalClientFrame(m) | |
| 49 | + | if e != nil { | |
| 50 | + | return e | |
| 51 | + | } | |
| 52 | + | return c.raw.Send(f) | |
| 53 | + | } | |
| 54 | + | func (c *rawClientConnection) ReceiveServer() (protocol.ServerMessage, error) { | |
| 55 | + | f, e := c.raw.Recv() | |
| 56 | + | if e != nil { | |
| 57 | + | return nil, e | |
| 58 | + | } | |
| 59 | + | return externalServerMessage(f) | |
| 60 | + | } | |
| 61 | + | func (c *rawClientConnection) Capabilities() protocol.ConnectionCapabilities { | |
| 62 | + | return externalCapabilities(c.raw) | |
| 63 | + | } | |
| 64 | + | func (c *rawClientConnection) LinkState() ports.LinkState { | |
| 65 | + | if r, ok := c.raw.(ports.LinkStateReporter); ok { | |
| 66 | + | return r.LinkState() | |
| 67 | + | } | |
| 68 | + | return ports.LinkStateConnected | |
| 69 | + | } | |
| 70 | + | func (c *rawClientConnection) LinkEvents() <-chan ports.LinkEvent { | |
| 71 | + | if r, ok := c.raw.(ports.LinkStateReporter); ok { | |
| 72 | + | return r.LinkEvents() | |
| 73 | + | } | |
| 74 | + | return nil | |
| 75 | + | } | |
| 76 | + | func (c *rawClientConnection) Close() error { return c.raw.Close() } | |
| 77 | + | ||
| 78 | + | type mockClientDialer struct{ *portsmocks.MockDialer } | |
| 79 | + | ||
| 80 | + | func newMockClientDialer(t mockTestingT) *mockClientDialer { | |
| 81 | + | return &mockClientDialer{MockDialer: portsmocks.NewMockDialer(t)} | |
| 82 | + | } | |
| 83 | + | func (d *mockClientDialer) Dial(ctx context.Context) (ports.ClientConnection, error) { | |
| 84 | + | raw, e := d.MockDialer.Dial(ctx) | |
| 85 | + | if e != nil { | |
| 86 | + | return nil, e | |
| 87 | + | } | |
| 88 | + | return &rawClientConnection{raw: raw}, nil | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | func externalCapabilities(raw ports.Transport) protocol.ConnectionCapabilities { | |
| 92 | + | _, dgram := raw.(ports.DatagramTransport) | |
| 93 | + | _, link := raw.(ports.LinkStateReporter) | |
| 94 | + | window := uint8(protocol.MaxOutputWindow) | |
| 95 | + | if dgram { | |
| 96 | + | window = 1 | |
| 97 | + | } | |
| 98 | + | return protocol.ConnectionCapabilities{ | |
| 99 | + | OutputDataLimit: protocol.MaxOutputDataLen, | |
| 100 | + | PreferredOutputWindow: window, | |
| 101 | + | LinkState: link, | |
| 102 | + | } | |
| 103 | + | } | |
| 104 | + | func externalClientFrame(m protocol.ClientMessage) (wire.Frame, error) { | |
| 105 | + | switch x := m.(type) { | |
| 106 | + | case protocol.Hello: | |
| 107 | + | return wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(x)}, nil | |
| 108 | + | case protocol.Input: | |
| 109 | + | return wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(x)}, nil | |
| 110 | + | case protocol.Resize: | |
| 111 | + | p, e := wire.MarshalResize(x) | |
| 112 | + | return wire.Frame{Type: wire.MsgResize, Payload: p}, e | |
| 113 | + | case protocol.Detach: | |
| 114 | + | return wire.Frame{Type: wire.MsgDetach}, nil | |
| 115 | + | case protocol.Theme: | |
| 116 | + | return wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(x)}, nil | |
| 117 | + | case protocol.Ack: | |
| 118 | + | p, e := wire.MarshalAck(x) | |
| 119 | + | return wire.Frame{Type: wire.MsgAck, Payload: p}, e | |
| 120 | + | case protocol.ImagePush: | |
| 121 | + | return wire.Frame{Type: wire.MsgImagePush, Payload: wire.MarshalImagePush(x)}, nil | |
| 122 | + | case protocol.ClientNotice: | |
| 123 | + | return wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(x)}, nil | |
| 124 | + | case protocol.OutputResetRequest: | |
| 125 | + | return wire.Frame{Type: wire.MsgOutputResetRequest}, nil | |
| 126 | + | case protocol.RecentRouteSnapshot: | |
| 127 | + | p, e := wire.MarshalRecentRouteSnapshot(x) | |
| 128 | + | return wire.Frame{Type: wire.MsgRecentRouteSnapshot, Payload: p}, e | |
| 129 | + | case protocol.RouteAttentionSubscription: | |
| 130 | + | p, e := wire.MarshalRouteAttentionSubscription(x) | |
| 131 | + | return wire.Frame{Type: wire.MsgRouteAttentionSubscription, Payload: p}, e | |
| 132 | + | case protocol.SamePeerSwitchRequest: | |
| 133 | + | p, e := wire.MarshalSamePeerSwitchRequest(x) | |
| 134 | + | return wire.Frame{Type: wire.MsgSamePeerSwitchRequest, Payload: p}, e | |
| 135 | + | case protocol.ParkedRouteRequest: | |
| 136 | + | return wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: wire.MarshalParkedRouteRequest(x)}, nil | |
| 137 | + | case protocol.RouteNavigationFailure: | |
| 138 | + | p, e := wire.MarshalRouteNavigationFailure(x) | |
| 139 | + | return wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: p}, e | |
| 140 | + | default: | |
| 141 | + | return wire.Frame{}, errors.New("test client: unsupported message") | |
| 142 | + | } | |
| 143 | + | } | |
| 144 | + | func externalServerMessage(f wire.Frame) (protocol.ServerMessage, error) { | |
| 145 | + | switch f.Type { | |
| 146 | + | case wire.MsgWelcome: | |
| 147 | + | return wire.UnmarshalWelcome(f.Payload) | |
| 148 | + | case wire.MsgError: | |
| 149 | + | return wire.UnmarshalErrorMsg(f.Payload) | |
| 150 | + | case wire.MsgOutput: | |
| 151 | + | return wire.UnmarshalOutput(f.Payload) | |
| 152 | + | case wire.MsgDetached: | |
| 153 | + | return wire.UnmarshalDetached(f.Payload) | |
| 154 | + | case wire.MsgPong: | |
| 155 | + | return wire.UnmarshalPong(f.Payload) | |
| 156 | + | case wire.MsgAttachTarget: | |
| 157 | + | return wire.UnmarshalAttachTarget(f.Payload) | |
| 158 | + | case wire.MsgNavigationAction: | |
| 159 | + | return wire.UnmarshalNavigationDirective(f.Payload) | |
| 160 | + | case wire.MsgParkedRouteResponse: | |
| 161 | + | return wire.UnmarshalParkedRouteResponse(f.Payload) | |
| 162 | + | case wire.MsgNavigateRecentRoute: | |
| 163 | + | return wire.UnmarshalRouteNavigationAction(f.Payload) | |
| 164 | + | case wire.MsgCommittedRouteIdentity: | |
| 165 | + | return wire.UnmarshalCommittedRouteIdentity(f.Payload) | |
| 166 | + | case wire.MsgSamePeerSwitchFailure: | |
| 167 | + | return wire.UnmarshalSamePeerSwitchFailure(f.Payload) | |
| 168 | + | case wire.MsgRoutePosition: | |
| 169 | + | return wire.UnmarshalRoutePosition(f.Payload) | |
| 170 | + | case wire.MsgRouteNavigationFailure: | |
| 171 | + | return wire.UnmarshalRouteNavigationFailure(f.Payload) | |
| 172 | + | default: | |
| 173 | + | return nil, errors.New("test client: unsupported server frame") | |
| 174 | + | } | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | func (t *markedDatagramTransport) SendClient(m protocol.ClientMessage) error { | |
| 178 | + | f, e := externalClientFrame(m) | |
| 179 | + | if e != nil { | |
| 180 | + | return e | |
| 181 | + | } | |
| 182 | + | return t.Send(f) | |
| 183 | + | } | |
| 184 | + | func (t *markedDatagramTransport) ReceiveServer() (protocol.ServerMessage, error) { | |
| 185 | + | f, e := t.Recv() | |
| 186 | + | if e != nil { | |
| 187 | + | return nil, e | |
| 188 | + | } | |
| 189 | + | return externalServerMessage(f) | |
| 190 | + | } | |
| 191 | + | func (t *markedDatagramTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 192 | + | return externalCapabilities(t) | |
| 193 | + | } | |
| 194 | + | func (t *markedDatagramTransport) LinkState() ports.LinkState { return ports.LinkStateConnected } | |
| 195 | + | func (t *markedDatagramTransport) LinkEvents() <-chan ports.LinkEvent { return nil } | |
| 196 | + | ||
| 197 | + | func (t *recordingTransport) SendClient(m protocol.ClientMessage) error { | |
| 198 | + | f, e := externalClientFrame(m) | |
| 199 | + | if e != nil { | |
| 200 | + | return e | |
| 201 | + | } | |
| 202 | + | return t.Send(f) | |
| 203 | + | } | |
| 204 | + | func (t *recordingTransport) ReceiveServer() (protocol.ServerMessage, error) { | |
| 205 | + | f, e := t.Recv() | |
| 206 | + | if e != nil { | |
| 207 | + | return nil, e | |
| 208 | + | } | |
| 209 | + | return externalServerMessage(f) | |
| 210 | + | } | |
| 211 | + | func (t *recordingTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 212 | + | return externalCapabilities(t) | |
| 213 | + | } | |
| 214 | + | func (t *recordingTransport) LinkState() ports.LinkState { return ports.LinkStateConnected } | |
| 215 | + | func (t *recordingTransport) LinkEvents() <-chan ports.LinkEvent { return nil } | |
| 216 | + | ||
| 217 | + | func (t *clipboardToastLifecycleTransport) SendClient(m protocol.ClientMessage) error { | |
| 218 | + | f, e := externalClientFrame(m) | |
| 219 | + | if e != nil { | |
| 220 | + | return e | |
| 221 | + | } | |
| 222 | + | return t.Send(f) | |
| 223 | + | } | |
| 224 | + | func (t *clipboardToastLifecycleTransport) ReceiveServer() (protocol.ServerMessage, error) { | |
| 225 | + | f, e := t.Recv() | |
| 226 | + | if e != nil { | |
| 227 | + | return nil, e | |
| 228 | + | } | |
| 229 | + | return externalServerMessage(f) | |
| 230 | + | } | |
| 231 | + | func (t *clipboardToastLifecycleTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 232 | + | return externalCapabilities(t) | |
| 233 | + | } | |
| 234 | + | func (t *clipboardToastLifecycleTransport) LinkState() ports.LinkState { | |
| 235 | + | return ports.LinkStateConnected | |
| 236 | + | } | |
| 237 | + | func (t *clipboardToastLifecycleTransport) LinkEvents() <-chan ports.LinkEvent { return nil } | |
| @@ -0,0 +1,237 @@ | |||||
| 1 | + | package client_test | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "errors" | |||
| 6 | + | ||||
| 7 | + | "github.com/stretchr/testify/mock" | |||
| 8 | + | ||||
| 9 | + | "github.com/bnema/vev/internal/ports" | |||
| 10 | + | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |||
| 11 | + | "github.com/bnema/vev/internal/protocol" | |||
| 12 | + | "github.com/bnema/vev/internal/protocol/wire" | |||
| 13 | + | ) | |||
| 14 | + | ||||
| 15 | + | type mockTestingT interface { | |||
| 16 | + | mock.TestingT | |||
| 17 | + | Cleanup(func()) | |||
| 18 | + | } | |||
| 19 | + | ||||
| 20 | + | type mockClientConnection struct{ *portsmocks.MockTransport } | |||
| 21 | + | ||||
| 22 | + | func newMockClientConnection(t mockTestingT) *mockClientConnection { | |||
| 23 | + | return &mockClientConnection{MockTransport: portsmocks.NewMockTransport(t)} | |||
| 24 | + | } | |||
| 25 | + | func (c *mockClientConnection) SendClient(m protocol.ClientMessage) error { | |||
| 26 | + | f, e := externalClientFrame(m) | |||
| 27 | + | if e != nil { | |||
| 28 | + | return e | |||
| 29 | + | } | |||
| 30 | + | return c.Send(f) | |||
| 31 | + | } | |||
| 32 | + | func (c *mockClientConnection) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 33 | + | f, e := c.Recv() | |||
| 34 | + | if e != nil { | |||
| 35 | + | return nil, e | |||
| 36 | + | } | |||
| 37 | + | return externalServerMessage(f) | |||
| 38 | + | } | |||
| 39 | + | func (c *mockClientConnection) Capabilities() protocol.ConnectionCapabilities { | |||
| 40 | + | return externalCapabilities(c.MockTransport) | |||
| 41 | + | } | |||
| 42 | + | func (c *mockClientConnection) LinkState() ports.LinkState { return ports.LinkStateConnected } | |||
| 43 | + | func (c *mockClientConnection) LinkEvents() <-chan ports.LinkEvent { return nil } | |||
| 44 | + | ||||
| 45 | + | type rawClientConnection struct{ raw ports.Transport } | |||
| 46 | + | ||||
| 47 | + | func (c *rawClientConnection) SendClient(m protocol.ClientMessage) error { | |||
| 48 | + | f, e := externalClientFrame(m) | |||
| 49 | + | if e != nil { | |||
| 50 | + | return e | |||
| 51 | + | } | |||
| 52 | + | return c.raw.Send(f) | |||
| 53 | + | } | |||
| 54 | + | func (c *rawClientConnection) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 55 | + | f, e := c.raw.Recv() | |||
| 56 | + | if e != nil { | |||
| 57 | + | return nil, e | |||
| 58 | + | } | |||
| 59 | + | return externalServerMessage(f) | |||
| 60 | + | } | |||
| 61 | + | func (c *rawClientConnection) Capabilities() protocol.ConnectionCapabilities { | |||
| 62 | + | return externalCapabilities(c.raw) | |||
| 63 | + | } | |||
| 64 | + | func (c *rawClientConnection) LinkState() ports.LinkState { | |||
| 65 | + | if r, ok := c.raw.(ports.LinkStateReporter); ok { | |||
| 66 | + | return r.LinkState() | |||
| 67 | + | } | |||
| 68 | + | return ports.LinkStateConnected | |||
| 69 | + | } | |||
| 70 | + | func (c *rawClientConnection) LinkEvents() <-chan ports.LinkEvent { | |||
| 71 | + | if r, ok := c.raw.(ports.LinkStateReporter); ok { | |||
| 72 | + | return r.LinkEvents() | |||
| 73 | + | } | |||
| 74 | + | return nil | |||
| 75 | + | } | |||
| 76 | + | func (c *rawClientConnection) Close() error { return c.raw.Close() } | |||
| 77 | + | ||||
| 78 | + | type mockClientDialer struct{ *portsmocks.MockDialer } | |||
| 79 | + | ||||
| 80 | + | func newMockClientDialer(t mockTestingT) *mockClientDialer { | |||
| 81 | + | return &mockClientDialer{MockDialer: portsmocks.NewMockDialer(t)} | |||
| 82 | + | } | |||
| 83 | + | func (d *mockClientDialer) Dial(ctx context.Context) (ports.ClientConnection, error) { | |||
| 84 | + | raw, e := d.MockDialer.Dial(ctx) | |||
| 85 | + | if e != nil { | |||
| 86 | + | return nil, e | |||
| 87 | + | } | |||
| 88 | + | return &rawClientConnection{raw: raw}, nil | |||
| 89 | + | } | |||
| 90 | + | ||||
| 91 | + | func externalCapabilities(raw ports.Transport) protocol.ConnectionCapabilities { | |||
| 92 | + | _, dgram := raw.(ports.DatagramTransport) | |||
| 93 | + | _, link := raw.(ports.LinkStateReporter) | |||
| 94 | + | window := uint8(protocol.MaxOutputWindow) | |||
| 95 | + | if dgram { | |||
| 96 | + | window = 1 | |||
| 97 | + | } | |||
| 98 | + | return protocol.ConnectionCapabilities{ | |||
| 99 | + | OutputDataLimit: protocol.MaxOutputDataLen, | |||
| 100 | + | PreferredOutputWindow: window, | |||
| 101 | + | LinkState: link, | |||
| 102 | + | } | |||
| 103 | + | } | |||
| 104 | + | func externalClientFrame(m protocol.ClientMessage) (wire.Frame, error) { | |||
| 105 | + | switch x := m.(type) { | |||
| 106 | + | case protocol.Hello: | |||
| 107 | + | return wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(x)}, nil | |||
| 108 | + | case protocol.Input: | |||
| 109 | + | return wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(x)}, nil | |||
| 110 | + | case protocol.Resize: | |||
| 111 | + | p, e := wire.MarshalResize(x) | |||
| 112 | + | return wire.Frame{Type: wire.MsgResize, Payload: p}, e | |||
| 113 | + | case protocol.Detach: | |||
| 114 | + | return wire.Frame{Type: wire.MsgDetach}, nil | |||
| 115 | + | case protocol.Theme: | |||
| 116 | + | return wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(x)}, nil | |||
| 117 | + | case protocol.Ack: | |||
| 118 | + | p, e := wire.MarshalAck(x) | |||
| 119 | + | return wire.Frame{Type: wire.MsgAck, Payload: p}, e | |||
| 120 | + | case protocol.ImagePush: | |||
| 121 | + | return wire.Frame{Type: wire.MsgImagePush, Payload: wire.MarshalImagePush(x)}, nil | |||
| 122 | + | case protocol.ClientNotice: | |||
| 123 | + | return wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(x)}, nil | |||
| 124 | + | case protocol.OutputResetRequest: | |||
| 125 | + | return wire.Frame{Type: wire.MsgOutputResetRequest}, nil | |||
| 126 | + | case protocol.RecentRouteSnapshot: | |||
| 127 | + | p, e := wire.MarshalRecentRouteSnapshot(x) | |||
| 128 | + | return wire.Frame{Type: wire.MsgRecentRouteSnapshot, Payload: p}, e | |||
| 129 | + | case protocol.RouteAttentionSubscription: | |||
| 130 | + | p, e := wire.MarshalRouteAttentionSubscription(x) | |||
| 131 | + | return wire.Frame{Type: wire.MsgRouteAttentionSubscription, Payload: p}, e | |||
| 132 | + | case protocol.SamePeerSwitchRequest: | |||
| 133 | + | p, e := wire.MarshalSamePeerSwitchRequest(x) | |||
| 134 | + | return wire.Frame{Type: wire.MsgSamePeerSwitchRequest, Payload: p}, e | |||
| 135 | + | case protocol.ParkedRouteRequest: | |||
| 136 | + | return wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: wire.MarshalParkedRouteRequest(x)}, nil | |||
| 137 | + | case protocol.RouteNavigationFailure: | |||
| 138 | + | p, e := wire.MarshalRouteNavigationFailure(x) | |||
| 139 | + | return wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: p}, e | |||
| 140 | + | default: | |||
| 141 | + | return wire.Frame{}, errors.New("test client: unsupported message") | |||
| 142 | + | } | |||
| 143 | + | } | |||
| 144 | + | func externalServerMessage(f wire.Frame) (protocol.ServerMessage, error) { | |||
| 145 | + | switch f.Type { | |||
| 146 | + | case wire.MsgWelcome: | |||
| 147 | + | return wire.UnmarshalWelcome(f.Payload) | |||
| 148 | + | case wire.MsgError: | |||
| 149 | + | return wire.UnmarshalErrorMsg(f.Payload) | |||
| 150 | + | case wire.MsgOutput: | |||
| 151 | + | return wire.UnmarshalOutput(f.Payload) | |||
| 152 | + | case wire.MsgDetached: | |||
| 153 | + | return wire.UnmarshalDetached(f.Payload) | |||
| 154 | + | case wire.MsgPong: | |||
| 155 | + | return wire.UnmarshalPong(f.Payload) | |||
| 156 | + | case wire.MsgAttachTarget: | |||
| 157 | + | return wire.UnmarshalAttachTarget(f.Payload) | |||
| 158 | + | case wire.MsgNavigationAction: | |||
| 159 | + | return wire.UnmarshalNavigationDirective(f.Payload) | |||
| 160 | + | case wire.MsgParkedRouteResponse: | |||
| 161 | + | return wire.UnmarshalParkedRouteResponse(f.Payload) | |||
| 162 | + | case wire.MsgNavigateRecentRoute: | |||
| 163 | + | return wire.UnmarshalRouteNavigationAction(f.Payload) | |||
| 164 | + | case wire.MsgCommittedRouteIdentity: | |||
| 165 | + | return wire.UnmarshalCommittedRouteIdentity(f.Payload) | |||
| 166 | + | case wire.MsgSamePeerSwitchFailure: | |||
| 167 | + | return wire.UnmarshalSamePeerSwitchFailure(f.Payload) | |||
| 168 | + | case wire.MsgRoutePosition: | |||
| 169 | + | return wire.UnmarshalRoutePosition(f.Payload) | |||
| 170 | + | case wire.MsgRouteNavigationFailure: | |||
| 171 | + | return wire.UnmarshalRouteNavigationFailure(f.Payload) | |||
| 172 | + | default: | |||
| 173 | + | return nil, errors.New("test client: unsupported server frame") | |||
| 174 | + | } | |||
| 175 | + | } | |||
| 176 | + | ||||
| 177 | + | func (t *markedDatagramTransport) SendClient(m protocol.ClientMessage) error { | |||
| 178 | + | f, e := externalClientFrame(m) | |||
| 179 | + | if e != nil { | |||
| 180 | + | return e | |||
| 181 | + | } | |||
| 182 | + | return t.Send(f) | |||
| 183 | + | } | |||
| 184 | + | func (t *markedDatagramTransport) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 185 | + | f, e := t.Recv() | |||
| 186 | + | if e != nil { | |||
| 187 | + | return nil, e | |||
| 188 | + | } | |||
| 189 | + | return externalServerMessage(f) | |||
| 190 | + | } | |||
| 191 | + | func (t *markedDatagramTransport) Capabilities() protocol.ConnectionCapabilities { | |||
| 192 | + | return externalCapabilities(t) | |||
| 193 | + | } | |||
| 194 | + | func (t *markedDatagramTransport) LinkState() ports.LinkState { return ports.LinkStateConnected } | |||
| 195 | + | func (t *markedDatagramTransport) LinkEvents() <-chan ports.LinkEvent { return nil } | |||
| 196 | + | ||||
| 197 | + | func (t *recordingTransport) SendClient(m protocol.ClientMessage) error { | |||
| 198 | + | f, e := externalClientFrame(m) | |||
| 199 | + | if e != nil { | |||
| 200 | + | return e | |||
| 201 | + | } | |||
| 202 | + | return t.Send(f) | |||
| 203 | + | } | |||
| 204 | + | func (t *recordingTransport) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 205 | + | f, e := t.Recv() | |||
| 206 | + | if e != nil { | |||
| 207 | + | return nil, e | |||
| 208 | + | } | |||
| 209 | + | return externalServerMessage(f) | |||
| 210 | + | } | |||
| 211 | + | func (t *recordingTransport) Capabilities() protocol.ConnectionCapabilities { | |||
| 212 | + | return externalCapabilities(t) | |||
| 213 | + | } | |||
| 214 | + | func (t *recordingTransport) LinkState() ports.LinkState { return ports.LinkStateConnected } | |||
| 215 | + | func (t *recordingTransport) LinkEvents() <-chan ports.LinkEvent { return nil } | |||
| 216 | + | ||||
| 217 | + | func (t *clipboardToastLifecycleTransport) SendClient(m protocol.ClientMessage) error { | |||
| 218 | + | f, e := externalClientFrame(m) | |||
| 219 | + | if e != nil { | |||
| 220 | + | return e | |||
| 221 | + | } | |||
| 222 | + | return t.Send(f) | |||
| 223 | + | } | |||
| 224 | + | func (t *clipboardToastLifecycleTransport) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 225 | + | f, e := t.Recv() | |||
| 226 | + | if e != nil { | |||
| 227 | + | return nil, e | |||
| 228 | + | } | |||
| 229 | + | return externalServerMessage(f) | |||
| 230 | + | } | |||
| 231 | + | func (t *clipboardToastLifecycleTransport) Capabilities() protocol.ConnectionCapabilities { | |||
| 232 | + | return externalCapabilities(t) | |||
| 233 | + | } | |||
| 234 | + | func (t *clipboardToastLifecycleTransport) LinkState() ports.LinkState { | |||
| 235 | + | return ports.LinkStateConnected | |||
| 236 | + | } | |||
| 237 | + | func (t *clipboardToastLifecycleTransport) LinkEvents() <-chan ports.LinkEvent { return nil } | |||
A internal/usecase/client/typed_connection_test.go +374 -0
| @@ -0,0 +1,374 @@ | |||
| 1 | + | package client | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "bytes" | |
| 5 | + | "context" | |
| 6 | + | "errors" | |
| 7 | + | "io" | |
| 8 | + | "log/slog" | |
| 9 | + | "testing" | |
| 10 | + | ||
| 11 | + | "github.com/stretchr/testify/mock" | |
| 12 | + | "github.com/stretchr/testify/require" | |
| 13 | + | ||
| 14 | + | "github.com/bnema/vev/internal/ports" | |
| 15 | + | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |
| 16 | + | "github.com/bnema/vev/internal/protocol" | |
| 17 | + | "github.com/bnema/vev/internal/protocol/wire" | |
| 18 | + | ) | |
| 19 | + | ||
| 20 | + | type mockTestingT interface { | |
| 21 | + | mock.TestingT | |
| 22 | + | Cleanup(func()) | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | type mockClientConnection struct{ *portsmocks.MockTransport } | |
| 26 | + | ||
| 27 | + | func newMockClientConnection(t mockTestingT) *mockClientConnection { | |
| 28 | + | return &mockClientConnection{MockTransport: portsmocks.NewMockTransport(t)} | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | func (c *mockClientConnection) SendClient(message protocol.ClientMessage) error { | |
| 32 | + | frame, err := testClientFrame(message) | |
| 33 | + | if err != nil { | |
| 34 | + | return err | |
| 35 | + | } | |
| 36 | + | return c.MockTransport.Send(frame) | |
| 37 | + | } | |
| 38 | + | func (c *mockClientConnection) ReceiveServer() (protocol.ServerMessage, error) { | |
| 39 | + | frame, err := c.MockTransport.Recv() | |
| 40 | + | if err != nil { | |
| 41 | + | return nil, err | |
| 42 | + | } | |
| 43 | + | return testServerMessage(frame) | |
| 44 | + | } | |
| 45 | + | func (c *mockClientConnection) Capabilities() protocol.ConnectionCapabilities { | |
| 46 | + | return testClientCapabilities(c.MockTransport) | |
| 47 | + | } | |
| 48 | + | func (c *mockClientConnection) LinkState() ports.LinkState { | |
| 49 | + | return testClientLinkState(c.MockTransport) | |
| 50 | + | } | |
| 51 | + | func (c *mockClientConnection) LinkEvents() <-chan ports.LinkEvent { | |
| 52 | + | return testClientLinkEvents(c.MockTransport) | |
| 53 | + | } | |
| 54 | + | ||
| 55 | + | type rawClientConnection struct{ raw ports.Transport } | |
| 56 | + | ||
| 57 | + | func (c *rawClientConnection) SendClient(message protocol.ClientMessage) error { | |
| 58 | + | frame, err := testClientFrame(message) | |
| 59 | + | if err != nil { | |
| 60 | + | return err | |
| 61 | + | } | |
| 62 | + | return c.raw.Send(frame) | |
| 63 | + | } | |
| 64 | + | func (c *rawClientConnection) ReceiveServer() (protocol.ServerMessage, error) { | |
| 65 | + | frame, err := c.raw.Recv() | |
| 66 | + | if err != nil { | |
| 67 | + | return nil, err | |
| 68 | + | } | |
| 69 | + | return testServerMessage(frame) | |
| 70 | + | } | |
| 71 | + | func (c *rawClientConnection) Capabilities() protocol.ConnectionCapabilities { | |
| 72 | + | return testClientCapabilities(c.raw) | |
| 73 | + | } | |
| 74 | + | func (c *rawClientConnection) LinkState() ports.LinkState { return testClientLinkState(c.raw) } | |
| 75 | + | func (c *rawClientConnection) LinkEvents() <-chan ports.LinkEvent { return testClientLinkEvents(c.raw) } | |
| 76 | + | func (c *rawClientConnection) Close() error { return c.raw.Close() } | |
| 77 | + | ||
| 78 | + | type mockClientDialer struct{ *portsmocks.MockDialer } | |
| 79 | + | ||
| 80 | + | func newMockClientDialer(t mockTestingT) *mockClientDialer { | |
| 81 | + | return &mockClientDialer{MockDialer: portsmocks.NewMockDialer(t)} | |
| 82 | + | } | |
| 83 | + | func (d *mockClientDialer) Dial(ctx context.Context) (ports.ClientConnection, error) { | |
| 84 | + | raw, err := d.MockDialer.Dial(ctx) | |
| 85 | + | if err != nil { | |
| 86 | + | return nil, err | |
| 87 | + | } | |
| 88 | + | return &rawClientConnection{raw: raw}, nil | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | func testClientFrame(message protocol.ClientMessage) (wire.Frame, error) { | |
| 92 | + | switch m := message.(type) { | |
| 93 | + | case protocol.Hello: | |
| 94 | + | return wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(m)}, nil | |
| 95 | + | case protocol.Input: | |
| 96 | + | return wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(m)}, nil | |
| 97 | + | case protocol.Resize: | |
| 98 | + | p, e := wire.MarshalResize(m) | |
| 99 | + | return wire.Frame{Type: wire.MsgResize, Payload: p}, e | |
| 100 | + | case protocol.Detach: | |
| 101 | + | return wire.Frame{Type: wire.MsgDetach, Payload: wire.MarshalDetach(m)}, nil | |
| 102 | + | case protocol.Ping: | |
| 103 | + | return wire.Frame{Type: wire.MsgPing, Payload: wire.MarshalPing(m)}, nil | |
| 104 | + | case protocol.List: | |
| 105 | + | return wire.Frame{Type: wire.MsgList, Payload: wire.MarshalList(m)}, nil | |
| 106 | + | case protocol.Kill: | |
| 107 | + | return wire.Frame{Type: wire.MsgKill, Payload: wire.MarshalKill(m)}, nil | |
| 108 | + | case protocol.Theme: | |
| 109 | + | return wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(m)}, nil | |
| 110 | + | case protocol.Ack: | |
| 111 | + | p, e := wire.MarshalAck(m) | |
| 112 | + | return wire.Frame{Type: wire.MsgAck, Payload: p}, e | |
| 113 | + | case protocol.ImagePush: | |
| 114 | + | return wire.Frame{Type: wire.MsgImagePush, Payload: wire.MarshalImagePush(m)}, nil | |
| 115 | + | case protocol.ClientNotice: | |
| 116 | + | return wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(m)}, nil | |
| 117 | + | case protocol.CommandRequest: | |
| 118 | + | p, e := wire.MarshalCommandRequest(m) | |
| 119 | + | return wire.Frame{Type: wire.MsgCommand, Payload: p}, e | |
| 120 | + | case protocol.OutputResetRequest: | |
| 121 | + | return wire.Frame{Type: wire.MsgOutputResetRequest}, nil | |
| 122 | + | case protocol.RemotePreviewRequest: | |
| 123 | + | return wire.Frame{Type: wire.MsgRemotePreviewRequest, Payload: wire.MarshalRemotePreviewRequest(m)}, nil | |
| 124 | + | case protocol.RouteAttentionSubscription: | |
| 125 | + | p, e := wire.MarshalRouteAttentionSubscription(m) | |
| 126 | + | return wire.Frame{Type: wire.MsgRouteAttentionSubscription, Payload: p}, e | |
| 127 | + | case protocol.SamePeerSwitchRequest: | |
| 128 | + | p, e := wire.MarshalSamePeerSwitchRequest(m) | |
| 129 | + | return wire.Frame{Type: wire.MsgSamePeerSwitchRequest, Payload: p}, e | |
| 130 | + | case protocol.ParkedRouteRequest: | |
| 131 | + | return wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: wire.MarshalParkedRouteRequest(m)}, nil | |
| 132 | + | case protocol.RecentRouteSnapshot: | |
| 133 | + | p, e := wire.MarshalRecentRouteSnapshot(m) | |
| 134 | + | return wire.Frame{Type: wire.MsgRecentRouteSnapshot, Payload: p}, e | |
| 135 | + | case protocol.RouteNavigationFailure: | |
| 136 | + | p, e := wire.MarshalRouteNavigationFailure(m) | |
| 137 | + | return wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: p}, e | |
| 138 | + | default: | |
| 139 | + | return wire.Frame{}, errors.New("test client connection: unsupported client message") | |
| 140 | + | } | |
| 141 | + | } | |
| 142 | + | ||
| 143 | + | func testServerMessage(frame wire.Frame) (protocol.ServerMessage, error) { | |
| 144 | + | switch frame.Type { | |
| 145 | + | case wire.MsgWelcome: | |
| 146 | + | return wire.UnmarshalWelcome(frame.Payload) | |
| 147 | + | case wire.MsgError: | |
| 148 | + | return wire.UnmarshalErrorMsg(frame.Payload) | |
| 149 | + | case wire.MsgOutput: | |
| 150 | + | return wire.UnmarshalOutput(frame.Payload) | |
| 151 | + | case wire.MsgDetached: | |
| 152 | + | return wire.UnmarshalDetached(frame.Payload) | |
| 153 | + | case wire.MsgPong: | |
| 154 | + | return wire.UnmarshalPong(frame.Payload) | |
| 155 | + | case wire.MsgSessions: | |
| 156 | + | return wire.UnmarshalSessions(frame.Payload) | |
| 157 | + | case wire.MsgCommandResult: | |
| 158 | + | return wire.UnmarshalCommandResult(frame.Payload) | |
| 159 | + | case wire.MsgNavigationAction: | |
| 160 | + | return wire.UnmarshalNavigationDirective(frame.Payload) | |
| 161 | + | case wire.MsgAttachTarget: | |
| 162 | + | return wire.UnmarshalAttachTarget(frame.Payload) | |
| 163 | + | case wire.MsgRemotePreviewResponse: | |
| 164 | + | return wire.UnmarshalRemotePreview(frame.Payload) | |
| 165 | + | case wire.MsgCommittedRouteIdentity: | |
| 166 | + | return wire.UnmarshalCommittedRouteIdentity(frame.Payload) | |
| 167 | + | case wire.MsgNavigateRecentRoute: | |
| 168 | + | return wire.UnmarshalRouteNavigationAction(frame.Payload) | |
| 169 | + | case wire.MsgRouteNavigationFailure: | |
| 170 | + | return wire.UnmarshalRouteNavigationFailure(frame.Payload) | |
| 171 | + | case wire.MsgRoutePosition: | |
| 172 | + | return wire.UnmarshalRoutePosition(frame.Payload) | |
| 173 | + | case wire.MsgSamePeerSwitchFailure: | |
| 174 | + | return wire.UnmarshalSamePeerSwitchFailure(frame.Payload) | |
| 175 | + | case wire.MsgParkedRouteResponse: | |
| 176 | + | return wire.UnmarshalParkedRouteResponse(frame.Payload) | |
| 177 | + | default: | |
| 178 | + | return nil, errors.New("test client connection: unsupported server frame") | |
| 179 | + | } | |
| 180 | + | } | |
| 181 | + | ||
| 182 | + | func testClientCapabilities(raw ports.Transport) protocol.ConnectionCapabilities { | |
| 183 | + | _, dgram := raw.(ports.DatagramTransport) | |
| 184 | + | _, link := raw.(ports.LinkStateReporter) | |
| 185 | + | window := uint8(protocol.MaxOutputWindow) | |
| 186 | + | if dgram { | |
| 187 | + | window = 1 | |
| 188 | + | } | |
| 189 | + | return protocol.ConnectionCapabilities{OutputDataLimit: protocol.MaxOutputDataLen, PreferredOutputWindow: window, LinkState: link} | |
| 190 | + | } | |
| 191 | + | func testClientLinkState(raw ports.Transport) ports.LinkState { | |
| 192 | + | if r, ok := raw.(ports.LinkStateReporter); ok { | |
| 193 | + | return r.LinkState() | |
| 194 | + | } | |
| 195 | + | return ports.LinkStateConnected | |
| 196 | + | } | |
| 197 | + | func testClientLinkEvents(raw ports.Transport) <-chan ports.LinkEvent { | |
| 198 | + | if r, ok := raw.(ports.LinkStateReporter); ok { | |
| 199 | + | return r.LinkEvents() | |
| 200 | + | } | |
| 201 | + | return nil | |
| 202 | + | } | |
| 203 | + | ||
| 204 | + | func TestRunRecvLogsIgnoredTypedBoundaryFailures(t *testing.T) { | |
| 205 | + | connection := portsmocks.NewMockClientConnection(t) | |
| 206 | + | connection.EXPECT().ReceiveServer().Return(nil, &protocol.DecodeFailure{Category: protocol.DecodeUnknownType, Type: 255}).Once() | |
| 207 | + | connection.EXPECT().ReceiveServer().Return(nil, &protocol.DecodeFailure{Category: protocol.DecodeWrongDirection, Type: uint8(wire.MsgInput)}).Once() | |
| 208 | + | connection.EXPECT().ReceiveServer().Return(protocol.Pong{}, nil).Once() | |
| 209 | + | connection.EXPECT().ReceiveServer().Return(nil, io.EOF).Once() | |
| 210 | + | results := make(chan recvResult, 2) | |
| 211 | + | failed := make(chan struct{}) | |
| 212 | + | var logs bytes.Buffer | |
| 213 | + | log := slog.New(slog.NewTextHandler(&logs, &slog.HandlerOptions{Level: slog.LevelDebug})) | |
| 214 | + | runRecv(context.Background(), connection, results, failed, log) | |
| 215 | + | first := <-results | |
| 216 | + | require.Equal(t, protocol.Pong{}, first.message) | |
| 217 | + | require.NoError(t, first.err) | |
| 218 | + | second := <-results | |
| 219 | + | require.ErrorIs(t, second.err, io.EOF) | |
| 220 | + | select { | |
| 221 | + | case <-failed: | |
| 222 | + | default: | |
| 223 | + | t.Fatal("terminal receive failure was not reported") | |
| 224 | + | } | |
| 225 | + | require.Contains(t, logs.String(), "ignoring rejected server message") | |
| 226 | + | require.Contains(t, logs.String(), "category=2") | |
| 227 | + | require.Contains(t, logs.String(), "category=3") | |
| 228 | + | require.Contains(t, logs.String(), "type=255") | |
| 229 | + | require.Contains(t, logs.String(), "type=2") | |
| 230 | + | } | |
| 231 | + | ||
| 232 | + | func (t *ackRecordingTransport) SendClient(m protocol.ClientMessage) error { | |
| 233 | + | f, e := testClientFrame(m) | |
| 234 | + | if e != nil { | |
| 235 | + | return e | |
| 236 | + | } | |
| 237 | + | return t.Send(f) | |
| 238 | + | } | |
| 239 | + | func (t *ackRecordingTransport) ReceiveServer() (protocol.ServerMessage, error) { | |
| 240 | + | f, e := t.Recv() | |
| 241 | + | if e != nil { | |
| 242 | + | return nil, e | |
| 243 | + | } | |
| 244 | + | return testServerMessage(f) | |
| 245 | + | } | |
| 246 | + | func (t *ackRecordingTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 247 | + | return testClientCapabilities(t) | |
| 248 | + | } | |
| 249 | + | func (t *ackRecordingTransport) LinkState() ports.LinkState { return ports.LinkStateConnected } | |
| 250 | + | func (t *ackRecordingTransport) LinkEvents() <-chan ports.LinkEvent { return nil } | |
| 251 | + | ||
| 252 | + | func (t *reconnectToastLinkTransport) SendClient(m protocol.ClientMessage) error { | |
| 253 | + | f, e := testClientFrame(m) | |
| 254 | + | if e != nil { | |
| 255 | + | return e | |
| 256 | + | } | |
| 257 | + | return t.Send(f) | |
| 258 | + | } | |
| 259 | + | func (t *reconnectToastLinkTransport) ReceiveServer() (protocol.ServerMessage, error) { | |
| 260 | + | f, e := t.Recv() | |
| 261 | + | if e != nil { | |
| 262 | + | return nil, e | |
| 263 | + | } | |
| 264 | + | return testServerMessage(f) | |
| 265 | + | } | |
| 266 | + | func (t *reconnectToastLinkTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 267 | + | return testClientCapabilities(t) | |
| 268 | + | } | |
| 269 | + | ||
| 270 | + | func (t *reconnectToastRecordingTransport) SendClient(m protocol.ClientMessage) error { | |
| 271 | + | f, e := testClientFrame(m) | |
| 272 | + | if e != nil { | |
| 273 | + | return e | |
| 274 | + | } | |
| 275 | + | return t.Send(f) | |
| 276 | + | } | |
| 277 | + | func (t *reconnectToastRecordingTransport) ReceiveServer() (protocol.ServerMessage, error) { | |
| 278 | + | f, e := t.Recv() | |
| 279 | + | if e != nil { | |
| 280 | + | return nil, e | |
| 281 | + | } | |
| 282 | + | return testServerMessage(f) | |
| 283 | + | } | |
| 284 | + | func (t *reconnectToastRecordingTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 285 | + | return testClientCapabilities(t) | |
| 286 | + | } | |
| 287 | + | func (t *reconnectToastRecordingTransport) LinkState() ports.LinkState { | |
| 288 | + | return ports.LinkStateConnected | |
| 289 | + | } | |
| 290 | + | func (t *reconnectToastRecordingTransport) LinkEvents() <-chan ports.LinkEvent { | |
| 291 | + | return nil | |
| 292 | + | } | |
| 293 | + | ||
| 294 | + | func (t *reconnectResetBlockingTransport) SendClient(m protocol.ClientMessage) error { | |
| 295 | + | f, e := testClientFrame(m) | |
| 296 | + | if e != nil { | |
| 297 | + | return e | |
| 298 | + | } | |
| 299 | + | return t.Send(f) | |
| 300 | + | } | |
| 301 | + | func (t *reconnectResetBlockingTransport) ReceiveServer() (protocol.ServerMessage, error) { | |
| 302 | + | f, e := t.Recv() | |
| 303 | + | if e != nil { | |
| 304 | + | return nil, e | |
| 305 | + | } | |
| 306 | + | return testServerMessage(f) | |
| 307 | + | } | |
| 308 | + | func (t *reconnectResetBlockingTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 309 | + | return testClientCapabilities(t) | |
| 310 | + | } | |
| 311 | + | func (t *reconnectResetBlockingTransport) LinkState() ports.LinkState { | |
| 312 | + | return ports.LinkStateConnected | |
| 313 | + | } | |
| 314 | + | func (t *reconnectResetBlockingTransport) LinkEvents() <-chan ports.LinkEvent { | |
| 315 | + | return nil | |
| 316 | + | } | |
| 317 | + | ||
| 318 | + | func (t *reconnectToastBlockingSendTransport) SendClient(m protocol.ClientMessage) error { | |
| 319 | + | f, e := testClientFrame(m) | |
| 320 | + | if e != nil { | |
| 321 | + | return e | |
| 322 | + | } | |
| 323 | + | return t.Send(f) | |
| 324 | + | } | |
| 325 | + | func (t *reconnectToastBlockingSendTransport) ReceiveServer() (protocol.ServerMessage, error) { | |
| 326 | + | f, e := t.Recv() | |
| 327 | + | if e != nil { | |
| 328 | + | return nil, e | |
| 329 | + | } | |
| 330 | + | return testServerMessage(f) | |
| 331 | + | } | |
| 332 | + | func (t *reconnectToastBlockingSendTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 333 | + | return testClientCapabilities(t) | |
| 334 | + | } | |
| 335 | + | ||
| 336 | + | func (t *paletteAttachTransport) SendClient(m protocol.ClientMessage) error { | |
| 337 | + | f, e := testClientFrame(m) | |
| 338 | + | if e != nil { | |
| 339 | + | return e | |
| 340 | + | } | |
| 341 | + | return t.Send(f) | |
| 342 | + | } | |
| 343 | + | func (t *paletteAttachTransport) ReceiveServer() (protocol.ServerMessage, error) { | |
| 344 | + | f, e := t.Recv() | |
| 345 | + | if e != nil { | |
| 346 | + | return nil, e | |
| 347 | + | } | |
| 348 | + | return testServerMessage(f) | |
| 349 | + | } | |
| 350 | + | func (t *paletteAttachTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 351 | + | return testClientCapabilities(t) | |
| 352 | + | } | |
| 353 | + | func (t *paletteAttachTransport) LinkState() ports.LinkState { return ports.LinkStateConnected } | |
| 354 | + | func (t *paletteAttachTransport) LinkEvents() <-chan ports.LinkEvent { return nil } | |
| 355 | + | ||
| 356 | + | func (t *attachPaletteTransport) SendClient(m protocol.ClientMessage) error { | |
| 357 | + | f, e := testClientFrame(m) | |
| 358 | + | if e != nil { | |
| 359 | + | return e | |
| 360 | + | } | |
| 361 | + | return t.Send(f) | |
| 362 | + | } | |
| 363 | + | func (t *attachPaletteTransport) ReceiveServer() (protocol.ServerMessage, error) { | |
| 364 | + | f, e := t.Recv() | |
| 365 | + | if e != nil { | |
| 366 | + | return nil, e | |
| 367 | + | } | |
| 368 | + | return testServerMessage(f) | |
| 369 | + | } | |
| 370 | + | func (t *attachPaletteTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 371 | + | return testClientCapabilities(t) | |
| 372 | + | } | |
| 373 | + | func (t *attachPaletteTransport) LinkState() ports.LinkState { return ports.LinkStateConnected } | |
| 374 | + | func (t *attachPaletteTransport) LinkEvents() <-chan ports.LinkEvent { return nil } | |
| @@ -0,0 +1,374 @@ | |||||
| 1 | + | package client | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "bytes" | |||
| 5 | + | "context" | |||
| 6 | + | "errors" | |||
| 7 | + | "io" | |||
| 8 | + | "log/slog" | |||
| 9 | + | "testing" | |||
| 10 | + | ||||
| 11 | + | "github.com/stretchr/testify/mock" | |||
| 12 | + | "github.com/stretchr/testify/require" | |||
| 13 | + | ||||
| 14 | + | "github.com/bnema/vev/internal/ports" | |||
| 15 | + | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |||
| 16 | + | "github.com/bnema/vev/internal/protocol" | |||
| 17 | + | "github.com/bnema/vev/internal/protocol/wire" | |||
| 18 | + | ) | |||
| 19 | + | ||||
| 20 | + | type mockTestingT interface { | |||
| 21 | + | mock.TestingT | |||
| 22 | + | Cleanup(func()) | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | type mockClientConnection struct{ *portsmocks.MockTransport } | |||
| 26 | + | ||||
| 27 | + | func newMockClientConnection(t mockTestingT) *mockClientConnection { | |||
| 28 | + | return &mockClientConnection{MockTransport: portsmocks.NewMockTransport(t)} | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | func (c *mockClientConnection) SendClient(message protocol.ClientMessage) error { | |||
| 32 | + | frame, err := testClientFrame(message) | |||
| 33 | + | if err != nil { | |||
| 34 | + | return err | |||
| 35 | + | } | |||
| 36 | + | return c.MockTransport.Send(frame) | |||
| 37 | + | } | |||
| 38 | + | func (c *mockClientConnection) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 39 | + | frame, err := c.MockTransport.Recv() | |||
| 40 | + | if err != nil { | |||
| 41 | + | return nil, err | |||
| 42 | + | } | |||
| 43 | + | return testServerMessage(frame) | |||
| 44 | + | } | |||
| 45 | + | func (c *mockClientConnection) Capabilities() protocol.ConnectionCapabilities { | |||
| 46 | + | return testClientCapabilities(c.MockTransport) | |||
| 47 | + | } | |||
| 48 | + | func (c *mockClientConnection) LinkState() ports.LinkState { | |||
| 49 | + | return testClientLinkState(c.MockTransport) | |||
| 50 | + | } | |||
| 51 | + | func (c *mockClientConnection) LinkEvents() <-chan ports.LinkEvent { | |||
| 52 | + | return testClientLinkEvents(c.MockTransport) | |||
| 53 | + | } | |||
| 54 | + | ||||
| 55 | + | type rawClientConnection struct{ raw ports.Transport } | |||
| 56 | + | ||||
| 57 | + | func (c *rawClientConnection) SendClient(message protocol.ClientMessage) error { | |||
| 58 | + | frame, err := testClientFrame(message) | |||
| 59 | + | if err != nil { | |||
| 60 | + | return err | |||
| 61 | + | } | |||
| 62 | + | return c.raw.Send(frame) | |||
| 63 | + | } | |||
| 64 | + | func (c *rawClientConnection) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 65 | + | frame, err := c.raw.Recv() | |||
| 66 | + | if err != nil { | |||
| 67 | + | return nil, err | |||
| 68 | + | } | |||
| 69 | + | return testServerMessage(frame) | |||
| 70 | + | } | |||
| 71 | + | func (c *rawClientConnection) Capabilities() protocol.ConnectionCapabilities { | |||
| 72 | + | return testClientCapabilities(c.raw) | |||
| 73 | + | } | |||
| 74 | + | func (c *rawClientConnection) LinkState() ports.LinkState { return testClientLinkState(c.raw) } | |||
| 75 | + | func (c *rawClientConnection) LinkEvents() <-chan ports.LinkEvent { return testClientLinkEvents(c.raw) } | |||
| 76 | + | func (c *rawClientConnection) Close() error { return c.raw.Close() } | |||
| 77 | + | ||||
| 78 | + | type mockClientDialer struct{ *portsmocks.MockDialer } | |||
| 79 | + | ||||
| 80 | + | func newMockClientDialer(t mockTestingT) *mockClientDialer { | |||
| 81 | + | return &mockClientDialer{MockDialer: portsmocks.NewMockDialer(t)} | |||
| 82 | + | } | |||
| 83 | + | func (d *mockClientDialer) Dial(ctx context.Context) (ports.ClientConnection, error) { | |||
| 84 | + | raw, err := d.MockDialer.Dial(ctx) | |||
| 85 | + | if err != nil { | |||
| 86 | + | return nil, err | |||
| 87 | + | } | |||
| 88 | + | return &rawClientConnection{raw: raw}, nil | |||
| 89 | + | } | |||
| 90 | + | ||||
| 91 | + | func testClientFrame(message protocol.ClientMessage) (wire.Frame, error) { | |||
| 92 | + | switch m := message.(type) { | |||
| 93 | + | case protocol.Hello: | |||
| 94 | + | return wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(m)}, nil | |||
| 95 | + | case protocol.Input: | |||
| 96 | + | return wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(m)}, nil | |||
| 97 | + | case protocol.Resize: | |||
| 98 | + | p, e := wire.MarshalResize(m) | |||
| 99 | + | return wire.Frame{Type: wire.MsgResize, Payload: p}, e | |||
| 100 | + | case protocol.Detach: | |||
| 101 | + | return wire.Frame{Type: wire.MsgDetach, Payload: wire.MarshalDetach(m)}, nil | |||
| 102 | + | case protocol.Ping: | |||
| 103 | + | return wire.Frame{Type: wire.MsgPing, Payload: wire.MarshalPing(m)}, nil | |||
| 104 | + | case protocol.List: | |||
| 105 | + | return wire.Frame{Type: wire.MsgList, Payload: wire.MarshalList(m)}, nil | |||
| 106 | + | case protocol.Kill: | |||
| 107 | + | return wire.Frame{Type: wire.MsgKill, Payload: wire.MarshalKill(m)}, nil | |||
| 108 | + | case protocol.Theme: | |||
| 109 | + | return wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(m)}, nil | |||
| 110 | + | case protocol.Ack: | |||
| 111 | + | p, e := wire.MarshalAck(m) | |||
| 112 | + | return wire.Frame{Type: wire.MsgAck, Payload: p}, e | |||
| 113 | + | case protocol.ImagePush: | |||
| 114 | + | return wire.Frame{Type: wire.MsgImagePush, Payload: wire.MarshalImagePush(m)}, nil | |||
| 115 | + | case protocol.ClientNotice: | |||
| 116 | + | return wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(m)}, nil | |||
| 117 | + | case protocol.CommandRequest: | |||
| 118 | + | p, e := wire.MarshalCommandRequest(m) | |||
| 119 | + | return wire.Frame{Type: wire.MsgCommand, Payload: p}, e | |||
| 120 | + | case protocol.OutputResetRequest: | |||
| 121 | + | return wire.Frame{Type: wire.MsgOutputResetRequest}, nil | |||
| 122 | + | case protocol.RemotePreviewRequest: | |||
| 123 | + | return wire.Frame{Type: wire.MsgRemotePreviewRequest, Payload: wire.MarshalRemotePreviewRequest(m)}, nil | |||
| 124 | + | case protocol.RouteAttentionSubscription: | |||
| 125 | + | p, e := wire.MarshalRouteAttentionSubscription(m) | |||
| 126 | + | return wire.Frame{Type: wire.MsgRouteAttentionSubscription, Payload: p}, e | |||
| 127 | + | case protocol.SamePeerSwitchRequest: | |||
| 128 | + | p, e := wire.MarshalSamePeerSwitchRequest(m) | |||
| 129 | + | return wire.Frame{Type: wire.MsgSamePeerSwitchRequest, Payload: p}, e | |||
| 130 | + | case protocol.ParkedRouteRequest: | |||
| 131 | + | return wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: wire.MarshalParkedRouteRequest(m)}, nil | |||
| 132 | + | case protocol.RecentRouteSnapshot: | |||
| 133 | + | p, e := wire.MarshalRecentRouteSnapshot(m) | |||
| 134 | + | return wire.Frame{Type: wire.MsgRecentRouteSnapshot, Payload: p}, e | |||
| 135 | + | case protocol.RouteNavigationFailure: | |||
| 136 | + | p, e := wire.MarshalRouteNavigationFailure(m) | |||
| 137 | + | return wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: p}, e | |||
| 138 | + | default: | |||
| 139 | + | return wire.Frame{}, errors.New("test client connection: unsupported client message") | |||
| 140 | + | } | |||
| 141 | + | } | |||
| 142 | + | ||||
| 143 | + | func testServerMessage(frame wire.Frame) (protocol.ServerMessage, error) { | |||
| 144 | + | switch frame.Type { | |||
| 145 | + | case wire.MsgWelcome: | |||
| 146 | + | return wire.UnmarshalWelcome(frame.Payload) | |||
| 147 | + | case wire.MsgError: | |||
| 148 | + | return wire.UnmarshalErrorMsg(frame.Payload) | |||
| 149 | + | case wire.MsgOutput: | |||
| 150 | + | return wire.UnmarshalOutput(frame.Payload) | |||
| 151 | + | case wire.MsgDetached: | |||
| 152 | + | return wire.UnmarshalDetached(frame.Payload) | |||
| 153 | + | case wire.MsgPong: | |||
| 154 | + | return wire.UnmarshalPong(frame.Payload) | |||
| 155 | + | case wire.MsgSessions: | |||
| 156 | + | return wire.UnmarshalSessions(frame.Payload) | |||
| 157 | + | case wire.MsgCommandResult: | |||
| 158 | + | return wire.UnmarshalCommandResult(frame.Payload) | |||
| 159 | + | case wire.MsgNavigationAction: | |||
| 160 | + | return wire.UnmarshalNavigationDirective(frame.Payload) | |||
| 161 | + | case wire.MsgAttachTarget: | |||
| 162 | + | return wire.UnmarshalAttachTarget(frame.Payload) | |||
| 163 | + | case wire.MsgRemotePreviewResponse: | |||
| 164 | + | return wire.UnmarshalRemotePreview(frame.Payload) | |||
| 165 | + | case wire.MsgCommittedRouteIdentity: | |||
| 166 | + | return wire.UnmarshalCommittedRouteIdentity(frame.Payload) | |||
| 167 | + | case wire.MsgNavigateRecentRoute: | |||
| 168 | + | return wire.UnmarshalRouteNavigationAction(frame.Payload) | |||
| 169 | + | case wire.MsgRouteNavigationFailure: | |||
| 170 | + | return wire.UnmarshalRouteNavigationFailure(frame.Payload) | |||
| 171 | + | case wire.MsgRoutePosition: | |||
| 172 | + | return wire.UnmarshalRoutePosition(frame.Payload) | |||
| 173 | + | case wire.MsgSamePeerSwitchFailure: | |||
| 174 | + | return wire.UnmarshalSamePeerSwitchFailure(frame.Payload) | |||
| 175 | + | case wire.MsgParkedRouteResponse: | |||
| 176 | + | return wire.UnmarshalParkedRouteResponse(frame.Payload) | |||
| 177 | + | default: | |||
| 178 | + | return nil, errors.New("test client connection: unsupported server frame") | |||
| 179 | + | } | |||
| 180 | + | } | |||
| 181 | + | ||||
| 182 | + | func testClientCapabilities(raw ports.Transport) protocol.ConnectionCapabilities { | |||
| 183 | + | _, dgram := raw.(ports.DatagramTransport) | |||
| 184 | + | _, link := raw.(ports.LinkStateReporter) | |||
| 185 | + | window := uint8(protocol.MaxOutputWindow) | |||
| 186 | + | if dgram { | |||
| 187 | + | window = 1 | |||
| 188 | + | } | |||
| 189 | + | return protocol.ConnectionCapabilities{OutputDataLimit: protocol.MaxOutputDataLen, PreferredOutputWindow: window, LinkState: link} | |||
| 190 | + | } | |||
| 191 | + | func testClientLinkState(raw ports.Transport) ports.LinkState { | |||
| 192 | + | if r, ok := raw.(ports.LinkStateReporter); ok { | |||
| 193 | + | return r.LinkState() | |||
| 194 | + | } | |||
| 195 | + | return ports.LinkStateConnected | |||
| 196 | + | } | |||
| 197 | + | func testClientLinkEvents(raw ports.Transport) <-chan ports.LinkEvent { | |||
| 198 | + | if r, ok := raw.(ports.LinkStateReporter); ok { | |||
| 199 | + | return r.LinkEvents() | |||
| 200 | + | } | |||
| 201 | + | return nil | |||
| 202 | + | } | |||
| 203 | + | ||||
| 204 | + | func TestRunRecvLogsIgnoredTypedBoundaryFailures(t *testing.T) { | |||
| 205 | + | connection := portsmocks.NewMockClientConnection(t) | |||
| 206 | + | connection.EXPECT().ReceiveServer().Return(nil, &protocol.DecodeFailure{Category: protocol.DecodeUnknownType, Type: 255}).Once() | |||
| 207 | + | connection.EXPECT().ReceiveServer().Return(nil, &protocol.DecodeFailure{Category: protocol.DecodeWrongDirection, Type: uint8(wire.MsgInput)}).Once() | |||
| 208 | + | connection.EXPECT().ReceiveServer().Return(protocol.Pong{}, nil).Once() | |||
| 209 | + | connection.EXPECT().ReceiveServer().Return(nil, io.EOF).Once() | |||
| 210 | + | results := make(chan recvResult, 2) | |||
| 211 | + | failed := make(chan struct{}) | |||
| 212 | + | var logs bytes.Buffer | |||
| 213 | + | log := slog.New(slog.NewTextHandler(&logs, &slog.HandlerOptions{Level: slog.LevelDebug})) | |||
| 214 | + | runRecv(context.Background(), connection, results, failed, log) | |||
| 215 | + | first := <-results | |||
| 216 | + | require.Equal(t, protocol.Pong{}, first.message) | |||
| 217 | + | require.NoError(t, first.err) | |||
| 218 | + | second := <-results | |||
| 219 | + | require.ErrorIs(t, second.err, io.EOF) | |||
| 220 | + | select { | |||
| 221 | + | case <-failed: | |||
| 222 | + | default: | |||
| 223 | + | t.Fatal("terminal receive failure was not reported") | |||
| 224 | + | } | |||
| 225 | + | require.Contains(t, logs.String(), "ignoring rejected server message") | |||
| 226 | + | require.Contains(t, logs.String(), "category=2") | |||
| 227 | + | require.Contains(t, logs.String(), "category=3") | |||
| 228 | + | require.Contains(t, logs.String(), "type=255") | |||
| 229 | + | require.Contains(t, logs.String(), "type=2") | |||
| 230 | + | } | |||
| 231 | + | ||||
| 232 | + | func (t *ackRecordingTransport) SendClient(m protocol.ClientMessage) error { | |||
| 233 | + | f, e := testClientFrame(m) | |||
| 234 | + | if e != nil { | |||
| 235 | + | return e | |||
| 236 | + | } | |||
| 237 | + | return t.Send(f) | |||
| 238 | + | } | |||
| 239 | + | func (t *ackRecordingTransport) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 240 | + | f, e := t.Recv() | |||
| 241 | + | if e != nil { | |||
| 242 | + | return nil, e | |||
| 243 | + | } | |||
| 244 | + | return testServerMessage(f) | |||
| 245 | + | } | |||
| 246 | + | func (t *ackRecordingTransport) Capabilities() protocol.ConnectionCapabilities { | |||
| 247 | + | return testClientCapabilities(t) | |||
| 248 | + | } | |||
| 249 | + | func (t *ackRecordingTransport) LinkState() ports.LinkState { return ports.LinkStateConnected } | |||
| 250 | + | func (t *ackRecordingTransport) LinkEvents() <-chan ports.LinkEvent { return nil } | |||
| 251 | + | ||||
| 252 | + | func (t *reconnectToastLinkTransport) SendClient(m protocol.ClientMessage) error { | |||
| 253 | + | f, e := testClientFrame(m) | |||
| 254 | + | if e != nil { | |||
| 255 | + | return e | |||
| 256 | + | } | |||
| 257 | + | return t.Send(f) | |||
| 258 | + | } | |||
| 259 | + | func (t *reconnectToastLinkTransport) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 260 | + | f, e := t.Recv() | |||
| 261 | + | if e != nil { | |||
| 262 | + | return nil, e | |||
| 263 | + | } | |||
| 264 | + | return testServerMessage(f) | |||
| 265 | + | } | |||
| 266 | + | func (t *reconnectToastLinkTransport) Capabilities() protocol.ConnectionCapabilities { | |||
| 267 | + | return testClientCapabilities(t) | |||
| 268 | + | } | |||
| 269 | + | ||||
| 270 | + | func (t *reconnectToastRecordingTransport) SendClient(m protocol.ClientMessage) error { | |||
| 271 | + | f, e := testClientFrame(m) | |||
| 272 | + | if e != nil { | |||
| 273 | + | return e | |||
| 274 | + | } | |||
| 275 | + | return t.Send(f) | |||
| 276 | + | } | |||
| 277 | + | func (t *reconnectToastRecordingTransport) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 278 | + | f, e := t.Recv() | |||
| 279 | + | if e != nil { | |||
| 280 | + | return nil, e | |||
| 281 | + | } | |||
| 282 | + | return testServerMessage(f) | |||
| 283 | + | } | |||
| 284 | + | func (t *reconnectToastRecordingTransport) Capabilities() protocol.ConnectionCapabilities { | |||
| 285 | + | return testClientCapabilities(t) | |||
| 286 | + | } | |||
| 287 | + | func (t *reconnectToastRecordingTransport) LinkState() ports.LinkState { | |||
| 288 | + | return ports.LinkStateConnected | |||
| 289 | + | } | |||
| 290 | + | func (t *reconnectToastRecordingTransport) LinkEvents() <-chan ports.LinkEvent { | |||
| 291 | + | return nil | |||
| 292 | + | } | |||
| 293 | + | ||||
| 294 | + | func (t *reconnectResetBlockingTransport) SendClient(m protocol.ClientMessage) error { | |||
| 295 | + | f, e := testClientFrame(m) | |||
| 296 | + | if e != nil { | |||
| 297 | + | return e | |||
| 298 | + | } | |||
| 299 | + | return t.Send(f) | |||
| 300 | + | } | |||
| 301 | + | func (t *reconnectResetBlockingTransport) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 302 | + | f, e := t.Recv() | |||
| 303 | + | if e != nil { | |||
| 304 | + | return nil, e | |||
| 305 | + | } | |||
| 306 | + | return testServerMessage(f) | |||
| 307 | + | } | |||
| 308 | + | func (t *reconnectResetBlockingTransport) Capabilities() protocol.ConnectionCapabilities { | |||
| 309 | + | return testClientCapabilities(t) | |||
| 310 | + | } | |||
| 311 | + | func (t *reconnectResetBlockingTransport) LinkState() ports.LinkState { | |||
| 312 | + | return ports.LinkStateConnected | |||
| 313 | + | } | |||
| 314 | + | func (t *reconnectResetBlockingTransport) LinkEvents() <-chan ports.LinkEvent { | |||
| 315 | + | return nil | |||
| 316 | + | } | |||
| 317 | + | ||||
| 318 | + | func (t *reconnectToastBlockingSendTransport) SendClient(m protocol.ClientMessage) error { | |||
| 319 | + | f, e := testClientFrame(m) | |||
| 320 | + | if e != nil { | |||
| 321 | + | return e | |||
| 322 | + | } | |||
| 323 | + | return t.Send(f) | |||
| 324 | + | } | |||
| 325 | + | func (t *reconnectToastBlockingSendTransport) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 326 | + | f, e := t.Recv() | |||
| 327 | + | if e != nil { | |||
| 328 | + | return nil, e | |||
| 329 | + | } | |||
| 330 | + | return testServerMessage(f) | |||
| 331 | + | } | |||
| 332 | + | func (t *reconnectToastBlockingSendTransport) Capabilities() protocol.ConnectionCapabilities { | |||
| 333 | + | return testClientCapabilities(t) | |||
| 334 | + | } | |||
| 335 | + | ||||
| 336 | + | func (t *paletteAttachTransport) SendClient(m protocol.ClientMessage) error { | |||
| 337 | + | f, e := testClientFrame(m) | |||
| 338 | + | if e != nil { | |||
| 339 | + | return e | |||
| 340 | + | } | |||
| 341 | + | return t.Send(f) | |||
| 342 | + | } | |||
| 343 | + | func (t *paletteAttachTransport) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 344 | + | f, e := t.Recv() | |||
| 345 | + | if e != nil { | |||
| 346 | + | return nil, e | |||
| 347 | + | } | |||
| 348 | + | return testServerMessage(f) | |||
| 349 | + | } | |||
| 350 | + | func (t *paletteAttachTransport) Capabilities() protocol.ConnectionCapabilities { | |||
| 351 | + | return testClientCapabilities(t) | |||
| 352 | + | } | |||
| 353 | + | func (t *paletteAttachTransport) LinkState() ports.LinkState { return ports.LinkStateConnected } | |||
| 354 | + | func (t *paletteAttachTransport) LinkEvents() <-chan ports.LinkEvent { return nil } | |||
| 355 | + | ||||
| 356 | + | func (t *attachPaletteTransport) SendClient(m protocol.ClientMessage) error { | |||
| 357 | + | f, e := testClientFrame(m) | |||
| 358 | + | if e != nil { | |||
| 359 | + | return e | |||
| 360 | + | } | |||
| 361 | + | return t.Send(f) | |||
| 362 | + | } | |||
| 363 | + | func (t *attachPaletteTransport) ReceiveServer() (protocol.ServerMessage, error) { | |||
| 364 | + | f, e := t.Recv() | |||
| 365 | + | if e != nil { | |||
| 366 | + | return nil, e | |||
| 367 | + | } | |||
| 368 | + | return testServerMessage(f) | |||
| 369 | + | } | |||
| 370 | + | func (t *attachPaletteTransport) Capabilities() protocol.ConnectionCapabilities { | |||
| 371 | + | return testClientCapabilities(t) | |||
| 372 | + | } | |||
| 373 | + | func (t *attachPaletteTransport) LinkState() ports.LinkState { return ports.LinkStateConnected } | |||
| 374 | + | func (t *attachPaletteTransport) LinkEvents() <-chan ports.LinkEvent { return nil } | |||