Multi stage command throws exception in Smack 4.4.6

Dear All,

I have to develop a Spark plugin what uses ad-hoc commands (XEP-50) between two clients for exchange informations. I use the Smack 4.4.6 what included with Spark 3.0.2 When i use a command what has only one stage it works fine. However if i want to send parameters for the execution, i have to use multi stage command. When the second stage execution is invoked, the smack of the receiver side throws and uncaught exception what is thrown in the line of 54 in FillableForm.java. The situation is the following, i send a submit form where i answer for the response form, but in this file there is the following:

if (dataForm.getType() != DataForm.Type.form) {
    throw new IllegalArgumentException();
}

so as i send a submit form the exception is thrown. I’ve tested the command from other clients like Pidgin and GajIm as well, with the same problem. My question is why the DataForm must be a form type why cannot be a submit type as well?
Thank you for your answer in advance.
Warm regards
Attila

Stack:

java.lang.IllegalArgumentException
        at org.jivesoftware.smackx.xdata.form.FillableForm.<init>(FillableForm.java:54)
        at org.jivesoftware.smackx.commands.AdHocCommandManager.processAdHocCommand(AdHocCommandManager.java:467)
        at org.jivesoftware.smackx.commands.AdHocCommandManager.access$100(AdHocCommandManager.java:70)
        at org.jivesoftware.smackx.commands.AdHocCommandManager$3.handleIQRequest(AdHocCommandManager.java:176)
        at org.jivesoftware.smack.AbstractXMPPConnection$3.run(AbstractXMPPConnection.java:1561)
        at org.jivesoftware.smack.AbstractXMPPConnection$10.run(AbstractXMPPConnection.java:2146)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:750)

Could you show us also the XMPP trace of the multi-stage ad-hoc command exchange? You can obtain it, for example, by setting SmackConfiguration.DEBUG = true.

Yes i can. There are the stanzas:

The first request:

<iq xmlns="jabber:client" to="responder@domain.org/Spark" from="requester@domain.org/gajim.TW9P8T92" id="06266a77-bc6a-4ea8-b7ac-4b95d2c92927" type="get">
  <query xmlns="http://jabber.org/protocol/disco#items" node="http://jabber.org/protocol/commands"/>
</iq>

The response with the registered commands:

<iq xmlns="jabber:client" to="requester@domain.org/gajim.TW9P8T92" id="06266a77-bc6a-4ea8-b7ac-4b95d2c92927" type="result">
  <query xmlns="http://jabber.org/protocol/disco#items" node="http://jabber.org/protocol/commands">
    <item jid="responder@domain.org/Spark" name="asd" node="asd"/>
    <item jid="responder@domain.org/Spark" name="SetUrl" node="SetUrl"/>
    <item jid="responder@domain.org/Spark" name="ControlMessage" node="ControlMessage"/>
    <item jid="responder@domain.org/Spark" name="dial" node="dial"/>
  </query>
</iq>

The chosen command

<iq xmlns="jabber:client" to="responder@domain.org/Spark" from="requester@domain.org/gajim.TW9P8T92" id="d69a2cd5-5e5e-464b-a895-69b6e2f205c8" type="set">
  <command xmlns="http://jabber.org/protocol/commands" node="SetUrl" action="execute"/>
</iq>

The response for stage 1:

<iq xmlns="jabber:client" to="requester@domain.org/gajim.TW9P8T92" id="d69a2cd5-5e5e-464b-a895-69b6e2f205c8" type="result">
  <command xmlns="http://jabber.org/protocol/commands" node="SetUrl" sessionid="KXHLURMT97XFBF3" status="executing">
    <actions>
      <execute/>
      <next/>
      <prev/>
      <cancel/>
    </actions>
    <x xmlns="jabber:x:data" type="form">
      <instructions>give me the url where the client can join</instructions>
      <field var="url"/>
    </x>
  </command>
</iq>

The answer for the response with the action next:

<iq xmlns="jabber:client" to="responder@domain.org/Spark" from="requester@domain.org/gajim.TW9P8T92" id="7cb8fab6-23db-4b1b-8ccd-95d48f6c90cf" type="set">
  <command xmlns="http://jabber.org/protocol/commands" node="SetUrl" sessionid="KXHLURMT97XFBF3" action="next">
    <x xmlns="jabber:x:data" type="submit">
      <instructions>give me the url where the client can join</instructions>
      <field var="url">
        <value>example.com</value>
      </field>
    </x>
  </command>
</iq>

There is the exception.

Thanks, I believe this is a bug in Smack and created

which I believe should fix this. But note that this is untested. You are more than welcome to test and provide feedback if you want.

1 Like

Thank you so much for the patch. I try to test and send feedback as i have result.

1 Like

I applied your patch, and tested with the same XMPP stanzas. Unfortunately there is an other exception in Form.java in the line of 29:

public Form(DataForm dataForm) {
        super(dataForm);
        if (dataForm.getType() != Type.form) {
            throw new IllegalArgumentException();
        }
    }

Stack:

java.lang.IllegalArgumentException
        at org.jivesoftware.smackx.xdata.form.Form.<init>(Form.java:29)
        at org.jivesoftware.smackx.commands.AdHocCommandManager.processAdHocCommand(AdHocCommandManager.java:467)
        at org.jivesoftware.smackx.commands.AdHocCommandManager.access$100(AdHocCommandManager.java:70)
        at org.jivesoftware.smackx.commands.AdHocCommandManager$3.handleIQRequest(AdHocCommandManager.java:176)
        at org.jivesoftware.smack.AbstractXMPPConnection$3.run(AbstractXMPPConnection.java:1561)
        at org.jivesoftware.smack.AbstractXMPPConnection$10.run(AbstractXMPPConnection.java:2146)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)    
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)    
        at java.lang.Thread.run(Thread.java:750)

What is your opinion, can be the solution if the line of 467 in AdHocCommandManager would be

Form filledForm = new FilledForm(dataForm);

instead of

Form filledForm = new Form(dataForm);

because there the form have to be filled. Thank you for the help.

ah, i just see FilledForm is abstract. There have to be other solution as well.

I have tried the following in AdHocCommandManager:

FilledSubscribeForm filledForm = new FilledSubscribeForm(dataForm);
                        command.next(filledForm);

After i set the hidden form type it works.
Only question is can be different the form type from FilledSubscribeForm ?

Ok, I think I have now a better understanding of the situation.

But I have think to about about the potential API design solutions. Right now, the API appears to be simply broken for AdHocManager and LocalCommand. Using FilledSubscribeForm there is not an option, as its PubSub specific and at that place you want to be generic.

I have updated the PR with

but that is really just an experiment, even though, it could get you going. But I am not sure if I like this approach (and if it is correct). This approach also means that you have to subclass FilleForm, but that is always a good idea.

I wonder if the subclasses of AdHocCommand should be more separated…

Meanwhile i noticed that the FilledSubscribeForm is different, so i agree with the generic solution. I tested your patch only a small thing what i modified, i have to put the visibility from private to protected in LocalCommand.java line 171. Thank you for the help, it works fine.

Thanks for the fast feedback. Please note that I can not guarantee that this will be the final solution. The more I think about it, the more I believe that the AdHocCommand API needs a overhaul. I’ll let you know once I have something to share.

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