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