AnsweredAssumed Answered

File Transfer fails on Smack 4.1.0-beta 1

Question asked by Nicolas Metts on Feb 2, 2015
Latest reply on Jun 28, 2015 by Kim BeeJay

NOTE: This question is also posted on StackOverflow. Flow posted a link to the specs, which I have read. I have received no useful responses from that forum, which is why I am posting my question here.

 

I am working on an application involving two Smack clients, one of which attempts to send a jar file to the other client. I have read the specs for XEP-0096: SI File Transfer, XEP-0095: Stream Initiation, XEP-0047: In-Band Bytestreams, and XEP-0065: SOCKS5 Bytestreams. From what I can tell from the XML stanzas, it looks like everything is being done correctly.

 

I am using Openfire 3.9.3 as the XMPP server. For all code examples I am using the following Smack libraries (version 4.1.0- beta 1): smack-java7, smack-tcp, smack-extensions, and smack-sasl-provided. Here's the code for the OutgoingFileTransfer (note in all code examples info such as Smack usernames have been anonymized):

 

   // LOG is of type org.apache.logging.log4j.Logger

    // transferManager is the FileTransferManager for this XMPPTCPConnection

    private void sendJarFile(final String to)

    {

        LOG.info("Sending " + jarFileName + " to " + to);

        OutgoingFileTransfer jarTransfer =

                transferManager.createOutgoingFileTransfer(to);

        final File jarFile = new File(jarFileName);

        try

        {

            jarTransfer.sendFile(jarFile, "The current jar file");

            while (!jarTransfer.isDone())

            {

                LOG.info("File transfer status: " + jarTransfer.getStatus());

                Thread.sleep(500);

            }

            LOG.info("File transfer to " + to + " is done");

            // Now that the file transfer is done check for errors

            // or exceptions

            FileTransfer.Error error = jarTransfer.getError();

            if (error != null)

            {

                LOG.error(error.getMessage());

            }

            Exception exception = jarTransfer.getException();

            if (exception != null)

            {

                if (exception instanceof XMPPException.XMPPErrorException)

                {

                    XMPPException.XMPPErrorException errorException =

                            (XMPPException.XMPPErrorException)exception;

                    XMPPError xmppError = errorException.getXMPPError();

                    LOG.error(xmppError);

                    LOG.error("Descriptive text: " + xmppError.getDescriptiveText());

                    LOG.error("Condition: " + xmppError.getCondition());

                    LOG.error("Type: " + xmppError.getType());

                }

            }

        }

        // For now, just catching and logging exceptions. Exception handling

        // will be added in top-level classes

        catch (SmackException e)

        {

            LOG.error("Exception trying to send jar file", e);

        }

        catch (InterruptedException e)

        {

            // Do nothing

        }

        catch (Exception e)

        {

            LOG.error("Exception trying to send jar file", e);

        }

    }

The output from this code is:

 

 

> Sending test.jar to receiver@local_openfire/Smack

>

> 14:09:43.748 INFO   - File transfer status: Initial

>

> 14:09:44.249 INFO   - File transfer status: Negotiating Stream

>

> 14:09:44.751 INFO   - File transfer status: Negotiating Stream

>

> // This message continues for several seconds, until finally

>

> 14:09:53.805 INFO   - File transfer status: Negotiating Stream

>

> 14:09:54.308 INFO   - File transfer to receiver@local_openfire/Smack

> is done

>

> 14:09:54.309 ERROR  - org.jivesoftware.smack.packet.XMPPError@30e95075

>

> 14:09:54.310 ERROR  - Descriptive text: null

>

> 14:09:54.310 ERROR  - Condition: service-unavailable

>

> 14:09:54.310 ERROR  - Type: cancel

 

 

For the IncomingFileTransfer, the code is:

 

 

    @Override

    public void fileTransferRequest(FileTransferRequest request)

    {

        final String requestorId = request.getRequestor();

        LOG.info("FileTransferRequest from: " + requestorId);

        // Only respond to requests from the sender

        if (requestorId.contains(senderId))

        {

            final IncomingFileTransfer transfer = request.accept();

            LOG.info("FileTransferRequest accepted");

            try

            {

                final String fileName = transfer.getFileName();

                transfer.recieveFile(new File(fileName));

                LOG.info("Incoming file transfer: " + fileName);

                LOG.info("Transfer status is: " + transfer.getStatus());

                while (!transfer.isDone())

                {

                    final double progress = transfer.getProgress();

                    final double progressPercent = progress * 100.0;

                    String percComplete = String.format("%1$,.2f", progressPercent);

                    LOG.info("Transfer status is: " + transfer.getStatus());

                    LOG.info("File transfer is " + percComplete + "% complete");

                    Thread.sleep(1000);

                }

 

 

                // Now that the file transfer is done check for errors

                // or exceptions

                FileTransfer.Error transferError = transfer.getError();

                if (transferError != null)

                {

                    LOG.error("Transfer error occurred: " + transferError.getMessage());

                }

                Exception transferException = transfer.getException();

                if (transferException != null)

                {

                    LOG.error("Transfer exception occurred: " + transferException);

                    if (transferException instanceof SmackException.NoResponseException)

                    {

                        SmackException.NoResponseException smackException = (SmackException.NoResponseException)transferException;

                        smackException.printStackTrace();

                    }

                }

                LOG.info("FileTransfer complete");

                provisioningComplete = true;

            }

            // For now just logging exceptions

            catch (SmackException e)

            {

                LOG.error("SmackException trying to receive jar file", e);

            }

            catch (InterruptedException e)

            {

                // Do nothing

            }

            catch (IOException e)

            {

                LOG.error("IOException trying to receive jar file", e);

            }

 

 

        }

        else

        {

            LOG.warn("FileTransferRequest rejected");

            try

            {

                request.reject();

            }

            catch (NotConnectedException e)

            {

                LOG.warn("NotConnectedException when rejecting FileTransferRequest");

            }

        }

    }

 

 

The output from this code is:

 

 

> 14:09:43.766  INFO   - FileTransferRequest from:

> sender@local_openfire/Smack

>

> 14:09:43.767  INFO   - FileTransferRequest accepted

>

> 14:09:43.768  INFO   - Incoming file transfer: test.jar

>

> 14:09:43.769  INFO   - Transfer status is: Negotiating Transfer

>

> 14:09:43.770  INFO   - Transfer status is: Negotiating Stream

>

> 14:09:43.770  INFO   - File transfer is 0.00% complete

>

> 14:09:44.771  INFO   - Transfer status is: Negotiating Stream

>

> 14:09:44.771  INFO   - File transfer is 0.00% complete

>

> 14:09:45.776  INFO   - Transfer status is: Negotiating Stream

>

> 14:09:45.776  INFO   - File transfer is 0.00% complete

>

> 14:09:46.778  INFO   - Transfer status is: Negotiating Stream

>

> 14:09:46.778  INFO   - File transfer is 0.00% complete

>

> 14:09:47.782  INFO   - Transfer status is: Negotiating Stream

>

> 14:09:47.783  INFO   - File transfer is 0.00% complete

>

> 14:09:48.784  ERROR  - Transfer exception occurred:

> org.jivesoftware.smack.SmackException: Error in execution

>

> 14:09:48.784  INFO   - FileTransfer complete

 

 

After this code has run, on the receiver end, I have a file named "test.jar" in the current working directory with a file size of 0 bytes. I've tried this both with the sender and receiver on different machines, and the sender and receiver on the same machine. I was initially using Smack 4.0.6, but switched to the latest code base (4.1.0- beta 1 as of this writing) in hopes that perhaps this bug would have been resolved.

 

I used the Smack debugging tool and captured the raw XML stanzas. For the sake of brevity, I only included those that were relevant to file transfer (i.e, not presence or roster packets). Here they are:

 

 

 

 

    <iq to="receiver@smack_server/Smack" id="NK8Lh-11" type="set"

    from="sender@smack_server/Smack">

        <si xmlns="http://jabber.org/protocol/si" id="jsi_3077759398544954943"

        mime-type="text/plain" profile="http://jabber.org/protocol/si/profile/file-transfer">

            <file xmlns="http://jabber.org/protocol/si/profile/file-transfer" name="test.txt"

            size="37">

                <desc>A test file</desc>

            </file>

            <feature xmlns="http://jabber.org/protocol/feature-neg">

                <x xmlns="jabber:x:data" type="form">

                    <field var="stream-method" type="list-single">

                        <option>

                            <value>http://jabber.org/protocol/bytestreams</value>

                        </option>

                        <option>

                            <value>http://jabber.org/protocol/ibb</value>

                        </option>

                    </field>

                </x>

            </feature>

        </si>

    </iq>

  

    <iq to="sender@smack_server/Smack" id="NK8Lh-11" type="result">

      <si xmlns="http://jabber.org/protocol/si">

        <feature xmlns="http://jabber.org/protocol/feature-neg">

          <x xmlns="jabber:x:data" type="submit">

            <field var="stream-method">

              <value>http://jabber.org/protocol/bytestreams</value>

              <value>http://jabber.org/protocol/ibb</value>

            </field>

          </x>

        </feature>

      </si>

    </iq>

  

  

    <iq to="receiver@smack_server/Smack" id="NK8Lh-13" type="get" from="sender@smack_server/Smack">

        <query xmlns="http://jabber.org/protocol/disco#info"/>

    </iq>

  

    <iq to="sender@smack_server/Smack" id="NK8Lh-13" type="result">

      <query xmlns="http://jabber.org/protocol/disco#info">

        <identity category="client" name="Smack" type="pc"/>

        <feature var="http://jabber.org/protocol/disco#items"/>

        <feature var="vcard-temp"/>

        <feature var="http://jabber.org/protocol/bytestreams"/>

        <feature var="http://jabber.org/protocol/ibb"/>

        <feature var="http://jabber.org/protocol/si"/>

        <feature var="http://jabber.org/protocol/xhtml-im"/>

        <feature var="jabber:x:data"/>

        <feature var="urn:xmpp:time"/>

        <feature var="jabber:iq:privacy"/>

        <feature var="http://jabber.org/protocol/si/profile/file-transfer"/>

        <feature var="urn:xmpp:ping"/>

        <feature var="jabber:iq:last"/>

        <feature var="http://jabber.org/protocol/commands"/>

        <feature var="http://jabber.org/protocol/muc"/>

        <feature var="http://jabber.org/protocol/xdata-validate"/>

        <feature var="http://jabber.org/protocol/xdata-layout"/>

        <feature var="http://jabber.org/protocol/disco#info"/>

      </query>

    </iq>

  

    <iq to="receiver@smack_server/Smack" id="NK8Lh-25" type="set" from="sender@smack_server/Smack">

        <query xmlns="http://jabber.org/protocol/bytestreams" sid="jsi_3077759398544954943" mode="tcp">

            <streamhost jid="sender@smack_server/Smack" host="ipv6_addr1" port="7778"/>

            <streamhost jid="sender@smack_server/Smack" host="ipv4_addr1" port="7778"/>

            <streamhost jid="sender@smack_server/Smack" host="ipv6_addr2" port="7778"/>

            <streamhost jid="proxy.smack_server" host="ipv4_addr2" port="7777"/>

        </query>

    </iq>

  

    <iq to="receiver@smack_server/Smack" id="NK8Lh-26" type="set" from="sender@smack_server/Smack">

        <open xmlns="http://jabber.org/protocol/ibb" block-size="4096" sid="jsi_3077759398544954943" stanza="iq"/>

    </iq>

 

 

 

 

From the best that I can tell having read the spec, it looks like everything is proceeding as it should. The sender sends the initial SI request, the receiver responds with the supported protocols (i.e., byte streams and IBB), the send then queries the receiver for all the disco items, the receiver responds with the list of features, the sender then sends various stream hosts, and then the sender sends a block of data via IBB. From there, the receiver gets the SmackException: Error in execution method, which is caused by a SmackException.NoResponseException with a message that a response was not received in 5 seconds. It looks like this question is basically being overlooked at this point, but in the hopes that someone does check it out I'd really appreciate any help. Thanks!

Outcomes