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