Transports need to handle open/close stanzas

XMPP and WebSocket transports require different open and close stanzas. To
handle this the responsibility handling those and creating the XML decoder is
moved to the Transport.
This commit is contained in:
Wichert Akkerman
2019-10-18 20:29:54 +02:00
committed by Mickaël Rémond
parent 25fd476328
commit 92329b48e6
15 changed files with 356 additions and 274 deletions
+59 -16
View File
@@ -2,11 +2,15 @@ package xmpp
import (
"context"
"encoding/xml"
"errors"
"fmt"
"io"
"net"
"strings"
"time"
"gosrc.io/xmpp/stanza"
"nhooyr.io/websocket"
)
@@ -16,35 +20,60 @@ var ServerDoesNotSupportXmppOverWebsocket = errors.New("The websocket server doe
type WebsocketTransport struct {
Config TransportConfiguration
decoder *xml.Decoder
wsConn *websocket.Conn
netConn net.Conn
ctx context.Context
logFile io.Writer
}
func (t *WebsocketTransport) Connect() error {
t.ctx = context.Background()
func (t *WebsocketTransport) Connect() (string, error) {
ctx := context.Background()
if t.Config.ConnectTimeout > 0 {
ctx, cancel := context.WithTimeout(t.ctx, time.Duration(t.Config.ConnectTimeout)*time.Second)
t.ctx = ctx
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(t.Config.ConnectTimeout)*time.Second)
defer cancel()
}
wsConn, response, err := websocket.Dial(t.ctx, t.Config.Address, &websocket.DialOptions{
wsConn, response, err := websocket.Dial(ctx, t.Config.Address, &websocket.DialOptions{
Subprotocols: []string{"xmpp"},
})
if err != nil {
return NewConnError(err, true)
return "", NewConnError(err, true)
}
if response.Header.Get("Sec-WebSocket-Protocol") != "xmpp" {
return ServerDoesNotSupportXmppOverWebsocket
_ = wsConn.Close(websocket.StatusBadGateway, "Could not negotiate XMPP subprotocol")
return "", NewConnError(ServerDoesNotSupportXmppOverWebsocket, true)
}
t.wsConn = wsConn
t.netConn = websocket.NetConn(t.ctx, t.wsConn, websocket.MessageText)
return nil
t.netConn = websocket.NetConn(ctx, t.wsConn, websocket.MessageText)
handshake := fmt.Sprintf("<open xmlns=\"urn:ietf:params:xml:ns:xmpp-framing\" to=\"%s\" version=\"1.0\" />", t.Config.Domain)
if _, err = t.Write([]byte(handshake)); err != nil {
_ = wsConn.Close(websocket.StatusBadGateway, "XMPP handshake error")
return "", NewConnError(err, false)
}
handshakeResponse := make([]byte, 2048)
if _, err = t.Read(handshakeResponse); err != nil {
_ = wsConn.Close(websocket.StatusBadGateway, "XMPP handshake error")
return "", NewConnError(err, false)
}
var openResponse = stanza.WebsocketOpen{}
if err = xml.Unmarshal(handshakeResponse, &openResponse); err != nil {
_ = wsConn.Close(websocket.StatusBadGateway, "XMPP handshake error")
return "", NewConnError(err, false)
}
t.decoder = xml.NewDecoder(t)
t.decoder.CharsetReader = t.Config.CharsetReader
return openResponse.Id, nil
}
func (t WebsocketTransport) StartTLS(domain string) error {
func (t WebsocketTransport) StartTLS() error {
return TLSNotSupported
}
@@ -52,6 +81,10 @@ func (t WebsocketTransport) DoesStartTLS() bool {
return false
}
func (t WebsocketTransport) GetDecoder() *xml.Decoder {
return t.decoder
}
func (t WebsocketTransport) IsSecure() bool {
return strings.HasPrefix(t.Config.Address, "wss:")
}
@@ -59,19 +92,29 @@ func (t WebsocketTransport) IsSecure() bool {
func (t WebsocketTransport) Ping() error {
ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
defer cancel()
// Note that we do not use wsConn.Ping(), because not all websocket servers
// (ejabberd for example) implement ping frames
return t.wsConn.Write(ctx, websocket.MessageText, []byte(" "))
return t.wsConn.Ping(ctx)
}
func (t WebsocketTransport) Read(p []byte) (n int, err error) {
return t.netConn.Read(p)
func (t *WebsocketTransport) Read(p []byte) (n int, err error) {
n, err = t.netConn.Read(p)
if t.logFile != nil && n > 0 {
_, _ = fmt.Fprintf(t.logFile, "RECV:\n%s\n\n", p)
}
return
}
func (t WebsocketTransport) Write(p []byte) (n int, err error) {
if t.logFile != nil {
_, _ = fmt.Fprintf(t.logFile, "SEND:\n%s\n\n", p)
}
return t.netConn.Write(p)
}
func (t WebsocketTransport) Close() error {
t.Write([]byte("<close xmlns=\"urn:ietf:params:xml:ns:xmpp-framing\" />"))
return t.netConn.Close()
}
func (t *WebsocketTransport) LogTraffic(logFile io.Writer) {
t.logFile = logFile
}