Bytestream.java

  1. /**
  2.  *
  3.  * Copyright the original author or authors
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.bytestreams.socks5.packet;

  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.List;

  21. import org.jivesoftware.smack.packet.IQ;
  22. import org.jivesoftware.smack.packet.NamedElement;
  23. import org.jivesoftware.smack.util.Objects;
  24. import org.jivesoftware.smack.util.StringUtils;
  25. import org.jivesoftware.smack.util.XmlStringBuilder;

  26. import org.jxmpp.jid.Jid;

  27. /**
  28.  * A stanza representing part of a SOCKS5 Bytestream negotiation.
  29.  *
  30.  * @author Alexander Wenckus
  31.  */
  32. public class Bytestream extends IQ {

  33.     public static final String ELEMENT = QUERY_ELEMENT;

  34.     /**
  35.      * The XMPP namespace of the SOCKS5 Bytestream.
  36.      */
  37.     public static final String NAMESPACE = "http://jabber.org/protocol/bytestreams";

  38.     private String sessionID;

  39.     private Mode mode = Mode.tcp;

  40.     private final List<StreamHost> streamHosts = new ArrayList<>();

  41.     private StreamHostUsed usedHost;

  42.     private Activate toActivate;

  43.     /**
  44.      * The default constructor.
  45.      */
  46.     public Bytestream() {
  47.         super(ELEMENT, NAMESPACE);
  48.     }

  49.     /**
  50.      * A constructor where the session ID can be specified.
  51.      *
  52.      * @param SID The session ID related to the negotiation.
  53.      * @see #setSessionID(String)
  54.      */
  55.     public Bytestream(final String SID) {
  56.         this();
  57.         setSessionID(SID);
  58.     }

  59.     /**
  60.      * Set the session ID related to the bytestream. The session ID is a unique identifier used to
  61.      * differentiate between stream negotiations.
  62.      *
  63.      * @param sessionID the unique session ID that identifies the transfer.
  64.      */
  65.     public void setSessionID(final String sessionID) {
  66.         this.sessionID = sessionID;
  67.     }

  68.     /**
  69.      * Returns the session ID related to the bytestream negotiation.
  70.      *
  71.      * @return Returns the session ID related to the bytestream negotiation.
  72.      * @see #setSessionID(String)
  73.      */
  74.     public String getSessionID() {
  75.         return sessionID;
  76.     }

  77.     /**
  78.      * Set the transport mode. This should be put in the initiation of the interaction.
  79.      *
  80.      * @param mode the transport mode, either UDP or TCP
  81.      * @see Mode
  82.      */
  83.     public void setMode(final Mode mode) {
  84.         this.mode = mode;
  85.     }

  86.     /**
  87.      * Returns the transport mode.
  88.      *
  89.      * @return Returns the transport mode.
  90.      * @see #setMode(Mode)
  91.      */
  92.     public Mode getMode() {
  93.         return mode;
  94.     }

  95.     /**
  96.      * Adds a potential stream host that the remote user can connect to to receive the file.
  97.      *
  98.      * @param JID The JID of the stream host.
  99.      * @param address The internet address of the stream host.
  100.      * @return The added stream host.
  101.      */
  102.     public StreamHost addStreamHost(final Jid JID, final String address) {
  103.         return addStreamHost(JID, address, 0);
  104.     }

  105.     /**
  106.      * Adds a potential stream host that the remote user can connect to to receive the file.
  107.      *
  108.      * @param JID The JID of the stream host.
  109.      * @param address The internet address of the stream host.
  110.      * @param port The port on which the remote host is seeking connections.
  111.      * @return The added stream host.
  112.      */
  113.     public StreamHost addStreamHost(final Jid JID, final String address, final int port) {
  114.         StreamHost host = new StreamHost(JID, address, port);
  115.         addStreamHost(host);

  116.         return host;
  117.     }

  118.     /**
  119.      * Adds a potential stream host that the remote user can transfer the file through.
  120.      *
  121.      * @param host The potential stream host.
  122.      */
  123.     public void addStreamHost(final StreamHost host) {
  124.         streamHosts.add(host);
  125.     }

  126.     /**
  127.      * Returns the list of stream hosts contained in the packet.
  128.      *
  129.      * @return Returns the list of stream hosts contained in the packet.
  130.      */
  131.     public List<StreamHost> getStreamHosts() {
  132.         return Collections.unmodifiableList(streamHosts);
  133.     }

  134.     /**
  135.      * Returns the stream host related to the given JID, or null if there is none.
  136.      *
  137.      * @param JID The JID of the desired stream host.
  138.      * @return Returns the stream host related to the given JID, or null if there is none.
  139.      */
  140.     public StreamHost getStreamHost(final Jid JID) {
  141.         if (JID == null) {
  142.             return null;
  143.         }
  144.         for (StreamHost host : streamHosts) {
  145.             if (host.getJID().equals(JID)) {
  146.                 return host;
  147.             }
  148.         }

  149.         return null;
  150.     }

  151.     /**
  152.      * Returns the count of stream hosts contained in this packet.
  153.      *
  154.      * @return Returns the count of stream hosts contained in this packet.
  155.      */
  156.     public int countStreamHosts() {
  157.         return streamHosts.size();
  158.     }

  159.     /**
  160.      * Upon connecting to the stream host the target of the stream replies to the initiator with the
  161.      * JID of the SOCKS5 host that they used.
  162.      *
  163.      * @param JID The JID of the used host.
  164.      */
  165.     public void setUsedHost(final Jid JID) {
  166.         this.usedHost = new StreamHostUsed(JID);
  167.     }

  168.     /**
  169.      * Returns the SOCKS5 host connected to by the remote user.
  170.      *
  171.      * @return Returns the SOCKS5 host connected to by the remote user.
  172.      */
  173.     public StreamHostUsed getUsedHost() {
  174.         return usedHost;
  175.     }

  176.     /**
  177.      * Returns the activate element of the stanza sent to the proxy host to verify the identity of
  178.      * the initiator and match them to the appropriate stream.
  179.      *
  180.      * @return Returns the activate element of the stanza sent to the proxy host to verify the
  181.      *         identity of the initiator and match them to the appropriate stream.
  182.      */
  183.     public Activate getToActivate() {
  184.         return toActivate;
  185.     }

  186.     /**
  187.      * Upon the response from the target of the used host the activate stanza is sent to the SOCKS5
  188.      * proxy. The proxy will activate the stream or return an error after verifying the identity of
  189.      * the initiator, using the activate packet.
  190.      *
  191.      * @param targetID The JID of the target of the file transfer.
  192.      */
  193.     public void setToActivate(final Jid targetID) {
  194.         this.toActivate = new Activate(targetID);
  195.     }

  196.     @Override
  197.     protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
  198.         switch (getType()) {
  199.         case set:
  200.             xml.optAttribute("sid", getSessionID());
  201.             xml.optAttribute("mode", getMode());
  202.             xml.rightAngleBracket();
  203.             if (getToActivate() == null) {
  204.                 for (StreamHost streamHost : getStreamHosts()) {
  205.                     xml.append(streamHost.toXML(null));
  206.                 }
  207.             }
  208.             else {
  209.                 xml.append(getToActivate().toXML(null));
  210.             }
  211.             break;
  212.         case result:
  213.             xml.rightAngleBracket();
  214.             xml.optAppend(getUsedHost());
  215.             // TODO Bytestream can include either used host *or* streamHosts. Never both. This should be ensured by the
  216.             // constructions mechanisms of Bytestream
  217.             // A result from the server can also contain stream hosts
  218.             for (StreamHost host : streamHosts) {
  219.                 xml.append(host.toXML(null));
  220.             }
  221.             break;
  222.         case get:
  223.             xml.setEmptyElement();
  224.             break;
  225.         default:
  226.             throw new IllegalStateException();
  227.         }

  228.         return xml;
  229.     }

  230.     /**
  231.      * Stanza extension that represents a potential SOCKS5 proxy for the file transfer. Stream hosts
  232.      * are forwarded to the target of the file transfer who then chooses and connects to one.
  233.      *
  234.      * @author Alexander Wenckus
  235.      */
  236.     public static class StreamHost implements NamedElement {

  237.         public static String ELEMENTNAME = "streamhost";

  238.         private final Jid JID;

  239.         private final String addy;

  240.         private final int port;

  241.         public StreamHost(Jid jid, String address) {
  242.             this(jid, address, 0);
  243.         }

  244.         /**
  245.          * Default constructor.
  246.          *
  247.          * @param JID The JID of the stream host.
  248.          * @param address The internet address of the stream host.
  249.          * @param port port of the stream host.
  250.          */
  251.         public StreamHost(final Jid JID, final String address, int port) {
  252.             this.JID = Objects.requireNonNull(JID, "StreamHost JID must not be null");
  253.             this.addy = StringUtils.requireNotNullOrEmpty(address, "StreamHost address must not be null");
  254.             this.port = port;
  255.         }

  256.         /**
  257.          * Returns the JID of the stream host.
  258.          *
  259.          * @return Returns the JID of the stream host.
  260.          */
  261.         public Jid getJID() {
  262.             return JID;
  263.         }

  264.         /**
  265.          * Returns the internet address of the stream host.
  266.          *
  267.          * @return Returns the internet address of the stream host.
  268.          */
  269.         public String getAddress() {
  270.             return addy;
  271.         }

  272.         /**
  273.          * Returns the port on which the potential stream host would accept the connection.
  274.          *
  275.          * @return Returns the port on which the potential stream host would accept the connection.
  276.          */
  277.         public int getPort() {
  278.             return port;
  279.         }

  280.         @Override
  281.         public String getElementName() {
  282.             return ELEMENTNAME;
  283.         }

  284.         @Override
  285.         public XmlStringBuilder toXML(String enclosingNamespace) {
  286.             XmlStringBuilder xml = new XmlStringBuilder(this);
  287.             xml.attribute("jid", getJID());
  288.             xml.attribute("host", getAddress());
  289.             if (getPort() != 0) {
  290.                 xml.attribute("port", Integer.toString(getPort()));
  291.             } else {
  292.                 xml.attribute("zeroconf", "_jabber.bytestreams");
  293.             }
  294.             xml.closeEmptyElement();
  295.             return xml;
  296.         }
  297.     }

  298.     /**
  299.      * After selected a SOCKS5 stream host and successfully connecting, the target of the file
  300.      * transfer returns a byte stream stanza with the stream host used extension.
  301.      *
  302.      * @author Alexander Wenckus
  303.      */
  304.     public static class StreamHostUsed implements NamedElement {

  305.         public static String ELEMENTNAME = "streamhost-used";

  306.         private final Jid JID;

  307.         /**
  308.          * Default constructor.
  309.          *
  310.          * @param JID The JID of the selected stream host.
  311.          */
  312.         public StreamHostUsed(final Jid JID) {
  313.             this.JID = JID;
  314.         }

  315.         /**
  316.          * Returns the JID of the selected stream host.
  317.          *
  318.          * @return Returns the JID of the selected stream host.
  319.          */
  320.         public Jid getJID() {
  321.             return JID;
  322.         }

  323.         @Override
  324.         public String getElementName() {
  325.             return ELEMENTNAME;
  326.         }

  327.         @Override
  328.         public XmlStringBuilder toXML(String enclosingNamespace) {
  329.             XmlStringBuilder xml = new XmlStringBuilder(this);
  330.             xml.attribute("jid", getJID());
  331.             xml.closeEmptyElement();
  332.             return xml;
  333.         }
  334.     }

  335.     /**
  336.      * The stanza sent by the stream initiator to the stream proxy to activate the connection.
  337.      *
  338.      * @author Alexander Wenckus
  339.      */
  340.     public static class Activate implements NamedElement {

  341.         public static String ELEMENTNAME = "activate";

  342.         private final Jid target;

  343.         /**
  344.          * Default constructor specifying the target of the stream.
  345.          *
  346.          * @param target The target of the stream.
  347.          */
  348.         public Activate(final Jid target) {
  349.             this.target = target;
  350.         }

  351.         /**
  352.          * Returns the target of the activation.
  353.          *
  354.          * @return Returns the target of the activation.
  355.          */
  356.         public Jid getTarget() {
  357.             return target;
  358.         }

  359.         @Override
  360.         public String getElementName() {
  361.             return ELEMENTNAME;
  362.         }

  363.         @Override
  364.         public XmlStringBuilder toXML(String enclosingNamespace) {
  365.             XmlStringBuilder xml = new XmlStringBuilder(this);
  366.             xml.rightAngleBracket();
  367.             xml.escape(getTarget());
  368.             xml.closeElement(this);
  369.             return xml;
  370.         }
  371.     }

  372.     /**
  373.      * The stream can be either a TCP stream or a UDP stream.
  374.      *
  375.      * @author Alexander Wenckus
  376.      */
  377.     public enum Mode {

  378.         /**
  379.          * A TCP based stream.
  380.          */
  381.         tcp,

  382.         /**
  383.          * A UDP based stream.
  384.          */
  385.         udp;

  386.         public static Mode fromName(String name) {
  387.             Mode mode;
  388.             try {
  389.                 mode = Mode.valueOf(name);
  390.             }
  391.             catch (Exception ex) {
  392.                 mode = tcp;
  393.             }

  394.             return mode;
  395.         }
  396.     }
  397. }