Smack WebSocket implementation does not convert <open> element into <stream> element unless it self-closes

Smack version: 4.5.0-alpha1-SNAPSHOT (4.3.4-856-g0a6c21982-master 2020-09-07)

XMPP message text received in phase openFrameSent:

<open xmlns="urn:ietf:params:xml:ns:xmpp-framing" xmlns:stream="http://etherx.jabber.org/streams" version="1.0" from="REMOVED" id="REMOVED" xml:lang="en"></open>

Relevant code from OkHttpWebSocket:

                case openFrameSent:
                    if (isOpenElement(text)) {
                        // Converts the <open> element received into <stream> element.
                        openStreamHeader = getStreamFromOpenElement(text);

Relevant code for AbstractWebSocket.getStreamFromOpenElement(String):

    protected static String getStreamFromOpenElement(String openElement) {
        String streamElement = openElement.replaceFirst("\\A<open ", "<stream ")
                                          .replace("urn:ietf:params:xml:ns:xmpp-framing", "jabber:client")
                                          .replaceFirst("/>\\s*\\z", ">");
        return streamElement;
    }

This method returns:

<stream xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0" from="REMOVED" id="REMOVED" xml:lang="en"></open>

This results in a parse error, which causes the connection to fail.

The relevant specification from RFC 7395:

3.3.1. Framed XML Stream

The start of a framed XML stream is marked by the use of an opening “stream header”, which is an <open/> element with the appropriate attributes and namespace declarations (see Section 3.3.2). The attributes of the <open/> element are the same as those of the <stream/> element defined for the ‘http://etherx.jabber.org/streams’ namespace RFC6120 and with the same semantics and restrictions.

Although the RFC references the <open/> element in self-closing style, it also references the <stream/> element in self-closing style. It would not make much sense to have a self-closing <stream/> element, therefore I don’t think the specification implies a requirement that the <open/> element must self-close, because it doesn’t explicitly specify it as self-closing.

It seems AbstractWebSocket.getStreamFromOpenElement(String) has a bug because it does not handle this scenario.

1 Like

Hi @carlton.whitehead,

Thanks for pointing this out :grinning:
While formulating the AbstractWebsocket.getStreamOpen(String) method, only self-closing open elements were considered.
The reason to discard <open...></open> kind of structure is that it adds unnecessary bytes to the wire which is not a great idea as far as I understand. Normally, XMPP servers would want to avoid that.
This motivates me to ask you, could you tell us, which XMPP server served you with the described open element?

XMPP servers like Openfire and Prosody do stick to RFC 7395 references by generating self-enclosing open elements.

Support for your websocket element can still be made available in smack-websocket but since there are very few servers providing websocket open element in that form, I am not sure if the reason is enough to convince Smack’s maintainer @Flow to incorporate changes here.

2 Likes

Smack wouldn’t be standards compliant if it does not support <open></open>. I have a private branch with some changes for the websocket code. I guess I will take care of this too (provided I don’t forget about it). But this definelty will need fixing before this code can be released.

2 Likes

I worked around this in a local build with the following patch applied:

diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/AbstractWebSocket.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/AbstractWebSocket.java
index d7e76e230..e623639de 100644
diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/imp
l/AbstractWebSocket.java b/smack-websocket/src/main/java/org/jivesoftware/smack
/websocket/impl/AbstractWebSocket.java
index d7e76e230..e623639de 100644
--- a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/AbstractWebSocket.java
+++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/AbstractWebSocket.java
@@ -31,7 +31,8 @@ public abstract class AbstractWebSocket {
     protected static String getStreamFromOpenElement(String openElement) {
         String streamElement = openElement.replaceFirst("\\A<open ", "<stream ")
                                           .replace("urn:ietf:params:xml:ns:xmpp-framing", "jabber:client")
-                                          .replaceFirst("/>\\s*\\z", ">");
+                                          .replaceFirst("/>\\s*\\z", ">")
+                                          .replaceFirst("</open>\\z", "");
         return streamElement;
     }
 

It removes a trailing </open> tag. I found that by replacing the </open> tag with a </stream> tag that it triggered the usual behavior of closing the stream. This is admittedly a hack so I could proceed to experiment with other parts of the websocket implementation.

I wonder if using an XML parser instead of string manipulation would be a better choice here for robustness’ sake.

1 Like

That is totally to be expected :wink:.

1 Like

Perhaps it’s time to file a Jira ticket?

1 Like

How can I get 4.5.0-alpha1-SNAPSHOT? I can not wait to try Smack with WebSocket.

There is an archiva instance, which however does have a hickup atm.
If you can’t wait you can always build Smack from source :wink:

How can we connect websocket using 4.5.0-alpha1-SNAPSHOT.
I am able to connect and login but how i can send a receive messages using websocket.

@carlton.whitehead if you can provide any example for websocket .

My code for websocket connection :-

Blockquote
DomainBareJid serviceName = JidCreate.domainBareFrom(“1054.com”);
EntityBareJid user = JidCreate.entityBareFrom(“manu@1054.com”);
ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration.builder();
builder.removeAllModules().setXmppDomain(serviceName)
.setSendPresence(true)
.setHost(“1054.com”)
.setSecurityMode(SecurityMode.disabled)
;
builder.setXmppAddressAndPassword(user, “password”);
//handshakeHttps();
// Set a fallback uri into websocket transport descriptor and add this descriptor into connection builder.
XmppWebSocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebSocketTransportModuleDescriptor.getBuilder(builder);
websocketBuilder.explicitlySetWebSocketEndpointAndDiscovery(new URI(“ws://1054.com:7070/ws/”), false);

    //XmppTcpTransportModuleDescriptor.Builder tcpBuilder = XMPPTCPConnectionConfiguration.builder();
    //websocketBuilder.explicitlySetWebSocketEndpointAndDiscovery(new URI("ws://1054.com:7070/ws/"), false);
    
    builder.addModule(websocketBuilder.build());

    ModularXmppClientToServerConnectionConfiguration config = builder.build();
    ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config);
    connection.setParsingExceptionCallback(new ExceptionThrowingCallbackWithHint());
    connection.addConnectionListener(new ConnectionListenerTest());
    connection.connect();
    
    final String messageBody = ": Hello from the other side!";
    connection.login();
    Message message = connection.getStanzaFactory().buildMessageStanza()
            .to("sandeep@1054.com")
            .setBody(messageBody)
            .build();
    connection.sendStanza(message);
    
	**//below code is working to send and receive message but not sure it is using websocket internally or tcp connection.i want to use websocket object to send and receive messages.**
    ChatManager chatManager = ChatManager.getInstanceFor(connection);
	chatManager.addIncomingListener(new IncomingChatMessageListener() {
	  @Override
	public
	  void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
	    System.out.println("New message from " + from + ": " + message.getBody());
	  }
	});
	EntityBareJid jid = JidCreate.entityBareFrom("sandeep@1054.com");
	Chat chat = chatManager.chatWith(jid);
	chat.send("Howdy!");
    
    //connection.addStanzaSendingListener(packetListener, packetFilter);
    //connection.addStanzaListener(stanzaListener, stanzaFilter);
    
    //connection.disconnect();

Please do not hijack other threads.

Since you create a websocket connection above, you are using it here.
Easiest way to verify would be to block port 5222 on your server and see if you can still send and receive messages.

i have created my own thread but didn’t get any reply. so i try to ask my query in this thread.
below is the link of my thread.

have one query. how to use websocketFactoryservice to get websoket objects and use those objects to send and receive messages.
in my code i used “ws://1054.com:7070/ws/” it means it is running on 7070 port.

you can reply on my thread.

This topic was automatically closed 62 days after the last reply. New replies are no longer allowed.