package serial import ( "context" "errors" "io" "net" "path/filepath" "reflect" "sync/atomic" "testing" "time" ) func TestStreamSocketClientTransfersArbitraryBinaryBothDirections(t *testing.T) { resolver := newFakeStreamResolver() peers := make(chan net.Conn, 1) manager, err := NewStreamLeaseManager(resolver, StreamLeaseOptions{ Opener: streamOpenFunc(func(string) (io.ReadWriteCloser, error) { serialSide, peer := net.Pipe() peers <- peer return serialSide, nil }), }) if err != nil { t.Fatal(err) } socketPath, stop := startStreamSocketServer(t, manager) defer stop() client := NewStreamSocketClient(socketPath, "sessiond-test") transport, err := client.Open(context.Background(), "CH01") if err != nil { t.Fatalf("open stream client: %v", err) } defer transport.Close() peer := <-peers defer peer.Close() tx := []byte{0x00, 0x3a, 0xff, '\n'} txDone := make(chan error, 1) go func() { _, err := transport.Write(tx) txDone <- err }() gotTX := make([]byte, len(tx)) if _, err := io.ReadFull(peer, gotTX); err != nil { t.Fatal(err) } if err := <-txDone; err != nil { t.Fatal(err) } if !reflect.DeepEqual(gotTX, tx) { t.Fatalf("tx = %v", gotTX) } rx := []byte{0xfe, 0x00, 0x7f} rxDone := make(chan error, 1) go func() { _, err := peer.Write(rx) rxDone <- err }() gotRX := make([]byte, len(rx)) if _, err := io.ReadFull(transport, gotRX); err != nil { t.Fatal(err) } if err := <-rxDone; err != nil { t.Fatal(err) } if !reflect.DeepEqual(gotRX, rx) { t.Fatalf("rx = %v", gotRX) } } func TestStreamSocketRejectsNonCanonicalAndDuplicateLease(t *testing.T) { resolver := newFakeStreamResolver() peers := make(chan net.Conn, 2) manager, err := NewStreamLeaseManager(resolver, StreamLeaseOptions{ Opener: streamOpenFunc(func(string) (io.ReadWriteCloser, error) { serialSide, peer := net.Pipe() peers <- peer return serialSide, nil }), }) if err != nil { t.Fatal(err) } socketPath, stop := startStreamSocketServer(t, manager) defer stop() client := NewStreamSocketClient(socketPath, "sessiond-test") if _, err := client.Open(context.Background(), "ch01"); err == nil { t.Fatal("expected non-canonical channel rejection") } first, err := client.Open(context.Background(), "CH01") if err != nil { t.Fatal(err) } peer := <-peers defer peer.Close() if _, err := client.Open(context.Background(), "CH01"); !errors.Is(err, ErrStreamLeaseBusy) { t.Fatalf("duplicate lease error = %v", err) } if err := first.Close(); err != nil { t.Fatal(err) } deadline := time.Now().Add(time.Second) for resolver.releaseCount("CH01") == 0 && time.Now().Before(deadline) { time.Sleep(time.Millisecond) } if resolver.releaseCount("CH01") != 1 { t.Fatal("lease was not released after client disconnect") } } func TestStreamSocketStatusReportsBoundedLeaseCounters(t *testing.T) { resolver := newFakeStreamResolver() manager, err := NewStreamLeaseManager(resolver, StreamLeaseOptions{ Opener: streamOpenFunc(func(string) (io.ReadWriteCloser, error) { serialSide, peer := net.Pipe() go peer.Close() return serialSide, nil }), }) if err != nil { t.Fatal(err) } socketPath, stop := startStreamSocketServer(t, manager) defer stop() client := NewStreamSocketClient(socketPath, "sessiond-health") status, err := client.Status(context.Background()) if err != nil { t.Fatalf("status: %v", err) } if status.ActiveLeases != 0 || status.RXBytes != 0 || status.TXBytes != 0 { t.Fatalf("status = %#v", status) } } func TestStreamSocketSendsBreakOnlyForActiveLease(t *testing.T) { resolver := newFakeStreamResolver() peers := make(chan net.Conn, 1) var device *breakableStreamDevice manager, err := NewStreamLeaseManager(resolver, StreamLeaseOptions{ Opener: streamOpenFunc(func(string) (io.ReadWriteCloser, error) { serialSide, peer := net.Pipe() device = &breakableStreamDevice{Conn: serialSide} peers <- peer return device, nil }), }) if err != nil { t.Fatal(err) } socketPath, stop := startStreamSocketServer(t, manager) defer stop() client := NewStreamSocketClient(socketPath, "sessiond-break-test") transport, err := client.Open(context.Background(), "CH01") if err != nil { t.Fatal(err) } defer transport.Close() peer := <-peers defer peer.Close() sender, ok := transport.(interface{ SendBreak(context.Context) error }) if !ok { t.Fatalf("transport %T does not expose SendBreak", transport) } if err := sender.SendBreak(context.Background()); err != nil { t.Fatalf("SendBreak() error = %v", err) } if got := device.breaks.Load(); got != 1 { t.Fatalf("break count = %d, want 1", got) } } type breakableStreamDevice struct { net.Conn breaks atomic.Int32 } func (d *breakableStreamDevice) SendBreak() error { d.breaks.Add(1) return nil } func startStreamSocketServer(t *testing.T, manager *StreamLeaseManager) (string, func()) { t.Helper() socketPath := filepath.Join(t.TempDir(), "serial-stream.sock") listener, err := net.Listen("unix", socketPath) if err != nil { t.Skipf("unix sockets unavailable: %v", err) } server := StreamSocketServer{Manager: manager} done := make(chan error, 1) go func() { done <- server.Serve(listener) }() return socketPath, func() { _ = listener.Close() select { case err := <-done: if err != nil { t.Errorf("stream server stop: %v", err) } case <-time.After(time.Second): t.Error("stream server did not stop") } } }