2019-06-18 07:28:30 -07:00
|
|
|
package xmpp
|
2017-10-04 16:27:35 -07:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/xml"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"testing"
|
2017-10-20 07:53:15 -07:00
|
|
|
"time"
|
2019-06-26 08:14:52 -07:00
|
|
|
|
|
|
|
"gosrc.io/xmpp/stanza"
|
2017-10-04 16:27:35 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// Default port is not standard XMPP port to avoid interfering
|
|
|
|
// with local running XMPP server
|
|
|
|
testXMPPAddress = "localhost:15222"
|
2017-10-20 07:53:15 -07:00
|
|
|
|
|
|
|
defaultTimeout = 2 * time.Second
|
2017-10-04 16:27:35 -07:00
|
|
|
)
|
|
|
|
|
2019-12-09 03:30:37 -08:00
|
|
|
func TestEventManager(t *testing.T) {
|
|
|
|
mgr := EventManager{}
|
|
|
|
mgr.updateState(StateConnected)
|
|
|
|
if mgr.CurrentState != StateConnected {
|
|
|
|
t.Fatal("CurrentState not updated by updateState()")
|
|
|
|
}
|
|
|
|
|
|
|
|
mgr.disconnected(SMState{})
|
|
|
|
if mgr.CurrentState != StateDisconnected {
|
|
|
|
t.Fatalf("CurrentState not reset by disconnected()")
|
|
|
|
}
|
|
|
|
|
|
|
|
mgr.streamError(ErrTLSNotSupported.Error(), "")
|
|
|
|
if mgr.CurrentState != StateStreamError {
|
|
|
|
t.Fatalf("CurrentState not set by streamError()")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-04 16:27:35 -07:00
|
|
|
func TestClient_Connect(t *testing.T) {
|
|
|
|
// Setup Mock server
|
2017-10-05 17:11:28 -07:00
|
|
|
mock := ServerMock{}
|
|
|
|
mock.Start(t, testXMPPAddress, handlerConnectSuccess)
|
2017-10-04 16:27:35 -07:00
|
|
|
|
|
|
|
// Test / Check result
|
2019-10-10 21:41:15 -07:00
|
|
|
config := Config{
|
|
|
|
TransportConfiguration: TransportConfiguration{
|
|
|
|
Address: testXMPPAddress,
|
|
|
|
},
|
|
|
|
Jid: "test@localhost",
|
|
|
|
Credential: Password("test"),
|
|
|
|
Insecure: true}
|
2017-10-04 16:27:35 -07:00
|
|
|
|
|
|
|
var client *Client
|
|
|
|
var err error
|
2019-06-18 03:41:58 -07:00
|
|
|
router := NewRouter()
|
|
|
|
if client, err = NewClient(config, router); err != nil {
|
2017-10-04 16:27:35 -07:00
|
|
|
t.Errorf("connect create XMPP client: %s", err)
|
|
|
|
}
|
|
|
|
|
2019-06-08 09:09:22 -07:00
|
|
|
if err = client.Connect(); err != nil {
|
2017-10-04 16:27:35 -07:00
|
|
|
t.Errorf("XMPP connection failed: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
mock.Stop()
|
|
|
|
}
|
|
|
|
|
2017-10-21 05:49:25 -07:00
|
|
|
func TestClient_NoInsecure(t *testing.T) {
|
|
|
|
// Setup Mock server
|
|
|
|
mock := ServerMock{}
|
2017-10-21 06:14:13 -07:00
|
|
|
mock.Start(t, testXMPPAddress, handlerAbortTLS)
|
2017-10-21 05:49:25 -07:00
|
|
|
|
|
|
|
// Test / Check result
|
2019-10-10 21:41:15 -07:00
|
|
|
config := Config{
|
|
|
|
TransportConfiguration: TransportConfiguration{
|
|
|
|
Address: testXMPPAddress,
|
|
|
|
},
|
|
|
|
Jid: "test@localhost",
|
|
|
|
Credential: Password("test"),
|
|
|
|
}
|
2017-10-21 05:49:25 -07:00
|
|
|
|
|
|
|
var client *Client
|
|
|
|
var err error
|
2019-06-18 03:41:58 -07:00
|
|
|
router := NewRouter()
|
|
|
|
if client, err = NewClient(config, router); err != nil {
|
2017-10-21 05:49:25 -07:00
|
|
|
t.Errorf("cannot create XMPP client: %s", err)
|
|
|
|
}
|
|
|
|
|
2019-06-08 09:09:22 -07:00
|
|
|
if err = client.Connect(); err == nil {
|
2017-10-21 05:49:25 -07:00
|
|
|
// When insecure is not allowed:
|
|
|
|
t.Errorf("should fail as insecure connection is not allowed and server does not support TLS")
|
|
|
|
}
|
|
|
|
|
|
|
|
mock.Stop()
|
|
|
|
}
|
|
|
|
|
2019-06-10 07:27:52 -07:00
|
|
|
// Check that the client is properly tracking features, as session negotiation progresses.
|
|
|
|
func TestClient_FeaturesTracking(t *testing.T) {
|
|
|
|
// Setup Mock server
|
|
|
|
mock := ServerMock{}
|
|
|
|
mock.Start(t, testXMPPAddress, handlerAbortTLS)
|
|
|
|
|
|
|
|
// Test / Check result
|
2019-10-10 21:41:15 -07:00
|
|
|
config := Config{
|
|
|
|
TransportConfiguration: TransportConfiguration{
|
|
|
|
Address: testXMPPAddress,
|
|
|
|
},
|
|
|
|
Jid: "test@localhost",
|
|
|
|
Credential: Password("test"),
|
|
|
|
}
|
2019-06-10 07:27:52 -07:00
|
|
|
|
|
|
|
var client *Client
|
|
|
|
var err error
|
2019-06-18 03:41:58 -07:00
|
|
|
router := NewRouter()
|
|
|
|
if client, err = NewClient(config, router); err != nil {
|
2019-06-10 07:27:52 -07:00
|
|
|
t.Errorf("cannot create XMPP client: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = client.Connect(); err == nil {
|
|
|
|
// When insecure is not allowed:
|
|
|
|
t.Errorf("should fail as insecure connection is not allowed and server does not support TLS")
|
|
|
|
}
|
|
|
|
|
|
|
|
mock.Stop()
|
2019-06-29 08:39:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestClient_RFC3921Session(t *testing.T) {
|
|
|
|
// Setup Mock server
|
|
|
|
mock := ServerMock{}
|
|
|
|
mock.Start(t, testXMPPAddress, handlerConnectWithSession)
|
|
|
|
|
|
|
|
// Test / Check result
|
2019-10-10 21:41:15 -07:00
|
|
|
config := Config{
|
|
|
|
TransportConfiguration: TransportConfiguration{
|
|
|
|
Address: testXMPPAddress,
|
|
|
|
},
|
|
|
|
Jid: "test@localhost",
|
|
|
|
Credential: Password("test"),
|
|
|
|
Insecure: true,
|
|
|
|
}
|
2019-06-29 08:39:19 -07:00
|
|
|
|
|
|
|
var client *Client
|
|
|
|
var err error
|
|
|
|
router := NewRouter()
|
|
|
|
if client, err = NewClient(config, router); err != nil {
|
|
|
|
t.Errorf("connect create XMPP client: %s", err)
|
|
|
|
}
|
2019-06-10 07:27:52 -07:00
|
|
|
|
2019-06-29 08:39:19 -07:00
|
|
|
if err = client.Connect(); err != nil {
|
|
|
|
t.Errorf("XMPP connection failed: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
mock.Stop()
|
2019-06-10 07:27:52 -07:00
|
|
|
}
|
|
|
|
|
2017-10-04 16:27:35 -07:00
|
|
|
//=============================================================================
|
|
|
|
// Basic XMPP Server Mock Handlers.
|
|
|
|
|
|
|
|
const serverStreamOpen = "<?xml version='1.0'?><stream:stream to='%s' id='%s' xmlns='%s' xmlns:stream='%s' version='1.0'>"
|
|
|
|
|
2019-06-29 08:39:19 -07:00
|
|
|
// Test connection with a basic straightforward workflow
|
2017-10-05 17:11:28 -07:00
|
|
|
func handlerConnectSuccess(t *testing.T, c net.Conn) {
|
2017-10-04 16:27:35 -07:00
|
|
|
decoder := xml.NewDecoder(c)
|
2017-10-05 17:11:28 -07:00
|
|
|
checkOpenStream(t, c, decoder)
|
2017-10-04 16:27:35 -07:00
|
|
|
|
2017-10-20 07:53:15 -07:00
|
|
|
sendStreamFeatures(t, c, decoder) // Send initial features
|
2017-10-04 16:27:35 -07:00
|
|
|
readAuth(t, decoder)
|
|
|
|
fmt.Fprintln(c, "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>")
|
|
|
|
|
2017-10-20 07:53:15 -07:00
|
|
|
checkOpenStream(t, c, decoder) // Reset stream
|
|
|
|
sendBindFeature(t, c, decoder) // Send post auth features
|
2017-10-04 16:27:35 -07:00
|
|
|
bind(t, c, decoder)
|
|
|
|
}
|
|
|
|
|
2017-10-21 06:14:13 -07:00
|
|
|
// We expect client will abort on TLS
|
|
|
|
func handlerAbortTLS(t *testing.T, c net.Conn) {
|
|
|
|
decoder := xml.NewDecoder(c)
|
|
|
|
checkOpenStream(t, c, decoder)
|
|
|
|
sendStreamFeatures(t, c, decoder) // Send initial features
|
|
|
|
}
|
|
|
|
|
2019-06-29 08:39:19 -07:00
|
|
|
// Test connection with mandatory session (RFC-3921)
|
|
|
|
func handlerConnectWithSession(t *testing.T, c net.Conn) {
|
|
|
|
decoder := xml.NewDecoder(c)
|
|
|
|
checkOpenStream(t, c, decoder)
|
|
|
|
|
|
|
|
sendStreamFeatures(t, c, decoder) // Send initial features
|
|
|
|
readAuth(t, decoder)
|
|
|
|
fmt.Fprintln(c, "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>")
|
|
|
|
|
|
|
|
checkOpenStream(t, c, decoder) // Reset stream
|
|
|
|
sendRFC3921Feature(t, c, decoder) // Send post auth features
|
|
|
|
bind(t, c, decoder)
|
|
|
|
session(t, c, decoder)
|
|
|
|
}
|
|
|
|
|
2017-10-05 17:11:28 -07:00
|
|
|
func checkOpenStream(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
2017-10-20 07:53:15 -07:00
|
|
|
c.SetDeadline(time.Now().Add(defaultTimeout))
|
|
|
|
defer c.SetDeadline(time.Time{})
|
|
|
|
|
|
|
|
for { // TODO clean up. That for loop is not elegant and I prefer bounded recursion.
|
2017-10-04 16:27:35 -07:00
|
|
|
var token xml.Token
|
|
|
|
token, err := decoder.Token()
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("cannot read next token: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch elem := token.(type) {
|
|
|
|
// Wait for first startElement
|
|
|
|
case xml.StartElement:
|
2019-06-26 08:14:52 -07:00
|
|
|
if elem.Name.Space != stanza.NSStream || elem.Name.Local != "stream" {
|
2017-10-04 16:27:35 -07:00
|
|
|
err = errors.New("xmpp: expected <stream> but got <" + elem.Name.Local + "> in " + elem.Name.Space)
|
2017-10-05 17:11:28 -07:00
|
|
|
return
|
|
|
|
}
|
2019-06-26 08:14:52 -07:00
|
|
|
if _, err := fmt.Fprintf(c, serverStreamOpen, "localhost", "streamid1", stanza.NSClient, stanza.NSStream); err != nil {
|
2017-10-05 17:11:28 -07:00
|
|
|
t.Errorf("cannot write server stream open: %s", err)
|
2017-10-04 16:27:35 -07:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-20 07:53:15 -07:00
|
|
|
func sendStreamFeatures(t *testing.T, c net.Conn, _ *xml.Decoder) {
|
2017-10-04 16:27:35 -07:00
|
|
|
// This is a basic server, supporting only 1 stream feature: SASL Plain Auth
|
|
|
|
features := `<stream:features>
|
|
|
|
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
|
|
|
<mechanism>PLAIN</mechanism>
|
|
|
|
</mechanisms>
|
|
|
|
</stream:features>`
|
|
|
|
if _, err := fmt.Fprintln(c, features); err != nil {
|
|
|
|
t.Errorf("cannot send stream feature: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO return err in case of error reading the auth params
|
|
|
|
func readAuth(t *testing.T, decoder *xml.Decoder) string {
|
2019-06-26 08:14:52 -07:00
|
|
|
se, err := stanza.NextStart(decoder)
|
2017-10-04 16:27:35 -07:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("cannot read auth: %s", err)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
var nv interface{}
|
2019-06-29 07:49:54 -07:00
|
|
|
nv = &stanza.SASLAuth{}
|
2017-10-04 16:27:35 -07:00
|
|
|
// Decode element into pointer storage
|
|
|
|
if err = decoder.DecodeElement(nv, &se); err != nil {
|
|
|
|
t.Errorf("cannot decode auth: %s", err)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
switch v := nv.(type) {
|
2019-06-29 07:49:54 -07:00
|
|
|
case *stanza.SASLAuth:
|
2017-10-04 16:27:35 -07:00
|
|
|
return v.Value
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2017-10-20 07:53:15 -07:00
|
|
|
func sendBindFeature(t *testing.T, c net.Conn, _ *xml.Decoder) {
|
2019-06-29 08:39:19 -07:00
|
|
|
// This is a basic server, supporting only 1 stream feature after auth: resource binding
|
2017-10-04 16:27:35 -07:00
|
|
|
features := `<stream:features>
|
|
|
|
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
|
|
|
|
</stream:features>`
|
|
|
|
if _, err := fmt.Fprintln(c, features); err != nil {
|
|
|
|
t.Errorf("cannot send stream feature: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-29 08:39:19 -07:00
|
|
|
func sendRFC3921Feature(t *testing.T, c net.Conn, _ *xml.Decoder) {
|
|
|
|
// This is a basic server, supporting only 2 features after auth: resource & session binding
|
|
|
|
features := `<stream:features>
|
|
|
|
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
|
|
|
|
<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
|
|
|
|
</stream:features>`
|
|
|
|
if _, err := fmt.Fprintln(c, features); err != nil {
|
|
|
|
t.Errorf("cannot send stream feature: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-04 16:27:35 -07:00
|
|
|
func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
2019-06-26 08:14:52 -07:00
|
|
|
se, err := stanza.NextStart(decoder)
|
2017-10-04 16:27:35 -07:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("cannot read bind: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-06-26 08:14:52 -07:00
|
|
|
iq := &stanza.IQ{}
|
2017-10-04 16:27:35 -07:00
|
|
|
// Decode element into pointer storage
|
|
|
|
if err = decoder.DecodeElement(&iq, &se); err != nil {
|
|
|
|
t.Errorf("cannot decode bind iq: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-01-15 03:28:34 -08:00
|
|
|
// TODO Check all elements
|
2019-06-19 01:21:57 -07:00
|
|
|
switch iq.Payload.(type) {
|
2019-06-29 07:49:54 -07:00
|
|
|
case *stanza.Bind:
|
2017-10-20 07:53:15 -07:00
|
|
|
result := `<iq id='%s' type='result'>
|
2017-10-04 16:27:35 -07:00
|
|
|
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
|
|
|
|
<jid>%s</jid>
|
|
|
|
</bind>
|
|
|
|
</iq>`
|
2017-10-20 07:53:15 -07:00
|
|
|
fmt.Fprintf(c, result, iq.Id, "test@localhost/test") // TODO use real JID
|
|
|
|
}
|
2017-10-04 16:27:35 -07:00
|
|
|
}
|
2019-06-29 08:39:19 -07:00
|
|
|
|
|
|
|
func session(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
|
|
|
se, err := stanza.NextStart(decoder)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("cannot read session: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
iq := &stanza.IQ{}
|
|
|
|
// Decode element into pointer storage
|
|
|
|
if err = decoder.DecodeElement(&iq, &se); err != nil {
|
|
|
|
t.Errorf("cannot decode session iq: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch iq.Payload.(type) {
|
|
|
|
case *stanza.StreamSession:
|
|
|
|
result := `<iq id='%s' type='result'/>`
|
|
|
|
fmt.Fprintf(c, result, iq.Id)
|
|
|
|
}
|
|
|
|
}
|