package notify import ( "context" "errors" "math" "net" "testing" "time" "b612.me/stario" ) func TestClientConnCurrentTransportConnStreamSnapshot(t *testing.T) { server := NewServer().(*ServerCommon) left, right := net.Pipe() defer left.Close() defer right.Close() logical := server.bootstrapAcceptedLogical("transport-stream", nil, left) if logical == nil { t.Fatal("bootstrapAcceptedLogical should return logical") } transport := logical.CurrentTransportConn() if transport == nil { t.Fatal("CurrentTransportConn should expose active stream transport") } if transport.LogicalConn() != logical { t.Fatal("LogicalConn mismatch") } if !transport.Attached() { t.Fatal("Attached mismatch: got false want true") } if !transport.HasRuntimeConn() { t.Fatal("HasRuntimeConn mismatch: got false want true") } if !transport.UsesStreamTransport() { t.Fatal("UsesStreamTransport mismatch: got false want true") } if !transport.IsCurrent() { t.Fatal("IsCurrent mismatch: got false want true") } snapshot, err := GetTransportConnRuntimeSnapshot(transport) if err != nil { t.Fatalf("GetTransportConnRuntimeSnapshot failed: %v", err) } if got, want := snapshot.ClientID, logical.ClientID; got != want { t.Fatalf("ClientID mismatch: got %q want %q", got, want) } if !snapshot.Attached || !snapshot.HasRuntimeConn || !snapshot.Current { t.Fatalf("unexpected transport snapshot: %+v", snapshot) } if got, want := snapshot.BindingOwner, "server-transport"; got != want { t.Fatalf("BindingOwner mismatch: got %q want %q", got, want) } if !snapshot.BindingCurrent || !snapshot.LogicalAlive { t.Fatalf("binding state mismatch: %+v", snapshot) } } func TestClientConnCurrentTransportConnPacketSnapshot(t *testing.T) { server := NewServer().(*ServerCommon) stopCtx, stopFn := context.WithCancel(context.Background()) defer stopFn() udpListener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}) if err != nil { t.Fatalf("ListenUDP failed: %v", err) } defer udpListener.Close() server.setServerSessionRuntime(&serverSessionRuntime{ stopCtx: stopCtx, stopFn: stopFn, udpListener: udpListener, }) addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:34567") if err != nil { t.Fatalf("ResolveUDPAddr failed: %v", err) } logical := server.bootstrapAcceptedLogical("transport-packet", addr, nil) if logical == nil { t.Fatal("bootstrapAcceptedLogical should return packet logical") } transport := logical.CurrentTransportConn() if transport == nil { t.Fatal("CurrentTransportConn should expose packet transport route") } if got, want := transport.RemoteAddr().String(), addr.String(); got != want { t.Fatalf("RemoteAddr mismatch: got %q want %q", got, want) } if !transport.Attached() { t.Fatal("Attached mismatch: got false want true") } if transport.HasRuntimeConn() { t.Fatal("packet transport should not expose runtime conn") } if transport.TransportGeneration() != 0 { t.Fatalf("packet transport generation mismatch: got %d want 0", transport.TransportGeneration()) } } func TestTransportConnSendRejectsStaleGenerationAfterReattach(t *testing.T) { server := NewServer().(*ServerCommon) UseLegacySecurityServer(server) runtimeCtx, runtimeCancel := context.WithCancel(context.Background()) defer runtimeCancel() queue := stario.NewQueueCtx(runtimeCtx, 4, math.MaxUint32) server.setServerSessionRuntime(&serverSessionRuntime{ stopCtx: runtimeCtx, stopFn: runtimeCancel, queue: queue, }) server.markSessionStarted() defer server.markSessionStopped("test done", nil) firstLeft, firstRight := net.Pipe() defer firstRight.Close() logical, _, _ := newRegisteredServerLogicalForTest(t, server, "transport-send-stale", firstLeft, runtimeCtx, runtimeCancel) logical.applyClientConnAttachmentProfile(0, 100*time.Millisecond, server.defaultMsgEn, server.defaultMsgDe, server.handshakeRsaKey, server.SecretKey) firstTransport := logical.CurrentTransportConn() if firstTransport == nil { t.Fatal("first transport snapshot should exist") } secondLeft, secondRight := net.Pipe() defer secondRight.Close() if err := logical.attachClientConnSessionTransport(secondLeft); err != nil { t.Fatalf("attachClientConnSessionTransport failed: %v", err) } secondTransport := logical.CurrentTransportConn() if secondTransport == nil { t.Fatal("second transport snapshot should exist after reattach") } if firstTransport.IsCurrent() { t.Fatal("first transport should become stale after reattach") } if err := firstTransport.Send("stale", MsgVal("payload")); !errors.Is(err, errTransportDetached) { t.Fatalf("stale transport send error = %v, want errors.Is(..., %v)", err, errTransportDetached) } recvCh := make(chan []byte, 1) errCh := make(chan error, 1) go func() { _ = secondRight.SetReadDeadline(time.Now().Add(time.Second)) reader := stario.NewFrameReader(secondRight, nil) payload, err := reader.Next() if err != nil { errCh <- err return } recvCh <- payload }() if err := secondTransport.Send("fresh", MsgVal("payload")); err != nil { t.Fatalf("fresh transport send failed: %v", err) } select { case err := <-errCh: t.Fatalf("fresh transport read failed: %v", err) case got := <-recvCh: if len(got) == 0 { t.Fatal("fresh transport should produce framed payload") } case <-time.After(time.Second): t.Fatal("fresh transport send timed out") } } func TestTransportConnRuntimeSnapshotIncludesDetachDiagnostics(t *testing.T) { server := NewServer().(*ServerCommon) left, right := net.Pipe() defer right.Close() logical := server.bootstrapAcceptedLogical("transport-detach", nil, left) if logical == nil { t.Fatal("bootstrapAcceptedLogical should return logical") } transport := logical.CurrentTransportConn() if transport == nil { t.Fatal("CurrentTransportConn should return active transport") } server.detachLogicalSessionTransport(logical, "read error", errors.New("boom")) snapshot, err := GetTransportConnRuntimeSnapshot(transport) if err != nil { t.Fatalf("GetTransportConnRuntimeSnapshot failed: %v", err) } if snapshot.Current { t.Fatalf("snapshot Current should be false after detach: %+v", snapshot) } if snapshot.BindingCurrent { t.Fatalf("snapshot BindingCurrent should be false after detach: %+v", snapshot) } if got, want := snapshot.LogicalReason, ""; got != want { t.Fatalf("snapshot LogicalReason = %q, want %q", got, want) } if got, want := snapshot.TransportDetachReason, "read error"; got != want { t.Fatalf("snapshot TransportDetachReason = %q, want %q", got, want) } if got, want := snapshot.TransportDetachKind, clientConnTransportDetachKindReadError; got != want { t.Fatalf("snapshot TransportDetachKind = %q, want %q", got, want) } if got, want := snapshot.TransportDetachError, "boom"; got != want { t.Fatalf("snapshot TransportDetachError = %q, want %q", got, want) } }