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