Fixed decoder usage.
Decoders have internal buffering, and creating many on a single TCP connection can cause issues in parsing exchanged XML documents.
This commit is contained in:
parent
fd48f52f3d
commit
3c9b0db5b8
|
@ -5,7 +5,6 @@ import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -157,10 +156,10 @@ func TestClient_RFC3921Session(t *testing.T) {
|
||||||
func TestClient_SendIQ(t *testing.T) {
|
func TestClient_SendIQ(t *testing.T) {
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
// Handler for Mock server
|
// Handler for Mock server
|
||||||
h := func(t *testing.T, c net.Conn) {
|
h := func(t *testing.T, sc *ServerConn) {
|
||||||
handlerClientConnectSuccess(t, c)
|
handlerClientConnectSuccess(t, sc)
|
||||||
discardPresence(t, c)
|
discardPresence(t, sc)
|
||||||
respondToIQ(t, c)
|
respondToIQ(t, sc)
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}
|
}
|
||||||
client, mock := mockClientConnection(t, h, testClientIqPort)
|
client, mock := mockClientConnection(t, h, testClientIqPort)
|
||||||
|
@ -199,10 +198,10 @@ func TestClient_SendIQ(t *testing.T) {
|
||||||
func TestClient_SendIQFail(t *testing.T) {
|
func TestClient_SendIQFail(t *testing.T) {
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
// Handler for Mock server
|
// Handler for Mock server
|
||||||
h := func(t *testing.T, c net.Conn) {
|
h := func(t *testing.T, sc *ServerConn) {
|
||||||
handlerClientConnectSuccess(t, c)
|
handlerClientConnectSuccess(t, sc)
|
||||||
discardPresence(t, c)
|
discardPresence(t, sc)
|
||||||
respondToIQ(t, c)
|
respondToIQ(t, sc)
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}
|
}
|
||||||
client, mock := mockClientConnection(t, h, testClientIqFailPort)
|
client, mock := mockClientConnection(t, h, testClientIqFailPort)
|
||||||
|
@ -244,10 +243,10 @@ func TestClient_SendIQFail(t *testing.T) {
|
||||||
func TestClient_SendRaw(t *testing.T) {
|
func TestClient_SendRaw(t *testing.T) {
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
// Handler for Mock server
|
// Handler for Mock server
|
||||||
h := func(t *testing.T, c net.Conn) {
|
h := func(t *testing.T, sc *ServerConn) {
|
||||||
handlerClientConnectSuccess(t, c)
|
handlerClientConnectSuccess(t, sc)
|
||||||
discardPresence(t, c)
|
discardPresence(t, sc)
|
||||||
respondToIQ(t, c)
|
respondToIQ(t, sc)
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}
|
}
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
|
@ -365,48 +364,44 @@ func TestClient_DisconnectStreamManager(t *testing.T) {
|
||||||
// Basic XMPP Server Mock Handlers.
|
// Basic XMPP Server Mock Handlers.
|
||||||
|
|
||||||
// Test connection with a basic straightforward workflow
|
// Test connection with a basic straightforward workflow
|
||||||
func handlerClientConnectSuccess(t *testing.T, c net.Conn) {
|
func handlerClientConnectSuccess(t *testing.T, sc *ServerConn) {
|
||||||
decoder := xml.NewDecoder(c)
|
checkClientOpenStream(t, sc)
|
||||||
checkClientOpenStream(t, c, decoder)
|
sendStreamFeatures(t, sc) // Send initial features
|
||||||
|
readAuth(t, sc.decoder)
|
||||||
|
fmt.Fprintln(sc.connection, "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>")
|
||||||
|
|
||||||
sendStreamFeatures(t, c, decoder) // Send initial features
|
checkClientOpenStream(t, sc) // Reset stream
|
||||||
readAuth(t, decoder)
|
sendBindFeature(t, sc) // Send post auth features
|
||||||
fmt.Fprintln(c, "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>")
|
bind(t, sc)
|
||||||
|
|
||||||
checkClientOpenStream(t, c, decoder) // Reset stream
|
|
||||||
sendBindFeature(t, c, decoder) // Send post auth features
|
|
||||||
bind(t, c, decoder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We expect client will abort on TLS
|
// We expect client will abort on TLS
|
||||||
func handlerAbortTLS(t *testing.T, c net.Conn) {
|
func handlerAbortTLS(t *testing.T, sc *ServerConn) {
|
||||||
decoder := xml.NewDecoder(c)
|
checkClientOpenStream(t, sc)
|
||||||
checkClientOpenStream(t, c, decoder)
|
sendStreamFeatures(t, sc) // Send initial features
|
||||||
sendStreamFeatures(t, c, decoder) // Send initial features
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test connection with mandatory session (RFC-3921)
|
// Test connection with mandatory session (RFC-3921)
|
||||||
func handlerClientConnectWithSession(t *testing.T, c net.Conn) {
|
func handlerClientConnectWithSession(t *testing.T, sc *ServerConn) {
|
||||||
decoder := xml.NewDecoder(c)
|
checkClientOpenStream(t, sc)
|
||||||
checkClientOpenStream(t, c, decoder)
|
|
||||||
|
|
||||||
sendStreamFeatures(t, c, decoder) // Send initial features
|
sendStreamFeatures(t, sc) // Send initial features
|
||||||
readAuth(t, decoder)
|
readAuth(t, sc.decoder)
|
||||||
fmt.Fprintln(c, "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>")
|
fmt.Fprintln(sc.connection, "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>")
|
||||||
|
|
||||||
checkClientOpenStream(t, c, decoder) // Reset stream
|
checkClientOpenStream(t, sc) // Reset stream
|
||||||
sendRFC3921Feature(t, c, decoder) // Send post auth features
|
sendRFC3921Feature(t, sc) // Send post auth features
|
||||||
bind(t, c, decoder)
|
bind(t, sc)
|
||||||
session(t, c, decoder)
|
session(t, sc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkClientOpenStream(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
func checkClientOpenStream(t *testing.T, sc *ServerConn) {
|
||||||
c.SetDeadline(time.Now().Add(defaultTimeout))
|
sc.connection.SetDeadline(time.Now().Add(defaultTimeout))
|
||||||
defer c.SetDeadline(time.Time{})
|
defer sc.connection.SetDeadline(time.Time{})
|
||||||
|
|
||||||
for { // TODO clean up. That for loop is not elegant and I prefer bounded recursion.
|
for { // TODO clean up. That for loop is not elegant and I prefer bounded recursion.
|
||||||
var token xml.Token
|
var token xml.Token
|
||||||
token, err := decoder.Token()
|
token, err := sc.decoder.Token()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("cannot read next token: %s", err)
|
t.Errorf("cannot read next token: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -418,7 +413,7 @@ func checkClientOpenStream(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
||||||
err = errors.New("xmpp: expected <stream> but got <" + elem.Name.Local + "> in " + elem.Name.Space)
|
err = errors.New("xmpp: expected <stream> but got <" + elem.Name.Local + "> in " + elem.Name.Space)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err := fmt.Fprintf(c, serverStreamOpen, "localhost", "streamid1", stanza.NSClient, stanza.NSStream); err != nil {
|
if _, err := fmt.Fprintf(sc.connection, serverStreamOpen, "localhost", "streamid1", stanza.NSClient, stanza.NSStream); err != nil {
|
||||||
t.Errorf("cannot write server stream open: %s", err)
|
t.Errorf("cannot write server stream open: %s", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -426,8 +421,8 @@ func checkClientOpenStream(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mockClientConnection(t *testing.T, serverHandler func(*testing.T, net.Conn), port int) (*Client, ServerMock) {
|
func mockClientConnection(t *testing.T, serverHandler func(*testing.T, *ServerConn), port int) (*Client, *ServerMock) {
|
||||||
mock := ServerMock{}
|
mock := &ServerMock{}
|
||||||
testServerAddress := fmt.Sprintf("%s:%d", testClientDomain, port)
|
testServerAddress := fmt.Sprintf("%s:%d", testClientDomain, port)
|
||||||
|
|
||||||
mock.Start(t, testServerAddress, serverHandler)
|
mock.Start(t, testServerAddress, serverHandler)
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"gosrc.io/xmpp/stanza"
|
"gosrc.io/xmpp/stanza"
|
||||||
"net"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -36,7 +35,7 @@ func TestHandshake(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests connection process with a handshake exchange
|
// Tests connection process with a handshake exchange
|
||||||
// Tests multiple session IDs. All connections should generate a unique stream ID
|
// Tests multiple session IDs. All serverConnections should generate a unique stream ID
|
||||||
func TestGenerateHandshakeId(t *testing.T) {
|
func TestGenerateHandshakeId(t *testing.T) {
|
||||||
// Using this array with a channel to make a queue of values to test
|
// Using this array with a channel to make a queue of values to test
|
||||||
// These are stream IDs that will be used to test the connection process, mixing them with the "secret" to generate
|
// These are stream IDs that will be used to test the connection process, mixing them with the "secret" to generate
|
||||||
|
@ -56,11 +55,11 @@ func TestGenerateHandshakeId(t *testing.T) {
|
||||||
|
|
||||||
// Performs a Component connection with a handshake. It expects to have an ID sent its way through the "uchan"
|
// Performs a Component connection with a handshake. It expects to have an ID sent its way through the "uchan"
|
||||||
// channel of this file. Otherwise it will hang for ever.
|
// channel of this file. Otherwise it will hang for ever.
|
||||||
h := func(t *testing.T, c net.Conn) {
|
h := func(t *testing.T, sc *ServerConn) {
|
||||||
decoder := xml.NewDecoder(c)
|
|
||||||
checkOpenStreamHandshakeID(t, c, decoder, <-uchan)
|
checkOpenStreamHandshakeID(t, sc, <-uchan)
|
||||||
readHandshakeComponent(t, decoder)
|
readHandshakeComponent(t, sc.decoder)
|
||||||
fmt.Fprintln(c, "<handshake/>") // That's all the server needs to return (see xep-0114)
|
fmt.Fprintln(sc.connection, "<handshake/>") // That's all the server needs to return (see xep-0114)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,8 +121,8 @@ func TestDecoder(t *testing.T) {
|
||||||
// Tests sending an IQ to the server, and getting the response
|
// Tests sending an IQ to the server, and getting the response
|
||||||
func TestSendIq(t *testing.T) {
|
func TestSendIq(t *testing.T) {
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
h := func(t *testing.T, c net.Conn) {
|
h := func(t *testing.T, sc *ServerConn) {
|
||||||
handlerForComponentIQSend(t, c)
|
handlerForComponentIQSend(t, sc)
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,8 +163,8 @@ func TestSendIq(t *testing.T) {
|
||||||
// Checking that error handling is done properly client side when an invalid IQ is sent and the server responds in kind.
|
// Checking that error handling is done properly client side when an invalid IQ is sent and the server responds in kind.
|
||||||
func TestSendIqFail(t *testing.T) {
|
func TestSendIqFail(t *testing.T) {
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
h := func(t *testing.T, c net.Conn) {
|
h := func(t *testing.T, sc *ServerConn) {
|
||||||
handlerForComponentIQSend(t, c)
|
handlerForComponentIQSend(t, sc)
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}
|
}
|
||||||
//Connecting to a mock server, initialized with given port and handler function
|
//Connecting to a mock server, initialized with given port and handler function
|
||||||
|
@ -213,27 +212,30 @@ func TestSendIqFail(t *testing.T) {
|
||||||
func TestSendRaw(t *testing.T) {
|
func TestSendRaw(t *testing.T) {
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
// Handler for the mock server
|
// Handler for the mock server
|
||||||
h := func(t *testing.T, c net.Conn) {
|
h := func(t *testing.T, sc *ServerConn) {
|
||||||
// Completes the connection by exchanging handshakes
|
// Completes the connection by exchanging handshakes
|
||||||
handlerForComponentHandshakeDefaultID(t, c)
|
handlerForComponentHandshakeDefaultID(t, sc)
|
||||||
receiveIq(c, xml.NewDecoder(c))
|
respondToIQ(t, sc)
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
req string
|
req string
|
||||||
shouldErr bool
|
shouldErr bool
|
||||||
|
port int
|
||||||
}
|
}
|
||||||
testRequests := make(map[string]testCase)
|
testRequests := make(map[string]testCase)
|
||||||
// Sending a correct IQ of type get. Not supposed to err
|
// Sending a correct IQ of type get. Not supposed to err
|
||||||
testRequests["Correct IQ"] = testCase{
|
testRequests["Correct IQ"] = testCase{
|
||||||
req: `<iq type="get" id="91bd0bba-012f-4d92-bb17-5fc41e6fe545" from="test1@localhost/mremond-mbp" to="testServer" lang="en"><query xmlns="http://jabber.org/protocol/disco#info"></query></iq>`,
|
req: `<iq type="get" id="91bd0bba-012f-4d92-bb17-5fc41e6fe545" from="test1@localhost/mremond-mbp" to="testServer" lang="en"><query xmlns="http://jabber.org/protocol/disco#info"></query></iq>`,
|
||||||
shouldErr: false,
|
shouldErr: false,
|
||||||
|
port: testSendRawPort + 100,
|
||||||
}
|
}
|
||||||
// Sending an IQ with a missing ID. Should err
|
// Sending an IQ with a missing ID. Should err
|
||||||
testRequests["IQ with missing ID"] = testCase{
|
testRequests["IQ with missing ID"] = testCase{
|
||||||
req: `<iq type="get" from="test1@localhost/mremond-mbp" to="testServer" lang="en"><query xmlns="http://jabber.org/protocol/disco#info"></query></iq>`,
|
req: `<iq type="get" from="test1@localhost/mremond-mbp" to="testServer" lang="en"><query xmlns="http://jabber.org/protocol/disco#info"></query></iq>`,
|
||||||
shouldErr: true,
|
shouldErr: true,
|
||||||
|
port: testSendRawPort + 200,
|
||||||
}
|
}
|
||||||
|
|
||||||
// A handler for the component.
|
// A handler for the component.
|
||||||
|
@ -247,7 +249,7 @@ func TestSendRaw(t *testing.T) {
|
||||||
for name, tcase := range testRequests {
|
for name, tcase := range testRequests {
|
||||||
t.Run(name, func(st *testing.T) {
|
t.Run(name, func(st *testing.T) {
|
||||||
//Connecting to a mock server, initialized with given port and handler function
|
//Connecting to a mock server, initialized with given port and handler function
|
||||||
c, m := mockComponentConnection(t, testSendRawPort, h)
|
c, m := mockComponentConnection(t, tcase.port, h)
|
||||||
c.ErrorHandler = errHandler
|
c.ErrorHandler = errHandler
|
||||||
// Sending raw xml from test case
|
// Sending raw xml from test case
|
||||||
err := c.SendRaw(tcase.req)
|
err := c.SendRaw(tcase.req)
|
||||||
|
@ -328,10 +330,10 @@ func TestStreamManagerDisconnect(t *testing.T) {
|
||||||
// Init mock server and connection
|
// Init mock server and connection
|
||||||
// Creating a mock server and connecting a Component to it. Initialized with given port and handler function
|
// Creating a mock server and connecting a Component to it. Initialized with given port and handler function
|
||||||
// The Component and mock are both returned
|
// The Component and mock are both returned
|
||||||
func mockComponentConnection(t *testing.T, port int, handler func(t *testing.T, c net.Conn)) (*Component, *ServerMock) {
|
func mockComponentConnection(t *testing.T, port int, handler func(t *testing.T, sc *ServerConn)) (*Component, *ServerMock) {
|
||||||
// Init mock server
|
// Init mock server
|
||||||
testComponentAddress := fmt.Sprintf("%s:%d", testComponentDomain, port)
|
testComponentAddress := fmt.Sprintf("%s:%d", testComponentDomain, port)
|
||||||
mock := ServerMock{}
|
mock := &ServerMock{}
|
||||||
mock.Start(t, testComponentAddress, handler)
|
mock.Start(t, testComponentAddress, handler)
|
||||||
|
|
||||||
//==================================
|
//==================================
|
||||||
|
@ -345,7 +347,9 @@ func mockComponentConnection(t *testing.T, port int, handler func(t *testing.T,
|
||||||
t.Errorf("%+v", err)
|
t.Errorf("%+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, &mock
|
// Now that the Component is connected, let's set the xml.Decoder for the server
|
||||||
|
|
||||||
|
return c, mock
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeBasicComponent(name string, mockServerAddr string, t *testing.T) *Component {
|
func makeBasicComponent(name string, mockServerAddr string, t *testing.T) *Component {
|
||||||
|
@ -380,19 +384,19 @@ func componentDefaultErrorHandler(err error) {
|
||||||
|
|
||||||
// Sends IQ response to Component request.
|
// Sends IQ response to Component request.
|
||||||
// No parsing of the request here. We just check that it's valid, and send the default response.
|
// No parsing of the request here. We just check that it's valid, and send the default response.
|
||||||
func handlerForComponentIQSend(t *testing.T, c net.Conn) {
|
func handlerForComponentIQSend(t *testing.T, sc *ServerConn) {
|
||||||
// Completes the connection by exchanging handshakes
|
// Completes the connection by exchanging handshakes
|
||||||
handlerForComponentHandshakeDefaultID(t, c)
|
handlerForComponentHandshakeDefaultID(t, sc)
|
||||||
respondToIQ(t, c)
|
respondToIQ(t, sc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used for ID and handshake related tests
|
// Used for ID and handshake related tests
|
||||||
func checkOpenStreamHandshakeID(t *testing.T, c net.Conn, decoder *xml.Decoder, streamID string) {
|
func checkOpenStreamHandshakeID(t *testing.T, sc *ServerConn, streamID string) {
|
||||||
c.SetDeadline(time.Now().Add(defaultTimeout))
|
sc.connection.SetDeadline(time.Now().Add(defaultTimeout))
|
||||||
defer c.SetDeadline(time.Time{})
|
defer sc.connection.SetDeadline(time.Time{})
|
||||||
|
|
||||||
for { // TODO clean up. That for loop is not elegant and I prefer bounded recursion.
|
for { // TODO clean up. That for loop is not elegant and I prefer bounded recursion.
|
||||||
token, err := decoder.Token()
|
token, err := sc.decoder.Token()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("cannot read next token: %s", err)
|
t.Errorf("cannot read next token: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -404,7 +408,7 @@ func checkOpenStreamHandshakeID(t *testing.T, c net.Conn, decoder *xml.Decoder,
|
||||||
err = errors.New("xmpp: expected <stream> but got <" + elem.Name.Local + "> in " + elem.Name.Space)
|
err = errors.New("xmpp: expected <stream> but got <" + elem.Name.Local + "> in " + elem.Name.Space)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err := fmt.Fprintf(c, serverStreamOpen, "localhost", streamID, stanza.NSComponent, stanza.NSStream); err != nil {
|
if _, err := fmt.Fprintf(sc.connection, serverStreamOpen, "localhost", streamID, stanza.NSComponent, stanza.NSStream); err != nil {
|
||||||
t.Errorf("cannot write server stream open: %s", err)
|
t.Errorf("cannot write server stream open: %s", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -412,16 +416,15 @@ func checkOpenStreamHandshakeID(t *testing.T, c net.Conn, decoder *xml.Decoder,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkOpenStreamHandshakeDefaultID(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
func checkOpenStreamHandshakeDefaultID(t *testing.T, sc *ServerConn) {
|
||||||
checkOpenStreamHandshakeID(t, c, decoder, defaultStreamID)
|
checkOpenStreamHandshakeID(t, sc, defaultStreamID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performs a Component connection with a handshake. It uses a default ID defined in this file as a constant.
|
// Performs a Component connection with a handshake. It uses a default ID defined in this file as a constant.
|
||||||
// This handler is supposed to fail by sending a "message" stanza instead of a <handshake/> stanza to finalize the handshake.
|
// This handler is supposed to fail by sending a "message" stanza instead of a <handshake/> stanza to finalize the handshake.
|
||||||
func handlerComponentFailedHandshakeDefaultID(t *testing.T, c net.Conn) {
|
func handlerComponentFailedHandshakeDefaultID(t *testing.T, sc *ServerConn) {
|
||||||
decoder := xml.NewDecoder(c)
|
checkOpenStreamHandshakeDefaultID(t, sc)
|
||||||
checkOpenStreamHandshakeDefaultID(t, c, decoder)
|
readHandshakeComponent(t, sc.decoder)
|
||||||
readHandshakeComponent(t, decoder)
|
|
||||||
|
|
||||||
// Send a message, instead of a "<handshake/>" tag, to fail the handshake process dans disconnect the client.
|
// Send a message, instead of a "<handshake/>" tag, to fail the handshake process dans disconnect the client.
|
||||||
me := stanza.Message{
|
me := stanza.Message{
|
||||||
|
@ -429,7 +432,7 @@ func handlerComponentFailedHandshakeDefaultID(t *testing.T, c net.Conn) {
|
||||||
Body: "Fail my handshake.",
|
Body: "Fail my handshake.",
|
||||||
}
|
}
|
||||||
s, _ := xml.Marshal(me)
|
s, _ := xml.Marshal(me)
|
||||||
fmt.Fprintln(c, string(s))
|
fmt.Fprintln(sc.connection, string(s))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -454,10 +457,9 @@ func readHandshakeComponent(t *testing.T, decoder *xml.Decoder) {
|
||||||
|
|
||||||
// Performs a Component connection with a handshake. It uses a default ID defined in this file as a constant.
|
// Performs a Component connection with a handshake. It uses a default ID defined in this file as a constant.
|
||||||
// Used in the mock server as a Handler
|
// Used in the mock server as a Handler
|
||||||
func handlerForComponentHandshakeDefaultID(t *testing.T, c net.Conn) {
|
func handlerForComponentHandshakeDefaultID(t *testing.T, sc *ServerConn) {
|
||||||
decoder := xml.NewDecoder(c)
|
checkOpenStreamHandshakeDefaultID(t, sc)
|
||||||
checkOpenStreamHandshakeDefaultID(t, c, decoder)
|
readHandshakeComponent(t, sc.decoder)
|
||||||
readHandshakeComponent(t, decoder)
|
fmt.Fprintln(sc.connection, "<handshake/>") // That's all the server needs to return (see xep-0114)
|
||||||
fmt.Fprintln(c, "<handshake/>") // That's all the server needs to return (see xep-0114)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
2
doc.go
2
doc.go
|
@ -29,7 +29,7 @@ Components
|
||||||
|
|
||||||
XMPP components can typically be used to extends the features of an XMPP
|
XMPP components can typically be used to extends the features of an XMPP
|
||||||
server, in a portable way, using component protocol over persistent TCP
|
server, in a portable way, using component protocol over persistent TCP
|
||||||
connections.
|
serverConnections.
|
||||||
|
|
||||||
Component protocol is defined in XEP-114 (https://xmpp.org/extensions/xep-0114.html).
|
Component protocol is defined in XEP-114 (https://xmpp.org/extensions/xep-0114.html).
|
||||||
|
|
||||||
|
|
|
@ -119,7 +119,7 @@ func (s *Session) startTlsIfSupported(o Config) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we do not allow cleartext connections, make it explicit that server do not support starttls
|
// If we do not allow cleartext serverConnections, make it explicit that server do not support starttls
|
||||||
if !o.Insecure {
|
if !o.Insecure {
|
||||||
s.err = errors.New("XMPP server does not advertise support for starttls")
|
s.err = errors.New("XMPP server does not advertise support for starttls")
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ const (
|
||||||
// ClientHandler is passed by the test client to provide custom behaviour to
|
// ClientHandler is passed by the test client to provide custom behaviour to
|
||||||
// the TCP server mock. This allows customizing the server behaviour to allow
|
// the TCP server mock. This allows customizing the server behaviour to allow
|
||||||
// testing clients under various scenarii.
|
// testing clients under various scenarii.
|
||||||
type ClientHandler func(t *testing.T, conn net.Conn)
|
type ClientHandler func(t *testing.T, serverConn *ServerConn)
|
||||||
|
|
||||||
// ServerMock is a simple TCP server that can be use to mock basic server
|
// ServerMock is a simple TCP server that can be use to mock basic server
|
||||||
// behaviour to test clients.
|
// behaviour to test clients.
|
||||||
|
@ -49,10 +49,15 @@ type ServerMock struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
handler ClientHandler
|
handler ClientHandler
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
connections []net.Conn
|
serverConnections []*ServerConn
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServerConn struct {
|
||||||
|
connection net.Conn
|
||||||
|
decoder *xml.Decoder
|
||||||
|
}
|
||||||
|
|
||||||
// Start launches the mock TCP server, listening to an actual address / port.
|
// Start launches the mock TCP server, listening to an actual address / port.
|
||||||
func (mock *ServerMock) Start(t *testing.T, addr string, handler ClientHandler) {
|
func (mock *ServerMock) Start(t *testing.T, addr string, handler ClientHandler) {
|
||||||
mock.t = t
|
mock.t = t
|
||||||
|
@ -68,9 +73,9 @@ func (mock *ServerMock) Stop() {
|
||||||
if mock.listener != nil {
|
if mock.listener != nil {
|
||||||
mock.listener.Close()
|
mock.listener.Close()
|
||||||
}
|
}
|
||||||
// Close all existing connections
|
// Close all existing serverConnections
|
||||||
for _, c := range mock.connections {
|
for _, c := range mock.serverConnections {
|
||||||
c.Close()
|
c.connection.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,13 +95,14 @@ func (mock *ServerMock) init(addr string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop accepts connections and creates a go routine per connection.
|
// loop accepts serverConnections and creates a go routine per connection.
|
||||||
// The go routine is running the client handler, that is used to provide the
|
// The go routine is running the client handler, that is used to provide the
|
||||||
// real TCP server behaviour.
|
// real TCP server behaviour.
|
||||||
func (mock *ServerMock) loop() {
|
func (mock *ServerMock) loop() {
|
||||||
listener := mock.listener
|
listener := mock.listener
|
||||||
for {
|
for {
|
||||||
conn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
|
serverConn := &ServerConn{conn, xml.NewDecoder(conn)}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
select {
|
select {
|
||||||
case <-mock.done:
|
case <-mock.done:
|
||||||
|
@ -106,9 +112,10 @@ func (mock *ServerMock) loop() {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mock.connections = append(mock.connections, conn)
|
mock.serverConnections = append(mock.serverConnections, serverConn)
|
||||||
|
|
||||||
// TODO Create and pass a context to cancel the handler if they are still around = avoid possible leak on complex handlers
|
// TODO Create and pass a context to cancel the handler if they are still around = avoid possible leak on complex handlers
|
||||||
go mock.handler(mock.t, conn)
|
go mock.handler(mock.t, serverConn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,27 +123,20 @@ func (mock *ServerMock) loop() {
|
||||||
// A few functions commonly used for tests. Trying to avoid duplicates in client and component test files.
|
// A few functions commonly used for tests. Trying to avoid duplicates in client and component test files.
|
||||||
//======================================================================================================================
|
//======================================================================================================================
|
||||||
|
|
||||||
func respondToIQ(t *testing.T, c net.Conn) {
|
func respondToIQ(t *testing.T, sc *ServerConn) {
|
||||||
recvBuf := make([]byte, 1024)
|
// Decoder to parse the request
|
||||||
var iqR stanza.IQ
|
iqReq, err := receiveIq(sc)
|
||||||
_, err := c.Read(recvBuf[:]) // recv data
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
t.Fatalf("failed to receive IQ : %s", err.Error())
|
||||||
t.Errorf("read timeout: %s", err)
|
|
||||||
} else {
|
|
||||||
t.Errorf("read error: %s", err)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
xml.Unmarshal(recvBuf, &iqR)
|
|
||||||
|
|
||||||
if !iqR.IsValid() {
|
if !iqReq.IsValid() {
|
||||||
mockIQError(c)
|
mockIQError(sc.connection)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crafting response
|
// Crafting response
|
||||||
iqResp := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: iqR.To, To: iqR.From, Id: iqR.Id, Lang: "en"})
|
iqResp := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: iqReq.To, To: iqReq.From, Id: iqReq.Id, Lang: "en"})
|
||||||
disco := iqResp.DiscoInfo()
|
disco := iqResp.DiscoInfo()
|
||||||
disco.AddFeatures("vcard-temp",
|
disco.AddFeatures("vcard-temp",
|
||||||
`http://jabber.org/protocol/address`)
|
`http://jabber.org/protocol/address`)
|
||||||
|
@ -146,7 +146,7 @@ func respondToIQ(t *testing.T, c net.Conn) {
|
||||||
|
|
||||||
// Sending response to the Component
|
// Sending response to the Component
|
||||||
mResp, err := xml.Marshal(iqResp)
|
mResp, err := xml.Marshal(iqResp)
|
||||||
_, err = fmt.Fprintln(c, string(mResp))
|
_, err = fmt.Fprintln(sc.connection, string(mResp))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Could not send response stanza : %s", err)
|
t.Errorf("Could not send response stanza : %s", err)
|
||||||
}
|
}
|
||||||
|
@ -155,13 +155,13 @@ func respondToIQ(t *testing.T, c net.Conn) {
|
||||||
|
|
||||||
// When a presence stanza is automatically sent (right now it's the case in the client), we may want to discard it
|
// When a presence stanza is automatically sent (right now it's the case in the client), we may want to discard it
|
||||||
// and test further stanzas.
|
// and test further stanzas.
|
||||||
func discardPresence(t *testing.T, c net.Conn) {
|
func discardPresence(t *testing.T, sc *ServerConn) {
|
||||||
c.SetDeadline(time.Now().Add(defaultTimeout))
|
sc.connection.SetDeadline(time.Now().Add(defaultTimeout))
|
||||||
defer c.SetDeadline(time.Time{})
|
defer sc.connection.SetDeadline(time.Time{})
|
||||||
var presenceStz stanza.Presence
|
var presenceStz stanza.Presence
|
||||||
|
|
||||||
recvBuf := make([]byte, len(InitialPresence))
|
recvBuf := make([]byte, len(InitialPresence))
|
||||||
_, err := c.Read(recvBuf[:]) // recv data
|
_, err := sc.connection.Read(recvBuf[:]) // recv data
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||||
|
@ -178,11 +178,11 @@ func discardPresence(t *testing.T, c net.Conn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads next request coming from the Component. Expecting it to be an IQ request
|
// Reads next request coming from the Component. Expecting it to be an IQ request
|
||||||
func receiveIq(c net.Conn, decoder *xml.Decoder) (*stanza.IQ, error) {
|
func receiveIq(sc *ServerConn) (*stanza.IQ, error) {
|
||||||
c.SetDeadline(time.Now().Add(defaultTimeout))
|
sc.connection.SetDeadline(time.Now().Add(defaultTimeout))
|
||||||
defer c.SetDeadline(time.Time{})
|
defer sc.connection.SetDeadline(time.Time{})
|
||||||
var iqStz stanza.IQ
|
var iqStz stanza.IQ
|
||||||
err := decoder.Decode(&iqStz)
|
err := sc.decoder.Decode(&iqStz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -202,14 +202,14 @@ func mockIQError(c net.Conn) {
|
||||||
fmt.Fprintln(c, `</stream:stream>`)
|
fmt.Fprintln(c, `</stream:stream>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendStreamFeatures(t *testing.T, c net.Conn, _ *xml.Decoder) {
|
func sendStreamFeatures(t *testing.T, sc *ServerConn) {
|
||||||
// This is a basic server, supporting only 1 stream feature: SASL Plain Auth
|
// This is a basic server, supporting only 1 stream feature: SASL Plain Auth
|
||||||
features := `<stream:features>
|
features := `<stream:features>
|
||||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||||
<mechanism>PLAIN</mechanism>
|
<mechanism>PLAIN</mechanism>
|
||||||
</mechanisms>
|
</mechanisms>
|
||||||
</stream:features>`
|
</stream:features>`
|
||||||
if _, err := fmt.Fprintln(c, features); err != nil {
|
if _, err := fmt.Fprintln(sc.connection, features); err != nil {
|
||||||
t.Errorf("cannot send stream feature: %s", err)
|
t.Errorf("cannot send stream feature: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,29 +237,29 @@ func readAuth(t *testing.T, decoder *xml.Decoder) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendBindFeature(t *testing.T, c net.Conn, _ *xml.Decoder) {
|
func sendBindFeature(t *testing.T, sc *ServerConn) {
|
||||||
// This is a basic server, supporting only 1 stream feature after auth: resource binding
|
// This is a basic server, supporting only 1 stream feature after auth: resource binding
|
||||||
features := `<stream:features>
|
features := `<stream:features>
|
||||||
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
|
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
|
||||||
</stream:features>`
|
</stream:features>`
|
||||||
if _, err := fmt.Fprintln(c, features); err != nil {
|
if _, err := fmt.Fprintln(sc.connection, features); err != nil {
|
||||||
t.Errorf("cannot send stream feature: %s", err)
|
t.Errorf("cannot send stream feature: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendRFC3921Feature(t *testing.T, c net.Conn, _ *xml.Decoder) {
|
func sendRFC3921Feature(t *testing.T, sc *ServerConn) {
|
||||||
// This is a basic server, supporting only 2 features after auth: resource & session binding
|
// This is a basic server, supporting only 2 features after auth: resource & session binding
|
||||||
features := `<stream:features>
|
features := `<stream:features>
|
||||||
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
|
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
|
||||||
<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
|
<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
|
||||||
</stream:features>`
|
</stream:features>`
|
||||||
if _, err := fmt.Fprintln(c, features); err != nil {
|
if _, err := fmt.Fprintln(sc.connection, features); err != nil {
|
||||||
t.Errorf("cannot send stream feature: %s", err)
|
t.Errorf("cannot send stream feature: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
func bind(t *testing.T, sc *ServerConn) {
|
||||||
se, err := stanza.NextStart(decoder)
|
se, err := stanza.NextStart(sc.decoder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("cannot read bind: %s", err)
|
t.Errorf("cannot read bind: %s", err)
|
||||||
return
|
return
|
||||||
|
@ -267,7 +267,7 @@ func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
||||||
|
|
||||||
iq := &stanza.IQ{}
|
iq := &stanza.IQ{}
|
||||||
// Decode element into pointer storage
|
// Decode element into pointer storage
|
||||||
if err = decoder.DecodeElement(&iq, &se); err != nil {
|
if err = sc.decoder.DecodeElement(&iq, &se); err != nil {
|
||||||
t.Errorf("cannot decode bind iq: %s", err)
|
t.Errorf("cannot decode bind iq: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -280,12 +280,12 @@ func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
||||||
<jid>%s</jid>
|
<jid>%s</jid>
|
||||||
</bind>
|
</bind>
|
||||||
</iq>`
|
</iq>`
|
||||||
fmt.Fprintf(c, result, iq.Id, "test@localhost/test") // TODO use real JID
|
fmt.Fprintf(sc.connection, result, iq.Id, "test@localhost/test") // TODO use real JID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func session(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
func session(t *testing.T, sc *ServerConn) {
|
||||||
se, err := stanza.NextStart(decoder)
|
se, err := stanza.NextStart(sc.decoder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("cannot read session: %s", err)
|
t.Errorf("cannot read session: %s", err)
|
||||||
return
|
return
|
||||||
|
@ -293,7 +293,7 @@ func session(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
||||||
|
|
||||||
iq := &stanza.IQ{}
|
iq := &stanza.IQ{}
|
||||||
// Decode element into pointer storage
|
// Decode element into pointer storage
|
||||||
if err = decoder.DecodeElement(&iq, &se); err != nil {
|
if err = sc.decoder.DecodeElement(&iq, &se); err != nil {
|
||||||
t.Errorf("cannot decode session iq: %s", err)
|
t.Errorf("cannot decode session iq: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -301,6 +301,6 @@ func session(t *testing.T, c net.Conn, decoder *xml.Decoder) {
|
||||||
switch iq.Payload.(type) {
|
switch iq.Payload.(type) {
|
||||||
case *stanza.StreamSession:
|
case *stanza.StreamSession:
|
||||||
result := `<iq id='%s' type='result'/>`
|
result := `<iq id='%s' type='result'/>`
|
||||||
fmt.Fprintf(c, result, iq.Id)
|
fmt.Fprintf(sc.connection, result, iq.Id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue