// SPDX-FileCopyrightText: 2026 Echolot contributors // SPDX-License-Identifier: GPL-3.0-or-later package control import ( "net/http" "net/http/httptest" "strings" "testing" "time" "echo-lot.app/server/internal/store" ) // devtoolsFixture is a server with one enrolled device, and that device's credential. func devtoolsFixture(t *testing.T) (*Server, string) { t.Helper() st, err := store.Open(t.TempDir()) if err != nil { t.Fatal(err) } st.SetADBEndpointRetention(24 * time.Hour) tok, err := st.NewEnrollToken(time.Hour, "test") if err != nil { t.Fatal(err) } dev, err := st.Redeem(tok, "tablet") if err != nil { t.Fatal(err) } return &Server{Store: st}, dev.Credential } func postEndpoint(t *testing.T, s *Server, cred, body string) *httptest.ResponseRecorder { t.Helper() req := httptest.NewRequest("POST", "/v1/devtools/adb-endpoint", strings.NewReader(body)) if cred != "" { req.Header.Set("Authorization", "Bearer "+cred) } rec := httptest.NewRecorder() s.Handler().ServeHTTP(rec, req) return rec } // The receiver this replaces took a port report from anyone who could reach it. This one does not. func TestADBEndpointNeedsADeviceCredential(t *testing.T) { s, _ := devtoolsFixture(t) rec := postEndpoint(t, s, "", `{"host":"10.13.102.128","port":45305}`) if rec.Code != http.StatusUnauthorized { t.Fatalf("no credential: code=%d, want 401", rec.Code) } rec = postEndpoint(t, s, "not-a-credential", `{"host":"10.13.102.128","port":45305}`) if rec.Code != http.StatusUnauthorized { t.Fatalf("wrong credential: code=%d, want 401", rec.Code) } if got := s.Store.ADBEndpoints(); len(got) != 0 { t.Fatalf("an unauthenticated report was stored: %+v", got) } } func TestADBEndpointRejectsImplausibleHostAndPort(t *testing.T) { s, cred := devtoolsFixture(t) for _, body := range []string{ `{"host":"","port":45305}`, `{"host":"10.13.102.128 && rm -rf /","port":45305}`, `{"host":"not a host","port":45305}`, `{"host":"-bad.example","port":45305}`, `{"host":"10.13.102.128","port":0}`, `{"host":"10.13.102.128","port":65536}`, `{"host":"10.13.102.128","port":-1}`, `not json at all`, } { rec := postEndpoint(t, s, cred, body) if rec.Code != http.StatusBadRequest { t.Errorf("%s: code=%d, want 400 (%s)", body, rec.Code, strings.TrimSpace(rec.Body.String())) } } if got := s.Store.ADBEndpoints(); len(got) != 0 { t.Fatalf("a rejected report was stored anyway: %+v", got) } } func TestADBEndpointStoresTheObservedSourceAndTheCallersDeviceID(t *testing.T) { s, cred := devtoolsFixture(t) rec := postEndpoint(t, s, cred, `{"host":"10.13.102.128","port":45305,"device_name":"TB330FU","note":"wireless debugging"}`) if rec.Code != http.StatusCreated { t.Fatalf("code=%d body=%s", rec.Code, rec.Body.String()) } got := s.Store.ADBEndpoints() if len(got) != 1 { t.Fatalf("got %d rows, want 1", len(got)) } e := got[0] if e.Host != "10.13.102.128" || e.Port != 45305 || e.DeviceName != "TB330FU" { t.Fatalf("report not stored as sent: %+v", e) } // The device id comes from the credential and the source IP from the connection, so neither is // something the body can claim. if e.Device == "" { t.Fatal("the submitting device was not recorded") } if e.SourceIP != "192.0.2.1" { // httptest's RemoteAddr t.Fatalf("source IP = %q, want the observed remote address", e.SourceIP) } if e.ReportedAt.IsZero() { t.Fatal("no server-side timestamp was recorded") } } func TestPlausibleHost(t *testing.T) { for _, tc := range []struct { host string want bool }{ {"10.13.102.128", true}, {"192.168.1.1", true}, {"fe80::1", true}, {"[2001:db8::1]", true}, {"tablet.lan", true}, {"tablet", true}, {"a-b.example.net.", true}, {"", false}, {"not a host", false}, {"10.0.0.1:5555", false}, // the port is its own field; a host must not smuggle one {"-lead.example", false}, {"trail-.example", false}, {"a..b", false}, {"http://10.0.0.1", false}, {strings.Repeat("x", 254), false}, } { if got := plausibleHost(tc.host); got != tc.want { t.Errorf("plausibleHost(%q) = %v, want %v", tc.host, got, tc.want) } } }