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