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 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<>(); 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 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 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 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(null)); 236 } 237 } 238 else { 239 xml.append(getToActivate().toXML(null)); 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(null)); 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 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 * @param port port of the stream host. 288 */ 289 public StreamHost(final Jid JID, final String address, int port) { 290 this.JID = Objects.requireNonNull(JID, "StreamHost JID must not be null"); 291 this.addy = StringUtils.requireNotNullOrEmpty(address, "StreamHost address must not be null"); 292 this.port = port; 293 } 294 295 /** 296 * Returns the JID of the stream host. 297 * 298 * @return Returns the JID of the stream host. 299 */ 300 public Jid getJID() { 301 return JID; 302 } 303 304 /** 305 * Returns the internet address of the stream host. 306 * 307 * @return Returns the internet address of the stream host. 308 */ 309 public String getAddress() { 310 return addy; 311 } 312 313 /** 314 * Returns the port on which the potential stream host would accept the connection. 315 * 316 * @return Returns the port on which the potential stream host would accept the connection. 317 */ 318 public int getPort() { 319 return port; 320 } 321 322 @Override 323 public String getElementName() { 324 return ELEMENTNAME; 325 } 326 327 @Override 328 public XmlStringBuilder toXML(String enclosingNamespace) { 329 XmlStringBuilder xml = new XmlStringBuilder(this); 330 xml.attribute("jid", getJID()); 331 xml.attribute("host", getAddress()); 332 if (getPort() != 0) { 333 xml.attribute("port", Integer.toString(getPort())); 334 } else { 335 xml.attribute("zeroconf", "_jabber.bytestreams"); 336 } 337 xml.closeEmptyElement(); 338 return xml; 339 } 340 } 341 342 /** 343 * After selected a SOCKS5 stream host and successfully connecting, the target of the file 344 * transfer returns a byte stream stanza with the stream host used extension. 345 * 346 * @author Alexander Wenckus 347 */ 348 public static class StreamHostUsed implements NamedElement { 349 350 public static String ELEMENTNAME = "streamhost-used"; 351 352 private final Jid JID; 353 354 /** 355 * Default constructor. 356 * 357 * @param JID The JID of the selected stream host. 358 */ 359 public StreamHostUsed(final Jid JID) { 360 this.JID = JID; 361 } 362 363 /** 364 * Returns the JID of the selected stream host. 365 * 366 * @return Returns the JID of the selected stream host. 367 */ 368 public Jid getJID() { 369 return JID; 370 } 371 372 @Override 373 public String getElementName() { 374 return ELEMENTNAME; 375 } 376 377 @Override 378 public XmlStringBuilder toXML(String enclosingNamespace) { 379 XmlStringBuilder xml = new XmlStringBuilder(this); 380 xml.attribute("jid", getJID()); 381 xml.closeEmptyElement(); 382 return xml; 383 } 384 } 385 386 /** 387 * The stanza sent by the stream initiator to the stream proxy to activate the connection. 388 * 389 * @author Alexander Wenckus 390 */ 391 public static class Activate implements NamedElement { 392 393 public static String ELEMENTNAME = "activate"; 394 395 private final Jid target; 396 397 /** 398 * Default constructor specifying the target of the stream. 399 * 400 * @param target The target of the stream. 401 */ 402 public Activate(final Jid target) { 403 this.target = target; 404 } 405 406 /** 407 * Returns the target of the activation. 408 * 409 * @return Returns the target of the activation. 410 */ 411 public Jid getTarget() { 412 return target; 413 } 414 415 @Override 416 public String getElementName() { 417 return ELEMENTNAME; 418 } 419 420 @Override 421 public XmlStringBuilder toXML(String enclosingNamespace) { 422 XmlStringBuilder xml = new XmlStringBuilder(this); 423 xml.rightAngleBracket(); 424 xml.escape(getTarget()); 425 xml.closeElement(this); 426 return xml; 427 } 428 } 429 430 /** 431 * The stream can be either a TCP stream or a UDP stream. 432 * 433 * @author Alexander Wenckus 434 */ 435 public enum Mode { 436 437 /** 438 * A TCP based stream. 439 */ 440 tcp, 441 442 /** 443 * A UDP based stream. 444 */ 445 udp; 446 447 public static Mode fromName(String name) { 448 Mode mode; 449 try { 450 mode = Mode.valueOf(name); 451 } 452 catch (Exception ex) { 453 mode = tcp; 454 } 455 456 return mode; 457 } 458 } 459}