Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
248 lines
6.2 KiB
Go
248 lines
6.2 KiB
Go
package devicelink
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestQUICEndpointCloseClosesOwnedUDPSocket(t *testing.T) {
|
|
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
endpoint, err := NewQUICEndpoint(conn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := endpoint.Close(); err != nil {
|
|
t.Fatalf("Close() error = %v", err)
|
|
}
|
|
if err := conn.SetReadDeadline(time.Now()); !errors.Is(err, net.ErrClosed) {
|
|
t.Fatalf("owned UDP socket deadline error = %v, want net.ErrClosed", err)
|
|
}
|
|
}
|
|
|
|
func TestDefaultQUICLiveness(t *testing.T) {
|
|
config := defaultQUICConfig()
|
|
if config.KeepAlivePeriod != 5*time.Second {
|
|
t.Fatalf("KeepAlivePeriod = %s, want 5s", config.KeepAlivePeriod)
|
|
}
|
|
if config.MaxIdleTimeout != 15*time.Second {
|
|
t.Fatalf("MaxIdleTimeout = %s, want 15s", config.MaxIdleTimeout)
|
|
}
|
|
}
|
|
|
|
func TestGlobalIPv6FromAddr(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
addr string
|
|
ok bool
|
|
}{
|
|
{name: "global", addr: "2001:db8::10/64", ok: true},
|
|
{name: "ula", addr: "fd00::10/64", ok: false},
|
|
{name: "link local", addr: "fe80::10%en0/64", ok: false},
|
|
{name: "ipv4", addr: "192.0.2.10/24", ok: false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, ok := globalIPv6FromAddr(testAddr(tt.addr))
|
|
if ok != tt.ok {
|
|
t.Fatalf("globalIPv6FromAddr(%q) ok = %v, want %v", tt.addr, ok, tt.ok)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListenGlobalIPv6CurrentHost(t *testing.T) {
|
|
if os.Getenv("TUTTI_TEST_GLOBAL_IPV6") != "1" {
|
|
t.Skip("set TUTTI_TEST_GLOBAL_IPV6=1 to probe physical IPv6 candidates")
|
|
}
|
|
endpoints, err := ListenGlobalIPv6(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("ListenGlobalIPv6() error = %v", err)
|
|
}
|
|
defer func() {
|
|
for _, endpoint := range endpoints {
|
|
_ = endpoint.Close()
|
|
}
|
|
}()
|
|
for _, endpoint := range endpoints {
|
|
if strings.HasPrefix(strings.ToLower(endpoint.Candidate.InterfaceName), "utun") {
|
|
t.Fatalf("ListenGlobalIPv6() returned TUN candidate on %q", endpoint.Candidate.InterfaceName)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQUICMutualPinningAndStreamRoundTrip(t *testing.T) {
|
|
callerUDP := listenLoopbackUDP6(t)
|
|
ownerUDP := listenLoopbackUDP6(t)
|
|
runQUICRoundTrip(t, context.Background(), callerUDP, ownerUDP, nil, nil, ALPN)
|
|
}
|
|
|
|
func TestQUICRollingALPNCompatibility(t *testing.T) {
|
|
legacy := "tsh-device-link-p2p/2"
|
|
tests := []struct {
|
|
name string
|
|
callerProtocols []string
|
|
ownerProtocols []string
|
|
}{
|
|
{name: "new caller old owner", callerProtocols: []string{ALPN, legacy}, ownerProtocols: []string{legacy}},
|
|
{name: "old caller new owner", callerProtocols: []string{legacy}, ownerProtocols: []string{ALPN, legacy}},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
runQUICRoundTrip(
|
|
t,
|
|
context.Background(),
|
|
listenLoopbackUDP6(t),
|
|
listenLoopbackUDP6(t),
|
|
tt.callerProtocols,
|
|
tt.ownerProtocols,
|
|
legacy,
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
func runQUICRoundTrip(
|
|
t *testing.T,
|
|
parent context.Context,
|
|
callerUDP, ownerUDP *net.UDPConn,
|
|
callerProtocols, ownerProtocols []string,
|
|
expectedProtocol string,
|
|
) {
|
|
t.Helper()
|
|
callerEndpoint, err := NewQUICEndpoint(callerUDP)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer callerEndpoint.Close()
|
|
ownerEndpoint, err := NewQUICEndpoint(ownerUDP)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ownerEndpoint.Close()
|
|
|
|
callerIdentity, err := NewEphemeralIdentity(time.Now())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ownerIdentity, err := NewEphemeralIdentity(time.Now())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
serverTLS, err := ownerIdentity.ServerTLSConfigForProtocols(callerIdentity.Fingerprint, ownerProtocols)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
clientTLS, err := callerIdentity.ClientTLSConfigForProtocols(ownerIdentity.Fingerprint, callerProtocols)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
listener, err := ownerEndpoint.Listen(serverTLS)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer listener.Close()
|
|
|
|
ctx, cancel := context.WithTimeout(parent, 5*time.Second)
|
|
defer cancel()
|
|
serverDone := make(chan error, 1)
|
|
go func() {
|
|
session, err := listener.Accept(ctx)
|
|
if err != nil {
|
|
serverDone <- err
|
|
return
|
|
}
|
|
defer session.Close()
|
|
if got := session.NegotiatedProtocol(); got != expectedProtocol {
|
|
serverDone <- fmt.Errorf("server negotiated protocol = %q, want %q", got, expectedProtocol)
|
|
return
|
|
}
|
|
stream, err := session.AcceptStream(ctx)
|
|
if err != nil {
|
|
serverDone <- err
|
|
return
|
|
}
|
|
defer stream.Close()
|
|
_, err = io.Copy(stream, stream)
|
|
serverDone <- err
|
|
}()
|
|
|
|
clientSession, err := callerEndpoint.Dial(ctx, ownerUDP.LocalAddr().(*net.UDPAddr), clientTLS)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := clientSession.NegotiatedProtocol(); got != expectedProtocol {
|
|
t.Fatalf("negotiated protocol = %q, want %q", got, expectedProtocol)
|
|
}
|
|
stream, err := clientSession.OpenStream(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
payload := []byte("device-link-quic-round-trip")
|
|
if _, err := stream.Write(payload); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := make([]byte, len(payload))
|
|
if _, err := io.ReadFull(stream, got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(got) != string(payload) {
|
|
t.Fatalf("round trip = %q, want %q", got, payload)
|
|
}
|
|
_ = stream.Close()
|
|
select {
|
|
case err := <-serverDone:
|
|
if err != nil && err != io.EOF {
|
|
t.Fatalf("server error = %v", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("server did not finish")
|
|
}
|
|
_ = clientSession.Close()
|
|
}
|
|
|
|
func TestQUICRejectsWrongPeerFingerprint(t *testing.T) {
|
|
identity, err := NewEphemeralIdentity(time.Now())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
other, err := NewEphemeralIdentity(time.Now())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
wrong, err := NewEphemeralIdentity(time.Now())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
config, err := identity.ClientTLSConfig(other.Fingerprint)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := config.VerifyPeerCertificate(wrong.Certificate.Certificate, nil); err == nil {
|
|
t.Fatal("VerifyPeerCertificate() accepted wrong fingerprint")
|
|
}
|
|
}
|
|
|
|
func listenLoopbackUDP6(t *testing.T) *net.UDPConn {
|
|
t.Helper()
|
|
conn, err := net.ListenUDP("udp6", &net.UDPAddr{IP: net.ParseIP("::1")})
|
|
if err != nil {
|
|
t.Fatalf("ListenUDP() error = %v", err)
|
|
}
|
|
return conn
|
|
}
|
|
|
|
type testAddr string
|
|
|
|
func (testAddr) Network() string { return "test" }
|
|
func (a testAddr) String() string { return string(a) }
|