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}