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}