/** | |
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.jivesoftware.smackx.bytestreams.socks5.packet; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.Collections; | |
import java.util.List; | |
import org.jivesoftware.smack.packet.IQ; | |
import org.jivesoftware.smack.packet.PacketExtension; | |
/** | |
* A packet representing part of a SOCKS5 Bytestream negotiation. | |
* | |
* @author Alexander Wenckus | |
*/ | |
public class Bytestream extends IQ { | |
private String sessionID; | |
private Mode mode = Mode.tcp; | |
private final List<StreamHost> streamHosts = new ArrayList<StreamHost>(); | |
private StreamHostUsed usedHost; | |
private Activate toActivate; | |
/** | |
* The default constructor | |
*/ | |
public Bytestream() { | |
super(); | |
} | |
/** | |
* A constructor where the session ID can be specified. | |
* | |
* @param SID The session ID related to the negotiation. | |
* @see #setSessionID(String) | |
*/ | |
public Bytestream(final String SID) { | |
super(); | |
setSessionID(SID); | |
} | |
/** | |
* Set the session ID related to the bytestream. The session ID is a unique identifier used to | |
* differentiate between stream negotiations. | |
* | |
* @param sessionID the unique session ID that identifies the transfer. | |
*/ | |
public void setSessionID(final String sessionID) { | |
this.sessionID = sessionID; | |
} | |
/** | |
* Returns the session ID related to the bytestream negotiation. | |
* | |
* @return Returns the session ID related to the bytestream negotiation. | |
* @see #setSessionID(String) | |
*/ | |
public String getSessionID() { | |
return sessionID; | |
} | |
/** | |
* Set the transport mode. This should be put in the initiation of the interaction. | |
* | |
* @param mode the transport mode, either UDP or TCP | |
* @see Mode | |
*/ | |
public void setMode(final Mode mode) { | |
this.mode = mode; | |
} | |
/** | |
* Returns the transport mode. | |
* | |
* @return Returns the transport mode. | |
* @see #setMode(Mode) | |
*/ | |
public Mode getMode() { | |
return mode; | |
} | |
/** | |
* Adds a potential stream host that the remote user can connect to to receive the file. | |
* | |
* @param JID The JID of the stream host. | |
* @param address The internet address of the stream host. | |
* @return The added stream host. | |
*/ | |
public StreamHost addStreamHost(final String JID, final String address) { | |
return addStreamHost(JID, address, 0); | |
} | |
/** | |
* Adds a potential stream host that the remote user can connect to to receive the file. | |
* | |
* @param JID The JID of the stream host. | |
* @param address The internet address of the stream host. | |
* @param port The port on which the remote host is seeking connections. | |
* @return The added stream host. | |
*/ | |
public StreamHost addStreamHost(final String JID, final String address, final int port) { | |
StreamHost host = new StreamHost(JID, address); | |
host.setPort(port); | |
addStreamHost(host); | |
return host; | |
} | |
/** | |
* Adds a potential stream host that the remote user can transfer the file through. | |
* | |
* @param host The potential stream host. | |
*/ | |
public void addStreamHost(final StreamHost host) { | |
streamHosts.add(host); | |
} | |
/** | |
* Returns the list of stream hosts contained in the packet. | |
* | |
* @return Returns the list of stream hosts contained in the packet. | |
*/ | |
public Collection<StreamHost> getStreamHosts() { | |
return Collections.unmodifiableCollection(streamHosts); | |
} | |
/** | |
* Returns the stream host related to the given JID, or null if there is none. | |
* | |
* @param JID The JID of the desired stream host. | |
* @return Returns the stream host related to the given JID, or null if there is none. | |
*/ | |
public StreamHost getStreamHost(final String JID) { | |
if (JID == null) { | |
return null; | |
} | |
for (StreamHost host : streamHosts) { | |
if (host.getJID().equals(JID)) { | |
return host; | |
} | |
} | |
return null; | |
} | |
/** | |
* Returns the count of stream hosts contained in this packet. | |
* | |
* @return Returns the count of stream hosts contained in this packet. | |
*/ | |
public int countStreamHosts() { | |
return streamHosts.size(); | |
} | |
/** | |
* Upon connecting to the stream host the target of the stream replies to the initiator with the | |
* JID of the SOCKS5 host that they used. | |
* | |
* @param JID The JID of the used host. | |
*/ | |
public void setUsedHost(final String JID) { | |
this.usedHost = new StreamHostUsed(JID); | |
} | |
/** | |
* Returns the SOCKS5 host connected to by the remote user. | |
* | |
* @return Returns the SOCKS5 host connected to by the remote user. | |
*/ | |
public StreamHostUsed getUsedHost() { | |
return usedHost; | |
} | |
/** | |
* Returns the activate element of the packet sent to the proxy host to verify the identity of | |
* the initiator and match them to the appropriate stream. | |
* | |
* @return Returns the activate element of the packet sent to the proxy host to verify the | |
* identity of the initiator and match them to the appropriate stream. | |
*/ | |
public Activate getToActivate() { | |
return toActivate; | |
} | |
/** | |
* Upon the response from the target of the used host the activate packet is sent to the SOCKS5 | |
* proxy. The proxy will activate the stream or return an error after verifying the identity of | |
* the initiator, using the activate packet. | |
* | |
* @param targetID The JID of the target of the file transfer. | |
*/ | |
public void setToActivate(final String targetID) { | |
this.toActivate = new Activate(targetID); | |
} | |
public String getChildElementXML() { | |
StringBuilder buf = new StringBuilder(); | |
buf.append("<query xmlns=\"http://jabber.org/protocol/bytestreams\""); | |
if (this.getType().equals(IQ.Type.SET)) { | |
if (getSessionID() != null) { | |
buf.append(" sid=\"").append(getSessionID()).append("\""); | |
} | |
if (getMode() != null) { | |
buf.append(" mode = \"").append(getMode()).append("\""); | |
} | |
buf.append(">"); | |
if (getToActivate() == null) { | |
for (StreamHost streamHost : getStreamHosts()) { | |
buf.append(streamHost.toXML()); | |
} | |
} | |
else { | |
buf.append(getToActivate().toXML()); | |
} | |
} | |
else if (this.getType().equals(IQ.Type.RESULT)) { | |
buf.append(">"); | |
if (getUsedHost() != null) { | |
buf.append(getUsedHost().toXML()); | |
} | |
// A result from the server can also contain stream hosts | |
else if (countStreamHosts() > 0) { | |
for (StreamHost host : streamHosts) { | |
buf.append(host.toXML()); | |
} | |
} | |
} | |
else if (this.getType().equals(IQ.Type.GET)) { | |
return buf.append("/>").toString(); | |
} | |
else { | |
return null; | |
} | |
buf.append("</query>"); | |
return buf.toString(); | |
} | |
/** | |
* Packet extension that represents a potential SOCKS5 proxy for the file transfer. Stream hosts | |
* are forwarded to the target of the file transfer who then chooses and connects to one. | |
* | |
* @author Alexander Wenckus | |
*/ | |
public static class StreamHost implements PacketExtension { | |
public static String NAMESPACE = ""; | |
public static String ELEMENTNAME = "streamhost"; | |
private final String JID; | |
private final String addy; | |
private int port = 0; | |
/** | |
* Default constructor. | |
* | |
* @param JID The JID of the stream host. | |
* @param address The internet address of the stream host. | |
*/ | |
public StreamHost(final String JID, final String address) { | |
this.JID = JID; | |
this.addy = address; | |
} | |
/** | |
* Returns the JID of the stream host. | |
* | |
* @return Returns the JID of the stream host. | |
*/ | |
public String getJID() { | |
return JID; | |
} | |
/** | |
* Returns the internet address of the stream host. | |
* | |
* @return Returns the internet address of the stream host. | |
*/ | |
public String getAddress() { | |
return addy; | |
} | |
/** | |
* Sets the port of the stream host. | |
* | |
* @param port The port on which the potential stream host would accept the connection. | |
*/ | |
public void setPort(final int port) { | |
this.port = port; | |
} | |
/** | |
* Returns the port on which the potential stream host would accept the connection. | |
* | |
* @return Returns the port on which the potential stream host would accept the connection. | |
*/ | |
public int getPort() { | |
return port; | |
} | |
public String getNamespace() { | |
return NAMESPACE; | |
} | |
public String getElementName() { | |
return ELEMENTNAME; | |
} | |
public String toXML() { | |
StringBuilder buf = new StringBuilder(); | |
buf.append("<").append(getElementName()).append(" "); | |
buf.append("jid=\"").append(getJID()).append("\" "); | |
buf.append("host=\"").append(getAddress()).append("\" "); | |
if (getPort() != 0) { | |
buf.append("port=\"").append(getPort()).append("\""); | |
} | |
else { | |
buf.append("zeroconf=\"_jabber.bytestreams\""); | |
} | |
buf.append("/>"); | |
return buf.toString(); | |
} | |
} | |
/** | |
* After selected a SOCKS5 stream host and successfully connecting, the target of the file | |
* transfer returns a byte stream packet with the stream host used extension. | |
* | |
* @author Alexander Wenckus | |
*/ | |
public static class StreamHostUsed implements PacketExtension { | |
public String NAMESPACE = ""; | |
public static String ELEMENTNAME = "streamhost-used"; | |
private final String JID; | |
/** | |
* Default constructor. | |
* | |
* @param JID The JID of the selected stream host. | |
*/ | |
public StreamHostUsed(final String JID) { | |
this.JID = JID; | |
} | |
/** | |
* Returns the JID of the selected stream host. | |
* | |
* @return Returns the JID of the selected stream host. | |
*/ | |
public String getJID() { | |
return JID; | |
} | |
public String getNamespace() { | |
return NAMESPACE; | |
} | |
public String getElementName() { | |
return ELEMENTNAME; | |
} | |
public String toXML() { | |
StringBuilder buf = new StringBuilder(); | |
buf.append("<").append(getElementName()).append(" "); | |
buf.append("jid=\"").append(getJID()).append("\" "); | |
buf.append("/>"); | |
return buf.toString(); | |
} | |
} | |
/** | |
* The packet sent by the stream initiator to the stream proxy to activate the connection. | |
* | |
* @author Alexander Wenckus | |
*/ | |
public static class Activate implements PacketExtension { | |
public String NAMESPACE = ""; | |
public static String ELEMENTNAME = "activate"; | |
private final String target; | |
/** | |
* Default constructor specifying the target of the stream. | |
* | |
* @param target The target of the stream. | |
*/ | |
public Activate(final String target) { | |
this.target = target; | |
} | |
/** | |
* Returns the target of the activation. | |
* | |
* @return Returns the target of the activation. | |
*/ | |
public String getTarget() { | |
return target; | |
} | |
public String getNamespace() { | |
return NAMESPACE; | |
} | |
public String getElementName() { | |
return ELEMENTNAME; | |
} | |
public String toXML() { | |
StringBuilder buf = new StringBuilder(); | |
buf.append("<").append(getElementName()).append(">"); | |
buf.append(getTarget()); | |
buf.append("</").append(getElementName()).append(">"); | |
return buf.toString(); | |
} | |
} | |
/** | |
* The stream can be either a TCP stream or a UDP stream. | |
* | |
* @author Alexander Wenckus | |
*/ | |
public enum Mode { | |
/** | |
* A TCP based stream. | |
*/ | |
tcp, | |
/** | |
* A UDP based stream. | |
*/ | |
udp; | |
public static Mode fromName(String name) { | |
Mode mode; | |
try { | |
mode = Mode.valueOf(name); | |
} | |
catch (Exception ex) { | |
mode = tcp; | |
} | |
return mode; | |
} | |
} | |
} |