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 | "github.com/bnema/vev/internal/adapters/ipc" | 24 | "github.com/bnema/vev/internal/adapters/ipc" | ||
| 25 | "github.com/bnema/vev/internal/adapters/lifecycle" | 25 | "github.com/bnema/vev/internal/adapters/lifecycle" | ||
| 26 | "github.com/bnema/vev/internal/adapters/pty" | 26 | "github.com/bnema/vev/internal/adapters/pty" | ||
| 27 | + | "github.com/bnema/vev/internal/adapters/sessionwire" | |||
| 27 | "github.com/bnema/vev/internal/adapters/snapshot" | 28 | "github.com/bnema/vev/internal/adapters/snapshot" | ||
| 28 | "github.com/bnema/vev/internal/domain" | 29 | "github.com/bnema/vev/internal/domain" | ||
| 29 | "github.com/bnema/vev/internal/persist" | 30 | "github.com/bnema/vev/internal/persist" | ||
| @@ -58,7 +59,7 @@ func startDaemonInDir(t *testing.T, dir string, opts ...daemon.Option) (string, | |||||
| 58 | d := daemon.New(pty.NewFactory(), clock.New(), discardLog(), opts...) | 59 | d := daemon.New(pty.NewFactory(), clock.New(), discardLog(), opts...) | ||
| 59 | ctx, cancel := context.WithCancel(context.Background()) | 60 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 60 | ch := make(chan error, 1) | 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 | t.Cleanup(cancel) | 63 | t.Cleanup(cancel) | ||
| 63 | return dir, ch | 64 | return dir, ch | ||
| 64 | } | 65 | } | ||
| @@ -611,7 +612,7 @@ func TestLifecycleOwnershipOutlivesMaintenanceWriter(t *testing.T) { | |||||
| 611 | if err != nil { | 612 | if err != nil { | ||
| 612 | return err | 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 | daemon.WithCatalogue(opened.Catalogue, opened.Records), | 747 | daemon.WithCatalogue(opened.Catalogue, opened.Records), | ||
| 747 | daemon.WithSnapshotRepository(blocking), | 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 | daemon.WithRecoveryCoordinator(coordinator), | 801 | daemon.WithRecoveryCoordinator(coordinator), | ||
| 801 | ) | 802 | ) | ||
| 802 | served := make(chan error, 1) | 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 | tr, _ := attach(t, runtimeDir, protocol.IntentNew, name, domain.Size{Cols: 80, Rows: 24}) | 806 | tr, _ := attach(t, runtimeDir, protocol.IntentNew, name, domain.Size{Cols: 80, Rows: 24}) | ||
| 806 | require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("checkpoint me\n")})})) | 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 | daemon.WithShell("/bin/cat", nil), | 884 | daemon.WithShell("/bin/cat", nil), | ||
| 884 | daemon.WithSnapshotRepository(repository), | 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 | oldCtx, stopOld := context.WithCancel(context.Background()) | 1075 | oldCtx, stopOld := context.WithCancel(context.Background()) | ||
| 1075 | oldDone := make(chan error, 1) | 1076 | oldDone := make(chan error, 1) | ||
| 1076 | go func() { | 1077 | go func() { | ||
| 1077 | - | serveErr := oldDaemon.Serve(oldCtx, controlledListener) | 1078 | + | serveErr := oldDaemon.Serve(oldCtx, sessionwire.NewServerListener(controlledListener)) |
| 1078 | teardownEvents <- "owner-release" | 1079 | teardownEvents <- "owner-release" | ||
| 1079 | close(ownerReleaseEntered) | 1080 | close(ownerReleaseEntered) | ||
| 1080 | <-allowOwnerRelease | 1081 | <-allowOwnerRelease | ||
M internal/app/run.go +2 -1
| @@ -37,6 +37,7 @@ import ( | |||||
| 37 | "github.com/bnema/vev/internal/adapters/observability" | 37 | "github.com/bnema/vev/internal/adapters/observability" | ||
| 38 | "github.com/bnema/vev/internal/adapters/pty" | 38 | "github.com/bnema/vev/internal/adapters/pty" | ||
| 39 | remoteadapter "github.com/bnema/vev/internal/adapters/remote" | 39 | remoteadapter "github.com/bnema/vev/internal/adapters/remote" | ||
| 40 | + | "github.com/bnema/vev/internal/adapters/sessionwire" | |||
| 40 | "github.com/bnema/vev/internal/adapters/shellcmd" | 41 | "github.com/bnema/vev/internal/adapters/shellcmd" | ||
| 41 | snapshotadapter "github.com/bnema/vev/internal/adapters/snapshot" | 42 | snapshotadapter "github.com/bnema/vev/internal/adapters/snapshot" | ||
| 42 | "github.com/bnema/vev/internal/adapters/sshstdio" | 43 | "github.com/bnema/vev/internal/adapters/sshstdio" | ||
| @@ -742,7 +743,7 @@ func runDaemonOwnedWithLogger(ctx context.Context, log *slog.Logger) (retErr err | |||||
| 742 | log.Warn("config watcher stopped", "path", configPath, "err", err) | 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 | log.Error("daemon exited", "err", err) | 747 | log.Error("daemon exited", "err", err) | ||
| 747 | return err | 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 | rebase bool | 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 | if entry == nil || entry.core() == nil || ac == nil || tr == nil { | 20 | if entry == nil || entry.core() == nil || ac == nil || tr == nil { | ||
| 21 | return attachmentCapability{} | 21 | return attachmentCapability{} | ||
| 22 | } | 22 | } | ||
| @@ -54,7 +54,7 @@ func captureAttachmentCapability(entry *session, ac *attachedClient, tr ports.Tr | |||||
| 54 | return token | 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 | return captureAttachmentCapability(s, ac, tr) | 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 | "sync/atomic" | 6 | "sync/atomic" | ||
| 7 | 7 | ||||
| 8 | "github.com/bnema/vev/internal/ports" | 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 | type attachmentEffectPhase uint8 | 12 | type attachmentEffectPhase uint8 | ||
| @@ -189,7 +189,7 @@ func (t *attachmentEffect) endTransportSend() { | |||||
| 189 | g.mu.Unlock() | 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 | if !t.current() || t.ac == nil || t.transport.transport == nil { | 193 | if !t.current() || t.ac == nil || t.transport.transport == nil { | ||
| 194 | return errAttachmentTransition | 194 | return errAttachmentTransition | ||
| 195 | } | 195 | } | ||
| @@ -198,7 +198,7 @@ func (t *attachmentEffect) sendControl(frame wire.Frame) error { | |||||
| 198 | if !t.current() || !t.ac.transportSnapshotCurrent(t.transport) || !t.beginTransportSend(t.transport) { | 198 | if !t.current() || !t.ac.transportSnapshotCurrent(t.transport) || !t.beginTransportSend(t.transport) { | ||
| 199 | return errAttachmentTransition | 199 | return errAttachmentTransition | ||
| 200 | } | 200 | } | ||
| 201 | - | err := t.transport.transport.Send(frame) | 201 | + | err := t.transport.transport.SendServer(message) |
| 202 | if err != nil { | 202 | if err != nil { | ||
| 203 | t.reportTransportFailure(t.transport) | 203 | t.reportTransportFailure(t.transport) | ||
| 204 | } | 204 | } | ||
| @@ -229,12 +229,12 @@ func (ac *attachedClient) beginAttachmentEffect(token attachmentCapability) (*at | |||||
| 229 | // admits the capability derived from the session registry. Handshakes use it | 229 | // admits the capability derived from the session registry. Handshakes use it | ||
| 230 | // after Welcome has completed so a replacement blocked behind that send can | 230 | // after Welcome has completed so a replacement blocked behind that send can | ||
| 231 | // publish before readiness or first paint. | 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 | //nolint:contextcheck // This compatibility wrapper deliberately delegates with a fresh context; cancellable callers use the context variant below. | 233 | //nolint:contextcheck // This compatibility wrapper deliberately delegates with a fresh context; cancellable callers use the context variant below. | ||
| 234 | return ac.beginCurrentAttachmentEffectContext(context.Background(), sess, tr) | 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 | if ctx == nil || ac == nil || sess == nil || tr == nil { | 238 | if ctx == nil || ac == nil || sess == nil || tr == nil { | ||
| 239 | return attachmentCapability{}, nil, false | 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 | go func() { | 127 | go func() { | ||
| 128 | defer close(done) | 128 | defer close(done) | ||
| 129 | defer ticket.End() | 129 | defer ticket.End() | ||
| 130 | - | _ = token.ac.sendExpectedTransportForAttachment(token.transport, framePong(), ticket) | 130 | + | _ = token.ac.sendExpectedTransportForAttachment(token.transport, serverPong(), ticket) |
| 131 | }() | 131 | }() | ||
| 132 | return done | 132 | return done | ||
| 133 | } | 133 | } | ||
M internal/usecase/daemon/attachment_output.go +20 -21
| @@ -10,7 +10,6 @@ import ( | |||||
| 10 | "github.com/bnema/vev/internal/domain" | 10 | "github.com/bnema/vev/internal/domain" | ||
| 11 | "github.com/bnema/vev/internal/ports" | 11 | "github.com/bnema/vev/internal/ports" | ||
| 12 | "github.com/bnema/vev/internal/protocol" | 12 | "github.com/bnema/vev/internal/protocol" | ||
| 13 | - | "github.com/bnema/vev/internal/protocol/wire" | |||
| 14 | ) | 13 | ) | ||
| 15 | 14 | ||||
| 16 | // attachmentOutput owns the terminal-output dependency chain and emitted | 15 | // attachmentOutput owns the terminal-output dependency chain and emitted | ||
| @@ -154,18 +153,18 @@ func (o *attachmentOutput) prepareFrame(d *Daemon, state *capturedRenderState, f | |||||
| 154 | return &preparedAttachmentOutput{output: o, ansi: ansi, graphics: preparedGraphics, cursor: cursor, data: data}, nil | 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 | if p == nil || p.ansi == nil { | 157 | if p == nil || p.ansi == nil { | ||
| 159 | return nil | 158 | return nil | ||
| 160 | } | 159 | } | ||
| 161 | - | sendFrame := send | 160 | + | sendOutput := send |
| 162 | if p.graphics != nil { | 161 | if p.graphics != nil { | ||
| 163 | - | sendFrame = func(frame wire.Frame) error { | 162 | + | sendOutput = func(output protocol.Output) error { |
| 164 | p.graphics.markSendAttempted() | 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 | p.abortGraphics() | 168 | p.abortGraphics() | ||
| 170 | return err | 169 | return err | ||
| 171 | } | 170 | } | ||
| @@ -286,7 +285,7 @@ func (s *attachmentOutput) prepare(frame renderer.Frame, damage []renderer.Damag | |||||
| 286 | }, nil | 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 | if p == nil || p.stream == nil || p.attempted { | 289 | if p == nil || p.stream == nil || p.attempted { | ||
| 291 | return nil | 290 | return nil | ||
| 292 | } | 291 | } | ||
| @@ -380,30 +379,30 @@ func (p *preparedOutput) currentLocked() bool { | |||||
| 380 | (p.stream.attachment == nil || p.stream.attachment.lifecycle.generationValue() == p.connectionGeneration) | 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) { | 382 | + | func marshalOutputState(data []byte, epoch, base, next, echoAck, viewRevision uint64, size domain.Size, full bool) (protocol.Output, error) { |
| 384 | - | payload, err := wire.MarshalOutput(protocol.Output{ | 383 | + | output := protocol.Output{ |
| 385 | Epoch: epoch, Base: base, New: next, Echo: echoAck, ViewRevision: viewRevision, | 384 | Epoch: epoch, Base: base, New: next, Echo: echoAck, ViewRevision: viewRevision, | ||
| 386 | Size: size, Full: full, Data: data, | 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 | 393 | + | // sideEffect builds output without advancing the state stream. When an |
| 395 | - | // an attachment is present, callers must hold its sendMu; attachment-bound | 394 | + | // attachment is present, callers must hold its sendMu; attachment-bound sends |
| 396 | - | // sends use sideEffectLocked after validating their transport incarnation. | 395 | + | // use sideEffectLocked after validating their connection incarnation. |
| 397 | - | func (s *attachmentOutput) sideEffect(data []byte, echoAck uint64) (wire.Frame, error) { | 396 | + | func (s *attachmentOutput) sideEffect(data []byte, echoAck uint64) (protocol.Output, error) { |
| 398 | s.lockView() | 397 | s.lockView() | ||
| 399 | defer s.unlockView() | 398 | defer s.unlockView() | ||
| 400 | return s.sideEffectLocked(data, echoAck) | 399 | return s.sideEffectLocked(data, echoAck) | ||
| 401 | } | 400 | } | ||
| 402 | 401 | ||||
| 403 | - | // sideEffectLocked requires the attachment view lock for both frame creation | 402 | + | // sideEffectLocked requires the attachment view lock for both output creation |
| 404 | - | // and transport emission. Keeping that lock across the send prevents a view | 403 | + | // and connection emission. Keeping that lock across the send prevents a view |
| 405 | - | // publication from overtaking a frame that was fenced just before it. | 404 | + | // publication from overtaking output fenced just before it. |
| 406 | - | func (s *attachmentOutput) sideEffectLocked(data []byte, echoAck uint64) (wire.Frame, error) { | 405 | + | func (s *attachmentOutput) sideEffectLocked(data []byte, echoAck uint64) (protocol.Output, error) { |
| 407 | _, epoch, viewRevision := s.fenceLocked() | 406 | _, epoch, viewRevision := s.fenceLocked() | ||
| 408 | size := domain.Size{Cols: 1, Rows: 1} | 407 | size := domain.Size{Cols: 1, Rows: 1} | ||
| 409 | if s != nil && s.attachment != nil { | 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 | release := make(chan struct{}) | 92 | release := make(chan struct{}) | ||
| 93 | sendDone := make(chan error, 1) | 93 | sendDone := make(chan error, 1) | ||
| 94 | go func() { | 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 | close(entered) | 96 | close(entered) | ||
| 97 | <-release | 97 | <-release | ||
| 98 | return nil | 98 | return nil | ||
| 99 | - | }) | 99 | + | })) |
| 100 | }() | 100 | }() | ||
| 101 | <-entered | 101 | <-entered | ||
| 102 | 102 | ||||
| @@ -134,10 +134,10 @@ func TestAttachmentOutputEpochsAreAttachmentLocalAndPreparedFramesAreFenced(t *t | |||||
| 134 | firstPrepared, err := first.prepare(frame, nil, true) | 134 | firstPrepared, err := first.prepare(frame, nil, true) | ||
| 135 | require.NoError(t, err) | 135 | require.NoError(t, err) | ||
| 136 | var firstFrame wire.Frame | 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 | firstFrame = frame | 138 | firstFrame = frame | ||
| 139 | return nil | 139 | return nil | ||
| 140 | - | })) | 140 | + | }))) |
| 141 | firstOutput, err := wire.UnmarshalOutput(firstFrame.Payload) | 141 | firstOutput, err := wire.UnmarshalOutput(firstFrame.Payload) | ||
| 142 | require.NoError(t, err) | 142 | require.NoError(t, err) | ||
| 143 | require.Equal(t, uint64(1), firstOutput.Epoch) | 143 | require.Equal(t, uint64(1), firstOutput.Epoch) | ||
| @@ -147,7 +147,7 @@ func TestAttachmentOutputEpochsAreAttachmentLocalAndPreparedFramesAreFenced(t *t | |||||
| 147 | 147 | ||||
| 148 | secondPrepared, err := second.prepare(frame, nil, true) | 148 | secondPrepared, err := second.prepare(frame, nil, true) | ||
| 149 | require.NoError(t, err) | 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 | require.Equal(t, uint64(1), second.epoch) | 151 | require.Equal(t, uint64(1), second.epoch) | ||
| 152 | require.Equal(t, uint64(1), second.next) | 152 | require.Equal(t, uint64(1), second.next) | ||
| 153 | require.Equal(t, uint64(1), first.next) | 153 | require.Equal(t, uint64(1), first.next) | ||
| @@ -161,10 +161,10 @@ func TestAttachmentOutputEpochsAreAttachmentLocalAndPreparedFramesAreFenced(t *t | |||||
| 161 | require.Zero(t, first.next) | 161 | require.Zero(t, first.next) | ||
| 162 | require.Zero(t, first.acked) | 162 | require.Zero(t, first.acked) | ||
| 163 | var staleSent bool | 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 | staleSent = true | 165 | staleSent = true | ||
| 166 | return nil | 166 | return nil | ||
| 167 | - | })) | 167 | + | }))) |
| 168 | require.False(t, staleSent) | 168 | require.False(t, staleSent) | ||
| 169 | require.Zero(t, first.next) | 169 | require.Zero(t, first.next) | ||
| 170 | first.ack(1, 1) | 170 | first.ack(1, 1) | ||
| @@ -173,10 +173,10 @@ func TestAttachmentOutputEpochsAreAttachmentLocalAndPreparedFramesAreFenced(t *t | |||||
| 173 | reset, err := first.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false) | 173 | reset, err := first.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false) | ||
| 174 | require.NoError(t, err) | 174 | require.NoError(t, err) | ||
| 175 | var resetFrame wire.Frame | 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 | resetFrame = frame | 177 | resetFrame = frame | ||
| 178 | return nil | 178 | return nil | ||
| 179 | - | })) | 179 | + | }))) |
| 180 | resetOutput, err := wire.UnmarshalOutput(resetFrame.Payload) | 180 | resetOutput, err := wire.UnmarshalOutput(resetFrame.Payload) | ||
| 181 | require.NoError(t, err) | 181 | require.NoError(t, err) | ||
| 182 | require.Equal(t, uint64(2), resetOutput.Epoch) | 182 | require.Equal(t, uint64(2), resetOutput.Epoch) | ||
| @@ -205,23 +205,23 @@ func TestPreparedOutputDropsReplacedConnectionAndView(t *testing.T) { | |||||
| 205 | fillOutputStateRows(frame, []string{"abc"}) | 205 | fillOutputStateRows(frame, []string{"abc"}) | ||
| 206 | initial, err := stream.prepare(frame, nil, true) | 206 | initial, err := stream.prepare(frame, nil, true) | ||
| 207 | require.NoError(t, err) | 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 | changed := frame.Clone() | 209 | changed := frame.Clone() | ||
| 210 | changed.Set(0, 0, renderer.Cell{Rune: 'x', Style: renderer.DefaultStyle()}) | 210 | changed.Set(0, 0, renderer.Cell{Rune: 'x', Style: renderer.DefaultStyle()}) | ||
| 211 | pending, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false) | 211 | pending, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false) | ||
| 212 | require.NoError(t, err) | 212 | require.NoError(t, err) | ||
| 213 | tt.change(ac) | 213 | tt.change(ac) | ||
| 214 | var sent bool | 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 | sent = true | 216 | sent = true | ||
| 217 | return nil | 217 | return nil | ||
| 218 | - | })) | 218 | + | }))) |
| 219 | require.False(t, sent) | 219 | require.False(t, sent) | ||
| 220 | require.Equal(t, uint64(1), stream.next) | 220 | require.Equal(t, uint64(1), stream.next) | ||
| 221 | 221 | ||||
| 222 | retry, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false) | 222 | retry, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false) | ||
| 223 | require.NoError(t, err) | 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 | require.Equal(t, uint64(2), stream.next) | 225 | require.Equal(t, uint64(2), stream.next) | ||
| 226 | }) | 226 | }) | ||
| 227 | } | 227 | } | ||
| @@ -242,9 +242,7 @@ func TestOutputStateSideEffectsDoNotAdvanceEpochStateOrACK(t *testing.T) { | |||||
| 242 | stream := newOutputStateStream() | 242 | stream := newOutputStateStream() | ||
| 243 | stream.ack(1, 0) | 243 | stream.ack(1, 0) | ||
| 244 | beforeEpoch, beforeNext, beforeAck := stream.epoch, stream.next, stream.acked | 244 | beforeEpoch, beforeNext, beforeAck := stream.epoch, stream.next, stream.acked | ||
| 245 | - | frame, err := stream.sideEffect([]byte("pty"), 9) | 245 | + | out, err := stream.sideEffect([]byte("pty"), 9) |
| 246 | - | require.NoError(t, err) | |||
| 247 | - | out, err := wire.UnmarshalOutput(frame.Payload) | |||
| 248 | require.NoError(t, err) | 246 | require.NoError(t, err) | ||
| 249 | require.Equal(t, beforeEpoch, out.Epoch) | 247 | require.Equal(t, beforeEpoch, out.Epoch) | ||
| 250 | require.Zero(t, out.Base) | 248 | require.Zero(t, out.Base) | ||
| @@ -261,7 +259,7 @@ func TestAttachmentOutputFailedSendRetriesSnapshotWithoutAdvancing(t *testing.T) | |||||
| 261 | fillOutputStateRows(initial, []string{"abc"}) | 259 | fillOutputStateRows(initial, []string{"abc"}) | ||
| 262 | first, err := stream.prepare(initial, nil, true) | 260 | first, err := stream.prepare(initial, nil, true) | ||
| 263 | require.NoError(t, err) | 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 | require.Equal(t, uint64(1), stream.next) | 263 | require.Equal(t, uint64(1), stream.next) | ||
| 266 | 264 | ||||
| 267 | changed := initial.Clone() | 265 | changed := initial.Clone() | ||
| @@ -273,7 +271,7 @@ func TestAttachmentOutputFailedSendRetriesSnapshotWithoutAdvancing(t *testing.T) | |||||
| 273 | require.Empty(t, probe.Bytes(), "preparation must not advance the renderer shadow") | 271 | require.Empty(t, probe.Bytes(), "preparation must not advance the renderer shadow") | ||
| 274 | 272 | ||||
| 275 | sendErr := errors.New("send failed") | 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 | require.Equal(t, uint64(1), stream.next, "failed send must not advance the state chain") | 275 | require.Equal(t, uint64(1), stream.next, "failed send must not advance the state chain") | ||
| 278 | pending.commitNoSend() | 276 | pending.commitNoSend() | ||
| 279 | probe, err = stream.renderer.Prepare(initial, nil, false) | 277 | probe, err = stream.renderer.Prepare(initial, nil, false) | ||
| @@ -283,19 +281,19 @@ func TestAttachmentOutputFailedSendRetriesSnapshotWithoutAdvancing(t *testing.T) | |||||
| 283 | retry, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false) | 281 | retry, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false) | ||
| 284 | require.NoError(t, err) | 282 | require.NoError(t, err) | ||
| 285 | var sent wire.Frame | 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 | sent = frame | 285 | sent = frame | ||
| 288 | return nil | 286 | return nil | ||
| 289 | - | })) | 287 | + | }))) |
| 290 | out, err := wire.UnmarshalOutput(sent.Payload) | 288 | out, err := wire.UnmarshalOutput(sent.Payload) | ||
| 291 | require.NoError(t, err) | 289 | require.NoError(t, err) | ||
| 292 | require.Zero(t, out.Base, "retry after ambiguous send failure must be dependency-free") | 290 | require.Zero(t, out.Base, "retry after ambiguous send failure must be dependency-free") | ||
| 293 | require.Equal(t, uint64(2), out.New, "state numbers remain monotonic across rebases") | 291 | require.Equal(t, uint64(2), out.New, "state numbers remain monotonic across rebases") | ||
| 294 | require.Equal(t, uint64(2), stream.next, "successful retry advances the chain exactly once") | 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 | t.Fatal("completed output sent twice") | 294 | t.Fatal("completed output sent twice") | ||
| 297 | return nil | 295 | return nil | ||
| 298 | - | })) | 296 | + | }))) |
| 299 | require.Equal(t, uint64(2), stream.next) | 297 | require.Equal(t, uint64(2), stream.next) | ||
| 300 | probe, err = stream.renderer.Prepare(changed, nil, false) | 298 | probe, err = stream.renderer.Prepare(changed, nil, false) | ||
| 301 | require.NoError(t, err) | 299 | require.NoError(t, err) | ||
| @@ -308,7 +306,7 @@ func TestAttachmentOutputNoByteCommitAdvancesShadowWithoutState(t *testing.T) { | |||||
| 308 | fillOutputStateRows(frame, []string{"abc"}) | 306 | fillOutputStateRows(frame, []string{"abc"}) | ||
| 309 | initial, err := stream.prepare(frame, nil, true) | 307 | initial, err := stream.prepare(frame, nil, true) | ||
| 310 | require.NoError(t, err) | 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 | noOp, err := stream.prepare(frame, nil, false) | 311 | noOp, err := stream.prepare(frame, nil, false) | ||
| 314 | require.NoError(t, err) | 312 | require.NoError(t, err) | ||
| @@ -378,7 +376,11 @@ func outputStateFrame(stream *attachmentOutput, data []byte, reset bool, echoAck | |||||
| 378 | if reset { | 376 | if reset { | ||
| 379 | base = 0 | 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 | if err != nil { | 384 | if err != nil { | ||
| 383 | panic(err) | 385 | panic(err) | ||
| 384 | } | 386 | } | ||
| @@ -395,10 +397,10 @@ func drawOutputState(t *testing.T, stream *attachmentOutput, frame renderer.Fram | |||||
| 395 | return wire.Frame{}, false, err | 397 | return wire.Frame{}, false, err | ||
| 396 | } | 398 | } | ||
| 397 | var output wire.Frame | 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 | output = frame | 401 | output = frame | ||
| 400 | return nil | 402 | return nil | ||
| 401 | - | }) | 403 | + | })) |
| 402 | if err != nil { | 404 | if err != nil { | ||
| 403 | return wire.Frame{}, false, err | 405 | return wire.Frame{}, false, err | ||
| 404 | } | 406 | } | ||
| @@ -456,7 +458,7 @@ func TestAttachmentOutputFailedSendKeepsTextCursorAndGraphicsSpeculative(t *test | |||||
| 456 | require.NoError(t, err) | 458 | require.NoError(t, err) | ||
| 457 | 459 | ||||
| 458 | sendErr := errors.New("send failed") | 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 | require.Equal(t, cursorOut{valid: true, row: 1, col: 1}, output.lastCursor) | 463 | require.Equal(t, cursorOut{valid: true, row: 1, col: 1}, output.lastCursor) | ||
| 462 | require.Empty(t, graphicsState.assets) | 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 | d.clientGone(sess, ac, oldTransport, false) | 142 | d.clientGone(sess, ac, oldTransport, false) | ||
| 143 | 143 | ||||
| 144 | resumedTransport := &closeTrackingTransport{} | 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 | newToken := ac.resumeToken | 146 | newToken := ac.resumeToken | ||
| 147 | require.NotEqual(t, oldToken, newToken) | 147 | require.NotEqual(t, oldToken, newToken) | ||
| 148 | d.mu.Lock() | 148 | d.mu.Lock() | ||
| @@ -166,7 +166,7 @@ func TestFailedResumeHandshakeKeepsParkedCredential(t *testing.T) { | |||||
| 166 | d.clientGone(sess, ac, oldTransport, false) | 166 | d.clientGone(sess, ac, oldTransport, false) | ||
| 167 | 167 | ||||
| 168 | failed := &resumeWelcomeFailureTransport{} | 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 | require.Nil(t, ac.transport()) | 170 | require.Nil(t, ac.transport()) | ||
| 171 | d.mu.Lock() | 171 | d.mu.Lock() | ||
| 172 | parked := d.parked[token] | 172 | parked := d.parked[token] | ||
M internal/usecase/daemon/attachment_registry_test.go +2 -7
| @@ -5,7 +5,6 @@ import ( | |||||
| 5 | "testing" | 5 | "testing" | ||
| 6 | 6 | ||||
| 7 | "github.com/bnema/vev/internal/domain" | 7 | "github.com/bnema/vev/internal/domain" | ||
| 8 | - | "github.com/bnema/vev/internal/protocol/wire" | |||
| 9 | "github.com/stretchr/testify/require" | 8 | "github.com/stretchr/testify/require" | ||
| 10 | ) | 9 | ) | ||
| 11 | 10 | ||||
| @@ -97,9 +96,7 @@ func TestTabForAttachmentRepairRebasesOutputBeforePublishingRevision(t *testing. | |||||
| 97 | after := ac.viewSnapshot() | 96 | after := ac.viewSnapshot() | ||
| 98 | require.Greater(t, after.revision, before.revision) | 97 | require.Greater(t, after.revision, before.revision) | ||
| 99 | require.Greater(t, ac.output.currentEpoch(), beforeEpoch) | 98 | require.Greater(t, ac.output.currentEpoch(), beforeEpoch) | ||
| 100 | - | frame, err := ac.output.sideEffect([]byte("x"), 0) | 99 | + | output, err := ac.output.sideEffect([]byte("x"), 0) |
| 101 | - | require.NoError(t, err) | |||
| 102 | - | output, err := wire.UnmarshalOutput(frame.Payload) | |||
| 103 | require.NoError(t, err) | 100 | require.NoError(t, err) | ||
| 104 | require.Equal(t, after.revision, output.ViewRevision) | 101 | require.Equal(t, after.revision, output.ViewRevision) | ||
| 105 | require.Equal(t, ac.output.currentEpoch(), output.Epoch) | 102 | require.Equal(t, ac.output.currentEpoch(), output.Epoch) | ||
| @@ -127,9 +124,7 @@ func TestPrepareRemovedTabViewRebasesOutputBeforeSideEffect(t *testing.T) { | |||||
| 127 | require.Equal(t, before.revision+1, after.revision) | 124 | require.Equal(t, before.revision+1, after.revision) | ||
| 128 | require.Greater(t, ac.output.currentEpoch(), beforeEpoch) | 125 | require.Greater(t, ac.output.currentEpoch(), beforeEpoch) | ||
| 129 | 126 | ||||
| 130 | - | frame, err := ac.output.sideEffect([]byte("replacement"), 0) | 127 | + | output, err := ac.output.sideEffect([]byte("replacement"), 0) |
| 131 | - | require.NoError(t, err) | |||
| 132 | - | output, err := wire.UnmarshalOutput(frame.Payload) | |||
| 133 | require.NoError(t, err) | 128 | require.NoError(t, err) | ||
| 134 | require.Equal(t, after.revision, output.ViewRevision) | 129 | require.Equal(t, after.revision, output.ViewRevision) | ||
| 135 | require.NotEqual(t, beforeEpoch, output.Epoch) | 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 | secondLease := rc.attachmentLease(second) | 283 | secondLease := rc.attachmentLease(second) | ||
| 284 | require.True(t, rc.markAttachmentReady(firstLease)) | 284 | require.True(t, rc.markAttachmentReady(firstLease)) | ||
| 285 | require.True(t, rc.markAttachmentReady(secondLease)) | 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 | token := sess.captureAttachmentCapability(ac, tr) | 287 | token := sess.captureAttachmentCapability(ac, tr) | ||
| 288 | d.handleAttachmentClientFrame(token, wire.Frame{ | 288 | d.handleAttachmentClientFrame(token, wire.Frame{ | ||
| 289 | Type: wire.MsgResize, | 289 | Type: wire.MsgResize, | ||
M internal/usecase/daemon/attention_test.go +1 -2
| @@ -13,7 +13,6 @@ import ( | |||||
| 13 | renderer "github.com/bnema/vev-vt" | 13 | renderer "github.com/bnema/vev-vt" | ||
| 14 | "github.com/bnema/vev/internal/domain" | 14 | "github.com/bnema/vev/internal/domain" | ||
| 15 | "github.com/bnema/vev/internal/ports" | 15 | "github.com/bnema/vev/internal/ports" | ||
| 16 | - | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |||
| 17 | "github.com/bnema/vev/internal/protocol/wire" | 16 | "github.com/bnema/vev/internal/protocol/wire" | ||
| 18 | "github.com/bnema/vev/internal/usecase/keys" | 17 | "github.com/bnema/vev/internal/usecase/keys" | ||
| 19 | "github.com/bnema/vev/internal/usecase/layout" | 18 | "github.com/bnema/vev/internal/usecase/layout" | ||
| @@ -164,7 +163,7 @@ func TestNoteAttentionDoesNotBlockOnWedgedOtherClient(t *testing.T) { | |||||
| 164 | defer releases[1]() | 163 | defer releases[1]() | ||
| 165 | selectTestAttachmentTab(sess, 0) | 164 | selectTestAttachmentTab(sess, 0) | ||
| 166 | 165 | ||||
| 167 | - | trW := portsmocks.NewMockTransport(t) | 166 | + | trW := newMockServerConnection(t) |
| 168 | block := make(chan struct{}) | 167 | block := make(chan struct{}) | ||
| 169 | t.Cleanup(func() { close(block) }) | 168 | t.Cleanup(func() { close(block) }) | ||
| 170 | trW.EXPECT().Send(mock.Anything).RunAndReturn(func(wire.Frame) error { | 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 | "github.com/bnema/vev/internal/domain" | 14 | "github.com/bnema/vev/internal/domain" | ||
| 15 | "github.com/bnema/vev/internal/ports" | 15 | "github.com/bnema/vev/internal/ports" | ||
| 16 | "github.com/bnema/vev/internal/protocol" | 16 | "github.com/bnema/vev/internal/protocol" | ||
| 17 | - | "github.com/bnema/vev/internal/protocol/wire" | |||
| 18 | "github.com/bnema/vev/internal/usecase/keys" | 17 | "github.com/bnema/vev/internal/usecase/keys" | ||
| 19 | "github.com/bnema/vev/internal/usecase/mouse" | 18 | "github.com/bnema/vev/internal/usecase/mouse" | ||
| 20 | themeui "github.com/bnema/vev/internal/usecase/theme" | 19 | themeui "github.com/bnema/vev/internal/usecase/theme" | ||
| 21 | ) | 20 | ) | ||
| 22 | 21 | ||||
| 23 | type transportSnapshot struct { | 22 | type transportSnapshot struct { | ||
| 24 | - | transport ports.Transport | 23 | + | transport ports.ServerConnection |
| 25 | incarnation uint64 | 24 | incarnation uint64 | ||
| 26 | } | 25 | } | ||
| 27 | 26 | ||||
| @@ -35,7 +34,7 @@ type appliedTheme struct { | |||||
| 35 | } | 34 | } | ||
| 36 | 35 | ||||
| 37 | type attachedClient struct { | 36 | type attachedClient struct { | ||
| 38 | - | tr ports.Transport | 37 | + | tr ports.ServerConnection |
| 39 | transportIncarnation uint64 | 38 | transportIncarnation uint64 | ||
| 40 | output *attachmentOutput | 39 | output *attachmentOutput | ||
| 41 | overlays *overlayRuntime | 40 | overlays *overlayRuntime | ||
| @@ -274,13 +273,13 @@ func (ac *attachedClient) setClientTheme(t themeui.Theme) { | |||||
| 274 | ac.themeMu.Unlock() | 273 | ac.themeMu.Unlock() | ||
| 275 | } | 274 | } | ||
| 276 | 275 | ||||
| 277 | - | func (ac *attachedClient) transport() ports.Transport { | 276 | + | func (ac *attachedClient) transport() ports.ServerConnection { |
| 278 | ac.linkMu.Lock() | 277 | ac.linkMu.Lock() | ||
| 279 | defer ac.linkMu.Unlock() | 278 | defer ac.linkMu.Unlock() | ||
| 280 | return ac.tr | 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 | ac.linkMu.Lock() | 283 | ac.linkMu.Lock() | ||
| 285 | ac.tr = tr | 284 | ac.tr = tr | ||
| 286 | ac.transportIncarnation++ | 285 | ac.transportIncarnation++ | ||
| @@ -290,7 +289,7 @@ func (ac *attachedClient) replaceTransport(tr ports.Transport) { | |||||
| 290 | // revokeTransport removes tr only if it is still this attachment's current | 289 | // revokeTransport removes tr only if it is still this attachment's current | ||
| 291 | // link. It returns the revoked transport, so asynchronous retirement can | 290 | // link. It returns the revoked transport, so asynchronous retirement can | ||
| 292 | // close exactly that link without touching a later connection incarnation. | 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 | if tr == nil { | 293 | if tr == nil { | ||
| 295 | return nil | 294 | return nil | ||
| 296 | } | 295 | } | ||
| @@ -319,20 +318,20 @@ func (ac *attachedClient) transportSnapshotCurrent(expected transportSnapshot) b | |||||
| 319 | return ac.tr == expected.transport && ac.transportIncarnation == expected.incarnation | 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 | if tr == nil { | 322 | if tr == nil { | ||
| 324 | return nil | 323 | return nil | ||
| 325 | } | 324 | } | ||
| 326 | return tr.Close() | 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 | ac.linkMu.Lock() | 329 | ac.linkMu.Lock() | ||
| 331 | defer ac.linkMu.Unlock() | 330 | defer ac.linkMu.Unlock() | ||
| 332 | return ac.tr == tr | 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 | return tr != nil && ac.transportIs(tr) | 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 | // sendExpectedTransport writes only when expected is still the attachment's | 410 | // sendExpectedTransport writes only when expected is still the attachment's | ||
| 412 | // current transport incarnation. It preserves sendMu -> linkMu lock ordering. | 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 | ac.sendMu.Lock() | 413 | ac.sendMu.Lock() | ||
| 415 | defer ac.sendMu.Unlock() | 414 | defer ac.sendMu.Unlock() | ||
| 416 | if !ac.transportSnapshotCurrent(expected) { | 415 | if !ac.transportSnapshotCurrent(expected) { | ||
| @@ -419,7 +418,7 @@ func (ac *attachedClient) sendExpectedTransport(expected transportSnapshot, f wi | |||||
| 419 | if expected.transport == nil { | 418 | if expected.transport == nil { | ||
| 420 | return errors.New("client transport is nil") | 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 | // beginExpectedTransportSendLocked validates that expected is still the | 424 | // beginExpectedTransportSendLocked validates that expected is still the | ||
| @@ -436,7 +435,7 @@ func (ac *attachedClient) beginExpectedTransportSendLocked(expected transportSna | |||||
| 436 | return nil | 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 | ac.sendMu.Lock() | 439 | ac.sendMu.Lock() | ||
| 441 | defer ac.sendMu.Unlock() | 440 | defer ac.sendMu.Unlock() | ||
| 442 | // A attachment-bound send requires a live ticket, and reports every rejection, | 441 | // A attachment-bound send requires a live ticket, and reports every rejection, | ||
| @@ -447,7 +446,7 @@ func (ac *attachedClient) sendExpectedTransportForAttachment(expected transportS | |||||
| 447 | if err := ac.beginExpectedTransportSendLocked(expected, ticket); err != nil { | 446 | if err := ac.beginExpectedTransportSendLocked(expected, ticket); err != nil { | ||
| 448 | return errAttachmentTransition | 447 | return errAttachmentTransition | ||
| 449 | } | 448 | } | ||
| 450 | - | err := expected.transport.Send(f) | 449 | + | err := expected.transport.SendServer(message) |
| 451 | if err != nil { | 450 | if err != nil { | ||
| 452 | ticket.reportTransportFailure(expected) | 451 | ticket.reportTransportFailure(expected) | ||
| 453 | } | 452 | } | ||
| @@ -460,17 +459,17 @@ func (ac *attachedClient) sendExpectedTransportForAttachment(expected transportS | |||||
| 460 | // detachNotifyTimeout, the transport is force-closed, failing the in-flight | 459 | // detachNotifyTimeout, the transport is force-closed, failing the in-flight | ||
| 461 | // write. Detach/kill/shutdown paths use this so they are never gated on a | 460 | // write. Detach/kill/shutdown paths use this so they are never gated on a | ||
| 462 | // client that has stopped draining its socket. | 461 | // client that has stopped draining its socket. | ||
| 463 | - | func (d *Daemon) boundedSend(ac *attachedClient, f wire.Frame) { | 462 | + | func (d *Daemon) boundedSend(ac *attachedClient, message protocol.ServerMessage) { |
| 464 | - | _ = d.boundedSendErr(ac, f) | 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 | expected := ac.transportSnapshot() | 467 | expected := ac.transportSnapshot() | ||
| 469 | if expected.transport == nil { | 468 | if expected.transport == nil { | ||
| 470 | return errors.New("client transport is nil") | 469 | return errors.New("client transport is nil") | ||
| 471 | } | 470 | } | ||
| 472 | tr, err := d.boundedSendWith(expected.transport, func() error { | 471 | tr, err := d.boundedSendWith(expected.transport, func() error { | ||
| 473 | - | return ac.sendExpectedTransport(expected, f) | 472 | + | return ac.sendExpectedTransport(expected, message) |
| 474 | }) | 473 | }) | ||
| 475 | if err != nil && errors.Is(err, errSendTimedOut) { | 474 | if err != nil && errors.Is(err, errSendTimedOut) { | ||
| 476 | _ = ac.closeCapturedTransport(tr) | 475 | _ = ac.closeCapturedTransport(tr) | ||
| @@ -483,12 +482,12 @@ func (d *Daemon) boundedSendOutputErr(ac *attachedClient, b []byte) error { | |||||
| 483 | return err | 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 | expected := ac.transportSnapshot() | 486 | expected := ac.transportSnapshot() | ||
| 488 | if expected.transport == nil { | 487 | if expected.transport == nil { | ||
| 489 | return nil, errors.New("client transport is nil") | 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 | ac.sendMu.Lock() | 491 | ac.sendMu.Lock() | ||
| 493 | defer ac.sendMu.Unlock() | 492 | defer ac.sendMu.Unlock() | ||
| 494 | if !ac.transportSnapshotCurrent(expected) { | 493 | if !ac.transportSnapshotCurrent(expected) { | ||
| @@ -499,9 +498,9 @@ func (d *Daemon) boundedSendOutputErrTransport(ac *attachedClient, b []byte) (po | |||||
| 499 | } | 498 | } | ||
| 500 | ac.output.lockView() | 499 | ac.output.lockView() | ||
| 501 | defer ac.output.unlockView() | 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 | if err == nil { | 502 | if err == nil { | ||
| 504 | - | err = owned.SendSynchronous(frame) | 503 | + | err = expected.transport.SendOutputSynchronous(output) |
| 505 | } | 504 | } | ||
| 506 | return expected.transport, err | 505 | return expected.transport, err | ||
| 507 | } | 506 | } | ||
| @@ -516,21 +515,21 @@ func (d *Daemon) boundedSendOutputErrTransport(ac *attachedClient, b []byte) (po | |||||
| 516 | } | 515 | } | ||
| 517 | ac.output.lockView() | 516 | ac.output.lockView() | ||
| 518 | defer ac.output.unlockView() | 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 | if err != nil { | 519 | if err != nil { | ||
| 521 | return err | 520 | return err | ||
| 522 | } | 521 | } | ||
| 523 | - | return expected.transport.Send(frame) | 522 | + | return expected.transport.SendOutput(output) |
| 524 | }) | 523 | }) | ||
| 525 | } | 524 | } | ||
| 526 | 525 | ||||
| 527 | var errSendTimedOut = errors.New("send timed out") | 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 | return d.boundedSendWithTimeout(detachNotifyTimeout, tr, send) | 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 | timer := d.clock.NewTimer(timeout) | 533 | timer := d.clock.NewTimer(timeout) | ||
| 535 | result := make(chan error, 1) | 534 | result := make(chan error, 1) | ||
| 536 | go func() { | 535 | go func() { | ||
| @@ -581,7 +580,7 @@ func (d *Daemon) notifyDetachedSnapshotAsync(snapshot detachedAttachmentSnapshot | |||||
| 581 | go func() { | 580 | go func() { | ||
| 582 | defer close(done) | 581 | defer close(done) | ||
| 583 | _, _ = d.boundedSendWith(snapshot.transport.transport, func() error { | 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 | revoked := snapshot.ac.revokeTransport(snapshot.transport.transport) | 585 | revoked := snapshot.ac.revokeTransport(snapshot.transport.transport) | ||
| 587 | if revoked == nil { | 586 | if revoked == nil { | ||
| @@ -619,7 +618,7 @@ type attachClientOptions struct { | |||||
| 619 | startupOverlay protocol.StartupOverlay | 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 | d.mu.Lock() | 622 | d.mu.Lock() | ||
| 624 | ac := d.prepareAttachedClientLocked(sess, tr, domain.Geometry{Size: sz}, opts) | 623 | ac := d.prepareAttachedClientLocked(sess, tr, domain.Geometry{Size: sz}, opts) | ||
| 625 | d.mu.Unlock() | 624 | d.mu.Unlock() | ||
| @@ -664,7 +663,7 @@ func (d *Daemon) finishAttachedClient(sess *session, ac *attachedClient, opts at | |||||
| 664 | // prepareAttachedClientLocked allocates one detached attachment. Caller holds | 663 | // prepareAttachedClientLocked allocates one detached attachment. Caller holds | ||
| 665 | // d.mu only to allocate its resume token; attachment publication happens later after | 664 | // d.mu only to allocate its resume token; attachment publication happens later after | ||
| 666 | // the caller releases every architecture lock. | 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 | resumeToken := uint64(0) | 667 | resumeToken := uint64(0) | ||
| 669 | if opts.resumeCapable { | 668 | if opts.resumeCapable { | ||
| 670 | resumeToken = d.nextResumeTokenLocked() | 669 | resumeToken = d.nextResumeTokenLocked() | ||
M internal/usecase/daemon/client_frame_routing.go +48 -91
| @@ -7,7 +7,6 @@ import ( | |||||
| 7 | "github.com/bnema/vev/internal/domain" | 7 | "github.com/bnema/vev/internal/domain" | ||
| 8 | "github.com/bnema/vev/internal/ports" | 8 | "github.com/bnema/vev/internal/ports" | ||
| 9 | "github.com/bnema/vev/internal/protocol" | 9 | "github.com/bnema/vev/internal/protocol" | ||
| 10 | - | "github.com/bnema/vev/internal/protocol/wire" | |||
| 11 | "github.com/bnema/vev/internal/usecase/command" | 10 | "github.com/bnema/vev/internal/usecase/command" | ||
| 12 | ) | 11 | ) | ||
| 13 | 12 | ||||
| @@ -16,7 +15,7 @@ const connectionSnapshotAttempts = 8 | |||||
| 16 | // currentAttachmentConnection retries the lock-free session-to-role snapshot when a | 15 | // currentAttachmentConnection retries the lock-free session-to-role snapshot when a | ||
| 17 | // handoff lands between those reads. The attachment gate still performs the final | 16 | // handoff lands between those reads. The attachment gate still performs the final | ||
| 18 | // mutation admission after this function returns. | 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 | for range connectionSnapshotAttempts { | 19 | for range connectionSnapshotAttempts { | ||
| 21 | if !ac.currentTransportIs(tr) { | 20 | if !ac.currentTransportIs(tr) { | ||
| 22 | break | 21 | break | ||
| @@ -50,8 +49,12 @@ func (d *Daemon) runConnLoop(ac *attachedClient) { | |||||
| 50 | if !ac.currentTransportIs(tr) || ac.currentAttachmentSession() == nil { | 49 | if !ac.currentTransportIs(tr) || ac.currentAttachmentSession() == nil { | ||
| 51 | return | 50 | return | ||
| 52 | } | 51 | } | ||
| 53 | - | f, err := tr.Recv() | 52 | + | message, err := tr.ReceiveClient() |
| 54 | if err != nil { | 53 | if err != nil { | ||
| 54 | + | var failure *protocol.DecodeFailure | |||
| 55 | + | if errors.As(err, &failure) { | |||
| 56 | + | continue | |||
| 57 | + | } | |||
| 55 | for range connectionSnapshotAttempts { | 58 | for range connectionSnapshotAttempts { | ||
| 56 | sess, _, ok := d.currentAttachmentConnection(ac, tr) | 59 | sess, _, ok := d.currentAttachmentConnection(ac, tr) | ||
| 57 | if !ok { | 60 | if !ok { | ||
| @@ -69,14 +72,14 @@ func (d *Daemon) runConnLoop(ac *attachedClient) { | |||||
| 69 | if !ok { | 72 | if !ok { | ||
| 70 | return | 73 | return | ||
| 71 | } | 74 | } | ||
| 72 | - | if d.handleAttachmentClientFrame(token, f) { | 75 | + | if d.handleAttachmentClientMessage(token, message) { |
| 73 | return | 76 | return | ||
| 74 | } | 77 | } | ||
| 75 | } | 78 | } | ||
| 76 | } | 79 | } | ||
| 77 | 80 | ||||
| 78 | - | // handleAttachmentClientFrame owns the attached-client protocol. | 81 | + | // handleAttachmentClientMessage owns the attached-client protocol. |
| 79 | - | func (d *Daemon) handleAttachmentClientFrame(capability attachmentCapability, f wire.Frame) bool { | 82 | + | func (d *Daemon) handleAttachmentClientMessage(capability attachmentCapability, message protocol.ClientMessage) bool { |
| 80 | if !capability.current() { | 83 | if !capability.current() { | ||
| 81 | return false | 84 | return false | ||
| 82 | } | 85 | } | ||
| @@ -91,63 +94,34 @@ func (d *Daemon) handleAttachmentClientFrame(capability attachmentCapability, f | |||||
| 91 | if d.afterAttachmentEffectAdmitted != nil { | 94 | if d.afterAttachmentEffectAdmitted != nil { | ||
| 92 | d.afterAttachmentEffectAdmitted(effect.capability()) | 95 | d.afterAttachmentEffectAdmitted(effect.capability()) | ||
| 93 | } | 96 | } | ||
| 94 | - | switch f.Type { | 97 | + | switch message := message.(type) { |
| 95 | - | case wire.MsgInput: | 98 | + | case protocol.Input: |
| 96 | - | if in, derr := wire.UnmarshalInput(f.Payload); derr == nil { | 99 | + | d.handleSequencedInputForAttachment(effect, message.InputSeq, message.Data) |
| 97 | - | d.handleSequencedInputForAttachment(effect, in.InputSeq, in.Data) | 100 | + | case protocol.Resize: |
| 98 | - | } | 101 | + | if effect.current() { |
| 99 | - | case wire.MsgResize: | 102 | + | d.resizeAttachmentGeometryForLease(effect, message.Geometry()) |
| 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) | |||
| 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 | if effect.current() { | 111 | if effect.current() { | ||
| 119 | d.clientGoneForAttachment(effect, true) | 112 | d.clientGoneForAttachment(effect, true) | ||
| 120 | return true | 113 | return true | ||
| 121 | } | 114 | } | ||
| 122 | - | case wire.MsgAck: | 115 | + | case protocol.Ack: |
| 123 | - | if ack, derr := wire.UnmarshalAck(f.Payload); derr == nil { | 116 | + | d.ackOutput(effect, message.Epoch, message.State) |
| 124 | - | d.ackOutput(effect, ack.Epoch, ack.State) | 117 | + | case protocol.OutputResetRequest: |
| 125 | - | } | 118 | + | d.resetOutput(effect) |
| 126 | - | case wire.MsgOutputResetRequest: | 119 | + | case protocol.SamePeerSwitchRequest: |
| 127 | - | if _, derr := wire.UnmarshalOutputResetRequest(f.Payload); derr == nil { | 120 | + | d.switchSamePeerForAttachment(effect, message) |
| 128 | - | d.resetOutput(effect) | 121 | + | case protocol.ParkedRouteRequest: |
| 129 | - | } | 122 | + | d.handleParkedRouteRequest(effect, message) |
| 130 | - | case wire.MsgSamePeerSwitchRequest: | 123 | + | case protocol.RecentRouteSnapshot: |
| 131 | - | request, derr := wire.UnmarshalSamePeerSwitchRequest(f.Payload) | 124 | + | replayIdentity := effect.ac.setRouteSnapshot(message) |
| 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) | |||
| 151 | d.invalidateRender(effect.sess, effect.ac, false, "client_frame_routing.go:route-snapshot") | 125 | d.invalidateRender(effect.sess, effect.ac, false, "client_frame_routing.go:route-snapshot") | ||
| 152 | if !replayIdentity { | 126 | if !replayIdentity { | ||
| 153 | break | 127 | break | ||
| @@ -155,61 +129,44 @@ func (d *Daemon) handleAttachmentClientFrame(capability attachmentCapability, f | |||||
| 155 | if err := d.sendCommittedRouteIdentityForAttachment(effect); err != nil { | 129 | if err := d.sendCommittedRouteIdentityForAttachment(effect); err != nil { | ||
| 156 | d.detachOnAttachmentSendError(effect.capability(), effect.transport.transport) | 130 | d.detachOnAttachmentSendError(effect.capability(), effect.transport.transport) | ||
| 157 | } | 131 | } | ||
| 158 | - | case wire.MsgRouteAttentionSubscription: | 132 | + | case protocol.RouteAttentionSubscription: |
| 159 | - | subscription, derr := wire.UnmarshalRouteAttentionSubscription(f.Payload) | 133 | + | effect.ac.setRouteAttentionSubscription(message) |
| 160 | - | if derr != nil { | |||
| 161 | - | d.log.Warn("malformed route attention subscription", "err", derr) | |||
| 162 | - | break | |||
| 163 | - | } | |||
| 164 | - | effect.ac.setRouteAttentionSubscription(subscription) | |||
| 165 | d.invalidateRender(effect.sess, effect.ac, false, "client_frame_routing.go:route-attention") | 134 | d.invalidateRender(effect.sess, effect.ac, false, "client_frame_routing.go:route-attention") | ||
| 166 | - | case wire.MsgRouteNavigationFailure: | 135 | + | case protocol.RouteNavigationFailure: |
| 167 | - | failure, derr := wire.UnmarshalRouteNavigationFailure(f.Payload) | 136 | + | notice := "route navigation failed" |
| 168 | - | if derr != nil { | 137 | + | switch message.Code { |
| 169 | - | d.log.Warn("malformed route navigation failure", "err", derr) | |||
| 170 | - | break | |||
| 171 | - | } | |||
| 172 | - | message := "route navigation failed" | |||
| 173 | - | switch failure.Code { | |||
| 174 | case protocol.RouteFailureStaleSelection: | 138 | case protocol.RouteFailureStaleSelection: | ||
| 175 | - | message = "that recent route is no longer available" | 139 | + | notice = "that recent route is no longer available" |
| 176 | case protocol.RouteFailureTargetChanged: | 140 | case protocol.RouteFailureTargetChanged: | ||
| 177 | - | message = "that recent route changed before attach" | 141 | + | notice = "that recent route changed before attach" |
| 178 | case protocol.RouteFailureOriginUnavailable: | 142 | case protocol.RouteFailureOriginUnavailable: | ||
| 179 | - | message = "the original route is no longer available" | 143 | + | notice = "the original route is no longer available" |
| 180 | case protocol.RouteFailureUnavailable: | 144 | case protocol.RouteFailureUnavailable: | ||
| 181 | - | message = "the selected route is unavailable" | 145 | + | notice = "the selected route is unavailable" |
| 182 | case protocol.RouteFailureNoSuchRoute: | 146 | case protocol.RouteFailureNoSuchRoute: | ||
| 183 | - | message = "that recent route no longer exists" | 147 | + | notice = "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 | |||
| 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 | resultEffect, ok := d.commandResultEffect(effect) | 152 | resultEffect, ok := d.commandResultEffect(effect) | ||
| 193 | if !ok { | 153 | if !ok { | ||
| 194 | // A remote handoff or explicit detach deliberately retired the old | 154 | // A remote handoff or explicit detach deliberately retired the old | ||
| 195 | // attachment; it must not be treated as a stale-result send error. | 155 | // attachment; it must not be treated as a stale-result send error. | ||
| 196 | return true | 156 | return true | ||
| 197 | } | 157 | } | ||
| 198 | - | if err := resultEffect.sendControl(frameCommandResult(result)); err != nil { | 158 | + | if err := resultEffect.sendControl(serverCommandResult(result)); err != nil { |
| 199 | resultEffect.End() | 159 | resultEffect.End() | ||
| 200 | d.detachOnAttachmentSendError(resultEffect.capability(), resultEffect.transport.transport) | 160 | d.detachOnAttachmentSendError(resultEffect.capability(), resultEffect.transport.transport) | ||
| 201 | return true | 161 | return true | ||
| 202 | } | 162 | } | ||
| 203 | resultEffect.End() | 163 | resultEffect.End() | ||
| 204 | - | case wire.MsgPing: | 164 | + | case protocol.Ping: |
| 205 | - | if err := effect.sendControl(framePong()); err != nil { | 165 | + | if err := effect.sendControl(serverPong()); err != nil { |
| 206 | effect.End() | 166 | effect.End() | ||
| 207 | d.detachOnAttachmentSendError(effect.capability(), effect.transport.transport) | 167 | d.detachOnAttachmentSendError(effect.capability(), effect.transport.transport) | ||
| 208 | return true | 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 | return false | 171 | return false | ||
| 215 | } | 172 | } | ||
M internal/usecase/daemon/client_transport_errors.go +10 -10
| @@ -7,15 +7,15 @@ import ( | |||||
| 7 | 7 | ||||
| 8 | // clientGone detaches ac if it is still the session's current client. The | 8 | // clientGone detaches ac if it is still the session's current client. The | ||
| 9 | // session remains registered and headless after the client is gone. | 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 | d.clientGoneWithNotice(sess, ac, failed, explicit, true) | 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 | d.clientGoneWithNotice(sess, ac, failed, explicit, false) | 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 | if sess == nil || ac == nil { | 19 | if sess == nil || ac == nil { | ||
| 20 | return | 20 | return | ||
| 21 | } | 21 | } | ||
| @@ -65,7 +65,7 @@ func (d *Daemon) clientGoneForAttachment(effect *attachmentEffect, explicit bool | |||||
| 65 | return true | 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 | if sess == nil || ac == nil { | 69 | if sess == nil || ac == nil { | ||
| 70 | return | 70 | return | ||
| 71 | } | 71 | } | ||
| @@ -111,7 +111,7 @@ func (d *Daemon) finishClientGone(sess *session, ac *attachedClient, failed port | |||||
| 111 | // (the client is actively awaiting it), but deadline-bounded so a | 111 | // (the client is actively awaiting it), but deadline-bounded so a | ||
| 112 | // wedged client cannot pin this conn handler and hang Serve's | 112 | // wedged client cannot pin this conn handler and hang Serve's | ||
| 113 | // connWg.Wait. | 113 | // connWg.Wait. | ||
| 114 | - | d.boundedSend(ac, frameDetached(protocol.ReasonDetach)) | 114 | + | d.boundedSend(ac, serverDetached(protocol.ReasonDetach)) |
| 115 | } | 115 | } | ||
| 116 | _ = ac.closeCapturedTransport(oldTr) | 116 | _ = ac.closeCapturedTransport(oldTr) | ||
| 117 | d.log.Info("client detached", "session", name, "explicit", explicit) | 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 | // detachOnSendError drops a client whose transport failed, leaving the session | 120 | // detachOnSendError drops a client whose transport failed, leaving the session | ||
| 121 | // registered and headless. | 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 | expected := transportSnapshot{} | 123 | expected := transportSnapshot{} | ||
| 124 | if failed != nil { | 124 | if failed != nil { | ||
| 125 | expected = ac.transportSnapshot() | 125 | expected = ac.transportSnapshot() | ||
| @@ -135,11 +135,11 @@ func (d *Daemon) detachOnSendError(sess *session, ac *attachedClient, failed por | |||||
| 135 | d.clearParkingInFlightIfAbandoned(sess, ac, parkingToken) | 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 | d.detachOnAttachmentSendErrorUntil(token, failed, nil) | 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 | // A delayed sender may report after the client has rebound. Only the exact | 143 | // A delayed sender may report after the client has rebound. Only the exact | ||
| 144 | // transport captured by this attachment is allowed to detach either lifecycle. | 144 | // transport captured by this attachment is allowed to detach either lifecycle. | ||
| 145 | if failed == nil || failed != token.transport.transport { | 145 | if failed == nil || failed != token.transport.transport { | ||
| @@ -160,7 +160,7 @@ func (d *Daemon) detachOnAttachmentSendErrorUntil(token attachmentCapability, fa | |||||
| 160 | // reserveAttachmentSendErrorCleanup accounts for cleanup before End releases the | 160 | // reserveAttachmentSendErrorCleanup accounts for cleanup before End releases the | ||
| 161 | // attachment gate. This closes the WaitGroup Add/Wait race with terminal teardown; | 161 | // attachment gate. This closes the WaitGroup Add/Wait race with terminal teardown; | ||
| 162 | // the returned launch function must be invoked immediately after ticket End. | 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 | d.attachmentCleanupWg.Add(1) | 164 | d.attachmentCleanupWg.Add(1) | ||
| 165 | return func() { | 165 | return func() { | ||
| 166 | go func() { | 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 | ac.clearSamePeerOffer() | 182 | ac.clearSamePeerOffer() | ||
| 183 | ac.clearParkedRoute() | 183 | ac.clearParkedRoute() | ||
| 184 | name := sess.nameSnapshot() | 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 | sess.mu.Unlock() | 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 | token := item.token | 259 | token := item.token | ||
| 260 | if token.ac == nil || ticket == nil || ticket.ended.Load() || token.transport.transport == nil { | 260 | if token.ac == nil || ticket == nil || ticket.ended.Load() || token.transport.transport == nil { | ||
| 261 | return token.transport.transport, errAttachmentTransition | 261 | return token.transport.transport, errAttachmentTransition | ||
| 262 | } | 262 | } | ||
| 263 | expected := token.transport | 263 | expected := token.transport | ||
| 264 | - | ownedTransport, owned := expected.transport.(ports.OwnedSynchronousTransport) | 264 | + | owned := expected.transport.Capabilities().OwnedSynchronousSend |
| 265 | send := func() error { | 265 | send := func() error { | ||
| 266 | ac := token.ac | 266 | ac := token.ac | ||
| 267 | ac.sendMu.Lock() | 267 | ac.sendMu.Lock() | ||
| @@ -277,12 +277,12 @@ func (d *Daemon) boundedSendClipboardForward(item clipboardForward, ticket *atta | |||||
| 277 | } | 277 | } | ||
| 278 | ac.output.lockView() | 278 | ac.output.lockView() | ||
| 279 | defer ac.output.unlockView() | 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 | if err == nil { | 281 | if err == nil { | ||
| 282 | if owned { | 282 | if owned { | ||
| 283 | - | err = ownedTransport.SendSynchronous(frame) | 283 | + | err = expected.transport.SendOutputSynchronous(output) |
| 284 | } else { | 284 | } else { | ||
| 285 | - | err = expected.transport.Send(frame) | 285 | + | err = expected.transport.SendOutput(output) |
| 286 | } | 286 | } | ||
| 287 | } | 287 | } | ||
| 288 | if err != nil { | 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 | return p | 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 | rc := d.attachCoordinator(sess, nil, ac, true) | 208 | rc := d.attachCoordinator(sess, nil, ac, true) | ||
| 209 | token := sess.captureAttachmentCapability(ac, tr) | 209 | token := sess.captureAttachmentCapability(ac, tr) | ||
| 210 | token.lease = rc.attachmentLease(ac) | 210 | token.lease = rc.attachmentLease(ac) | ||
M internal/usecase/daemon/control.go +9 -17
| @@ -13,22 +13,16 @@ import ( | |||||
| 13 | "github.com/bnema/vev/internal/domain" | 13 | "github.com/bnema/vev/internal/domain" | ||
| 14 | "github.com/bnema/vev/internal/ports" | 14 | "github.com/bnema/vev/internal/ports" | ||
| 15 | "github.com/bnema/vev/internal/protocol" | 15 | "github.com/bnema/vev/internal/protocol" | ||
| 16 | - | "github.com/bnema/vev/internal/protocol/wire" | |||
| 17 | "github.com/bnema/vev/internal/usecase/command" | 16 | "github.com/bnema/vev/internal/usecase/command" | ||
| 18 | "github.com/bnema/vev/internal/usecase/layout" | 17 | "github.com/bnema/vev/internal/usecase/layout" | ||
| 19 | ) | 18 | ) | ||
| 20 | 19 | ||||
| 21 | - | // handleCommand serves one one-shot control request. The leading version is | 20 | + | // handleCommand serves one typed one-shot control request. |
| 22 | - | // checked before decoding the versioned body so newer layouts fail cleanly. | 21 | + | func (d *Daemon) handleCommand(tr ports.ServerConnection, request protocol.CommandRequest) error { |
| 23 | - | func (d *Daemon) handleCommand(tr ports.Transport, f wire.Frame) error { | |||
| 24 | defer func() { _ = tr.Close() }() | 22 | defer func() { _ = tr.Close() }() | ||
| 25 | 23 | ||||
| 26 | - | if version, ok := wire.PeekCommandVersion(f.Payload); !ok || version != protocol.Version { | 24 | + | if request.Version != protocol.Version { |
| 27 | - | return d.sendCommandResult(tr, protocol.CommandResult{Code: protocol.ErrVersionMismatch, Text: "protocol version mismatch"}) | 25 | + | return d.sendCommandResult(tr, protocol.CommandResult{RequestID: request.RequestID, 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"}) | |||
| 32 | } | 26 | } | ||
| 33 | if request.Attached { | 27 | if request.Attached { | ||
| 34 | return d.sendCommandResult(tr, protocol.CommandResult{ | 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 | // sendCommandResult gives one-shot control responses the same observable | 64 | // sendCommandResult gives one-shot control responses the same observable | ||
| 71 | // transport-failure behavior regardless of which validation path produced it. | 65 | // transport-failure behavior regardless of which validation path produced it. | ||
| 72 | - | func (d *Daemon) sendCommandResult(tr ports.Transport, result protocol.CommandResult) error { | 66 | + | func (d *Daemon) sendCommandResult(tr ports.ServerConnection, result protocol.CommandResult) error { |
| 73 | - | if err := d.boundedControlSend(tr, frameCommandResult(result)); err != nil { | 67 | + | if err := d.boundedControlSend(tr, serverCommandResult(result)); err != nil { |
| 74 | d.log.Warn("command response send failed", "err", err) | 68 | d.log.Warn("command response send failed", "err", err) | ||
| 75 | return err | 69 | return err | ||
| 76 | } | 70 | } | ||
| @@ -80,17 +74,15 @@ func (d *Daemon) sendCommandResult(tr ports.Transport, result protocol.CommandRe | |||||
| 80 | // boundedControlSend keeps one-shot control handlers from waiting forever on a | 74 | // boundedControlSend keeps one-shot control handlers from waiting forever on a | ||
| 81 | // client that stopped reading. A timeout closes the exact transport so a | 75 | // client that stopped reading. A timeout closes the exact transport so a | ||
| 82 | // blocked Send can unwind before the handler returns. | 76 | // blocked Send can unwind before the handler returns. | ||
| 83 | - | func (d *Daemon) boundedControlSend(tr ports.Transport, frame wire.Frame) error { | 77 | + | func (d *Daemon) boundedControlSend(tr ports.ServerConnection, message protocol.ServerMessage) error { |
| 84 | - | _, err := d.boundedSendWith(tr, func() error { return tr.Send(frame) }) | 78 | + | _, err := d.boundedSendWith(tr, func() error { return tr.SendServer(message) }) |
| 85 | if errors.Is(err, errSendTimedOut) { | 79 | if errors.Is(err, errSendTimedOut) { | ||
| 86 | _ = tr.Close() | 80 | _ = tr.Close() | ||
| 87 | } | 81 | } | ||
| 88 | return err | 82 | return err | ||
| 89 | } | 83 | } | ||
| 90 | 84 | ||||
| 91 | - | func frameCommandResult(result protocol.CommandResult) wire.Frame { | 85 | + | func serverCommandResult(result protocol.CommandResult) protocol.CommandResult { return result } |
| 92 | - | return wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(result)} | |||
| 93 | - | } | |||
| 94 | 86 | ||||
| 95 | func (d *Daemon) dispatchCommand(ctx context.Context, request protocol.CommandRequest) protocol.CommandResult { | 87 | func (d *Daemon) dispatchCommand(ctx context.Context, request protocol.CommandRequest) protocol.CommandResult { | ||
| 96 | cmd, ok := command.BySlug(request.Slug) | 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 | defer releaseConn() | 139 | defer releaseConn() | ||
| 140 | done := make(chan error, 1) | 140 | done := make(chan error, 1) | ||
| 141 | go func() { | 141 | go func() { | ||
| 142 | - | done <- d.handleCommand(tr, frame) | 142 | + | done <- d.handleCommandFrame(tr, frame) |
| 143 | }() | 143 | }() | ||
| 144 | <-factory.entered | 144 | <-factory.entered | ||
| 145 | timer := <-clock.timers | 145 | timer := <-clock.timers | ||
| @@ -159,7 +159,7 @@ func TestHandleCommandResponseSendTimeoutClosesTransport(t *testing.T) { | |||||
| 159 | tr := newBlockingControlSendTransport() | 159 | tr := newBlockingControlSendTransport() | ||
| 160 | frame := commandFrame(t, protocol.CommandRequest{RequestID: 41, Attached: true}) | 160 | frame := commandFrame(t, protocol.CommandRequest{RequestID: 41, Attached: true}) | ||
| 161 | done := make(chan error, 1) | 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 | <-tr.started | 164 | <-tr.started | ||
| 165 | timer := <-clock.timers | 165 | timer := <-clock.timers | ||
| @@ -184,7 +184,7 @@ func TestHandleCommandAttachedRejectionReturnsSendFailure(t *testing.T) { | |||||
| 184 | sendErr := errors.New("command result send failed") | 184 | sendErr := errors.New("command result send failed") | ||
| 185 | tr := &commandSendErrorTransport{err: sendErr} | 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 | RequestID: 41, | 188 | RequestID: 41, | ||
| 189 | Attached: true, | 189 | Attached: true, | ||
| 190 | })) | 190 | })) | ||
| @@ -201,7 +201,7 @@ func TestHandleCommandRejectsVersionBeforeDecodeOrDispatch(t *testing.T) { | |||||
| 201 | tr, sends, _ := newConn(t, frame) | 201 | tr, sends, _ := newConn(t, frame) | ||
| 202 | 202 | ||||
| 203 | d := newTestDaemon(t, nil, stubClock{}) | 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 | result := awaitCommandResult(t, sends) | 206 | result := awaitCommandResult(t, sends) | ||
| 207 | require.False(t, result.OK) | 207 | require.False(t, result.OK) | ||
| @@ -887,7 +887,7 @@ func TestHandleCommandSerializesSelfTargetOnNonActiveTab(t *testing.T) { | |||||
| 887 | firstTransport, firstSends, _ := newConn(t, firstFrame) | 887 | firstTransport, firstSends, _ := newConn(t, firstFrame) | ||
| 888 | firstDone := make(chan struct{}) | 888 | firstDone := make(chan struct{}) | ||
| 889 | go func() { | 889 | go func() { | ||
| 890 | - | if err := d.handleCommand(firstTransport, firstFrame); err != nil { | 890 | + | if err := d.handleCommandFrame(firstTransport, firstFrame); err != nil { |
| 891 | t.Errorf("handle first command: %v", err) | 891 | t.Errorf("handle first command: %v", err) | ||
| 892 | } | 892 | } | ||
| 893 | close(firstDone) | 893 | close(firstDone) | ||
| @@ -905,7 +905,7 @@ func TestHandleCommandSerializesSelfTargetOnNonActiveTab(t *testing.T) { | |||||
| 905 | secondTransport, secondSends, _ := newConn(t, secondFrame) | 905 | secondTransport, secondSends, _ := newConn(t, secondFrame) | ||
| 906 | secondDone := make(chan struct{}) | 906 | secondDone := make(chan struct{}) | ||
| 907 | go func() { | 907 | go func() { | ||
| 908 | - | if err := d.handleCommand(secondTransport, secondFrame); err != nil { | 908 | + | if err := d.handleCommandFrame(secondTransport, secondFrame); err != nil { |
| 909 | t.Errorf("handle second command: %v", err) | 909 | t.Errorf("handle second command: %v", err) | ||
| 910 | } | 910 | } | ||
| 911 | close(secondDone) | 911 | close(secondDone) | ||
| @@ -1287,14 +1287,14 @@ func TestHandleCommandOppositeMoveCommandsDoNotDeadlock(t *testing.T) { | |||||
| 1287 | done := make(chan struct{}, 2) | 1287 | done := make(chan struct{}, 2) | ||
| 1288 | go func() { | 1288 | go func() { | ||
| 1289 | <-start | 1289 | <-start | ||
| 1290 | - | if err := d.handleCommand(leftTransport, leftFrame); err != nil { | 1290 | + | if err := d.handleCommandFrame(leftTransport, leftFrame); err != nil { |
| 1291 | t.Errorf("handle left command: %v", err) | 1291 | t.Errorf("handle left command: %v", err) | ||
| 1292 | } | 1292 | } | ||
| 1293 | done <- struct{}{} | 1293 | done <- struct{}{} | ||
| 1294 | }() | 1294 | }() | ||
| 1295 | go func() { | 1295 | go func() { | ||
| 1296 | <-start | 1296 | <-start | ||
| 1297 | - | if err := d.handleCommand(rightTransport, rightFrame); err != nil { | 1297 | + | if err := d.handleCommandFrame(rightTransport, rightFrame); err != nil { |
| 1298 | t.Errorf("handle right command: %v", err) | 1298 | t.Errorf("handle right command: %v", err) | ||
| 1299 | } | 1299 | } | ||
| 1300 | done <- struct{}{} | 1300 | done <- struct{}{} | ||
| @@ -1339,7 +1339,7 @@ func sendCommand(t *testing.T, d *Daemon, request protocol.CommandRequest) proto | |||||
| 1339 | t.Helper() | 1339 | t.Helper() | ||
| 1340 | frame := commandFrame(t, request) | 1340 | frame := commandFrame(t, request) | ||
| 1341 | tr, sends, _ := newConn(t, frame) | 1341 | tr, sends, _ := newConn(t, frame) | ||
| 1342 | - | require.NoError(t, d.handleCommand(tr, frame)) | 1342 | + | require.NoError(t, d.handleCommandFrame(tr, frame)) |
| 1343 | return awaitCommandResult(t, sends) | 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 | type swapErrorTransport struct { | 69 | type swapErrorTransport struct { | ||
| 70 | ac *attachedClient | 70 | ac *attachedClient | ||
| 71 | - | replacement ports.Transport | 71 | + | replacement ports.ServerConnection |
| 72 | err error | 72 | err error | ||
| 73 | } | 73 | } | ||
| 74 | 74 | ||||
| @@ -82,7 +82,7 @@ func (t *swapErrorTransport) Close() error { return nil } | |||||
| 82 | 82 | ||||
| 83 | type ownedSwapErrorTransport struct { | 83 | type ownedSwapErrorTransport struct { | ||
| 84 | ac *attachedClient | 84 | ac *attachedClient | ||
| 85 | - | replacement ports.Transport | 85 | + | replacement ports.ServerConnection |
| 86 | err error | 86 | err error | ||
| 87 | sent chan wire.Frame | 87 | sent chan wire.Frame | ||
| 88 | } | 88 | } | ||
M internal/usecase/daemon/daemon.go +76 -64
| @@ -18,7 +18,6 @@ import ( | |||||
| 18 | "github.com/bnema/vev/internal/domain" | 18 | "github.com/bnema/vev/internal/domain" | ||
| 19 | "github.com/bnema/vev/internal/ports" | 19 | "github.com/bnema/vev/internal/ports" | ||
| 20 | "github.com/bnema/vev/internal/protocol" | 20 | "github.com/bnema/vev/internal/protocol" | ||
| 21 | - | "github.com/bnema/vev/internal/protocol/wire" | |||
| 22 | "github.com/bnema/vev/internal/usecase/keys" | 21 | "github.com/bnema/vev/internal/usecase/keys" | ||
| 23 | recoveryusecase "github.com/bnema/vev/internal/usecase/recovery" | 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 | // Serve runs the accept loop over l, owning it for the loop's lifetime. It | 738 | // Serve runs the accept loop over l, owning it for the loop's lifetime. It | ||
| 740 | // returns when the last session is removed or ctx is cancelled; on the latter | 739 | // returns when the last session is removed or ctx is cancelled; on the latter | ||
| 741 | // path attached clients are detached with ReasonServerShutdown. | 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 | d.serveCtx, d.serveCancel = context.WithCancel(ctx) | 742 | d.serveCtx, d.serveCancel = context.WithCancel(ctx) | ||
| 744 | defer d.serveCancel() | 743 | defer d.serveCancel() | ||
| 745 | d.hardCtx, d.hardCancel = context.WithCancel(context.Background()) | 744 | d.hardCtx, d.hardCancel = context.WithCancel(context.Background()) | ||
| @@ -1012,54 +1011,58 @@ func (d *Daemon) sessionsSnapshotLocked() []*session { | |||||
| 1012 | return sessionsSnapshot(d.sessions) | 1011 | return sessionsSnapshot(d.sessions) | ||
| 1013 | } | 1012 | } | ||
| 1014 | 1013 | ||||
| 1015 | - | // handleConn reads the first frame off a fresh connection and routes it. A | 1014 | + | // handleConn reads the first typed message off a fresh connection and routes it. |
| 1016 | - | // context watcher closes the transport on serve-context cancel or handshake | 1015 | + | // A context watcher closes the connection on cancellation or handshake timeout. |
| 1017 | - | // timeout so blocked transport operations unwind. | 1016 | + | func (d *Daemon) handleConn(tr ports.ServerConnection) { |
| 1018 | - | func (d *Daemon) handleConn(tr ports.Transport) { | |||
| 1019 | handshakeCtx, timedOut, finishHandshake := d.newHandshakeContext(d.hardCtx) | 1017 | handshakeCtx, timedOut, finishHandshake := d.newHandshakeContext(d.hardCtx) | ||
| 1020 | stopTransport := watchHandshakeTransport(handshakeCtx, tr) | 1018 | stopTransport := watchHandshakeTransport(handshakeCtx, tr) | ||
| 1021 | defer finishHandshake() | 1019 | defer finishHandshake() | ||
| 1022 | defer stopTransport() | 1020 | defer stopTransport() | ||
| 1023 | 1021 | ||||
| 1024 | - | var first wire.Frame | 1022 | + | var first protocol.ClientMessage |
| 1025 | err := boundedHandshakeOperation(handshakeCtx, tr, func() error { | 1023 | err := boundedHandshakeOperation(handshakeCtx, tr, func() error { | ||
| 1026 | - | var recvErr error | 1024 | + | var receiveErr error |
| 1027 | - | first, recvErr = tr.Recv() | 1025 | + | first, receiveErr = tr.ReceiveClient() |
| 1028 | - | return recvErr | 1026 | + | return receiveErr |
| 1029 | }) | 1027 | }) | ||
| 1030 | if err != nil { | 1028 | if err != nil { | ||
| 1031 | - | err = handshakeContextError(d.hardCtx, timedOut, err) | 1029 | + | var failure *protocol.DecodeFailure |
| 1032 | - | d.log.Warn("connection closed before hello", "err", err) | 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 | _ = tr.Close() | 1036 | _ = tr.Close() | ||
| 1034 | return | 1037 | return | ||
| 1035 | } | 1038 | } | ||
| 1036 | - | switch first.Type { | 1039 | + | switch message := first.(type) { |
| 1037 | - | case wire.MsgList: | 1040 | + | case protocol.List: |
| 1038 | stopTransport() | 1041 | stopTransport() | ||
| 1039 | finishHandshake() | 1042 | finishHandshake() | ||
| 1040 | d.handleList(tr) | 1043 | d.handleList(tr) | ||
| 1041 | - | case wire.MsgCommand: | 1044 | + | case protocol.CommandRequest: |
| 1042 | stopTransport() | 1045 | stopTransport() | ||
| 1043 | finishHandshake() | 1046 | finishHandshake() | ||
| 1044 | - | if err := d.handleCommand(tr, first); err != nil { | 1047 | + | if err := d.handleCommand(tr, message); err != nil { |
| 1045 | d.log.Warn("command handler failed", "err", err) | 1048 | d.log.Warn("command handler failed", "err", err) | ||
| 1046 | } | 1049 | } | ||
| 1047 | - | case wire.MsgRemotePreviewRequest: | 1050 | + | case protocol.RemotePreviewRequest: |
| 1048 | stopTransport() | 1051 | stopTransport() | ||
| 1049 | finishHandshake() | 1052 | finishHandshake() | ||
| 1050 | - | if err := d.handleRemotePreview(tr, first); err != nil { | 1053 | + | if err := d.handleRemotePreview(tr, message); err != nil { |
| 1051 | d.log.Warn("remote preview handler failed", "err", err) | 1054 | d.log.Warn("remote preview handler failed", "err", err) | ||
| 1052 | } | 1055 | } | ||
| 1053 | - | case wire.MsgKill: | 1056 | + | case protocol.Kill: |
| 1054 | stopTransport() | 1057 | stopTransport() | ||
| 1055 | finishHandshake() | 1058 | finishHandshake() | ||
| 1056 | - | d.handleKill(tr, first) | 1059 | + | d.handleKill(tr, message) |
| 1057 | - | case wire.MsgHello: | 1060 | + | case protocol.Hello: |
| 1058 | - | d.handleHelloWithContext(handshakeCtx, timedOut, stopTransport, finishHandshake, tr, first) | 1061 | + | d.handleHelloWithContext(handshakeCtx, timedOut, stopTransport, finishHandshake, tr, message) |
| 1059 | default: | 1062 | default: | ||
| 1060 | - | d.log.Warn("hello rejected", "err", "expected hello", "type", first.Type) | 1063 | + | d.log.Warn("hello rejected", "err", "expected hello") |
| 1061 | _ = boundedHandshakeOperation(handshakeCtx, tr, func() error { | 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 | stopTransport() | 1067 | stopTransport() | ||
| 1065 | finishHandshake() | 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 | // handleList replies with the current session listing and closes the (one-shot | 1099 | // handleList replies with the current session listing and closes the (one-shot | ||
| 1071 | // control) connection. | 1100 | // control) connection. | ||
| 1072 | - | func (d *Daemon) handleList(tr ports.Transport) { | 1101 | + | func (d *Daemon) handleList(tr ports.ServerConnection) { |
| 1073 | defer func() { _ = tr.Close() }() | 1102 | defer func() { _ = tr.Close() }() | ||
| 1074 | 1103 | ||||
| 1075 | d.mu.Lock() | 1104 | d.mu.Lock() | ||
| @@ -1105,35 +1134,30 @@ func (d *Daemon) handleList(tr ports.Transport) { | |||||
| 1105 | d.mu.Unlock() | 1134 | d.mu.Unlock() | ||
| 1106 | 1135 | ||||
| 1107 | sort.Slice(infos, func(i, j int) bool { return infos[i].Name < infos[j].Name }) | 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 | // handleKill terminates the requested live session or stopped named session, | 1140 | // handleKill terminates the requested live session or stopped named session, | ||
| 1112 | // or all sessions, and closes the control connection; the resulting EOF is the | 1141 | // or all sessions, and closes the control connection; the resulting EOF is the | ||
| 1113 | // client's success signal. | 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 | defer func() { _ = tr.Close() }() | 1144 | defer func() { _ = tr.Close() }() | ||
| 1116 | 1145 | ||||
| 1117 | - | k, err := wire.UnmarshalKill(f.Payload) | 1146 | + | if request.All { |
| 1118 | - | if err != nil { | |||
| 1119 | - | _ = d.boundedControlSend(tr, frameError(protocol.ErrInternal, "malformed kill request")) | |||
| 1120 | - | return | |||
| 1121 | - | } | |||
| 1122 | - | if k.All { | |||
| 1123 | d.shutdownAll(protocol.ReasonServerShutdown) | 1147 | d.shutdownAll(protocol.ReasonServerShutdown) | ||
| 1124 | return | 1148 | return | ||
| 1125 | } | 1149 | } | ||
| 1126 | 1150 | ||||
| 1127 | d.mu.Lock() | 1151 | d.mu.Lock() | ||
| 1128 | - | target := d.findByNameLocked(k.Name) | 1152 | + | target := d.findByNameLocked(request.Name) |
| 1129 | if target == nil { | 1153 | if target == nil { | ||
| 1130 | - | if _, ok := d.inactive[k.Name]; ok { | 1154 | + | if _, ok := d.inactive[request.Name]; ok { |
| 1131 | d.mu.Unlock() | 1155 | d.mu.Unlock() | ||
| 1132 | // Stopped sessions use the same catalogue-first, incarnation-second | 1156 | // Stopped sessions use the same catalogue-first, incarnation-second | ||
| 1133 | // deletion order as live and offline purges. | 1157 | // deletion order as live and offline purges. | ||
| 1134 | - | if err := d.retryStoppedPurge(k.Name); err != nil { | 1158 | + | if err := d.retryStoppedPurge(request.Name); err != nil { |
| 1135 | - | d.log.Warn("deleting stopped session failed", "err", err, "session", k.Name) | 1159 | + | d.log.Warn("deleting stopped session failed", "err", err, "session", request.Name) |
| 1136 | - | _ = d.boundedControlSend(tr, frameError(protocol.ErrInternal, "deleting stopped session failed")) | 1160 | + | _ = d.boundedControlSend(tr, serverError(protocol.ErrInternal, "deleting stopped session failed")) |
| 1137 | } | 1161 | } | ||
| 1138 | return | 1162 | return | ||
| 1139 | } | 1163 | } | ||
| @@ -1141,54 +1165,42 @@ func (d *Daemon) handleKill(tr ports.Transport, f wire.Frame) { | |||||
| 1141 | d.mu.Unlock() | 1165 | d.mu.Unlock() | ||
| 1142 | 1166 | ||||
| 1143 | if target == nil { | 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 | return | 1169 | return | ||
| 1146 | } | 1170 | } | ||
| 1147 | if err := d.killSession(target, protocol.ReasonSessionKilled, true); err != nil { | 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 | // handleHello runs the attach handshake for direct package callers. Accepted | 1176 | // handleHello runs the attach handshake for direct package callers. Accepted | ||
| 1153 | // connections use handleHelloWithContext so the deadline also covers the first | 1177 | // connections use handleHelloWithContext so the deadline also covers the first | ||
| 1154 | // frame read in handleConn. | 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 | handshakeCtx, timedOut, finishHandshake := d.newHandshakeContext(context.Background()) | 1180 | handshakeCtx, timedOut, finishHandshake := d.newHandshakeContext(context.Background()) | ||
| 1157 | stopTransport := watchHandshakeTransport(handshakeCtx, tr) | 1181 | stopTransport := watchHandshakeTransport(handshakeCtx, tr) | ||
| 1158 | defer finishHandshake() | 1182 | defer finishHandshake() | ||
| 1159 | defer stopTransport() | 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 | // handleHelloWithContext runs the attach handshake: version check, intent | 1187 | // handleHelloWithContext runs the attach handshake: version check, intent | ||
| 1164 | // routing, Welcome, guaranteed first paint, then the per-connection input loop. | 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 | if err := handshakeContextError(context.Background(), timedOut, handshakeCtx.Err()); err != nil { | 1190 | if err := handshakeContextError(context.Background(), timedOut, handshakeCtx.Err()); err != nil { | ||
| 1167 | _ = tr.Close() | 1191 | _ = tr.Close() | ||
| 1168 | return | 1192 | return | ||
| 1169 | } | 1193 | } | ||
| 1170 | - | sendHandshake := func(frame wire.Frame) error { | 1194 | + | sendHandshake := func(message protocol.ServerMessage) error { |
| 1171 | - | err := boundedHandshakeOperation(handshakeCtx, tr, func() error { return tr.Send(frame) }) | 1195 | + | err := boundedHandshakeOperation(handshakeCtx, tr, func() error { return tr.SendServer(message) }) |
| 1172 | if err != nil { | 1196 | if err != nil { | ||
| 1173 | return handshakeContextError(context.Background(), timedOut, err) | 1197 | return handshakeContextError(context.Background(), timedOut, err) | ||
| 1174 | } | 1198 | } | ||
| 1175 | return nil | 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 | if h.Version != protocol.Version { | 1201 | if h.Version != protocol.Version { | ||
| 1190 | d.log.Warn("hello rejected", "err", "protocol version mismatch", "version", h.Version, "expected", protocol.Version, "intent", h.Intent, "session", h.Name) | 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 | _ = tr.Close() | 1204 | _ = tr.Close() | ||
| 1193 | return | 1205 | return | ||
| 1194 | } | 1206 | } | ||
| @@ -1197,9 +1209,9 @@ func (d *Daemon) handleHelloWithContext(handshakeCtx context.Context, timedOut < | |||||
| 1197 | if rerr != nil { | 1209 | if rerr != nil { | ||
| 1198 | d.log.Warn("hello rejected", "err", rerr, "intent", h.Intent, "session", h.Name) | 1210 | d.log.Warn("hello rejected", "err", rerr, "intent", h.Intent, "session", h.Name) | ||
| 1199 | if pe, ok := errors.AsType[*protoErr](rerr); ok { | 1211 | if pe, ok := errors.AsType[*protoErr](rerr); ok { | ||
| 1200 | - | _ = sendHandshake(frameError(pe.code, pe.text)) | 1212 | + | _ = sendHandshake(serverError(pe.code, pe.text)) |
| 1201 | } else { | 1213 | } else { | ||
| 1202 | - | _ = sendHandshake(frameError(protocol.ErrInternal, rerr.Error())) | 1214 | + | _ = sendHandshake(serverError(protocol.ErrInternal, rerr.Error())) |
| 1203 | } | 1215 | } | ||
| 1204 | _ = tr.Close() | 1216 | _ = tr.Close() | ||
| 1205 | return | 1217 | return | ||
| @@ -1230,7 +1242,7 @@ func (d *Daemon) handleHelloWithContext(handshakeCtx context.Context, timedOut < | |||||
| 1230 | return | 1242 | return | ||
| 1231 | } | 1243 | } | ||
| 1232 | welcomeDone, welcomeErr := boundedHandshakeOperationTracked(handshakeCtx, tr, func() error { | 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 | if err != nil { | 1246 | if err != nil { | ||
| 1235 | return err | 1247 | return err | ||
| 1236 | } | 1248 | } | ||
| @@ -1311,7 +1323,7 @@ func (e *protoErr) Error() string { return e.text } | |||||
| 1311 | // unlocking d.mu before returning, including every error path. It publishes | 1323 | // unlocking d.mu before returning, including every error path. It publishes | ||
| 1312 | // terminal and role state before releasing d.mu, then defers coordinator | 1324 | // terminal and role state before releasing d.mu, then defers coordinator | ||
| 1313 | // cleanup so obsolete workers never delay the new handshake. | 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 | initialTabIndex := -1 | 1327 | initialTabIndex := -1 | ||
| 1316 | if h.RemoteTarget != nil { | 1328 | if h.RemoteTarget != nil { | ||
| 1317 | var ok bool | 1329 | var ok bool | ||
| @@ -1478,7 +1490,7 @@ func (d *Daemon) validateExactSessionTarget(ctx context.Context, target protocol | |||||
| 1478 | return d.validateExactSessionTargetLocked(target) | 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 | ctx := d.serveCtx | 1494 | ctx := d.serveCtx | ||
| 1483 | if ctx == nil { | 1495 | if ctx == nil { | ||
| 1484 | ctx = context.Background() | 1496 | ctx = context.Background() | ||
| @@ -1486,7 +1498,7 @@ func (d *Daemon) route(h protocol.Hello, tr ports.Transport) (*session, *attache | |||||
| 1486 | return d.routeWithContext(ctx, h, tr) | 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 | if ctx == nil { | 1502 | if ctx == nil { | ||
| 1491 | ctx = context.Background() | 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 | // newConn scripts a MockTransport: Recv yields first then more (in order), | 237 | // newConn scripts a MockTransport: Recv yields first then more (in order), | ||
| 238 | // then blocks until the connection is released or Closed (returning io.EOF). | 238 | // then blocks until the connection is released or Closed (returning io.EOF). | ||
| 239 | // Every Send is captured on the returned channel. | 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 | t.Helper() | 241 | t.Helper() | ||
| 242 | - | tr := portsmocks.NewMockTransport(t) | 242 | + | tr := newMockServerConnection(t) |
| 243 | sends := make(chan wire.Frame, 64) | 243 | sends := make(chan wire.Frame, 64) | ||
| 244 | recvCh := make(chan wire.Frame, 1+len(more)) | 244 | recvCh := make(chan wire.Frame, 1+len(more)) | ||
| 245 | recvCh <- first | 245 | recvCh <- first | ||
| @@ -331,7 +331,7 @@ func listSessions(t *testing.T, d *Daemon) protocol.Sessions { | |||||
| 331 | // attachment publication and Welcome acceptance. It uses the generated | 331 | // attachment publication and Welcome acceptance. It uses the generated | ||
| 332 | // transport mock while channels make both sides of the wire deterministic. | 332 | // transport mock while channels make both sides of the wire deterministic. | ||
| 333 | type welcomeBlockingTransport struct { | 333 | type welcomeBlockingTransport struct { | ||
| 334 | - | tr *portsmocks.MockTransport | 334 | + | tr *mockServerConnection |
| 335 | sends chan wire.Frame | 335 | sends chan wire.Frame | ||
| 336 | welcomeEntered chan struct{} | 336 | welcomeEntered chan struct{} | ||
| 337 | releaseWelcome chan struct{} | 337 | releaseWelcome chan struct{} | ||
| @@ -348,7 +348,7 @@ func newWelcomeBlockingTransport(t *testing.T) *welcomeBlockingTransport { | |||||
| 348 | releaseWelcome: make(chan struct{}), | 348 | releaseWelcome: make(chan struct{}), | ||
| 349 | recvDone: make(chan struct{}), | 349 | recvDone: make(chan struct{}), | ||
| 350 | } | 350 | } | ||
| 351 | - | b.tr = portsmocks.NewMockTransport(t) | 351 | + | b.tr = newMockServerConnection(t) |
| 352 | b.tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error { | 352 | b.tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error { | ||
| 353 | b.sends <- f | 353 | b.sends <- f | ||
| 354 | if f.Type == wire.MsgWelcome { | 354 | if f.Type == wire.MsgWelcome { | ||
| @@ -375,9 +375,9 @@ func (b *welcomeBlockingTransport) finish() { | |||||
| 375 | b.closeOnce.Do(func() { close(b.recvDone) }) | 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 | t.Helper() | 379 | t.Helper() | ||
| 380 | - | tr := portsmocks.NewMockTransport(t) | 380 | + | tr := newMockServerConnection(t) |
| 381 | sends := make(chan wire.Frame, 64) | 381 | sends := make(chan wire.Frame, 64) | ||
| 382 | tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error { | 382 | tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error { | ||
| 383 | sends <- f | 383 | sends <- f | ||
| @@ -666,7 +666,7 @@ func TestHandleHelloDefersFreshOutputUntilWelcome(t *testing.T) { | |||||
| 666 | tr := newWelcomeBlockingTransport(t) | 666 | tr := newWelcomeBlockingTransport(t) | ||
| 667 | done := make(chan struct{}) | 667 | done := make(chan struct{}) | ||
| 668 | go func() { | 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 | close(done) | 670 | close(done) | ||
| 671 | }() | 671 | }() | ||
| 672 | 672 | ||||
M internal/usecase/daemon/graphics_output.go +6 -7
| @@ -15,7 +15,6 @@ import ( | |||||
| 15 | renderer "github.com/bnema/vev-vt" | 15 | renderer "github.com/bnema/vev-vt" | ||
| 16 | "github.com/bnema/vev-vt/graphics" | 16 | "github.com/bnema/vev-vt/graphics" | ||
| 17 | "github.com/bnema/vev/internal/domain" | 17 | "github.com/bnema/vev/internal/domain" | ||
| 18 | - | "github.com/bnema/vev/internal/ports" | |||
| 19 | "github.com/bnema/vev/internal/protocol" | 18 | "github.com/bnema/vev/internal/protocol" | ||
| 20 | "github.com/bnema/vev/internal/usecase/layout" | 19 | "github.com/bnema/vev/internal/usecase/layout" | ||
| 21 | themeui "github.com/bnema/vev/internal/usecase/theme" | 20 | themeui "github.com/bnema/vev/internal/usecase/theme" | ||
| @@ -1008,23 +1007,23 @@ func (d *Daemon) cleanupAttachmentOutput(ac *attachedClient) error { | |||||
| 1008 | return errors.New("client transport is nil") | 1007 | return errors.New("client transport is nil") | ||
| 1009 | } | 1008 | } | ||
| 1010 | ac.output.lockView() | 1009 | ac.output.lockView() | ||
| 1011 | - | frame, frameErr := ac.output.sideEffectLocked(prepared.data, ac.echoAck.Load()) | 1010 | + | output, outputErr := ac.output.sideEffectLocked(prepared.data, ac.echoAck.Load()) |
| 1012 | - | if frameErr != nil { | 1011 | + | if outputErr != nil { |
| 1013 | ac.output.unlockView() | 1012 | ac.output.unlockView() | ||
| 1014 | prepared.abort() | 1013 | prepared.abort() | ||
| 1015 | d.retireGraphicsOutput(ac, state) | 1014 | d.retireGraphicsOutput(ac, state) | ||
| 1016 | - | return frameErr | 1015 | + | return outputErr |
| 1017 | } | 1016 | } | ||
| 1018 | prepared.markSendAttempted() | 1017 | prepared.markSendAttempted() | ||
| 1019 | var sendErr error | 1018 | var sendErr error | ||
| 1020 | - | if owned, ok := expected.transport.(ports.OwnedSynchronousTransport); ok { | 1019 | + | if expected.transport.Capabilities().OwnedSynchronousSend { |
| 1021 | - | sendErr = owned.SendSynchronous(frame) | 1020 | + | sendErr = expected.transport.SendOutputSynchronous(output) |
| 1022 | } else { | 1021 | } else { | ||
| 1023 | _, sendErr = d.boundedSendWith(expected.transport, func() error { | 1022 | _, sendErr = d.boundedSendWith(expected.transport, func() error { | ||
| 1024 | if !ac.transportSnapshotCurrent(expected) { | 1023 | if !ac.transportSnapshotCurrent(expected) { | ||
| 1025 | return errTransportReplaced | 1024 | return errTransportReplaced | ||
| 1026 | } | 1025 | } | ||
| 1027 | - | return expected.transport.Send(frame) | 1026 | + | return expected.transport.SendOutput(output) |
| 1028 | }) | 1027 | }) | ||
| 1029 | if errors.Is(sendErr, errSendTimedOut) { | 1028 | if errors.Is(sendErr, errSendTimedOut) { | ||
| 1030 | _ = ac.closeCapturedTransport(expected.transport) | 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 | // watchHandshakeTransport closes a transport when the handshake context ends. | 53 | // watchHandshakeTransport closes a transport when the handshake context ends. | ||
| 54 | // Transport.Close is required to interrupt blocked Send and Recv operations. | 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 | stop := make(chan struct{}) | 56 | stop := make(chan struct{}) | ||
| 57 | done := make(chan struct{}) | 57 | done := make(chan struct{}) | ||
| 58 | go func() { | 58 | go func() { | ||
| @@ -74,8 +74,8 @@ func watchHandshakeTransport(ctx context.Context, transport ports.Transport) fun | |||||
| 74 | 74 | ||||
| 75 | // boundedHandshakeOperation waits for one transport operation while the shared | 75 | // boundedHandshakeOperation waits for one transport operation while the shared | ||
| 76 | // handshake context remains live. Closing the transport is the interruption | 76 | // handshake context remains live. Closing the transport is the interruption | ||
| 77 | - | // mechanism because ports.Transport methods do not accept contexts. | 77 | + | // mechanism because ServerConnection methods do not accept contexts. |
| 78 | - | func boundedHandshakeOperation(ctx context.Context, transport ports.Transport, operation func() error) error { | 78 | + | func boundedHandshakeOperation(ctx context.Context, transport ports.ServerConnection, operation func() error) error { |
| 79 | _, err := boundedHandshakeOperationTracked(ctx, transport, operation) | 79 | _, err := boundedHandshakeOperationTracked(ctx, transport, operation) | ||
| 80 | return err | 80 | return err | ||
| 81 | } | 81 | } | ||
| @@ -84,7 +84,7 @@ func boundedHandshakeOperation(ctx context.Context, transport ports.Transport, o | |||||
| 84 | // operation worker. Callers that own an effect gate must wait for this signal | 84 | // operation worker. Callers that own an effect gate must wait for this signal | ||
| 85 | // before releasing the gate after cancellation; the transport operation itself | 85 | // before releasing the gate after cancellation; the transport operation itself | ||
| 86 | // may still be unwinding after Close interrupts it. | 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 | done := make(chan struct{}) | 88 | done := make(chan struct{}) | ||
| 89 | if err := ctx.Err(); err != nil { | 89 | if err := ctx.Err(); err != nil { | ||
| 90 | close(done) | 90 | close(done) | ||
| @@ -129,7 +129,7 @@ func handshakeContextError(parent context.Context, timedOut <-chan struct{}, fal | |||||
| 129 | // failHandshakeAttachment synchronously retires the exact connection admitted | 129 | // failHandshakeAttachment synchronously retires the exact connection admitted | ||
| 130 | // by route. A fresh route-owned session is purged only after its attachment is | 130 | // by route. A fresh route-owned session is purged only after its attachment is | ||
| 131 | // removed; existing sessions and their unrelated attachments are preserved. | 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 | if sess == nil || ac == nil { | 133 | if sess == nil || ac == nil { | ||
| 134 | if tr != nil { | 134 | if tr != nil { | ||
| 135 | _ = tr.Close() | 135 | _ = tr.Close() | ||
M internal/usecase/daemon/handshake_test.go +6 -6
| @@ -128,7 +128,7 @@ func TestHandshakeFirstPaintCancellationDoesNotRacePaintResult(t *testing.T) { | |||||
| 128 | tr := newHandshakeBlockingTransport(false) | 128 | tr := newHandshakeBlockingTransport(false) | ||
| 129 | handshakeDone := make(chan struct{}) | 129 | handshakeDone := make(chan struct{}) | ||
| 130 | go func() { | 130 | go func() { | ||
| 131 | - | d.handleHelloWithContext(ctx, make(chan struct{}), func() {}, func() {}, tr, | 131 | + | d.handleHelloFrameWithContext(ctx, make(chan struct{}), func() {}, func() {}, tr, |
| 132 | mustHello(protocol.IntentNew, "paint-cancel", domain.Size{Cols: 80, Rows: 24})) | 132 | mustHello(protocol.IntentNew, "paint-cancel", domain.Size{Cols: 80, Rows: 24})) | ||
| 133 | close(handshakeDone) | 133 | close(handshakeDone) | ||
| 134 | }() | 134 | }() | ||
| @@ -183,7 +183,7 @@ func TestFailedResumeHandshakeRestoresParkedCredential(t *testing.T) { | |||||
| 183 | resumeTransport := newHandshakeBlockingTransport(true) | 183 | resumeTransport := newHandshakeBlockingTransport(true) | ||
| 184 | done := make(chan struct{}) | 184 | done := make(chan struct{}) | ||
| 185 | go func() { | 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 | close(done) | 187 | close(done) | ||
| 188 | }() | 188 | }() | ||
| 189 | 189 | ||||
| @@ -211,7 +211,7 @@ func TestHandshakeTimeoutClosesBlockedWelcomeSend(t *testing.T) { | |||||
| 211 | tr := newHandshakeBlockingTransport(true) | 211 | tr := newHandshakeBlockingTransport(true) | ||
| 212 | done := make(chan struct{}) | 212 | done := make(chan struct{}) | ||
| 213 | go func() { | 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 | close(done) | 215 | close(done) | ||
| 216 | }() | 216 | }() | ||
| 217 | 217 | ||||
| @@ -247,7 +247,7 @@ func TestHandshakeTimeoutCancelsRouteRestoreWait(t *testing.T) { | |||||
| 247 | tr := newHandshakeBlockingTransport(false) | 247 | tr := newHandshakeBlockingTransport(false) | ||
| 248 | done := make(chan struct{}) | 248 | done := make(chan struct{}) | ||
| 249 | go func() { | 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 | close(done) | 251 | close(done) | ||
| 252 | }() | 252 | }() | ||
| 253 | 253 | ||||
| @@ -275,7 +275,7 @@ func TestHandshakeTimeoutRemovesRestoredEmptySession(t *testing.T) { | |||||
| 275 | tr := newHandshakeBlockingTransport(true) | 275 | tr := newHandshakeBlockingTransport(true) | ||
| 276 | done := make(chan struct{}) | 276 | done := make(chan struct{}) | ||
| 277 | go func() { | 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 | close(done) | 279 | close(done) | ||
| 280 | }() | 280 | }() | ||
| 281 | 281 | ||||
| @@ -305,7 +305,7 @@ func TestHandshakeTimeoutPreservesUnrelatedAttachment(t *testing.T) { | |||||
| 305 | tr := newHandshakeBlockingTransport(true) | 305 | tr := newHandshakeBlockingTransport(true) | ||
| 306 | done := make(chan struct{}) | 306 | done := make(chan struct{}) | ||
| 307 | go func() { | 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 | close(done) | 309 | close(done) | ||
| 310 | }() | 310 | }() | ||
| 311 | 311 | ||||
M internal/usecase/daemon/notify_test.go +2 -3
| @@ -15,7 +15,6 @@ import ( | |||||
| 15 | vt "github.com/bnema/vev-vt" | 15 | vt "github.com/bnema/vev-vt" | ||
| 16 | "github.com/bnema/vev/internal/domain" | 16 | "github.com/bnema/vev/internal/domain" | ||
| 17 | "github.com/bnema/vev/internal/ports" | 17 | "github.com/bnema/vev/internal/ports" | ||
| 18 | - | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |||
| 19 | "github.com/bnema/vev/internal/protocol" | 18 | "github.com/bnema/vev/internal/protocol" | ||
| 20 | "github.com/bnema/vev/internal/protocol/wire" | 19 | "github.com/bnema/vev/internal/protocol/wire" | ||
| 21 | ) | 20 | ) | ||
| @@ -55,8 +54,8 @@ func TestClientNoticeMapsFixedActionsAndDismissesOnlyConnectionToast(t *testing. | |||||
| 55 | func TestMalformedClientNoticeIsIgnored(t *testing.T) { | 54 | func TestMalformedClientNoticeIsIgnored(t *testing.T) { | ||
| 56 | d, sess, ac, _ := newNoticeFixture(t, ¬iceClock{}) | 55 | d, sess, ac, _ := newNoticeFixture(t, ¬iceClock{}) | ||
| 57 | d.attachCoordinator(sess, nil, ac, true) | 56 | d.attachCoordinator(sess, nil, ac, true) | ||
| 58 | - | tr, ok := ac.tr.(*portsmocks.MockTransport) | 57 | + | tr, ok := ac.tr.(*mockServerConnection) |
| 59 | - | require.True(t, ok, "attached client transport must be a MockTransport") | 58 | + | require.True(t, ok, "attached client connection must use the test wrapper") |
| 60 | frames := []wire.Frame{ | 59 | frames := []wire.Frame{ | ||
| 61 | {Type: wire.MsgClientNotice, Payload: []byte{0xff}}, | 60 | {Type: wire.MsgClientNotice, Payload: []byte{0xff}}, | ||
| 62 | {Type: wire.MsgDetach, Payload: wire.MarshalDetach(protocol.Detach{})}, | 61 | {Type: wire.MsgDetach, Payload: wire.MarshalDetach(protocol.Detach{})}, | ||
M internal/usecase/daemon/parked_route.go +2 -19
| @@ -9,7 +9,6 @@ import ( | |||||
| 9 | "github.com/bnema/vev/internal/domain" | 9 | "github.com/bnema/vev/internal/domain" | ||
| 10 | "github.com/bnema/vev/internal/ports" | 10 | "github.com/bnema/vev/internal/ports" | ||
| 11 | "github.com/bnema/vev/internal/protocol" | 11 | "github.com/bnema/vev/internal/protocol" | ||
| 12 | - | "github.com/bnema/vev/internal/protocol/wire" | |||
| 13 | ) | 12 | ) | ||
| 14 | 13 | ||||
| 15 | const parkedRouteLeaseTTL = 15 * time.Minute | 14 | const parkedRouteLeaseTTL = 15 * time.Minute | ||
| @@ -238,33 +237,17 @@ func (ac *attachedClient) consumePublishedParkedRoute(previous, published attach | |||||
| 238 | return 0 | 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 | func (d *Daemon) sendParkedRouteResponse(effect *attachmentEffect, response protocol.ParkedRouteResponse) error { | 240 | func (d *Daemon) sendParkedRouteResponse(effect *attachmentEffect, response protocol.ParkedRouteResponse) error { | ||
| 250 | - | frame, err := parkedRouteResponseFrame(response) | 241 | + | return effect.sendControl(response) |
| 251 | - | if err != nil { | |||
| 252 | - | return err | |||
| 253 | - | } | |||
| 254 | - | return effect.sendControl(frame) | |||
| 255 | } | 242 | } | ||
| 256 | 243 | ||||
| 257 | // sendParkedRouteResponseLocked preserves response -> full-output ordering. | 244 | // sendParkedRouteResponseLocked preserves response -> full-output ordering. | ||
| 258 | // Caller holds ac.sendMu and keeps parkedRouteOutput set through this send. | 245 | // Caller holds ac.sendMu and keeps parkedRouteOutput set through this send. | ||
| 259 | func (d *Daemon) sendParkedRouteResponseLocked(ac *attachedClient, expected transportSnapshot, response protocol.ParkedRouteResponse) error { | 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 | if ac == nil || !ac.transportSnapshotCurrent(expected) || expected.transport == nil { | 247 | if ac == nil || !ac.transportSnapshotCurrent(expected) || expected.transport == nil { | ||
| 265 | return errAttachmentTransition | 248 | return errAttachmentTransition | ||
| 266 | } | 249 | } | ||
| 267 | - | return expected.transport.Send(frame) | 250 | + | return expected.transport.SendServer(response) |
| 268 | } | 251 | } | ||
| 269 | 252 | ||||
| 270 | func (ac *attachedClient) releaseParkedOutputLocked() { | 253 | func (ac *attachedClient) releaseParkedOutputLocked() { | ||
M internal/usecase/daemon/picker.go +9 -8
| @@ -11,7 +11,6 @@ import ( | |||||
| 11 | "github.com/bnema/vev/internal/domain" | 11 | "github.com/bnema/vev/internal/domain" | ||
| 12 | "github.com/bnema/vev/internal/ports" | 12 | "github.com/bnema/vev/internal/ports" | ||
| 13 | "github.com/bnema/vev/internal/protocol" | 13 | "github.com/bnema/vev/internal/protocol" | ||
| 14 | - | "github.com/bnema/vev/internal/protocol/wire" | |||
| 15 | "github.com/bnema/vev/internal/usecase/layout" | 14 | "github.com/bnema/vev/internal/usecase/layout" | ||
| 16 | "github.com/bnema/vev/internal/usecase/picker" | 15 | "github.com/bnema/vev/internal/usecase/picker" | ||
| 17 | "github.com/bnema/vev/internal/usecase/ui" | 16 | "github.com/bnema/vev/internal/usecase/ui" | ||
| @@ -924,14 +923,17 @@ func (d *Daemon) sendLocalAttachTargetForAttachment(effect *attachmentEffect, ta | |||||
| 924 | // Explicit tab rows already take the direct transition path above, while | 923 | // Explicit tab rows already take the direct transition path above, while | ||
| 925 | // stopped sessions have no active target session and retain the fallback. | 924 | // stopped sessions have no active target session and retain the fallback. | ||
| 926 | samePeerEligible := guard.allowSamePeer && targetSess != nil && target.TabIndex <= 0 | 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 | Session: sessionName, | 930 | Session: sessionName, | ||
| 929 | Intent: protocol.IntentAttach, | 931 | Intent: protocol.IntentAttach, | ||
| 930 | ExactTarget: exactTarget, | 932 | ExactTarget: exactTarget, | ||
| 931 | EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned, | 933 | EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned, | ||
| 932 | SamePeer: samePeerEligible, | 934 | SamePeer: samePeerEligible, | ||
| 933 | - | }) | 935 | + | } |
| 934 | - | if payload == nil || exactTarget == nil { | 936 | + | if protocol.ValidateAttachTarget(handoff) != nil { |
| 935 | return errAttachmentTransition | 937 | return errAttachmentTransition | ||
| 936 | } | 938 | } | ||
| 937 | // A close-and-dial handoff leaves this daemon's Kitty namespace. A | 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 | } else { | 946 | } else { | ||
| 945 | effect.ac.offerSamePeerTarget(*exactTarget) | 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 | if samePeerEligible { | 950 | if samePeerEligible { | ||
| 949 | effect.ac.clearSamePeerOffer() | 951 | effect.ac.clearSamePeerOffer() | ||
| 950 | } | 952 | } | ||
| @@ -990,11 +992,10 @@ func (d *Daemon) sendRemoteAttachTargetForAttachment(effect *attachmentEffect, t | |||||
| 990 | RemoteTarget: &remoteTarget, | 992 | RemoteTarget: &remoteTarget, | ||
| 991 | EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned, | 993 | EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned, | ||
| 992 | } | 994 | } | ||
| 993 | - | payload := wire.MarshalAttachTarget(handoff) | 995 | + | if protocol.ValidateAttachTarget(handoff) != nil { |
| 994 | - | if payload == nil { | |||
| 995 | return failUnavailable() | 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 | return domain.UserErr(domain.NoticeSessionUnavailable, "couldn't attach to remote session", err) | 999 | return domain.UserErr(domain.NoticeSessionUnavailable, "couldn't attach to remote session", err) | ||
| 999 | } | 1000 | } | ||
| 1000 | if guard.closePicker { | 1001 | if guard.closePicker { | ||
M internal/usecase/daemon/picker_test.go +2 -2
| @@ -1011,7 +1011,7 @@ func TestPickerSessionSwitchPublishesBeforeInFlightPaintSendCompletes(t *testing | |||||
| 1011 | d := newTestDaemon(t, nil, stubClock{}) | 1011 | d := newTestDaemon(t, nil, stubClock{}) | ||
| 1012 | enteredSend := make(chan struct{}) | 1012 | enteredSend := make(chan struct{}) | ||
| 1013 | releaseSend := make(chan struct{}) | 1013 | releaseSend := make(chan struct{}) | ||
| 1014 | - | tr := portsmocks.NewMockTransport(t) | 1014 | + | tr := newMockServerConnection(t) |
| 1015 | tr.EXPECT().Send(mock.Anything).RunAndReturn(func(wire.Frame) error { | 1015 | tr.EXPECT().Send(mock.Anything).RunAndReturn(func(wire.Frame) error { | ||
| 1016 | close(enteredSend) | 1016 | close(enteredSend) | ||
| 1017 | <-releaseSend | 1017 | <-releaseSend | ||
| @@ -1408,7 +1408,7 @@ func TestResumeStoppedAndSwitchInheritsTerminalEnv(t *testing.T) { | |||||
| 1408 | })).Return(floating, nil).Once() | 1408 | })).Return(floating, nil).Once() | ||
| 1409 | d := newTestDaemon(t, f, stubClock{}) | 1409 | d := newTestDaemon(t, f, stubClock{}) | ||
| 1410 | d.inactive["old"] = inactiveSession{name: "old", cwd: t.TempDir(), createdAt: 1, state: protocol.SessionDown} | 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 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | 1412 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | ||
| 1413 | tr.EXPECT().Close().Return(nil).Maybe() | 1413 | tr.EXPECT().Close().Return(nil).Maybe() | ||
| 1414 | sess, ac, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: sz, TrueColor: true}, tr) | 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 | "github.com/bnema/vev/internal/domain" | 7 | "github.com/bnema/vev/internal/domain" | ||
| 8 | "github.com/bnema/vev/internal/ports" | 8 | "github.com/bnema/vev/internal/ports" | ||
| 9 | "github.com/bnema/vev/internal/protocol" | 9 | "github.com/bnema/vev/internal/protocol" | ||
| 10 | - | "github.com/bnema/vev/internal/protocol/wire" | |||
| 11 | ) | 10 | ) | ||
| 12 | 11 | ||||
| 13 | // routeRemoteTargetWithContext is the exact-target branch for picker handoffs. | 12 | // routeRemoteTargetWithContext is the exact-target branch for picker handoffs. | ||
| 14 | // It is deliberately separate from legacy name routing: a lifecycle mismatch, | 13 | // It is deliberately separate from legacy name routing: a lifecycle mismatch, | ||
| 15 | // missing tab, broken record, or stopped-record race is a no-such-target error, | 14 | // missing tab, broken record, or stopped-record race is a no-such-target error, | ||
| 16 | // never an invitation to attach a same-name replacement. | 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 | if h.RemoteTarget == nil { | 17 | if h.RemoteTarget == nil { | ||
| 19 | return nil, nil, errors.New("daemon: missing remote target") | 18 | return nil, nil, errors.New("daemon: missing remote target") | ||
| 20 | } | 19 | } | ||
| @@ -120,12 +119,7 @@ func (d *Daemon) sendNavigationActionForAttachment(effect *attachmentEffect, act | |||||
| 120 | effect.ac.clearParkedRoute() | 119 | effect.ac.clearParkedRoute() | ||
| 121 | } | 120 | } | ||
| 122 | } | 121 | } | ||
| 123 | - | payload := wire.MarshalNavigationDirective(directive) | 122 | + | if err := effect.sendControl(directive); err != nil { |
| 124 | - | if payload == nil { | |||
| 125 | - | rollback() | |||
| 126 | - | return errAttachmentTransition | |||
| 127 | - | } | |||
| 128 | - | if err := effect.sendControl(wire.Frame{Type: wire.MsgNavigationAction, Payload: payload}); err != nil { | |||
| 129 | rollback() | 123 | rollback() | ||
| 130 | return err | 124 | return err | ||
| 131 | } | 125 | } | ||
| @@ -133,11 +127,10 @@ func (d *Daemon) sendNavigationActionForAttachment(effect *attachmentEffect, act | |||||
| 133 | } | 127 | } | ||
| 134 | 128 | ||||
| 135 | func (d *Daemon) sendRecentRouteNavigationActionForAttachment(effect *attachmentEffect, action protocol.RouteNavigationAction) error { | 129 | func (d *Daemon) sendRecentRouteNavigationActionForAttachment(effect *attachmentEffect, action protocol.RouteNavigationAction) error { | ||
| 136 | - | payload, err := wire.MarshalRouteNavigationAction(action) | 130 | + | if action.SnapshotGeneration == 0 || action.Key == 0 || action.Generation == 0 { |
| 137 | - | if err != nil { | |||
| 138 | return errAttachmentTransition | 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 | func (d *Daemon) sendCommittedRouteIdentityForAttachment(effect *attachmentEffect) error { | 136 | func (d *Daemon) sendCommittedRouteIdentityForAttachment(effect *attachmentEffect) error { | ||
| @@ -150,14 +143,13 @@ func (d *Daemon) sendCommittedRouteIdentityForAttachment(effect *attachmentEffec | |||||
| 150 | Ephemeral: effect.sess.ephemeral, | 143 | Ephemeral: effect.sess.ephemeral, | ||
| 151 | } | 144 | } | ||
| 152 | effect.sess.mu.Unlock() | 145 | effect.sess.mu.Unlock() | ||
| 153 | - | payload, err := wire.MarshalCommittedRouteIdentity(identity) | 146 | + | if err := identity.Validate(); err != nil { |
| 154 | - | if err != nil { | |||
| 155 | return errAttachmentTransition | 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 | ac, err := d.finishAttach(sess, tr, sz, h) | 153 | ac, err := d.finishAttach(sess, tr, sz, h) | ||
| 162 | if err != nil && routeCreated { | 154 | if err != nil && routeCreated { | ||
| 163 | if cleanupErr := d.killSessionIfEmpty(sess, protocol.ReasonSessionKilled, purge); cleanupErr != nil { | 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 | "github.com/bnema/vev/internal/domain" | 17 | "github.com/bnema/vev/internal/domain" | ||
| 18 | "github.com/bnema/vev/internal/ports" | 18 | "github.com/bnema/vev/internal/ports" | ||
| 19 | - | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |||
| 20 | "github.com/bnema/vev/internal/protocol" | 19 | "github.com/bnema/vev/internal/protocol" | ||
| 21 | "github.com/bnema/vev/internal/protocol/wire" | 20 | "github.com/bnema/vev/internal/protocol/wire" | ||
| 22 | "github.com/bnema/vev/internal/usecase/picker" | 21 | "github.com/bnema/vev/internal/usecase/picker" | ||
| @@ -890,7 +889,7 @@ func TestRemotePickerHandoffSendFailureKeepsPickerOpen(t *testing.T) { | |||||
| 890 | d.remoteCatalog.status["arch"] = remoteHostFresh | 889 | d.remoteCatalog.status["arch"] = remoteHostFresh | ||
| 891 | d.remoteCatalog.mu.Unlock() | 890 | d.remoteCatalog.mu.Unlock() | ||
| 892 | cause := errors.New("remote attach send failed") | 891 | cause := errors.New("remote attach send failed") | ||
| 893 | - | tr := portsmocks.NewMockTransport(t) | 892 | + | tr := newMockServerConnection(t) |
| 894 | tr.EXPECT().Send(mock.Anything).Return(cause) | 893 | tr.EXPECT().Send(mock.Anything).Return(cause) | ||
| 895 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "local", tr) | 894 | sess, ac, _ := addRemoteRefreshPickerOwner(t, d, "local", tr) | ||
| 896 | ac.overlays.pickerMu.Lock() | 895 | ac.overlays.pickerMu.Lock() | ||
| @@ -921,9 +920,9 @@ func TestRemotePickerHandoffSendFailureKeepsPickerOpen(t *testing.T) { | |||||
| 921 | require.Same(t, sess, ac.currentAttachmentSession()) | 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 | t.Helper() | 924 | t.Helper() | ||
| 926 | - | var tr ports.Transport | 925 | + | var tr ports.ServerConnection |
| 927 | var sends chan wire.Frame | 926 | var sends chan wire.Frame | ||
| 928 | if len(transports) != 0 { | 927 | if len(transports) != 0 { | ||
| 929 | tr = transports[0] | 928 | tr = transports[0] | ||
M internal/usecase/daemon/remote_preview.go +5 -11
| @@ -7,15 +7,10 @@ import ( | |||||
| 7 | "github.com/bnema/vev/internal/domain" | 7 | "github.com/bnema/vev/internal/domain" | ||
| 8 | "github.com/bnema/vev/internal/ports" | 8 | "github.com/bnema/vev/internal/ports" | ||
| 9 | "github.com/bnema/vev/internal/protocol" | 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 | defer func() { _ = tr.Close() }() | 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 | preview, err := d.captureRemotePreview(request) | 14 | preview, err := d.captureRemotePreview(request) | ||
| 20 | if err != nil { | 15 | if err != nil { | ||
| 21 | status := protocol.RemotePreviewUnavailable | 16 | status := protocol.RemotePreviewUnavailable | ||
| @@ -27,12 +22,11 @@ func (d *Daemon) handleRemotePreview(tr ports.Transport, frame wire.Frame) error | |||||
| 27 | return d.sendRemotePreview(tr, preview) | 22 | return d.sendRemotePreview(tr, preview) | ||
| 28 | } | 23 | } | ||
| 29 | 24 | ||||
| 30 | - | func (d *Daemon) sendRemotePreview(tr ports.Transport, preview protocol.RemotePreview) error { | 25 | + | func (d *Daemon) sendRemotePreview(tr ports.ServerConnection, preview protocol.RemotePreview) error { |
| 31 | - | payload := wire.MarshalRemotePreview(preview) | 26 | + | if protocol.ValidateRemotePreview(preview) != nil { |
| 32 | - | if payload == nil { | 27 | + | preview = protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: protocol.RemotePreviewMalformed} |
| 33 | - | payload = wire.MarshalRemotePreview(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 | var errRemotePreviewNoSuchTarget = errors.New("daemon: remote preview target does not exist") | 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 | d.clientGone(sess, winner(), winnerTransport, false) | 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 | t.Helper() | 187 | t.Helper() | ||
| 188 | winnerTransport, _ := newCapturingTransport(t) | 188 | winnerTransport, _ := newCapturingTransport(t) | ||
| 189 | var winner *attachedClient | 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 | for _, tt := range []struct { | 108 | for _, tt := range []struct { | ||
| 109 | name string | 109 | name string | ||
| 110 | fail func(*composedRenderFrame, *attachedClient) | 110 | fail func(*composedRenderFrame, *attachedClient) | ||
| 111 | - | restore func(*session, *attachedClient, ports.Transport) | 111 | + | restore func(*session, *attachedClient, ports.ServerConnection) |
| 112 | }{ | 112 | }{ | ||
| 113 | { | 113 | { | ||
| 114 | name: "prepare failure", | 114 | name: "prepare failure", | ||
| @@ -116,12 +116,12 @@ func TestRenderDamageReceiptRetainsRealVTDamageAcrossFailedEmission(t *testing.T | |||||
| 116 | // An invalid frame makes the real output.prepare reject after capture. | 116 | // An invalid frame makes the real output.prepare reject after capture. | ||
| 117 | c.frame = renderer.Frame{Width: 1} | 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 | name: "send failure", | 122 | name: "send failure", | ||
| 123 | fail: func(_ *composedRenderFrame, ac *attachedClient) { ac.replaceTransport(cacheFailTransport{}) }, | 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 | sess.mu.Lock() | 125 | sess.mu.Lock() | ||
| 126 | sess.registerAttachmentLocked(ac) | 126 | sess.registerAttachmentLocked(ac) | ||
| 127 | sess.mu.Unlock() | 127 | sess.mu.Unlock() | ||
M internal/usecase/daemon/render_pipeline.go +9 -13
| @@ -7,7 +7,6 @@ import ( | |||||
| 7 | "github.com/bnema/vev/internal/domain" | 7 | "github.com/bnema/vev/internal/domain" | ||
| 8 | "github.com/bnema/vev/internal/ports" | 8 | "github.com/bnema/vev/internal/ports" | ||
| 9 | "github.com/bnema/vev/internal/protocol" | 9 | "github.com/bnema/vev/internal/protocol" | ||
| 10 | - | "github.com/bnema/vev/internal/protocol/wire" | |||
| 11 | "github.com/bnema/vev/internal/usecase/layout" | 10 | "github.com/bnema/vev/internal/usecase/layout" | ||
| 12 | "github.com/bnema/vev/internal/usecase/notices" | 11 | "github.com/bnema/vev/internal/usecase/notices" | ||
| 13 | "github.com/bnema/vev/internal/usecase/palette" | 12 | "github.com/bnema/vev/internal/usecase/palette" | ||
| @@ -475,7 +474,7 @@ func (d *Daemon) emitFrame(entry *session, ac *attachedClient, state *capturedRe | |||||
| 475 | d.invalidateRender(entry, ac, true, "render_pipeline.go:prepare-failed") | 474 | d.invalidateRender(entry, ac, true, "render_pipeline.go:prepare-failed") | ||
| 476 | return true | 475 | return true | ||
| 477 | } | 476 | } | ||
| 478 | - | var sendTr ports.Transport | 477 | + | var sendTr ports.ServerConnection |
| 479 | var sendErr error | 478 | var sendErr error | ||
| 480 | suppressedGraphics := state.suppressedGraphics && !ac.terminalCapabilities.SupportsKittyGraphics() | 479 | suppressedGraphics := state.suppressedGraphics && !ac.terminalCapabilities.SupportsKittyGraphics() | ||
| 481 | if len(data) > 0 { | 480 | if len(data) > 0 { | ||
| @@ -485,9 +484,9 @@ func (d *Daemon) emitFrame(entry *session, ac *attachedClient, state *capturedRe | |||||
| 485 | if sendTr == nil { | 484 | if sendTr == nil { | ||
| 486 | sendErr = errors.New("client transport is nil") | 485 | sendErr = errors.New("client transport is nil") | ||
| 487 | } else { | 486 | } else { | ||
| 488 | - | send := sendTr.Send | 487 | + | send := sendTr.SendOutput |
| 489 | - | if async, ok := sendTr.(ports.AsyncTransport); ok { | 488 | + | if sendTr.Capabilities().AsyncSend { |
| 490 | - | send = async.SendAsync | 489 | + | send = sendTr.SendOutputAsync |
| 491 | } | 490 | } | ||
| 492 | interruptible := false | 491 | interruptible := false | ||
| 493 | if marks.attachmentEffect != nil { | 492 | if marks.attachmentEffect != nil { | ||
| @@ -540,9 +539,8 @@ func (d *Daemon) emitFrame(entry *session, ac *attachedClient, state *capturedRe | |||||
| 540 | } | 539 | } | ||
| 541 | 540 | ||||
| 542 | if position.ActiveTabID != "" && position != ac.output.lastRoutePosition { | 541 | if position.ActiveTabID != "" && position != ac.output.lastRoutePosition { | ||
| 543 | - | payload, positionMarshalErr := wire.MarshalRoutePosition(position) | 542 | + | if positionValidateErr := position.Validate(); positionValidateErr != nil { |
| 544 | - | if positionMarshalErr != nil { | 543 | + | d.log.Error("validate route position", "err", positionValidateErr) |
| 545 | - | d.log.Error("marshal route position", "err", positionMarshalErr) | |||
| 546 | } else { | 544 | } else { | ||
| 547 | sendTransport := ac.transportSnapshot() | 545 | sendTransport := ac.transportSnapshot() | ||
| 548 | sendTr = sendTransport.transport | 546 | sendTr = sendTransport.transport | ||
| @@ -556,12 +554,10 @@ func (d *Daemon) emitFrame(entry *session, ac *attachedClient, state *capturedRe | |||||
| 556 | if sendErr == nil { | 554 | if sendErr == nil { | ||
| 557 | if sendTr == nil { | 555 | if sendTr == nil { | ||
| 558 | sendErr = errors.New("client transport is nil") | 556 | sendErr = errors.New("client transport is nil") | ||
| 557 | + | } else if sendTr.Capabilities().AsyncSend { | |||
| 558 | + | sendErr = sendTr.SendServerAsync(position) | |||
| 559 | } else { | 559 | } else { | ||
| 560 | - | send := sendTr.Send | 560 | + | sendErr = sendTr.SendServer(position) |
| 561 | - | if async, ok := sendTr.(ports.AsyncTransport); ok { | |||
| 562 | - | send = async.SendAsync | |||
| 563 | - | } | |||
| 564 | - | sendErr = send(wire.Frame{Type: wire.MsgRoutePosition, Payload: payload}) | |||
| 565 | } | 561 | } | ||
| 566 | } | 562 | } | ||
| 567 | if marks.attachmentEffect != nil && interruptible { | 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 | prepared, err := stream.prepareFrame(nil, &state, composed.frame, composed.damage, composed.reset, composed.cursor) | 333 | prepared, err := stream.prepareFrame(nil, &state, composed.frame, composed.damage, composed.reset, composed.cursor) | ||
| 334 | require.NoError(t, err) | 334 | require.NoError(t, err) | ||
| 335 | var outputFrame wire.Frame | 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 | outputFrame = frame | 337 | outputFrame = frame | ||
| 338 | return nil | 338 | return nil | ||
| 339 | - | })) | 339 | + | }))) |
| 340 | output, err := wire.UnmarshalOutput(outputFrame.Payload) | 340 | output, err := wire.UnmarshalOutput(outputFrame.Payload) | ||
| 341 | require.NoError(t, err) | 341 | require.NoError(t, err) | ||
| 342 | terminalBytes := output.Data | 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 | win.focusedPane().screen.Row = 3 | 677 | win.focusedPane().screen.Row = 3 | ||
| 678 | 678 | ||||
| 679 | - | tr := portsmocks.NewMockTransport(t) | 679 | + | tr := newMockServerConnection(t) |
| 680 | tr.EXPECT().Close().Return(nil).Maybe() | 680 | tr.EXPECT().Close().Return(nil).Maybe() | ||
| 681 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | 681 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | ||
| 682 | 682 | ||||
| @@ -931,7 +931,7 @@ func TestResizeOrdersPTYBeforeScreen(t *testing.T) { | |||||
| 931 | win.focusedPane().pty = p | 931 | win.focusedPane().pty = p | ||
| 932 | 932 | ||||
| 933 | var gotOutput atomic.Bool | 933 | var gotOutput atomic.Bool | ||
| 934 | - | tr := portsmocks.NewMockTransport(t) | 934 | + | tr := newMockServerConnection(t) |
| 935 | tr.EXPECT().Close().Return(nil).Maybe() | 935 | tr.EXPECT().Close().Return(nil).Maybe() | ||
| 936 | tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error { | 936 | tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error { | ||
| 937 | if f.Type == wire.MsgOutput { | 937 | if f.Type == wire.MsgOutput { | ||
| @@ -963,7 +963,7 @@ func TestSendErrorKeepsEphemeralHeadless(t *testing.T) { | |||||
| 963 | p := portsmocks.NewMockPTY(t) | 963 | p := portsmocks.NewMockPTY(t) | ||
| 964 | p.EXPECT().Close().Return(nil).Maybe() | 964 | p.EXPECT().Close().Return(nil).Maybe() | ||
| 965 | 965 | ||||
| 966 | - | tr := portsmocks.NewMockTransport(t) | 966 | + | tr := newMockServerConnection(t) |
| 967 | tr.EXPECT().Send(mock.Anything).Return(io.ErrClosedPipe).Maybe() | 967 | tr.EXPECT().Send(mock.Anything).Return(io.ErrClosedPipe).Maybe() | ||
| 968 | tr.EXPECT().Close().Return(nil).Maybe() | 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 | // to the named session's active attachment because transport teardown has not | 480 | // to the named session's active attachment because transport teardown has not | ||
| 481 | // parked it yet. Only the exact live owner (same session, token, and client ID) | 481 | // parked it yet. Only the exact live owner (same session, token, and client ID) | ||
| 482 | // is accepted; arbitrary unknown tokens stay fail-closed. | 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 | d.mu.Lock() | 484 | d.mu.Lock() | ||
| 485 | if d.closing { | 485 | if d.closing { | ||
| 486 | d.mu.Unlock() | 486 | d.mu.Unlock() | ||
| @@ -583,7 +583,7 @@ func (d *Daemon) resumeLiveAttachment(h protocol.Hello, tr ports.Transport, sz d | |||||
| 583 | // registry lock, preserving the global sendMu > Daemon.mu ordering. The parked | 583 | // registry lock, preserving the global sendMu > Daemon.mu ordering. The parked | ||
| 584 | // entry is revalidated after both locks are held because it may expire between | 584 | // entry is revalidated after both locks are held because it may expire between | ||
| 585 | // the initial lookup and lock acquisition. | 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 | d.mu.Lock() | 587 | d.mu.Lock() | ||
| 588 | parked := d.parked[h.ResumeToken] | 588 | parked := d.parked[h.ResumeToken] | ||
| 589 | d.mu.Unlock() | 589 | d.mu.Unlock() | ||
| @@ -632,7 +632,7 @@ func (d *Daemon) resumeParked(h protocol.Hello, tr ports.Transport, sz domain.Si | |||||
| 632 | 632 | ||||
| 633 | // resumeParkedLocked completes a validated resume. Caller holds both ac.sendMu | 633 | // resumeParkedLocked completes a validated resume. Caller holds both ac.sendMu | ||
| 634 | // and d.mu in that order. | 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 | parked := d.parked[h.ResumeToken] | 636 | parked := d.parked[h.ResumeToken] | ||
| 637 | if parked == nil || h.ResumeToken == 0 { | 637 | if parked == nil || h.ResumeToken == 0 { | ||
| 638 | return nil, nil, false, nil | 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 | expected := ac.transportSnapshot() | 254 | expected := ac.transportSnapshot() | ||
| 255 | orphanDone := make(chan struct{}) | 255 | orphanDone := make(chan struct{}) | ||
| 256 | type boundedResult struct { | 256 | type boundedResult struct { | ||
| 257 | - | transport ports.Transport | 257 | + | transport ports.ServerConnection |
| 258 | err error | 258 | err error | ||
| 259 | } | 259 | } | ||
| 260 | result := make(chan boundedResult, 1) | 260 | result := make(chan boundedResult, 1) | ||
| 261 | go func() { | 261 | go func() { | ||
| 262 | transport, sendErr := d.boundedSendWithTimeout(time.Second, expected.transport, func() error { | 262 | transport, sendErr := d.boundedSendWithTimeout(time.Second, expected.transport, func() error { | ||
| 263 | defer close(orphanDone) | 263 | defer close(orphanDone) | ||
| 264 | - | return ac.sendExpectedTransport(expected, frameDetached(protocol.ReasonDetach)) | 264 | + | return ac.sendExpectedTransport(expected, serverDetached(protocol.ReasonDetach)) |
| 265 | }) | 265 | }) | ||
| 266 | result <- boundedResult{transport: transport, err: sendErr} | 266 | result <- boundedResult{transport: transport, err: sendErr} | ||
| 267 | }() | 267 | }() | ||
| @@ -327,7 +327,7 @@ func TestHandleHelloResumeDefersFreshOutputUntilWelcome(t *testing.T) { | |||||
| 327 | done := make(chan struct{}) | 327 | done := make(chan struct{}) | ||
| 328 | resumeHello := helloResumeCapable(protocol.IntentResume, sess.name, token) | 328 | resumeHello := helloResumeCapable(protocol.IntentResume, sess.name, token) | ||
| 329 | go func() { | 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 | close(done) | 331 | close(done) | ||
| 332 | }() | 332 | }() | ||
| 333 | 333 | ||||
M internal/usecase/daemon/same_peer_switch.go +3 -4
| @@ -3,7 +3,6 @@ package daemon | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "github.com/bnema/vev/internal/domain" | 4 | "github.com/bnema/vev/internal/domain" | ||
| 5 | "github.com/bnema/vev/internal/protocol" | 5 | "github.com/bnema/vev/internal/protocol" | ||
| 6 | - | "github.com/bnema/vev/internal/protocol/wire" | |||
| 7 | ) | 6 | ) | ||
| 8 | 7 | ||||
| 9 | func (ac *attachedClient) offerSamePeerTarget(target protocol.ExactSessionTarget) { | 8 | func (ac *attachedClient) offerSamePeerTarget(target protocol.ExactSessionTarget) { | ||
| @@ -122,11 +121,11 @@ func (d *Daemon) sendSamePeerSwitchFailure(effect *attachmentEffect, requestID u | |||||
| 122 | } | 121 | } | ||
| 123 | defer sender.End() | 122 | defer sender.End() | ||
| 124 | } | 123 | } | ||
| 125 | - | payload, err := wire.MarshalSamePeerSwitchFailure(protocol.SamePeerSwitchFailure{RequestID: requestID, Code: code}) | 124 | + | failure := protocol.SamePeerSwitchFailure{RequestID: requestID, Code: code} |
| 126 | - | if err != nil { | 125 | + | if failure.Validate() != nil { |
| 127 | return | 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 | d.detachOnAttachmentSendError(sender.capability(), sender.transport.transport) | 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 | p, releasePTY := newBlockingPTY(t) | 398 | p, releasePTY := newBlockingPTY(t) | ||
| 399 | tr, sends, _ := newConn(t, mustHello(protocol.IntentEphemeral, "", domain.Size{Cols: 80, Rows: 24})) | 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 | connCh := make(chan ports.Transport, 1) | 402 | connCh := make(chan ports.Transport, 1) | ||
| 403 | connCh <- tr | 403 | connCh <- tr | ||
| 404 | closed := make(chan struct{}) | 404 | closed := make(chan struct{}) | ||
| @@ -438,7 +438,7 @@ func TestServeGracefulShutdownOnContextCancel(t *testing.T) { | |||||
| 438 | defer releasePTY() | 438 | defer releasePTY() | ||
| 439 | tr, sends, _ := newConn(t, mustHello(protocol.IntentNew, "long", domain.Size{Cols: 80, Rows: 24})) | 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 | connCh := make(chan ports.Transport, 1) | 442 | connCh := make(chan ports.Transport, 1) | ||
| 443 | connCh <- tr | 443 | connCh <- tr | ||
| 444 | closed := make(chan struct{}) | 444 | closed := make(chan struct{}) | ||
| @@ -573,7 +573,7 @@ func TestHelloRacingShutdownIsRejected(t *testing.T) { | |||||
| 573 | func TestServeReturnsDespiteWedgedClientOnShutdown(t *testing.T) { | 573 | func TestServeReturnsDespiteWedgedClientOnShutdown(t *testing.T) { | ||
| 574 | p, _ := newBlockingPTY(t) // Close unblocks the parked reader | 574 | p, _ := newBlockingPTY(t) // Close unblocks the parked reader | ||
| 575 | 575 | ||||
| 576 | - | tr := portsmocks.NewMockTransport(t) | 576 | + | tr := newMockServerConnection(t) |
| 577 | sends := make(chan wire.Frame, 64) | 577 | sends := make(chan wire.Frame, 64) | ||
| 578 | recvCh := make(chan wire.Frame, 1) | 578 | recvCh := make(chan wire.Frame, 1) | ||
| 579 | recvCh <- mustHello(protocol.IntentNew, "wedge", domain.Size{Cols: 80, Rows: 24}) | 579 | recvCh <- mustHello(protocol.IntentNew, "wedge", domain.Size{Cols: 80, Rows: 24}) | ||
| @@ -600,7 +600,7 @@ func TestServeReturnsDespiteWedgedClientOnShutdown(t *testing.T) { | |||||
| 600 | }).Maybe() | 600 | }).Maybe() | ||
| 601 | tr.EXPECT().Close().RunAndReturn(func() error { closeConn(); return nil }).Maybe() | 601 | tr.EXPECT().Close().RunAndReturn(func() error { closeConn(); return nil }).Maybe() | ||
| 602 | 602 | ||||
| 603 | - | l := portsmocks.NewMockListener(t) | 603 | + | l := newMockServerListener(t) |
| 604 | connCh := make(chan ports.Transport, 1) | 604 | connCh := make(chan ports.Transport, 1) | ||
| 605 | connCh <- tr | 605 | connCh <- tr | ||
| 606 | lnClosed := make(chan struct{}) | 606 | lnClosed := make(chan struct{}) | ||
| @@ -1021,7 +1021,7 @@ func TestAttachRestoresPersistedTabNames(t *testing.T) { | |||||
| 1021 | require.NoError(t, err) | 1021 | require.NoError(t, err) | ||
| 1022 | require.True(t, ok) | 1022 | require.True(t, ok) | ||
| 1023 | d.inactive["work"] = inactiveSessionFromRecord(record, protocol.SessionDown, nil) | 1023 | d.inactive["work"] = inactiveSessionFromRecord(record, protocol.SessionDown, nil) | ||
| 1024 | - | tr := portsmocks.NewMockTransport(t) | 1024 | + | tr := newMockServerConnection(t) |
| 1025 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | 1025 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | ||
| 1026 | tr.EXPECT().Close().Return(nil).Maybe() | 1026 | tr.EXPECT().Close().Return(nil).Maybe() | ||
| 1027 | 1027 | ||||
| @@ -1195,7 +1195,7 @@ func TestAttachResumesStoppedSessionFromStoredCwd(t *testing.T) { | |||||
| 1195 | WithStore(t, store)(d) | 1195 | WithStore(t, store)(d) | ||
| 1196 | cwd := t.TempDir() | 1196 | cwd := t.TempDir() | ||
| 1197 | d.inactive["work"] = inactiveSession{name: "work", cwd: cwd, createdAt: 1, state: protocol.SessionDown} | 1197 | d.inactive["work"] = inactiveSession{name: "work", cwd: cwd, createdAt: 1, state: protocol.SessionDown} | ||
| 1198 | - | tr := portsmocks.NewMockTransport(t) | 1198 | + | tr := newMockServerConnection(t) |
| 1199 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | 1199 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | ||
| 1200 | tr.EXPECT().Close().Return(nil).Maybe() | 1200 | tr.EXPECT().Close().Return(nil).Maybe() | ||
| 1201 | 1201 | ||||
| @@ -1218,7 +1218,7 @@ func TestAttachStoppedMissingCwdFallsBackToHome(t *testing.T) { | |||||
| 1218 | d := newTestDaemon(t, newFactory(t, p), stubClock{}) | 1218 | d := newTestDaemon(t, newFactory(t, p), stubClock{}) | ||
| 1219 | WithStore(t, store)(d) | 1219 | WithStore(t, store)(d) | ||
| 1220 | d.inactive["work"] = inactiveSession{name: "work", cwd: "/definitely/missing/vev", createdAt: 1, state: protocol.SessionDown} | 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 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | 1222 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | ||
| 1223 | tr.EXPECT().Close().Return(nil).Maybe() | 1223 | tr.EXPECT().Close().Return(nil).Maybe() | ||
| 1224 | 1224 | ||||
| @@ -1299,14 +1299,14 @@ func TestNewSessionAssignsStableIDsAndChildEnv(t *testing.T) { | |||||
| 1299 | func TestIntentNewStoppedNameRejected(t *testing.T) { | 1299 | func TestIntentNewStoppedNameRejected(t *testing.T) { | ||
| 1300 | d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{}) | 1300 | d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{}) | ||
| 1301 | d.inactive["taken"] = inactiveSession{name: "taken", cwd: "/tmp", createdAt: 1} | 1301 | d.inactive["taken"] = inactiveSession{name: "taken", cwd: "/tmp", createdAt: 1} | ||
| 1302 | - | tr := portsmocks.NewMockTransport(t) | 1302 | + | tr := newMockServerConnection(t) |
| 1303 | _, _, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "taken", Size: domain.Size{Cols: 80, Rows: 24}}, tr) | 1303 | _, _, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "taken", Size: domain.Size{Cols: 80, Rows: 24}}, tr) | ||
| 1304 | require.ErrorContains(t, err, "name already in use") | 1304 | require.ErrorContains(t, err, "name already in use") | ||
| 1305 | } | 1305 | } | ||
| 1306 | 1306 | ||||
| 1307 | func TestIntentNewUnsafeNameRejected(t *testing.T) { | 1307 | func TestIntentNewUnsafeNameRejected(t *testing.T) { | ||
| 1308 | d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{}) | 1308 | d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{}) | ||
| 1309 | - | tr := portsmocks.NewMockTransport(t) | 1309 | + | tr := newMockServerConnection(t) |
| 1310 | _, _, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "my work", Size: domain.Size{Cols: 80, Rows: 24}}, tr) | 1310 | _, _, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "my work", Size: domain.Size{Cols: 80, Rows: 24}}, tr) | ||
| 1311 | require.ErrorContains(t, err, domain.ErrInvalidSessionName.Error()) | 1311 | require.ErrorContains(t, err, domain.ErrInvalidSessionName.Error()) | ||
| 1312 | } | 1312 | } | ||
| @@ -1446,7 +1446,7 @@ func TestAttachUpdatesFutureChildEnvTrueColor(t *testing.T) { | |||||
| 1446 | 1446 | ||||
| 1447 | d := newTestDaemon(t, f, stubClock{}) | 1447 | d := newTestDaemon(t, f, stubClock{}) | ||
| 1448 | d.baseEnv = []string{"KEEP=1", "COLORTERM=old"} | 1448 | d.baseEnv = []string{"KEEP=1", "COLORTERM=old"} | ||
| 1449 | - | tr := portsmocks.NewMockTransport(t) | 1449 | + | tr := newMockServerConnection(t) |
| 1450 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | 1450 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | ||
| 1451 | tr.EXPECT().Close().Return(nil).Maybe() | 1451 | tr.EXPECT().Close().Return(nil).Maybe() | ||
| 1452 | sess, ac, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: sz, TrueColor: true}, tr) | 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 | expectFloatingPrewarmOpen(f, normalSize, floating) | 1488 | expectFloatingPrewarmOpen(f, normalSize, floating) | ||
| 1489 | 1489 | ||||
| 1490 | d := newTestDaemon(t, f, stubClock{}) | 1490 | d := newTestDaemon(t, f, stubClock{}) | ||
| 1491 | - | tr1 := portsmocks.NewMockTransport(t) | 1491 | + | tr1 := newMockServerConnection(t) |
| 1492 | tr1.EXPECT().Send(mock.Anything).Return(nil).Maybe() | 1492 | tr1.EXPECT().Send(mock.Anything).Return(nil).Maybe() | ||
| 1493 | tr1.EXPECT().Close().Return(nil).Maybe() | 1493 | tr1.EXPECT().Close().Return(nil).Maybe() | ||
| 1494 | - | tr2 := portsmocks.NewMockTransport(t) | 1494 | + | tr2 := newMockServerConnection(t) |
| 1495 | tr2.EXPECT().Send(mock.Anything).Return(nil).Maybe() | 1495 | tr2.EXPECT().Send(mock.Anything).Return(nil).Maybe() | ||
| 1496 | tr2.EXPECT().Close().Return(nil).Maybe() | 1496 | tr2.EXPECT().Close().Return(nil).Maybe() | ||
| 1497 | sess, _, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: sz, TrueColor: false}, tr1) | 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 | floating := newQuietPTY() | 1532 | floating := newQuietPTY() | ||
| 1533 | expectFloatingPrewarmOpen(f, normalSize, floating) | 1533 | expectFloatingPrewarmOpen(f, normalSize, floating) | ||
| 1534 | d := newTestDaemon(t, f, stubClock{}) | 1534 | d := newTestDaemon(t, f, stubClock{}) | ||
| 1535 | - | tr := portsmocks.NewMockTransport(t) | 1535 | + | tr := newMockServerConnection(t) |
| 1536 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | 1536 | tr.EXPECT().Send(mock.Anything).Return(nil).Maybe() | ||
| 1537 | tr.EXPECT().Close().Return(nil).Maybe() | 1537 | tr.EXPECT().Close().Return(nil).Maybe() | ||
| 1538 | sess, ac, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: sz, TrueColor: true}, tr) | 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 | floating := newQuietPTY() | 1601 | floating := newQuietPTY() | ||
| 1602 | expectFloatingPrewarmOpen(f, normalSize, floating) | 1602 | expectFloatingPrewarmOpen(f, normalSize, floating) | ||
| 1603 | d := newTestDaemon(t, f, stubClock{}) | 1603 | d := newTestDaemon(t, f, stubClock{}) | ||
| 1604 | - | tr1 := portsmocks.NewMockTransport(t) | 1604 | + | tr1 := newMockServerConnection(t) |
| 1605 | tr1.EXPECT().Send(mock.Anything).Return(nil).Maybe() | 1605 | tr1.EXPECT().Send(mock.Anything).Return(nil).Maybe() | ||
| 1606 | tr1.EXPECT().Close().Return(nil).Maybe() | 1606 | tr1.EXPECT().Close().Return(nil).Maybe() | ||
| 1607 | - | tr2 := portsmocks.NewMockTransport(t) | 1607 | + | tr2 := newMockServerConnection(t) |
| 1608 | tr2.EXPECT().Send(mock.Anything).Return(nil).Maybe() | 1608 | tr2.EXPECT().Send(mock.Anything).Return(nil).Maybe() | ||
| 1609 | tr2.EXPECT().Close().Return(nil).Maybe() | 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 | t.Helper() | 617 | t.Helper() | ||
| 618 | - | l := portsmocks.NewMockListener(t) | 618 | + | l := newMockServerListener(t) |
| 619 | connections := make(chan ports.Transport, 1) | 619 | connections := make(chan ports.Transport, 1) | ||
| 620 | connections <- tr | 620 | connections <- tr | ||
| 621 | closed := make(chan struct{}) | 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 | import ( | 4 | import ( | ||
| 5 | "github.com/bnema/vev/internal/domain" | 5 | "github.com/bnema/vev/internal/domain" | ||
| 6 | "github.com/bnema/vev/internal/protocol" | 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 | s.mu.Lock() | 10 | s.mu.Lock() | ||
| 12 | var identity *protocol.CommittedRouteIdentity | 11 | var identity *protocol.CommittedRouteIdentity | ||
| 13 | if s.incarnation != (domain.SessionLifecycleID{}) { | 12 | if s.incarnation != (domain.SessionLifecycleID{}) { | ||
| @@ -16,7 +15,7 @@ func frameWelcome(s *session, resumeToken uint64) (wire.Frame, error) { | |||||
| 16 | Ephemeral: s.ephemeral, | 15 | Ephemeral: s.ephemeral, | ||
| 17 | } | 16 | } | ||
| 18 | } | 17 | } | ||
| 19 | - | w := protocol.Welcome{ | 18 | + | message := protocol.Welcome{ |
| 20 | SessionID: string(s.id), | 19 | SessionID: string(s.id), | ||
| 21 | SessionName: s.name, | 20 | SessionName: s.name, | ||
| 22 | Ephemeral: s.ephemeral, | 21 | Ephemeral: s.ephemeral, | ||
| @@ -24,37 +23,32 @@ func frameWelcome(s *session, resumeToken uint64) (wire.Frame, error) { | |||||
| 24 | CommittedIdentity: identity, | 23 | CommittedIdentity: identity, | ||
| 25 | } | 24 | } | ||
| 26 | s.mu.Unlock() | 25 | s.mu.Unlock() | ||
| 27 | - | w.ResumeToken = resumeToken | 26 | + | message.ResumeToken = resumeToken |
| 28 | - | payload := wire.MarshalWelcome(w) | 27 | + | if identity != nil { |
| 29 | - | if payload == nil { | 28 | + | if err := identity.Validate(); err != nil { |
| 30 | - | return wire.Frame{}, protocol.ErrInvalidRouteWire | 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 { | 35 | + | func serverError(code uint16, text string) protocol.ErrorMsg { |
| 36 | - | return wire.Frame{Type: wire.MsgError, Payload: wire.MarshalErrorMsg(protocol.ErrorMsg{Code: code, Text: text})} | 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) { | 39 | + | func serverOutputState(data []byte, baseState, state, echoAck uint64) (protocol.Output, error) { |
| 40 | - | payload, err := wire.MarshalOutput(protocol.Output{ | 40 | + | message := protocol.Output{ |
| 41 | Epoch: 1, Base: baseState, New: state, Echo: echoAck, | 41 | Epoch: 1, Base: baseState, New: state, Echo: echoAck, | ||
| 42 | - | Size: domain.Size{Cols: 1, Rows: 1}, Full: state != 0 && baseState == 0, Data: b, | 42 | + | Size: domain.Size{Cols: 1, Rows: 1}, Full: state != 0 && baseState == 0, Data: data, |
| 43 | - | }) | |||
| 44 | - | if err != nil { | |||
| 45 | - | return wire.Frame{}, err | |||
| 46 | } | 43 | } | ||
| 47 | - | return wire.Frame{Type: wire.MsgOutput, Payload: payload}, nil | 44 | + | if err := protocol.ValidateOutput(message); err != nil { |
| 48 | - | } | 45 | + | return protocol.Output{}, err |
| 49 | - | 46 | + | } | |
| 50 | - | func frameDetached(reason uint8) wire.Frame { | 47 | + | return message, nil |
| 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{})} | |||
| 56 | } | 48 | } | ||
| 57 | 49 | ||||
| 58 | - | func frameSessions(infos []protocol.SessionInfo) wire.Frame { | 50 | + | func serverDetached(reason uint8) protocol.Detached { return protocol.Detached{Reason: reason} } |
| 59 | - | return wire.Frame{Type: wire.MsgSessions, Payload: wire.MarshalSessions(protocol.Sessions{Sessions: infos})} | 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 | "fmt" | 4 | "fmt" | ||
| 5 | "sync" | 5 | "sync" | ||
| 6 | "testing" | 6 | "testing" | ||
| 7 | - | ||||
| 8 | - | "github.com/bnema/vev/internal/protocol/wire" | |||
| 9 | ) | 7 | ) | ||
| 10 | 8 | ||||
| 11 | func TestFrameWelcomeSnapshotsSessionMetadataUnderLock(t *testing.T) { | 9 | func TestFrameWelcomeSnapshotsSessionMetadataUnderLock(t *testing.T) { | ||
| @@ -23,13 +21,9 @@ func TestFrameWelcomeSnapshotsSessionMetadataUnderLock(t *testing.T) { | |||||
| 23 | }) | 21 | }) | ||
| 24 | 22 | ||||
| 25 | for range 1000 { | 23 | for range 1000 { | ||
| 26 | - | frame, frameErr := frameWelcome(sess, ac.resumeToken) | 24 | + | welcome, err := serverWelcome(sess, ac.resumeToken) |
| 27 | - | if frameErr != nil { | |||
| 28 | - | t.Fatalf("frameWelcome() error = %v", frameErr) | |||
| 29 | - | } | |||
| 30 | - | welcome, err := wire.UnmarshalWelcome(frame.Payload) | |||
| 31 | if err != nil { | 25 | if err != nil { | ||
| 32 | - | t.Fatalf("UnmarshalWelcome() error = %v", err) | 26 | + | t.Fatalf("serverWelcome() error = %v", err) |
| 33 | } | 27 | } | ||
| 34 | if welcome.SessionID != "session" || welcome.SessionName == "" { | 28 | if welcome.SessionID != "session" || welcome.SessionName == "" { | ||
| 35 | t.Fatalf("welcome metadata = %+v", welcome) | 29 | t.Fatalf("welcome metadata = %+v", welcome) | ||