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}