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