mirror of
https://dev.narayana.im/narayana/telegabber.git
synced 2026-08-05 04:07:07 +00:00
181 lines
5.8 KiB
Go
181 lines
5.8 KiB
Go
package xmppsig
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pion/webrtc/v4"
|
|
"gosrc.io/xmpp/stanza"
|
|
)
|
|
|
|
func TestExtractServices_ProsodyResponse(t *testing.T) {
|
|
// verbatim shape from prosody 0.12 mod_external_services
|
|
raw := []byte(`<iq xmlns="jabber:client" type="result" from="localhost" to="probe@localhost/abc" id="x">` +
|
|
`<services xmlns="urn:xmpp:extdisco:2">` +
|
|
`<service transport="udp" port="3478" host="172.18.0.1" type="stun"/>` +
|
|
`<service restricted="1" port="3478" transport="udp" username="1779481470" password="F5aXytaIwbEJWt6FJ66OqeEQtvw=" host="172.18.0.1" type="turn"/>` +
|
|
`</services>` +
|
|
`</iq>`)
|
|
got, err := extractServices(raw)
|
|
if err != nil {
|
|
t.Fatalf("extractServices: %v", err)
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("want 2 services, got %d: %+v", len(got), got)
|
|
}
|
|
if got[0] != (Service{Type: "stun", Host: "172.18.0.1", Port: 3478, Transport: "udp"}) {
|
|
t.Errorf("STUN entry mismatch: %+v", got[0])
|
|
}
|
|
if got[1] != (Service{
|
|
Type: "turn", Host: "172.18.0.1", Port: 3478, Transport: "udp",
|
|
Username: "1779481470", Password: "F5aXytaIwbEJWt6FJ66OqeEQtvw=",
|
|
}) {
|
|
t.Errorf("TURN entry mismatch: %+v", got[1])
|
|
}
|
|
}
|
|
|
|
func TestToICEServers_StunAndTurn(t *testing.T) {
|
|
in := []Service{
|
|
{Type: "stun", Host: "stun.example.com", Port: 3478, Transport: "udp"},
|
|
{Type: "turn", Host: "turn.example.com", Port: 3478, Transport: "udp", Username: "u", Password: "p"},
|
|
{Type: "turns", Host: "turn.example.com", Port: 5349, Transport: "tcp", Username: "u", Password: "p"},
|
|
}
|
|
out := ToICEServers(in)
|
|
if len(out) != 3 {
|
|
t.Fatalf("want 3 servers, got %d", len(out))
|
|
}
|
|
if out[0].URLs[0] != "stun:stun.example.com:3478" {
|
|
t.Errorf("STUN URL mismatch: %q", out[0].URLs[0])
|
|
}
|
|
if out[1].URLs[0] != "turn:turn.example.com:3478?transport=udp" {
|
|
t.Errorf("TURN URL mismatch: %q", out[1].URLs[0])
|
|
}
|
|
if out[1].Username != "u" || out[1].Credential != "p" {
|
|
t.Errorf("TURN creds mismatch: user=%q cred=%v", out[1].Username, out[1].Credential)
|
|
}
|
|
if out[1].CredentialType != webrtc.ICECredentialTypePassword {
|
|
t.Errorf("TURN cred type: want Password, got %v", out[1].CredentialType)
|
|
}
|
|
if out[2].URLs[0] != "turns:turn.example.com:5349?transport=tcp" {
|
|
t.Errorf("TURNS URL mismatch: %q", out[2].URLs[0])
|
|
}
|
|
}
|
|
|
|
// pion rejects the whole PC on any creds-less turn URL, so a single
|
|
// misconfigured prosody entry must not poison every call
|
|
func TestToICEServers_SkipsBrokenAndUnknown(t *testing.T) {
|
|
in := []Service{
|
|
{Type: "stun", Host: "h", Port: 3478, Transport: "udp"},
|
|
{Type: "turn", Host: "h", Port: 3478, Transport: "udp", Username: "u", Password: "p"},
|
|
// turns with creds: kept
|
|
{Type: "turns", Host: "h", Port: 5349, Transport: "tcp", Username: "u", Password: "p"},
|
|
// turn with no creds: dropped
|
|
{Type: "turn", Host: "broken", Port: 3478, Transport: "udp"},
|
|
// turns with no creds (prosody algorithm-omission bug): dropped
|
|
{Type: "turns", Host: "broken", Port: 5349, Transport: "tcp"},
|
|
// turn with username but no password: dropped (defensive)
|
|
{Type: "turn", Host: "half", Port: 3478, Transport: "udp", Username: "u"},
|
|
// unknown type: dropped
|
|
{Type: "ftp", Host: "h", Port: 21, Transport: "tcp"},
|
|
}
|
|
out := ToICEServers(in)
|
|
if len(out) != 3 {
|
|
t.Fatalf("want 3 servers (stun + turn-with-creds + turns-with-creds), got %d: %+v", len(out), out)
|
|
}
|
|
for _, srv := range out {
|
|
for _, u := range srv.URLs {
|
|
if (containsScheme(u, "turn:") || containsScheme(u, "turns:")) && (srv.Username == "" || srv.Credential == nil || srv.Credential == "") {
|
|
t.Errorf("TURN entry kept without creds: %+v", srv)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func containsScheme(url, scheme string) bool {
|
|
return len(url) >= len(scheme) && url[:len(scheme)] == scheme
|
|
}
|
|
|
|
// minimal Sender for QueryServices E2E against a hand-crafted response
|
|
type fakeSender struct {
|
|
sent []stanza.Packet
|
|
reply func(*stanza.IQ) stanza.IQ
|
|
failSend bool
|
|
}
|
|
|
|
func (f *fakeSender) Send(p stanza.Packet) error {
|
|
if f.failSend {
|
|
return fmt.Errorf("simulated send failure")
|
|
}
|
|
f.sent = append(f.sent, p)
|
|
return nil
|
|
}
|
|
func (f *fakeSender) SendRaw(string) error { return nil }
|
|
func (f *fakeSender) SendIQ(_ context.Context, iq *stanza.IQ) (chan stanza.IQ, error) {
|
|
ch := make(chan stanza.IQ, 1)
|
|
if err := f.Send(iq); err != nil {
|
|
return nil, err
|
|
}
|
|
if f.reply != nil {
|
|
ch <- f.reply(iq)
|
|
}
|
|
return ch, nil
|
|
}
|
|
|
|
func TestQueryServices_ParsesReply(t *testing.T) {
|
|
fs := &fakeSender{
|
|
reply: func(req *stanza.IQ) stanza.IQ {
|
|
respPtr, _ := stanza.NewIQ(stanza.Attrs{
|
|
Type: stanza.IQTypeResult,
|
|
From: req.To,
|
|
To: req.From,
|
|
Id: req.Id,
|
|
})
|
|
respPtr.Payload = &servicesQuery{Services: []serviceElem{
|
|
{Type: "stun", Host: "1.2.3.4", Port: "3478", Transport: "udp"},
|
|
{Type: "turn", Host: "1.2.3.4", Port: "3478", Transport: "udp", Username: "u", Password: "p"},
|
|
}}
|
|
return *respPtr
|
|
},
|
|
}
|
|
got, err := QueryServices(fs, "tlgrm.example.com", "example.com", time.Second)
|
|
if err != nil {
|
|
t.Fatalf("QueryServices: %v", err)
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("want 2, got %d: %+v", len(got), got)
|
|
}
|
|
if got[0].Type != "stun" || got[1].Type != "turn" {
|
|
t.Errorf("unexpected service types: %+v", got)
|
|
}
|
|
if got[1].Username != "u" || got[1].Password != "p" {
|
|
t.Errorf("TURN creds lost: %+v", got[1])
|
|
}
|
|
}
|
|
|
|
func TestQueryServices_Timeout(t *testing.T) {
|
|
// no reply hook -> never delivers, must hit context timeout
|
|
fs := &fakeSender{}
|
|
_, err := QueryServices(fs, "tlgrm.example.com", "example.com", 50*time.Millisecond)
|
|
if err == nil {
|
|
t.Error("expected timeout error")
|
|
}
|
|
}
|
|
|
|
func TestQueryServices_ErrorIQ(t *testing.T) {
|
|
fs := &fakeSender{
|
|
reply: func(req *stanza.IQ) stanza.IQ {
|
|
respPtr, _ := stanza.NewIQ(stanza.Attrs{
|
|
Type: stanza.IQTypeError,
|
|
From: req.To,
|
|
To: req.From,
|
|
Id: req.Id,
|
|
})
|
|
return *respPtr
|
|
},
|
|
}
|
|
if _, err := QueryServices(fs, "tlgrm.example.com", "example.com", time.Second); err == nil {
|
|
t.Error("expected error on <iq type='error'>")
|
|
}
|
|
}
|