refactor(daemon): consume typed session messages
- commit
- 44294570fdee51089f2af811cb71db025889dabf
- author
- brice · · 2026-08-28 06:33 UTC
- committer
- brice · · 2026-08-28 13:08 UTC
- parent
- f791584
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
50 files · +2949 -473
A internal/adapters/sessionwire/server.go +373 -0
| @@ -0,0 +1,373 @@ | |||
| 1 | + | // Package sessionwire adapts typed session messages to strict binary frames. | |
| 2 | + | package sessionwire | |
| 3 | + | ||
| 4 | + | import ( | |
| 5 | + | "encoding/binary" | |
| 6 | + | "errors" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/vev/internal/ports" | |
| 9 | + | "github.com/bnema/vev/internal/protocol" | |
| 10 | + | "github.com/bnema/vev/internal/protocol/wire" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | var ( | |
| 14 | + | ErrUnknownMessageType = errors.New("sessionwire: unknown message type") | |
| 15 | + | ErrWrongDirection = errors.New("sessionwire: wrong message direction") | |
| 16 | + | ErrInvalidMessage = errors.New("sessionwire: invalid message") | |
| 17 | + | ErrUnsupportedSend = errors.New("sessionwire: send mode is unsupported") | |
| 18 | + | ) | |
| 19 | + | ||
| 20 | + | type serverConnection struct{ raw ports.Transport } | |
| 21 | + | ||
| 22 | + | var _ ports.ServerConnection = (*serverConnection)(nil) | |
| 23 | + | ||
| 24 | + | // NewServerConnection wraps one raw connection incarnation exactly once. | |
| 25 | + | func NewServerConnection(raw ports.Transport) ports.ServerConnection { | |
| 26 | + | if raw == nil { | |
| 27 | + | return nil | |
| 28 | + | } | |
| 29 | + | return &serverConnection{raw: raw} | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | func (c *serverConnection) ReceiveClient() (protocol.ClientMessage, error) { | |
| 33 | + | frame, err := c.raw.Recv() | |
| 34 | + | if err != nil { | |
| 35 | + | return nil, err | |
| 36 | + | } | |
| 37 | + | message, err := decodeClient(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 | + | switch frame.Type { | |
| 48 | + | case wire.MsgHello: | |
| 49 | + | failure.Kind = protocol.DecodeMessageHello | |
| 50 | + | failure.Version, _ = wire.PeekHelloVersion(frame.Payload) | |
| 51 | + | case wire.MsgCommand: | |
| 52 | + | failure.Kind = protocol.DecodeMessageCommand | |
| 53 | + | failure.Version, _ = wire.PeekCommandVersion(frame.Payload) | |
| 54 | + | if len(frame.Payload) >= 10 { | |
| 55 | + | failure.RequestID = binary.BigEndian.Uint64(frame.Payload[2:10]) | |
| 56 | + | } | |
| 57 | + | case wire.MsgKill: | |
| 58 | + | failure.Kind = protocol.DecodeMessageKill | |
| 59 | + | case wire.MsgRemotePreviewRequest: | |
| 60 | + | failure.Kind = protocol.DecodeMessageRemotePreview | |
| 61 | + | } | |
| 62 | + | return nil, failure | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | func (c *serverConnection) SendServer(message protocol.ServerMessage) error { | |
| 66 | + | frame, err := encodeServer(message) | |
| 67 | + | if err != nil { | |
| 68 | + | return err | |
| 69 | + | } | |
| 70 | + | return c.raw.Send(frame) | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | func (c *serverConnection) SendServerAsync(message protocol.ServerMessage) error { | |
| 74 | + | transport, ok := c.raw.(ports.AsyncTransport) | |
| 75 | + | if !ok { | |
| 76 | + | return ErrUnsupportedSend | |
| 77 | + | } | |
| 78 | + | frame, err := encodeServer(message) | |
| 79 | + | if err != nil { | |
| 80 | + | return err | |
| 81 | + | } | |
| 82 | + | return transport.SendAsync(frame) | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | func (c *serverConnection) SendServerSynchronous(message protocol.ServerMessage) error { | |
| 86 | + | transport, ok := c.raw.(ports.OwnedSynchronousTransport) | |
| 87 | + | if !ok { | |
| 88 | + | return ErrUnsupportedSend | |
| 89 | + | } | |
| 90 | + | frame, err := encodeServer(message) | |
| 91 | + | if err != nil { | |
| 92 | + | return err | |
| 93 | + | } | |
| 94 | + | return transport.SendSynchronous(frame) | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | func (c *serverConnection) SendOutput(output protocol.Output) error { | |
| 98 | + | frame, err := encodeOutput(output) | |
| 99 | + | if err != nil { | |
| 100 | + | return err | |
| 101 | + | } | |
| 102 | + | return c.raw.Send(frame) | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | func (c *serverConnection) SendOutputAsync(output protocol.Output) error { | |
| 106 | + | transport, ok := c.raw.(ports.AsyncTransport) | |
| 107 | + | if !ok { | |
| 108 | + | return ErrUnsupportedSend | |
| 109 | + | } | |
| 110 | + | frame, err := encodeOutput(output) | |
| 111 | + | if err != nil { | |
| 112 | + | return err | |
| 113 | + | } | |
| 114 | + | return transport.SendAsync(frame) | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | func (c *serverConnection) SendOutputSynchronous(output protocol.Output) error { | |
| 118 | + | transport, ok := c.raw.(ports.OwnedSynchronousTransport) | |
| 119 | + | if !ok { | |
| 120 | + | return ErrUnsupportedSend | |
| 121 | + | } | |
| 122 | + | frame, err := encodeOutput(output) | |
| 123 | + | if err != nil { | |
| 124 | + | return err | |
| 125 | + | } | |
| 126 | + | return transport.SendSynchronous(frame) | |
| 127 | + | } | |
| 128 | + | ||
| 129 | + | func encodeOutput(output protocol.Output) (wire.Frame, error) { | |
| 130 | + | payload, err := wire.MarshalOutput(output) | |
| 131 | + | return wire.Frame{Type: wire.MsgOutput, Payload: payload}, err | |
| 132 | + | } | |
| 133 | + | ||
| 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) | |
| 139 | + | window := uint8(8) | |
| 140 | + | if datagram { | |
| 141 | + | window = 1 | |
| 142 | + | } | |
| 143 | + | return protocol.ConnectionCapabilities{ | |
| 144 | + | OutputDataLimit: protocol.MaxOutputDataLen, | |
| 145 | + | PreferredOutputWindow: window, | |
| 146 | + | AsyncSend: async, | |
| 147 | + | OwnedSynchronousSend: synchronous, | |
| 148 | + | LinkState: linkState, | |
| 149 | + | } | |
| 150 | + | } | |
| 151 | + | ||
| 152 | + | func (c *serverConnection) LinkState() ports.LinkState { | |
| 153 | + | if reporter, ok := c.raw.(ports.LinkStateReporter); ok { | |
| 154 | + | return reporter.LinkState() | |
| 155 | + | } | |
| 156 | + | return ports.LinkStateConnected | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | func (c *serverConnection) LinkEvents() <-chan ports.LinkEvent { | |
| 160 | + | if reporter, ok := c.raw.(ports.LinkStateReporter); ok { | |
| 161 | + | return reporter.LinkEvents() | |
| 162 | + | } | |
| 163 | + | return nil | |
| 164 | + | } | |
| 165 | + | ||
| 166 | + | func (c *serverConnection) Close() error { return c.raw.Close() } | |
| 167 | + | ||
| 168 | + | type serverListener struct{ raw ports.Listener } | |
| 169 | + | ||
| 170 | + | var _ ports.ServerListener = (*serverListener)(nil) | |
| 171 | + | ||
| 172 | + | // NewServerListener wraps every accepted raw connection in a stable typed adapter. | |
| 173 | + | func NewServerListener(raw ports.Listener) ports.ServerListener { | |
| 174 | + | if raw == nil { | |
| 175 | + | return nil | |
| 176 | + | } | |
| 177 | + | return &serverListener{raw: raw} | |
| 178 | + | } | |
| 179 | + | ||
| 180 | + | func (l *serverListener) Accept() (ports.ServerConnection, error) { | |
| 181 | + | raw, err := l.raw.Accept() | |
| 182 | + | if err != nil { | |
| 183 | + | return nil, err | |
| 184 | + | } | |
| 185 | + | return NewServerConnection(raw), nil | |
| 186 | + | } | |
| 187 | + | ||
| 188 | + | func (l *serverListener) Close() error { return l.raw.Close() } | |
| 189 | + | func (l *serverListener) Addr() string { return l.raw.Addr() } | |
| 190 | + | ||
| 191 | + | func decodeClient(frame wire.Frame) (protocol.ClientMessage, error) { | |
| 192 | + | switch frame.Type { | |
| 193 | + | case wire.MsgHello: | |
| 194 | + | return wire.UnmarshalHello(frame.Payload) | |
| 195 | + | case wire.MsgInput: | |
| 196 | + | return wire.UnmarshalInput(frame.Payload) | |
| 197 | + | case wire.MsgResize: | |
| 198 | + | return wire.UnmarshalResize(frame.Payload) | |
| 199 | + | case wire.MsgDetach: | |
| 200 | + | return protocol.Detach{}, nil | |
| 201 | + | case wire.MsgPing: | |
| 202 | + | return protocol.Ping{}, nil | |
| 203 | + | case wire.MsgList: | |
| 204 | + | return protocol.List{}, nil | |
| 205 | + | case wire.MsgKill: | |
| 206 | + | return wire.UnmarshalKill(frame.Payload) | |
| 207 | + | case wire.MsgTheme: | |
| 208 | + | return wire.UnmarshalTheme(frame.Payload) | |
| 209 | + | case wire.MsgAck: | |
| 210 | + | return wire.UnmarshalAck(frame.Payload) | |
| 211 | + | case wire.MsgImagePush: | |
| 212 | + | return wire.UnmarshalImagePush(frame.Payload) | |
| 213 | + | case wire.MsgClientNotice: | |
| 214 | + | return wire.UnmarshalClientNotice(frame.Payload) | |
| 215 | + | case wire.MsgCommand: | |
| 216 | + | return wire.UnmarshalCommandRequest(frame.Payload) | |
| 217 | + | case wire.MsgOutputResetRequest: | |
| 218 | + | return wire.UnmarshalOutputResetRequest(frame.Payload) | |
| 219 | + | case wire.MsgRemotePreviewRequest: | |
| 220 | + | return wire.UnmarshalRemotePreviewRequest(frame.Payload) | |
| 221 | + | case wire.MsgRouteAttentionSubscription: | |
| 222 | + | return wire.UnmarshalRouteAttentionSubscription(frame.Payload) | |
| 223 | + | case wire.MsgSamePeerSwitchRequest: | |
| 224 | + | return wire.UnmarshalSamePeerSwitchRequest(frame.Payload) | |
| 225 | + | case wire.MsgParkedRouteRequest: | |
| 226 | + | return wire.UnmarshalParkedRouteRequest(frame.Payload) | |
| 227 | + | case wire.MsgRecentRouteSnapshot: | |
| 228 | + | return wire.UnmarshalRecentRouteSnapshot(frame.Payload) | |
| 229 | + | case wire.MsgRouteNavigationFailure: | |
| 230 | + | return wire.UnmarshalRouteNavigationFailure(frame.Payload) | |
| 231 | + | case wire.MsgWelcome, wire.MsgError, wire.MsgOutput, wire.MsgDetached, wire.MsgPong, | |
| 232 | + | wire.MsgSessions, wire.MsgCommandResult, wire.MsgNavigationAction, | |
| 233 | + | wire.MsgAttachTarget, wire.MsgRemotePreviewResponse, | |
| 234 | + | wire.MsgCommittedRouteIdentity, wire.MsgNavigateRecentRoute, | |
| 235 | + | wire.MsgRoutePosition, wire.MsgSamePeerSwitchFailure, wire.MsgParkedRouteResponse: | |
| 236 | + | return nil, ErrWrongDirection | |
| 237 | + | default: | |
| 238 | + | return nil, ErrUnknownMessageType | |
| 239 | + | } | |
| 240 | + | } | |
| 241 | + | ||
| 242 | + | func encodeServer(message protocol.ServerMessage) (wire.Frame, error) { | |
| 243 | + | switch m := message.(type) { | |
| 244 | + | case protocol.Welcome: | |
| 245 | + | payload := wire.MarshalWelcome(m) | |
| 246 | + | if payload == nil { | |
| 247 | + | return wire.Frame{}, ErrInvalidMessage | |
| 248 | + | } | |
| 249 | + | return wire.Frame{Type: wire.MsgWelcome, Payload: payload}, nil | |
| 250 | + | case protocol.ErrorMsg: | |
| 251 | + | return wire.Frame{Type: wire.MsgError, Payload: wire.MarshalErrorMsg(m)}, nil | |
| 252 | + | case protocol.Output: | |
| 253 | + | payload, err := wire.MarshalOutput(m) | |
| 254 | + | return wire.Frame{Type: wire.MsgOutput, Payload: payload}, err | |
| 255 | + | case protocol.Detached: | |
| 256 | + | if err := protocol.ValidateDetached(m); err != nil { | |
| 257 | + | return wire.Frame{}, err | |
| 258 | + | } | |
| 259 | + | return wire.Frame{Type: wire.MsgDetached, Payload: wire.MarshalDetached(m)}, nil | |
| 260 | + | case protocol.Pong: | |
| 261 | + | return wire.Frame{Type: wire.MsgPong, Payload: wire.MarshalPong(m)}, nil | |
| 262 | + | case protocol.Sessions: | |
| 263 | + | return wire.Frame{Type: wire.MsgSessions, Payload: wire.MarshalSessions(m)}, nil | |
| 264 | + | case protocol.CommandResult: | |
| 265 | + | return wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(m)}, nil | |
| 266 | + | case protocol.NavigationDirective: | |
| 267 | + | payload := wire.MarshalNavigationDirective(m) | |
| 268 | + | if payload == nil { | |
| 269 | + | return wire.Frame{}, ErrInvalidMessage | |
| 270 | + | } | |
| 271 | + | return wire.Frame{Type: wire.MsgNavigationAction, Payload: payload}, nil | |
| 272 | + | case protocol.AttachTarget: | |
| 273 | + | payload := wire.MarshalAttachTarget(m) | |
| 274 | + | if payload == nil { | |
| 275 | + | return wire.Frame{}, ErrInvalidMessage | |
| 276 | + | } | |
| 277 | + | return wire.Frame{Type: wire.MsgAttachTarget, Payload: payload}, nil | |
| 278 | + | case protocol.RemotePreview: | |
| 279 | + | payload := wire.MarshalRemotePreview(m) | |
| 280 | + | if payload == nil { | |
| 281 | + | return wire.Frame{}, ErrInvalidMessage | |
| 282 | + | } | |
| 283 | + | return wire.Frame{Type: wire.MsgRemotePreviewResponse, Payload: payload}, nil | |
| 284 | + | case protocol.CommittedRouteIdentity: | |
| 285 | + | payload, err := wire.MarshalCommittedRouteIdentity(m) | |
| 286 | + | return wire.Frame{Type: wire.MsgCommittedRouteIdentity, Payload: payload}, err | |
| 287 | + | case protocol.RouteNavigationAction: | |
| 288 | + | payload, err := wire.MarshalRouteNavigationAction(m) | |
| 289 | + | return wire.Frame{Type: wire.MsgNavigateRecentRoute, Payload: payload}, err | |
| 290 | + | case protocol.RouteNavigationFailure: | |
| 291 | + | payload, err := wire.MarshalRouteNavigationFailure(m) | |
| 292 | + | return wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: payload}, err | |
| 293 | + | case protocol.RoutePosition: | |
| 294 | + | payload, err := wire.MarshalRoutePosition(m) | |
| 295 | + | return wire.Frame{Type: wire.MsgRoutePosition, Payload: payload}, err | |
| 296 | + | case protocol.SamePeerSwitchFailure: | |
| 297 | + | payload, err := wire.MarshalSamePeerSwitchFailure(m) | |
| 298 | + | return wire.Frame{Type: wire.MsgSamePeerSwitchFailure, Payload: payload}, err | |
| 299 | + | case protocol.ParkedRouteResponse: | |
| 300 | + | payload := wire.MarshalParkedRouteResponse(m) | |
| 301 | + | if payload == nil { | |
| 302 | + | return wire.Frame{}, ErrInvalidMessage | |
| 303 | + | } | |
| 304 | + | return wire.Frame{Type: wire.MsgParkedRouteResponse, Payload: payload}, nil | |
| 305 | + | case *protocol.Welcome: | |
| 306 | + | if m != nil { | |
| 307 | + | return encodeServer(*m) | |
| 308 | + | } | |
| 309 | + | case *protocol.ErrorMsg: | |
| 310 | + | if m != nil { | |
| 311 | + | return encodeServer(*m) | |
| 312 | + | } | |
| 313 | + | case *protocol.Output: | |
| 314 | + | if m != nil { | |
| 315 | + | return encodeServer(*m) | |
| 316 | + | } | |
| 317 | + | case *protocol.Detached: | |
| 318 | + | if m != nil { | |
| 319 | + | return encodeServer(*m) | |
| 320 | + | } | |
| 321 | + | case *protocol.Pong: | |
| 322 | + | if m != nil { | |
| 323 | + | return encodeServer(*m) | |
| 324 | + | } | |
| 325 | + | case *protocol.Sessions: | |
| 326 | + | if m != nil { | |
| 327 | + | return encodeServer(*m) | |
| 328 | + | } | |
| 329 | + | case *protocol.CommandResult: | |
| 330 | + | if m != nil { | |
| 331 | + | return encodeServer(*m) | |
| 332 | + | } | |
| 333 | + | case *protocol.NavigationDirective: | |
| 334 | + | if m != nil { | |
| 335 | + | return encodeServer(*m) | |
| 336 | + | } | |
| 337 | + | case *protocol.AttachTarget: | |
| 338 | + | if m != nil { | |
| 339 | + | return encodeServer(*m) | |
| 340 | + | } | |
| 341 | + | case *protocol.RemotePreview: | |
| 342 | + | if m != nil { | |
| 343 | + | return encodeServer(*m) | |
| 344 | + | } | |
| 345 | + | case *protocol.CommittedRouteIdentity: | |
| 346 | + | if m != nil { | |
| 347 | + | return encodeServer(*m) | |
| 348 | + | } | |
| 349 | + | case *protocol.RouteNavigationAction: | |
| 350 | + | if m != nil { | |
| 351 | + | return encodeServer(*m) | |
| 352 | + | } | |
| 353 | + | case *protocol.RouteNavigationFailure: | |
| 354 | + | if m != nil { | |
| 355 | + | return encodeServer(*m) | |
| 356 | + | } | |
| 357 | + | case *protocol.RoutePosition: | |
| 358 | + | if m != nil { | |
| 359 | + | return encodeServer(*m) | |
| 360 | + | } | |
| 361 | + | case *protocol.SamePeerSwitchFailure: | |
| 362 | + | if m != nil { | |
| 363 | + | return encodeServer(*m) | |
| 364 | + | } | |
| 365 | + | case *protocol.ParkedRouteResponse: | |
| 366 | + | if m != nil { | |
| 367 | + | return encodeServer(*m) | |
| 368 | + | } | |
| 369 | + | default: | |
| 370 | + | return wire.Frame{}, ErrWrongDirection | |
| 371 | + | } | |
| 372 | + | return wire.Frame{}, ErrInvalidMessage | |
| 373 | + | } | |
A internal/adapters/sessionwire/server_test.go +254 -0
| @@ -0,0 +1,254 @@ | |||
| 1 | + | package sessionwire | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "errors" | |
| 5 | + | "io" | |
| 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 | + | type scriptedTransport struct { | |
| 16 | + | recv wire.Frame | |
| 17 | + | recvErr error | |
| 18 | + | sent wire.Frame | |
| 19 | + | sendErr error | |
| 20 | + | closeErr error | |
| 21 | + | closeCall int | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | func (t *scriptedTransport) Send(frame wire.Frame) error { t.sent = frame; return t.sendErr } | |
| 25 | + | func (t *scriptedTransport) Recv() (wire.Frame, error) { return t.recv, t.recvErr } | |
| 26 | + | func (t *scriptedTransport) Close() error { t.closeCall++; return t.closeErr } | |
| 27 | + | ||
| 28 | + | type capableTransport struct { | |
| 29 | + | scriptedTransport | |
| 30 | + | async wire.Frame | |
| 31 | + | synchronous wire.Frame | |
| 32 | + | events chan ports.LinkEvent | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | func (*capableTransport) DatagramTransport() {} | |
| 36 | + | func (t *capableTransport) SendAsync(frame wire.Frame) error { | |
| 37 | + | t.async = frame | |
| 38 | + | return t.sendErr | |
| 39 | + | } | |
| 40 | + | func (t *capableTransport) SendSynchronous(frame wire.Frame) error { | |
| 41 | + | t.synchronous = frame | |
| 42 | + | return t.sendErr | |
| 43 | + | } | |
| 44 | + | func (*capableTransport) LinkState() ports.LinkState { return ports.LinkStateDegraded } | |
| 45 | + | func (t *capableTransport) LinkEvents() <-chan ports.LinkEvent { return t.events } | |
| 46 | + | ||
| 47 | + | func testTarget() domain.RemoteSessionTarget { | |
| 48 | + | return domain.RemoteSessionTarget{ | |
| 49 | + | Endpoint: "remote", DisplayOrigin: "remote", LifecycleID: domain.SessionLifecycleID{1}, | |
| 50 | + | SessionName: "work", LiveTabID: "tab-1", | |
| 51 | + | } | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | func TestServerConnectionDecodesEveryClientMessage(t *testing.T) { | |
| 55 | + | target := testTarget() | |
| 56 | + | exact := protocol.ExactSessionTarget{LifecycleID: target.LifecycleID, SessionName: target.SessionName} | |
| 57 | + | lease := protocol.ParkedRouteLeaseID{1} | |
| 58 | + | commandPayload, err := wire.MarshalCommandRequest(protocol.CommandRequest{Version: protocol.Version, RequestID: 7, Slug: "list-sessions"}) | |
| 59 | + | require.NoError(t, err) | |
| 60 | + | resizePayload, err := wire.MarshalResize(protocol.Resize{Size: domain.Size{Cols: 80, Rows: 24}}) | |
| 61 | + | require.NoError(t, err) | |
| 62 | + | ackPayload, err := wire.MarshalAck(protocol.Ack{Epoch: 1, State: 2}) | |
| 63 | + | require.NoError(t, err) | |
| 64 | + | attentionPayload, err := wire.MarshalRouteAttentionSubscription(protocol.RouteAttentionSubscription{}) | |
| 65 | + | require.NoError(t, err) | |
| 66 | + | switchPayload, err := wire.MarshalSamePeerSwitchRequest(protocol.SamePeerSwitchRequest{RequestID: 3, Target: exact}) | |
| 67 | + | require.NoError(t, err) | |
| 68 | + | snapshotPayload, err := wire.MarshalRecentRouteSnapshot(protocol.RecentRouteSnapshot{}) | |
| 69 | + | require.NoError(t, err) | |
| 70 | + | failurePayload, err := wire.MarshalRouteNavigationFailure(protocol.RouteNavigationFailure{Key: 1, Generation: 1, Code: protocol.RouteFailureUnavailable}) | |
| 71 | + | require.NoError(t, err) | |
| 72 | + | ||
| 73 | + | tests := []struct { | |
| 74 | + | name string | |
| 75 | + | frame wire.Frame | |
| 76 | + | want protocol.ClientMessage | |
| 77 | + | }{ | |
| 78 | + | {name: "hello", frame: wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}})}, want: protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}}}, | |
| 79 | + | {name: "input", frame: wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{InputSeq: 1, Data: []byte("x")})}, want: protocol.Input{InputSeq: 1, Data: []byte("x")}}, | |
| 80 | + | {name: "resize", frame: wire.Frame{Type: wire.MsgResize, Payload: resizePayload}, want: protocol.Resize{Size: domain.Size{Cols: 80, Rows: 24}}}, | |
| 81 | + | {name: "detach", frame: wire.Frame{Type: wire.MsgDetach}, want: protocol.Detach{}}, | |
| 82 | + | {name: "ping", frame: wire.Frame{Type: wire.MsgPing}, want: protocol.Ping{}}, | |
| 83 | + | {name: "list", frame: wire.Frame{Type: wire.MsgList}, want: protocol.List{}}, | |
| 84 | + | {name: "kill", frame: wire.Frame{Type: wire.MsgKill, Payload: wire.MarshalKill(protocol.Kill{Name: "work"})}, want: protocol.Kill{Name: "work"}}, | |
| 85 | + | {name: "theme", frame: wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(protocol.Theme{TrueColor: true})}, want: protocol.Theme{TrueColor: true}}, | |
| 86 | + | {name: "ack", frame: wire.Frame{Type: wire.MsgAck, Payload: ackPayload}, want: protocol.Ack{Epoch: 1, State: 2}}, | |
| 87 | + | {name: "image", frame: wire.Frame{Type: wire.MsgImagePush, Payload: wire.MarshalImagePush(protocol.ImagePush{InputSeq: 2, Mime: "image/png", Data: []byte{1}})}, want: protocol.ImagePush{InputSeq: 2, Mime: "image/png", Data: []byte{1}}}, | |
| 88 | + | {name: "notice", frame: wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(protocol.ClientNotice{Action: protocol.ClientNoticeLinkConnected})}, want: protocol.ClientNotice{Action: protocol.ClientNoticeLinkConnected}}, | |
| 89 | + | {name: "command", frame: wire.Frame{Type: wire.MsgCommand, Payload: commandPayload}, want: protocol.CommandRequest{Version: protocol.Version, RequestID: 7, Slug: "list-sessions"}}, | |
| 90 | + | {name: "reset", frame: wire.Frame{Type: wire.MsgOutputResetRequest}, want: protocol.OutputResetRequest{}}, | |
| 91 | + | {name: "preview", frame: wire.Frame{Type: wire.MsgRemotePreviewRequest, Payload: wire.MarshalRemotePreviewRequest(protocol.RemotePreviewRequest{Version: protocol.RemotePreviewSchemaVersion, Target: target, Width: 1, Height: 1})}, want: protocol.RemotePreviewRequest{Version: protocol.RemotePreviewSchemaVersion, Target: target, Width: 1, Height: 1}}, | |
| 92 | + | {name: "attention", frame: wire.Frame{Type: wire.MsgRouteAttentionSubscription, Payload: attentionPayload}, want: protocol.RouteAttentionSubscription{Targets: []protocol.RouteAttentionTarget{}}}, | |
| 93 | + | {name: "same peer", frame: wire.Frame{Type: wire.MsgSamePeerSwitchRequest, Payload: switchPayload}, want: protocol.SamePeerSwitchRequest{RequestID: 3, Target: exact}}, | |
| 94 | + | {name: "parked", frame: wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: wire.MarshalParkedRouteRequest(protocol.ParkedRouteRequest{RequestID: 4, LeaseID: lease, Action: protocol.ParkedRoutePrepare})}, want: protocol.ParkedRouteRequest{RequestID: 4, LeaseID: lease, Action: protocol.ParkedRoutePrepare}}, | |
| 95 | + | {name: "snapshot", frame: wire.Frame{Type: wire.MsgRecentRouteSnapshot, Payload: snapshotPayload}, want: protocol.RecentRouteSnapshot{}}, | |
| 96 | + | {name: "route failure", frame: wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: failurePayload}, want: protocol.RouteNavigationFailure{Key: 1, Generation: 1, Code: protocol.RouteFailureUnavailable}}, | |
| 97 | + | } | |
| 98 | + | for _, tt := range tests { | |
| 99 | + | t.Run(tt.name, func(t *testing.T) { | |
| 100 | + | raw := &scriptedTransport{recv: tt.frame} | |
| 101 | + | got, err := NewServerConnection(raw).ReceiveClient() | |
| 102 | + | require.NoError(t, err) | |
| 103 | + | require.Equal(t, tt.want, got) | |
| 104 | + | }) | |
| 105 | + | } | |
| 106 | + | } | |
| 107 | + | ||
| 108 | + | func TestServerConnectionEncodesEveryServerMessage(t *testing.T) { | |
| 109 | + | exact := protocol.ExactSessionTarget{LifecycleID: domain.SessionLifecycleID{1}, SessionName: "work"} | |
| 110 | + | lease := protocol.ParkedRouteLeaseID{1} | |
| 111 | + | tests := []struct { | |
| 112 | + | name string | |
| 113 | + | message protocol.ServerMessage | |
| 114 | + | typeID wire.MsgType | |
| 115 | + | }{ | |
| 116 | + | {name: "welcome", message: protocol.Welcome{SessionID: "session"}, typeID: wire.MsgWelcome}, | |
| 117 | + | {name: "error", message: protocol.ErrorMsg{Code: protocol.ErrInternal, Text: "error"}, typeID: wire.MsgError}, | |
| 118 | + | {name: "output", message: protocol.Output{Epoch: 1, New: 1, Full: true, Size: domain.Size{Cols: 80, Rows: 24}}, typeID: wire.MsgOutput}, | |
| 119 | + | {name: "detached", message: protocol.Detached{Reason: protocol.ReasonDetach}, typeID: wire.MsgDetached}, | |
| 120 | + | {name: "pong", message: protocol.Pong{}, typeID: wire.MsgPong}, | |
| 121 | + | {name: "sessions", message: protocol.Sessions{}, typeID: wire.MsgSessions}, | |
| 122 | + | {name: "command result", message: protocol.CommandResult{RequestID: 1, OK: true}, typeID: wire.MsgCommandResult}, | |
| 123 | + | {name: "navigation", message: protocol.NavigationDirective{Action: protocol.NavigationOpenHomePicker, LeaseID: lease}, typeID: wire.MsgNavigationAction}, | |
| 124 | + | {name: "attach target", message: protocol.AttachTarget{Session: "work", Intent: protocol.IntentAttach}, typeID: wire.MsgAttachTarget}, | |
| 125 | + | {name: "preview", message: protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: protocol.RemotePreviewUnavailable}, typeID: wire.MsgRemotePreviewResponse}, | |
| 126 | + | {name: "identity", message: protocol.CommittedRouteIdentity{Target: exact}, typeID: wire.MsgCommittedRouteIdentity}, | |
| 127 | + | {name: "route action", message: protocol.RouteNavigationAction{SnapshotGeneration: 1, Key: 1, Generation: 1}, typeID: wire.MsgNavigateRecentRoute}, | |
| 128 | + | {name: "route failure", message: protocol.RouteNavigationFailure{Key: 1, Generation: 1, Code: protocol.RouteFailureUnavailable}, typeID: wire.MsgRouteNavigationFailure}, | |
| 129 | + | {name: "route position", message: protocol.RoutePosition{Target: exact, ActiveTabID: "tab-1"}, typeID: wire.MsgRoutePosition}, | |
| 130 | + | {name: "switch failure", message: protocol.SamePeerSwitchFailure{RequestID: 1, Code: protocol.SamePeerSwitchUnavailable}, typeID: wire.MsgSamePeerSwitchFailure}, | |
| 131 | + | {name: "parked response", message: protocol.ParkedRouteResponse{RequestID: 1, Status: protocol.ParkedRouteReady}, typeID: wire.MsgParkedRouteResponse}, | |
| 132 | + | } | |
| 133 | + | for _, tt := range tests { | |
| 134 | + | t.Run(tt.name, func(t *testing.T) { | |
| 135 | + | raw := &scriptedTransport{} | |
| 136 | + | require.NoError(t, NewServerConnection(raw).SendServer(tt.message)) | |
| 137 | + | require.Equal(t, tt.typeID, raw.sent.Type) | |
| 138 | + | }) | |
| 139 | + | } | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | func TestServerConnectionPreservesPayloadAgnosticClientControls(t *testing.T) { | |
| 143 | + | for _, tt := range []struct { | |
| 144 | + | name string | |
| 145 | + | frame wire.Frame | |
| 146 | + | want protocol.ClientMessage | |
| 147 | + | }{ | |
| 148 | + | {name: "list", frame: wire.Frame{Type: wire.MsgList, Payload: []byte{1}}, want: protocol.List{}}, | |
| 149 | + | {name: "detach", frame: wire.Frame{Type: wire.MsgDetach, Payload: []byte{1}}, want: protocol.Detach{}}, | |
| 150 | + | {name: "ping", frame: wire.Frame{Type: wire.MsgPing, Payload: []byte{1}}, want: protocol.Ping{}}, | |
| 151 | + | } { | |
| 152 | + | t.Run(tt.name, func(t *testing.T) { | |
| 153 | + | message, err := NewServerConnection(&scriptedTransport{recv: tt.frame}).ReceiveClient() | |
| 154 | + | require.NoError(t, err) | |
| 155 | + | require.Equal(t, tt.want, message) | |
| 156 | + | }) | |
| 157 | + | } | |
| 158 | + | } | |
| 159 | + | ||
| 160 | + | func TestServerConnectionClassifiesDecodeFailures(t *testing.T) { | |
| 161 | + | tests := []struct { | |
| 162 | + | name string | |
| 163 | + | frame wire.Frame | |
| 164 | + | category protocol.DecodeCategory | |
| 165 | + | version uint16 | |
| 166 | + | request uint64 | |
| 167 | + | }{ | |
| 168 | + | {name: "malformed hello", frame: wire.Frame{Type: wire.MsgHello, Payload: []byte{0, 38}}, category: protocol.DecodeMalformed, version: 38}, | |
| 169 | + | {name: "malformed command", frame: wire.Frame{Type: wire.MsgCommand, Payload: []byte{0, 37, 0, 0, 0, 0, 0, 0, 0, 9}}, category: protocol.DecodeMalformed, version: 37, request: 9}, | |
| 170 | + | {name: "wrong direction", frame: wire.Frame{Type: wire.MsgOutput}, category: protocol.DecodeWrongDirection}, | |
| 171 | + | {name: "unknown", frame: wire.Frame{Type: 255}, category: protocol.DecodeUnknownType}, | |
| 172 | + | } | |
| 173 | + | for _, tt := range tests { | |
| 174 | + | t.Run(tt.name, func(t *testing.T) { | |
| 175 | + | _, err := NewServerConnection(&scriptedTransport{recv: tt.frame}).ReceiveClient() | |
| 176 | + | var failure *protocol.DecodeFailure | |
| 177 | + | require.ErrorAs(t, err, &failure) | |
| 178 | + | require.Equal(t, tt.category, failure.Category) | |
| 179 | + | require.Equal(t, tt.version, failure.Version) | |
| 180 | + | require.Equal(t, tt.request, failure.RequestID) | |
| 181 | + | }) | |
| 182 | + | } | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | func TestServerConnectionPreservesSendModesCapabilitiesAndErrors(t *testing.T) { | |
| 186 | + | sendErr := errors.New("send failed") | |
| 187 | + | events := make(chan ports.LinkEvent, 1) | |
| 188 | + | raw := &capableTransport{scriptedTransport: scriptedTransport{sendErr: sendErr}, events: events} | |
| 189 | + | connection := NewServerConnection(raw) | |
| 190 | + | ||
| 191 | + | caps := connection.Capabilities() | |
| 192 | + | require.Equal(t, uint8(1), caps.PreferredOutputWindow) | |
| 193 | + | require.Equal(t, protocol.MaxOutputDataLen, caps.OutputDataLimit) | |
| 194 | + | require.True(t, caps.AsyncSend) | |
| 195 | + | require.True(t, caps.OwnedSynchronousSend) | |
| 196 | + | require.True(t, caps.LinkState) | |
| 197 | + | require.Equal(t, ports.LinkStateDegraded, connection.LinkState()) | |
| 198 | + | require.Equal(t, (<-chan ports.LinkEvent)(events), connection.LinkEvents()) | |
| 199 | + | ||
| 200 | + | message := protocol.Pong{} | |
| 201 | + | require.ErrorIs(t, connection.SendServer(message), sendErr) | |
| 202 | + | require.Equal(t, wire.MsgPong, raw.sent.Type) | |
| 203 | + | require.ErrorIs(t, connection.SendServerAsync(message), sendErr) | |
| 204 | + | require.Equal(t, wire.MsgPong, raw.async.Type) | |
| 205 | + | require.ErrorIs(t, connection.SendServerSynchronous(message), sendErr) | |
| 206 | + | require.Equal(t, wire.MsgPong, raw.synchronous.Type) | |
| 207 | + | ||
| 208 | + | output := protocol.Output{Epoch: 1, New: 1, Full: true, Size: domain.Size{Cols: 80, Rows: 24}} | |
| 209 | + | require.ErrorIs(t, connection.SendOutput(output), sendErr) | |
| 210 | + | require.Equal(t, wire.MsgOutput, raw.sent.Type) | |
| 211 | + | require.ErrorIs(t, connection.SendOutputAsync(output), sendErr) | |
| 212 | + | require.Equal(t, wire.MsgOutput, raw.async.Type) | |
| 213 | + | require.ErrorIs(t, connection.SendOutputSynchronous(output), sendErr) | |
| 214 | + | require.Equal(t, wire.MsgOutput, raw.synchronous.Type) | |
| 215 | + | ||
| 216 | + | plain := NewServerConnection(&scriptedTransport{}) | |
| 217 | + | require.ErrorIs(t, plain.SendServerAsync(message), ErrUnsupportedSend) | |
| 218 | + | require.ErrorIs(t, plain.SendServerSynchronous(message), ErrUnsupportedSend) | |
| 219 | + | require.ErrorIs(t, plain.SendOutputAsync(output), ErrUnsupportedSend) | |
| 220 | + | require.ErrorIs(t, plain.SendOutputSynchronous(output), ErrUnsupportedSend) | |
| 221 | + | require.Equal(t, uint8(8), plain.Capabilities().PreferredOutputWindow) | |
| 222 | + | require.Equal(t, ports.LinkStateConnected, plain.LinkState()) | |
| 223 | + | require.Nil(t, plain.LinkEvents()) | |
| 224 | + | } | |
| 225 | + | ||
| 226 | + | func TestServerConnectionForwardsReceiveAndCloseErrors(t *testing.T) { | |
| 227 | + | recvErr := errors.Join(io.EOF, errors.New("closed")) | |
| 228 | + | closeErr := errors.New("close failed") | |
| 229 | + | raw := &scriptedTransport{recvErr: recvErr, closeErr: closeErr} | |
| 230 | + | connection := NewServerConnection(raw) | |
| 231 | + | _, err := connection.ReceiveClient() | |
| 232 | + | require.ErrorIs(t, err, io.EOF) | |
| 233 | + | require.ErrorIs(t, connection.Close(), closeErr) | |
| 234 | + | require.Equal(t, 1, raw.closeCall) | |
| 235 | + | } | |
| 236 | + | ||
| 237 | + | func TestServerListenerWrapsEachAcceptedIncarnation(t *testing.T) { | |
| 238 | + | raw := &scriptedTransport{recv: wire.Frame{Type: wire.MsgPing}} | |
| 239 | + | listener := &scriptedListener{transport: raw} | |
| 240 | + | wrapped := NewServerListener(listener) | |
| 241 | + | first, err := wrapped.Accept() | |
| 242 | + | require.NoError(t, err) | |
| 243 | + | message, err := first.ReceiveClient() | |
| 244 | + | require.NoError(t, err) | |
| 245 | + | require.Equal(t, protocol.Ping{}, message) | |
| 246 | + | require.Equal(t, "test", wrapped.Addr()) | |
| 247 | + | require.NoError(t, wrapped.Close()) | |
| 248 | + | } | |
| 249 | + | ||
| 250 | + | type scriptedListener struct{ transport ports.Transport } | |
| 251 | + | ||
| 252 | + | func (l *scriptedListener) Accept() (ports.Transport, error) { return l.transport, nil } | |
| 253 | + | func (*scriptedListener) Close() error { return nil } | |
| 254 | + | func (*scriptedListener) Addr() string { return "test" } | |
M internal/app/daemon_integration_test.go +7 -6
| @@ -24,6 +24,7 @@ import ( | |||
| 24 | 24 | "github.com/bnema/vev/internal/adapters/ipc" | |
| 25 | 25 | "github.com/bnema/vev/internal/adapters/lifecycle" | |
| 26 | 26 | "github.com/bnema/vev/internal/adapters/pty" | |
| 27 | + | "github.com/bnema/vev/internal/adapters/sessionwire" | |
| 27 | 28 | "github.com/bnema/vev/internal/adapters/snapshot" | |
| 28 | 29 | "github.com/bnema/vev/internal/domain" | |
| 29 | 30 | "github.com/bnema/vev/internal/persist" | |
| @@ -58,7 +59,7 @@ func startDaemonInDir(t *testing.T, dir string, opts ...daemon.Option) (string, | |||
| 58 | 59 | d := daemon.New(pty.NewFactory(), clock.New(), discardLog(), opts...) | |
| 59 | 60 | ctx, cancel := context.WithCancel(context.Background()) | |
| 60 | 61 | ch := make(chan error, 1) | |
| 61 | - | go func() { ch <- d.Serve(ctx, ln) }() | |
| 62 | + | go func() { ch <- d.Serve(ctx, sessionwire.NewServerListener(ln)) }() | |
| 62 | 63 | t.Cleanup(cancel) | |
| 63 | 64 | return dir, ch | |
| 64 | 65 | } | |
| @@ -611,7 +612,7 @@ func TestLifecycleOwnershipOutlivesMaintenanceWriter(t *testing.T) { | |||
| 611 | 612 | if err != nil { | |
| 612 | 613 | return err | |
| 613 | 614 | } | |
| 614 | - | return d.Serve(ctx, listener) | |
| 615 | + | return d.Serve(ctx, sessionwire.NewServerListener(listener)) | |
| 615 | 616 | }) | |
| 616 | 617 | }() | |
| 617 | 618 | ||
| @@ -746,7 +747,7 @@ func TestLifecycleOwnershipOutlivesRestorationWriter(t *testing.T) { | |||
| 746 | 747 | daemon.WithCatalogue(opened.Catalogue, opened.Records), | |
| 747 | 748 | daemon.WithSnapshotRepository(blocking), | |
| 748 | 749 | ) | |
| 749 | - | return d.Serve(ctx, listener) | |
| 750 | + | return d.Serve(ctx, sessionwire.NewServerListener(listener)) | |
| 750 | 751 | }) | |
| 751 | 752 | }() | |
| 752 | 753 | ||
| @@ -800,7 +801,7 @@ func publishRestorableCheckpoint(t *testing.T, stateDir string, repository *snap | |||
| 800 | 801 | daemon.WithRecoveryCoordinator(coordinator), | |
| 801 | 802 | ) | |
| 802 | 803 | served := make(chan error, 1) | |
| 803 | - | go func() { served <- d.Serve(ctx, listener) }() | |
| 804 | + | go func() { served <- d.Serve(ctx, sessionwire.NewServerListener(listener)) }() | |
| 804 | 805 | ||
| 805 | 806 | tr, _ := attach(t, runtimeDir, protocol.IntentNew, name, domain.Size{Cols: 80, Rows: 24}) | |
| 806 | 807 | require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("checkpoint me\n")})})) | |
| @@ -883,7 +884,7 @@ func runBlockedSnapshotWriterShutdown(t *testing.T) blockedSnapshotWriterShutdow | |||
| 883 | 884 | daemon.WithShell("/bin/cat", nil), | |
| 884 | 885 | daemon.WithSnapshotRepository(repository), | |
| 885 | 886 | ) | |
| 886 | - | return d.Serve(ctx, observed) | |
| 887 | + | return d.Serve(ctx, sessionwire.NewServerListener(observed)) | |
| 887 | 888 | }) | |
| 888 | 889 | }() | |
| 889 | 890 | ||
| @@ -1074,7 +1075,7 @@ func TestLifecycleSocketCloseCatalogueRace(t *testing.T) { | |||
| 1074 | 1075 | oldCtx, stopOld := context.WithCancel(context.Background()) | |
| 1075 | 1076 | oldDone := make(chan error, 1) | |
| 1076 | 1077 | go func() { | |
| 1077 | - | serveErr := oldDaemon.Serve(oldCtx, controlledListener) | |
| 1078 | + | serveErr := oldDaemon.Serve(oldCtx, sessionwire.NewServerListener(controlledListener)) | |
| 1078 | 1079 | teardownEvents <- "owner-release" | |
| 1079 | 1080 | close(ownerReleaseEntered) | |
| 1080 | 1081 | <-allowOwnerRelease | |
M internal/app/run.go +2 -1
| @@ -37,6 +37,7 @@ import ( | |||
| 37 | 37 | "github.com/bnema/vev/internal/adapters/observability" | |
| 38 | 38 | "github.com/bnema/vev/internal/adapters/pty" | |
| 39 | 39 | remoteadapter "github.com/bnema/vev/internal/adapters/remote" | |
| 40 | + | "github.com/bnema/vev/internal/adapters/sessionwire" | |
| 40 | 41 | "github.com/bnema/vev/internal/adapters/shellcmd" | |
| 41 | 42 | snapshotadapter "github.com/bnema/vev/internal/adapters/snapshot" | |
| 42 | 43 | "github.com/bnema/vev/internal/adapters/sshstdio" | |
| @@ -742,7 +743,7 @@ func runDaemonOwnedWithLogger(ctx context.Context, log *slog.Logger) (retErr err | |||
| 742 | 743 | log.Warn("config watcher stopped", "path", configPath, "err", err) | |
| 743 | 744 | } | |
| 744 | 745 | }() | |
| 745 | - | if err := d.Serve(ctx, ln); err != nil { | |
| 746 | + | if err := d.Serve(ctx, sessionwire.NewServerListener(ln)); err != nil { | |
| 746 | 747 | log.Error("daemon exited", "err", err) | |
| 747 | 748 | return err | |
| 748 | 749 | } | |
A internal/ports/mocks/session_mock.go +747 -0
| @@ -0,0 +1,747 @@ | |||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |
| 2 | + | // github.com/vektra/mockery | |
| 3 | + | // template: testify | |
| 4 | + | ||
| 5 | + | package portsmocks | |
| 6 | + | ||
| 7 | + | import ( | |
| 8 | + | "github.com/bnema/vev/internal/ports" | |
| 9 | + | "github.com/bnema/vev/internal/protocol" | |
| 10 | + | mock "github.com/stretchr/testify/mock" | |
| 11 | + | ) | |
| 12 | + | ||
| 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. | |
| 14 | + | // The first argument is typically a *testing.T value. | |
| 15 | + | func NewMockServerConnection(t interface { | |
| 16 | + | mock.TestingT | |
| 17 | + | Cleanup(func()) | |
| 18 | + | }) *MockServerConnection { | |
| 19 | + | mock := &MockServerConnection{} | |
| 20 | + | mock.Mock.Test(t) | |
| 21 | + | ||
| 22 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 23 | + | ||
| 24 | + | return mock | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | // MockServerConnection is an autogenerated mock type for the ServerConnection type | |
| 28 | + | type MockServerConnection struct { | |
| 29 | + | mock.Mock | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | type MockServerConnection_Expecter struct { | |
| 33 | + | mock *mock.Mock | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | func (_m *MockServerConnection) EXPECT() *MockServerConnection_Expecter { | |
| 37 | + | return &MockServerConnection_Expecter{mock: &_m.Mock} | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | // Capabilities provides a mock function for the type MockServerConnection | |
| 41 | + | func (_mock *MockServerConnection) Capabilities() protocol.ConnectionCapabilities { | |
| 42 | + | ret := _mock.Called() | |
| 43 | + | ||
| 44 | + | if len(ret) == 0 { | |
| 45 | + | panic("no return value specified for Capabilities") | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | var r0 protocol.ConnectionCapabilities | |
| 49 | + | if returnFunc, ok := ret.Get(0).(func() protocol.ConnectionCapabilities); ok { | |
| 50 | + | r0 = returnFunc() | |
| 51 | + | } else { | |
| 52 | + | r0 = ret.Get(0).(protocol.ConnectionCapabilities) | |
| 53 | + | } | |
| 54 | + | return r0 | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | // MockServerConnection_Capabilities_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Capabilities' | |
| 58 | + | type MockServerConnection_Capabilities_Call struct { | |
| 59 | + | *mock.Call | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | // Capabilities is a helper method to define mock.On call | |
| 63 | + | func (_e *MockServerConnection_Expecter) Capabilities() *MockServerConnection_Capabilities_Call { | |
| 64 | + | return &MockServerConnection_Capabilities_Call{Call: _e.mock.On("Capabilities")} | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | func (_c *MockServerConnection_Capabilities_Call) Run(run func()) *MockServerConnection_Capabilities_Call { | |
| 68 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 69 | + | run() | |
| 70 | + | }) | |
| 71 | + | return _c | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | func (_c *MockServerConnection_Capabilities_Call) Return(connectionCapabilities protocol.ConnectionCapabilities) *MockServerConnection_Capabilities_Call { | |
| 75 | + | _c.Call.Return(connectionCapabilities) | |
| 76 | + | return _c | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | func (_c *MockServerConnection_Capabilities_Call) RunAndReturn(run func() protocol.ConnectionCapabilities) *MockServerConnection_Capabilities_Call { | |
| 80 | + | _c.Call.Return(run) | |
| 81 | + | return _c | |
| 82 | + | } | |
| 83 | + | ||
| 84 | + | // Close provides a mock function for the type MockServerConnection | |
| 85 | + | func (_mock *MockServerConnection) Close() error { | |
| 86 | + | ret := _mock.Called() | |
| 87 | + | ||
| 88 | + | if len(ret) == 0 { | |
| 89 | + | panic("no return value specified for Close") | |
| 90 | + | } | |
| 91 | + | ||
| 92 | + | var r0 error | |
| 93 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |
| 94 | + | r0 = returnFunc() | |
| 95 | + | } else { | |
| 96 | + | r0 = ret.Error(0) | |
| 97 | + | } | |
| 98 | + | return r0 | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | // MockServerConnection_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |
| 102 | + | type MockServerConnection_Close_Call struct { | |
| 103 | + | *mock.Call | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | // Close is a helper method to define mock.On call | |
| 107 | + | func (_e *MockServerConnection_Expecter) Close() *MockServerConnection_Close_Call { | |
| 108 | + | return &MockServerConnection_Close_Call{Call: _e.mock.On("Close")} | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | func (_c *MockServerConnection_Close_Call) Run(run func()) *MockServerConnection_Close_Call { | |
| 112 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 113 | + | run() | |
| 114 | + | }) | |
| 115 | + | return _c | |
| 116 | + | } | |
| 117 | + | ||
| 118 | + | func (_c *MockServerConnection_Close_Call) Return(err error) *MockServerConnection_Close_Call { | |
| 119 | + | _c.Call.Return(err) | |
| 120 | + | return _c | |
| 121 | + | } | |
| 122 | + | ||
| 123 | + | func (_c *MockServerConnection_Close_Call) RunAndReturn(run func() error) *MockServerConnection_Close_Call { | |
| 124 | + | _c.Call.Return(run) | |
| 125 | + | return _c | |
| 126 | + | } | |
| 127 | + | ||
| 128 | + | // LinkEvents provides a mock function for the type MockServerConnection | |
| 129 | + | func (_mock *MockServerConnection) LinkEvents() <-chan ports.LinkEvent { | |
| 130 | + | ret := _mock.Called() | |
| 131 | + | ||
| 132 | + | if len(ret) == 0 { | |
| 133 | + | panic("no return value specified for LinkEvents") | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | var r0 <-chan ports.LinkEvent | |
| 137 | + | if returnFunc, ok := ret.Get(0).(func() <-chan ports.LinkEvent); ok { | |
| 138 | + | r0 = returnFunc() | |
| 139 | + | } else { | |
| 140 | + | if ret.Get(0) != nil { | |
| 141 | + | r0 = ret.Get(0).(<-chan ports.LinkEvent) | |
| 142 | + | } | |
| 143 | + | } | |
| 144 | + | return r0 | |
| 145 | + | } | |
| 146 | + | ||
| 147 | + | // MockServerConnection_LinkEvents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LinkEvents' | |
| 148 | + | type MockServerConnection_LinkEvents_Call struct { | |
| 149 | + | *mock.Call | |
| 150 | + | } | |
| 151 | + | ||
| 152 | + | // LinkEvents is a helper method to define mock.On call | |
| 153 | + | func (_e *MockServerConnection_Expecter) LinkEvents() *MockServerConnection_LinkEvents_Call { | |
| 154 | + | return &MockServerConnection_LinkEvents_Call{Call: _e.mock.On("LinkEvents")} | |
| 155 | + | } | |
| 156 | + | ||
| 157 | + | func (_c *MockServerConnection_LinkEvents_Call) Run(run func()) *MockServerConnection_LinkEvents_Call { | |
| 158 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 159 | + | run() | |
| 160 | + | }) | |
| 161 | + | return _c | |
| 162 | + | } | |
| 163 | + | ||
| 164 | + | func (_c *MockServerConnection_LinkEvents_Call) Return(linkEventCh <-chan ports.LinkEvent) *MockServerConnection_LinkEvents_Call { | |
| 165 | + | _c.Call.Return(linkEventCh) | |
| 166 | + | return _c | |
| 167 | + | } | |
| 168 | + | ||
| 169 | + | func (_c *MockServerConnection_LinkEvents_Call) RunAndReturn(run func() <-chan ports.LinkEvent) *MockServerConnection_LinkEvents_Call { | |
| 170 | + | _c.Call.Return(run) | |
| 171 | + | return _c | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | // LinkState provides a mock function for the type MockServerConnection | |
| 175 | + | func (_mock *MockServerConnection) LinkState() ports.LinkState { | |
| 176 | + | ret := _mock.Called() | |
| 177 | + | ||
| 178 | + | if len(ret) == 0 { | |
| 179 | + | panic("no return value specified for LinkState") | |
| 180 | + | } | |
| 181 | + | ||
| 182 | + | var r0 ports.LinkState | |
| 183 | + | if returnFunc, ok := ret.Get(0).(func() ports.LinkState); ok { | |
| 184 | + | r0 = returnFunc() | |
| 185 | + | } else { | |
| 186 | + | r0 = ret.Get(0).(ports.LinkState) | |
| 187 | + | } | |
| 188 | + | return r0 | |
| 189 | + | } | |
| 190 | + | ||
| 191 | + | // MockServerConnection_LinkState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LinkState' | |
| 192 | + | type MockServerConnection_LinkState_Call struct { | |
| 193 | + | *mock.Call | |
| 194 | + | } | |
| 195 | + | ||
| 196 | + | // LinkState is a helper method to define mock.On call | |
| 197 | + | func (_e *MockServerConnection_Expecter) LinkState() *MockServerConnection_LinkState_Call { | |
| 198 | + | return &MockServerConnection_LinkState_Call{Call: _e.mock.On("LinkState")} | |
| 199 | + | } | |
| 200 | + | ||
| 201 | + | func (_c *MockServerConnection_LinkState_Call) Run(run func()) *MockServerConnection_LinkState_Call { | |
| 202 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 203 | + | run() | |
| 204 | + | }) | |
| 205 | + | return _c | |
| 206 | + | } | |
| 207 | + | ||
| 208 | + | func (_c *MockServerConnection_LinkState_Call) Return(linkState ports.LinkState) *MockServerConnection_LinkState_Call { | |
| 209 | + | _c.Call.Return(linkState) | |
| 210 | + | return _c | |
| 211 | + | } | |
| 212 | + | ||
| 213 | + | func (_c *MockServerConnection_LinkState_Call) RunAndReturn(run func() ports.LinkState) *MockServerConnection_LinkState_Call { | |
| 214 | + | _c.Call.Return(run) | |
| 215 | + | return _c | |
| 216 | + | } | |
| 217 | + | ||
| 218 | + | // ReceiveClient provides a mock function for the type MockServerConnection | |
| 219 | + | func (_mock *MockServerConnection) ReceiveClient() (protocol.ClientMessage, error) { | |
| 220 | + | ret := _mock.Called() | |
| 221 | + | ||
| 222 | + | if len(ret) == 0 { | |
| 223 | + | panic("no return value specified for ReceiveClient") | |
| 224 | + | } | |
| 225 | + | ||
| 226 | + | var r0 protocol.ClientMessage | |
| 227 | + | var r1 error | |
| 228 | + | if returnFunc, ok := ret.Get(0).(func() (protocol.ClientMessage, error)); ok { | |
| 229 | + | return returnFunc() | |
| 230 | + | } | |
| 231 | + | if returnFunc, ok := ret.Get(0).(func() protocol.ClientMessage); ok { | |
| 232 | + | r0 = returnFunc() | |
| 233 | + | } else { | |
| 234 | + | if ret.Get(0) != nil { | |
| 235 | + | r0 = ret.Get(0).(protocol.ClientMessage) | |
| 236 | + | } | |
| 237 | + | } | |
| 238 | + | if returnFunc, ok := ret.Get(1).(func() error); ok { | |
| 239 | + | r1 = returnFunc() | |
| 240 | + | } else { | |
| 241 | + | r1 = ret.Error(1) | |
| 242 | + | } | |
| 243 | + | return r0, r1 | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | // MockServerConnection_ReceiveClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReceiveClient' | |
| 247 | + | type MockServerConnection_ReceiveClient_Call struct { | |
| 248 | + | *mock.Call | |
| 249 | + | } | |
| 250 | + | ||
| 251 | + | // ReceiveClient is a helper method to define mock.On call | |
| 252 | + | func (_e *MockServerConnection_Expecter) ReceiveClient() *MockServerConnection_ReceiveClient_Call { | |
| 253 | + | return &MockServerConnection_ReceiveClient_Call{Call: _e.mock.On("ReceiveClient")} | |
| 254 | + | } | |
| 255 | + | ||
| 256 | + | func (_c *MockServerConnection_ReceiveClient_Call) Run(run func()) *MockServerConnection_ReceiveClient_Call { | |
| 257 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 258 | + | run() | |
| 259 | + | }) | |
| 260 | + | return _c | |
| 261 | + | } | |
| 262 | + | ||
| 263 | + | func (_c *MockServerConnection_ReceiveClient_Call) Return(clientMessage protocol.ClientMessage, err error) *MockServerConnection_ReceiveClient_Call { | |
| 264 | + | _c.Call.Return(clientMessage, err) | |
| 265 | + | return _c | |
| 266 | + | } | |
| 267 | + | ||
| 268 | + | func (_c *MockServerConnection_ReceiveClient_Call) RunAndReturn(run func() (protocol.ClientMessage, error)) *MockServerConnection_ReceiveClient_Call { | |
| 269 | + | _c.Call.Return(run) | |
| 270 | + | return _c | |
| 271 | + | } | |
| 272 | + | ||
| 273 | + | // SendOutput provides a mock function for the type MockServerConnection | |
| 274 | + | func (_mock *MockServerConnection) SendOutput(output protocol.Output) error { | |
| 275 | + | ret := _mock.Called(output) | |
| 276 | + | ||
| 277 | + | if len(ret) == 0 { | |
| 278 | + | panic("no return value specified for SendOutput") | |
| 279 | + | } | |
| 280 | + | ||
| 281 | + | var r0 error | |
| 282 | + | if returnFunc, ok := ret.Get(0).(func(protocol.Output) error); ok { | |
| 283 | + | r0 = returnFunc(output) | |
| 284 | + | } else { | |
| 285 | + | r0 = ret.Error(0) | |
| 286 | + | } | |
| 287 | + | return r0 | |
| 288 | + | } | |
| 289 | + | ||
| 290 | + | // MockServerConnection_SendOutput_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendOutput' | |
| 291 | + | type MockServerConnection_SendOutput_Call struct { | |
| 292 | + | *mock.Call | |
| 293 | + | } | |
| 294 | + | ||
| 295 | + | // SendOutput is a helper method to define mock.On call | |
| 296 | + | // - output protocol.Output | |
| 297 | + | func (_e *MockServerConnection_Expecter) SendOutput(output any) *MockServerConnection_SendOutput_Call { | |
| 298 | + | return &MockServerConnection_SendOutput_Call{Call: _e.mock.On("SendOutput", output)} | |
| 299 | + | } | |
| 300 | + | ||
| 301 | + | func (_c *MockServerConnection_SendOutput_Call) Run(run func(output protocol.Output)) *MockServerConnection_SendOutput_Call { | |
| 302 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 303 | + | var arg0 protocol.Output | |
| 304 | + | if args[0] != nil { | |
| 305 | + | arg0 = args[0].(protocol.Output) | |
| 306 | + | } | |
| 307 | + | run( | |
| 308 | + | arg0, | |
| 309 | + | ) | |
| 310 | + | }) | |
| 311 | + | return _c | |
| 312 | + | } | |
| 313 | + | ||
| 314 | + | func (_c *MockServerConnection_SendOutput_Call) Return(err error) *MockServerConnection_SendOutput_Call { | |
| 315 | + | _c.Call.Return(err) | |
| 316 | + | return _c | |
| 317 | + | } | |
| 318 | + | ||
| 319 | + | func (_c *MockServerConnection_SendOutput_Call) RunAndReturn(run func(output protocol.Output) error) *MockServerConnection_SendOutput_Call { | |
| 320 | + | _c.Call.Return(run) | |
| 321 | + | return _c | |
| 322 | + | } | |
| 323 | + | ||
| 324 | + | // SendOutputAsync provides a mock function for the type MockServerConnection | |
| 325 | + | func (_mock *MockServerConnection) SendOutputAsync(output protocol.Output) error { | |
| 326 | + | ret := _mock.Called(output) | |
| 327 | + | ||
| 328 | + | if len(ret) == 0 { | |
| 329 | + | panic("no return value specified for SendOutputAsync") | |
| 330 | + | } | |
| 331 | + | ||
| 332 | + | var r0 error | |
| 333 | + | if returnFunc, ok := ret.Get(0).(func(protocol.Output) error); ok { | |
| 334 | + | r0 = returnFunc(output) | |
| 335 | + | } else { | |
| 336 | + | r0 = ret.Error(0) | |
| 337 | + | } | |
| 338 | + | return r0 | |
| 339 | + | } | |
| 340 | + | ||
| 341 | + | // MockServerConnection_SendOutputAsync_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendOutputAsync' | |
| 342 | + | type MockServerConnection_SendOutputAsync_Call struct { | |
| 343 | + | *mock.Call | |
| 344 | + | } | |
| 345 | + | ||
| 346 | + | // SendOutputAsync is a helper method to define mock.On call | |
| 347 | + | // - output protocol.Output | |
| 348 | + | func (_e *MockServerConnection_Expecter) SendOutputAsync(output any) *MockServerConnection_SendOutputAsync_Call { | |
| 349 | + | return &MockServerConnection_SendOutputAsync_Call{Call: _e.mock.On("SendOutputAsync", output)} | |
| 350 | + | } | |
| 351 | + | ||
| 352 | + | func (_c *MockServerConnection_SendOutputAsync_Call) Run(run func(output protocol.Output)) *MockServerConnection_SendOutputAsync_Call { | |
| 353 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 354 | + | var arg0 protocol.Output | |
| 355 | + | if args[0] != nil { | |
| 356 | + | arg0 = args[0].(protocol.Output) | |
| 357 | + | } | |
| 358 | + | run( | |
| 359 | + | arg0, | |
| 360 | + | ) | |
| 361 | + | }) | |
| 362 | + | return _c | |
| 363 | + | } | |
| 364 | + | ||
| 365 | + | func (_c *MockServerConnection_SendOutputAsync_Call) Return(err error) *MockServerConnection_SendOutputAsync_Call { | |
| 366 | + | _c.Call.Return(err) | |
| 367 | + | return _c | |
| 368 | + | } | |
| 369 | + | ||
| 370 | + | func (_c *MockServerConnection_SendOutputAsync_Call) RunAndReturn(run func(output protocol.Output) error) *MockServerConnection_SendOutputAsync_Call { | |
| 371 | + | _c.Call.Return(run) | |
| 372 | + | return _c | |
| 373 | + | } | |
| 374 | + | ||
| 375 | + | // SendOutputSynchronous provides a mock function for the type MockServerConnection | |
| 376 | + | func (_mock *MockServerConnection) SendOutputSynchronous(output protocol.Output) error { | |
| 377 | + | ret := _mock.Called(output) | |
| 378 | + | ||
| 379 | + | if len(ret) == 0 { | |
| 380 | + | panic("no return value specified for SendOutputSynchronous") | |
| 381 | + | } | |
| 382 | + | ||
| 383 | + | var r0 error | |
| 384 | + | if returnFunc, ok := ret.Get(0).(func(protocol.Output) error); ok { | |
| 385 | + | r0 = returnFunc(output) | |
| 386 | + | } else { | |
| 387 | + | r0 = ret.Error(0) | |
| 388 | + | } | |
| 389 | + | return r0 | |
| 390 | + | } | |
| 391 | + | ||
| 392 | + | // MockServerConnection_SendOutputSynchronous_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendOutputSynchronous' | |
| 393 | + | type MockServerConnection_SendOutputSynchronous_Call struct { | |
| 394 | + | *mock.Call | |
| 395 | + | } | |
| 396 | + | ||
| 397 | + | // SendOutputSynchronous is a helper method to define mock.On call | |
| 398 | + | // - output protocol.Output | |
| 399 | + | func (_e *MockServerConnection_Expecter) SendOutputSynchronous(output any) *MockServerConnection_SendOutputSynchronous_Call { | |
| 400 | + | return &MockServerConnection_SendOutputSynchronous_Call{Call: _e.mock.On("SendOutputSynchronous", output)} | |
| 401 | + | } | |
| 402 | + | ||
| 403 | + | func (_c *MockServerConnection_SendOutputSynchronous_Call) Run(run func(output protocol.Output)) *MockServerConnection_SendOutputSynchronous_Call { | |
| 404 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 405 | + | var arg0 protocol.Output | |
| 406 | + | if args[0] != nil { | |
| 407 | + | arg0 = args[0].(protocol.Output) | |
| 408 | + | } | |
| 409 | + | run( | |
| 410 | + | arg0, | |
| 411 | + | ) | |
| 412 | + | }) | |
| 413 | + | return _c | |
| 414 | + | } | |
| 415 | + | ||
| 416 | + | func (_c *MockServerConnection_SendOutputSynchronous_Call) Return(err error) *MockServerConnection_SendOutputSynchronous_Call { | |
| 417 | + | _c.Call.Return(err) | |
| 418 | + | return _c | |
| 419 | + | } | |
| 420 | + | ||
| 421 | + | func (_c *MockServerConnection_SendOutputSynchronous_Call) RunAndReturn(run func(output protocol.Output) error) *MockServerConnection_SendOutputSynchronous_Call { | |
| 422 | + | _c.Call.Return(run) | |
| 423 | + | return _c | |
| 424 | + | } | |
| 425 | + | ||
| 426 | + | // SendServer provides a mock function for the type MockServerConnection | |
| 427 | + | func (_mock *MockServerConnection) SendServer(serverMessage protocol.ServerMessage) error { | |
| 428 | + | ret := _mock.Called(serverMessage) | |
| 429 | + | ||
| 430 | + | if len(ret) == 0 { | |
| 431 | + | panic("no return value specified for SendServer") | |
| 432 | + | } | |
| 433 | + | ||
| 434 | + | var r0 error | |
| 435 | + | if returnFunc, ok := ret.Get(0).(func(protocol.ServerMessage) error); ok { | |
| 436 | + | r0 = returnFunc(serverMessage) | |
| 437 | + | } else { | |
| 438 | + | r0 = ret.Error(0) | |
| 439 | + | } | |
| 440 | + | return r0 | |
| 441 | + | } | |
| 442 | + | ||
| 443 | + | // MockServerConnection_SendServer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendServer' | |
| 444 | + | type MockServerConnection_SendServer_Call struct { | |
| 445 | + | *mock.Call | |
| 446 | + | } | |
| 447 | + | ||
| 448 | + | // SendServer is a helper method to define mock.On call | |
| 449 | + | // - serverMessage protocol.ServerMessage | |
| 450 | + | func (_e *MockServerConnection_Expecter) SendServer(serverMessage any) *MockServerConnection_SendServer_Call { | |
| 451 | + | return &MockServerConnection_SendServer_Call{Call: _e.mock.On("SendServer", serverMessage)} | |
| 452 | + | } | |
| 453 | + | ||
| 454 | + | func (_c *MockServerConnection_SendServer_Call) Run(run func(serverMessage protocol.ServerMessage)) *MockServerConnection_SendServer_Call { | |
| 455 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 456 | + | var arg0 protocol.ServerMessage | |
| 457 | + | if args[0] != nil { | |
| 458 | + | arg0 = args[0].(protocol.ServerMessage) | |
| 459 | + | } | |
| 460 | + | run( | |
| 461 | + | arg0, | |
| 462 | + | ) | |
| 463 | + | }) | |
| 464 | + | return _c | |
| 465 | + | } | |
| 466 | + | ||
| 467 | + | func (_c *MockServerConnection_SendServer_Call) Return(err error) *MockServerConnection_SendServer_Call { | |
| 468 | + | _c.Call.Return(err) | |
| 469 | + | return _c | |
| 470 | + | } | |
| 471 | + | ||
| 472 | + | func (_c *MockServerConnection_SendServer_Call) RunAndReturn(run func(serverMessage protocol.ServerMessage) error) *MockServerConnection_SendServer_Call { | |
| 473 | + | _c.Call.Return(run) | |
| 474 | + | return _c | |
| 475 | + | } | |
| 476 | + | ||
| 477 | + | // SendServerAsync provides a mock function for the type MockServerConnection | |
| 478 | + | func (_mock *MockServerConnection) SendServerAsync(serverMessage protocol.ServerMessage) error { | |
| 479 | + | ret := _mock.Called(serverMessage) | |
| 480 | + | ||
| 481 | + | if len(ret) == 0 { | |
| 482 | + | panic("no return value specified for SendServerAsync") | |
| 483 | + | } | |
| 484 | + | ||
| 485 | + | var r0 error | |
| 486 | + | if returnFunc, ok := ret.Get(0).(func(protocol.ServerMessage) error); ok { | |
| 487 | + | r0 = returnFunc(serverMessage) | |
| 488 | + | } else { | |
| 489 | + | r0 = ret.Error(0) | |
| 490 | + | } | |
| 491 | + | return r0 | |
| 492 | + | } | |
| 493 | + | ||
| 494 | + | // MockServerConnection_SendServerAsync_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendServerAsync' | |
| 495 | + | type MockServerConnection_SendServerAsync_Call struct { | |
| 496 | + | *mock.Call | |
| 497 | + | } | |
| 498 | + | ||
| 499 | + | // SendServerAsync is a helper method to define mock.On call | |
| 500 | + | // - serverMessage protocol.ServerMessage | |
| 501 | + | func (_e *MockServerConnection_Expecter) SendServerAsync(serverMessage any) *MockServerConnection_SendServerAsync_Call { | |
| 502 | + | return &MockServerConnection_SendServerAsync_Call{Call: _e.mock.On("SendServerAsync", serverMessage)} | |
| 503 | + | } | |
| 504 | + | ||
| 505 | + | func (_c *MockServerConnection_SendServerAsync_Call) Run(run func(serverMessage protocol.ServerMessage)) *MockServerConnection_SendServerAsync_Call { | |
| 506 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 507 | + | var arg0 protocol.ServerMessage | |
| 508 | + | if args[0] != nil { | |
| 509 | + | arg0 = args[0].(protocol.ServerMessage) | |
| 510 | + | } | |
| 511 | + | run( | |
| 512 | + | arg0, | |
| 513 | + | ) | |
| 514 | + | }) | |
| 515 | + | return _c | |
| 516 | + | } | |
| 517 | + | ||
| 518 | + | func (_c *MockServerConnection_SendServerAsync_Call) Return(err error) *MockServerConnection_SendServerAsync_Call { | |
| 519 | + | _c.Call.Return(err) | |
| 520 | + | return _c | |
| 521 | + | } | |
| 522 | + | ||
| 523 | + | func (_c *MockServerConnection_SendServerAsync_Call) RunAndReturn(run func(serverMessage protocol.ServerMessage) error) *MockServerConnection_SendServerAsync_Call { | |
| 524 | + | _c.Call.Return(run) | |
| 525 | + | return _c | |
| 526 | + | } | |
| 527 | + | ||
| 528 | + | // SendServerSynchronous provides a mock function for the type MockServerConnection | |
| 529 | + | func (_mock *MockServerConnection) SendServerSynchronous(serverMessage protocol.ServerMessage) error { | |
| 530 | + | ret := _mock.Called(serverMessage) | |
| 531 | + | ||
| 532 | + | if len(ret) == 0 { | |
| 533 | + | panic("no return value specified for SendServerSynchronous") | |
| 534 | + | } | |
| 535 | + | ||
| 536 | + | var r0 error | |
| 537 | + | if returnFunc, ok := ret.Get(0).(func(protocol.ServerMessage) error); ok { | |
| 538 | + | r0 = returnFunc(serverMessage) | |
| 539 | + | } else { | |
| 540 | + | r0 = ret.Error(0) | |
| 541 | + | } | |
| 542 | + | return r0 | |
| 543 | + | } | |
| 544 | + | ||
| 545 | + | // MockServerConnection_SendServerSynchronous_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendServerSynchronous' | |
| 546 | + | type MockServerConnection_SendServerSynchronous_Call struct { | |
| 547 | + | *mock.Call | |
| 548 | + | } | |
| 549 | + | ||
| 550 | + | // SendServerSynchronous is a helper method to define mock.On call | |
| 551 | + | // - serverMessage protocol.ServerMessage | |
| 552 | + | func (_e *MockServerConnection_Expecter) SendServerSynchronous(serverMessage any) *MockServerConnection_SendServerSynchronous_Call { | |
| 553 | + | return &MockServerConnection_SendServerSynchronous_Call{Call: _e.mock.On("SendServerSynchronous", serverMessage)} | |
| 554 | + | } | |
| 555 | + | ||
| 556 | + | func (_c *MockServerConnection_SendServerSynchronous_Call) Run(run func(serverMessage protocol.ServerMessage)) *MockServerConnection_SendServerSynchronous_Call { | |
| 557 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 558 | + | var arg0 protocol.ServerMessage | |
| 559 | + | if args[0] != nil { | |
| 560 | + | arg0 = args[0].(protocol.ServerMessage) | |
| 561 | + | } | |
| 562 | + | run( | |
| 563 | + | arg0, | |
| 564 | + | ) | |
| 565 | + | }) | |
| 566 | + | return _c | |
| 567 | + | } | |
| 568 | + | ||
| 569 | + | func (_c *MockServerConnection_SendServerSynchronous_Call) Return(err error) *MockServerConnection_SendServerSynchronous_Call { | |
| 570 | + | _c.Call.Return(err) | |
| 571 | + | return _c | |
| 572 | + | } | |
| 573 | + | ||
| 574 | + | func (_c *MockServerConnection_SendServerSynchronous_Call) RunAndReturn(run func(serverMessage protocol.ServerMessage) error) *MockServerConnection_SendServerSynchronous_Call { | |
| 575 | + | _c.Call.Return(run) | |
| 576 | + | return _c | |
| 577 | + | } | |
| 578 | + | ||
| 579 | + | // NewMockServerListener creates a new instance of MockServerListener. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 580 | + | // The first argument is typically a *testing.T value. | |
| 581 | + | func NewMockServerListener(t interface { | |
| 582 | + | mock.TestingT | |
| 583 | + | Cleanup(func()) | |
| 584 | + | }) *MockServerListener { | |
| 585 | + | mock := &MockServerListener{} | |
| 586 | + | mock.Mock.Test(t) | |
| 587 | + | ||
| 588 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 589 | + | ||
| 590 | + | return mock | |
| 591 | + | } | |
| 592 | + | ||
| 593 | + | // MockServerListener is an autogenerated mock type for the ServerListener type | |
| 594 | + | type MockServerListener struct { | |
| 595 | + | mock.Mock | |
| 596 | + | } | |
| 597 | + | ||
| 598 | + | type MockServerListener_Expecter struct { | |
| 599 | + | mock *mock.Mock | |
| 600 | + | } | |
| 601 | + | ||
| 602 | + | func (_m *MockServerListener) EXPECT() *MockServerListener_Expecter { | |
| 603 | + | return &MockServerListener_Expecter{mock: &_m.Mock} | |
| 604 | + | } | |
| 605 | + | ||
| 606 | + | // Accept provides a mock function for the type MockServerListener | |
| 607 | + | func (_mock *MockServerListener) Accept() (ports.ServerConnection, error) { | |
| 608 | + | ret := _mock.Called() | |
| 609 | + | ||
| 610 | + | if len(ret) == 0 { | |
| 611 | + | panic("no return value specified for Accept") | |
| 612 | + | } | |
| 613 | + | ||
| 614 | + | var r0 ports.ServerConnection | |
| 615 | + | var r1 error | |
| 616 | + | if returnFunc, ok := ret.Get(0).(func() (ports.ServerConnection, error)); ok { | |
| 617 | + | return returnFunc() | |
| 618 | + | } | |
| 619 | + | if returnFunc, ok := ret.Get(0).(func() ports.ServerConnection); ok { | |
| 620 | + | r0 = returnFunc() | |
| 621 | + | } else { | |
| 622 | + | if ret.Get(0) != nil { | |
| 623 | + | r0 = ret.Get(0).(ports.ServerConnection) | |
| 624 | + | } | |
| 625 | + | } | |
| 626 | + | if returnFunc, ok := ret.Get(1).(func() error); ok { | |
| 627 | + | r1 = returnFunc() | |
| 628 | + | } else { | |
| 629 | + | r1 = ret.Error(1) | |
| 630 | + | } | |
| 631 | + | return r0, r1 | |
| 632 | + | } | |
| 633 | + | ||
| 634 | + | // MockServerListener_Accept_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Accept' | |
| 635 | + | type MockServerListener_Accept_Call struct { | |
| 636 | + | *mock.Call | |
| 637 | + | } | |
| 638 | + | ||
| 639 | + | // Accept is a helper method to define mock.On call | |
| 640 | + | func (_e *MockServerListener_Expecter) Accept() *MockServerListener_Accept_Call { | |
| 641 | + | return &MockServerListener_Accept_Call{Call: _e.mock.On("Accept")} | |
| 642 | + | } | |
| 643 | + | ||
| 644 | + | func (_c *MockServerListener_Accept_Call) Run(run func()) *MockServerListener_Accept_Call { | |
| 645 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 646 | + | run() | |
| 647 | + | }) | |
| 648 | + | return _c | |
| 649 | + | } | |
| 650 | + | ||
| 651 | + | func (_c *MockServerListener_Accept_Call) Return(serverConnection ports.ServerConnection, err error) *MockServerListener_Accept_Call { | |
| 652 | + | _c.Call.Return(serverConnection, err) | |
| 653 | + | return _c | |
| 654 | + | } | |
| 655 | + | ||
| 656 | + | func (_c *MockServerListener_Accept_Call) RunAndReturn(run func() (ports.ServerConnection, error)) *MockServerListener_Accept_Call { | |
| 657 | + | _c.Call.Return(run) | |
| 658 | + | return _c | |
| 659 | + | } | |
| 660 | + | ||
| 661 | + | // Addr provides a mock function for the type MockServerListener | |
| 662 | + | func (_mock *MockServerListener) Addr() string { | |
| 663 | + | ret := _mock.Called() | |
| 664 | + | ||
| 665 | + | if len(ret) == 0 { | |
| 666 | + | panic("no return value specified for Addr") | |
| 667 | + | } | |
| 668 | + | ||
| 669 | + | var r0 string | |
| 670 | + | if returnFunc, ok := ret.Get(0).(func() string); ok { | |
| 671 | + | r0 = returnFunc() | |
| 672 | + | } else { | |
| 673 | + | r0 = ret.Get(0).(string) | |
| 674 | + | } | |
| 675 | + | return r0 | |
| 676 | + | } | |
| 677 | + | ||
| 678 | + | // MockServerListener_Addr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Addr' | |
| 679 | + | type MockServerListener_Addr_Call struct { | |
| 680 | + | *mock.Call | |
| 681 | + | } | |
| 682 | + | ||
| 683 | + | // Addr is a helper method to define mock.On call | |
| 684 | + | func (_e *MockServerListener_Expecter) Addr() *MockServerListener_Addr_Call { | |
| 685 | + | return &MockServerListener_Addr_Call{Call: _e.mock.On("Addr")} | |
| 686 | + | } | |
| 687 | + | ||
| 688 | + | func (_c *MockServerListener_Addr_Call) Run(run func()) *MockServerListener_Addr_Call { | |
| 689 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 690 | + | run() | |
| 691 | + | }) | |
| 692 | + | return _c | |
| 693 | + | } | |
| 694 | + | ||
| 695 | + | func (_c *MockServerListener_Addr_Call) Return(s string) *MockServerListener_Addr_Call { | |
| 696 | + | _c.Call.Return(s) | |
| 697 | + | return _c | |
| 698 | + | } | |
| 699 | + | ||
| 700 | + | func (_c *MockServerListener_Addr_Call) RunAndReturn(run func() string) *MockServerListener_Addr_Call { | |
| 701 | + | _c.Call.Return(run) | |
| 702 | + | return _c | |
| 703 | + | } | |
| 704 | + | ||
| 705 | + | // Close provides a mock function for the type MockServerListener | |
| 706 | + | func (_mock *MockServerListener) Close() error { | |
| 707 | + | ret := _mock.Called() | |
| 708 | + | ||
| 709 | + | if len(ret) == 0 { | |
| 710 | + | panic("no return value specified for Close") | |
| 711 | + | } | |
| 712 | + | ||
| 713 | + | var r0 error | |
| 714 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |
| 715 | + | r0 = returnFunc() | |
| 716 | + | } else { | |
| 717 | + | r0 = ret.Error(0) | |
| 718 | + | } | |
| 719 | + | return r0 | |
| 720 | + | } | |
| 721 | + | ||
| 722 | + | // MockServerListener_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |
| 723 | + | type MockServerListener_Close_Call struct { | |
| 724 | + | *mock.Call | |
| 725 | + | } | |
| 726 | + | ||
| 727 | + | // Close is a helper method to define mock.On call | |
| 728 | + | func (_e *MockServerListener_Expecter) Close() *MockServerListener_Close_Call { | |
| 729 | + | return &MockServerListener_Close_Call{Call: _e.mock.On("Close")} | |
| 730 | + | } | |
| 731 | + | ||
| 732 | + | func (_c *MockServerListener_Close_Call) Run(run func()) *MockServerListener_Close_Call { | |
| 733 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 734 | + | run() | |
| 735 | + | }) | |
| 736 | + | return _c | |
| 737 | + | } | |
| 738 | + | ||
| 739 | + | func (_c *MockServerListener_Close_Call) Return(err error) *MockServerListener_Close_Call { | |
| 740 | + | _c.Call.Return(err) | |
| 741 | + | return _c | |
| 742 | + | } | |
| 743 | + | ||
| 744 | + | func (_c *MockServerListener_Close_Call) RunAndReturn(run func() error) *MockServerListener_Close_Call { | |
| 745 | + | _c.Call.Return(run) | |
| 746 | + | return _c | |
| 747 | + | } | |
A internal/ports/session.go +26 -0
| @@ -0,0 +1,26 @@ | |||
| 1 | + | package ports | |
| 2 | + | ||
| 3 | + | import "github.com/bnema/vev/internal/protocol" | |
| 4 | + | ||
| 5 | + | // ServerConnection is one typed client/daemon session channel. Close must be | |
| 6 | + | // concurrent-safe and unblock ReceiveClient and every send mode. | |
| 7 | + | type ServerConnection interface { | |
| 8 | + | ReceiveClient() (protocol.ClientMessage, error) | |
| 9 | + | SendServer(protocol.ServerMessage) error | |
| 10 | + | SendServerAsync(protocol.ServerMessage) error | |
| 11 | + | SendServerSynchronous(protocol.ServerMessage) error | |
| 12 | + | SendOutput(protocol.Output) error | |
| 13 | + | SendOutputAsync(protocol.Output) error | |
| 14 | + | SendOutputSynchronous(protocol.Output) error | |
| 15 | + | Capabilities() protocol.ConnectionCapabilities | |
| 16 | + | LinkState() LinkState | |
| 17 | + | LinkEvents() <-chan LinkEvent | |
| 18 | + | Close() error | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | // ServerListener accepts typed server-side session connections. | |
| 22 | + | type ServerListener interface { | |
| 23 | + | Accept() (ServerConnection, error) | |
| 24 | + | Close() error | |
| 25 | + | Addr() string | |
| 26 | + | } | |
A internal/protocol/decode.go +48 -0
| @@ -0,0 +1,48 @@ | |||
| 1 | + | package protocol | |
| 2 | + | ||
| 3 | + | import "fmt" | |
| 4 | + | ||
| 5 | + | // DecodeCategory classifies encoded input rejected before it reaches a use case. | |
| 6 | + | type DecodeCategory uint8 | |
| 7 | + | ||
| 8 | + | const ( | |
| 9 | + | DecodeMalformed DecodeCategory = iota + 1 | |
| 10 | + | DecodeUnknownType | |
| 11 | + | DecodeWrongDirection | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | // DecodeMessageKind identifies message families whose malformed prefixes need | |
| 15 | + | // compatibility responses before a typed message can be produced. | |
| 16 | + | type DecodeMessageKind uint8 | |
| 17 | + | ||
| 18 | + | const ( | |
| 19 | + | DecodeMessageUnknown DecodeMessageKind = iota | |
| 20 | + | DecodeMessageHello | |
| 21 | + | DecodeMessageCommand | |
| 22 | + | DecodeMessageKill | |
| 23 | + | DecodeMessageRemotePreview | |
| 24 | + | ) | |
| 25 | + | ||
| 26 | + | // DecodeFailure describes rejected encoded input without retaining payload bytes. | |
| 27 | + | type DecodeFailure struct { | |
| 28 | + | Category DecodeCategory | |
| 29 | + | Kind DecodeMessageKind | |
| 30 | + | Type uint8 | |
| 31 | + | Version uint16 | |
| 32 | + | RequestID uint64 | |
| 33 | + | Err error | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | func (e *DecodeFailure) Error() string { | |
| 37 | + | if e == nil { | |
| 38 | + | return "protocol: decode failure" | |
| 39 | + | } | |
| 40 | + | return fmt.Sprintf("protocol: decode message type %d: %v", e.Type, e.Err) | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | func (e *DecodeFailure) Unwrap() error { | |
| 44 | + | if e == nil { | |
| 45 | + | return nil | |
| 46 | + | } | |
| 47 | + | return e.Err | |
| 48 | + | } | |
A internal/protocol/messages.go +44 -0
| @@ -0,0 +1,44 @@ | |||
| 1 | + | package protocol | |
| 2 | + | ||
| 3 | + | // ClientMessage is a closed set of messages accepted from a session client. | |
| 4 | + | type ClientMessage interface{ clientMessage() } | |
| 5 | + | ||
| 6 | + | // ServerMessage is a closed set of messages emitted by a session daemon. | |
| 7 | + | type ServerMessage interface{ serverMessage() } | |
| 8 | + | ||
| 9 | + | func (Hello) clientMessage() {} | |
| 10 | + | func (Input) clientMessage() {} | |
| 11 | + | func (Resize) clientMessage() {} | |
| 12 | + | func (Detach) clientMessage() {} | |
| 13 | + | func (Ping) clientMessage() {} | |
| 14 | + | func (List) clientMessage() {} | |
| 15 | + | func (Kill) clientMessage() {} | |
| 16 | + | func (Theme) clientMessage() {} | |
| 17 | + | func (Ack) clientMessage() {} | |
| 18 | + | func (ImagePush) clientMessage() {} | |
| 19 | + | func (ClientNotice) clientMessage() {} | |
| 20 | + | func (CommandRequest) clientMessage() {} | |
| 21 | + | func (OutputResetRequest) clientMessage() {} | |
| 22 | + | func (RemotePreviewRequest) clientMessage() {} | |
| 23 | + | func (RouteAttentionSubscription) clientMessage() {} | |
| 24 | + | func (SamePeerSwitchRequest) clientMessage() {} | |
| 25 | + | func (ParkedRouteRequest) clientMessage() {} | |
| 26 | + | func (RecentRouteSnapshot) clientMessage() {} | |
| 27 | + | func (RouteNavigationFailure) clientMessage() {} | |
| 28 | + | ||
| 29 | + | func (Welcome) serverMessage() {} | |
| 30 | + | func (ErrorMsg) serverMessage() {} | |
| 31 | + | func (Output) serverMessage() {} | |
| 32 | + | func (Detached) serverMessage() {} | |
| 33 | + | func (Pong) serverMessage() {} | |
| 34 | + | func (Sessions) serverMessage() {} | |
| 35 | + | func (CommandResult) serverMessage() {} | |
| 36 | + | func (NavigationDirective) serverMessage() {} | |
| 37 | + | func (AttachTarget) serverMessage() {} | |
| 38 | + | func (RemotePreview) serverMessage() {} | |
| 39 | + | func (CommittedRouteIdentity) serverMessage() {} | |
| 40 | + | func (RouteNavigationAction) serverMessage() {} | |
| 41 | + | func (RouteNavigationFailure) serverMessage() {} | |
| 42 | + | func (RoutePosition) serverMessage() {} | |
| 43 | + | func (SamePeerSwitchFailure) serverMessage() {} | |
| 44 | + | func (ParkedRouteResponse) serverMessage() {} | |
M internal/usecase/daemon/attachment_connection.go +2 -2
| @@ -16,7 +16,7 @@ type attachmentCapability struct { | |||
| 16 | 16 | rebase bool | |
| 17 | 17 | } | |
| 18 | 18 | ||
| 19 | - | func captureAttachmentCapability(entry *session, ac *attachedClient, tr ports.Transport) attachmentCapability { | |
| 19 | + | func captureAttachmentCapability(entry *session, ac *attachedClient, tr ports.ServerConnection) attachmentCapability { | |
| 20 | 20 | if entry == nil || entry.core() == nil || ac == nil || tr == nil { | |
| 21 | 21 | return attachmentCapability{} | |
| 22 | 22 | } | |
| @@ -54,7 +54,7 @@ func captureAttachmentCapability(entry *session, ac *attachedClient, tr ports.Tr | |||
| 54 | 54 | return token | |
| 55 | 55 | } | |
| 56 | 56 | ||
| 57 | - | func (s *session) captureAttachmentCapability(ac *attachedClient, tr ports.Transport) attachmentCapability { | |
| 57 | + | func (s *session) captureAttachmentCapability(ac *attachedClient, tr ports.ServerConnection) attachmentCapability { | |
| 58 | 58 | return captureAttachmentCapability(s, ac, tr) | |
| 59 | 59 | } | |
| 60 | 60 | ||
M internal/usecase/daemon/attachment_effect_gate.go +5 -5
| @@ -6,7 +6,7 @@ import ( | |||
| 6 | 6 | "sync/atomic" | |
| 7 | 7 | ||
| 8 | 8 | "github.com/bnema/vev/internal/ports" | |
| 9 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 9 | + | "github.com/bnema/vev/internal/protocol" | |
| 10 | 10 | ) | |
| 11 | 11 | ||
| 12 | 12 | type attachmentEffectPhase uint8 | |
| @@ -189,7 +189,7 @@ func (t *attachmentEffect) endTransportSend() { | |||
| 189 | 189 | g.mu.Unlock() | |
| 190 | 190 | } | |
| 191 | 191 | ||
| 192 | - | func (t *attachmentEffect) sendControl(frame wire.Frame) error { | |
| 192 | + | func (t *attachmentEffect) sendControl(message protocol.ServerMessage) error { | |
| 193 | 193 | if !t.current() || t.ac == nil || t.transport.transport == nil { | |
| 194 | 194 | return errAttachmentTransition | |
| 195 | 195 | } | |
| @@ -198,7 +198,7 @@ func (t *attachmentEffect) sendControl(frame wire.Frame) error { | |||
| 198 | 198 | if !t.current() || !t.ac.transportSnapshotCurrent(t.transport) || !t.beginTransportSend(t.transport) { | |
| 199 | 199 | return errAttachmentTransition | |
| 200 | 200 | } | |
| 201 | - | err := t.transport.transport.Send(frame) | |
| 201 | + | err := t.transport.transport.SendServer(message) | |
| 202 | 202 | if err != nil { | |
| 203 | 203 | t.reportTransportFailure(t.transport) | |
| 204 | 204 | } | |
| @@ -229,12 +229,12 @@ func (ac *attachedClient) beginAttachmentEffect(token attachmentCapability) (*at | |||
| 229 | 229 | // admits the capability derived from the session registry. Handshakes use it | |
| 230 | 230 | // after Welcome has completed so a replacement blocked behind that send can | |
| 231 | 231 | // publish before readiness or first paint. | |
| 232 | - | func (ac *attachedClient) beginCurrentAttachmentEffect(sess *session, tr ports.Transport) (attachmentCapability, *attachmentEffect, bool) { | |
| 232 | + | func (ac *attachedClient) beginCurrentAttachmentEffect(sess *session, tr ports.ServerConnection) (attachmentCapability, *attachmentEffect, bool) { | |
| 233 | 233 | //nolint:contextcheck // This compatibility wrapper deliberately delegates with a fresh context; cancellable callers use the context variant below. | |
| 234 | 234 | return ac.beginCurrentAttachmentEffectContext(context.Background(), sess, tr) | |
| 235 | 235 | } | |
| 236 | 236 | ||
| 237 | - | func (ac *attachedClient) beginCurrentAttachmentEffectContext(ctx context.Context, sess *session, tr ports.Transport) (attachmentCapability, *attachmentEffect, bool) { | |
| 237 | + | func (ac *attachedClient) beginCurrentAttachmentEffectContext(ctx context.Context, sess *session, tr ports.ServerConnection) (attachmentCapability, *attachmentEffect, bool) { | |
| 238 | 238 | if ctx == nil || ac == nil || sess == nil || tr == nil { | |
| 239 | 239 | return attachmentCapability{}, nil, false | |
| 240 | 240 | } | |
M internal/usecase/daemon/attachment_effect_gate_test.go +1 -1
| @@ -127,7 +127,7 @@ func startBlockedAttachmentSend(t *testing.T, token attachmentCapability) <-chan | |||
| 127 | 127 | go func() { | |
| 128 | 128 | defer close(done) | |
| 129 | 129 | defer ticket.End() | |
| 130 | - | _ = token.ac.sendExpectedTransportForAttachment(token.transport, framePong(), ticket) | |
| 130 | + | _ = token.ac.sendExpectedTransportForAttachment(token.transport, serverPong(), ticket) | |
| 131 | 131 | }() | |
| 132 | 132 | return done | |
| 133 | 133 | } | |
M internal/usecase/daemon/attachment_output.go +20 -21
| @@ -10,7 +10,6 @@ import ( | |||
| 10 | 10 | "github.com/bnema/vev/internal/domain" | |
| 11 | 11 | "github.com/bnema/vev/internal/ports" | |
| 12 | 12 | "github.com/bnema/vev/internal/protocol" | |
| 13 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 14 | 13 | ) | |
| 15 | 14 | ||
| 16 | 15 | // attachmentOutput owns the terminal-output dependency chain and emitted | |
| @@ -154,18 +153,18 @@ func (o *attachmentOutput) prepareFrame(d *Daemon, state *capturedRenderState, f | |||
| 154 | 153 | return &preparedAttachmentOutput{output: o, ansi: ansi, graphics: preparedGraphics, cursor: cursor, data: data}, nil | |
| 155 | 154 | } | |
| 156 | 155 | ||
| 157 | - | func (p *preparedAttachmentOutput) send(echoAck uint64, send func(wire.Frame) error) error { | |
| 156 | + | func (p *preparedAttachmentOutput) send(echoAck uint64, send func(protocol.Output) error) error { | |
| 158 | 157 | if p == nil || p.ansi == nil { | |
| 159 | 158 | return nil | |
| 160 | 159 | } | |
| 161 | - | sendFrame := send | |
| 160 | + | sendOutput := send | |
| 162 | 161 | if p.graphics != nil { | |
| 163 | - | sendFrame = func(frame wire.Frame) error { | |
| 162 | + | sendOutput = func(output protocol.Output) error { | |
| 164 | 163 | p.graphics.markSendAttempted() | |
| 165 | - | return send(frame) | |
| 164 | + | return send(output) | |
| 166 | 165 | } | |
| 167 | 166 | } | |
| 168 | - | if err := p.ansi.send(p.data, echoAck, sendFrame); err != nil { | |
| 167 | + | if err := p.ansi.send(p.data, echoAck, sendOutput); err != nil { | |
| 169 | 168 | p.abortGraphics() | |
| 170 | 169 | return err | |
| 171 | 170 | } | |
| @@ -286,7 +285,7 @@ func (s *attachmentOutput) prepare(frame renderer.Frame, damage []renderer.Damag | |||
| 286 | 285 | }, nil | |
| 287 | 286 | } | |
| 288 | 287 | ||
| 289 | - | func (p *preparedOutput) send(data []byte, echoAck uint64, send func(wire.Frame) error) error { | |
| 288 | + | func (p *preparedOutput) send(data []byte, echoAck uint64, send func(protocol.Output) error) error { | |
| 290 | 289 | if p == nil || p.stream == nil || p.attempted { | |
| 291 | 290 | return nil | |
| 292 | 291 | } | |
| @@ -380,30 +379,30 @@ func (p *preparedOutput) currentLocked() bool { | |||
| 380 | 379 | (p.stream.attachment == nil || p.stream.attachment.lifecycle.generationValue() == p.connectionGeneration) | |
| 381 | 380 | } | |
| 382 | 381 | ||
| 383 | - | func marshalOutputState(data []byte, epoch, base, next, echoAck, viewRevision uint64, size domain.Size, full bool) (wire.Frame, error) { | |
| 384 | - | payload, err := wire.MarshalOutput(protocol.Output{ | |
| 382 | + | func marshalOutputState(data []byte, epoch, base, next, echoAck, viewRevision uint64, size domain.Size, full bool) (protocol.Output, error) { | |
| 383 | + | output := protocol.Output{ | |
| 385 | 384 | Epoch: epoch, Base: base, New: next, Echo: echoAck, ViewRevision: viewRevision, | |
| 386 | 385 | Size: size, Full: full, Data: data, | |
| 387 | - | }) | |
| 388 | - | if err != nil { | |
| 389 | - | return wire.Frame{}, err | |
| 390 | 386 | } | |
| 391 | - | return wire.Frame{Type: wire.MsgOutput, Payload: payload}, nil | |
| 387 | + | if err := protocol.ValidateOutput(output); err != nil { | |
| 388 | + | return protocol.Output{}, err | |
| 389 | + | } | |
| 390 | + | return output, nil | |
| 392 | 391 | } | |
| 393 | 392 | ||
| 394 | - | // sideEffect builds an output frame without advancing the state stream. When | |
| 395 | - | // an attachment is present, callers must hold its sendMu; attachment-bound | |
| 396 | - | // sends use sideEffectLocked after validating their transport incarnation. | |
| 397 | - | func (s *attachmentOutput) sideEffect(data []byte, echoAck uint64) (wire.Frame, error) { | |
| 393 | + | // sideEffect builds output without advancing the state stream. When an | |
| 394 | + | // attachment is present, callers must hold its sendMu; attachment-bound sends | |
| 395 | + | // use sideEffectLocked after validating their connection incarnation. | |
| 396 | + | func (s *attachmentOutput) sideEffect(data []byte, echoAck uint64) (protocol.Output, error) { | |
| 398 | 397 | s.lockView() | |
| 399 | 398 | defer s.unlockView() | |
| 400 | 399 | return s.sideEffectLocked(data, echoAck) | |
| 401 | 400 | } | |
| 402 | 401 | ||
| 403 | - | // sideEffectLocked requires the attachment view lock for both frame creation | |
| 404 | - | // and transport emission. Keeping that lock across the send prevents a view | |
| 405 | - | // publication from overtaking a frame that was fenced just before it. | |
| 406 | - | func (s *attachmentOutput) sideEffectLocked(data []byte, echoAck uint64) (wire.Frame, error) { | |
| 402 | + | // sideEffectLocked requires the attachment view lock for both output creation | |
| 403 | + | // and connection emission. Keeping that lock across the send prevents a view | |
| 404 | + | // publication from overtaking output fenced just before it. | |
| 405 | + | func (s *attachmentOutput) sideEffectLocked(data []byte, echoAck uint64) (protocol.Output, error) { | |
| 407 | 406 | _, epoch, viewRevision := s.fenceLocked() | |
| 408 | 407 | size := domain.Size{Cols: 1, Rows: 1} | |
| 409 | 408 | if s != nil && s.attachment != nil { | |
M internal/usecase/daemon/attachment_output_test.go +29 -27
| @@ -92,11 +92,11 @@ func TestAttachmentOutputCapacityProbeDoesNotRaceWithSend(t *testing.T) { | |||
| 92 | 92 | release := make(chan struct{}) | |
| 93 | 93 | sendDone := make(chan error, 1) | |
| 94 | 94 | go func() { | |
| 95 | - | sendDone <- prepared.send(prepared.data, 0, func(wire.Frame) error { | |
| 95 | + | sendDone <- prepared.send(prepared.data, 0, outputFrameSender(func(wire.Frame) error { | |
| 96 | 96 | close(entered) | |
| 97 | 97 | <-release | |
| 98 | 98 | return nil | |
| 99 | - | }) | |
| 99 | + | })) | |
| 100 | 100 | }() | |
| 101 | 101 | <-entered | |
| 102 | 102 | ||
| @@ -134,10 +134,10 @@ func TestAttachmentOutputEpochsAreAttachmentLocalAndPreparedFramesAreFenced(t *t | |||
| 134 | 134 | firstPrepared, err := first.prepare(frame, nil, true) | |
| 135 | 135 | require.NoError(t, err) | |
| 136 | 136 | var firstFrame wire.Frame | |
| 137 | - | require.NoError(t, firstPrepared.send(firstPrepared.data, 0, func(frame wire.Frame) error { | |
| 137 | + | require.NoError(t, firstPrepared.send(firstPrepared.data, 0, outputFrameSender(func(frame wire.Frame) error { | |
| 138 | 138 | firstFrame = frame | |
| 139 | 139 | return nil | |
| 140 | - | })) | |
| 140 | + | }))) | |
| 141 | 141 | firstOutput, err := wire.UnmarshalOutput(firstFrame.Payload) | |
| 142 | 142 | require.NoError(t, err) | |
| 143 | 143 | require.Equal(t, uint64(1), firstOutput.Epoch) | |
| @@ -147,7 +147,7 @@ func TestAttachmentOutputEpochsAreAttachmentLocalAndPreparedFramesAreFenced(t *t | |||
| 147 | 147 | ||
| 148 | 148 | secondPrepared, err := second.prepare(frame, nil, true) | |
| 149 | 149 | require.NoError(t, err) | |
| 150 | - | require.NoError(t, secondPrepared.send(secondPrepared.data, 0, func(wire.Frame) error { return nil })) | |
| 150 | + | require.NoError(t, secondPrepared.send(secondPrepared.data, 0, outputFrameSender(func(wire.Frame) error { return nil }))) | |
| 151 | 151 | require.Equal(t, uint64(1), second.epoch) | |
| 152 | 152 | require.Equal(t, uint64(1), second.next) | |
| 153 | 153 | require.Equal(t, uint64(1), first.next) | |
| @@ -161,10 +161,10 @@ func TestAttachmentOutputEpochsAreAttachmentLocalAndPreparedFramesAreFenced(t *t | |||
| 161 | 161 | require.Zero(t, first.next) | |
| 162 | 162 | require.Zero(t, first.acked) | |
| 163 | 163 | var staleSent bool | |
| 164 | - | require.NoError(t, pending.send(pending.data, 0, func(wire.Frame) error { | |
| 164 | + | require.NoError(t, pending.send(pending.data, 0, outputFrameSender(func(wire.Frame) error { | |
| 165 | 165 | staleSent = true | |
| 166 | 166 | return nil | |
| 167 | - | })) | |
| 167 | + | }))) | |
| 168 | 168 | require.False(t, staleSent) | |
| 169 | 169 | require.Zero(t, first.next) | |
| 170 | 170 | first.ack(1, 1) | |
| @@ -173,10 +173,10 @@ func TestAttachmentOutputEpochsAreAttachmentLocalAndPreparedFramesAreFenced(t *t | |||
| 173 | 173 | reset, err := first.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false) | |
| 174 | 174 | require.NoError(t, err) | |
| 175 | 175 | var resetFrame wire.Frame | |
| 176 | - | require.NoError(t, reset.send(reset.data, 0, func(frame wire.Frame) error { | |
| 176 | + | require.NoError(t, reset.send(reset.data, 0, outputFrameSender(func(frame wire.Frame) error { | |
| 177 | 177 | resetFrame = frame | |
| 178 | 178 | return nil | |
| 179 | - | })) | |
| 179 | + | }))) | |
| 180 | 180 | resetOutput, err := wire.UnmarshalOutput(resetFrame.Payload) | |
| 181 | 181 | require.NoError(t, err) | |
| 182 | 182 | require.Equal(t, uint64(2), resetOutput.Epoch) | |
| @@ -205,23 +205,23 @@ func TestPreparedOutputDropsReplacedConnectionAndView(t *testing.T) { | |||
| 205 | 205 | fillOutputStateRows(frame, []string{"abc"}) | |
| 206 | 206 | initial, err := stream.prepare(frame, nil, true) | |
| 207 | 207 | require.NoError(t, err) | |
| 208 | - | require.NoError(t, initial.send(initial.data, 0, func(wire.Frame) error { return nil })) | |
| 208 | + | require.NoError(t, initial.send(initial.data, 0, outputFrameSender(func(wire.Frame) error { return nil }))) | |
| 209 | 209 | changed := frame.Clone() | |
| 210 | 210 | changed.Set(0, 0, renderer.Cell{Rune: 'x', Style: renderer.DefaultStyle()}) | |
| 211 | 211 | pending, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false) | |
| 212 | 212 | require.NoError(t, err) | |
| 213 | 213 | tt.change(ac) | |
| 214 | 214 | var sent bool | |
| 215 | - | require.NoError(t, pending.send(pending.data, 0, func(wire.Frame) error { | |
| 215 | + | require.NoError(t, pending.send(pending.data, 0, outputFrameSender(func(wire.Frame) error { | |
| 216 | 216 | sent = true | |
| 217 | 217 | return nil | |
| 218 | - | })) | |
| 218 | + | }))) | |
| 219 | 219 | require.False(t, sent) | |
| 220 | 220 | require.Equal(t, uint64(1), stream.next) | |
| 221 | 221 | ||
| 222 | 222 | retry, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false) | |
| 223 | 223 | require.NoError(t, err) | |
| 224 | - | require.NoError(t, retry.send(retry.data, 0, func(wire.Frame) error { return nil })) | |
| 224 | + | require.NoError(t, retry.send(retry.data, 0, outputFrameSender(func(wire.Frame) error { return nil }))) | |
| 225 | 225 | require.Equal(t, uint64(2), stream.next) | |
| 226 | 226 | }) | |
| 227 | 227 | } | |
| @@ -242,9 +242,7 @@ func TestOutputStateSideEffectsDoNotAdvanceEpochStateOrACK(t *testing.T) { | |||
| 242 | 242 | stream := newOutputStateStream() | |
| 243 | 243 | stream.ack(1, 0) | |
| 244 | 244 | beforeEpoch, beforeNext, beforeAck := stream.epoch, stream.next, stream.acked | |
| 245 | - | frame, err := stream.sideEffect([]byte("pty"), 9) | |
| 246 | - | require.NoError(t, err) | |
| 247 | - | out, err := wire.UnmarshalOutput(frame.Payload) | |
| 245 | + | out, err := stream.sideEffect([]byte("pty"), 9) | |
| 248 | 246 | require.NoError(t, err) | |
| 249 | 247 | require.Equal(t, beforeEpoch, out.Epoch) | |
| 250 | 248 | require.Zero(t, out.Base) | |
| @@ -261,7 +259,7 @@ func TestAttachmentOutputFailedSendRetriesSnapshotWithoutAdvancing(t *testing.T) | |||
| 261 | 259 | fillOutputStateRows(initial, []string{"abc"}) | |
| 262 | 260 | first, err := stream.prepare(initial, nil, true) | |
| 263 | 261 | require.NoError(t, err) | |
| 264 | - | require.NoError(t, first.send(first.data, 0, func(wire.Frame) error { return nil })) | |
| 262 | + | require.NoError(t, first.send(first.data, 0, outputFrameSender(func(wire.Frame) error { return nil }))) | |
| 265 | 263 | require.Equal(t, uint64(1), stream.next) | |
| 266 | 264 | ||
| 267 | 265 | changed := initial.Clone() | |
| @@ -273,7 +271,7 @@ func TestAttachmentOutputFailedSendRetriesSnapshotWithoutAdvancing(t *testing.T) | |||
| 273 | 271 | require.Empty(t, probe.Bytes(), "preparation must not advance the renderer shadow") | |
| 274 | 272 | ||
| 275 | 273 | sendErr := errors.New("send failed") | |
| 276 | - | require.ErrorIs(t, pending.send(pending.data, 0, func(wire.Frame) error { return sendErr }), sendErr) | |
| 274 | + | require.ErrorIs(t, pending.send(pending.data, 0, outputFrameSender(func(wire.Frame) error { return sendErr })), sendErr) | |
| 277 | 275 | require.Equal(t, uint64(1), stream.next, "failed send must not advance the state chain") | |
| 278 | 276 | pending.commitNoSend() | |
| 279 | 277 | probe, err = stream.renderer.Prepare(initial, nil, false) | |
| @@ -283,19 +281,19 @@ func TestAttachmentOutputFailedSendRetriesSnapshotWithoutAdvancing(t *testing.T) | |||
| 283 | 281 | retry, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false) | |
| 284 | 282 | require.NoError(t, err) | |
| 285 | 283 | var sent wire.Frame | |
| 286 | - | require.NoError(t, retry.send(retry.data, 0, func(frame wire.Frame) error { | |
| 284 | + | require.NoError(t, retry.send(retry.data, 0, outputFrameSender(func(frame wire.Frame) error { | |
| 287 | 285 | sent = frame | |
| 288 | 286 | return nil | |
| 289 | - | })) | |
| 287 | + | }))) | |
| 290 | 288 | out, err := wire.UnmarshalOutput(sent.Payload) | |
| 291 | 289 | require.NoError(t, err) | |
| 292 | 290 | require.Zero(t, out.Base, "retry after ambiguous send failure must be dependency-free") | |
| 293 | 291 | require.Equal(t, uint64(2), out.New, "state numbers remain monotonic across rebases") | |
| 294 | 292 | require.Equal(t, uint64(2), stream.next, "successful retry advances the chain exactly once") | |
| 295 | - | require.NoError(t, retry.send(retry.data, 0, func(wire.Frame) error { | |
| 293 | + | require.NoError(t, retry.send(retry.data, 0, outputFrameSender(func(wire.Frame) error { | |
| 296 | 294 | t.Fatal("completed output sent twice") | |
| 297 | 295 | return nil | |
| 298 | - | })) | |
| 296 | + | }))) | |
| 299 | 297 | require.Equal(t, uint64(2), stream.next) | |
| 300 | 298 | probe, err = stream.renderer.Prepare(changed, nil, false) | |
| 301 | 299 | require.NoError(t, err) | |
| @@ -308,7 +306,7 @@ func TestAttachmentOutputNoByteCommitAdvancesShadowWithoutState(t *testing.T) { | |||
| 308 | 306 | fillOutputStateRows(frame, []string{"abc"}) | |
| 309 | 307 | initial, err := stream.prepare(frame, nil, true) | |
| 310 | 308 | require.NoError(t, err) | |
| 311 | - | require.NoError(t, initial.send(initial.data, 0, func(wire.Frame) error { return nil })) | |
| 309 | + | require.NoError(t, initial.send(initial.data, 0, outputFrameSender(func(wire.Frame) error { return nil }))) | |
| 312 | 310 | ||
| 313 | 311 | noOp, err := stream.prepare(frame, nil, false) | |
| 314 | 312 | require.NoError(t, err) | |
| @@ -378,7 +376,11 @@ func outputStateFrame(stream *attachmentOutput, data []byte, reset bool, echoAck | |||
| 378 | 376 | if reset { | |
| 379 | 377 | base = 0 | |
| 380 | 378 | } | |
| 381 | - | frame, err := frameOutputState(data, base, stream.next, echoAck) | |
| 379 | + | output, err := serverOutputState(data, base, stream.next, echoAck) | |
| 380 | + | if err != nil { | |
| 381 | + | panic(err) | |
| 382 | + | } | |
| 383 | + | frame, err := testServerFrame(output) | |
| 382 | 384 | if err != nil { | |
| 383 | 385 | panic(err) | |
| 384 | 386 | } | |
| @@ -395,10 +397,10 @@ func drawOutputState(t *testing.T, stream *attachmentOutput, frame renderer.Fram | |||
| 395 | 397 | return wire.Frame{}, false, err | |
| 396 | 398 | } | |
| 397 | 399 | var output wire.Frame | |
| 398 | - | err = prepared.send(prepared.data, echoAck, func(frame wire.Frame) error { | |
| 400 | + | err = prepared.send(prepared.data, echoAck, outputFrameSender(func(frame wire.Frame) error { | |
| 399 | 401 | output = frame | |
| 400 | 402 | return nil | |
| 401 | - | }) | |
| 403 | + | })) | |
| 402 | 404 | if err != nil { | |
| 403 | 405 | return wire.Frame{}, false, err | |
| 404 | 406 | } | |
| @@ -456,7 +458,7 @@ func TestAttachmentOutputFailedSendKeepsTextCursorAndGraphicsSpeculative(t *test | |||
| 456 | 458 | require.NoError(t, err) | |
| 457 | 459 | ||
| 458 | 460 | sendErr := errors.New("send failed") | |
| 459 | - | require.ErrorIs(t, prepared.send(0, func(wire.Frame) error { return sendErr }), sendErr) | |
| 461 | + | require.ErrorIs(t, prepared.send(0, outputFrameSender(func(wire.Frame) error { return sendErr })), sendErr) | |
| 460 | 462 | ||
| 461 | 463 | require.Equal(t, cursorOut{valid: true, row: 1, col: 1}, output.lastCursor) | |
| 462 | 464 | require.Empty(t, graphicsState.assets) | |
M internal/usecase/daemon/attachment_parking_test.go +2 -2
| @@ -142,7 +142,7 @@ func TestSuccessfulResumeRejectsOldCredentialAfterWelcome(t *testing.T) { | |||
| 142 | 142 | d.clientGone(sess, ac, oldTransport, false) | |
| 143 | 143 | ||
| 144 | 144 | resumedTransport := &closeTrackingTransport{} | |
| 145 | - | d.handleHello(resumedTransport, wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(helloResumeCapable(protocol.IntentResume, "work", oldToken))}) | |
| 145 | + | d.handleHello(resumedTransport, helloResumeCapable(protocol.IntentResume, "work", oldToken)) | |
| 146 | 146 | newToken := ac.resumeToken | |
| 147 | 147 | require.NotEqual(t, oldToken, newToken) | |
| 148 | 148 | d.mu.Lock() | |
| @@ -166,7 +166,7 @@ func TestFailedResumeHandshakeKeepsParkedCredential(t *testing.T) { | |||
| 166 | 166 | d.clientGone(sess, ac, oldTransport, false) | |
| 167 | 167 | ||
| 168 | 168 | failed := &resumeWelcomeFailureTransport{} | |
| 169 | - | d.handleHello(failed, wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(helloResumeCapable(protocol.IntentResume, "work", token))}) | |
| 169 | + | d.handleHello(failed, helloResumeCapable(protocol.IntentResume, "work", token)) | |
| 170 | 170 | require.Nil(t, ac.transport()) | |
| 171 | 171 | d.mu.Lock() | |
| 172 | 172 | parked := d.parked[token] | |
M internal/usecase/daemon/attachment_registry_test.go +2 -7
| @@ -5,7 +5,6 @@ import ( | |||
| 5 | 5 | "testing" | |
| 6 | 6 | ||
| 7 | 7 | "github.com/bnema/vev/internal/domain" | |
| 8 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 9 | 8 | "github.com/stretchr/testify/require" | |
| 10 | 9 | ) | |
| 11 | 10 | ||
| @@ -97,9 +96,7 @@ func TestTabForAttachmentRepairRebasesOutputBeforePublishingRevision(t *testing. | |||
| 97 | 96 | after := ac.viewSnapshot() | |
| 98 | 97 | require.Greater(t, after.revision, before.revision) | |
| 99 | 98 | require.Greater(t, ac.output.currentEpoch(), beforeEpoch) | |
| 100 | - | frame, err := ac.output.sideEffect([]byte("x"), 0) | |
| 101 | - | require.NoError(t, err) | |
| 102 | - | output, err := wire.UnmarshalOutput(frame.Payload) | |
| 99 | + | output, err := ac.output.sideEffect([]byte("x"), 0) | |
| 103 | 100 | require.NoError(t, err) | |
| 104 | 101 | require.Equal(t, after.revision, output.ViewRevision) | |
| 105 | 102 | require.Equal(t, ac.output.currentEpoch(), output.Epoch) | |
| @@ -127,9 +124,7 @@ func TestPrepareRemovedTabViewRebasesOutputBeforeSideEffect(t *testing.T) { | |||
| 127 | 124 | require.Equal(t, before.revision+1, after.revision) | |
| 128 | 125 | require.Greater(t, ac.output.currentEpoch(), beforeEpoch) | |
| 129 | 126 | ||
| 130 | - | frame, err := ac.output.sideEffect([]byte("replacement"), 0) | |
| 131 | - | require.NoError(t, err) | |
| 132 | - | output, err := wire.UnmarshalOutput(frame.Payload) | |
| 127 | + | output, err := ac.output.sideEffect([]byte("replacement"), 0) | |
| 133 | 128 | require.NoError(t, err) | |
| 134 | 129 | require.Equal(t, after.revision, output.ViewRevision) | |
| 135 | 130 | require.NotEqual(t, beforeEpoch, output.Epoch) | |
M internal/usecase/daemon/attachment_render_test.go +1 -1
| @@ -283,7 +283,7 @@ func TestAttachmentResizeUsesLatestClaimedSessionGeometry(t *testing.T) { | |||
| 283 | 283 | secondLease := rc.attachmentLease(second) | |
| 284 | 284 | require.True(t, rc.markAttachmentReady(firstLease)) | |
| 285 | 285 | require.True(t, rc.markAttachmentReady(secondLease)) | |
| 286 | - | resize := func(ac *attachedClient, tr ports.Transport, size domain.Size) { | |
| 286 | + | resize := func(ac *attachedClient, tr ports.ServerConnection, size domain.Size) { | |
| 287 | 287 | token := sess.captureAttachmentCapability(ac, tr) | |
| 288 | 288 | d.handleAttachmentClientFrame(token, wire.Frame{ | |
| 289 | 289 | Type: wire.MsgResize, | |
M internal/usecase/daemon/attention_test.go +1 -2
| @@ -13,7 +13,6 @@ import ( | |||
| 13 | 13 | renderer "github.com/bnema/vev-vt" | |
| 14 | 14 | "github.com/bnema/vev/internal/domain" | |
| 15 | 15 | "github.com/bnema/vev/internal/ports" | |
| 16 | - | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |
| 17 | 16 | "github.com/bnema/vev/internal/protocol/wire" | |
| 18 | 17 | "github.com/bnema/vev/internal/usecase/keys" | |
| 19 | 18 | "github.com/bnema/vev/internal/usecase/layout" | |
| @@ -164,7 +163,7 @@ func TestNoteAttentionDoesNotBlockOnWedgedOtherClient(t *testing.T) { | |||
| 164 | 163 | defer releases[1]() | |
| 165 | 164 | selectTestAttachmentTab(sess, 0) | |
| 166 | 165 | ||
| 167 | - | trW := portsmocks.NewMockTransport(t) | |
| 166 | + | trW := newMockServerConnection(t) | |
| 168 | 167 | block := make(chan struct{}) | |
| 169 | 168 | t.Cleanup(func() { close(block) }) | |
| 170 | 169 | trW.EXPECT().Send(mock.Anything).RunAndReturn(func(wire.Frame) error { | |
M internal/usecase/daemon/client.go +27 -28
| @@ -14,14 +14,13 @@ import ( | |||
| 14 | 14 | "github.com/bnema/vev/internal/domain" | |
| 15 | 15 | "github.com/bnema/vev/internal/ports" | |
| 16 | 16 | "github.com/bnema/vev/internal/protocol" | |
| 17 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 18 | 17 | "github.com/bnema/vev/internal/usecase/keys" | |
| 19 | 18 | "github.com/bnema/vev/internal/usecase/mouse" | |
| 20 | 19 | themeui "github.com/bnema/vev/internal/usecase/theme" | |
| 21 | 20 | ) | |
| 22 | 21 | ||
| 23 | 22 | type transportSnapshot struct { | |
| 24 | - | transport ports.Transport | |
| 23 | + | transport ports.ServerConnection | |
| 25 | 24 | incarnation uint64 | |
| 26 | 25 | } | |
| 27 | 26 | ||
| @@ -35,7 +34,7 @@ type appliedTheme struct { | |||
| 35 | 34 | } | |
| 36 | 35 | ||
| 37 | 36 | type attachedClient struct { | |
| 38 | - | tr ports.Transport | |
| 37 | + | tr ports.ServerConnection | |
| 39 | 38 | transportIncarnation uint64 | |
| 40 | 39 | output *attachmentOutput | |
| 41 | 40 | overlays *overlayRuntime | |
| @@ -274,13 +273,13 @@ func (ac *attachedClient) setClientTheme(t themeui.Theme) { | |||
| 274 | 273 | ac.themeMu.Unlock() | |
| 275 | 274 | } | |
| 276 | 275 | ||
| 277 | - | func (ac *attachedClient) transport() ports.Transport { | |
| 276 | + | func (ac *attachedClient) transport() ports.ServerConnection { | |
| 278 | 277 | ac.linkMu.Lock() | |
| 279 | 278 | defer ac.linkMu.Unlock() | |
| 280 | 279 | return ac.tr | |
| 281 | 280 | } | |
| 282 | 281 | ||
| 283 | - | func (ac *attachedClient) replaceTransport(tr ports.Transport) { | |
| 282 | + | func (ac *attachedClient) replaceTransport(tr ports.ServerConnection) { | |
| 284 | 283 | ac.linkMu.Lock() | |
| 285 | 284 | ac.tr = tr | |
| 286 | 285 | ac.transportIncarnation++ | |
| @@ -290,7 +289,7 @@ func (ac *attachedClient) replaceTransport(tr ports.Transport) { | |||
| 290 | 289 | // revokeTransport removes tr only if it is still this attachment's current | |
| 291 | 290 | // link. It returns the revoked transport, so asynchronous retirement can | |
| 292 | 291 | // close exactly that link without touching a later connection incarnation. | |
| 293 | - | func (ac *attachedClient) revokeTransport(tr ports.Transport) ports.Transport { | |
| 292 | + | func (ac *attachedClient) revokeTransport(tr ports.ServerConnection) ports.ServerConnection { | |
| 294 | 293 | if tr == nil { | |
| 295 | 294 | return nil | |
| 296 | 295 | } | |
| @@ -319,20 +318,20 @@ func (ac *attachedClient) transportSnapshotCurrent(expected transportSnapshot) b | |||
| 319 | 318 | return ac.tr == expected.transport && ac.transportIncarnation == expected.incarnation | |
| 320 | 319 | } | |
| 321 | 320 | ||
| 322 | - | func (ac *attachedClient) closeCapturedTransport(tr ports.Transport) error { | |
| 321 | + | func (ac *attachedClient) closeCapturedTransport(tr ports.ServerConnection) error { | |
| 323 | 322 | if tr == nil { | |
| 324 | 323 | return nil | |
| 325 | 324 | } | |
| 326 | 325 | return tr.Close() | |
| 327 | 326 | } | |
| 328 | 327 | ||
| 329 | - | func (ac *attachedClient) transportIs(tr ports.Transport) bool { | |
| 328 | + | func (ac *attachedClient) transportIs(tr ports.ServerConnection) bool { | |
| 330 | 329 | ac.linkMu.Lock() | |
| 331 | 330 | defer ac.linkMu.Unlock() | |
| 332 | 331 | return ac.tr == tr | |
| 333 | 332 | } | |
| 334 | 333 | ||
| 335 | - | func (ac *attachedClient) currentTransportIs(tr ports.Transport) bool { | |
| 334 | + | func (ac *attachedClient) currentTransportIs(tr ports.ServerConnection) bool { | |
| 336 | 335 | return tr != nil && ac.transportIs(tr) | |
| 337 | 336 | } | |
| 338 | 337 | ||
| @@ -410,7 +409,7 @@ var errTransportReplaced = errors.New("client transport was replaced") | |||
| 410 | 409 | ||
| 411 | 410 | // sendExpectedTransport writes only when expected is still the attachment's | |
| 412 | 411 | // current transport incarnation. It preserves sendMu -> linkMu lock ordering. | |
| 413 | - | func (ac *attachedClient) sendExpectedTransport(expected transportSnapshot, f wire.Frame) error { | |
| 412 | + | func (ac *attachedClient) sendExpectedTransport(expected transportSnapshot, message protocol.ServerMessage) error { | |
| 414 | 413 | ac.sendMu.Lock() | |
| 415 | 414 | defer ac.sendMu.Unlock() | |
| 416 | 415 | if !ac.transportSnapshotCurrent(expected) { | |
| @@ -419,7 +418,7 @@ func (ac *attachedClient) sendExpectedTransport(expected transportSnapshot, f wi | |||
| 419 | 418 | if expected.transport == nil { | |
| 420 | 419 | return errors.New("client transport is nil") | |
| 421 | 420 | } | |
| 422 | - | return expected.transport.Send(f) | |
| 421 | + | return expected.transport.SendServer(message) | |
| 423 | 422 | } | |
| 424 | 423 | ||
| 425 | 424 | // beginExpectedTransportSendLocked validates that expected is still the | |
| @@ -436,7 +435,7 @@ func (ac *attachedClient) beginExpectedTransportSendLocked(expected transportSna | |||
| 436 | 435 | return nil | |
| 437 | 436 | } | |
| 438 | 437 | ||
| 439 | - | func (ac *attachedClient) sendExpectedTransportForAttachment(expected transportSnapshot, f wire.Frame, ticket *attachmentEffect) error { | |
| 438 | + | func (ac *attachedClient) sendExpectedTransportForAttachment(expected transportSnapshot, message protocol.ServerMessage, ticket *attachmentEffect) error { | |
| 440 | 439 | ac.sendMu.Lock() | |
| 441 | 440 | defer ac.sendMu.Unlock() | |
| 442 | 441 | // A attachment-bound send requires a live ticket, and reports every rejection, | |
| @@ -447,7 +446,7 @@ func (ac *attachedClient) sendExpectedTransportForAttachment(expected transportS | |||
| 447 | 446 | if err := ac.beginExpectedTransportSendLocked(expected, ticket); err != nil { | |
| 448 | 447 | return errAttachmentTransition | |
| 449 | 448 | } | |
| 450 | - | err := expected.transport.Send(f) | |
| 449 | + | err := expected.transport.SendServer(message) | |
| 451 | 450 | if err != nil { | |
| 452 | 451 | ticket.reportTransportFailure(expected) | |
| 453 | 452 | } | |
| @@ -460,17 +459,17 @@ func (ac *attachedClient) sendExpectedTransportForAttachment(expected transportS | |||
| 460 | 459 | // detachNotifyTimeout, the transport is force-closed, failing the in-flight | |
| 461 | 460 | // write. Detach/kill/shutdown paths use this so they are never gated on a | |
| 462 | 461 | // client that has stopped draining its socket. | |
| 463 | - | func (d *Daemon) boundedSend(ac *attachedClient, f wire.Frame) { | |
| 464 | - | _ = d.boundedSendErr(ac, f) | |
| 462 | + | func (d *Daemon) boundedSend(ac *attachedClient, message protocol.ServerMessage) { | |
| 463 | + | _ = d.boundedSendErr(ac, message) | |
| 465 | 464 | } | |
| 466 | 465 | ||
| 467 | - | func (d *Daemon) boundedSendErr(ac *attachedClient, f wire.Frame) error { | |
| 466 | + | func (d *Daemon) boundedSendErr(ac *attachedClient, message protocol.ServerMessage) error { | |
| 468 | 467 | expected := ac.transportSnapshot() | |
| 469 | 468 | if expected.transport == nil { | |
| 470 | 469 | return errors.New("client transport is nil") | |
| 471 | 470 | } | |
| 472 | 471 | tr, err := d.boundedSendWith(expected.transport, func() error { | |
| 473 | - | return ac.sendExpectedTransport(expected, f) | |
| 472 | + | return ac.sendExpectedTransport(expected, message) | |
| 474 | 473 | }) | |
| 475 | 474 | if err != nil && errors.Is(err, errSendTimedOut) { | |
| 476 | 475 | _ = ac.closeCapturedTransport(tr) | |
| @@ -483,12 +482,12 @@ func (d *Daemon) boundedSendOutputErr(ac *attachedClient, b []byte) error { | |||
| 483 | 482 | return err | |
| 484 | 483 | } | |
| 485 | 484 | ||
| 486 | - | func (d *Daemon) boundedSendOutputErrTransport(ac *attachedClient, b []byte) (ports.Transport, error) { | |
| 485 | + | func (d *Daemon) boundedSendOutputErrTransport(ac *attachedClient, b []byte) (ports.ServerConnection, error) { | |
| 487 | 486 | expected := ac.transportSnapshot() | |
| 488 | 487 | if expected.transport == nil { | |
| 489 | 488 | return nil, errors.New("client transport is nil") | |
| 490 | 489 | } | |
| 491 | - | if owned, ok := expected.transport.(ports.OwnedSynchronousTransport); ok { | |
| 490 | + | if expected.transport.Capabilities().OwnedSynchronousSend { | |
| 492 | 491 | ac.sendMu.Lock() | |
| 493 | 492 | defer ac.sendMu.Unlock() | |
| 494 | 493 | if !ac.transportSnapshotCurrent(expected) { | |
| @@ -499,9 +498,9 @@ func (d *Daemon) boundedSendOutputErrTransport(ac *attachedClient, b []byte) (po | |||
| 499 | 498 | } | |
| 500 | 499 | ac.output.lockView() | |
| 501 | 500 | defer ac.output.unlockView() | |
| 502 | - | frame, err := ac.output.sideEffectLocked(b, ac.echoAck.Load()) | |
| 501 | + | output, err := ac.output.sideEffectLocked(b, ac.echoAck.Load()) | |
| 503 | 502 | if err == nil { | |
| 504 | - | err = owned.SendSynchronous(frame) | |
| 503 | + | err = expected.transport.SendOutputSynchronous(output) | |
| 505 | 504 | } | |
| 506 | 505 | return expected.transport, err | |
| 507 | 506 | } | |
| @@ -516,21 +515,21 @@ func (d *Daemon) boundedSendOutputErrTransport(ac *attachedClient, b []byte) (po | |||
| 516 | 515 | } | |
| 517 | 516 | ac.output.lockView() | |
| 518 | 517 | defer ac.output.unlockView() | |
| 519 | - | frame, err := ac.output.sideEffectLocked(b, ac.echoAck.Load()) | |
| 518 | + | output, err := ac.output.sideEffectLocked(b, ac.echoAck.Load()) | |
| 520 | 519 | if err != nil { | |
| 521 | 520 | return err | |
| 522 | 521 | } | |
| 523 | - | return expected.transport.Send(frame) | |
| 522 | + | return expected.transport.SendOutput(output) | |
| 524 | 523 | }) | |
| 525 | 524 | } | |
| 526 | 525 | ||
| 527 | 526 | var errSendTimedOut = errors.New("send timed out") | |
| 528 | 527 | ||
| 529 | - | func (d *Daemon) boundedSendWith(tr ports.Transport, send func() error) (ports.Transport, error) { | |
| 528 | + | func (d *Daemon) boundedSendWith(tr ports.ServerConnection, send func() error) (ports.ServerConnection, error) { | |
| 530 | 529 | return d.boundedSendWithTimeout(detachNotifyTimeout, tr, send) | |
| 531 | 530 | } | |
| 532 | 531 | ||
| 533 | - | func (d *Daemon) boundedSendWithTimeout(timeout time.Duration, tr ports.Transport, send func() error) (ports.Transport, error) { | |
| 532 | + | func (d *Daemon) boundedSendWithTimeout(timeout time.Duration, tr ports.ServerConnection, send func() error) (ports.ServerConnection, error) { | |
| 534 | 533 | timer := d.clock.NewTimer(timeout) | |
| 535 | 534 | result := make(chan error, 1) | |
| 536 | 535 | go func() { | |
| @@ -581,7 +580,7 @@ func (d *Daemon) notifyDetachedSnapshotAsync(snapshot detachedAttachmentSnapshot | |||
| 581 | 580 | go func() { | |
| 582 | 581 | defer close(done) | |
| 583 | 582 | _, _ = d.boundedSendWith(snapshot.transport.transport, func() error { | |
| 584 | - | return snapshot.ac.sendExpectedTransport(snapshot.transport, frameDetached(reason)) | |
| 583 | + | return snapshot.ac.sendExpectedTransport(snapshot.transport, serverDetached(reason)) | |
| 585 | 584 | }) | |
| 586 | 585 | revoked := snapshot.ac.revokeTransport(snapshot.transport.transport) | |
| 587 | 586 | if revoked == nil { | |
| @@ -619,7 +618,7 @@ type attachClientOptions struct { | |||
| 619 | 618 | startupOverlay protocol.StartupOverlay | |
| 620 | 619 | } | |
| 621 | 620 | ||
| 622 | - | func (d *Daemon) attachClient(sess *session, tr ports.Transport, sz domain.Size, opts attachClientOptions) (*attachedClient, error) { | |
| 621 | + | func (d *Daemon) attachClient(sess *session, tr ports.ServerConnection, sz domain.Size, opts attachClientOptions) (*attachedClient, error) { | |
| 623 | 622 | d.mu.Lock() | |
| 624 | 623 | ac := d.prepareAttachedClientLocked(sess, tr, domain.Geometry{Size: sz}, opts) | |
| 625 | 624 | d.mu.Unlock() | |
| @@ -664,7 +663,7 @@ func (d *Daemon) finishAttachedClient(sess *session, ac *attachedClient, opts at | |||
| 664 | 663 | // prepareAttachedClientLocked allocates one detached attachment. Caller holds | |
| 665 | 664 | // d.mu only to allocate its resume token; attachment publication happens later after | |
| 666 | 665 | // the caller releases every architecture lock. | |
| 667 | - | func (d *Daemon) prepareAttachedClientLocked(sess *session, tr ports.Transport, geometry domain.Geometry, opts attachClientOptions) *attachedClient { | |
| 666 | + | func (d *Daemon) prepareAttachedClientLocked(sess *session, tr ports.ServerConnection, geometry domain.Geometry, opts attachClientOptions) *attachedClient { | |
| 668 | 667 | resumeToken := uint64(0) | |
| 669 | 668 | if opts.resumeCapable { | |
| 670 | 669 | resumeToken = d.nextResumeTokenLocked() | |
M internal/usecase/daemon/client_frame_routing.go +48 -91
| @@ -7,7 +7,6 @@ import ( | |||
| 7 | 7 | "github.com/bnema/vev/internal/domain" | |
| 8 | 8 | "github.com/bnema/vev/internal/ports" | |
| 9 | 9 | "github.com/bnema/vev/internal/protocol" | |
| 10 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 11 | 10 | "github.com/bnema/vev/internal/usecase/command" | |
| 12 | 11 | ) | |
| 13 | 12 | ||
| @@ -16,7 +15,7 @@ const connectionSnapshotAttempts = 8 | |||
| 16 | 15 | // currentAttachmentConnection retries the lock-free session-to-role snapshot when a | |
| 17 | 16 | // handoff lands between those reads. The attachment gate still performs the final | |
| 18 | 17 | // mutation admission after this function returns. | |
| 19 | - | func (d *Daemon) currentAttachmentConnection(ac *attachedClient, tr ports.Transport) (*session, attachmentCapability, bool) { | |
| 18 | + | func (d *Daemon) currentAttachmentConnection(ac *attachedClient, tr ports.ServerConnection) (*session, attachmentCapability, bool) { | |
| 20 | 19 | for range connectionSnapshotAttempts { | |
| 21 | 20 | if !ac.currentTransportIs(tr) { | |
| 22 | 21 | break | |
| @@ -50,8 +49,12 @@ func (d *Daemon) runConnLoop(ac *attachedClient) { | |||
| 50 | 49 | if !ac.currentTransportIs(tr) || ac.currentAttachmentSession() == nil { | |
| 51 | 50 | return | |
| 52 | 51 | } | |
| 53 | - | f, err := tr.Recv() | |
| 52 | + | message, err := tr.ReceiveClient() | |
| 54 | 53 | if err != nil { | |
| 54 | + | var failure *protocol.DecodeFailure | |
| 55 | + | if errors.As(err, &failure) { | |
| 56 | + | continue | |
| 57 | + | } | |
| 55 | 58 | for range connectionSnapshotAttempts { | |
| 56 | 59 | sess, _, ok := d.currentAttachmentConnection(ac, tr) | |
| 57 | 60 | if !ok { | |
| @@ -69,14 +72,14 @@ func (d *Daemon) runConnLoop(ac *attachedClient) { | |||
| 69 | 72 | if !ok { | |
| 70 | 73 | return | |
| 71 | 74 | } | |
| 72 | - | if d.handleAttachmentClientFrame(token, f) { | |
| 75 | + | if d.handleAttachmentClientMessage(token, message) { | |
| 73 | 76 | return | |
| 74 | 77 | } | |
| 75 | 78 | } | |
| 76 | 79 | } | |
| 77 | 80 | ||
| 78 | - | // handleAttachmentClientFrame owns the attached-client protocol. | |
| 79 | - | func (d *Daemon) handleAttachmentClientFrame(capability attachmentCapability, f wire.Frame) bool { | |
| 81 | + | // handleAttachmentClientMessage owns the attached-client protocol. | |
| 82 | + | func (d *Daemon) handleAttachmentClientMessage(capability attachmentCapability, message protocol.ClientMessage) bool { | |
| 80 | 83 | if !capability.current() { | |
| 81 | 84 | return false | |
| 82 | 85 | } | |
| @@ -91,63 +94,34 @@ func (d *Daemon) handleAttachmentClientFrame(capability attachmentCapability, f | |||
| 91 | 94 | if d.afterAttachmentEffectAdmitted != nil { | |
| 92 | 95 | d.afterAttachmentEffectAdmitted(effect.capability()) | |
| 93 | 96 | } | |
| 94 | - | switch f.Type { | |
| 95 | - | case wire.MsgInput: | |
| 96 | - | if in, derr := wire.UnmarshalInput(f.Payload); derr == nil { | |
| 97 | - | d.handleSequencedInputForAttachment(effect, in.InputSeq, in.Data) | |
| 98 | - | } | |
| 99 | - | case wire.MsgResize: | |
| 100 | - | if rz, derr := wire.UnmarshalResize(f.Payload); derr == nil && effect.current() { | |
| 101 | - | d.resizeAttachmentGeometryForLease(effect, rz.Geometry()) | |
| 102 | - | } | |
| 103 | - | case wire.MsgTheme: | |
| 104 | - | if th, derr := wire.UnmarshalTheme(f.Payload); derr == nil { | |
| 105 | - | d.applyThemeForAttachment(effect, th) | |
| 106 | - | } | |
| 107 | - | case wire.MsgImagePush: | |
| 108 | - | if ip, derr := wire.UnmarshalImagePush(f.Payload); derr == nil { | |
| 109 | - | d.handleSequencedImagePushForAttachment(effect, ip.InputSeq, ip) | |
| 110 | - | } | |
| 111 | - | case wire.MsgClientNotice: | |
| 112 | - | if notice, derr := wire.UnmarshalClientNotice(f.Payload); derr == nil { | |
| 113 | - | d.handleClientNoticeForAttachment(effect, notice) | |
| 114 | - | } else { | |
| 115 | - | d.log.Warn("malformed client notice", "err", derr) | |
| 97 | + | switch message := message.(type) { | |
| 98 | + | case protocol.Input: | |
| 99 | + | d.handleSequencedInputForAttachment(effect, message.InputSeq, message.Data) | |
| 100 | + | case protocol.Resize: | |
| 101 | + | if effect.current() { | |
| 102 | + | d.resizeAttachmentGeometryForLease(effect, message.Geometry()) | |
| 116 | 103 | } | |
| 117 | - | case wire.MsgDetach: | |
| 104 | + | case protocol.Theme: | |
| 105 | + | d.applyThemeForAttachment(effect, message) | |
| 106 | + | case protocol.ImagePush: | |
| 107 | + | d.handleSequencedImagePushForAttachment(effect, message.InputSeq, message) | |
| 108 | + | case protocol.ClientNotice: | |
| 109 | + | d.handleClientNoticeForAttachment(effect, message) | |
| 110 | + | case protocol.Detach: | |
| 118 | 111 | if effect.current() { | |
| 119 | 112 | d.clientGoneForAttachment(effect, true) | |
| 120 | 113 | return true | |
| 121 | 114 | } | |
| 122 | - | case wire.MsgAck: | |
| 123 | - | if ack, derr := wire.UnmarshalAck(f.Payload); derr == nil { | |
| 124 | - | d.ackOutput(effect, ack.Epoch, ack.State) | |
| 125 | - | } | |
| 126 | - | case wire.MsgOutputResetRequest: | |
| 127 | - | if _, derr := wire.UnmarshalOutputResetRequest(f.Payload); derr == nil { | |
| 128 | - | d.resetOutput(effect) | |
| 129 | - | } | |
| 130 | - | case wire.MsgSamePeerSwitchRequest: | |
| 131 | - | request, derr := wire.UnmarshalSamePeerSwitchRequest(f.Payload) | |
| 132 | - | if derr != nil { | |
| 133 | - | d.log.Warn("malformed same-peer switch request", "err", derr) | |
| 134 | - | break | |
| 135 | - | } | |
| 136 | - | d.switchSamePeerForAttachment(effect, request) | |
| 137 | - | case wire.MsgParkedRouteRequest: | |
| 138 | - | request, derr := wire.UnmarshalParkedRouteRequest(f.Payload) | |
| 139 | - | if derr != nil { | |
| 140 | - | d.log.Warn("malformed parked-route request", "err", derr) | |
| 141 | - | break | |
| 142 | - | } | |
| 143 | - | d.handleParkedRouteRequest(effect, request) | |
| 144 | - | case wire.MsgRecentRouteSnapshot: | |
| 145 | - | snapshot, derr := wire.UnmarshalRecentRouteSnapshot(f.Payload) | |
| 146 | - | if derr != nil { | |
| 147 | - | d.log.Warn("malformed recent route snapshot", "err", derr) | |
| 148 | - | break | |
| 149 | - | } | |
| 150 | - | replayIdentity := effect.ac.setRouteSnapshot(snapshot) | |
| 115 | + | case protocol.Ack: | |
| 116 | + | d.ackOutput(effect, message.Epoch, message.State) | |
| 117 | + | case protocol.OutputResetRequest: | |
| 118 | + | d.resetOutput(effect) | |
| 119 | + | case protocol.SamePeerSwitchRequest: | |
| 120 | + | d.switchSamePeerForAttachment(effect, message) | |
| 121 | + | case protocol.ParkedRouteRequest: | |
| 122 | + | d.handleParkedRouteRequest(effect, message) | |
| 123 | + | case protocol.RecentRouteSnapshot: | |
| 124 | + | replayIdentity := effect.ac.setRouteSnapshot(message) | |
| 151 | 125 | d.invalidateRender(effect.sess, effect.ac, false, "client_frame_routing.go:route-snapshot") | |
| 152 | 126 | if !replayIdentity { | |
| 153 | 127 | break | |
| @@ -155,61 +129,44 @@ func (d *Daemon) handleAttachmentClientFrame(capability attachmentCapability, f | |||
| 155 | 129 | if err := d.sendCommittedRouteIdentityForAttachment(effect); err != nil { | |
| 156 | 130 | d.detachOnAttachmentSendError(effect.capability(), effect.transport.transport) | |
| 157 | 131 | } | |
| 158 | - | case wire.MsgRouteAttentionSubscription: | |
| 159 | - | subscription, derr := wire.UnmarshalRouteAttentionSubscription(f.Payload) | |
| 160 | - | if derr != nil { | |
| 161 | - | d.log.Warn("malformed route attention subscription", "err", derr) | |
| 162 | - | break | |
| 163 | - | } | |
| 164 | - | effect.ac.setRouteAttentionSubscription(subscription) | |
| 132 | + | case protocol.RouteAttentionSubscription: | |
| 133 | + | effect.ac.setRouteAttentionSubscription(message) | |
| 165 | 134 | d.invalidateRender(effect.sess, effect.ac, false, "client_frame_routing.go:route-attention") | |
| 166 | - | case wire.MsgRouteNavigationFailure: | |
| 167 | - | failure, derr := wire.UnmarshalRouteNavigationFailure(f.Payload) | |
| 168 | - | if derr != nil { | |
| 169 | - | d.log.Warn("malformed route navigation failure", "err", derr) | |
| 170 | - | break | |
| 171 | - | } | |
| 172 | - | message := "route navigation failed" | |
| 173 | - | switch failure.Code { | |
| 135 | + | case protocol.RouteNavigationFailure: | |
| 136 | + | notice := "route navigation failed" | |
| 137 | + | switch message.Code { | |
| 174 | 138 | case protocol.RouteFailureStaleSelection: | |
| 175 | - | message = "that recent route is no longer available" | |
| 139 | + | notice = "that recent route is no longer available" | |
| 176 | 140 | case protocol.RouteFailureTargetChanged: | |
| 177 | - | message = "that recent route changed before attach" | |
| 141 | + | notice = "that recent route changed before attach" | |
| 178 | 142 | case protocol.RouteFailureOriginUnavailable: | |
| 179 | - | message = "the original route is no longer available" | |
| 143 | + | notice = "the original route is no longer available" | |
| 180 | 144 | case protocol.RouteFailureUnavailable: | |
| 181 | - | message = "the selected route is unavailable" | |
| 145 | + | notice = "the selected route is unavailable" | |
| 182 | 146 | case protocol.RouteFailureNoSuchRoute: | |
| 183 | - | message = "that recent route no longer exists" | |
| 184 | - | } | |
| 185 | - | d.notify(effect.sess, domain.NoticeWarn, domain.NoticeSessionUnavailable, message, nil) | |
| 186 | - | case wire.MsgCommand: | |
| 187 | - | request, derr := wire.UnmarshalCommandRequest(f.Payload) | |
| 188 | - | if derr != nil { | |
| 189 | - | return false | |
| 147 | + | notice = "that recent route no longer exists" | |
| 190 | 148 | } | |
| 191 | - | result := d.executeAttachedCommand(effect, request) | |
| 149 | + | d.notify(effect.sess, domain.NoticeWarn, domain.NoticeSessionUnavailable, notice, nil) | |
| 150 | + | case protocol.CommandRequest: | |
| 151 | + | result := d.executeAttachedCommand(effect, message) | |
| 192 | 152 | resultEffect, ok := d.commandResultEffect(effect) | |
| 193 | 153 | if !ok { | |
| 194 | 154 | // A remote handoff or explicit detach deliberately retired the old | |
| 195 | 155 | // attachment; it must not be treated as a stale-result send error. | |
| 196 | 156 | return true | |
| 197 | 157 | } | |
| 198 | - | if err := resultEffect.sendControl(frameCommandResult(result)); err != nil { | |
| 158 | + | if err := resultEffect.sendControl(serverCommandResult(result)); err != nil { | |
| 199 | 159 | resultEffect.End() | |
| 200 | 160 | d.detachOnAttachmentSendError(resultEffect.capability(), resultEffect.transport.transport) | |
| 201 | 161 | return true | |
| 202 | 162 | } | |
| 203 | 163 | resultEffect.End() | |
| 204 | - | case wire.MsgPing: | |
| 205 | - | if err := effect.sendControl(framePong()); err != nil { | |
| 164 | + | case protocol.Ping: | |
| 165 | + | if err := effect.sendControl(serverPong()); err != nil { | |
| 206 | 166 | effect.End() | |
| 207 | 167 | d.detachOnAttachmentSendError(effect.capability(), effect.transport.transport) | |
| 208 | 168 | return true | |
| 209 | 169 | } | |
| 210 | - | default: | |
| 211 | - | // Unknown/out-of-band client messages are ignored so a newer | |
| 212 | - | // client can add message types without breaking an older daemon. | |
| 213 | 170 | } | |
| 214 | 171 | return false | |
| 215 | 172 | } | |
M internal/usecase/daemon/client_transport_errors.go +10 -10
| @@ -7,15 +7,15 @@ import ( | |||
| 7 | 7 | ||
| 8 | 8 | // clientGone detaches ac if it is still the session's current client. The | |
| 9 | 9 | // session remains registered and headless after the client is gone. | |
| 10 | - | func (d *Daemon) clientGone(sess *session, ac *attachedClient, failed ports.Transport, explicit bool) { | |
| 10 | + | func (d *Daemon) clientGone(sess *session, ac *attachedClient, failed ports.ServerConnection, explicit bool) { | |
| 11 | 11 | d.clientGoneWithNotice(sess, ac, failed, explicit, true) | |
| 12 | 12 | } | |
| 13 | 13 | ||
| 14 | - | func (d *Daemon) clientGoneWithoutNotice(sess *session, ac *attachedClient, failed ports.Transport, explicit bool) { | |
| 14 | + | func (d *Daemon) clientGoneWithoutNotice(sess *session, ac *attachedClient, failed ports.ServerConnection, explicit bool) { | |
| 15 | 15 | d.clientGoneWithNotice(sess, ac, failed, explicit, false) | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | - | func (d *Daemon) clientGoneWithNotice(sess *session, ac *attachedClient, failed ports.Transport, explicit, notice bool) { | |
| 18 | + | func (d *Daemon) clientGoneWithNotice(sess *session, ac *attachedClient, failed ports.ServerConnection, explicit, notice bool) { | |
| 19 | 19 | if sess == nil || ac == nil { | |
| 20 | 20 | return | |
| 21 | 21 | } | |
| @@ -65,7 +65,7 @@ func (d *Daemon) clientGoneForAttachment(effect *attachmentEffect, explicit bool | |||
| 65 | 65 | return true | |
| 66 | 66 | } | |
| 67 | 67 | ||
| 68 | - | func (d *Daemon) finishClientGone(sess *session, ac *attachedClient, failed ports.Transport, explicit, notice bool) { | |
| 68 | + | func (d *Daemon) finishClientGone(sess *session, ac *attachedClient, failed ports.ServerConnection, explicit, notice bool) { | |
| 69 | 69 | if sess == nil || ac == nil { | |
| 70 | 70 | return | |
| 71 | 71 | } | |
| @@ -111,7 +111,7 @@ func (d *Daemon) finishClientGone(sess *session, ac *attachedClient, failed port | |||
| 111 | 111 | // (the client is actively awaiting it), but deadline-bounded so a | |
| 112 | 112 | // wedged client cannot pin this conn handler and hang Serve's | |
| 113 | 113 | // connWg.Wait. | |
| 114 | - | d.boundedSend(ac, frameDetached(protocol.ReasonDetach)) | |
| 114 | + | d.boundedSend(ac, serverDetached(protocol.ReasonDetach)) | |
| 115 | 115 | } | |
| 116 | 116 | _ = ac.closeCapturedTransport(oldTr) | |
| 117 | 117 | d.log.Info("client detached", "session", name, "explicit", explicit) | |
| @@ -119,7 +119,7 @@ func (d *Daemon) finishClientGone(sess *session, ac *attachedClient, failed port | |||
| 119 | 119 | ||
| 120 | 120 | // detachOnSendError drops a client whose transport failed, leaving the session | |
| 121 | 121 | // registered and headless. | |
| 122 | - | func (d *Daemon) detachOnSendError(sess *session, ac *attachedClient, failed ports.Transport) { | |
| 122 | + | func (d *Daemon) detachOnSendError(sess *session, ac *attachedClient, failed ports.ServerConnection) { | |
| 123 | 123 | expected := transportSnapshot{} | |
| 124 | 124 | if failed != nil { | |
| 125 | 125 | expected = ac.transportSnapshot() | |
| @@ -135,11 +135,11 @@ func (d *Daemon) detachOnSendError(sess *session, ac *attachedClient, failed por | |||
| 135 | 135 | d.clearParkingInFlightIfAbandoned(sess, ac, parkingToken) | |
| 136 | 136 | } | |
| 137 | 137 | ||
| 138 | - | func (d *Daemon) detachOnAttachmentSendError(token attachmentCapability, failed ports.Transport) { | |
| 138 | + | func (d *Daemon) detachOnAttachmentSendError(token attachmentCapability, failed ports.ServerConnection) { | |
| 139 | 139 | d.detachOnAttachmentSendErrorUntil(token, failed, nil) | |
| 140 | 140 | } | |
| 141 | 141 | ||
| 142 | - | func (d *Daemon) detachOnAttachmentSendErrorUntil(token attachmentCapability, failed ports.Transport, done func() <-chan struct{}) { | |
| 142 | + | func (d *Daemon) detachOnAttachmentSendErrorUntil(token attachmentCapability, failed ports.ServerConnection, done func() <-chan struct{}) { | |
| 143 | 143 | // A delayed sender may report after the client has rebound. Only the exact | |
| 144 | 144 | // transport captured by this attachment is allowed to detach either lifecycle. | |
| 145 | 145 | if failed == nil || failed != token.transport.transport { | |
| @@ -160,7 +160,7 @@ func (d *Daemon) detachOnAttachmentSendErrorUntil(token attachmentCapability, fa | |||
| 160 | 160 | // reserveAttachmentSendErrorCleanup accounts for cleanup before End releases the | |
| 161 | 161 | // attachment gate. This closes the WaitGroup Add/Wait race with terminal teardown; | |
| 162 | 162 | // the returned launch function must be invoked immediately after ticket End. | |
| 163 | - | func (d *Daemon) reserveAttachmentSendErrorCleanup(token attachmentCapability, failed ports.Transport) func() { | |
| 163 | + | func (d *Daemon) reserveAttachmentSendErrorCleanup(token attachmentCapability, failed ports.ServerConnection) func() { | |
| 164 | 164 | d.attachmentCleanupWg.Add(1) | |
| 165 | 165 | return func() { | |
| 166 | 166 | go func() { | |
| @@ -178,7 +178,7 @@ func (d *Daemon) reserveAttachmentSendErrorCleanup(token attachmentCapability, f | |||
| 178 | 178 | } | |
| 179 | 179 | } | |
| 180 | 180 | ||
| 181 | - | func (d *Daemon) finishSendErrorDetach(sess *session, ac *attachedClient, failed ports.Transport) { | |
| 181 | + | func (d *Daemon) finishSendErrorDetach(sess *session, ac *attachedClient, failed ports.ServerConnection) { | |
| 182 | 182 | ac.clearSamePeerOffer() | |
| 183 | 183 | ac.clearParkedRoute() | |
| 184 | 184 | name := sess.nameSnapshot() | |
M internal/usecase/daemon/clipboard.go +5 -5
| @@ -255,13 +255,13 @@ func (d *Daemon) forwardClipboardAsync(owner paneEffectLease, b64 string) { | |||
| 255 | 255 | sess.mu.Unlock() | |
| 256 | 256 | } | |
| 257 | 257 | ||
| 258 | - | func (d *Daemon) boundedSendClipboardForward(item clipboardForward, ticket *attachmentEffect) (ports.Transport, error) { | |
| 258 | + | func (d *Daemon) boundedSendClipboardForward(item clipboardForward, ticket *attachmentEffect) (ports.ServerConnection, error) { | |
| 259 | 259 | token := item.token | |
| 260 | 260 | if token.ac == nil || ticket == nil || ticket.ended.Load() || token.transport.transport == nil { | |
| 261 | 261 | return token.transport.transport, errAttachmentTransition | |
| 262 | 262 | } | |
| 263 | 263 | expected := token.transport | |
| 264 | - | ownedTransport, owned := expected.transport.(ports.OwnedSynchronousTransport) | |
| 264 | + | owned := expected.transport.Capabilities().OwnedSynchronousSend | |
| 265 | 265 | send := func() error { | |
| 266 | 266 | ac := token.ac | |
| 267 | 267 | ac.sendMu.Lock() | |
| @@ -277,12 +277,12 @@ func (d *Daemon) boundedSendClipboardForward(item clipboardForward, ticket *atta | |||
| 277 | 277 | } | |
| 278 | 278 | ac.output.lockView() | |
| 279 | 279 | defer ac.output.unlockView() | |
| 280 | - | frame, err := ac.output.sideEffectLocked(item.seq, ac.echoAck.Load()) | |
| 280 | + | output, err := ac.output.sideEffectLocked(item.seq, ac.echoAck.Load()) | |
| 281 | 281 | if err == nil { | |
| 282 | 282 | if owned { | |
| 283 | - | err = ownedTransport.SendSynchronous(frame) | |
| 283 | + | err = expected.transport.SendOutputSynchronous(output) | |
| 284 | 284 | } else { | |
| 285 | - | err = expected.transport.Send(frame) | |
| 285 | + | err = expected.transport.SendOutput(output) | |
| 286 | 286 | } | |
| 287 | 287 | } | |
| 288 | 288 | if err != nil { | |
M internal/usecase/daemon/clipboard_test.go +1 -1
| @@ -204,7 +204,7 @@ func chunkReadPTY(t *testing.T, chunks ...[]byte) *portsmocks.MockPTY { | |||
| 204 | 204 | return p | |
| 205 | 205 | } | |
| 206 | 206 | ||
| 207 | - | func publishActiveClipboardCapability(d *Daemon, sess *session, ac *attachedClient, tr ports.Transport) { | |
| 207 | + | func publishActiveClipboardCapability(d *Daemon, sess *session, ac *attachedClient, tr ports.ServerConnection) { | |
| 208 | 208 | rc := d.attachCoordinator(sess, nil, ac, true) | |
| 209 | 209 | token := sess.captureAttachmentCapability(ac, tr) | |
| 210 | 210 | token.lease = rc.attachmentLease(ac) | |
M internal/usecase/daemon/control.go +9 -17
| @@ -13,22 +13,16 @@ import ( | |||
| 13 | 13 | "github.com/bnema/vev/internal/domain" | |
| 14 | 14 | "github.com/bnema/vev/internal/ports" | |
| 15 | 15 | "github.com/bnema/vev/internal/protocol" | |
| 16 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 17 | 16 | "github.com/bnema/vev/internal/usecase/command" | |
| 18 | 17 | "github.com/bnema/vev/internal/usecase/layout" | |
| 19 | 18 | ) | |
| 20 | 19 | ||
| 21 | - | // handleCommand serves one one-shot control request. The leading version is | |
| 22 | - | // checked before decoding the versioned body so newer layouts fail cleanly. | |
| 23 | - | func (d *Daemon) handleCommand(tr ports.Transport, f wire.Frame) error { | |
| 20 | + | // handleCommand serves one typed one-shot control request. | |
| 21 | + | func (d *Daemon) handleCommand(tr ports.ServerConnection, request protocol.CommandRequest) error { | |
| 24 | 22 | defer func() { _ = tr.Close() }() | |
| 25 | 23 | ||
| 26 | - | if version, ok := wire.PeekCommandVersion(f.Payload); !ok || version != protocol.Version { | |
| 27 | - | return d.sendCommandResult(tr, protocol.CommandResult{Code: protocol.ErrVersionMismatch, Text: "protocol version mismatch"}) | |
| 28 | - | } | |
| 29 | - | request, err := wire.UnmarshalCommandRequest(f.Payload) | |
| 30 | - | if err != nil { | |
| 31 | - | return d.sendCommandResult(tr, protocol.CommandResult{Code: protocol.ErrInternal, Text: "malformed command request"}) | |
| 24 | + | if request.Version != protocol.Version { | |
| 25 | + | return d.sendCommandResult(tr, protocol.CommandResult{RequestID: request.RequestID, Code: protocol.ErrVersionMismatch, Text: "protocol version mismatch"}) | |
| 32 | 26 | } | |
| 33 | 27 | if request.Attached { | |
| 34 | 28 | return d.sendCommandResult(tr, protocol.CommandResult{ | |
| @@ -69,8 +63,8 @@ func (d *Daemon) handleCommand(tr ports.Transport, f wire.Frame) error { | |||
| 69 | 63 | ||
| 70 | 64 | // sendCommandResult gives one-shot control responses the same observable | |
| 71 | 65 | // transport-failure behavior regardless of which validation path produced it. | |
| 72 | - | func (d *Daemon) sendCommandResult(tr ports.Transport, result protocol.CommandResult) error { | |
| 73 | - | if err := d.boundedControlSend(tr, frameCommandResult(result)); err != nil { | |
| 66 | + | func (d *Daemon) sendCommandResult(tr ports.ServerConnection, result protocol.CommandResult) error { | |
| 67 | + | if err := d.boundedControlSend(tr, serverCommandResult(result)); err != nil { | |
| 74 | 68 | d.log.Warn("command response send failed", "err", err) | |
| 75 | 69 | return err | |
| 76 | 70 | } | |
| @@ -80,17 +74,15 @@ func (d *Daemon) sendCommandResult(tr ports.Transport, result protocol.CommandRe | |||
| 80 | 74 | // boundedControlSend keeps one-shot control handlers from waiting forever on a | |
| 81 | 75 | // client that stopped reading. A timeout closes the exact transport so a | |
| 82 | 76 | // blocked Send can unwind before the handler returns. | |
| 83 | - | func (d *Daemon) boundedControlSend(tr ports.Transport, frame wire.Frame) error { | |
| 84 | - | _, err := d.boundedSendWith(tr, func() error { return tr.Send(frame) }) | |
| 77 | + | func (d *Daemon) boundedControlSend(tr ports.ServerConnection, message protocol.ServerMessage) error { | |
| 78 | + | _, err := d.boundedSendWith(tr, func() error { return tr.SendServer(message) }) | |
| 85 | 79 | if errors.Is(err, errSendTimedOut) { | |
| 86 | 80 | _ = tr.Close() | |
| 87 | 81 | } | |
| 88 | 82 | return err | |
| 89 | 83 | } | |
| 90 | 84 | ||
| 91 | - | func frameCommandResult(result protocol.CommandResult) wire.Frame { | |
| 92 | - | return wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(result)} | |
| 93 | - | } | |
| 85 | + | func serverCommandResult(result protocol.CommandResult) protocol.CommandResult { return result } | |
| 94 | 86 | ||
| 95 | 87 | func (d *Daemon) dispatchCommand(ctx context.Context, request protocol.CommandRequest) protocol.CommandResult { | |
| 96 | 88 | cmd, ok := command.BySlug(request.Slug) | |
M internal/usecase/daemon/control_test.go +9 -9
| @@ -139,7 +139,7 @@ func TestHandleCommandTimesOutWithRequestGeneration(t *testing.T) { | |||
| 139 | 139 | defer releaseConn() | |
| 140 | 140 | done := make(chan error, 1) | |
| 141 | 141 | go func() { | |
| 142 | - | done <- d.handleCommand(tr, frame) | |
| 142 | + | done <- d.handleCommandFrame(tr, frame) | |
| 143 | 143 | }() | |
| 144 | 144 | <-factory.entered | |
| 145 | 145 | timer := <-clock.timers | |
| @@ -159,7 +159,7 @@ func TestHandleCommandResponseSendTimeoutClosesTransport(t *testing.T) { | |||
| 159 | 159 | tr := newBlockingControlSendTransport() | |
| 160 | 160 | frame := commandFrame(t, protocol.CommandRequest{RequestID: 41, Attached: true}) | |
| 161 | 161 | done := make(chan error, 1) | |
| 162 | - | go func() { done <- d.handleCommand(tr, frame) }() | |
| 162 | + | go func() { done <- d.handleCommandFrame(tr, frame) }() | |
| 163 | 163 | ||
| 164 | 164 | <-tr.started | |
| 165 | 165 | timer := <-clock.timers | |
| @@ -184,7 +184,7 @@ func TestHandleCommandAttachedRejectionReturnsSendFailure(t *testing.T) { | |||
| 184 | 184 | sendErr := errors.New("command result send failed") | |
| 185 | 185 | tr := &commandSendErrorTransport{err: sendErr} | |
| 186 | 186 | ||
| 187 | - | err := d.handleCommand(tr, commandFrame(t, protocol.CommandRequest{ | |
| 187 | + | err := d.handleCommandFrame(tr, commandFrame(t, protocol.CommandRequest{ | |
| 188 | 188 | RequestID: 41, | |
| 189 | 189 | Attached: true, | |
| 190 | 190 | })) | |
| @@ -201,7 +201,7 @@ func TestHandleCommandRejectsVersionBeforeDecodeOrDispatch(t *testing.T) { | |||
| 201 | 201 | tr, sends, _ := newConn(t, frame) | |
| 202 | 202 | ||
| 203 | 203 | d := newTestDaemon(t, nil, stubClock{}) | |
| 204 | - | require.NoError(t, d.handleCommand(tr, frame)) | |
| 204 | + | require.NoError(t, d.handleCommandFrame(tr, frame)) | |
| 205 | 205 | ||
| 206 | 206 | result := awaitCommandResult(t, sends) | |
| 207 | 207 | require.False(t, result.OK) | |
| @@ -887,7 +887,7 @@ func TestHandleCommandSerializesSelfTargetOnNonActiveTab(t *testing.T) { | |||
| 887 | 887 | firstTransport, firstSends, _ := newConn(t, firstFrame) | |
| 888 | 888 | firstDone := make(chan struct{}) | |
| 889 | 889 | go func() { | |
| 890 | - | if err := d.handleCommand(firstTransport, firstFrame); err != nil { | |
| 890 | + | if err := d.handleCommandFrame(firstTransport, firstFrame); err != nil { | |
| 891 | 891 | t.Errorf("handle first command: %v", err) | |
| 892 | 892 | } | |
| 893 | 893 | close(firstDone) | |
| @@ -905,7 +905,7 @@ func TestHandleCommandSerializesSelfTargetOnNonActiveTab(t *testing.T) { | |||
| 905 | 905 | secondTransport, secondSends, _ := newConn(t, secondFrame) | |
| 906 | 906 | secondDone := make(chan struct{}) | |
| 907 | 907 | go func() { | |
| 908 | - | if err := d.handleCommand(secondTransport, secondFrame); err != nil { | |
| 908 | + | if err := d.handleCommandFrame(secondTransport, secondFrame); err != nil { | |
| 909 | 909 | t.Errorf("handle second command: %v", err) | |
| 910 | 910 | } | |
| 911 | 911 | close(secondDone) | |
| @@ -1287,14 +1287,14 @@ func TestHandleCommandOppositeMoveCommandsDoNotDeadlock(t *testing.T) { | |||
| 1287 | 1287 | done := make(chan struct{}, 2) | |
| 1288 | 1288 | go func() { | |
| 1289 | 1289 | <-start | |
| 1290 | - | if err := d.handleCommand(leftTransport, leftFrame); err != nil { | |
| 1290 | + | if err := d.handleCommandFrame(leftTransport, leftFrame); err != nil { | |
| 1291 | 1291 | t.Errorf("handle left command: %v", err) | |
| 1292 | 1292 | } | |
| 1293 | 1293 | done <- struct{}{} | |
| 1294 | 1294 | }() | |
| 1295 | 1295 | go func() { | |
| 1296 | 1296 | <-start | |
| 1297 | - | if err := d.handleCommand(rightTransport, rightFrame); err != nil { | |
| 1297 | + | if err := d.handleCommandFrame(rightTransport, rightFrame); err != nil { | |
| 1298 | 1298 | t.Errorf("handle right command: %v", err) | |
| 1299 | 1299 | } | |
| 1300 | 1300 | done <- struct{}{} | |
| @@ -1339,7 +1339,7 @@ func sendCommand(t *testing.T, d *Daemon, request protocol.CommandRequest) proto | |||
| 1339 | 1339 | t.Helper() | |
| 1340 | 1340 | frame := commandFrame(t, request) | |
| 1341 | 1341 | tr, sends, _ := newConn(t, frame) | |
| 1342 | - | require.NoError(t, d.handleCommand(tr, frame)) | |
| 1342 | + | require.NoError(t, d.handleCommandFrame(tr, frame)) | |
| 1343 | 1343 | return awaitCommandResult(t, sends) | |
| 1344 | 1344 | } | |
| 1345 | 1345 | ||
M internal/usecase/daemon/copymode_test.go +2 -2
| @@ -68,7 +68,7 @@ func TestOwnedSynchronousSendReturnsCapturedTransportAcrossReplacement(t *testin | |||
| 68 | 68 | ||
| 69 | 69 | type swapErrorTransport struct { | |
| 70 | 70 | ac *attachedClient | |
| 71 | - | replacement ports.Transport | |
| 71 | + | replacement ports.ServerConnection | |
| 72 | 72 | err error | |
| 73 | 73 | } | |
| 74 | 74 | ||
| @@ -82,7 +82,7 @@ func (t *swapErrorTransport) Close() error { return nil } | |||
| 82 | 82 | ||
| 83 | 83 | type ownedSwapErrorTransport struct { | |
| 84 | 84 | ac *attachedClient | |
| 85 | - | replacement ports.Transport | |
| 85 | + | replacement ports.ServerConnection | |
| 86 | 86 | err error | |
| 87 | 87 | sent chan wire.Frame | |
| 88 | 88 | } | |
M internal/usecase/daemon/daemon.go +76 -64
| @@ -18,7 +18,6 @@ import ( | |||
| 18 | 18 | "github.com/bnema/vev/internal/domain" | |
| 19 | 19 | "github.com/bnema/vev/internal/ports" | |
| 20 | 20 | "github.com/bnema/vev/internal/protocol" | |
| 21 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 22 | 21 | "github.com/bnema/vev/internal/usecase/keys" | |
| 23 | 22 | recoveryusecase "github.com/bnema/vev/internal/usecase/recovery" | |
| 24 | 23 | ) | |
| @@ -739,7 +738,7 @@ func New(ptys ports.PTYFactory, clock ports.Clock, log *slog.Logger, opts ...Opt | |||
| 739 | 738 | // Serve runs the accept loop over l, owning it for the loop's lifetime. It | |
| 740 | 739 | // returns when the last session is removed or ctx is cancelled; on the latter | |
| 741 | 740 | // path attached clients are detached with ReasonServerShutdown. | |
| 742 | - | func (d *Daemon) Serve(ctx context.Context, l ports.Listener) error { | |
| 741 | + | func (d *Daemon) Serve(ctx context.Context, l ports.ServerListener) error { | |
| 743 | 742 | d.serveCtx, d.serveCancel = context.WithCancel(ctx) | |
| 744 | 743 | defer d.serveCancel() | |
| 745 | 744 | d.hardCtx, d.hardCancel = context.WithCancel(context.Background()) | |
| @@ -1012,54 +1011,58 @@ func (d *Daemon) sessionsSnapshotLocked() []*session { | |||
| 1012 | 1011 | return sessionsSnapshot(d.sessions) | |
| 1013 | 1012 | } | |
| 1014 | 1013 | ||
| 1015 | - | // handleConn reads the first frame off a fresh connection and routes it. A | |
| 1016 | - | // context watcher closes the transport on serve-context cancel or handshake | |
| 1017 | - | // timeout so blocked transport operations unwind. | |
| 1018 | - | func (d *Daemon) handleConn(tr ports.Transport) { | |
| 1014 | + | // handleConn reads the first typed message off a fresh connection and routes it. | |
| 1015 | + | // A context watcher closes the connection on cancellation or handshake timeout. | |
| 1016 | + | func (d *Daemon) handleConn(tr ports.ServerConnection) { | |
| 1019 | 1017 | handshakeCtx, timedOut, finishHandshake := d.newHandshakeContext(d.hardCtx) | |
| 1020 | 1018 | stopTransport := watchHandshakeTransport(handshakeCtx, tr) | |
| 1021 | 1019 | defer finishHandshake() | |
| 1022 | 1020 | defer stopTransport() | |
| 1023 | 1021 | ||
| 1024 | - | var first wire.Frame | |
| 1022 | + | var first protocol.ClientMessage | |
| 1025 | 1023 | err := boundedHandshakeOperation(handshakeCtx, tr, func() error { | |
| 1026 | - | var recvErr error | |
| 1027 | - | first, recvErr = tr.Recv() | |
| 1028 | - | return recvErr | |
| 1024 | + | var receiveErr error | |
| 1025 | + | first, receiveErr = tr.ReceiveClient() | |
| 1026 | + | return receiveErr | |
| 1029 | 1027 | }) | |
| 1030 | 1028 | if err != nil { | |
| 1031 | - | err = handshakeContextError(d.hardCtx, timedOut, err) | |
| 1032 | - | d.log.Warn("connection closed before hello", "err", err) | |
| 1029 | + | var failure *protocol.DecodeFailure | |
| 1030 | + | if errors.As(err, &failure) { | |
| 1031 | + | d.handleInitialDecodeFailure(handshakeCtx, tr, failure) | |
| 1032 | + | } else { | |
| 1033 | + | err = handshakeContextError(d.hardCtx, timedOut, err) | |
| 1034 | + | d.log.Warn("connection closed before hello", "err", err) | |
| 1035 | + | } | |
| 1033 | 1036 | _ = tr.Close() | |
| 1034 | 1037 | return | |
| 1035 | 1038 | } | |
| 1036 | - | switch first.Type { | |
| 1037 | - | case wire.MsgList: | |
| 1039 | + | switch message := first.(type) { | |
| 1040 | + | case protocol.List: | |
| 1038 | 1041 | stopTransport() | |
| 1039 | 1042 | finishHandshake() | |
| 1040 | 1043 | d.handleList(tr) | |
| 1041 | - | case wire.MsgCommand: | |
| 1044 | + | case protocol.CommandRequest: | |
| 1042 | 1045 | stopTransport() | |
| 1043 | 1046 | finishHandshake() | |
| 1044 | - | if err := d.handleCommand(tr, first); err != nil { | |
| 1047 | + | if err := d.handleCommand(tr, message); err != nil { | |
| 1045 | 1048 | d.log.Warn("command handler failed", "err", err) | |
| 1046 | 1049 | } | |
| 1047 | - | case wire.MsgRemotePreviewRequest: | |
| 1050 | + | case protocol.RemotePreviewRequest: | |
| 1048 | 1051 | stopTransport() | |
| 1049 | 1052 | finishHandshake() | |
| 1050 | - | if err := d.handleRemotePreview(tr, first); err != nil { | |
| 1053 | + | if err := d.handleRemotePreview(tr, message); err != nil { | |
| 1051 | 1054 | d.log.Warn("remote preview handler failed", "err", err) | |
| 1052 | 1055 | } | |
| 1053 | - | case wire.MsgKill: | |
| 1056 | + | case protocol.Kill: | |
| 1054 | 1057 | stopTransport() | |
| 1055 | 1058 | finishHandshake() | |
| 1056 | - | d.handleKill(tr, first) | |
| 1057 | - | case wire.MsgHello: | |
| 1058 | - | d.handleHelloWithContext(handshakeCtx, timedOut, stopTransport, finishHandshake, tr, first) | |
| 1059 | + | d.handleKill(tr, message) | |
| 1060 | + | case protocol.Hello: | |
| 1061 | + | d.handleHelloWithContext(handshakeCtx, timedOut, stopTransport, finishHandshake, tr, message) | |
| 1059 | 1062 | default: | |
| 1060 | - | d.log.Warn("hello rejected", "err", "expected hello", "type", first.Type) | |
| 1063 | + | d.log.Warn("hello rejected", "err", "expected hello") | |
| 1061 | 1064 | _ = boundedHandshakeOperation(handshakeCtx, tr, func() error { | |
| 1062 | - | return tr.Send(frameError(protocol.ErrInternal, "expected hello")) | |
| 1065 | + | return tr.SendServer(serverError(protocol.ErrInternal, "expected hello")) | |
| 1063 | 1066 | }) | |
| 1064 | 1067 | stopTransport() | |
| 1065 | 1068 | finishHandshake() | |
| @@ -1067,9 +1070,35 @@ func (d *Daemon) handleConn(tr ports.Transport) { | |||
| 1067 | 1070 | } | |
| 1068 | 1071 | } | |
| 1069 | 1072 | ||
| 1073 | + | func (d *Daemon) handleInitialDecodeFailure(ctx context.Context, tr ports.ServerConnection, failure *protocol.DecodeFailure) { | |
| 1074 | + | send := func(message protocol.ServerMessage) { | |
| 1075 | + | _ = boundedHandshakeOperation(ctx, tr, func() error { return tr.SendServer(message) }) | |
| 1076 | + | } | |
| 1077 | + | switch failure.Kind { | |
| 1078 | + | case protocol.DecodeMessageHello: | |
| 1079 | + | if failure.Version != 0 && failure.Version != protocol.Version { | |
| 1080 | + | send(serverError(protocol.ErrVersionMismatch, "protocol version mismatch")) | |
| 1081 | + | return | |
| 1082 | + | } | |
| 1083 | + | send(serverError(protocol.ErrInternal, "malformed hello")) | |
| 1084 | + | case protocol.DecodeMessageCommand: | |
| 1085 | + | code, text := uint16(protocol.ErrInternal), "malformed command request" | |
| 1086 | + | if failure.Version != 0 && failure.Version != protocol.Version { | |
| 1087 | + | code, text = protocol.ErrVersionMismatch, "protocol version mismatch" | |
| 1088 | + | } | |
| 1089 | + | send(protocol.CommandResult{RequestID: failure.RequestID, Code: code, Text: text}) | |
| 1090 | + | case protocol.DecodeMessageKill: | |
| 1091 | + | send(serverError(protocol.ErrInternal, "malformed kill request")) | |
| 1092 | + | case protocol.DecodeMessageRemotePreview: | |
| 1093 | + | send(protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: protocol.RemotePreviewMalformed}) | |
| 1094 | + | default: | |
| 1095 | + | send(serverError(protocol.ErrInternal, "expected hello")) | |
| 1096 | + | } | |
| 1097 | + | } | |
| 1098 | + | ||
| 1070 | 1099 | // handleList replies with the current session listing and closes the (one-shot | |
| 1071 | 1100 | // control) connection. | |
| 1072 | - | func (d *Daemon) handleList(tr ports.Transport) { | |
| 1101 | + | func (d *Daemon) handleList(tr ports.ServerConnection) { | |
| 1073 | 1102 | defer func() { _ = tr.Close() }() | |
| 1074 | 1103 | ||
| 1075 | 1104 | d.mu.Lock() | |
| @@ -1105,35 +1134,30 @@ func (d *Daemon) handleList(tr ports.Transport) { | |||
| 1105 | 1134 | d.mu.Unlock() | |
| 1106 | 1135 | ||
| 1107 | 1136 | sort.Slice(infos, func(i, j int) bool { return infos[i].Name < infos[j].Name }) | |
| 1108 | - | _ = d.boundedControlSend(tr, frameSessions(infos)) | |
| 1137 | + | _ = d.boundedControlSend(tr, serverSessions(infos)) | |
| 1109 | 1138 | } | |
| 1110 | 1139 | ||
| 1111 | 1140 | // handleKill terminates the requested live session or stopped named session, | |
| 1112 | 1141 | // or all sessions, and closes the control connection; the resulting EOF is the | |
| 1113 | 1142 | // client's success signal. | |
| 1114 | - | func (d *Daemon) handleKill(tr ports.Transport, f wire.Frame) { | |
| 1143 | + | func (d *Daemon) handleKill(tr ports.ServerConnection, request protocol.Kill) { | |
| 1115 | 1144 | defer func() { _ = tr.Close() }() | |
| 1116 | 1145 | ||
| 1117 | - | k, err := wire.UnmarshalKill(f.Payload) | |
| 1118 | - | if err != nil { | |
| 1119 | - | _ = d.boundedControlSend(tr, frameError(protocol.ErrInternal, "malformed kill request")) | |
| 1120 | - | return | |
| 1121 | - | } | |
| 1122 | - | if k.All { | |
| 1146 | + | if request.All { | |
| 1123 | 1147 | d.shutdownAll(protocol.ReasonServerShutdown) | |
| 1124 | 1148 | return | |
| 1125 | 1149 | } | |
| 1126 | 1150 | ||
| 1127 | 1151 | d.mu.Lock() | |
| 1128 | - | target := d.findByNameLocked(k.Name) | |
| 1152 | + | target := d.findByNameLocked(request.Name) | |
| 1129 | 1153 | if target == nil { | |
| 1130 | - | if _, ok := d.inactive[k.Name]; ok { | |
| 1154 | + | if _, ok := d.inactive[request.Name]; ok { | |
| 1131 | 1155 | d.mu.Unlock() | |
| 1132 | 1156 | // Stopped sessions use the same catalogue-first, incarnation-second | |
| 1133 | 1157 | // deletion order as live and offline purges. | |
| 1134 | - | if err := d.retryStoppedPurge(k.Name); err != nil { | |
| 1135 | - | d.log.Warn("deleting stopped session failed", "err", err, "session", k.Name) | |
| 1136 | - | _ = d.boundedControlSend(tr, frameError(protocol.ErrInternal, "deleting stopped session failed")) | |
| 1158 | + | if err := d.retryStoppedPurge(request.Name); err != nil { | |
| 1159 | + | d.log.Warn("deleting stopped session failed", "err", err, "session", request.Name) | |
| 1160 | + | _ = d.boundedControlSend(tr, serverError(protocol.ErrInternal, "deleting stopped session failed")) | |
| 1137 | 1161 | } | |
| 1138 | 1162 | return | |
| 1139 | 1163 | } | |
| @@ -1141,54 +1165,42 @@ func (d *Daemon) handleKill(tr ports.Transport, f wire.Frame) { | |||
| 1141 | 1165 | d.mu.Unlock() | |
| 1142 | 1166 | ||
| 1143 | 1167 | if target == nil { | |
| 1144 | - | _ = d.boundedControlSend(tr, frameError(protocol.ErrNoSuchSession, "no such session: "+k.Name)) | |
| 1168 | + | _ = d.boundedControlSend(tr, serverError(protocol.ErrNoSuchSession, "no such session: "+request.Name)) | |
| 1145 | 1169 | return | |
| 1146 | 1170 | } | |
| 1147 | 1171 | if err := d.killSession(target, protocol.ReasonSessionKilled, true); err != nil { | |
| 1148 | - | _ = d.boundedControlSend(tr, frameError(protocol.ErrInternal, "deleting persisted session failed")) | |
| 1172 | + | _ = d.boundedControlSend(tr, serverError(protocol.ErrInternal, "deleting persisted session failed")) | |
| 1149 | 1173 | } | |
| 1150 | 1174 | } | |
| 1151 | 1175 | ||
| 1152 | 1176 | // handleHello runs the attach handshake for direct package callers. Accepted | |
| 1153 | 1177 | // connections use handleHelloWithContext so the deadline also covers the first | |
| 1154 | 1178 | // frame read in handleConn. | |
| 1155 | - | func (d *Daemon) handleHello(tr ports.Transport, f wire.Frame) { | |
| 1179 | + | func (d *Daemon) handleHello(tr ports.ServerConnection, hello protocol.Hello) { | |
| 1156 | 1180 | handshakeCtx, timedOut, finishHandshake := d.newHandshakeContext(context.Background()) | |
| 1157 | 1181 | stopTransport := watchHandshakeTransport(handshakeCtx, tr) | |
| 1158 | 1182 | defer finishHandshake() | |
| 1159 | 1183 | defer stopTransport() | |
| 1160 | - | d.handleHelloWithContext(handshakeCtx, timedOut, stopTransport, finishHandshake, tr, f) | |
| 1184 | + | d.handleHelloWithContext(handshakeCtx, timedOut, stopTransport, finishHandshake, tr, hello) | |
| 1161 | 1185 | } | |
| 1162 | 1186 | ||
| 1163 | 1187 | // handleHelloWithContext runs the attach handshake: version check, intent | |
| 1164 | 1188 | // routing, Welcome, guaranteed first paint, then the per-connection input loop. | |
| 1165 | - | func (d *Daemon) handleHelloWithContext(handshakeCtx context.Context, timedOut <-chan struct{}, stopHandshakeTransport, finishHandshake func(), tr ports.Transport, f wire.Frame) { | |
| 1189 | + | func (d *Daemon) handleHelloWithContext(handshakeCtx context.Context, timedOut <-chan struct{}, stopHandshakeTransport, finishHandshake func(), tr ports.ServerConnection, h protocol.Hello) { | |
| 1166 | 1190 | if err := handshakeContextError(context.Background(), timedOut, handshakeCtx.Err()); err != nil { | |
| 1167 | 1191 | _ = tr.Close() | |
| 1168 | 1192 | return | |
| 1169 | 1193 | } | |
| 1170 | - | sendHandshake := func(frame wire.Frame) error { | |
| 1171 | - | err := boundedHandshakeOperation(handshakeCtx, tr, func() error { return tr.Send(frame) }) | |
| 1194 | + | sendHandshake := func(message protocol.ServerMessage) error { | |
| 1195 | + | err := boundedHandshakeOperation(handshakeCtx, tr, func() error { return tr.SendServer(message) }) | |
| 1172 | 1196 | if err != nil { | |
| 1173 | 1197 | return handshakeContextError(context.Background(), timedOut, err) | |
| 1174 | 1198 | } | |
| 1175 | 1199 | return nil | |
| 1176 | 1200 | } | |
| 1177 | - | h, err := wire.UnmarshalHello(f.Payload) | |
| 1178 | - | if err != nil { | |
| 1179 | - | if version, ok := wire.PeekHelloVersion(f.Payload); ok && version != protocol.Version { | |
| 1180 | - | d.log.Warn("hello rejected", "err", "protocol version mismatch", "version", version, "expected", protocol.Version) | |
| 1181 | - | _ = sendHandshake(frameError(protocol.ErrVersionMismatch, "protocol version mismatch")) | |
| 1182 | - | } else { | |
| 1183 | - | d.log.Warn("hello rejected", "err", err) | |
| 1184 | - | _ = sendHandshake(frameError(protocol.ErrInternal, "malformed hello")) | |
| 1185 | - | } | |
| 1186 | - | _ = tr.Close() | |
| 1187 | - | return | |
| 1188 | - | } | |
| 1189 | 1201 | if h.Version != protocol.Version { | |
| 1190 | 1202 | d.log.Warn("hello rejected", "err", "protocol version mismatch", "version", h.Version, "expected", protocol.Version, "intent", h.Intent, "session", h.Name) | |
| 1191 | - | _ = sendHandshake(frameError(protocol.ErrVersionMismatch, "protocol version mismatch")) | |
| 1203 | + | _ = sendHandshake(serverError(protocol.ErrVersionMismatch, "protocol version mismatch")) | |
| 1192 | 1204 | _ = tr.Close() | |
| 1193 | 1205 | return | |
| 1194 | 1206 | } | |
| @@ -1197,9 +1209,9 @@ func (d *Daemon) handleHelloWithContext(handshakeCtx context.Context, timedOut < | |||
| 1197 | 1209 | if rerr != nil { | |
| 1198 | 1210 | d.log.Warn("hello rejected", "err", rerr, "intent", h.Intent, "session", h.Name) | |
| 1199 | 1211 | if pe, ok := errors.AsType[*protoErr](rerr); ok { | |
| 1200 | - | _ = sendHandshake(frameError(pe.code, pe.text)) | |
| 1212 | + | _ = sendHandshake(serverError(pe.code, pe.text)) | |
| 1201 | 1213 | } else { | |
| 1202 | - | _ = sendHandshake(frameError(protocol.ErrInternal, rerr.Error())) | |
| 1214 | + | _ = sendHandshake(serverError(protocol.ErrInternal, rerr.Error())) | |
| 1203 | 1215 | } | |
| 1204 | 1216 | _ = tr.Close() | |
| 1205 | 1217 | return | |
| @@ -1230,7 +1242,7 @@ func (d *Daemon) handleHelloWithContext(handshakeCtx context.Context, timedOut < | |||
| 1230 | 1242 | return | |
| 1231 | 1243 | } | |
| 1232 | 1244 | welcomeDone, welcomeErr := boundedHandshakeOperationTracked(handshakeCtx, tr, func() error { | |
| 1233 | - | frame, err := frameWelcome(sess, d.resumeTokenSnapshot(ac)) | |
| 1245 | + | frame, err := serverWelcome(sess, d.resumeTokenSnapshot(ac)) | |
| 1234 | 1246 | if err != nil { | |
| 1235 | 1247 | return err | |
| 1236 | 1248 | } | |
| @@ -1311,7 +1323,7 @@ func (e *protoErr) Error() string { return e.text } | |||
| 1311 | 1323 | // unlocking d.mu before returning, including every error path. It publishes | |
| 1312 | 1324 | // terminal and role state before releasing d.mu, then defers coordinator | |
| 1313 | 1325 | // cleanup so obsolete workers never delay the new handshake. | |
| 1314 | - | func (d *Daemon) finishAttach(sess *session, tr ports.Transport, sz domain.Size, h protocol.Hello) (*attachedClient, error) { | |
| 1326 | + | func (d *Daemon) finishAttach(sess *session, tr ports.ServerConnection, sz domain.Size, h protocol.Hello) (*attachedClient, error) { | |
| 1315 | 1327 | initialTabIndex := -1 | |
| 1316 | 1328 | if h.RemoteTarget != nil { | |
| 1317 | 1329 | var ok bool | |
| @@ -1478,7 +1490,7 @@ func (d *Daemon) validateExactSessionTarget(ctx context.Context, target protocol | |||
| 1478 | 1490 | return d.validateExactSessionTargetLocked(target) | |
| 1479 | 1491 | } | |
| 1480 | 1492 | ||
| 1481 | - | func (d *Daemon) route(h protocol.Hello, tr ports.Transport) (*session, *attachedClient, error) { | |
| 1493 | + | func (d *Daemon) route(h protocol.Hello, tr ports.ServerConnection) (*session, *attachedClient, error) { | |
| 1482 | 1494 | ctx := d.serveCtx | |
| 1483 | 1495 | if ctx == nil { | |
| 1484 | 1496 | ctx = context.Background() | |
| @@ -1486,7 +1498,7 @@ func (d *Daemon) route(h protocol.Hello, tr ports.Transport) (*session, *attache | |||
| 1486 | 1498 | return d.routeWithContext(ctx, h, tr) | |
| 1487 | 1499 | } | |
| 1488 | 1500 | ||
| 1489 | - | func (d *Daemon) routeWithContext(ctx context.Context, h protocol.Hello, tr ports.Transport) (*session, *attachedClient, error) { | |
| 1501 | + | func (d *Daemon) routeWithContext(ctx context.Context, h protocol.Hello, tr ports.ServerConnection) (*session, *attachedClient, error) { | |
| 1490 | 1502 | if ctx == nil { | |
| 1491 | 1503 | ctx = context.Background() | |
| 1492 | 1504 | } | |
M internal/usecase/daemon/daemon_test.go +7 -7
| @@ -237,9 +237,9 @@ func newFactorySeq(t *testing.T, ptys ...ports.PTY) *portsmocks.MockPTYFactory { | |||
| 237 | 237 | // newConn scripts a MockTransport: Recv yields first then more (in order), | |
| 238 | 238 | // then blocks until the connection is released or Closed (returning io.EOF). | |
| 239 | 239 | // Every Send is captured on the returned channel. | |
| 240 | - | func newConn(t *testing.T, first wire.Frame, more ...wire.Frame) (*portsmocks.MockTransport, chan wire.Frame, func()) { | |
| 240 | + | func newConn(t *testing.T, first wire.Frame, more ...wire.Frame) (*mockServerConnection, chan wire.Frame, func()) { | |
| 241 | 241 | t.Helper() | |
| 242 | - | tr := portsmocks.NewMockTransport(t) | |
| 242 | + | tr := newMockServerConnection(t) | |
| 243 | 243 | sends := make(chan wire.Frame, 64) | |
| 244 | 244 | recvCh := make(chan wire.Frame, 1+len(more)) | |
| 245 | 245 | recvCh <- first | |
| @@ -331,7 +331,7 @@ func listSessions(t *testing.T, d *Daemon) protocol.Sessions { | |||
| 331 | 331 | // attachment publication and Welcome acceptance. It uses the generated | |
| 332 | 332 | // transport mock while channels make both sides of the wire deterministic. | |
| 333 | 333 | type welcomeBlockingTransport struct { | |
| 334 | - | tr *portsmocks.MockTransport | |
| 334 | + | tr *mockServerConnection | |
| 335 | 335 | sends chan wire.Frame | |
| 336 | 336 | welcomeEntered chan struct{} | |
| 337 | 337 | releaseWelcome chan struct{} | |
| @@ -348,7 +348,7 @@ func newWelcomeBlockingTransport(t *testing.T) *welcomeBlockingTransport { | |||
| 348 | 348 | releaseWelcome: make(chan struct{}), | |
| 349 | 349 | recvDone: make(chan struct{}), | |
| 350 | 350 | } | |
| 351 | - | b.tr = portsmocks.NewMockTransport(t) | |
| 351 | + | b.tr = newMockServerConnection(t) | |
| 352 | 352 | b.tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error { | |
| 353 | 353 | b.sends <- f | |
| 354 | 354 | if f.Type == wire.MsgWelcome { | |
| @@ -375,9 +375,9 @@ func (b *welcomeBlockingTransport) finish() { | |||
| 375 | 375 | b.closeOnce.Do(func() { close(b.recvDone) }) | |
| 376 | 376 | } | |
| 377 | 377 | ||
| 378 | - | func newCapturingTransport(t testing.TB) (*portsmocks.MockTransport, chan wire.Frame) { | |
| 378 | + | func newCapturingTransport(t testing.TB) (*mockServerConnection, chan wire.Frame) { | |
| 379 | 379 | t.Helper() | |
| 380 | - | tr := portsmocks.NewMockTransport(t) | |
| 380 | + | tr := newMockServerConnection(t) | |
| 381 | 381 | sends := make(chan wire.Frame, 64) | |
| 382 | 382 | tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error { | |
| 383 | 383 | sends <- f | |
| @@ -666,7 +666,7 @@ func TestHandleHelloDefersFreshOutputUntilWelcome(t *testing.T) { | |||
| 666 | 666 | tr := newWelcomeBlockingTransport(t) | |
| 667 | 667 | done := make(chan struct{}) | |
| 668 | 668 | go func() { | |
| 669 | - | d.handleHello(tr.tr, mustHello(protocol.IntentNew, "welcome-gate", domain.Size{Cols: 80, Rows: 24})) | |
| 669 | + | d.handleHelloFrame(tr.tr, mustHello(protocol.IntentNew, "welcome-gate", domain.Size{Cols: 80, Rows: 24})) | |
| 670 | 670 | close(done) | |
| 671 | 671 | }() | |
| 672 | 672 | ||
M internal/usecase/daemon/graphics_output.go +6 -7
| @@ -15,7 +15,6 @@ import ( | |||
| 15 | 15 | renderer "github.com/bnema/vev-vt" | |
| 16 | 16 | "github.com/bnema/vev-vt/graphics" | |
| 17 | 17 | "github.com/bnema/vev/internal/domain" | |
| 18 | - | "github.com/bnema/vev/internal/ports" | |
| 19 | 18 | "github.com/bnema/vev/internal/protocol" | |
| 20 | 19 | "github.com/bnema/vev/internal/usecase/layout" | |
| 21 | 20 | themeui "github.com/bnema/vev/internal/usecase/theme" | |
| @@ -1008,23 +1007,23 @@ func (d *Daemon) cleanupAttachmentOutput(ac *attachedClient) error { | |||
| 1008 | 1007 | return errors.New("client transport is nil") | |
| 1009 | 1008 | } | |
| 1010 | 1009 | ac.output.lockView() | |
| 1011 | - | frame, frameErr := ac.output.sideEffectLocked(prepared.data, ac.echoAck.Load()) | |
| 1012 | - | if frameErr != nil { | |
| 1010 | + | output, outputErr := ac.output.sideEffectLocked(prepared.data, ac.echoAck.Load()) | |
| 1011 | + | if outputErr != nil { | |
| 1013 | 1012 | ac.output.unlockView() | |
| 1014 | 1013 | prepared.abort() | |
| 1015 | 1014 | d.retireGraphicsOutput(ac, state) | |
| 1016 | - | return frameErr | |
| 1015 | + | return outputErr | |
| 1017 | 1016 | } | |
| 1018 | 1017 | prepared.markSendAttempted() | |
| 1019 | 1018 | var sendErr error | |
| 1020 | - | if owned, ok := expected.transport.(ports.OwnedSynchronousTransport); ok { | |
| 1021 | - | sendErr = owned.SendSynchronous(frame) | |
| 1019 | + | if expected.transport.Capabilities().OwnedSynchronousSend { | |
| 1020 | + | sendErr = expected.transport.SendOutputSynchronous(output) | |
| 1022 | 1021 | } else { | |
| 1023 | 1022 | _, sendErr = d.boundedSendWith(expected.transport, func() error { | |
| 1024 | 1023 | if !ac.transportSnapshotCurrent(expected) { | |
| 1025 | 1024 | return errTransportReplaced | |
| 1026 | 1025 | } | |
| 1027 | - | return expected.transport.Send(frame) | |
| 1026 | + | return expected.transport.SendOutput(output) | |
| 1028 | 1027 | }) | |
| 1029 | 1028 | if errors.Is(sendErr, errSendTimedOut) { | |
| 1030 | 1029 | _ = ac.closeCapturedTransport(expected.transport) | |
M internal/usecase/daemon/handshake.go +5 -5
| @@ -52,7 +52,7 @@ func (d *Daemon) newHandshakeContext(parent context.Context) (context.Context, < | |||
| 52 | 52 | ||
| 53 | 53 | // watchHandshakeTransport closes a transport when the handshake context ends. | |
| 54 | 54 | // Transport.Close is required to interrupt blocked Send and Recv operations. | |
| 55 | - | func watchHandshakeTransport(ctx context.Context, transport ports.Transport) func() { | |
| 55 | + | func watchHandshakeTransport(ctx context.Context, transport ports.ServerConnection) func() { | |
| 56 | 56 | stop := make(chan struct{}) | |
| 57 | 57 | done := make(chan struct{}) | |
| 58 | 58 | go func() { | |
| @@ -74,8 +74,8 @@ func watchHandshakeTransport(ctx context.Context, transport ports.Transport) fun | |||
| 74 | 74 | ||
| 75 | 75 | // boundedHandshakeOperation waits for one transport operation while the shared | |
| 76 | 76 | // handshake context remains live. Closing the transport is the interruption | |
| 77 | - | // mechanism because ports.Transport methods do not accept contexts. | |
| 78 | - | func boundedHandshakeOperation(ctx context.Context, transport ports.Transport, operation func() error) error { | |
| 77 | + | // mechanism because ServerConnection methods do not accept contexts. | |
| 78 | + | func boundedHandshakeOperation(ctx context.Context, transport ports.ServerConnection, operation func() error) error { | |
| 79 | 79 | _, err := boundedHandshakeOperationTracked(ctx, transport, operation) | |
| 80 | 80 | return err | |
| 81 | 81 | } | |
| @@ -84,7 +84,7 @@ func boundedHandshakeOperation(ctx context.Context, transport ports.Transport, o | |||
| 84 | 84 | // operation worker. Callers that own an effect gate must wait for this signal | |
| 85 | 85 | // before releasing the gate after cancellation; the transport operation itself | |
| 86 | 86 | // may still be unwinding after Close interrupts it. | |
| 87 | - | func boundedHandshakeOperationTracked(ctx context.Context, transport ports.Transport, operation func() error) (<-chan struct{}, error) { | |
| 87 | + | func boundedHandshakeOperationTracked(ctx context.Context, transport ports.ServerConnection, operation func() error) (<-chan struct{}, error) { | |
| 88 | 88 | done := make(chan struct{}) | |
| 89 | 89 | if err := ctx.Err(); err != nil { | |
| 90 | 90 | close(done) | |
| @@ -129,7 +129,7 @@ func handshakeContextError(parent context.Context, timedOut <-chan struct{}, fal | |||
| 129 | 129 | // failHandshakeAttachment synchronously retires the exact connection admitted | |
| 130 | 130 | // by route. A fresh route-owned session is purged only after its attachment is | |
| 131 | 131 | // removed; existing sessions and their unrelated attachments are preserved. | |
| 132 | - | func (d *Daemon) failHandshakeAttachment(sess *session, ac *attachedClient, tr ports.Transport, welcomed bool) { | |
| 132 | + | func (d *Daemon) failHandshakeAttachment(sess *session, ac *attachedClient, tr ports.ServerConnection, welcomed bool) { | |
| 133 | 133 | if sess == nil || ac == nil { | |
| 134 | 134 | if tr != nil { | |
| 135 | 135 | _ = tr.Close() | |
M internal/usecase/daemon/handshake_test.go +6 -6
| @@ -128,7 +128,7 @@ func TestHandshakeFirstPaintCancellationDoesNotRacePaintResult(t *testing.T) { | |||
| 128 | 128 | tr := newHandshakeBlockingTransport(false) | |
| 129 | 129 | handshakeDone := make(chan struct{}) | |
| 130 | 130 | go func() { | |
| 131 | - | d.handleHelloWithContext(ctx, make(chan struct{}), func() {}, func() {}, tr, | |
| 131 | + | d.handleHelloFrameWithContext(ctx, make(chan struct{}), func() {}, func() {}, tr, | |
| 132 | 132 | mustHello(protocol.IntentNew, "paint-cancel", domain.Size{Cols: 80, Rows: 24})) | |
| 133 | 133 | close(handshakeDone) | |
| 134 | 134 | }() | |
| @@ -183,7 +183,7 @@ func TestFailedResumeHandshakeRestoresParkedCredential(t *testing.T) { | |||
| 183 | 183 | resumeTransport := newHandshakeBlockingTransport(true) | |
| 184 | 184 | done := make(chan struct{}) | |
| 185 | 185 | go func() { | |
| 186 | - | d.handleHello(resumeTransport, wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(helloResumeCapable(protocol.IntentResume, sess.name, token))}) | |
| 186 | + | d.handleHelloFrame(resumeTransport, wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(helloResumeCapable(protocol.IntentResume, sess.name, token))}) | |
| 187 | 187 | close(done) | |
| 188 | 188 | }() | |
| 189 | 189 | ||
| @@ -211,7 +211,7 @@ func TestHandshakeTimeoutClosesBlockedWelcomeSend(t *testing.T) { | |||
| 211 | 211 | tr := newHandshakeBlockingTransport(true) | |
| 212 | 212 | done := make(chan struct{}) | |
| 213 | 213 | go func() { | |
| 214 | - | d.handleHello(tr, mustHello(protocol.IntentNew, "timeout-send", domain.Size{Cols: 80, Rows: 24})) | |
| 214 | + | d.handleHelloFrame(tr, mustHello(protocol.IntentNew, "timeout-send", domain.Size{Cols: 80, Rows: 24})) | |
| 215 | 215 | close(done) | |
| 216 | 216 | }() | |
| 217 | 217 | ||
| @@ -247,7 +247,7 @@ func TestHandshakeTimeoutCancelsRouteRestoreWait(t *testing.T) { | |||
| 247 | 247 | tr := newHandshakeBlockingTransport(false) | |
| 248 | 248 | done := make(chan struct{}) | |
| 249 | 249 | go func() { | |
| 250 | - | d.handleHello(tr, mustHello(protocol.IntentAttach, "restoring", domain.Size{Cols: 80, Rows: 24})) | |
| 250 | + | d.handleHelloFrame(tr, mustHello(protocol.IntentAttach, "restoring", domain.Size{Cols: 80, Rows: 24})) | |
| 251 | 251 | close(done) | |
| 252 | 252 | }() | |
| 253 | 253 | ||
| @@ -275,7 +275,7 @@ func TestHandshakeTimeoutRemovesRestoredEmptySession(t *testing.T) { | |||
| 275 | 275 | tr := newHandshakeBlockingTransport(true) | |
| 276 | 276 | done := make(chan struct{}) | |
| 277 | 277 | go func() { | |
| 278 | - | d.handleHello(tr, mustHello(protocol.IntentAttach, "restored", domain.Size{Cols: 80, Rows: 24})) | |
| 278 | + | d.handleHelloFrame(tr, mustHello(protocol.IntentAttach, "restored", domain.Size{Cols: 80, Rows: 24})) | |
| 279 | 279 | close(done) | |
| 280 | 280 | }() | |
| 281 | 281 | ||
| @@ -305,7 +305,7 @@ func TestHandshakeTimeoutPreservesUnrelatedAttachment(t *testing.T) { | |||
| 305 | 305 | tr := newHandshakeBlockingTransport(true) | |
| 306 | 306 | done := make(chan struct{}) | |
| 307 | 307 | go func() { | |
| 308 | - | d.handleHello(tr, mustHello(protocol.IntentAttach, "shared", domain.Size{Cols: 80, Rows: 24})) | |
| 308 | + | d.handleHelloFrame(tr, mustHello(protocol.IntentAttach, "shared", domain.Size{Cols: 80, Rows: 24})) | |
| 309 | 309 | close(done) | |
| 310 | 310 | }() | |
| 311 | 311 | ||
M internal/usecase/daemon/notify_test.go +2 -3
| @@ -15,7 +15,6 @@ import ( | |||
| 15 | 15 | vt "github.com/bnema/vev-vt" | |
| 16 | 16 | "github.com/bnema/vev/internal/domain" | |
| 17 | 17 | "github.com/bnema/vev/internal/ports" | |
| 18 | - | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |
| 19 | 18 | "github.com/bnema/vev/internal/protocol" | |
| 20 | 19 | "github.com/bnema/vev/internal/protocol/wire" | |
| 21 | 20 | ) | |
| @@ -55,8 +54,8 @@ func TestClientNoticeMapsFixedActionsAndDismissesOnlyConnectionToast(t *testing. | |||
| 55 | 54 | func TestMalformedClientNoticeIsIgnored(t *testing.T) { | |
| 56 | 55 | d, sess, ac, _ := newNoticeFixture(t, ¬iceClock{}) | |
| 57 | 56 | d.attachCoordinator(sess, nil, ac, true) | |
| 58 | - | tr, ok := ac.tr.(*portsmocks.MockTransport) | |
| 59 | - | require.True(t, ok, "attached client transport must be a MockTransport") | |
| 57 | + | tr, ok := ac.tr.(*mockServerConnection) | |
| 58 | + | require.True(t, ok, "attached client connection must use the test wrapper") | |
| 60 | 59 | frames := []wire.Frame{ | |
| 61 | 60 | {Type: wire.MsgClientNotice, Payload: []byte{0xff}}, | |
| 62 | 61 | {Type: wire.MsgDetach, Payload: wire.MarshalDetach(protocol.Detach{})}, | |
M internal/usecase/daemon/parked_route.go +2 -19
| @@ -9,7 +9,6 @@ import ( | |||
| 9 | 9 | "github.com/bnema/vev/internal/domain" | |
| 10 | 10 | "github.com/bnema/vev/internal/ports" | |
| 11 | 11 | "github.com/bnema/vev/internal/protocol" | |
| 12 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 13 | 12 | ) | |
| 14 | 13 | ||
| 15 | 14 | const parkedRouteLeaseTTL = 15 * time.Minute | |
| @@ -238,33 +237,17 @@ func (ac *attachedClient) consumePublishedParkedRoute(previous, published attach | |||
| 238 | 237 | return 0 | |
| 239 | 238 | } | |
| 240 | 239 | ||
| 241 | - | func parkedRouteResponseFrame(response protocol.ParkedRouteResponse) (wire.Frame, error) { | |
| 242 | - | payload := wire.MarshalParkedRouteResponse(response) | |
| 243 | - | if payload == nil { | |
| 244 | - | return wire.Frame{}, errAttachmentTransition | |
| 245 | - | } | |
| 246 | - | return wire.Frame{Type: wire.MsgParkedRouteResponse, Payload: payload}, nil | |
| 247 | - | } | |
| 248 | - | ||
| 249 | 240 | func (d *Daemon) sendParkedRouteResponse(effect *attachmentEffect, response protocol.ParkedRouteResponse) error { | |
| 250 | - | frame, err := parkedRouteResponseFrame(response) | |
| 251 | - | if err != nil { | |
| 252 | - | return err | |
| 253 | - | } | |
| 254 | - | return effect.sendControl(frame) | |
| 241 | + | return effect.sendControl(response) | |
| 255 | 242 | } | |
| 256 | 243 | ||
| 257 | 244 | // sendParkedRouteResponseLocked preserves response -> full-output ordering. | |
| 258 | 245 | // Caller holds ac.sendMu and keeps parkedRouteOutput set through this send. | |
| 259 | 246 | func (d *Daemon) sendParkedRouteResponseLocked(ac *attachedClient, expected transportSnapshot, response protocol.ParkedRouteResponse) error { | |
| 260 | - | frame, err := parkedRouteResponseFrame(response) | |
| 261 | - | if err != nil { | |
| 262 | - | return err | |
| 263 | - | } | |
| 264 | 247 | if ac == nil || !ac.transportSnapshotCurrent(expected) || expected.transport == nil { | |
| 265 | 248 | return errAttachmentTransition | |
| 266 | 249 | } | |
| 267 | - | return expected.transport.Send(frame) | |
| 250 | + | return expected.transport.SendServer(response) | |
| 268 | 251 | } | |
| 269 | 252 | ||
| 270 | 253 | func (ac *attachedClient) releaseParkedOutputLocked() { | |
M internal/usecase/daemon/picker.go +9 -8
| @@ -11,7 +11,6 @@ import ( | |||
| 11 | 11 | "github.com/bnema/vev/internal/domain" | |
| 12 | 12 | "github.com/bnema/vev/internal/ports" | |
| 13 | 13 | "github.com/bnema/vev/internal/protocol" | |
| 14 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 15 | 14 | "github.com/bnema/vev/internal/usecase/layout" | |
| 16 | 15 | "github.com/bnema/vev/internal/usecase/picker" | |
| 17 | 16 | "github.com/bnema/vev/internal/usecase/ui" | |
| @@ -924,14 +923,17 @@ func (d *Daemon) sendLocalAttachTargetForAttachment(effect *attachmentEffect, ta | |||
| 924 | 923 | // Explicit tab rows already take the direct transition path above, while | |
| 925 | 924 | // stopped sessions have no active target session and retain the fallback. | |
| 926 | 925 | samePeerEligible := guard.allowSamePeer && targetSess != nil && target.TabIndex <= 0 | |
| 927 | - | payload := wire.MarshalAttachTarget(protocol.AttachTarget{ | |
| 926 | + | if exactTarget == nil { | |
| 927 | + | return errAttachmentTransition | |
| 928 | + | } | |
| 929 | + | handoff := protocol.AttachTarget{ | |
| 928 | 930 | Session: sessionName, | |
| 929 | 931 | Intent: protocol.IntentAttach, | |
| 930 | 932 | ExactTarget: exactTarget, | |
| 931 | 933 | EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned, | |
| 932 | 934 | SamePeer: samePeerEligible, | |
| 933 | - | }) | |
| 934 | - | if payload == nil || exactTarget == nil { | |
| 935 | + | } | |
| 936 | + | if protocol.ValidateAttachTarget(handoff) != nil { | |
| 935 | 937 | return errAttachmentTransition | |
| 936 | 938 | } | |
| 937 | 939 | // A close-and-dial handoff leaves this daemon's Kitty namespace. A | |
| @@ -944,7 +946,7 @@ func (d *Daemon) sendLocalAttachTargetForAttachment(effect *attachmentEffect, ta | |||
| 944 | 946 | } else { | |
| 945 | 947 | effect.ac.offerSamePeerTarget(*exactTarget) | |
| 946 | 948 | } | |
| 947 | - | if err := effect.sendControl(wire.Frame{Type: wire.MsgAttachTarget, Payload: payload}); err != nil { | |
| 949 | + | if err := effect.sendControl(handoff); err != nil { | |
| 948 | 950 | if samePeerEligible { | |
| 949 | 951 | effect.ac.clearSamePeerOffer() | |
| 950 | 952 | } | |
| @@ -990,11 +992,10 @@ func (d *Daemon) sendRemoteAttachTargetForAttachment(effect *attachmentEffect, t | |||
| 990 | 992 | RemoteTarget: &remoteTarget, | |
| 991 | 993 | EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned, | |
| 992 | 994 | } | |
| 993 | - | payload := wire.MarshalAttachTarget(handoff) | |
| 994 | - | if payload == nil { | |
| 995 | + | if protocol.ValidateAttachTarget(handoff) != nil { | |
| 995 | 996 | return failUnavailable() | |
| 996 | 997 | } | |
| 997 | - | if err := effect.sendControl(wire.Frame{Type: wire.MsgAttachTarget, Payload: payload}); err != nil { | |
| 998 | + | if err := effect.sendControl(handoff); err != nil { | |
| 998 | 999 | return domain.UserErr(domain.NoticeSessionUnavailable, "couldn't attach to remote session", err) | |
| 999 | 1000 | } | |
| 1000 | 1001 | if guard.closePicker { | |
M internal/usecase/daemon/picker_test.go +2 -2
| @@ -1011,7 +1011,7 @@ func TestPickerSessionSwitchPublishesBeforeInFlightPaintSendCompletes(t *testing | |||
| 1011 | 1011 | d := newTestDaemon(t, nil, stubClock{}) | |
| 1012 | 1012 | enteredSend := make(chan struct{}) | |
| 1013 | 1013 | releaseSend := make(chan struct{}) | |
| 1014 | - | tr := portsmocks.NewMockTransport(t) | |
| 1014 | + | tr := newMockServerConnection(t) | |
| 1015 | 1015 | tr.EXPECT().Send(mock.Anything).RunAndReturn(func(wire.Frame) error { | |
| 1016 | 1016 | close(enteredSend) | |
| 1017 | 1017 | <-releaseSend | |
| @@ -1408,7 +1408,7 @@ func TestResumeStoppedAndSwitchInheritsTerminalEnv(t *testing.T) { | |||
| 1408 | 1408 | })).Return(floating, nil).Once() | |
| 1409 | 1409 | d := newTestDaemon(t, f, stubClock{}) | |
| 1410 | 1410 | d.inactive["old"] = inactiveSession{name: "old", cwd: t.TempDir(), createdAt: 1, state: protocol.SessionDown} | |
| 1411 | - | tr := portsmocks.NewMockTransport(t) | |
| 1411 | + | tr := newMockServerConnection(t) | |
| 1412 | 1412 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | |
| 1413 | 1413 | tr.EXPECT().Close().Return(nil).Maybe() | |
| 1414 | 1414 | sess, ac, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: sz, TrueColor: true}, tr) | |
M internal/usecase/daemon/remote_attach.go +7 -15
| @@ -7,14 +7,13 @@ import ( | |||
| 7 | 7 | "github.com/bnema/vev/internal/domain" | |
| 8 | 8 | "github.com/bnema/vev/internal/ports" | |
| 9 | 9 | "github.com/bnema/vev/internal/protocol" | |
| 10 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 11 | 10 | ) | |
| 12 | 11 | ||
| 13 | 12 | // routeRemoteTargetWithContext is the exact-target branch for picker handoffs. | |
| 14 | 13 | // It is deliberately separate from legacy name routing: a lifecycle mismatch, | |
| 15 | 14 | // missing tab, broken record, or stopped-record race is a no-such-target error, | |
| 16 | 15 | // never an invitation to attach a same-name replacement. | |
| 17 | - | func (d *Daemon) routeRemoteTargetWithContext(ctx context.Context, h protocol.Hello, tr ports.Transport) (*session, *attachedClient, error) { | |
| 16 | + | func (d *Daemon) routeRemoteTargetWithContext(ctx context.Context, h protocol.Hello, tr ports.ServerConnection) (*session, *attachedClient, error) { | |
| 18 | 17 | if h.RemoteTarget == nil { | |
| 19 | 18 | return nil, nil, errors.New("daemon: missing remote target") | |
| 20 | 19 | } | |
| @@ -120,12 +119,7 @@ func (d *Daemon) sendNavigationActionForAttachment(effect *attachmentEffect, act | |||
| 120 | 119 | effect.ac.clearParkedRoute() | |
| 121 | 120 | } | |
| 122 | 121 | } | |
| 123 | - | payload := wire.MarshalNavigationDirective(directive) | |
| 124 | - | if payload == nil { | |
| 125 | - | rollback() | |
| 126 | - | return errAttachmentTransition | |
| 127 | - | } | |
| 128 | - | if err := effect.sendControl(wire.Frame{Type: wire.MsgNavigationAction, Payload: payload}); err != nil { | |
| 122 | + | if err := effect.sendControl(directive); err != nil { | |
| 129 | 123 | rollback() | |
| 130 | 124 | return err | |
| 131 | 125 | } | |
| @@ -133,11 +127,10 @@ func (d *Daemon) sendNavigationActionForAttachment(effect *attachmentEffect, act | |||
| 133 | 127 | } | |
| 134 | 128 | ||
| 135 | 129 | func (d *Daemon) sendRecentRouteNavigationActionForAttachment(effect *attachmentEffect, action protocol.RouteNavigationAction) error { | |
| 136 | - | payload, err := wire.MarshalRouteNavigationAction(action) | |
| 137 | - | if err != nil { | |
| 130 | + | if action.SnapshotGeneration == 0 || action.Key == 0 || action.Generation == 0 { | |
| 138 | 131 | return errAttachmentTransition | |
| 139 | 132 | } | |
| 140 | - | return effect.sendControl(wire.Frame{Type: wire.MsgNavigateRecentRoute, Payload: payload}) | |
| 133 | + | return effect.sendControl(action) | |
| 141 | 134 | } | |
| 142 | 135 | ||
| 143 | 136 | func (d *Daemon) sendCommittedRouteIdentityForAttachment(effect *attachmentEffect) error { | |
| @@ -150,14 +143,13 @@ func (d *Daemon) sendCommittedRouteIdentityForAttachment(effect *attachmentEffec | |||
| 150 | 143 | Ephemeral: effect.sess.ephemeral, | |
| 151 | 144 | } | |
| 152 | 145 | effect.sess.mu.Unlock() | |
| 153 | - | payload, err := wire.MarshalCommittedRouteIdentity(identity) | |
| 154 | - | if err != nil { | |
| 146 | + | if err := identity.Validate(); err != nil { | |
| 155 | 147 | return errAttachmentTransition | |
| 156 | 148 | } | |
| 157 | - | return effect.sendControl(wire.Frame{Type: wire.MsgCommittedRouteIdentity, Payload: payload}) | |
| 149 | + | return effect.sendControl(identity) | |
| 158 | 150 | } | |
| 159 | 151 | ||
| 160 | - | func (d *Daemon) finishRouteAttach(sess *session, tr ports.Transport, sz domain.Size, h protocol.Hello, routeCreated, purge bool) (*attachedClient, error) { | |
| 152 | + | func (d *Daemon) finishRouteAttach(sess *session, tr ports.ServerConnection, sz domain.Size, h protocol.Hello, routeCreated, purge bool) (*attachedClient, error) { | |
| 161 | 153 | ac, err := d.finishAttach(sess, tr, sz, h) | |
| 162 | 154 | if err != nil && routeCreated { | |
| 163 | 155 | if cleanupErr := d.killSessionIfEmpty(sess, protocol.ReasonSessionKilled, purge); cleanupErr != nil { | |
M internal/usecase/daemon/remote_picker_test.go +3 -4
| @@ -16,7 +16,6 @@ import ( | |||
| 16 | 16 | ||
| 17 | 17 | "github.com/bnema/vev/internal/domain" | |
| 18 | 18 | "github.com/bnema/vev/internal/ports" | |
| 19 | - | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |
| 20 | 19 | "github.com/bnema/vev/internal/protocol" | |
| 21 | 20 | "github.com/bnema/vev/internal/protocol/wire" | |
| 22 | 21 | "github.com/bnema/vev/internal/usecase/picker" | |
| @@ -890,7 +889,7 @@ func TestRemotePickerHandoffSendFailureKeepsPickerOpen(t *testing.T) { | |||
| 890 | 889 | d.remoteCatalog.status["arch"] = remoteHostFresh | |
| 891 | 890 | d.remoteCatalog.mu.Unlock() | |
| 892 | 891 | cause := errors.New("remote attach send failed") | |
| 893 | - | tr := portsmocks.NewMockTransport(t) | |
| 892 | + | tr := newMockServerConnection(t) | |
| 894 | 893 | tr.EXPECT().Send(mock.Anything).Return(cause) | |
| 895 | 894 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "local", tr) | |
| 896 | 895 | ac.overlays.pickerMu.Lock() | |
| @@ -921,9 +920,9 @@ func TestRemotePickerHandoffSendFailureKeepsPickerOpen(t *testing.T) { | |||
| 921 | 920 | require.Same(t, sess, ac.currentAttachmentSession()) | |
| 922 | 921 | } | |
| 923 | 922 | ||
| 924 | - | func addRemoteRefreshPickerOwner(t *testing.T, d *Daemon, id domain.SessionID, transports ...ports.Transport) (*session, *attachedClient, chan wire.Frame) { | |
| 923 | + | func addRemoteRefreshPickerOwner(t *testing.T, d *Daemon, id domain.SessionID, transports ...ports.ServerConnection) (*session, *attachedClient, chan wire.Frame) { | |
| 925 | 924 | t.Helper() | |
| 926 | - | var tr ports.Transport | |
| 925 | + | var tr ports.ServerConnection | |
| 927 | 926 | var sends chan wire.Frame | |
| 928 | 927 | if len(transports) != 0 { | |
| 929 | 928 | tr = transports[0] | |
M internal/usecase/daemon/remote_preview.go +5 -11
| @@ -7,15 +7,10 @@ import ( | |||
| 7 | 7 | "github.com/bnema/vev/internal/domain" | |
| 8 | 8 | "github.com/bnema/vev/internal/ports" | |
| 9 | 9 | "github.com/bnema/vev/internal/protocol" | |
| 10 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 11 | 10 | ) | |
| 12 | 11 | ||
| 13 | - | func (d *Daemon) handleRemotePreview(tr ports.Transport, frame wire.Frame) error { | |
| 12 | + | func (d *Daemon) handleRemotePreview(tr ports.ServerConnection, request protocol.RemotePreviewRequest) error { | |
| 14 | 13 | defer func() { _ = tr.Close() }() | |
| 15 | - | request, err := wire.UnmarshalRemotePreviewRequest(frame.Payload) | |
| 16 | - | if err != nil { | |
| 17 | - | return d.sendRemotePreview(tr, protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: protocol.RemotePreviewMalformed}) | |
| 18 | - | } | |
| 19 | 14 | preview, err := d.captureRemotePreview(request) | |
| 20 | 15 | if err != nil { | |
| 21 | 16 | status := protocol.RemotePreviewUnavailable | |
| @@ -27,12 +22,11 @@ func (d *Daemon) handleRemotePreview(tr ports.Transport, frame wire.Frame) error | |||
| 27 | 22 | return d.sendRemotePreview(tr, preview) | |
| 28 | 23 | } | |
| 29 | 24 | ||
| 30 | - | func (d *Daemon) sendRemotePreview(tr ports.Transport, preview protocol.RemotePreview) error { | |
| 31 | - | payload := wire.MarshalRemotePreview(preview) | |
| 32 | - | if payload == nil { | |
| 33 | - | payload = wire.MarshalRemotePreview(protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: protocol.RemotePreviewMalformed}) | |
| 25 | + | func (d *Daemon) sendRemotePreview(tr ports.ServerConnection, preview protocol.RemotePreview) error { | |
| 26 | + | if protocol.ValidateRemotePreview(preview) != nil { | |
| 27 | + | preview = protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: protocol.RemotePreviewMalformed} | |
| 34 | 28 | } | |
| 35 | - | return d.boundedControlSend(tr, wire.Frame{Type: wire.MsgRemotePreviewResponse, Payload: payload}) | |
| 29 | + | return d.boundedControlSend(tr, preview) | |
| 36 | 30 | } | |
| 37 | 31 | ||
| 38 | 32 | var errRemotePreviewNoSuchTarget = errors.New("daemon: remote preview target does not exist") | |
M internal/usecase/daemon/remote_route_test.go +1 -1
| @@ -183,7 +183,7 @@ func TestFailedHandshakeCleanupPreservesConcurrentAttachment(t *testing.T) { | |||
| 183 | 183 | d.clientGone(sess, winner(), winnerTransport, false) | |
| 184 | 184 | } | |
| 185 | 185 | ||
| 186 | - | func attachWhenRouteCleanupSnapshots(t *testing.T, d *Daemon, sess *session) (func() *attachedClient, ports.Transport) { | |
| 186 | + | func attachWhenRouteCleanupSnapshots(t *testing.T, d *Daemon, sess *session) (func() *attachedClient, ports.ServerConnection) { | |
| 187 | 187 | t.Helper() | |
| 188 | 188 | winnerTransport, _ := newCapturingTransport(t) | |
| 189 | 189 | var winner *attachedClient | |
M internal/usecase/daemon/render_damage_receipt_test.go +3 -3
| @@ -108,7 +108,7 @@ func TestRenderDamageReceiptRetainsRealVTDamageAcrossFailedEmission(t *testing.T | |||
| 108 | 108 | for _, tt := range []struct { | |
| 109 | 109 | name string | |
| 110 | 110 | fail func(*composedRenderFrame, *attachedClient) | |
| 111 | - | restore func(*session, *attachedClient, ports.Transport) | |
| 111 | + | restore func(*session, *attachedClient, ports.ServerConnection) | |
| 112 | 112 | }{ | |
| 113 | 113 | { | |
| 114 | 114 | name: "prepare failure", | |
| @@ -116,12 +116,12 @@ func TestRenderDamageReceiptRetainsRealVTDamageAcrossFailedEmission(t *testing.T | |||
| 116 | 116 | // An invalid frame makes the real output.prepare reject after capture. | |
| 117 | 117 | c.frame = renderer.Frame{Width: 1} | |
| 118 | 118 | }, | |
| 119 | - | restore: func(_ *session, _ *attachedClient, _ ports.Transport) {}, | |
| 119 | + | restore: func(_ *session, _ *attachedClient, _ ports.ServerConnection) {}, | |
| 120 | 120 | }, | |
| 121 | 121 | { | |
| 122 | 122 | name: "send failure", | |
| 123 | 123 | fail: func(_ *composedRenderFrame, ac *attachedClient) { ac.replaceTransport(cacheFailTransport{}) }, | |
| 124 | - | restore: func(sess *session, ac *attachedClient, healthy ports.Transport) { | |
| 124 | + | restore: func(sess *session, ac *attachedClient, healthy ports.ServerConnection) { | |
| 125 | 125 | sess.mu.Lock() | |
| 126 | 126 | sess.registerAttachmentLocked(ac) | |
| 127 | 127 | sess.mu.Unlock() | |
M internal/usecase/daemon/render_pipeline.go +9 -13
| @@ -7,7 +7,6 @@ import ( | |||
| 7 | 7 | "github.com/bnema/vev/internal/domain" | |
| 8 | 8 | "github.com/bnema/vev/internal/ports" | |
| 9 | 9 | "github.com/bnema/vev/internal/protocol" | |
| 10 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 11 | 10 | "github.com/bnema/vev/internal/usecase/layout" | |
| 12 | 11 | "github.com/bnema/vev/internal/usecase/notices" | |
| 13 | 12 | "github.com/bnema/vev/internal/usecase/palette" | |
| @@ -475,7 +474,7 @@ func (d *Daemon) emitFrame(entry *session, ac *attachedClient, state *capturedRe | |||
| 475 | 474 | d.invalidateRender(entry, ac, true, "render_pipeline.go:prepare-failed") | |
| 476 | 475 | return true | |
| 477 | 476 | } | |
| 478 | - | var sendTr ports.Transport | |
| 477 | + | var sendTr ports.ServerConnection | |
| 479 | 478 | var sendErr error | |
| 480 | 479 | suppressedGraphics := state.suppressedGraphics && !ac.terminalCapabilities.SupportsKittyGraphics() | |
| 481 | 480 | if len(data) > 0 { | |
| @@ -485,9 +484,9 @@ func (d *Daemon) emitFrame(entry *session, ac *attachedClient, state *capturedRe | |||
| 485 | 484 | if sendTr == nil { | |
| 486 | 485 | sendErr = errors.New("client transport is nil") | |
| 487 | 486 | } else { | |
| 488 | - | send := sendTr.Send | |
| 489 | - | if async, ok := sendTr.(ports.AsyncTransport); ok { | |
| 490 | - | send = async.SendAsync | |
| 487 | + | send := sendTr.SendOutput | |
| 488 | + | if sendTr.Capabilities().AsyncSend { | |
| 489 | + | send = sendTr.SendOutputAsync | |
| 491 | 490 | } | |
| 492 | 491 | interruptible := false | |
| 493 | 492 | if marks.attachmentEffect != nil { | |
| @@ -540,9 +539,8 @@ func (d *Daemon) emitFrame(entry *session, ac *attachedClient, state *capturedRe | |||
| 540 | 539 | } | |
| 541 | 540 | ||
| 542 | 541 | if position.ActiveTabID != "" && position != ac.output.lastRoutePosition { | |
| 543 | - | payload, positionMarshalErr := wire.MarshalRoutePosition(position) | |
| 544 | - | if positionMarshalErr != nil { | |
| 545 | - | d.log.Error("marshal route position", "err", positionMarshalErr) | |
| 542 | + | if positionValidateErr := position.Validate(); positionValidateErr != nil { | |
| 543 | + | d.log.Error("validate route position", "err", positionValidateErr) | |
| 546 | 544 | } else { | |
| 547 | 545 | sendTransport := ac.transportSnapshot() | |
| 548 | 546 | sendTr = sendTransport.transport | |
| @@ -556,12 +554,10 @@ func (d *Daemon) emitFrame(entry *session, ac *attachedClient, state *capturedRe | |||
| 556 | 554 | if sendErr == nil { | |
| 557 | 555 | if sendTr == nil { | |
| 558 | 556 | sendErr = errors.New("client transport is nil") | |
| 557 | + | } else if sendTr.Capabilities().AsyncSend { | |
| 558 | + | sendErr = sendTr.SendServerAsync(position) | |
| 559 | 559 | } else { | |
| 560 | - | send := sendTr.Send | |
| 561 | - | if async, ok := sendTr.(ports.AsyncTransport); ok { | |
| 562 | - | send = async.SendAsync | |
| 563 | - | } | |
| 564 | - | sendErr = send(wire.Frame{Type: wire.MsgRoutePosition, Payload: payload}) | |
| 560 | + | sendErr = sendTr.SendServer(position) | |
| 565 | 561 | } | |
| 566 | 562 | } | |
| 567 | 563 | if marks.attachmentEffect != nil && interruptible { | |
M internal/usecase/daemon/render_pipeline_test.go +2 -2
| @@ -333,10 +333,10 @@ func TestComposeEmitExactReplayTiledFloatingBarsOverlayAndCursor(t *testing.T) { | |||
| 333 | 333 | prepared, err := stream.prepareFrame(nil, &state, composed.frame, composed.damage, composed.reset, composed.cursor) | |
| 334 | 334 | require.NoError(t, err) | |
| 335 | 335 | var outputFrame wire.Frame | |
| 336 | - | require.NoError(t, prepared.send(0, func(frame wire.Frame) error { | |
| 336 | + | require.NoError(t, prepared.send(0, outputFrameSender(func(frame wire.Frame) error { | |
| 337 | 337 | outputFrame = frame | |
| 338 | 338 | return nil | |
| 339 | - | })) | |
| 339 | + | }))) | |
| 340 | 340 | output, err := wire.UnmarshalOutput(outputFrame.Payload) | |
| 341 | 341 | require.NoError(t, err) | |
| 342 | 342 | terminalBytes := output.Data | |
M internal/usecase/daemon/render_test.go +3 -3
| @@ -676,7 +676,7 @@ func TestResizePreservesLiveContentAndEvictsScrollback(t *testing.T) { | |||
| 676 | 676 | } | |
| 677 | 677 | win.focusedPane().screen.Row = 3 | |
| 678 | 678 | ||
| 679 | - | tr := portsmocks.NewMockTransport(t) | |
| 679 | + | tr := newMockServerConnection(t) | |
| 680 | 680 | tr.EXPECT().Close().Return(nil).Maybe() | |
| 681 | 681 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | |
| 682 | 682 | ||
| @@ -931,7 +931,7 @@ func TestResizeOrdersPTYBeforeScreen(t *testing.T) { | |||
| 931 | 931 | win.focusedPane().pty = p | |
| 932 | 932 | ||
| 933 | 933 | var gotOutput atomic.Bool | |
| 934 | - | tr := portsmocks.NewMockTransport(t) | |
| 934 | + | tr := newMockServerConnection(t) | |
| 935 | 935 | tr.EXPECT().Close().Return(nil).Maybe() | |
| 936 | 936 | tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error { | |
| 937 | 937 | if f.Type == wire.MsgOutput { | |
| @@ -963,7 +963,7 @@ func TestSendErrorKeepsEphemeralHeadless(t *testing.T) { | |||
| 963 | 963 | p := portsmocks.NewMockPTY(t) | |
| 964 | 964 | p.EXPECT().Close().Return(nil).Maybe() | |
| 965 | 965 | ||
| 966 | - | tr := portsmocks.NewMockTransport(t) | |
| 966 | + | tr := newMockServerConnection(t) | |
| 967 | 967 | tr.EXPECT().Send(mock.Anything).Return(io.ErrClosedPipe).Maybe() | |
| 968 | 968 | tr.EXPECT().Close().Return(nil).Maybe() | |
| 969 | 969 | ||
M internal/usecase/daemon/resume.go +3 -3
| @@ -480,7 +480,7 @@ func (d *Daemon) finishParkedAttachmentRetirements(retirements []parkedAttachmen | |||
| 480 | 480 | // to the named session's active attachment because transport teardown has not | |
| 481 | 481 | // parked it yet. Only the exact live owner (same session, token, and client ID) | |
| 482 | 482 | // is accepted; arbitrary unknown tokens stay fail-closed. | |
| 483 | - | func (d *Daemon) resumeLiveAttachment(h protocol.Hello, tr ports.Transport, sz domain.Size) (*session, *attachedClient, bool, error) { | |
| 483 | + | func (d *Daemon) resumeLiveAttachment(h protocol.Hello, tr ports.ServerConnection, sz domain.Size) (*session, *attachedClient, bool, error) { | |
| 484 | 484 | d.mu.Lock() | |
| 485 | 485 | if d.closing { | |
| 486 | 486 | d.mu.Unlock() | |
| @@ -583,7 +583,7 @@ func (d *Daemon) resumeLiveAttachment(h protocol.Hello, tr ports.Transport, sz d | |||
| 583 | 583 | // registry lock, preserving the global sendMu > Daemon.mu ordering. The parked | |
| 584 | 584 | // entry is revalidated after both locks are held because it may expire between | |
| 585 | 585 | // the initial lookup and lock acquisition. | |
| 586 | - | func (d *Daemon) resumeParked(h protocol.Hello, tr ports.Transport, sz domain.Size) (*session, *attachedClient, bool, error) { | |
| 586 | + | func (d *Daemon) resumeParked(h protocol.Hello, tr ports.ServerConnection, sz domain.Size) (*session, *attachedClient, bool, error) { | |
| 587 | 587 | d.mu.Lock() | |
| 588 | 588 | parked := d.parked[h.ResumeToken] | |
| 589 | 589 | d.mu.Unlock() | |
| @@ -632,7 +632,7 @@ func (d *Daemon) resumeParked(h protocol.Hello, tr ports.Transport, sz domain.Si | |||
| 632 | 632 | ||
| 633 | 633 | // resumeParkedLocked completes a validated resume. Caller holds both ac.sendMu | |
| 634 | 634 | // and d.mu in that order. | |
| 635 | - | func (d *Daemon) resumeParkedLocked(h protocol.Hello, tr ports.Transport, sz domain.Size) (*session, *attachedClient, bool, error) { | |
| 635 | + | func (d *Daemon) resumeParkedLocked(h protocol.Hello, tr ports.ServerConnection, sz domain.Size) (*session, *attachedClient, bool, error) { | |
| 636 | 636 | parked := d.parked[h.ResumeToken] | |
| 637 | 637 | if parked == nil || h.ResumeToken == 0 { | |
| 638 | 638 | return nil, nil, false, nil | |
M internal/usecase/daemon/resume_test.go +3 -3
| @@ -254,14 +254,14 @@ func TestBoundedSendTimeoutCannotTargetResumedTransport(t *testing.T) { | |||
| 254 | 254 | expected := ac.transportSnapshot() | |
| 255 | 255 | orphanDone := make(chan struct{}) | |
| 256 | 256 | type boundedResult struct { | |
| 257 | - | transport ports.Transport | |
| 257 | + | transport ports.ServerConnection | |
| 258 | 258 | err error | |
| 259 | 259 | } | |
| 260 | 260 | result := make(chan boundedResult, 1) | |
| 261 | 261 | go func() { | |
| 262 | 262 | transport, sendErr := d.boundedSendWithTimeout(time.Second, expected.transport, func() error { | |
| 263 | 263 | defer close(orphanDone) | |
| 264 | - | return ac.sendExpectedTransport(expected, frameDetached(protocol.ReasonDetach)) | |
| 264 | + | return ac.sendExpectedTransport(expected, serverDetached(protocol.ReasonDetach)) | |
| 265 | 265 | }) | |
| 266 | 266 | result <- boundedResult{transport: transport, err: sendErr} | |
| 267 | 267 | }() | |
| @@ -327,7 +327,7 @@ func TestHandleHelloResumeDefersFreshOutputUntilWelcome(t *testing.T) { | |||
| 327 | 327 | done := make(chan struct{}) | |
| 328 | 328 | resumeHello := helloResumeCapable(protocol.IntentResume, sess.name, token) | |
| 329 | 329 | go func() { | |
| 330 | - | d.handleHello(tr.tr, wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(resumeHello)}) | |
| 330 | + | d.handleHelloFrame(tr.tr, wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(resumeHello)}) | |
| 331 | 331 | close(done) | |
| 332 | 332 | }() | |
| 333 | 333 | ||
M internal/usecase/daemon/same_peer_switch.go +3 -4
| @@ -3,7 +3,6 @@ package daemon | |||
| 3 | 3 | import ( | |
| 4 | 4 | "github.com/bnema/vev/internal/domain" | |
| 5 | 5 | "github.com/bnema/vev/internal/protocol" | |
| 6 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 7 | 6 | ) | |
| 8 | 7 | ||
| 9 | 8 | func (ac *attachedClient) offerSamePeerTarget(target protocol.ExactSessionTarget) { | |
| @@ -122,11 +121,11 @@ func (d *Daemon) sendSamePeerSwitchFailure(effect *attachmentEffect, requestID u | |||
| 122 | 121 | } | |
| 123 | 122 | defer sender.End() | |
| 124 | 123 | } | |
| 125 | - | payload, err := wire.MarshalSamePeerSwitchFailure(protocol.SamePeerSwitchFailure{RequestID: requestID, Code: code}) | |
| 126 | - | if err != nil { | |
| 124 | + | failure := protocol.SamePeerSwitchFailure{RequestID: requestID, Code: code} | |
| 125 | + | if failure.Validate() != nil { | |
| 127 | 126 | return | |
| 128 | 127 | } | |
| 129 | - | if err := sender.sendControl(wire.Frame{Type: wire.MsgSamePeerSwitchFailure, Payload: payload}); err != nil { | |
| 128 | + | if err := sender.sendControl(failure); err != nil { | |
| 130 | 129 | d.detachOnAttachmentSendError(sender.capability(), sender.transport.transport) | |
| 131 | 130 | } | |
| 132 | 131 | } | |
M internal/usecase/daemon/session_test.go +15 -15
| @@ -398,7 +398,7 @@ func TestServeReturnsWhenLastSessionExits(t *testing.T) { | |||
| 398 | 398 | p, releasePTY := newBlockingPTY(t) | |
| 399 | 399 | tr, sends, _ := newConn(t, mustHello(protocol.IntentEphemeral, "", domain.Size{Cols: 80, Rows: 24})) | |
| 400 | 400 | ||
| 401 | - | l := portsmocks.NewMockListener(t) | |
| 401 | + | l := newMockServerListener(t) | |
| 402 | 402 | connCh := make(chan ports.Transport, 1) | |
| 403 | 403 | connCh <- tr | |
| 404 | 404 | closed := make(chan struct{}) | |
| @@ -438,7 +438,7 @@ func TestServeGracefulShutdownOnContextCancel(t *testing.T) { | |||
| 438 | 438 | defer releasePTY() | |
| 439 | 439 | tr, sends, _ := newConn(t, mustHello(protocol.IntentNew, "long", domain.Size{Cols: 80, Rows: 24})) | |
| 440 | 440 | ||
| 441 | - | l := portsmocks.NewMockListener(t) | |
| 441 | + | l := newMockServerListener(t) | |
| 442 | 442 | connCh := make(chan ports.Transport, 1) | |
| 443 | 443 | connCh <- tr | |
| 444 | 444 | closed := make(chan struct{}) | |
| @@ -573,7 +573,7 @@ func TestHelloRacingShutdownIsRejected(t *testing.T) { | |||
| 573 | 573 | func TestServeReturnsDespiteWedgedClientOnShutdown(t *testing.T) { | |
| 574 | 574 | p, _ := newBlockingPTY(t) // Close unblocks the parked reader | |
| 575 | 575 | ||
| 576 | - | tr := portsmocks.NewMockTransport(t) | |
| 576 | + | tr := newMockServerConnection(t) | |
| 577 | 577 | sends := make(chan wire.Frame, 64) | |
| 578 | 578 | recvCh := make(chan wire.Frame, 1) | |
| 579 | 579 | recvCh <- mustHello(protocol.IntentNew, "wedge", domain.Size{Cols: 80, Rows: 24}) | |
| @@ -600,7 +600,7 @@ func TestServeReturnsDespiteWedgedClientOnShutdown(t *testing.T) { | |||
| 600 | 600 | }).Maybe() | |
| 601 | 601 | tr.EXPECT().Close().RunAndReturn(func() error { closeConn(); return nil }).Maybe() | |
| 602 | 602 | ||
| 603 | - | l := portsmocks.NewMockListener(t) | |
| 603 | + | l := newMockServerListener(t) | |
| 604 | 604 | connCh := make(chan ports.Transport, 1) | |
| 605 | 605 | connCh <- tr | |
| 606 | 606 | lnClosed := make(chan struct{}) | |
| @@ -1021,7 +1021,7 @@ func TestAttachRestoresPersistedTabNames(t *testing.T) { | |||
| 1021 | 1021 | require.NoError(t, err) | |
| 1022 | 1022 | require.True(t, ok) | |
| 1023 | 1023 | d.inactive["work"] = inactiveSessionFromRecord(record, protocol.SessionDown, nil) | |
| 1024 | - | tr := portsmocks.NewMockTransport(t) | |
| 1024 | + | tr := newMockServerConnection(t) | |
| 1025 | 1025 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | |
| 1026 | 1026 | tr.EXPECT().Close().Return(nil).Maybe() | |
| 1027 | 1027 | ||
| @@ -1195,7 +1195,7 @@ func TestAttachResumesStoppedSessionFromStoredCwd(t *testing.T) { | |||
| 1195 | 1195 | WithStore(t, store)(d) | |
| 1196 | 1196 | cwd := t.TempDir() | |
| 1197 | 1197 | d.inactive["work"] = inactiveSession{name: "work", cwd: cwd, createdAt: 1, state: protocol.SessionDown} | |
| 1198 | - | tr := portsmocks.NewMockTransport(t) | |
| 1198 | + | tr := newMockServerConnection(t) | |
| 1199 | 1199 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | |
| 1200 | 1200 | tr.EXPECT().Close().Return(nil).Maybe() | |
| 1201 | 1201 | ||
| @@ -1218,7 +1218,7 @@ func TestAttachStoppedMissingCwdFallsBackToHome(t *testing.T) { | |||
| 1218 | 1218 | d := newTestDaemon(t, newFactory(t, p), stubClock{}) | |
| 1219 | 1219 | WithStore(t, store)(d) | |
| 1220 | 1220 | d.inactive["work"] = inactiveSession{name: "work", cwd: "/definitely/missing/vev", createdAt: 1, state: protocol.SessionDown} | |
| 1221 | - | tr := portsmocks.NewMockTransport(t) | |
| 1221 | + | tr := newMockServerConnection(t) | |
| 1222 | 1222 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | |
| 1223 | 1223 | tr.EXPECT().Close().Return(nil).Maybe() | |
| 1224 | 1224 | ||
| @@ -1299,14 +1299,14 @@ func TestNewSessionAssignsStableIDsAndChildEnv(t *testing.T) { | |||
| 1299 | 1299 | func TestIntentNewStoppedNameRejected(t *testing.T) { | |
| 1300 | 1300 | d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{}) | |
| 1301 | 1301 | d.inactive["taken"] = inactiveSession{name: "taken", cwd: "/tmp", createdAt: 1} | |
| 1302 | - | tr := portsmocks.NewMockTransport(t) | |
| 1302 | + | tr := newMockServerConnection(t) | |
| 1303 | 1303 | _, _, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "taken", Size: domain.Size{Cols: 80, Rows: 24}}, tr) | |
| 1304 | 1304 | require.ErrorContains(t, err, "name already in use") | |
| 1305 | 1305 | } | |
| 1306 | 1306 | ||
| 1307 | 1307 | func TestIntentNewUnsafeNameRejected(t *testing.T) { | |
| 1308 | 1308 | d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{}) | |
| 1309 | - | tr := portsmocks.NewMockTransport(t) | |
| 1309 | + | tr := newMockServerConnection(t) | |
| 1310 | 1310 | _, _, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "my work", Size: domain.Size{Cols: 80, Rows: 24}}, tr) | |
| 1311 | 1311 | require.ErrorContains(t, err, domain.ErrInvalidSessionName.Error()) | |
| 1312 | 1312 | } | |
| @@ -1446,7 +1446,7 @@ func TestAttachUpdatesFutureChildEnvTrueColor(t *testing.T) { | |||
| 1446 | 1446 | ||
| 1447 | 1447 | d := newTestDaemon(t, f, stubClock{}) | |
| 1448 | 1448 | d.baseEnv = []string{"KEEP=1", "COLORTERM=old"} | |
| 1449 | - | tr := portsmocks.NewMockTransport(t) | |
| 1449 | + | tr := newMockServerConnection(t) | |
| 1450 | 1450 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | |
| 1451 | 1451 | tr.EXPECT().Close().Return(nil).Maybe() | |
| 1452 | 1452 | sess, ac, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: sz, TrueColor: true}, tr) | |
| @@ -1488,10 +1488,10 @@ func TestLiveAttachUpdatesFutureChildEnvTrueColor(t *testing.T) { | |||
| 1488 | 1488 | expectFloatingPrewarmOpen(f, normalSize, floating) | |
| 1489 | 1489 | ||
| 1490 | 1490 | d := newTestDaemon(t, f, stubClock{}) | |
| 1491 | - | tr1 := portsmocks.NewMockTransport(t) | |
| 1491 | + | tr1 := newMockServerConnection(t) | |
| 1492 | 1492 | tr1.EXPECT().Send(mock.Anything).Return(nil).Maybe() | |
| 1493 | 1493 | tr1.EXPECT().Close().Return(nil).Maybe() | |
| 1494 | - | tr2 := portsmocks.NewMockTransport(t) | |
| 1494 | + | tr2 := newMockServerConnection(t) | |
| 1495 | 1495 | tr2.EXPECT().Send(mock.Anything).Return(nil).Maybe() | |
| 1496 | 1496 | tr2.EXPECT().Close().Return(nil).Maybe() | |
| 1497 | 1497 | sess, _, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: sz, TrueColor: false}, tr1) | |
| @@ -1532,7 +1532,7 @@ func TestCreateSessionAndSwitchInheritsTerminalEnv(t *testing.T) { | |||
| 1532 | 1532 | floating := newQuietPTY() | |
| 1533 | 1533 | expectFloatingPrewarmOpen(f, normalSize, floating) | |
| 1534 | 1534 | d := newTestDaemon(t, f, stubClock{}) | |
| 1535 | - | tr := portsmocks.NewMockTransport(t) | |
| 1535 | + | tr := newMockServerConnection(t) | |
| 1536 | 1536 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | |
| 1537 | 1537 | tr.EXPECT().Close().Return(nil).Maybe() | |
| 1538 | 1538 | sess, ac, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: sz, TrueColor: true}, tr) | |
| @@ -1601,10 +1601,10 @@ func TestAttachEnvironmentReplacesFuturePTYInputs(t *testing.T) { | |||
| 1601 | 1601 | floating := newQuietPTY() | |
| 1602 | 1602 | expectFloatingPrewarmOpen(f, normalSize, floating) | |
| 1603 | 1603 | d := newTestDaemon(t, f, stubClock{}) | |
| 1604 | - | tr1 := portsmocks.NewMockTransport(t) | |
| 1604 | + | tr1 := newMockServerConnection(t) | |
| 1605 | 1605 | tr1.EXPECT().Send(mock.Anything).Return(nil).Maybe() | |
| 1606 | 1606 | tr1.EXPECT().Close().Return(nil).Maybe() | |
| 1607 | - | tr2 := portsmocks.NewMockTransport(t) | |
| 1607 | + | tr2 := newMockServerConnection(t) | |
| 1608 | 1608 | tr2.EXPECT().Send(mock.Anything).Return(nil).Maybe() | |
| 1609 | 1609 | tr2.EXPECT().Close().Return(nil).Maybe() | |
| 1610 | 1610 | ||
M internal/usecase/daemon/snapshot_scheduler_test.go +2 -2
| @@ -613,9 +613,9 @@ func (c *serveShutdownClock) nextFinalTimer(t *testing.T) *snapshotDeadlineTimer | |||
| 613 | 613 | } | |
| 614 | 614 | } | |
| 615 | 615 | ||
| 616 | - | func serveSnapshotListener(t *testing.T, tr ports.Transport) *portsmocks.MockListener { | |
| 616 | + | func serveSnapshotListener(t *testing.T, tr ports.Transport) *mockServerListener { | |
| 617 | 617 | t.Helper() | |
| 618 | - | l := portsmocks.NewMockListener(t) | |
| 618 | + | l := newMockServerListener(t) | |
| 619 | 619 | connections := make(chan ports.Transport, 1) | |
| 620 | 620 | connections <- tr | |
| 621 | 621 | closed := make(chan struct{}) | |
A internal/usecase/daemon/typed_connection_test.go +1077 -0
| @@ -0,0 +1,1077 @@ | |||
| 1 | + | package daemon | |
| 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 mockServerConnection struct{ *portsmocks.MockTransport } | |
| 21 | + | ||
| 22 | + | func newMockServerConnection(t mockTestingT) *mockServerConnection { | |
| 23 | + | return &mockServerConnection{MockTransport: portsmocks.NewMockTransport(t)} | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | func (c *mockServerConnection) ReceiveClient() (protocol.ClientMessage, error) { | |
| 27 | + | return testReceiveClient(c.MockTransport) | |
| 28 | + | } | |
| 29 | + | func (c *mockServerConnection) SendServer(message protocol.ServerMessage) error { | |
| 30 | + | return testSendServer(c.MockTransport, message) | |
| 31 | + | } | |
| 32 | + | func (c *mockServerConnection) SendServerAsync(message protocol.ServerMessage) error { | |
| 33 | + | return testSendServerAsync(c.MockTransport, message) | |
| 34 | + | } | |
| 35 | + | func (c *mockServerConnection) SendServerSynchronous(message protocol.ServerMessage) error { | |
| 36 | + | return testSendServerSynchronous(c.MockTransport, message) | |
| 37 | + | } | |
| 38 | + | func (c *mockServerConnection) SendOutput(output protocol.Output) error { | |
| 39 | + | return testSendOutput(c.MockTransport, output) | |
| 40 | + | } | |
| 41 | + | func (c *mockServerConnection) SendOutputAsync(output protocol.Output) error { | |
| 42 | + | return testSendOutputAsync(c.MockTransport, output) | |
| 43 | + | } | |
| 44 | + | func (c *mockServerConnection) SendOutputSynchronous(output protocol.Output) error { | |
| 45 | + | return testSendOutputSynchronous(c.MockTransport, output) | |
| 46 | + | } | |
| 47 | + | func (c *mockServerConnection) Capabilities() protocol.ConnectionCapabilities { | |
| 48 | + | return testServerCapabilities(c.MockTransport) | |
| 49 | + | } | |
| 50 | + | func (c *mockServerConnection) LinkState() ports.LinkState { return testLinkState(c.MockTransport) } | |
| 51 | + | func (c *mockServerConnection) LinkEvents() <-chan ports.LinkEvent { | |
| 52 | + | return testLinkEvents(c.MockTransport) | |
| 53 | + | } | |
| 54 | + | ||
| 55 | + | type rawServerConnection struct{ raw ports.Transport } | |
| 56 | + | ||
| 57 | + | func (c *rawServerConnection) ReceiveClient() (protocol.ClientMessage, error) { | |
| 58 | + | return testReceiveClient(c.raw) | |
| 59 | + | } | |
| 60 | + | func (c *rawServerConnection) SendServer(message protocol.ServerMessage) error { | |
| 61 | + | return testSendServer(c.raw, message) | |
| 62 | + | } | |
| 63 | + | func (c *rawServerConnection) SendServerAsync(message protocol.ServerMessage) error { | |
| 64 | + | return testSendServerAsync(c.raw, message) | |
| 65 | + | } | |
| 66 | + | func (c *rawServerConnection) SendServerSynchronous(message protocol.ServerMessage) error { | |
| 67 | + | return testSendServerSynchronous(c.raw, message) | |
| 68 | + | } | |
| 69 | + | func (c *rawServerConnection) SendOutput(output protocol.Output) error { | |
| 70 | + | return testSendOutput(c.raw, output) | |
| 71 | + | } | |
| 72 | + | func (c *rawServerConnection) SendOutputAsync(output protocol.Output) error { | |
| 73 | + | return testSendOutputAsync(c.raw, output) | |
| 74 | + | } | |
| 75 | + | func (c *rawServerConnection) SendOutputSynchronous(output protocol.Output) error { | |
| 76 | + | return testSendOutputSynchronous(c.raw, output) | |
| 77 | + | } | |
| 78 | + | func (c *rawServerConnection) Capabilities() protocol.ConnectionCapabilities { | |
| 79 | + | return testServerCapabilities(c.raw) | |
| 80 | + | } | |
| 81 | + | func (c *rawServerConnection) LinkState() ports.LinkState { return testLinkState(c.raw) } | |
| 82 | + | func (c *rawServerConnection) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(c.raw) } | |
| 83 | + | func (c *rawServerConnection) Close() error { return c.raw.Close() } | |
| 84 | + | ||
| 85 | + | type mockServerListener struct{ *portsmocks.MockListener } | |
| 86 | + | ||
| 87 | + | func newMockServerListener(t mockTestingT) *mockServerListener { | |
| 88 | + | return &mockServerListener{MockListener: portsmocks.NewMockListener(t)} | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | func (l *mockServerListener) Accept() (ports.ServerConnection, error) { | |
| 92 | + | raw, err := l.MockListener.Accept() | |
| 93 | + | if err != nil { | |
| 94 | + | return nil, err | |
| 95 | + | } | |
| 96 | + | return &rawServerConnection{raw: raw}, nil | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | func testReceiveClient(raw ports.Transport) (protocol.ClientMessage, error) { | |
| 100 | + | frame, err := raw.Recv() | |
| 101 | + | if err != nil { | |
| 102 | + | return nil, err | |
| 103 | + | } | |
| 104 | + | message, err := testDecodeClientFrame(frame) | |
| 105 | + | if err == nil { | |
| 106 | + | return message, nil | |
| 107 | + | } | |
| 108 | + | failure := &protocol.DecodeFailure{Category: protocol.DecodeMalformed, Type: uint8(frame.Type), Err: err} | |
| 109 | + | switch frame.Type { | |
| 110 | + | case wire.MsgHello: | |
| 111 | + | failure.Kind = protocol.DecodeMessageHello | |
| 112 | + | failure.Version, _ = wire.PeekHelloVersion(frame.Payload) | |
| 113 | + | case wire.MsgCommand: | |
| 114 | + | failure.Kind = protocol.DecodeMessageCommand | |
| 115 | + | failure.Version, _ = wire.PeekCommandVersion(frame.Payload) | |
| 116 | + | case wire.MsgKill: | |
| 117 | + | failure.Kind = protocol.DecodeMessageKill | |
| 118 | + | case wire.MsgRemotePreviewRequest: | |
| 119 | + | failure.Kind = protocol.DecodeMessageRemotePreview | |
| 120 | + | } | |
| 121 | + | return nil, failure | |
| 122 | + | } | |
| 123 | + | ||
| 124 | + | func testDecodeClientFrame(frame wire.Frame) (protocol.ClientMessage, error) { | |
| 125 | + | switch frame.Type { | |
| 126 | + | case wire.MsgHello: | |
| 127 | + | return wire.UnmarshalHello(frame.Payload) | |
| 128 | + | case wire.MsgInput: | |
| 129 | + | return wire.UnmarshalInput(frame.Payload) | |
| 130 | + | case wire.MsgResize: | |
| 131 | + | return wire.UnmarshalResize(frame.Payload) | |
| 132 | + | case wire.MsgDetach: | |
| 133 | + | return protocol.Detach{}, nil | |
| 134 | + | case wire.MsgPing: | |
| 135 | + | return protocol.Ping{}, nil | |
| 136 | + | case wire.MsgList: | |
| 137 | + | return protocol.List{}, nil | |
| 138 | + | case wire.MsgKill: | |
| 139 | + | return wire.UnmarshalKill(frame.Payload) | |
| 140 | + | case wire.MsgTheme: | |
| 141 | + | return wire.UnmarshalTheme(frame.Payload) | |
| 142 | + | case wire.MsgAck: | |
| 143 | + | return wire.UnmarshalAck(frame.Payload) | |
| 144 | + | case wire.MsgImagePush: | |
| 145 | + | return wire.UnmarshalImagePush(frame.Payload) | |
| 146 | + | case wire.MsgClientNotice: | |
| 147 | + | return wire.UnmarshalClientNotice(frame.Payload) | |
| 148 | + | case wire.MsgCommand: | |
| 149 | + | return wire.UnmarshalCommandRequest(frame.Payload) | |
| 150 | + | case wire.MsgOutputResetRequest: | |
| 151 | + | return wire.UnmarshalOutputResetRequest(frame.Payload) | |
| 152 | + | case wire.MsgRemotePreviewRequest: | |
| 153 | + | return wire.UnmarshalRemotePreviewRequest(frame.Payload) | |
| 154 | + | case wire.MsgRouteAttentionSubscription: | |
| 155 | + | return wire.UnmarshalRouteAttentionSubscription(frame.Payload) | |
| 156 | + | case wire.MsgSamePeerSwitchRequest: | |
| 157 | + | return wire.UnmarshalSamePeerSwitchRequest(frame.Payload) | |
| 158 | + | case wire.MsgParkedRouteRequest: | |
| 159 | + | return wire.UnmarshalParkedRouteRequest(frame.Payload) | |
| 160 | + | case wire.MsgRecentRouteSnapshot: | |
| 161 | + | return wire.UnmarshalRecentRouteSnapshot(frame.Payload) | |
| 162 | + | case wire.MsgRouteNavigationFailure: | |
| 163 | + | return wire.UnmarshalRouteNavigationFailure(frame.Payload) | |
| 164 | + | default: | |
| 165 | + | return nil, errors.New("test server connection: unsupported client frame") | |
| 166 | + | } | |
| 167 | + | } | |
| 168 | + | ||
| 169 | + | func testSendServer(raw ports.Transport, message protocol.ServerMessage) error { | |
| 170 | + | frame, err := testServerFrame(message) | |
| 171 | + | if err != nil { | |
| 172 | + | return err | |
| 173 | + | } | |
| 174 | + | return raw.Send(frame) | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | func testSendServerAsync(raw ports.Transport, message protocol.ServerMessage) error { | |
| 178 | + | transport, ok := raw.(ports.AsyncTransport) | |
| 179 | + | if !ok { | |
| 180 | + | return errors.New("test server connection: async unsupported") | |
| 181 | + | } | |
| 182 | + | frame, err := testServerFrame(message) | |
| 183 | + | if err != nil { | |
| 184 | + | return err | |
| 185 | + | } | |
| 186 | + | return transport.SendAsync(frame) | |
| 187 | + | } | |
| 188 | + | ||
| 189 | + | func testSendServerSynchronous(raw ports.Transport, message protocol.ServerMessage) error { | |
| 190 | + | transport, ok := raw.(ports.OwnedSynchronousTransport) | |
| 191 | + | if !ok { | |
| 192 | + | return errors.New("test server connection: synchronous unsupported") | |
| 193 | + | } | |
| 194 | + | frame, err := testServerFrame(message) | |
| 195 | + | if err != nil { | |
| 196 | + | return err | |
| 197 | + | } | |
| 198 | + | return transport.SendSynchronous(frame) | |
| 199 | + | } | |
| 200 | + | ||
| 201 | + | func testSendOutput(raw ports.Transport, output protocol.Output) error { | |
| 202 | + | frame, err := testServerFrame(output) | |
| 203 | + | if err != nil { | |
| 204 | + | return err | |
| 205 | + | } | |
| 206 | + | return raw.Send(frame) | |
| 207 | + | } | |
| 208 | + | ||
| 209 | + | func testSendOutputAsync(raw ports.Transport, output protocol.Output) error { | |
| 210 | + | transport, ok := raw.(ports.AsyncTransport) | |
| 211 | + | if !ok { | |
| 212 | + | return errors.New("test server connection: async unsupported") | |
| 213 | + | } | |
| 214 | + | frame, err := testServerFrame(output) | |
| 215 | + | if err != nil { | |
| 216 | + | return err | |
| 217 | + | } | |
| 218 | + | return transport.SendAsync(frame) | |
| 219 | + | } | |
| 220 | + | ||
| 221 | + | func testSendOutputSynchronous(raw ports.Transport, output protocol.Output) error { | |
| 222 | + | transport, ok := raw.(ports.OwnedSynchronousTransport) | |
| 223 | + | if !ok { | |
| 224 | + | return errors.New("test server connection: synchronous unsupported") | |
| 225 | + | } | |
| 226 | + | frame, err := testServerFrame(output) | |
| 227 | + | if err != nil { | |
| 228 | + | return err | |
| 229 | + | } | |
| 230 | + | return transport.SendSynchronous(frame) | |
| 231 | + | } | |
| 232 | + | ||
| 233 | + | func testServerCapabilities(raw ports.Transport) protocol.ConnectionCapabilities { | |
| 234 | + | _, datagram := raw.(ports.DatagramTransport) | |
| 235 | + | _, async := raw.(ports.AsyncTransport) | |
| 236 | + | _, synchronous := raw.(ports.OwnedSynchronousTransport) | |
| 237 | + | _, link := raw.(ports.LinkStateReporter) | |
| 238 | + | window := uint8(8) | |
| 239 | + | if datagram { | |
| 240 | + | window = 1 | |
| 241 | + | } | |
| 242 | + | return protocol.ConnectionCapabilities{OutputDataLimit: protocol.MaxOutputDataLen, PreferredOutputWindow: window, AsyncSend: async, OwnedSynchronousSend: synchronous, LinkState: link} | |
| 243 | + | } | |
| 244 | + | ||
| 245 | + | func testLinkState(raw ports.Transport) ports.LinkState { | |
| 246 | + | if reporter, ok := raw.(ports.LinkStateReporter); ok { | |
| 247 | + | return reporter.LinkState() | |
| 248 | + | } | |
| 249 | + | return ports.LinkStateConnected | |
| 250 | + | } | |
| 251 | + | ||
| 252 | + | func testLinkEvents(raw ports.Transport) <-chan ports.LinkEvent { | |
| 253 | + | if reporter, ok := raw.(ports.LinkStateReporter); ok { | |
| 254 | + | return reporter.LinkEvents() | |
| 255 | + | } | |
| 256 | + | return nil | |
| 257 | + | } | |
| 258 | + | ||
| 259 | + | func outputFrameSender(send func(wire.Frame) error) func(protocol.Output) error { | |
| 260 | + | return func(output protocol.Output) error { | |
| 261 | + | frame, err := testServerFrame(output) | |
| 262 | + | if err != nil { | |
| 263 | + | return err | |
| 264 | + | } | |
| 265 | + | return send(frame) | |
| 266 | + | } | |
| 267 | + | } | |
| 268 | + | ||
| 269 | + | func testServerFrame(message protocol.ServerMessage) (wire.Frame, error) { | |
| 270 | + | switch m := message.(type) { | |
| 271 | + | case protocol.Welcome: | |
| 272 | + | return wire.Frame{Type: wire.MsgWelcome, Payload: wire.MarshalWelcome(m)}, nil | |
| 273 | + | case protocol.ErrorMsg: | |
| 274 | + | return wire.Frame{Type: wire.MsgError, Payload: wire.MarshalErrorMsg(m)}, nil | |
| 275 | + | case protocol.Output: | |
| 276 | + | payload, err := wire.MarshalOutput(m) | |
| 277 | + | return wire.Frame{Type: wire.MsgOutput, Payload: payload}, err | |
| 278 | + | case protocol.Detached: | |
| 279 | + | return wire.Frame{Type: wire.MsgDetached, Payload: wire.MarshalDetached(m)}, nil | |
| 280 | + | case protocol.Pong: | |
| 281 | + | return wire.Frame{Type: wire.MsgPong, Payload: wire.MarshalPong(m)}, nil | |
| 282 | + | case protocol.Sessions: | |
| 283 | + | return wire.Frame{Type: wire.MsgSessions, Payload: wire.MarshalSessions(m)}, nil | |
| 284 | + | case protocol.CommandResult: | |
| 285 | + | return wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(m)}, nil | |
| 286 | + | case protocol.NavigationDirective: | |
| 287 | + | return wire.Frame{Type: wire.MsgNavigationAction, Payload: wire.MarshalNavigationDirective(m)}, nil | |
| 288 | + | case protocol.AttachTarget: | |
| 289 | + | return wire.Frame{Type: wire.MsgAttachTarget, Payload: wire.MarshalAttachTarget(m)}, nil | |
| 290 | + | case protocol.RemotePreview: | |
| 291 | + | return wire.Frame{Type: wire.MsgRemotePreviewResponse, Payload: wire.MarshalRemotePreview(m)}, nil | |
| 292 | + | case protocol.CommittedRouteIdentity: | |
| 293 | + | payload, err := wire.MarshalCommittedRouteIdentity(m) | |
| 294 | + | return wire.Frame{Type: wire.MsgCommittedRouteIdentity, Payload: payload}, err | |
| 295 | + | case protocol.RouteNavigationAction: | |
| 296 | + | payload, err := wire.MarshalRouteNavigationAction(m) | |
| 297 | + | return wire.Frame{Type: wire.MsgNavigateRecentRoute, Payload: payload}, err | |
| 298 | + | case protocol.RouteNavigationFailure: | |
| 299 | + | payload, err := wire.MarshalRouteNavigationFailure(m) | |
| 300 | + | return wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: payload}, err | |
| 301 | + | case protocol.RoutePosition: | |
| 302 | + | payload, err := wire.MarshalRoutePosition(m) | |
| 303 | + | return wire.Frame{Type: wire.MsgRoutePosition, Payload: payload}, err | |
| 304 | + | case protocol.SamePeerSwitchFailure: | |
| 305 | + | payload, err := wire.MarshalSamePeerSwitchFailure(m) | |
| 306 | + | return wire.Frame{Type: wire.MsgSamePeerSwitchFailure, Payload: payload}, err | |
| 307 | + | case protocol.ParkedRouteResponse: | |
| 308 | + | return wire.Frame{Type: wire.MsgParkedRouteResponse, Payload: wire.MarshalParkedRouteResponse(m)}, nil | |
| 309 | + | default: | |
| 310 | + | return wire.Frame{}, errors.New("test server connection: unsupported server message") | |
| 311 | + | } | |
| 312 | + | } | |
| 313 | + | ||
| 314 | + | func (d *Daemon) handleCommandFrame(connection ports.ServerConnection, frame wire.Frame) error { | |
| 315 | + | request, err := wire.UnmarshalCommandRequest(frame.Payload) | |
| 316 | + | if err != nil { | |
| 317 | + | code := uint16(protocol.ErrInternal) | |
| 318 | + | if version, ok := wire.PeekCommandVersion(frame.Payload); ok && version != protocol.Version { | |
| 319 | + | code = protocol.ErrVersionMismatch | |
| 320 | + | } | |
| 321 | + | return d.sendCommandResult(connection, protocol.CommandResult{Code: code, Text: err.Error()}) | |
| 322 | + | } | |
| 323 | + | return d.handleCommand(connection, request) | |
| 324 | + | } | |
| 325 | + | ||
| 326 | + | func (d *Daemon) handleHelloFrame(connection ports.ServerConnection, frame wire.Frame) { | |
| 327 | + | hello, err := wire.UnmarshalHello(frame.Payload) | |
| 328 | + | if err != nil { | |
| 329 | + | return | |
| 330 | + | } | |
| 331 | + | d.handleHello(connection, hello) | |
| 332 | + | } | |
| 333 | + | ||
| 334 | + | func (d *Daemon) handleHelloFrameWithContext(ctx context.Context, timedOut <-chan struct{}, stop, finish func(), connection ports.ServerConnection, frame wire.Frame) { | |
| 335 | + | hello, err := wire.UnmarshalHello(frame.Payload) | |
| 336 | + | if err != nil { | |
| 337 | + | return | |
| 338 | + | } | |
| 339 | + | d.handleHelloWithContext(ctx, timedOut, stop, finish, connection, hello) | |
| 340 | + | } | |
| 341 | + | ||
| 342 | + | func (d *Daemon) handleAttachmentClientFrame(capability attachmentCapability, frame wire.Frame) bool { | |
| 343 | + | message, err := testReceiveClient(&singleFrameTransport{frame: frame}) | |
| 344 | + | if err != nil { | |
| 345 | + | return false | |
| 346 | + | } | |
| 347 | + | return d.handleAttachmentClientMessage(capability, message) | |
| 348 | + | } | |
| 349 | + | ||
| 350 | + | type singleFrameTransport struct{ frame wire.Frame } | |
| 351 | + | ||
| 352 | + | func (*singleFrameTransport) Send(wire.Frame) error { return nil } | |
| 353 | + | func (t *singleFrameTransport) Recv() (wire.Frame, error) { return t.frame, nil } | |
| 354 | + | func (*singleFrameTransport) Close() error { return nil } | |
| 355 | + | ||
| 356 | + | // Methods below let the existing raw transport fixtures exercise the typed | |
| 357 | + | // daemon boundary while preserving their byte-level assertions. | |
| 358 | + | ||
| 359 | + | func (t *blockedRenderReplacementTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 360 | + | return testReceiveClient(t) | |
| 361 | + | } | |
| 362 | + | func (t *blockedRenderReplacementTransport) SendServer(m protocol.ServerMessage) error { | |
| 363 | + | return testSendServer(t, m) | |
| 364 | + | } | |
| 365 | + | func (t *blockedRenderReplacementTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 366 | + | return testSendServerAsync(t, m) | |
| 367 | + | } | |
| 368 | + | func (t *blockedRenderReplacementTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 369 | + | return testSendServerSynchronous(t, m) | |
| 370 | + | } | |
| 371 | + | func (t *blockedRenderReplacementTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 372 | + | return testServerCapabilities(t) | |
| 373 | + | } | |
| 374 | + | func (t *blockedRenderReplacementTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 375 | + | func (t *blockedRenderReplacementTransport) LinkEvents() <-chan ports.LinkEvent { | |
| 376 | + | return testLinkEvents(t) | |
| 377 | + | } | |
| 378 | + | ||
| 379 | + | func (t *teardownBlockingTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 380 | + | return testReceiveClient(t) | |
| 381 | + | } | |
| 382 | + | func (t *teardownBlockingTransport) SendServer(m protocol.ServerMessage) error { | |
| 383 | + | return testSendServer(t, m) | |
| 384 | + | } | |
| 385 | + | func (t *teardownBlockingTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 386 | + | return testSendServerAsync(t, m) | |
| 387 | + | } | |
| 388 | + | func (t *teardownBlockingTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 389 | + | return testSendServerSynchronous(t, m) | |
| 390 | + | } | |
| 391 | + | func (t *teardownBlockingTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 392 | + | return testServerCapabilities(t) | |
| 393 | + | } | |
| 394 | + | func (t *teardownBlockingTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 395 | + | func (t *teardownBlockingTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 396 | + | ||
| 397 | + | func (t *closeTrackingTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 398 | + | return testReceiveClient(t) | |
| 399 | + | } | |
| 400 | + | func (t *closeTrackingTransport) SendServer(m protocol.ServerMessage) error { | |
| 401 | + | return testSendServer(t, m) | |
| 402 | + | } | |
| 403 | + | func (t *closeTrackingTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 404 | + | return testSendServerAsync(t, m) | |
| 405 | + | } | |
| 406 | + | func (t *closeTrackingTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 407 | + | return testSendServerSynchronous(t, m) | |
| 408 | + | } | |
| 409 | + | func (t *closeTrackingTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 410 | + | return testServerCapabilities(t) | |
| 411 | + | } | |
| 412 | + | func (t *closeTrackingTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 413 | + | func (t *closeTrackingTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 414 | + | ||
| 415 | + | func (t *closeCountingBlockedTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 416 | + | return testReceiveClient(t) | |
| 417 | + | } | |
| 418 | + | func (t *closeCountingBlockedTransport) SendServer(m protocol.ServerMessage) error { | |
| 419 | + | return testSendServer(t, m) | |
| 420 | + | } | |
| 421 | + | func (t *closeCountingBlockedTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 422 | + | return testSendServerAsync(t, m) | |
| 423 | + | } | |
| 424 | + | func (t *closeCountingBlockedTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 425 | + | return testSendServerSynchronous(t, m) | |
| 426 | + | } | |
| 427 | + | func (t *closeCountingBlockedTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 428 | + | return testServerCapabilities(t) | |
| 429 | + | } | |
| 430 | + | func (t *closeCountingBlockedTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 431 | + | func (t *closeCountingBlockedTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 432 | + | ||
| 433 | + | func (t *handshakeBlockingTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 434 | + | return testReceiveClient(t) | |
| 435 | + | } | |
| 436 | + | func (t *handshakeBlockingTransport) SendServer(m protocol.ServerMessage) error { | |
| 437 | + | return testSendServer(t, m) | |
| 438 | + | } | |
| 439 | + | func (t *handshakeBlockingTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 440 | + | return testSendServerAsync(t, m) | |
| 441 | + | } | |
| 442 | + | func (t *handshakeBlockingTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 443 | + | return testSendServerSynchronous(t, m) | |
| 444 | + | } | |
| 445 | + | func (t *handshakeBlockingTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 446 | + | return testServerCapabilities(t) | |
| 447 | + | } | |
| 448 | + | func (t *handshakeBlockingTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 449 | + | func (t *handshakeBlockingTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 450 | + | ||
| 451 | + | func (t *lateGraphicsCleanupTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 452 | + | return testReceiveClient(t) | |
| 453 | + | } | |
| 454 | + | func (t *lateGraphicsCleanupTransport) SendServer(m protocol.ServerMessage) error { | |
| 455 | + | return testSendServer(t, m) | |
| 456 | + | } | |
| 457 | + | func (t *lateGraphicsCleanupTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 458 | + | return testSendServerAsync(t, m) | |
| 459 | + | } | |
| 460 | + | func (t *lateGraphicsCleanupTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 461 | + | return testSendServerSynchronous(t, m) | |
| 462 | + | } | |
| 463 | + | func (t *lateGraphicsCleanupTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 464 | + | return testServerCapabilities(t) | |
| 465 | + | } | |
| 466 | + | func (t *lateGraphicsCleanupTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 467 | + | func (t *lateGraphicsCleanupTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 468 | + | ||
| 469 | + | func (t *datagramTestTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 470 | + | return testReceiveClient(t) | |
| 471 | + | } | |
| 472 | + | func (t *datagramTestTransport) SendServer(m protocol.ServerMessage) error { | |
| 473 | + | return testSendServer(t, m) | |
| 474 | + | } | |
| 475 | + | func (t *datagramTestTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 476 | + | return testSendServerAsync(t, m) | |
| 477 | + | } | |
| 478 | + | func (t *datagramTestTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 479 | + | return testSendServerSynchronous(t, m) | |
| 480 | + | } | |
| 481 | + | func (t *datagramTestTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 482 | + | return testServerCapabilities(t) | |
| 483 | + | } | |
| 484 | + | func (t *datagramTestTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 485 | + | func (t *datagramTestTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 486 | + | ||
| 487 | + | func (t *asyncPaintTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 488 | + | return testReceiveClient(t) | |
| 489 | + | } | |
| 490 | + | func (t *asyncPaintTransport) SendServer(m protocol.ServerMessage) error { return testSendServer(t, m) } | |
| 491 | + | func (t *asyncPaintTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 492 | + | return testSendServerAsync(t, m) | |
| 493 | + | } | |
| 494 | + | func (t *asyncPaintTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 495 | + | return testSendServerSynchronous(t, m) | |
| 496 | + | } | |
| 497 | + | func (t *asyncPaintTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 498 | + | return testServerCapabilities(t) | |
| 499 | + | } | |
| 500 | + | func (t *asyncPaintTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 501 | + | func (t *asyncPaintTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 502 | + | ||
| 503 | + | func (t *timedSideEffectTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 504 | + | return testReceiveClient(t) | |
| 505 | + | } | |
| 506 | + | func (t *timedSideEffectTransport) SendServer(m protocol.ServerMessage) error { | |
| 507 | + | return testSendServer(t, m) | |
| 508 | + | } | |
| 509 | + | func (t *timedSideEffectTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 510 | + | return testSendServerAsync(t, m) | |
| 511 | + | } | |
| 512 | + | func (t *timedSideEffectTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 513 | + | return testSendServerSynchronous(t, m) | |
| 514 | + | } | |
| 515 | + | func (t *timedSideEffectTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 516 | + | return testServerCapabilities(t) | |
| 517 | + | } | |
| 518 | + | func (t *timedSideEffectTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 519 | + | func (t *timedSideEffectTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 520 | + | ||
| 521 | + | func (t *scriptedReplayTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 522 | + | return testReceiveClient(t) | |
| 523 | + | } | |
| 524 | + | func (t *scriptedReplayTransport) SendServer(m protocol.ServerMessage) error { | |
| 525 | + | return testSendServer(t, m) | |
| 526 | + | } | |
| 527 | + | func (t *scriptedReplayTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 528 | + | return testSendServerAsync(t, m) | |
| 529 | + | } | |
| 530 | + | func (t *scriptedReplayTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 531 | + | return testSendServerSynchronous(t, m) | |
| 532 | + | } | |
| 533 | + | func (t *scriptedReplayTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 534 | + | return testServerCapabilities(t) | |
| 535 | + | } | |
| 536 | + | func (t *scriptedReplayTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 537 | + | func (t *scriptedReplayTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 538 | + | ||
| 539 | + | func (t *swapErrorTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 540 | + | return testReceiveClient(t) | |
| 541 | + | } | |
| 542 | + | func (t *swapErrorTransport) SendServer(m protocol.ServerMessage) error { return testSendServer(t, m) } | |
| 543 | + | func (t *swapErrorTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 544 | + | return testSendServerAsync(t, m) | |
| 545 | + | } | |
| 546 | + | func (t *swapErrorTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 547 | + | return testSendServerSynchronous(t, m) | |
| 548 | + | } | |
| 549 | + | func (t *swapErrorTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 550 | + | return testServerCapabilities(t) | |
| 551 | + | } | |
| 552 | + | func (t *swapErrorTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 553 | + | func (t *swapErrorTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 554 | + | ||
| 555 | + | func (t *ownedSwapErrorTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 556 | + | return testReceiveClient(t) | |
| 557 | + | } | |
| 558 | + | func (t *ownedSwapErrorTransport) SendServer(m protocol.ServerMessage) error { | |
| 559 | + | return testSendServer(t, m) | |
| 560 | + | } | |
| 561 | + | func (t *ownedSwapErrorTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 562 | + | return testSendServerAsync(t, m) | |
| 563 | + | } | |
| 564 | + | func (t *ownedSwapErrorTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 565 | + | return testSendServerSynchronous(t, m) | |
| 566 | + | } | |
| 567 | + | func (t *ownedSwapErrorTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 568 | + | return testServerCapabilities(t) | |
| 569 | + | } | |
| 570 | + | func (t *ownedSwapErrorTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 571 | + | func (t *ownedSwapErrorTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 572 | + | ||
| 573 | + | func (t *blockingControlSendTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 574 | + | return testReceiveClient(t) | |
| 575 | + | } | |
| 576 | + | func (t *blockingControlSendTransport) SendServer(m protocol.ServerMessage) error { | |
| 577 | + | return testSendServer(t, m) | |
| 578 | + | } | |
| 579 | + | func (t *blockingControlSendTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 580 | + | return testSendServerAsync(t, m) | |
| 581 | + | } | |
| 582 | + | func (t *blockingControlSendTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 583 | + | return testSendServerSynchronous(t, m) | |
| 584 | + | } | |
| 585 | + | func (t *blockingControlSendTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 586 | + | return testServerCapabilities(t) | |
| 587 | + | } | |
| 588 | + | func (t *blockingControlSendTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 589 | + | func (t *blockingControlSendTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 590 | + | ||
| 591 | + | func (t *commandSendErrorTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 592 | + | return testReceiveClient(t) | |
| 593 | + | } | |
| 594 | + | func (t *commandSendErrorTransport) SendServer(m protocol.ServerMessage) error { | |
| 595 | + | return testSendServer(t, m) | |
| 596 | + | } | |
| 597 | + | func (t *commandSendErrorTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 598 | + | return testSendServerAsync(t, m) | |
| 599 | + | } | |
| 600 | + | func (t *commandSendErrorTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 601 | + | return testSendServerSynchronous(t, m) | |
| 602 | + | } | |
| 603 | + | func (t *commandSendErrorTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 604 | + | return testServerCapabilities(t) | |
| 605 | + | } | |
| 606 | + | func (t *commandSendErrorTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 607 | + | func (t *commandSendErrorTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 608 | + | ||
| 609 | + | func (t *fanoutBlockingTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 610 | + | return testReceiveClient(t) | |
| 611 | + | } | |
| 612 | + | func (t *fanoutBlockingTransport) SendServer(m protocol.ServerMessage) error { | |
| 613 | + | return testSendServer(t, m) | |
| 614 | + | } | |
| 615 | + | func (t *fanoutBlockingTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 616 | + | return testSendServerAsync(t, m) | |
| 617 | + | } | |
| 618 | + | func (t *fanoutBlockingTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 619 | + | return testSendServerSynchronous(t, m) | |
| 620 | + | } | |
| 621 | + | func (t *fanoutBlockingTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 622 | + | return testServerCapabilities(t) | |
| 623 | + | } | |
| 624 | + | func (t *fanoutBlockingTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 625 | + | func (t *fanoutBlockingTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 626 | + | ||
| 627 | + | func (t *staleClipboardErrorTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 628 | + | return testReceiveClient(t) | |
| 629 | + | } | |
| 630 | + | func (t *staleClipboardErrorTransport) SendServer(m protocol.ServerMessage) error { | |
| 631 | + | return testSendServer(t, m) | |
| 632 | + | } | |
| 633 | + | func (t *staleClipboardErrorTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 634 | + | return testSendServerAsync(t, m) | |
| 635 | + | } | |
| 636 | + | func (t *staleClipboardErrorTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 637 | + | return testSendServerSynchronous(t, m) | |
| 638 | + | } | |
| 639 | + | func (t *staleClipboardErrorTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 640 | + | return testServerCapabilities(t) | |
| 641 | + | } | |
| 642 | + | func (t *staleClipboardErrorTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 643 | + | func (t *staleClipboardErrorTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 644 | + | ||
| 645 | + | func (t *movingClipboardErrorTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 646 | + | return testReceiveClient(t) | |
| 647 | + | } | |
| 648 | + | func (t *movingClipboardErrorTransport) SendServer(m protocol.ServerMessage) error { | |
| 649 | + | return testSendServer(t, m) | |
| 650 | + | } | |
| 651 | + | func (t *movingClipboardErrorTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 652 | + | return testSendServerAsync(t, m) | |
| 653 | + | } | |
| 654 | + | func (t *movingClipboardErrorTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 655 | + | return testSendServerSynchronous(t, m) | |
| 656 | + | } | |
| 657 | + | func (t *movingClipboardErrorTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 658 | + | return testServerCapabilities(t) | |
| 659 | + | } | |
| 660 | + | func (t *movingClipboardErrorTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 661 | + | func (t *movingClipboardErrorTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 662 | + | ||
| 663 | + | func (t *blockingClipboardTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 664 | + | return testReceiveClient(t) | |
| 665 | + | } | |
| 666 | + | func (t *blockingClipboardTransport) SendServer(m protocol.ServerMessage) error { | |
| 667 | + | return testSendServer(t, m) | |
| 668 | + | } | |
| 669 | + | func (t *blockingClipboardTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 670 | + | return testSendServerAsync(t, m) | |
| 671 | + | } | |
| 672 | + | func (t *blockingClipboardTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 673 | + | return testSendServerSynchronous(t, m) | |
| 674 | + | } | |
| 675 | + | func (t *blockingClipboardTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 676 | + | return testServerCapabilities(t) | |
| 677 | + | } | |
| 678 | + | func (t *blockingClipboardTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 679 | + | func (t *blockingClipboardTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 680 | + | ||
| 681 | + | func (t *blockedAttachmentTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 682 | + | return testReceiveClient(t) | |
| 683 | + | } | |
| 684 | + | func (t *blockedAttachmentTransport) SendServer(m protocol.ServerMessage) error { | |
| 685 | + | return testSendServer(t, m) | |
| 686 | + | } | |
| 687 | + | func (t *blockedAttachmentTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 688 | + | return testSendServerAsync(t, m) | |
| 689 | + | } | |
| 690 | + | func (t *blockedAttachmentTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 691 | + | return testSendServerSynchronous(t, m) | |
| 692 | + | } | |
| 693 | + | func (t *blockedAttachmentTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 694 | + | return testServerCapabilities(t) | |
| 695 | + | } | |
| 696 | + | func (t *blockedAttachmentTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 697 | + | func (t *blockedAttachmentTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 698 | + | ||
| 699 | + | func (t *resumeWelcomeFailureTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 700 | + | return testReceiveClient(t) | |
| 701 | + | } | |
| 702 | + | func (t *resumeWelcomeFailureTransport) SendServer(m protocol.ServerMessage) error { | |
| 703 | + | return testSendServer(t, m) | |
| 704 | + | } | |
| 705 | + | func (t *resumeWelcomeFailureTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 706 | + | return testSendServerAsync(t, m) | |
| 707 | + | } | |
| 708 | + | func (t *resumeWelcomeFailureTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 709 | + | return testSendServerSynchronous(t, m) | |
| 710 | + | } | |
| 711 | + | func (t *resumeWelcomeFailureTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 712 | + | return testServerCapabilities(t) | |
| 713 | + | } | |
| 714 | + | func (t *resumeWelcomeFailureTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 715 | + | func (t *resumeWelcomeFailureTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 716 | + | ||
| 717 | + | func (t *committedIdentityErrorTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 718 | + | return testReceiveClient(t) | |
| 719 | + | } | |
| 720 | + | func (t *committedIdentityErrorTransport) SendServer(m protocol.ServerMessage) error { | |
| 721 | + | return testSendServer(t, m) | |
| 722 | + | } | |
| 723 | + | func (t *committedIdentityErrorTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 724 | + | return testSendServerAsync(t, m) | |
| 725 | + | } | |
| 726 | + | func (t *committedIdentityErrorTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 727 | + | return testSendServerSynchronous(t, m) | |
| 728 | + | } | |
| 729 | + | func (t *committedIdentityErrorTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 730 | + | return testServerCapabilities(t) | |
| 731 | + | } | |
| 732 | + | func (t *committedIdentityErrorTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 733 | + | func (t *committedIdentityErrorTransport) LinkEvents() <-chan ports.LinkEvent { | |
| 734 | + | return testLinkEvents(t) | |
| 735 | + | } | |
| 736 | + | ||
| 737 | + | func (t *countingOutputTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 738 | + | return testReceiveClient(t) | |
| 739 | + | } | |
| 740 | + | func (t *countingOutputTransport) SendServer(m protocol.ServerMessage) error { | |
| 741 | + | return testSendServer(t, m) | |
| 742 | + | } | |
| 743 | + | func (t *countingOutputTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 744 | + | return testSendServerAsync(t, m) | |
| 745 | + | } | |
| 746 | + | func (t *countingOutputTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 747 | + | return testSendServerSynchronous(t, m) | |
| 748 | + | } | |
| 749 | + | func (t *countingOutputTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 750 | + | return testServerCapabilities(t) | |
| 751 | + | } | |
| 752 | + | func (t *countingOutputTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 753 | + | func (t *countingOutputTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 754 | + | ||
| 755 | + | func (t *parkedRouteExpiryTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 756 | + | return testReceiveClient(t) | |
| 757 | + | } | |
| 758 | + | func (t *parkedRouteExpiryTransport) SendServer(m protocol.ServerMessage) error { | |
| 759 | + | return testSendServer(t, m) | |
| 760 | + | } | |
| 761 | + | func (t *parkedRouteExpiryTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 762 | + | return testSendServerAsync(t, m) | |
| 763 | + | } | |
| 764 | + | func (t *parkedRouteExpiryTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 765 | + | return testSendServerSynchronous(t, m) | |
| 766 | + | } | |
| 767 | + | func (t *parkedRouteExpiryTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 768 | + | return testServerCapabilities(t) | |
| 769 | + | } | |
| 770 | + | func (t *parkedRouteExpiryTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 771 | + | func (t *parkedRouteExpiryTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 772 | + | ||
| 773 | + | func (t *remotePickerSendErrorTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 774 | + | return testReceiveClient(t) | |
| 775 | + | } | |
| 776 | + | func (t *remotePickerSendErrorTransport) SendServer(m protocol.ServerMessage) error { | |
| 777 | + | return testSendServer(t, m) | |
| 778 | + | } | |
| 779 | + | func (t *remotePickerSendErrorTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 780 | + | return testSendServerAsync(t, m) | |
| 781 | + | } | |
| 782 | + | func (t *remotePickerSendErrorTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 783 | + | return testSendServerSynchronous(t, m) | |
| 784 | + | } | |
| 785 | + | func (t *remotePickerSendErrorTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 786 | + | return testServerCapabilities(t) | |
| 787 | + | } | |
| 788 | + | func (t *remotePickerSendErrorTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 789 | + | func (t *remotePickerSendErrorTransport) LinkEvents() <-chan ports.LinkEvent { | |
| 790 | + | return testLinkEvents(t) | |
| 791 | + | } | |
| 792 | + | ||
| 793 | + | func (t failingOutputTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 794 | + | return testReceiveClient(t) | |
| 795 | + | } | |
| 796 | + | func (t failingOutputTransport) SendServer(m protocol.ServerMessage) error { | |
| 797 | + | return testSendServer(t, m) | |
| 798 | + | } | |
| 799 | + | func (t failingOutputTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 800 | + | return testSendServerAsync(t, m) | |
| 801 | + | } | |
| 802 | + | func (t failingOutputTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 803 | + | return testSendServerSynchronous(t, m) | |
| 804 | + | } | |
| 805 | + | func (t failingOutputTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 806 | + | return testServerCapabilities(t) | |
| 807 | + | } | |
| 808 | + | func (t failingOutputTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 809 | + | func (t failingOutputTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 810 | + | ||
| 811 | + | func (t cacheFailTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 812 | + | return testReceiveClient(t) | |
| 813 | + | } | |
| 814 | + | func (t cacheFailTransport) SendServer(m protocol.ServerMessage) error { return testSendServer(t, m) } | |
| 815 | + | func (t cacheFailTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 816 | + | return testSendServerAsync(t, m) | |
| 817 | + | } | |
| 818 | + | func (t cacheFailTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 819 | + | return testSendServerSynchronous(t, m) | |
| 820 | + | } | |
| 821 | + | func (t cacheFailTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 822 | + | return testServerCapabilities(t) | |
| 823 | + | } | |
| 824 | + | func (t cacheFailTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 825 | + | func (t cacheFailTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 826 | + | ||
| 827 | + | func (t parkedRouteFailTransport) ReceiveClient() (protocol.ClientMessage, error) { | |
| 828 | + | return testReceiveClient(t) | |
| 829 | + | } | |
| 830 | + | func (t parkedRouteFailTransport) SendServer(m protocol.ServerMessage) error { | |
| 831 | + | return testSendServer(t, m) | |
| 832 | + | } | |
| 833 | + | func (t parkedRouteFailTransport) SendServerAsync(m protocol.ServerMessage) error { | |
| 834 | + | return testSendServerAsync(t, m) | |
| 835 | + | } | |
| 836 | + | func (t parkedRouteFailTransport) SendServerSynchronous(m protocol.ServerMessage) error { | |
| 837 | + | return testSendServerSynchronous(t, m) | |
| 838 | + | } | |
| 839 | + | func (t parkedRouteFailTransport) Capabilities() protocol.ConnectionCapabilities { | |
| 840 | + | return testServerCapabilities(t) | |
| 841 | + | } | |
| 842 | + | func (t parkedRouteFailTransport) LinkState() ports.LinkState { return testLinkState(t) } | |
| 843 | + | func (t parkedRouteFailTransport) LinkEvents() <-chan ports.LinkEvent { return testLinkEvents(t) } | |
| 844 | + | ||
| 845 | + | func (t *blockedRenderReplacementTransport) SendOutput(o protocol.Output) error { | |
| 846 | + | return testSendOutput(t, o) | |
| 847 | + | } | |
| 848 | + | func (t *blockedRenderReplacementTransport) SendOutputAsync(o protocol.Output) error { | |
| 849 | + | return testSendOutputAsync(t, o) | |
| 850 | + | } | |
| 851 | + | func (t *blockedRenderReplacementTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 852 | + | return testSendOutputSynchronous(t, o) | |
| 853 | + | } | |
| 854 | + | ||
| 855 | + | func (t *teardownBlockingTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 856 | + | func (t *teardownBlockingTransport) SendOutputAsync(o protocol.Output) error { | |
| 857 | + | return testSendOutputAsync(t, o) | |
| 858 | + | } | |
| 859 | + | func (t *teardownBlockingTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 860 | + | return testSendOutputSynchronous(t, o) | |
| 861 | + | } | |
| 862 | + | ||
| 863 | + | func (t *closeTrackingTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 864 | + | func (t *closeTrackingTransport) SendOutputAsync(o protocol.Output) error { | |
| 865 | + | return testSendOutputAsync(t, o) | |
| 866 | + | } | |
| 867 | + | func (t *closeTrackingTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 868 | + | return testSendOutputSynchronous(t, o) | |
| 869 | + | } | |
| 870 | + | ||
| 871 | + | func (t *closeCountingBlockedTransport) SendOutput(o protocol.Output) error { | |
| 872 | + | return testSendOutput(t, o) | |
| 873 | + | } | |
| 874 | + | func (t *closeCountingBlockedTransport) SendOutputAsync(o protocol.Output) error { | |
| 875 | + | return testSendOutputAsync(t, o) | |
| 876 | + | } | |
| 877 | + | func (t *closeCountingBlockedTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 878 | + | return testSendOutputSynchronous(t, o) | |
| 879 | + | } | |
| 880 | + | ||
| 881 | + | func (t *handshakeBlockingTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 882 | + | func (t *handshakeBlockingTransport) SendOutputAsync(o protocol.Output) error { | |
| 883 | + | return testSendOutputAsync(t, o) | |
| 884 | + | } | |
| 885 | + | func (t *handshakeBlockingTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 886 | + | return testSendOutputSynchronous(t, o) | |
| 887 | + | } | |
| 888 | + | ||
| 889 | + | func (t *lateGraphicsCleanupTransport) SendOutput(o protocol.Output) error { | |
| 890 | + | return testSendOutput(t, o) | |
| 891 | + | } | |
| 892 | + | func (t *lateGraphicsCleanupTransport) SendOutputAsync(o protocol.Output) error { | |
| 893 | + | return testSendOutputAsync(t, o) | |
| 894 | + | } | |
| 895 | + | func (t *lateGraphicsCleanupTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 896 | + | return testSendOutputSynchronous(t, o) | |
| 897 | + | } | |
| 898 | + | ||
| 899 | + | func (t *datagramTestTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 900 | + | func (t *datagramTestTransport) SendOutputAsync(o protocol.Output) error { | |
| 901 | + | return testSendOutputAsync(t, o) | |
| 902 | + | } | |
| 903 | + | func (t *datagramTestTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 904 | + | return testSendOutputSynchronous(t, o) | |
| 905 | + | } | |
| 906 | + | ||
| 907 | + | func (t *asyncPaintTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 908 | + | func (t *asyncPaintTransport) SendOutputAsync(o protocol.Output) error { | |
| 909 | + | return testSendOutputAsync(t, o) | |
| 910 | + | } | |
| 911 | + | func (t *asyncPaintTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 912 | + | return testSendOutputSynchronous(t, o) | |
| 913 | + | } | |
| 914 | + | ||
| 915 | + | func (t *timedSideEffectTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 916 | + | func (t *timedSideEffectTransport) SendOutputAsync(o protocol.Output) error { | |
| 917 | + | return testSendOutputAsync(t, o) | |
| 918 | + | } | |
| 919 | + | func (t *timedSideEffectTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 920 | + | return testSendOutputSynchronous(t, o) | |
| 921 | + | } | |
| 922 | + | ||
| 923 | + | func (t *scriptedReplayTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 924 | + | func (t *scriptedReplayTransport) SendOutputAsync(o protocol.Output) error { | |
| 925 | + | return testSendOutputAsync(t, o) | |
| 926 | + | } | |
| 927 | + | func (t *scriptedReplayTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 928 | + | return testSendOutputSynchronous(t, o) | |
| 929 | + | } | |
| 930 | + | ||
| 931 | + | func (t *swapErrorTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 932 | + | func (t *swapErrorTransport) SendOutputAsync(o protocol.Output) error { | |
| 933 | + | return testSendOutputAsync(t, o) | |
| 934 | + | } | |
| 935 | + | func (t *swapErrorTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 936 | + | return testSendOutputSynchronous(t, o) | |
| 937 | + | } | |
| 938 | + | ||
| 939 | + | func (t *ownedSwapErrorTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 940 | + | func (t *ownedSwapErrorTransport) SendOutputAsync(o protocol.Output) error { | |
| 941 | + | return testSendOutputAsync(t, o) | |
| 942 | + | } | |
| 943 | + | func (t *ownedSwapErrorTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 944 | + | return testSendOutputSynchronous(t, o) | |
| 945 | + | } | |
| 946 | + | ||
| 947 | + | func (t *blockingControlSendTransport) SendOutput(o protocol.Output) error { | |
| 948 | + | return testSendOutput(t, o) | |
| 949 | + | } | |
| 950 | + | func (t *blockingControlSendTransport) SendOutputAsync(o protocol.Output) error { | |
| 951 | + | return testSendOutputAsync(t, o) | |
| 952 | + | } | |
| 953 | + | func (t *blockingControlSendTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 954 | + | return testSendOutputSynchronous(t, o) | |
| 955 | + | } | |
| 956 | + | ||
| 957 | + | func (t *commandSendErrorTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 958 | + | func (t *commandSendErrorTransport) SendOutputAsync(o protocol.Output) error { | |
| 959 | + | return testSendOutputAsync(t, o) | |
| 960 | + | } | |
| 961 | + | func (t *commandSendErrorTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 962 | + | return testSendOutputSynchronous(t, o) | |
| 963 | + | } | |
| 964 | + | ||
| 965 | + | func (t *fanoutBlockingTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 966 | + | func (t *fanoutBlockingTransport) SendOutputAsync(o protocol.Output) error { | |
| 967 | + | return testSendOutputAsync(t, o) | |
| 968 | + | } | |
| 969 | + | func (t *fanoutBlockingTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 970 | + | return testSendOutputSynchronous(t, o) | |
| 971 | + | } | |
| 972 | + | ||
| 973 | + | func (t *staleClipboardErrorTransport) SendOutput(o protocol.Output) error { | |
| 974 | + | return testSendOutput(t, o) | |
| 975 | + | } | |
| 976 | + | func (t *staleClipboardErrorTransport) SendOutputAsync(o protocol.Output) error { | |
| 977 | + | return testSendOutputAsync(t, o) | |
| 978 | + | } | |
| 979 | + | func (t *staleClipboardErrorTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 980 | + | return testSendOutputSynchronous(t, o) | |
| 981 | + | } | |
| 982 | + | ||
| 983 | + | func (t *movingClipboardErrorTransport) SendOutput(o protocol.Output) error { | |
| 984 | + | return testSendOutput(t, o) | |
| 985 | + | } | |
| 986 | + | func (t *movingClipboardErrorTransport) SendOutputAsync(o protocol.Output) error { | |
| 987 | + | return testSendOutputAsync(t, o) | |
| 988 | + | } | |
| 989 | + | func (t *movingClipboardErrorTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 990 | + | return testSendOutputSynchronous(t, o) | |
| 991 | + | } | |
| 992 | + | ||
| 993 | + | func (t *blockingClipboardTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 994 | + | func (t *blockingClipboardTransport) SendOutputAsync(o protocol.Output) error { | |
| 995 | + | return testSendOutputAsync(t, o) | |
| 996 | + | } | |
| 997 | + | func (t *blockingClipboardTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 998 | + | return testSendOutputSynchronous(t, o) | |
| 999 | + | } | |
| 1000 | + | ||
| 1001 | + | func (t *blockedAttachmentTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 1002 | + | func (t *blockedAttachmentTransport) SendOutputAsync(o protocol.Output) error { | |
| 1003 | + | return testSendOutputAsync(t, o) | |
| 1004 | + | } | |
| 1005 | + | func (t *blockedAttachmentTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 1006 | + | return testSendOutputSynchronous(t, o) | |
| 1007 | + | } | |
| 1008 | + | ||
| 1009 | + | func (t *resumeWelcomeFailureTransport) SendOutput(o protocol.Output) error { | |
| 1010 | + | return testSendOutput(t, o) | |
| 1011 | + | } | |
| 1012 | + | func (t *resumeWelcomeFailureTransport) SendOutputAsync(o protocol.Output) error { | |
| 1013 | + | return testSendOutputAsync(t, o) | |
| 1014 | + | } | |
| 1015 | + | func (t *resumeWelcomeFailureTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 1016 | + | return testSendOutputSynchronous(t, o) | |
| 1017 | + | } | |
| 1018 | + | ||
| 1019 | + | func (t *committedIdentityErrorTransport) SendOutput(o protocol.Output) error { | |
| 1020 | + | return testSendOutput(t, o) | |
| 1021 | + | } | |
| 1022 | + | func (t *committedIdentityErrorTransport) SendOutputAsync(o protocol.Output) error { | |
| 1023 | + | return testSendOutputAsync(t, o) | |
| 1024 | + | } | |
| 1025 | + | func (t *committedIdentityErrorTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 1026 | + | return testSendOutputSynchronous(t, o) | |
| 1027 | + | } | |
| 1028 | + | ||
| 1029 | + | func (t *countingOutputTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 1030 | + | func (t *countingOutputTransport) SendOutputAsync(o protocol.Output) error { | |
| 1031 | + | return testSendOutputAsync(t, o) | |
| 1032 | + | } | |
| 1033 | + | func (t *countingOutputTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 1034 | + | return testSendOutputSynchronous(t, o) | |
| 1035 | + | } | |
| 1036 | + | ||
| 1037 | + | func (t *parkedRouteExpiryTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 1038 | + | func (t *parkedRouteExpiryTransport) SendOutputAsync(o protocol.Output) error { | |
| 1039 | + | return testSendOutputAsync(t, o) | |
| 1040 | + | } | |
| 1041 | + | func (t *parkedRouteExpiryTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 1042 | + | return testSendOutputSynchronous(t, o) | |
| 1043 | + | } | |
| 1044 | + | ||
| 1045 | + | func (t *remotePickerSendErrorTransport) SendOutput(o protocol.Output) error { | |
| 1046 | + | return testSendOutput(t, o) | |
| 1047 | + | } | |
| 1048 | + | func (t *remotePickerSendErrorTransport) SendOutputAsync(o protocol.Output) error { | |
| 1049 | + | return testSendOutputAsync(t, o) | |
| 1050 | + | } | |
| 1051 | + | func (t *remotePickerSendErrorTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 1052 | + | return testSendOutputSynchronous(t, o) | |
| 1053 | + | } | |
| 1054 | + | ||
| 1055 | + | func (t failingOutputTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 1056 | + | func (t failingOutputTransport) SendOutputAsync(o protocol.Output) error { | |
| 1057 | + | return testSendOutputAsync(t, o) | |
| 1058 | + | } | |
| 1059 | + | func (t failingOutputTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 1060 | + | return testSendOutputSynchronous(t, o) | |
| 1061 | + | } | |
| 1062 | + | ||
| 1063 | + | func (t cacheFailTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 1064 | + | func (t cacheFailTransport) SendOutputAsync(o protocol.Output) error { | |
| 1065 | + | return testSendOutputAsync(t, o) | |
| 1066 | + | } | |
| 1067 | + | func (t cacheFailTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 1068 | + | return testSendOutputSynchronous(t, o) | |
| 1069 | + | } | |
| 1070 | + | ||
| 1071 | + | func (t parkedRouteFailTransport) SendOutput(o protocol.Output) error { return testSendOutput(t, o) } | |
| 1072 | + | func (t parkedRouteFailTransport) SendOutputAsync(o protocol.Output) error { | |
| 1073 | + | return testSendOutputAsync(t, o) | |
| 1074 | + | } | |
| 1075 | + | func (t parkedRouteFailTransport) SendOutputSynchronous(o protocol.Output) error { | |
| 1076 | + | return testSendOutputSynchronous(t, o) | |
| 1077 | + | } | |
M internal/usecase/daemon/wire.go +21 -27
| @@ -4,10 +4,9 @@ package daemon | |||
| 4 | 4 | import ( | |
| 5 | 5 | "github.com/bnema/vev/internal/domain" | |
| 6 | 6 | "github.com/bnema/vev/internal/protocol" | |
| 7 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 8 | 7 | ) | |
| 9 | 8 | ||
| 10 | - | func frameWelcome(s *session, resumeToken uint64) (wire.Frame, error) { | |
| 9 | + | func serverWelcome(s *session, resumeToken uint64) (protocol.Welcome, error) { | |
| 11 | 10 | s.mu.Lock() | |
| 12 | 11 | var identity *protocol.CommittedRouteIdentity | |
| 13 | 12 | if s.incarnation != (domain.SessionLifecycleID{}) { | |
| @@ -16,7 +15,7 @@ func frameWelcome(s *session, resumeToken uint64) (wire.Frame, error) { | |||
| 16 | 15 | Ephemeral: s.ephemeral, | |
| 17 | 16 | } | |
| 18 | 17 | } | |
| 19 | - | w := protocol.Welcome{ | |
| 18 | + | message := protocol.Welcome{ | |
| 20 | 19 | SessionID: string(s.id), | |
| 21 | 20 | SessionName: s.name, | |
| 22 | 21 | Ephemeral: s.ephemeral, | |
| @@ -24,37 +23,32 @@ func frameWelcome(s *session, resumeToken uint64) (wire.Frame, error) { | |||
| 24 | 23 | CommittedIdentity: identity, | |
| 25 | 24 | } | |
| 26 | 25 | s.mu.Unlock() | |
| 27 | - | w.ResumeToken = resumeToken | |
| 28 | - | payload := wire.MarshalWelcome(w) | |
| 29 | - | if payload == nil { | |
| 30 | - | return wire.Frame{}, protocol.ErrInvalidRouteWire | |
| 26 | + | message.ResumeToken = resumeToken | |
| 27 | + | if identity != nil { | |
| 28 | + | if err := identity.Validate(); err != nil { | |
| 29 | + | return protocol.Welcome{}, err | |
| 30 | + | } | |
| 31 | 31 | } | |
| 32 | - | return wire.Frame{Type: wire.MsgWelcome, Payload: payload}, nil | |
| 32 | + | return message, nil | |
| 33 | 33 | } | |
| 34 | 34 | ||
| 35 | - | func frameError(code uint16, text string) wire.Frame { | |
| 36 | - | return wire.Frame{Type: wire.MsgError, Payload: wire.MarshalErrorMsg(protocol.ErrorMsg{Code: code, Text: text})} | |
| 35 | + | func serverError(code uint16, text string) protocol.ErrorMsg { | |
| 36 | + | return protocol.ErrorMsg{Code: code, Text: text} | |
| 37 | 37 | } | |
| 38 | 38 | ||
| 39 | - | func frameOutputState(b []byte, baseState uint64, state uint64, echoAck uint64) (wire.Frame, error) { | |
| 40 | - | payload, err := wire.MarshalOutput(protocol.Output{ | |
| 39 | + | func serverOutputState(data []byte, baseState, state, echoAck uint64) (protocol.Output, error) { | |
| 40 | + | message := protocol.Output{ | |
| 41 | 41 | Epoch: 1, Base: baseState, New: state, Echo: echoAck, | |
| 42 | - | Size: domain.Size{Cols: 1, Rows: 1}, Full: state != 0 && baseState == 0, Data: b, | |
| 43 | - | }) | |
| 44 | - | if err != nil { | |
| 45 | - | return wire.Frame{}, err | |
| 42 | + | Size: domain.Size{Cols: 1, Rows: 1}, Full: state != 0 && baseState == 0, Data: data, | |
| 46 | 43 | } | |
| 47 | - | return wire.Frame{Type: wire.MsgOutput, Payload: payload}, nil | |
| 48 | - | } | |
| 49 | - | ||
| 50 | - | func frameDetached(reason uint8) wire.Frame { | |
| 51 | - | return wire.Frame{Type: wire.MsgDetached, Payload: wire.MarshalDetached(protocol.Detached{Reason: reason})} | |
| 52 | - | } | |
| 53 | - | ||
| 54 | - | func framePong() wire.Frame { | |
| 55 | - | return wire.Frame{Type: wire.MsgPong, Payload: wire.MarshalPong(protocol.Pong{})} | |
| 44 | + | if err := protocol.ValidateOutput(message); err != nil { | |
| 45 | + | return protocol.Output{}, err | |
| 46 | + | } | |
| 47 | + | return message, nil | |
| 56 | 48 | } | |
| 57 | 49 | ||
| 58 | - | func frameSessions(infos []protocol.SessionInfo) wire.Frame { | |
| 59 | - | return wire.Frame{Type: wire.MsgSessions, Payload: wire.MarshalSessions(protocol.Sessions{Sessions: infos})} | |
| 50 | + | func serverDetached(reason uint8) protocol.Detached { return protocol.Detached{Reason: reason} } | |
| 51 | + | func serverPong() protocol.Pong { return protocol.Pong{} } | |
| 52 | + | func serverSessions(infos []protocol.SessionInfo) protocol.Sessions { | |
| 53 | + | return protocol.Sessions{Sessions: infos} | |
| 60 | 54 | } | |
M internal/usecase/daemon/wire_metadata_test.go +2 -8
| @@ -4,8 +4,6 @@ import ( | |||
| 4 | 4 | "fmt" | |
| 5 | 5 | "sync" | |
| 6 | 6 | "testing" | |
| 7 | - | ||
| 8 | - | "github.com/bnema/vev/internal/protocol/wire" | |
| 9 | 7 | ) | |
| 10 | 8 | ||
| 11 | 9 | func TestFrameWelcomeSnapshotsSessionMetadataUnderLock(t *testing.T) { | |
| @@ -23,13 +21,9 @@ func TestFrameWelcomeSnapshotsSessionMetadataUnderLock(t *testing.T) { | |||
| 23 | 21 | }) | |
| 24 | 22 | ||
| 25 | 23 | for range 1000 { | |
| 26 | - | frame, frameErr := frameWelcome(sess, ac.resumeToken) | |
| 27 | - | if frameErr != nil { | |
| 28 | - | t.Fatalf("frameWelcome() error = %v", frameErr) | |
| 29 | - | } | |
| 30 | - | welcome, err := wire.UnmarshalWelcome(frame.Payload) | |
| 24 | + | welcome, err := serverWelcome(sess, ac.resumeToken) | |
| 31 | 25 | if err != nil { | |
| 32 | - | t.Fatalf("UnmarshalWelcome() error = %v", err) | |
| 26 | + | t.Fatalf("serverWelcome() error = %v", err) | |
| 33 | 27 | } | |
| 34 | 28 | if welcome.SessionID != "session" || welcome.SessionName == "" { | |
| 35 | 29 | t.Fatalf("welcome metadata = %+v", welcome) | |