handshake performs the initial CoderVPN protocol handshake over the given conn
( ctx context.Context, conn io.ReadWriteCloser, logger slog.Logger, me, them SpeakerRole, )
| 252 | |
| 253 | // handshake performs the initial CoderVPN protocol handshake over the given conn |
| 254 | func handshake( |
| 255 | ctx context.Context, conn io.ReadWriteCloser, logger slog.Logger, me, them SpeakerRole, |
| 256 | ) error { |
| 257 | // read and write simultaneously to avoid deadlocking if the conn is not buffered |
| 258 | errCh := make(chan error, 2) |
| 259 | go func() { |
| 260 | ours := headerString(me, CurrentSupportedVersions) |
| 261 | _, err := conn.Write([]byte(ours)) |
| 262 | logger.Debug(ctx, "wrote out header") |
| 263 | if err != nil { |
| 264 | err = xerrors.Errorf("write header: %w", err) |
| 265 | } |
| 266 | errCh <- err |
| 267 | }() |
| 268 | headerCh := make(chan string, 1) |
| 269 | go func() { |
| 270 | // we can't use bufio.Scanner here because we need to ensure we don't read beyond the |
| 271 | // first newline. So, we'll read one byte at a time. It's inefficient, but the initial |
| 272 | // header is only a few characters, so we'll keep this code simple. |
| 273 | buf := make([]byte, 256) |
| 274 | have := 0 |
| 275 | for { |
| 276 | _, err := conn.Read(buf[have : have+1]) |
| 277 | if err != nil { |
| 278 | errCh <- xerrors.Errorf("read header: %w", err) |
| 279 | return |
| 280 | } |
| 281 | if buf[have] == '\n' { |
| 282 | logger.Debug(ctx, "got newline header delimiter") |
| 283 | // use have (not have+1) since we don't want the delimiter for verification. |
| 284 | headerCh <- string(buf[:have]) |
| 285 | return |
| 286 | } |
| 287 | have++ |
| 288 | if have >= len(buf) { |
| 289 | errCh <- xerrors.Errorf("header malformed or too large: %s", string(buf)) |
| 290 | return |
| 291 | } |
| 292 | } |
| 293 | }() |
| 294 | |
| 295 | writeOK := false |
| 296 | theirHeader := "" |
| 297 | readOK := false |
| 298 | for !(readOK && writeOK) { |
| 299 | select { |
| 300 | case <-ctx.Done(): |
| 301 | _ = conn.Close() // ensure our read/write goroutines get a chance to clean up |
| 302 | return ctx.Err() |
| 303 | case err := <-errCh: |
| 304 | if err == nil { |
| 305 | // write goroutine sends nil when completing successfully. |
| 306 | logger.Debug(ctx, "write ok") |
| 307 | writeOK = true |
| 308 | continue |
| 309 | } |
| 310 | _ = conn.Close() |
| 311 | return err |
no test coverage detected