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(/packet) 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<StreamHost>();
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(/packet) 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(/packet) 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(/packet) 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());
236                }
237            }
238            else {
239                xml.append(getToActivate().toXML());
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());
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(/Packet) 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         */
288        public StreamHost(final Jid JID, final String address, int port) {
289            this.JID = Objects.requireNonNull(JID, "StreamHost JID must not be null");
290            this.addy = StringUtils.requireNotNullOrEmpty(address, "StreamHost address must not be null");
291            this.port = port;
292        }
293
294        /**
295         * Returns the JID of the stream host.
296         * 
297         * @return Returns the JID of the stream host.
298         */
299        public Jid getJID() {
300            return JID;
301        }
302
303        /**
304         * Returns the internet address of the stream host.
305         * 
306         * @return Returns the internet address of the stream host.
307         */
308        public String getAddress() {
309            return addy;
310        }
311
312        /**
313         * Returns the port on which the potential stream host would accept the connection.
314         * 
315         * @return Returns the port on which the potential stream host would accept the connection.
316         */
317        public int getPort() {
318            return port;
319        }
320
321        @Override
322        public String getElementName() {
323            return ELEMENTNAME;
324        }
325
326        @Override
327        public XmlStringBuilder toXML() {
328            XmlStringBuilder xml = new XmlStringBuilder(this);
329            xml.attribute("jid", getJID());
330            xml.attribute("host", getAddress());
331            if (getPort() != 0) {
332                xml.attribute("port", Integer.toString(getPort()));
333            } else {
334                xml.attribute("zeroconf", "_jabber.bytestreams");
335            }
336            xml.closeEmptyElement();
337            return xml;
338        }
339    }
340
341    /**
342     * After selected a SOCKS5 stream host and successfully connecting, the target of the file
343     * transfer returns a byte stream stanza(/packet) with the stream host used extension.
344     * 
345     * @author Alexander Wenckus
346     */
347    public static class StreamHostUsed implements NamedElement {
348
349        public static String ELEMENTNAME = "streamhost-used";
350
351        private final Jid JID;
352
353        /**
354         * Default constructor.
355         * 
356         * @param JID The JID of the selected stream host.
357         */
358        public StreamHostUsed(final Jid JID) {
359            this.JID = JID;
360        }
361
362        /**
363         * Returns the JID of the selected stream host.
364         * 
365         * @return Returns the JID of the selected stream host.
366         */
367        public Jid getJID() {
368            return JID;
369        }
370
371        @Override
372        public String getElementName() {
373            return ELEMENTNAME;
374        }
375
376        @Override
377        public XmlStringBuilder toXML() {
378            XmlStringBuilder xml = new XmlStringBuilder(this);
379            xml.attribute("jid", getJID());
380            xml.closeEmptyElement();
381            return xml;
382        }
383    }
384
385    /**
386     * The stanza(/packet) sent by the stream initiator to the stream proxy to activate the connection.
387     * 
388     * @author Alexander Wenckus
389     */
390    public static class Activate implements NamedElement {
391
392        public static String ELEMENTNAME = "activate";
393
394        private final Jid target;
395
396        /**
397         * Default constructor specifying the target of the stream.
398         * 
399         * @param target The target of the stream.
400         */
401        public Activate(final Jid target) {
402            this.target = target;
403        }
404
405        /**
406         * Returns the target of the activation.
407         * 
408         * @return Returns the target of the activation.
409         */
410        public Jid getTarget() {
411            return target;
412        }
413
414        @Override
415        public String getElementName() {
416            return ELEMENTNAME;
417        }
418
419        @Override
420        public XmlStringBuilder toXML() {
421            XmlStringBuilder xml = new XmlStringBuilder(this);
422            xml.rightAngleBracket();
423            xml.escape(getTarget());
424            xml.closeElement(this);
425            return xml;
426        }
427    }
428
429    /**
430     * The stream can be either a TCP stream or a UDP stream.
431     * 
432     * @author Alexander Wenckus
433     */
434    public enum Mode {
435
436        /**
437         * A TCP based stream.
438         */
439        tcp,
440
441        /**
442         * A UDP based stream.
443         */
444        udp;
445
446        public static Mode fromName(String name) {
447            Mode mode;
448            try {
449                mode = Mode.valueOf(name);
450            }
451            catch (Exception ex) {
452                mode = tcp;
453            }
454
455            return mode;
456        }
457    }
458}