001/**
002 *
003 * Copyright the original author or authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.bytestreams.socks5.packet;
018
019import java.net.InetAddress;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.List;
023
024import org.jivesoftware.smack.packet.ExtensionElement;
025import org.jivesoftware.smack.packet.IQ;
026import org.jivesoftware.smack.util.InternetAddress;
027import org.jivesoftware.smack.util.Objects;
028import org.jivesoftware.smack.util.XmlStringBuilder;
029
030import org.jxmpp.jid.Jid;
031
032/**
033 * A stanza representing part of a SOCKS5 Bytestream negotiation.
034 *
035 * @author Alexander Wenckus
036 */
037public class Bytestream extends IQ {
038
039    public static final String ELEMENT = QUERY_ELEMENT;
040
041    /**
042     * The XMPP namespace of the SOCKS5 Bytestream.
043     */
044    public static final String NAMESPACE = "http://jabber.org/protocol/bytestreams";
045
046    private String sessionID;
047
048    private Mode mode = Mode.tcp;
049
050    private final List<StreamHost> streamHosts = new ArrayList<>();
051
052    private StreamHostUsed usedHost;
053
054    private Activate toActivate;
055
056    /**
057     * The default constructor.
058     */
059    public Bytestream() {
060        super(ELEMENT, NAMESPACE);
061    }
062
063    /**
064     * A constructor where the session ID can be specified.
065     *
066     * @param SID The session ID related to the negotiation.
067     * @see #setSessionID(String)
068     */
069    public Bytestream(final String SID) {
070        this();
071        setSessionID(SID);
072    }
073
074    /**
075     * Set the session ID related to the bytestream. The session ID is a unique identifier used to
076     * differentiate between stream negotiations.
077     *
078     * @param sessionID the unique session ID that identifies the transfer.
079     */
080    public void setSessionID(final String sessionID) {
081        this.sessionID = sessionID;
082    }
083
084    /**
085     * Returns the session ID related to the bytestream negotiation.
086     *
087     * @return Returns the session ID related to the bytestream negotiation.
088     * @see #setSessionID(String)
089     */
090    public String getSessionID() {
091        return sessionID;
092    }
093
094    /**
095     * Set the transport mode. This should be put in the initiation of the interaction.
096     *
097     * @param mode the transport mode, either UDP or TCP
098     * @see Mode
099     */
100    public void setMode(final Mode mode) {
101        this.mode = mode;
102    }
103
104    /**
105     * Returns the transport mode.
106     *
107     * @return Returns the transport mode.
108     * @see #setMode(Mode)
109     */
110    public Mode getMode() {
111        return mode;
112    }
113
114    /**
115     * Adds a potential stream host that the remote user can connect to to receive the file.
116     *
117     * @param JID The JID of the stream host.
118     * @param address The internet address of the stream host.
119     * @return The added stream host.
120     */
121    public StreamHost addStreamHost(final Jid JID, String address) {
122        return addStreamHost(JID, address, 0);
123    }
124
125    /**
126     * Adds a potential stream host that the remote user can connect to to receive the file.
127     *
128     * @param JID The JID of the stream host.
129     * @param address The internet address of the stream host.
130     * @param port The port on which the remote host is seeking connections.
131     * @return The added stream host.
132     */
133    public StreamHost addStreamHost(final Jid JID, String address, final int port) {
134        StreamHost host = new StreamHost(JID, address, port);
135        addStreamHost(host);
136
137        return host;
138    }
139
140    /**
141     * Adds a potential stream host that the remote user can transfer the file through.
142     *
143     * @param host The potential stream host.
144     */
145    public void addStreamHost(final StreamHost host) {
146        streamHosts.add(host);
147    }
148
149    /**
150     * Returns the list of stream hosts contained in the packet.
151     *
152     * @return Returns the list of stream hosts contained in the packet.
153     */
154    public List<StreamHost> getStreamHosts() {
155        return Collections.unmodifiableList(streamHosts);
156    }
157
158    /**
159     * Returns the stream host related to the given JID, or null if there is none.
160     *
161     * @param JID The JID of the desired stream host.
162     * @return Returns the stream host related to the given JID, or null if there is none.
163     */
164    public StreamHost getStreamHost(final Jid JID) {
165        if (JID == null) {
166            return null;
167        }
168        for (StreamHost host : streamHosts) {
169            if (host.getJID().equals(JID)) {
170                return host;
171            }
172        }
173
174        return null;
175    }
176
177    /**
178     * Returns the count of stream hosts contained in this packet.
179     *
180     * @return Returns the count of stream hosts contained in this packet.
181     */
182    public int countStreamHosts() {
183        return streamHosts.size();
184    }
185
186    /**
187     * Upon connecting to the stream host the target of the stream replies to the initiator with the
188     * JID of the SOCKS5 host that they used.
189     *
190     * @param JID The JID of the used host.
191     */
192    public void setUsedHost(final Jid JID) {
193        this.usedHost = new StreamHostUsed(JID);
194    }
195
196    /**
197     * Returns the SOCKS5 host connected to by the remote user.
198     *
199     * @return Returns the SOCKS5 host connected to by the remote user.
200     */
201    public StreamHostUsed getUsedHost() {
202        return usedHost;
203    }
204
205    /**
206     * Returns the activate element of the stanza sent to the proxy host to verify the identity of
207     * the initiator and match them to the appropriate stream.
208     *
209     * @return Returns the activate element of the stanza sent to the proxy host to verify the
210     *         identity of the initiator and match them to the appropriate stream.
211     */
212    public Activate getToActivate() {
213        return toActivate;
214    }
215
216    /**
217     * Upon the response from the target of the used host the activate stanza is sent to the SOCKS5
218     * proxy. The proxy will activate the stream or return an error after verifying the identity of
219     * the initiator, using the activate packet.
220     *
221     * @param targetID The JID of the target of the file transfer.
222     */
223    public void setToActivate(final Jid targetID) {
224        this.toActivate = new Activate(targetID);
225    }
226
227    @Override
228    protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
229        switch (getType()) {
230        case set:
231            xml.optAttribute("sid", getSessionID());
232            xml.optAttribute("mode", getMode());
233            xml.rightAngleBracket();
234            if (getToActivate() == null) {
235                for (StreamHost streamHost : getStreamHosts()) {
236                    xml.append(streamHost.toXML());
237                }
238            }
239            else {
240                xml.append(getToActivate().toXML());
241            }
242            break;
243        case result:
244            xml.rightAngleBracket();
245            xml.optAppend(getUsedHost());
246            // TODO Bytestream can include either used host *or* streamHosts. Never both. This should be ensured by the
247            // constructions mechanisms of Bytestream
248            // A result from the server can also contain stream hosts
249            for (StreamHost host : streamHosts) {
250                xml.append(host.toXML());
251            }
252            break;
253        case get:
254            xml.setEmptyElement();
255            break;
256        default:
257            throw new IllegalStateException();
258        }
259
260        return xml;
261    }
262
263    private abstract static class BytestreamExtensionElement implements ExtensionElement {
264        @Override
265        public final String getNamespace() {
266            return NAMESPACE;
267        }
268    }
269
270    /**
271     * Stanza extension that represents a potential SOCKS5 proxy for the file transfer. Stream hosts
272     * are forwarded to the target of the file transfer who then chooses and connects to one.
273     *
274     * @author Alexander Wenckus
275     */
276    public static class StreamHost extends BytestreamExtensionElement {
277
278        public static String ELEMENTNAME = "streamhost";
279
280        private final Jid jid;
281
282        private final InternetAddress address;
283
284        private final int port;
285
286        public StreamHost(Jid jid, String address) {
287            this(jid, address, 0);
288        }
289
290        /**
291         * Default constructor.
292         *
293         * @param jid The JID of the stream host.
294         * @param address The internet address of the stream host.
295         * @param port port of the stream host.
296         */
297        public StreamHost(final Jid jid, final String address, int port) {
298            this(jid, InternetAddress.from(address), port);
299        }
300
301        public StreamHost(Jid jid, InetAddress address, int port) {
302            this(jid, InternetAddress.from(address), port);
303        }
304
305        /**
306         * Stream Host constructor.
307         *
308         * @param jid The JID of the stream host.
309         * @param address The internet address of the stream host.
310         * @param port port of the stream host.
311         */
312        public StreamHost(Jid jid, InternetAddress address, int port) {
313            this.jid = Objects.requireNonNull(jid, "StreamHost JID must not be null");
314            this.address = Objects.requireNonNull(address);
315            this.port = port;
316        }
317
318        /**
319         * Returns the JID of the stream host.
320         *
321         * @return Returns the JID of the stream host.
322         */
323        public Jid getJID() {
324            return jid;
325        }
326
327        /**
328         * Returns the internet address of the stream host.
329         *
330         * @return Returns the internet address of the stream host.
331         */
332        public InternetAddress getAddress() {
333            return address;
334        }
335
336        /**
337         * Returns the port on which the potential stream host would accept the connection.
338         *
339         * @return Returns the port on which the potential stream host would accept the connection.
340         */
341        public int getPort() {
342            return port;
343        }
344
345        @Override
346        public String getElementName() {
347            return ELEMENTNAME;
348        }
349
350        @Override
351        public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
352            XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
353            xml.attribute("jid", getJID());
354            xml.attribute("host", address);
355            if (getPort() != 0) {
356                xml.attribute("port", Integer.toString(getPort()));
357            } else {
358                xml.attribute("zeroconf", "_jabber.bytestreams");
359            }
360            xml.closeEmptyElement();
361            return xml;
362        }
363
364        @Override
365        public String toString() {
366            return "SOCKS5 Stream Host: " + jid + "[" + address + ":" + port + "]";
367        }
368    }
369
370    /**
371     * After selected a SOCKS5 stream host and successfully connecting, the target of the file
372     * transfer returns a byte stream stanza with the stream host used extension.
373     *
374     * @author Alexander Wenckus
375     */
376    public static class StreamHostUsed extends BytestreamExtensionElement {
377
378        public static String ELEMENTNAME = "streamhost-used";
379
380        private final Jid jid;
381
382        /**
383         * Default constructor.
384         *
385         * @param jid The JID of the selected stream host.
386         */
387        public StreamHostUsed(final Jid jid) {
388            this.jid = jid;
389        }
390
391        /**
392         * Returns the JID of the selected stream host.
393         *
394         * @return Returns the JID of the selected stream host.
395         */
396        public Jid getJID() {
397            return jid;
398        }
399
400        @Override
401        public String getElementName() {
402            return ELEMENTNAME;
403        }
404
405        @Override
406        public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
407            XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
408            xml.attribute("jid", getJID());
409            xml.closeEmptyElement();
410            return xml;
411        }
412    }
413
414    /**
415     * The stanza sent by the stream initiator to the stream proxy to activate the connection.
416     *
417     * @author Alexander Wenckus
418     */
419    public static class Activate extends BytestreamExtensionElement {
420
421        public static String ELEMENTNAME = "activate";
422
423        private final Jid target;
424
425        /**
426         * Default constructor specifying the target of the stream.
427         *
428         * @param target The target of the stream.
429         */
430        public Activate(final Jid target) {
431            this.target = target;
432        }
433
434        /**
435         * Returns the target of the activation.
436         *
437         * @return Returns the target of the activation.
438         */
439        public Jid getTarget() {
440            return target;
441        }
442
443        @Override
444        public String getElementName() {
445            return ELEMENTNAME;
446        }
447
448        @Override
449        public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
450            XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
451            xml.rightAngleBracket();
452            xml.escape(getTarget());
453            xml.closeElement(this);
454            return xml;
455        }
456
457    }
458
459    /**
460     * The stream can be either a TCP stream or a UDP stream.
461     *
462     * @author Alexander Wenckus
463     */
464    public enum Mode {
465
466        /**
467         * A TCP based stream.
468         */
469        tcp,
470
471        /**
472         * A UDP based stream.
473         */
474        udp;
475
476        public static Mode fromName(String name) {
477            Mode mode;
478            try {
479                mode = Mode.valueOf(name);
480            }
481            catch (Exception ex) {
482                mode = tcp;
483            }
484
485            return mode;
486        }
487    }
488}