package wshub import ( "net" "testing" "time" "marte2/common/udpsprotocol" ) // TestUDPClientSendsPeriodicKeepAliveAcks verifies that a unicast UDPClient // re-sends ACK datagrams from the SAME socket at keepAliveInterval. The // UDPSServer refreshes a unicast client's last-seen only on client->server // traffic; without this keepalive it evicts the client after ClientTimeout // (default 30 s) and the stream dies. func TestUDPClientSendsPeriodicKeepAliveAcks(t *testing.T) { srv, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")}) if err != nil { t.Fatal(err) } defer srv.Close() c := NewUDPClient(srv.LocalAddr().String(), "ka1", NewHub(), "", 0) c.keepAliveInterval = 150 * time.Millisecond go c.Run() defer c.Stop() buf := make([]byte, 512) // 1) CONNECT from the client's ephemeral socket. srv.SetReadDeadline(time.Now().Add(3 * time.Second)) n, clientAddr, err := srv.ReadFromUDP(buf) if err != nil { t.Fatalf("expected CONNECT: %v", err) } hdr, err := udpsprotocol.ParseHeader(buf[:n]) if err != nil { t.Fatalf("parse CONNECT: %v", err) } if hdr.Type != udpsprotocol.PktConnect { t.Fatalf("first packet type = %d, want CONNECT (%d)", hdr.Type, udpsprotocol.PktConnect) } // 2) Collect ACKs for ~1 s: must be periodic and from the SAME socket // (a new ephemeral socket would be registered as a new client). deadline := time.Now().Add(time.Second) acks := 0 for time.Now().Before(deadline) { srv.SetReadDeadline(deadline) n, addr, err := srv.ReadFromUDP(buf) if err != nil { break } hdr, err := udpsprotocol.ParseHeader(buf[:n]) if err != nil { continue } if hdr.Type != udpsprotocol.PktACK { t.Fatalf("unexpected packet type %d from %s", hdr.Type, addr) } if addr.String() != clientAddr.String() { t.Fatalf("ACK from %s, want same socket as CONNECT (%s)", addr, clientAddr) } acks++ } if acks < 3 { t.Fatalf("expected >= 3 keepalive ACKs in 1 s, got %d", acks) } }