aboutsummaryrefslogtreecommitdiff
path: root/src/org/jivesoftware/smackx/bytestreams/ibb
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/jivesoftware/smackx/bytestreams/ibb')
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/CloseListener.java75
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/DataListener.java73
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamListener.java46
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamManager.java546
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamRequest.java92
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamSession.java795
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/InitiationListener.java127
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/packet/Close.java65
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/packet/Data.java64
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java149
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/packet/Open.java126
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/provider/CloseIQProvider.java33
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/provider/DataPacketProvider.java45
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/ibb/provider/OpenIQProvider.java45
14 files changed, 2281 insertions, 0 deletions
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/CloseListener.java b/src/org/jivesoftware/smackx/bytestreams/ibb/CloseListener.java
new file mode 100644
index 0000000..7690e95
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/CloseListener.java
@@ -0,0 +1,75 @@
+/**
+ * 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.ibb;
+
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.filter.AndFilter;
+import org.jivesoftware.smack.filter.IQTypeFilter;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.PacketTypeFilter;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smackx.bytestreams.ibb.packet.Close;
+
+/**
+ * CloseListener handles all In-Band Bytestream close requests.
+ * <p>
+ * If a close request is received it looks if a stored In-Band Bytestream
+ * session exists and closes it. If no session with the given session ID exists
+ * an &lt;item-not-found/&gt; error is returned to the sender.
+ *
+ * @author Henning Staib
+ */
+class CloseListener implements PacketListener {
+
+ /* manager containing the listeners and the XMPP connection */
+ private final InBandBytestreamManager manager;
+
+ /* packet filter for all In-Band Bytestream close requests */
+ private final PacketFilter closeFilter = new AndFilter(new PacketTypeFilter(
+ Close.class), new IQTypeFilter(IQ.Type.SET));
+
+ /**
+ * Constructor.
+ *
+ * @param manager the In-Band Bytestream manager
+ */
+ protected CloseListener(InBandBytestreamManager manager) {
+ this.manager = manager;
+ }
+
+ public void processPacket(Packet packet) {
+ Close closeRequest = (Close) packet;
+ InBandBytestreamSession ibbSession = this.manager.getSessions().get(
+ closeRequest.getSessionID());
+ if (ibbSession == null) {
+ this.manager.replyItemNotFoundPacket(closeRequest);
+ }
+ else {
+ ibbSession.closeByPeer(closeRequest);
+ this.manager.getSessions().remove(closeRequest.getSessionID());
+ }
+
+ }
+
+ /**
+ * Returns the packet filter for In-Band Bytestream close requests.
+ *
+ * @return the packet filter for In-Band Bytestream close requests
+ */
+ protected PacketFilter getFilter() {
+ return this.closeFilter;
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/DataListener.java b/src/org/jivesoftware/smackx/bytestreams/ibb/DataListener.java
new file mode 100644
index 0000000..166c146
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/DataListener.java
@@ -0,0 +1,73 @@
+/**
+ * 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.ibb;
+
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.filter.AndFilter;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.PacketTypeFilter;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smackx.bytestreams.ibb.packet.Data;
+
+/**
+ * DataListener handles all In-Band Bytestream IQ stanzas containing a data
+ * packet extension that don't belong to an existing session.
+ * <p>
+ * If a data packet is received it looks if a stored In-Band Bytestream session
+ * exists. If no session with the given session ID exists an
+ * &lt;item-not-found/&gt; error is returned to the sender.
+ * <p>
+ * Data packets belonging to a running In-Band Bytestream session are processed
+ * by more specific listeners registered when an {@link InBandBytestreamSession}
+ * is created.
+ *
+ * @author Henning Staib
+ */
+class DataListener implements PacketListener {
+
+ /* manager containing the listeners and the XMPP connection */
+ private final InBandBytestreamManager manager;
+
+ /* packet filter for all In-Band Bytestream data packets */
+ private final PacketFilter dataFilter = new AndFilter(
+ new PacketTypeFilter(Data.class));
+
+ /**
+ * Constructor.
+ *
+ * @param manager the In-Band Bytestream manager
+ */
+ public DataListener(InBandBytestreamManager manager) {
+ this.manager = manager;
+ }
+
+ public void processPacket(Packet packet) {
+ Data data = (Data) packet;
+ InBandBytestreamSession ibbSession = this.manager.getSessions().get(
+ data.getDataPacketExtension().getSessionID());
+ if (ibbSession == null) {
+ this.manager.replyItemNotFoundPacket(data);
+ }
+ }
+
+ /**
+ * Returns the packet filter for In-Band Bytestream data packets.
+ *
+ * @return the packet filter for In-Band Bytestream data packets
+ */
+ protected PacketFilter getFilter() {
+ return this.dataFilter;
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamListener.java b/src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamListener.java
new file mode 100644
index 0000000..68791a6
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamListener.java
@@ -0,0 +1,46 @@
+/**
+ * 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.ibb;
+
+import org.jivesoftware.smackx.bytestreams.BytestreamListener;
+import org.jivesoftware.smackx.bytestreams.BytestreamRequest;
+
+/**
+ * InBandBytestreamListener are informed if a remote user wants to initiate an In-Band Bytestream.
+ * Implement this interface to handle incoming In-Band Bytestream requests.
+ * <p>
+ * There are two ways to add this listener. See
+ * {@link InBandBytestreamManager#addIncomingBytestreamListener(BytestreamListener)} and
+ * {@link InBandBytestreamManager#addIncomingBytestreamListener(BytestreamListener, String)} for
+ * further details.
+ *
+ * @author Henning Staib
+ */
+public abstract class InBandBytestreamListener implements BytestreamListener {
+
+
+
+ public void incomingBytestreamRequest(BytestreamRequest request) {
+ incomingBytestreamRequest((InBandBytestreamRequest) request);
+ }
+
+ /**
+ * This listener is notified if an In-Band Bytestream request from another user has been
+ * received.
+ *
+ * @param request the incoming In-Band Bytestream request
+ */
+ public abstract void incomingBytestreamRequest(InBandBytestreamRequest request);
+
+}
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamManager.java b/src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamManager.java
new file mode 100644
index 0000000..ef52531
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamManager.java
@@ -0,0 +1,546 @@
+/**
+ * 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.ibb;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.jivesoftware.smack.AbstractConnectionListener;
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.ConnectionCreationListener;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.XMPPError;
+import org.jivesoftware.smack.util.SyncPacketSend;
+import org.jivesoftware.smackx.bytestreams.BytestreamListener;
+import org.jivesoftware.smackx.bytestreams.BytestreamManager;
+import org.jivesoftware.smackx.bytestreams.ibb.packet.Open;
+import org.jivesoftware.smackx.filetransfer.FileTransferManager;
+
+/**
+ * The InBandBytestreamManager class handles establishing In-Band Bytestreams as specified in the <a
+ * href="http://xmpp.org/extensions/xep-0047.html">XEP-0047</a>.
+ * <p>
+ * The In-Band Bytestreams (IBB) enables two entities to establish a virtual bytestream over which
+ * they can exchange Base64-encoded chunks of data over XMPP itself. It is the fall-back mechanism
+ * in case the Socks5 bytestream method of transferring data is not available.
+ * <p>
+ * There are two ways to send data over an In-Band Bytestream. It could either use IQ stanzas to
+ * send data packets or message stanzas. If IQ stanzas are used every data packet is acknowledged by
+ * the receiver. This is the recommended way to avoid possible rate-limiting penalties. Message
+ * stanzas are not acknowledged because most XMPP server implementation don't support stanza
+ * flow-control method like <a href="http://xmpp.org/extensions/xep-0079.html">Advanced Message
+ * Processing</a>. To set the stanza that should be used invoke {@link #setStanza(StanzaType)}.
+ * <p>
+ * To establish an In-Band Bytestream invoke the {@link #establishSession(String)} method. This will
+ * negotiate an in-band bytestream with the given target JID and return a session.
+ * <p>
+ * If a session ID for the In-Band Bytestream was already negotiated (e.g. while negotiating a file
+ * transfer) invoke {@link #establishSession(String, String)}.
+ * <p>
+ * To handle incoming In-Band Bytestream requests add an {@link InBandBytestreamListener} to the
+ * manager. There are two ways to add this listener. If you want to be informed about incoming
+ * In-Band Bytestreams from a specific user add the listener by invoking
+ * {@link #addIncomingBytestreamListener(BytestreamListener, String)}. If the listener should
+ * respond to all In-Band Bytestream requests invoke
+ * {@link #addIncomingBytestreamListener(BytestreamListener)}.
+ * <p>
+ * Note that the registered {@link InBandBytestreamListener} will NOT be notified on incoming
+ * In-Band bytestream requests sent in the context of <a
+ * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See
+ * {@link FileTransferManager})
+ * <p>
+ * If no {@link InBandBytestreamListener}s are registered, all incoming In-Band bytestream requests
+ * will be rejected by returning a &lt;not-acceptable/&gt; error to the initiator.
+ *
+ * @author Henning Staib
+ */
+public class InBandBytestreamManager implements BytestreamManager {
+
+ /**
+ * Stanzas that can be used to encapsulate In-Band Bytestream data packets.
+ */
+ public enum StanzaType {
+
+ /**
+ * IQ stanza.
+ */
+ IQ,
+
+ /**
+ * Message stanza.
+ */
+ MESSAGE
+ }
+
+ /*
+ * create a new InBandBytestreamManager and register its shutdown listener on every established
+ * connection
+ */
+ static {
+ Connection.addConnectionCreationListener(new ConnectionCreationListener() {
+ public void connectionCreated(Connection connection) {
+ final InBandBytestreamManager manager;
+ manager = InBandBytestreamManager.getByteStreamManager(connection);
+
+ // register shutdown listener
+ connection.addConnectionListener(new AbstractConnectionListener() {
+
+ public void connectionClosed() {
+ manager.disableService();
+ }
+
+ });
+
+ }
+ });
+ }
+
+ /**
+ * The XMPP namespace of the In-Band Bytestream
+ */
+ public static final String NAMESPACE = "http://jabber.org/protocol/ibb";
+
+ /**
+ * Maximum block size that is allowed for In-Band Bytestreams
+ */
+ public static final int MAXIMUM_BLOCK_SIZE = 65535;
+
+ /* prefix used to generate session IDs */
+ private static final String SESSION_ID_PREFIX = "jibb_";
+
+ /* random generator to create session IDs */
+ private final static Random randomGenerator = new Random();
+
+ /* stores one InBandBytestreamManager for each XMPP connection */
+ private final static Map<Connection, InBandBytestreamManager> managers = new HashMap<Connection, InBandBytestreamManager>();
+
+ /* XMPP connection */
+ private final Connection connection;
+
+ /*
+ * assigns a user to a listener that is informed if an In-Band Bytestream request for this user
+ * is received
+ */
+ private final Map<String, BytestreamListener> userListeners = new ConcurrentHashMap<String, BytestreamListener>();
+
+ /*
+ * list of listeners that respond to all In-Band Bytestream requests if there are no user
+ * specific listeners for that request
+ */
+ private final List<BytestreamListener> allRequestListeners = Collections.synchronizedList(new LinkedList<BytestreamListener>());
+
+ /* listener that handles all incoming In-Band Bytestream requests */
+ private final InitiationListener initiationListener;
+
+ /* listener that handles all incoming In-Band Bytestream IQ data packets */
+ private final DataListener dataListener;
+
+ /* listener that handles all incoming In-Band Bytestream close requests */
+ private final CloseListener closeListener;
+
+ /* assigns a session ID to the In-Band Bytestream session */
+ private final Map<String, InBandBytestreamSession> sessions = new ConcurrentHashMap<String, InBandBytestreamSession>();
+
+ /* block size used for new In-Band Bytestreams */
+ private int defaultBlockSize = 4096;
+
+ /* maximum block size allowed for this connection */
+ private int maximumBlockSize = MAXIMUM_BLOCK_SIZE;
+
+ /* the stanza used to send data packets */
+ private StanzaType stanza = StanzaType.IQ;
+
+ /*
+ * list containing session IDs of In-Band Bytestream open packets that should be ignored by the
+ * InitiationListener
+ */
+ private List<String> ignoredBytestreamRequests = Collections.synchronizedList(new LinkedList<String>());
+
+ /**
+ * Returns the InBandBytestreamManager to handle In-Band Bytestreams for a given
+ * {@link Connection}.
+ *
+ * @param connection the XMPP connection
+ * @return the InBandBytestreamManager for the given XMPP connection
+ */
+ public static synchronized InBandBytestreamManager getByteStreamManager(Connection connection) {
+ if (connection == null)
+ return null;
+ InBandBytestreamManager manager = managers.get(connection);
+ if (manager == null) {
+ manager = new InBandBytestreamManager(connection);
+ managers.put(connection, manager);
+ }
+ return manager;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param connection the XMPP connection
+ */
+ private InBandBytestreamManager(Connection connection) {
+ this.connection = connection;
+
+ // register bytestream open packet listener
+ this.initiationListener = new InitiationListener(this);
+ this.connection.addPacketListener(this.initiationListener,
+ this.initiationListener.getFilter());
+
+ // register bytestream data packet listener
+ this.dataListener = new DataListener(this);
+ this.connection.addPacketListener(this.dataListener, this.dataListener.getFilter());
+
+ // register bytestream close packet listener
+ this.closeListener = new CloseListener(this);
+ this.connection.addPacketListener(this.closeListener, this.closeListener.getFilter());
+
+ }
+
+ /**
+ * Adds InBandBytestreamListener that is called for every incoming in-band bytestream request
+ * unless there is a user specific InBandBytestreamListener registered.
+ * <p>
+ * If no listeners are registered all In-Band Bytestream request are rejected with a
+ * &lt;not-acceptable/&gt; error.
+ * <p>
+ * Note that the registered {@link InBandBytestreamListener} will NOT be notified on incoming
+ * Socks5 bytestream requests sent in the context of <a
+ * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See
+ * {@link FileTransferManager})
+ *
+ * @param listener the listener to register
+ */
+ public void addIncomingBytestreamListener(BytestreamListener listener) {
+ this.allRequestListeners.add(listener);
+ }
+
+ /**
+ * Removes the given listener from the list of listeners for all incoming In-Band Bytestream
+ * requests.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeIncomingBytestreamListener(BytestreamListener listener) {
+ this.allRequestListeners.remove(listener);
+ }
+
+ /**
+ * Adds InBandBytestreamListener that is called for every incoming in-band bytestream request
+ * from the given user.
+ * <p>
+ * Use this method if you are awaiting an incoming Socks5 bytestream request from a specific
+ * user.
+ * <p>
+ * If no listeners are registered all In-Band Bytestream request are rejected with a
+ * &lt;not-acceptable/&gt; error.
+ * <p>
+ * Note that the registered {@link InBandBytestreamListener} will NOT be notified on incoming
+ * Socks5 bytestream requests sent in the context of <a
+ * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See
+ * {@link FileTransferManager})
+ *
+ * @param listener the listener to register
+ * @param initiatorJID the JID of the user that wants to establish an In-Band Bytestream
+ */
+ public void addIncomingBytestreamListener(BytestreamListener listener, String initiatorJID) {
+ this.userListeners.put(initiatorJID, listener);
+ }
+
+ /**
+ * Removes the listener for the given user.
+ *
+ * @param initiatorJID the JID of the user the listener should be removed
+ */
+ public void removeIncomingBytestreamListener(String initiatorJID) {
+ this.userListeners.remove(initiatorJID);
+ }
+
+ /**
+ * Use this method to ignore the next incoming In-Band Bytestream request containing the given
+ * session ID. No listeners will be notified for this request and and no error will be returned
+ * to the initiator.
+ * <p>
+ * This method should be used if you are awaiting an In-Band Bytestream request as a reply to
+ * another packet (e.g. file transfer).
+ *
+ * @param sessionID to be ignored
+ */
+ public void ignoreBytestreamRequestOnce(String sessionID) {
+ this.ignoredBytestreamRequests.add(sessionID);
+ }
+
+ /**
+ * Returns the default block size that is used for all outgoing in-band bytestreams for this
+ * connection.
+ * <p>
+ * The recommended default block size is 4096 bytes. See <a
+ * href="http://xmpp.org/extensions/xep-0047.html#usage">XEP-0047</a> Section 5.
+ *
+ * @return the default block size
+ */
+ public int getDefaultBlockSize() {
+ return defaultBlockSize;
+ }
+
+ /**
+ * Sets the default block size that is used for all outgoing in-band bytestreams for this
+ * connection.
+ * <p>
+ * The default block size must be between 1 and 65535 bytes. The recommended default block size
+ * is 4096 bytes. See <a href="http://xmpp.org/extensions/xep-0047.html#usage">XEP-0047</a>
+ * Section 5.
+ *
+ * @param defaultBlockSize the default block size to set
+ */
+ public void setDefaultBlockSize(int defaultBlockSize) {
+ if (defaultBlockSize <= 0 || defaultBlockSize > MAXIMUM_BLOCK_SIZE) {
+ throw new IllegalArgumentException("Default block size must be between 1 and "
+ + MAXIMUM_BLOCK_SIZE);
+ }
+ this.defaultBlockSize = defaultBlockSize;
+ }
+
+ /**
+ * Returns the maximum block size that is allowed for In-Band Bytestreams for this connection.
+ * <p>
+ * Incoming In-Band Bytestream open request will be rejected with an
+ * &lt;resource-constraint/&gt; error if the block size is greater then the maximum allowed
+ * block size.
+ * <p>
+ * The default maximum block size is 65535 bytes.
+ *
+ * @return the maximum block size
+ */
+ public int getMaximumBlockSize() {
+ return maximumBlockSize;
+ }
+
+ /**
+ * Sets the maximum block size that is allowed for In-Band Bytestreams for this connection.
+ * <p>
+ * The maximum block size must be between 1 and 65535 bytes.
+ * <p>
+ * Incoming In-Band Bytestream open request will be rejected with an
+ * &lt;resource-constraint/&gt; error if the block size is greater then the maximum allowed
+ * block size.
+ *
+ * @param maximumBlockSize the maximum block size to set
+ */
+ public void setMaximumBlockSize(int maximumBlockSize) {
+ if (maximumBlockSize <= 0 || maximumBlockSize > MAXIMUM_BLOCK_SIZE) {
+ throw new IllegalArgumentException("Maximum block size must be between 1 and "
+ + MAXIMUM_BLOCK_SIZE);
+ }
+ this.maximumBlockSize = maximumBlockSize;
+ }
+
+ /**
+ * Returns the stanza used to send data packets.
+ * <p>
+ * Default is {@link StanzaType#IQ}. See <a
+ * href="http://xmpp.org/extensions/xep-0047.html#message">XEP-0047</a> Section 4.
+ *
+ * @return the stanza used to send data packets
+ */
+ public StanzaType getStanza() {
+ return stanza;
+ }
+
+ /**
+ * Sets the stanza used to send data packets.
+ * <p>
+ * The use of {@link StanzaType#IQ} is recommended. See <a
+ * href="http://xmpp.org/extensions/xep-0047.html#message">XEP-0047</a> Section 4.
+ *
+ * @param stanza the stanza to set
+ */
+ public void setStanza(StanzaType stanza) {
+ this.stanza = stanza;
+ }
+
+ /**
+ * Establishes an In-Band Bytestream with the given user and returns the session to send/receive
+ * data to/from the user.
+ * <p>
+ * Use this method to establish In-Band Bytestreams to users accepting all incoming In-Band
+ * Bytestream requests since this method doesn't provide a way to tell the user something about
+ * the data to be sent.
+ * <p>
+ * To establish an In-Band Bytestream after negotiation the kind of data to be sent (e.g. file
+ * transfer) use {@link #establishSession(String, String)}.
+ *
+ * @param targetJID the JID of the user an In-Band Bytestream should be established
+ * @return the session to send/receive data to/from the user
+ * @throws XMPPException if the user doesn't support or accept in-band bytestreams, or if the
+ * user prefers smaller block sizes
+ */
+ public InBandBytestreamSession establishSession(String targetJID) throws XMPPException {
+ String sessionID = getNextSessionID();
+ return establishSession(targetJID, sessionID);
+ }
+
+ /**
+ * Establishes an In-Band Bytestream with the given user using the given session ID and returns
+ * the session to send/receive data to/from the user.
+ *
+ * @param targetJID the JID of the user an In-Band Bytestream should be established
+ * @param sessionID the session ID for the In-Band Bytestream request
+ * @return the session to send/receive data to/from the user
+ * @throws XMPPException if the user doesn't support or accept in-band bytestreams, or if the
+ * user prefers smaller block sizes
+ */
+ public InBandBytestreamSession establishSession(String targetJID, String sessionID)
+ throws XMPPException {
+ Open byteStreamRequest = new Open(sessionID, this.defaultBlockSize, this.stanza);
+ byteStreamRequest.setTo(targetJID);
+
+ // sending packet will throw exception on timeout or error reply
+ SyncPacketSend.getReply(this.connection, byteStreamRequest);
+
+ InBandBytestreamSession inBandBytestreamSession = new InBandBytestreamSession(
+ this.connection, byteStreamRequest, targetJID);
+ this.sessions.put(sessionID, inBandBytestreamSession);
+
+ return inBandBytestreamSession;
+ }
+
+ /**
+ * Responses to the given IQ packet's sender with an XMPP error that an In-Band Bytestream is
+ * not accepted.
+ *
+ * @param request IQ packet that should be answered with a not-acceptable error
+ */
+ protected void replyRejectPacket(IQ request) {
+ XMPPError xmppError = new XMPPError(XMPPError.Condition.no_acceptable);
+ IQ error = IQ.createErrorResponse(request, xmppError);
+ this.connection.sendPacket(error);
+ }
+
+ /**
+ * Responses to the given IQ packet's sender with an XMPP error that an In-Band Bytestream open
+ * request is rejected because its block size is greater than the maximum allowed block size.
+ *
+ * @param request IQ packet that should be answered with a resource-constraint error
+ */
+ protected void replyResourceConstraintPacket(IQ request) {
+ XMPPError xmppError = new XMPPError(XMPPError.Condition.resource_constraint);
+ IQ error = IQ.createErrorResponse(request, xmppError);
+ this.connection.sendPacket(error);
+ }
+
+ /**
+ * Responses to the given IQ packet's sender with an XMPP error that an In-Band Bytestream
+ * session could not be found.
+ *
+ * @param request IQ packet that should be answered with a item-not-found error
+ */
+ protected void replyItemNotFoundPacket(IQ request) {
+ XMPPError xmppError = new XMPPError(XMPPError.Condition.item_not_found);
+ IQ error = IQ.createErrorResponse(request, xmppError);
+ this.connection.sendPacket(error);
+ }
+
+ /**
+ * Returns a new unique session ID.
+ *
+ * @return a new unique session ID
+ */
+ private String getNextSessionID() {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append(SESSION_ID_PREFIX);
+ buffer.append(Math.abs(randomGenerator.nextLong()));
+ return buffer.toString();
+ }
+
+ /**
+ * Returns the XMPP connection.
+ *
+ * @return the XMPP connection
+ */
+ protected Connection getConnection() {
+ return this.connection;
+ }
+
+ /**
+ * Returns the {@link InBandBytestreamListener} that should be informed if a In-Band Bytestream
+ * request from the given initiator JID is received.
+ *
+ * @param initiator the initiator's JID
+ * @return the listener
+ */
+ protected BytestreamListener getUserListener(String initiator) {
+ return this.userListeners.get(initiator);
+ }
+
+ /**
+ * Returns a list of {@link InBandBytestreamListener} that are informed if there are no
+ * listeners for a specific initiator.
+ *
+ * @return list of listeners
+ */
+ protected List<BytestreamListener> getAllRequestListeners() {
+ return this.allRequestListeners;
+ }
+
+ /**
+ * Returns the sessions map.
+ *
+ * @return the sessions map
+ */
+ protected Map<String, InBandBytestreamSession> getSessions() {
+ return sessions;
+ }
+
+ /**
+ * Returns the list of session IDs that should be ignored by the InitialtionListener
+ *
+ * @return list of session IDs
+ */
+ protected List<String> getIgnoredBytestreamRequests() {
+ return ignoredBytestreamRequests;
+ }
+
+ /**
+ * Disables the InBandBytestreamManager by removing its packet listeners and resetting its
+ * internal status.
+ */
+ private void disableService() {
+
+ // remove manager from static managers map
+ managers.remove(connection);
+
+ // remove all listeners registered by this manager
+ this.connection.removePacketListener(this.initiationListener);
+ this.connection.removePacketListener(this.dataListener);
+ this.connection.removePacketListener(this.closeListener);
+
+ // shutdown threads
+ this.initiationListener.shutdown();
+
+ // reset internal status
+ this.userListeners.clear();
+ this.allRequestListeners.clear();
+ this.sessions.clear();
+ this.ignoredBytestreamRequests.clear();
+
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamRequest.java b/src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamRequest.java
new file mode 100644
index 0000000..5bc689a
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamRequest.java
@@ -0,0 +1,92 @@
+/**
+ * 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.ibb;
+
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smackx.bytestreams.BytestreamRequest;
+import org.jivesoftware.smackx.bytestreams.ibb.packet.Open;
+
+/**
+ * InBandBytestreamRequest class handles incoming In-Band Bytestream requests.
+ *
+ * @author Henning Staib
+ */
+public class InBandBytestreamRequest implements BytestreamRequest {
+
+ /* the bytestream initialization request */
+ private final Open byteStreamRequest;
+
+ /*
+ * In-Band Bytestream manager containing the XMPP connection and helper
+ * methods
+ */
+ private final InBandBytestreamManager manager;
+
+ protected InBandBytestreamRequest(InBandBytestreamManager manager,
+ Open byteStreamRequest) {
+ this.manager = manager;
+ this.byteStreamRequest = byteStreamRequest;
+ }
+
+ /**
+ * Returns the sender of the In-Band Bytestream open request.
+ *
+ * @return the sender of the In-Band Bytestream open request
+ */
+ public String getFrom() {
+ return this.byteStreamRequest.getFrom();
+ }
+
+ /**
+ * Returns the session ID of the In-Band Bytestream open request.
+ *
+ * @return the session ID of the In-Band Bytestream open request
+ */
+ public String getSessionID() {
+ return this.byteStreamRequest.getSessionID();
+ }
+
+ /**
+ * Accepts the In-Band Bytestream open request and returns the session to
+ * send/receive data.
+ *
+ * @return the session to send/receive data
+ * @throws XMPPException if stream is invalid.
+ */
+ public InBandBytestreamSession accept() throws XMPPException {
+ Connection connection = this.manager.getConnection();
+
+ // create In-Band Bytestream session and store it
+ InBandBytestreamSession ibbSession = new InBandBytestreamSession(connection,
+ this.byteStreamRequest, this.byteStreamRequest.getFrom());
+ this.manager.getSessions().put(this.byteStreamRequest.getSessionID(), ibbSession);
+
+ // acknowledge request
+ IQ resultIQ = IQ.createResultIQ(this.byteStreamRequest);
+ connection.sendPacket(resultIQ);
+
+ return ibbSession;
+ }
+
+ /**
+ * Rejects the In-Band Bytestream request by sending a reject error to the
+ * initiator.
+ */
+ public void reject() {
+ this.manager.replyRejectPacket(this.byteStreamRequest);
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamSession.java b/src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamSession.java
new file mode 100644
index 0000000..a33682c
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamSession.java
@@ -0,0 +1,795 @@
+/**
+ * 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.ibb;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketTimeoutException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.AndFilter;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.PacketTypeFilter;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.packet.XMPPError;
+import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smack.util.SyncPacketSend;
+import org.jivesoftware.smackx.bytestreams.BytestreamSession;
+import org.jivesoftware.smackx.bytestreams.ibb.packet.Close;
+import org.jivesoftware.smackx.bytestreams.ibb.packet.Data;
+import org.jivesoftware.smackx.bytestreams.ibb.packet.DataPacketExtension;
+import org.jivesoftware.smackx.bytestreams.ibb.packet.Open;
+
+/**
+ * InBandBytestreamSession class represents an In-Band Bytestream session.
+ * <p>
+ * In-band bytestreams are bidirectional and this session encapsulates the streams for both
+ * directions.
+ * <p>
+ * Note that closing the In-Band Bytestream session will close both streams. If both streams are
+ * closed individually the session will be closed automatically once the second stream is closed.
+ * Use the {@link #setCloseBothStreamsEnabled(boolean)} method if both streams should be closed
+ * automatically if one of them is closed.
+ *
+ * @author Henning Staib
+ */
+public class InBandBytestreamSession implements BytestreamSession {
+
+ /* XMPP connection */
+ private final Connection connection;
+
+ /* the In-Band Bytestream open request for this session */
+ private final Open byteStreamRequest;
+
+ /*
+ * the input stream for this session (either IQIBBInputStream or MessageIBBInputStream)
+ */
+ private IBBInputStream inputStream;
+
+ /*
+ * the output stream for this session (either IQIBBOutputStream or MessageIBBOutputStream)
+ */
+ private IBBOutputStream outputStream;
+
+ /* JID of the remote peer */
+ private String remoteJID;
+
+ /* flag to close both streams if one of them is closed */
+ private boolean closeBothStreamsEnabled = false;
+
+ /* flag to indicate if session is closed */
+ private boolean isClosed = false;
+
+ /**
+ * Constructor.
+ *
+ * @param connection the XMPP connection
+ * @param byteStreamRequest the In-Band Bytestream open request for this session
+ * @param remoteJID JID of the remote peer
+ */
+ protected InBandBytestreamSession(Connection connection, Open byteStreamRequest,
+ String remoteJID) {
+ this.connection = connection;
+ this.byteStreamRequest = byteStreamRequest;
+ this.remoteJID = remoteJID;
+
+ // initialize streams dependent to the uses stanza type
+ switch (byteStreamRequest.getStanza()) {
+ case IQ:
+ this.inputStream = new IQIBBInputStream();
+ this.outputStream = new IQIBBOutputStream();
+ break;
+ case MESSAGE:
+ this.inputStream = new MessageIBBInputStream();
+ this.outputStream = new MessageIBBOutputStream();
+ break;
+ }
+
+ }
+
+ public InputStream getInputStream() {
+ return this.inputStream;
+ }
+
+ public OutputStream getOutputStream() {
+ return this.outputStream;
+ }
+
+ public int getReadTimeout() {
+ return this.inputStream.readTimeout;
+ }
+
+ public void setReadTimeout(int timeout) {
+ if (timeout < 0) {
+ throw new IllegalArgumentException("Timeout must be >= 0");
+ }
+ this.inputStream.readTimeout = timeout;
+ }
+
+ /**
+ * Returns whether both streams should be closed automatically if one of the streams is closed.
+ * Default is <code>false</code>.
+ *
+ * @return <code>true</code> if both streams will be closed if one of the streams is closed,
+ * <code>false</code> if both streams can be closed independently.
+ */
+ public boolean isCloseBothStreamsEnabled() {
+ return closeBothStreamsEnabled;
+ }
+
+ /**
+ * Sets whether both streams should be closed automatically if one of the streams is closed.
+ * Default is <code>false</code>.
+ *
+ * @param closeBothStreamsEnabled <code>true</code> if both streams should be closed if one of
+ * the streams is closed, <code>false</code> if both streams should be closed
+ * independently
+ */
+ public void setCloseBothStreamsEnabled(boolean closeBothStreamsEnabled) {
+ this.closeBothStreamsEnabled = closeBothStreamsEnabled;
+ }
+
+ public void close() throws IOException {
+ closeByLocal(true); // close input stream
+ closeByLocal(false); // close output stream
+ }
+
+ /**
+ * This method is invoked if a request to close the In-Band Bytestream has been received.
+ *
+ * @param closeRequest the close request from the remote peer
+ */
+ protected void closeByPeer(Close closeRequest) {
+
+ /*
+ * close streams without flushing them, because stream is already considered closed on the
+ * remote peers side
+ */
+ this.inputStream.closeInternal();
+ this.inputStream.cleanup();
+ this.outputStream.closeInternal(false);
+
+ // acknowledge close request
+ IQ confirmClose = IQ.createResultIQ(closeRequest);
+ this.connection.sendPacket(confirmClose);
+
+ }
+
+ /**
+ * This method is invoked if one of the streams has been closed locally, if an error occurred
+ * locally or if the whole session should be closed.
+ *
+ * @throws IOException if an error occurs while sending the close request
+ */
+ protected synchronized void closeByLocal(boolean in) throws IOException {
+ if (this.isClosed) {
+ return;
+ }
+
+ if (this.closeBothStreamsEnabled) {
+ this.inputStream.closeInternal();
+ this.outputStream.closeInternal(true);
+ }
+ else {
+ if (in) {
+ this.inputStream.closeInternal();
+ }
+ else {
+ // close stream but try to send any data left
+ this.outputStream.closeInternal(true);
+ }
+ }
+
+ if (this.inputStream.isClosed && this.outputStream.isClosed) {
+ this.isClosed = true;
+
+ // send close request
+ Close close = new Close(this.byteStreamRequest.getSessionID());
+ close.setTo(this.remoteJID);
+ try {
+ SyncPacketSend.getReply(this.connection, close);
+ }
+ catch (XMPPException e) {
+ throw new IOException("Error while closing stream: " + e.getMessage());
+ }
+
+ this.inputStream.cleanup();
+
+ // remove session from manager
+ InBandBytestreamManager.getByteStreamManager(this.connection).getSessions().remove(this);
+ }
+
+ }
+
+ /**
+ * IBBInputStream class is the base implementation of an In-Band Bytestream input stream.
+ * Subclasses of this input stream must provide a packet listener along with a packet filter to
+ * collect the In-Band Bytestream data packets.
+ */
+ private abstract class IBBInputStream extends InputStream {
+
+ /* the data packet listener to fill the data queue */
+ private final PacketListener dataPacketListener;
+
+ /* queue containing received In-Band Bytestream data packets */
+ protected final BlockingQueue<DataPacketExtension> dataQueue = new LinkedBlockingQueue<DataPacketExtension>();
+
+ /* buffer containing the data from one data packet */
+ private byte[] buffer;
+
+ /* pointer to the next byte to read from buffer */
+ private int bufferPointer = -1;
+
+ /* data packet sequence (range from 0 to 65535) */
+ private long seq = -1;
+
+ /* flag to indicate if input stream is closed */
+ private boolean isClosed = false;
+
+ /* flag to indicate if close method was invoked */
+ private boolean closeInvoked = false;
+
+ /* timeout for read operations */
+ private int readTimeout = 0;
+
+ /**
+ * Constructor.
+ */
+ public IBBInputStream() {
+ // add data packet listener to connection
+ this.dataPacketListener = getDataPacketListener();
+ connection.addPacketListener(this.dataPacketListener, getDataPacketFilter());
+ }
+
+ /**
+ * Returns the packet listener that processes In-Band Bytestream data packets.
+ *
+ * @return the data packet listener
+ */
+ protected abstract PacketListener getDataPacketListener();
+
+ /**
+ * Returns the packet filter that accepts In-Band Bytestream data packets.
+ *
+ * @return the data packet filter
+ */
+ protected abstract PacketFilter getDataPacketFilter();
+
+ public synchronized int read() throws IOException {
+ checkClosed();
+
+ // if nothing read yet or whole buffer has been read fill buffer
+ if (bufferPointer == -1 || bufferPointer >= buffer.length) {
+ // if no data available and stream was closed return -1
+ if (!loadBuffer()) {
+ return -1;
+ }
+ }
+
+ // return byte and increment buffer pointer
+ return ((int) buffer[bufferPointer++]) & 0xff;
+ }
+
+ public synchronized int read(byte[] b, int off, int len) throws IOException {
+ if (b == null) {
+ throw new NullPointerException();
+ }
+ else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length)
+ || ((off + len) < 0)) {
+ throw new IndexOutOfBoundsException();
+ }
+ else if (len == 0) {
+ return 0;
+ }
+
+ checkClosed();
+
+ // if nothing read yet or whole buffer has been read fill buffer
+ if (bufferPointer == -1 || bufferPointer >= buffer.length) {
+ // if no data available and stream was closed return -1
+ if (!loadBuffer()) {
+ return -1;
+ }
+ }
+
+ // if more bytes wanted than available return all available
+ int bytesAvailable = buffer.length - bufferPointer;
+ if (len > bytesAvailable) {
+ len = bytesAvailable;
+ }
+
+ System.arraycopy(buffer, bufferPointer, b, off, len);
+ bufferPointer += len;
+ return len;
+ }
+
+ public synchronized int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ /**
+ * This method blocks until a data packet is received, the stream is closed or the current
+ * thread is interrupted.
+ *
+ * @return <code>true</code> if data was received, otherwise <code>false</code>
+ * @throws IOException if data packets are out of sequence
+ */
+ private synchronized boolean loadBuffer() throws IOException {
+
+ // wait until data is available or stream is closed
+ DataPacketExtension data = null;
+ try {
+ if (this.readTimeout == 0) {
+ while (data == null) {
+ if (isClosed && this.dataQueue.isEmpty()) {
+ return false;
+ }
+ data = this.dataQueue.poll(1000, TimeUnit.MILLISECONDS);
+ }
+ }
+ else {
+ data = this.dataQueue.poll(this.readTimeout, TimeUnit.MILLISECONDS);
+ if (data == null) {
+ throw new SocketTimeoutException();
+ }
+ }
+ }
+ catch (InterruptedException e) {
+ // Restore the interrupted status
+ Thread.currentThread().interrupt();
+ return false;
+ }
+
+ // handle sequence overflow
+ if (this.seq == 65535) {
+ this.seq = -1;
+ }
+
+ // check if data packets sequence is successor of last seen sequence
+ long seq = data.getSeq();
+ if (seq - 1 != this.seq) {
+ // packets out of order; close stream/session
+ InBandBytestreamSession.this.close();
+ throw new IOException("Packets out of sequence");
+ }
+ else {
+ this.seq = seq;
+ }
+
+ // set buffer to decoded data
+ buffer = data.getDecodedData();
+ bufferPointer = 0;
+ return true;
+ }
+
+ /**
+ * Checks if this stream is closed and throws an IOException if necessary
+ *
+ * @throws IOException if stream is closed and no data should be read anymore
+ */
+ private void checkClosed() throws IOException {
+ /* throw no exception if there is data available, but not if close method was invoked */
+ if ((isClosed && this.dataQueue.isEmpty()) || closeInvoked) {
+ // clear data queue in case additional data was received after stream was closed
+ this.dataQueue.clear();
+ throw new IOException("Stream is closed");
+ }
+ }
+
+ public boolean markSupported() {
+ return false;
+ }
+
+ public void close() throws IOException {
+ if (isClosed) {
+ return;
+ }
+
+ this.closeInvoked = true;
+
+ InBandBytestreamSession.this.closeByLocal(true);
+ }
+
+ /**
+ * This method sets the close flag and removes the data packet listener.
+ */
+ private void closeInternal() {
+ if (isClosed) {
+ return;
+ }
+ isClosed = true;
+ }
+
+ /**
+ * Invoked if the session is closed.
+ */
+ private void cleanup() {
+ connection.removePacketListener(this.dataPacketListener);
+ }
+
+ }
+
+ /**
+ * IQIBBInputStream class implements IBBInputStream to be used with IQ stanzas encapsulating the
+ * data packets.
+ */
+ private class IQIBBInputStream extends IBBInputStream {
+
+ protected PacketListener getDataPacketListener() {
+ return new PacketListener() {
+
+ private long lastSequence = -1;
+
+ public void processPacket(Packet packet) {
+ // get data packet extension
+ DataPacketExtension data = (DataPacketExtension) packet.getExtension(
+ DataPacketExtension.ELEMENT_NAME,
+ InBandBytestreamManager.NAMESPACE);
+
+ /*
+ * check if sequence was not used already (see XEP-0047 Section 2.2)
+ */
+ if (data.getSeq() <= this.lastSequence) {
+ IQ unexpectedRequest = IQ.createErrorResponse((IQ) packet, new XMPPError(
+ XMPPError.Condition.unexpected_request));
+ connection.sendPacket(unexpectedRequest);
+ return;
+
+ }
+
+ // check if encoded data is valid (see XEP-0047 Section 2.2)
+ if (data.getDecodedData() == null) {
+ // data is invalid; respond with bad-request error
+ IQ badRequest = IQ.createErrorResponse((IQ) packet, new XMPPError(
+ XMPPError.Condition.bad_request));
+ connection.sendPacket(badRequest);
+ return;
+ }
+
+ // data is valid; add to data queue
+ dataQueue.offer(data);
+
+ // confirm IQ
+ IQ confirmData = IQ.createResultIQ((IQ) packet);
+ connection.sendPacket(confirmData);
+
+ // set last seen sequence
+ this.lastSequence = data.getSeq();
+ if (this.lastSequence == 65535) {
+ this.lastSequence = -1;
+ }
+
+ }
+
+ };
+ }
+
+ protected PacketFilter getDataPacketFilter() {
+ /*
+ * filter all IQ stanzas having type 'SET' (represented by Data class), containing a
+ * data packet extension, matching session ID and recipient
+ */
+ return new AndFilter(new PacketTypeFilter(Data.class), new IBBDataPacketFilter());
+ }
+
+ }
+
+ /**
+ * MessageIBBInputStream class implements IBBInputStream to be used with message stanzas
+ * encapsulating the data packets.
+ */
+ private class MessageIBBInputStream extends IBBInputStream {
+
+ protected PacketListener getDataPacketListener() {
+ return new PacketListener() {
+
+ public void processPacket(Packet packet) {
+ // get data packet extension
+ DataPacketExtension data = (DataPacketExtension) packet.getExtension(
+ DataPacketExtension.ELEMENT_NAME,
+ InBandBytestreamManager.NAMESPACE);
+
+ // check if encoded data is valid
+ if (data.getDecodedData() == null) {
+ /*
+ * TODO once a majority of XMPP server implementation support XEP-0079
+ * Advanced Message Processing the invalid message could be answered with an
+ * appropriate error. For now we just ignore the packet. Subsequent packets
+ * with an increased sequence will cause the input stream to close the
+ * stream/session.
+ */
+ return;
+ }
+
+ // data is valid; add to data queue
+ dataQueue.offer(data);
+
+ // TODO confirm packet once XMPP servers support XEP-0079
+ }
+
+ };
+ }
+
+ @Override
+ protected PacketFilter getDataPacketFilter() {
+ /*
+ * filter all message stanzas containing a data packet extension, matching session ID
+ * and recipient
+ */
+ return new AndFilter(new PacketTypeFilter(Message.class), new IBBDataPacketFilter());
+ }
+
+ }
+
+ /**
+ * IBBDataPacketFilter class filters all packets from the remote peer of this session,
+ * containing an In-Band Bytestream data packet extension whose session ID matches this sessions
+ * ID.
+ */
+ private class IBBDataPacketFilter implements PacketFilter {
+
+ public boolean accept(Packet packet) {
+ // sender equals remote peer
+ if (!packet.getFrom().equalsIgnoreCase(remoteJID)) {
+ return false;
+ }
+
+ // stanza contains data packet extension
+ PacketExtension packetExtension = packet.getExtension(DataPacketExtension.ELEMENT_NAME,
+ InBandBytestreamManager.NAMESPACE);
+ if (packetExtension == null || !(packetExtension instanceof DataPacketExtension)) {
+ return false;
+ }
+
+ // session ID equals this session ID
+ DataPacketExtension data = (DataPacketExtension) packetExtension;
+ if (!data.getSessionID().equals(byteStreamRequest.getSessionID())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ }
+
+ /**
+ * IBBOutputStream class is the base implementation of an In-Band Bytestream output stream.
+ * Subclasses of this output stream must provide a method to send data over XMPP stream.
+ */
+ private abstract class IBBOutputStream extends OutputStream {
+
+ /* buffer with the size of this sessions block size */
+ protected final byte[] buffer;
+
+ /* pointer to next byte to write to buffer */
+ protected int bufferPointer = 0;
+
+ /* data packet sequence (range from 0 to 65535) */
+ protected long seq = 0;
+
+ /* flag to indicate if output stream is closed */
+ protected boolean isClosed = false;
+
+ /**
+ * Constructor.
+ */
+ public IBBOutputStream() {
+ this.buffer = new byte[(byteStreamRequest.getBlockSize()/4)*3];
+ }
+
+ /**
+ * Writes the given data packet to the XMPP stream.
+ *
+ * @param data the data packet
+ * @throws IOException if an I/O error occurred while sending or if the stream is closed
+ */
+ protected abstract void writeToXML(DataPacketExtension data) throws IOException;
+
+ public synchronized void write(int b) throws IOException {
+ if (this.isClosed) {
+ throw new IOException("Stream is closed");
+ }
+
+ // if buffer is full flush buffer
+ if (bufferPointer >= buffer.length) {
+ flushBuffer();
+ }
+
+ buffer[bufferPointer++] = (byte) b;
+ }
+
+ public synchronized void write(byte b[], int off, int len) throws IOException {
+ if (b == null) {
+ throw new NullPointerException();
+ }
+ else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length)
+ || ((off + len) < 0)) {
+ throw new IndexOutOfBoundsException();
+ }
+ else if (len == 0) {
+ return;
+ }
+
+ if (this.isClosed) {
+ throw new IOException("Stream is closed");
+ }
+
+ // is data to send greater than buffer size
+ if (len >= buffer.length) {
+
+ // "byte" off the first chunk to write out
+ writeOut(b, off, buffer.length);
+
+ // recursively call this method with the lesser amount
+ write(b, off + buffer.length, len - buffer.length);
+ }
+ else {
+ writeOut(b, off, len);
+ }
+ }
+
+ public synchronized void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ /**
+ * Fills the buffer with the given data and sends it over the XMPP stream if the buffers
+ * capacity has been reached. This method is only called from this class so it is assured
+ * that the amount of data to send is <= buffer capacity
+ *
+ * @param b the data
+ * @param off the data
+ * @param len the number of bytes to write
+ * @throws IOException if an I/O error occurred while sending or if the stream is closed
+ */
+ private synchronized void writeOut(byte b[], int off, int len) throws IOException {
+ if (this.isClosed) {
+ throw new IOException("Stream is closed");
+ }
+
+ // set to 0 in case the next 'if' block is not executed
+ int available = 0;
+
+ // is data to send greater that buffer space left
+ if (len > buffer.length - bufferPointer) {
+ // fill buffer to capacity and send it
+ available = buffer.length - bufferPointer;
+ System.arraycopy(b, off, buffer, bufferPointer, available);
+ bufferPointer += available;
+ flushBuffer();
+ }
+
+ // copy the data left to buffer
+ System.arraycopy(b, off + available, buffer, bufferPointer, len - available);
+ bufferPointer += len - available;
+ }
+
+ public synchronized void flush() throws IOException {
+ if (this.isClosed) {
+ throw new IOException("Stream is closed");
+ }
+ flushBuffer();
+ }
+
+ private synchronized void flushBuffer() throws IOException {
+
+ // do nothing if no data to send available
+ if (bufferPointer == 0) {
+ return;
+ }
+
+ // create data packet
+ String enc = StringUtils.encodeBase64(buffer, 0, bufferPointer, false);
+ DataPacketExtension data = new DataPacketExtension(byteStreamRequest.getSessionID(),
+ this.seq, enc);
+
+ // write to XMPP stream
+ writeToXML(data);
+
+ // reset buffer pointer
+ bufferPointer = 0;
+
+ // increment sequence, considering sequence overflow
+ this.seq = (this.seq + 1 == 65535 ? 0 : this.seq + 1);
+
+ }
+
+ public void close() throws IOException {
+ if (isClosed) {
+ return;
+ }
+ InBandBytestreamSession.this.closeByLocal(false);
+ }
+
+ /**
+ * Sets the close flag and optionally flushes the stream.
+ *
+ * @param flush if <code>true</code> flushes the stream
+ */
+ protected void closeInternal(boolean flush) {
+ if (this.isClosed) {
+ return;
+ }
+ this.isClosed = true;
+
+ try {
+ if (flush) {
+ flushBuffer();
+ }
+ }
+ catch (IOException e) {
+ /*
+ * ignore, because writeToXML() will not throw an exception if stream is already
+ * closed
+ */
+ }
+ }
+
+ }
+
+ /**
+ * IQIBBOutputStream class implements IBBOutputStream to be used with IQ stanzas encapsulating
+ * the data packets.
+ */
+ private class IQIBBOutputStream extends IBBOutputStream {
+
+ @Override
+ protected synchronized void writeToXML(DataPacketExtension data) throws IOException {
+ // create IQ stanza containing data packet
+ IQ iq = new Data(data);
+ iq.setTo(remoteJID);
+
+ try {
+ SyncPacketSend.getReply(connection, iq);
+ }
+ catch (XMPPException e) {
+ // close session unless it is already closed
+ if (!this.isClosed) {
+ InBandBytestreamSession.this.close();
+ throw new IOException("Error while sending Data: " + e.getMessage());
+ }
+ }
+
+ }
+
+ }
+
+ /**
+ * MessageIBBOutputStream class implements IBBOutputStream to be used with message stanzas
+ * encapsulating the data packets.
+ */
+ private class MessageIBBOutputStream extends IBBOutputStream {
+
+ @Override
+ protected synchronized void writeToXML(DataPacketExtension data) {
+ // create message stanza containing data packet
+ Message message = new Message(remoteJID);
+ message.addExtension(data);
+
+ connection.sendPacket(message);
+
+ }
+
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/InitiationListener.java b/src/org/jivesoftware/smackx/bytestreams/ibb/InitiationListener.java
new file mode 100644
index 0000000..0ecb081
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/InitiationListener.java
@@ -0,0 +1,127 @@
+/**
+ * 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.ibb;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.filter.AndFilter;
+import org.jivesoftware.smack.filter.IQTypeFilter;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.PacketTypeFilter;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smackx.bytestreams.BytestreamListener;
+import org.jivesoftware.smackx.bytestreams.ibb.packet.Open;
+
+/**
+ * InitiationListener handles all incoming In-Band Bytestream open requests. If there are no
+ * listeners for a In-Band Bytestream request InitiationListener will always refuse the request and
+ * reply with a &lt;not-acceptable/&gt; error (<a
+ * href="http://xmpp.org/extensions/xep-0047.html#example-5" >XEP-0047</a> Section 2.1).
+ * <p>
+ * All In-Band Bytestream request having a block size greater than the maximum allowed block size
+ * for this connection are rejected with an &lt;resource-constraint/&gt; error. The maximum block
+ * size can be set by invoking {@link InBandBytestreamManager#setMaximumBlockSize(int)}.
+ *
+ * @author Henning Staib
+ */
+class InitiationListener implements PacketListener {
+
+ /* manager containing the listeners and the XMPP connection */
+ private final InBandBytestreamManager manager;
+
+ /* packet filter for all In-Band Bytestream requests */
+ private final PacketFilter initFilter = new AndFilter(new PacketTypeFilter(Open.class),
+ new IQTypeFilter(IQ.Type.SET));
+
+ /* executor service to process incoming requests concurrently */
+ private final ExecutorService initiationListenerExecutor;
+
+ /**
+ * Constructor.
+ *
+ * @param manager the In-Band Bytestream manager
+ */
+ protected InitiationListener(InBandBytestreamManager manager) {
+ this.manager = manager;
+ initiationListenerExecutor = Executors.newCachedThreadPool();
+ }
+
+ public void processPacket(final Packet packet) {
+ initiationListenerExecutor.execute(new Runnable() {
+
+ public void run() {
+ processRequest(packet);
+ }
+ });
+ }
+
+ private void processRequest(Packet packet) {
+ Open ibbRequest = (Open) packet;
+
+ // validate that block size is within allowed range
+ if (ibbRequest.getBlockSize() > this.manager.getMaximumBlockSize()) {
+ this.manager.replyResourceConstraintPacket(ibbRequest);
+ return;
+ }
+
+ // ignore request if in ignore list
+ if (this.manager.getIgnoredBytestreamRequests().remove(ibbRequest.getSessionID()))
+ return;
+
+ // build bytestream request from packet
+ InBandBytestreamRequest request = new InBandBytestreamRequest(this.manager, ibbRequest);
+
+ // notify listeners for bytestream initiation from a specific user
+ BytestreamListener userListener = this.manager.getUserListener(ibbRequest.getFrom());
+ if (userListener != null) {
+ userListener.incomingBytestreamRequest(request);
+
+ }
+ else if (!this.manager.getAllRequestListeners().isEmpty()) {
+ /*
+ * if there is no user specific listener inform listeners for all initiation requests
+ */
+ for (BytestreamListener listener : this.manager.getAllRequestListeners()) {
+ listener.incomingBytestreamRequest(request);
+ }
+
+ }
+ else {
+ /*
+ * if there is no listener for this initiation request, reply with reject message
+ */
+ this.manager.replyRejectPacket(ibbRequest);
+ }
+ }
+
+ /**
+ * Returns the packet filter for In-Band Bytestream open requests.
+ *
+ * @return the packet filter for In-Band Bytestream open requests
+ */
+ protected PacketFilter getFilter() {
+ return this.initFilter;
+ }
+
+ /**
+ * Shuts down the listeners executor service.
+ */
+ protected void shutdown() {
+ this.initiationListenerExecutor.shutdownNow();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/packet/Close.java b/src/org/jivesoftware/smackx/bytestreams/ibb/packet/Close.java
new file mode 100644
index 0000000..9a78d73
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/packet/Close.java
@@ -0,0 +1,65 @@
+/**
+ * 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.ibb.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager;
+
+/**
+ * Represents a request to close an In-Band Bytestream.
+ *
+ * @author Henning Staib
+ */
+public class Close extends IQ {
+
+ /* unique session ID identifying this In-Band Bytestream */
+ private final String sessionID;
+
+ /**
+ * Creates a new In-Band Bytestream close request packet.
+ *
+ * @param sessionID unique session ID identifying this In-Band Bytestream
+ */
+ public Close(String sessionID) {
+ if (sessionID == null || "".equals(sessionID)) {
+ throw new IllegalArgumentException("Session ID must not be null or empty");
+ }
+ this.sessionID = sessionID;
+ setType(Type.SET);
+ }
+
+ /**
+ * Returns the unique session ID identifying this In-Band Bytestream.
+ *
+ * @return the unique session ID identifying this In-Band Bytestream
+ */
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ @Override
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<close ");
+ buf.append("xmlns=\"");
+ buf.append(InBandBytestreamManager.NAMESPACE);
+ buf.append("\" ");
+ buf.append("sid=\"");
+ buf.append(sessionID);
+ buf.append("\"");
+ buf.append("/>");
+ return buf.toString();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/packet/Data.java b/src/org/jivesoftware/smackx/bytestreams/ibb/packet/Data.java
new file mode 100644
index 0000000..696fa75
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/packet/Data.java
@@ -0,0 +1,64 @@
+/**
+ * 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.ibb.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+
+/**
+ * Represents a chunk of data sent over an In-Band Bytestream encapsulated in an
+ * IQ stanza.
+ *
+ * @author Henning Staib
+ */
+public class Data extends IQ {
+
+ /* the data packet extension */
+ private final DataPacketExtension dataPacketExtension;
+
+ /**
+ * Constructor.
+ *
+ * @param data data packet extension containing the encoded data
+ */
+ public Data(DataPacketExtension data) {
+ if (data == null) {
+ throw new IllegalArgumentException("Data must not be null");
+ }
+ this.dataPacketExtension = data;
+
+ /*
+ * also set as packet extension so that data packet extension can be
+ * retrieved from IQ stanza and message stanza in the same way
+ */
+ addExtension(data);
+ setType(IQ.Type.SET);
+ }
+
+ /**
+ * Returns the data packet extension.
+ * <p>
+ * Convenience method for <code>packet.getExtension("data",
+ * "http://jabber.org/protocol/ibb")</code>.
+ *
+ * @return the data packet extension
+ */
+ public DataPacketExtension getDataPacketExtension() {
+ return this.dataPacketExtension;
+ }
+
+ public String getChildElementXML() {
+ return this.dataPacketExtension.toXML();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java b/src/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java
new file mode 100644
index 0000000..80ed1e1
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java
@@ -0,0 +1,149 @@
+/**
+ * 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.ibb.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager;
+
+/**
+ * Represents a chunk of data of an In-Band Bytestream within an IQ stanza or a
+ * message stanza
+ *
+ * @author Henning Staib
+ */
+public class DataPacketExtension implements PacketExtension {
+
+ /**
+ * The element name of the data packet extension.
+ */
+ public final static String ELEMENT_NAME = "data";
+
+ /* unique session ID identifying this In-Band Bytestream */
+ private final String sessionID;
+
+ /* sequence of this packet in regard to the other data packets */
+ private final long seq;
+
+ /* the data contained in this packet */
+ private final String data;
+
+ private byte[] decodedData;
+
+ /**
+ * Creates a new In-Band Bytestream data packet.
+ *
+ * @param sessionID unique session ID identifying this In-Band Bytestream
+ * @param seq sequence of this packet in regard to the other data packets
+ * @param data the base64 encoded data contained in this packet
+ */
+ public DataPacketExtension(String sessionID, long seq, String data) {
+ if (sessionID == null || "".equals(sessionID)) {
+ throw new IllegalArgumentException("Session ID must not be null or empty");
+ }
+ if (seq < 0 || seq > 65535) {
+ throw new IllegalArgumentException("Sequence must not be between 0 and 65535");
+ }
+ if (data == null) {
+ throw new IllegalArgumentException("Data must not be null");
+ }
+ this.sessionID = sessionID;
+ this.seq = seq;
+ this.data = data;
+ }
+
+ /**
+ * Returns the unique session ID identifying this In-Band Bytestream.
+ *
+ * @return the unique session ID identifying this In-Band Bytestream
+ */
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ /**
+ * Returns the sequence of this packet in regard to the other data packets.
+ *
+ * @return the sequence of this packet in regard to the other data packets.
+ */
+ public long getSeq() {
+ return seq;
+ }
+
+ /**
+ * Returns the data contained in this packet.
+ *
+ * @return the data contained in this packet.
+ */
+ public String getData() {
+ return data;
+ }
+
+ /**
+ * Returns the decoded data or null if data could not be decoded.
+ * <p>
+ * The encoded data is invalid if it contains bad Base64 input characters or
+ * if it contains the pad ('=') character on a position other than the last
+ * character(s) of the data. See <a
+ * href="http://xmpp.org/extensions/xep-0047.html#sec">XEP-0047</a> Section
+ * 6.
+ *
+ * @return the decoded data
+ */
+ public byte[] getDecodedData() {
+ // return cached decoded data
+ if (this.decodedData != null) {
+ return this.decodedData;
+ }
+
+ // data must not contain the pad (=) other than end of data
+ if (data.matches(".*={1,2}+.+")) {
+ return null;
+ }
+
+ // decodeBase64 will return null if bad characters are included
+ this.decodedData = StringUtils.decodeBase64(data);
+ return this.decodedData;
+ }
+
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ public String getNamespace() {
+ return InBandBytestreamManager.NAMESPACE;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<");
+ buf.append(getElementName());
+ buf.append(" ");
+ buf.append("xmlns=\"");
+ buf.append(InBandBytestreamManager.NAMESPACE);
+ buf.append("\" ");
+ buf.append("seq=\"");
+ buf.append(seq);
+ buf.append("\" ");
+ buf.append("sid=\"");
+ buf.append(sessionID);
+ buf.append("\">");
+ buf.append(data);
+ buf.append("</");
+ buf.append(getElementName());
+ buf.append(">");
+ return buf.toString();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/packet/Open.java b/src/org/jivesoftware/smackx/bytestreams/ibb/packet/Open.java
new file mode 100644
index 0000000..94a7a9b
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/packet/Open.java
@@ -0,0 +1,126 @@
+/**
+ * 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.ibb.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager;
+import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager.StanzaType;
+
+/**
+ * Represents a request to open an In-Band Bytestream.
+ *
+ * @author Henning Staib
+ */
+public class Open extends IQ {
+
+ /* unique session ID identifying this In-Band Bytestream */
+ private final String sessionID;
+
+ /* block size in which the data will be fragmented */
+ private final int blockSize;
+
+ /* stanza type used to encapsulate the data */
+ private final StanzaType stanza;
+
+ /**
+ * Creates a new In-Band Bytestream open request packet.
+ * <p>
+ * The data sent over this In-Band Bytestream will be fragmented in blocks
+ * with the given block size. The block size should not be greater than
+ * 65535. A recommended default value is 4096.
+ * <p>
+ * The data can be sent using IQ stanzas or message stanzas.
+ *
+ * @param sessionID unique session ID identifying this In-Band Bytestream
+ * @param blockSize block size in which the data will be fragmented
+ * @param stanza stanza type used to encapsulate the data
+ */
+ public Open(String sessionID, int blockSize, StanzaType stanza) {
+ if (sessionID == null || "".equals(sessionID)) {
+ throw new IllegalArgumentException("Session ID must not be null or empty");
+ }
+ if (blockSize <= 0) {
+ throw new IllegalArgumentException("Block size must be greater than zero");
+ }
+
+ this.sessionID = sessionID;
+ this.blockSize = blockSize;
+ this.stanza = stanza;
+ setType(Type.SET);
+ }
+
+ /**
+ * Creates a new In-Band Bytestream open request packet.
+ * <p>
+ * The data sent over this In-Band Bytestream will be fragmented in blocks
+ * with the given block size. The block size should not be greater than
+ * 65535. A recommended default value is 4096.
+ * <p>
+ * The data will be sent using IQ stanzas.
+ *
+ * @param sessionID unique session ID identifying this In-Band Bytestream
+ * @param blockSize block size in which the data will be fragmented
+ */
+ public Open(String sessionID, int blockSize) {
+ this(sessionID, blockSize, StanzaType.IQ);
+ }
+
+ /**
+ * Returns the unique session ID identifying this In-Band Bytestream.
+ *
+ * @return the unique session ID identifying this In-Band Bytestream
+ */
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ /**
+ * Returns the block size in which the data will be fragmented.
+ *
+ * @return the block size in which the data will be fragmented
+ */
+ public int getBlockSize() {
+ return blockSize;
+ }
+
+ /**
+ * Returns the stanza type used to encapsulate the data.
+ *
+ * @return the stanza type used to encapsulate the data
+ */
+ public StanzaType getStanza() {
+ return stanza;
+ }
+
+ @Override
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<open ");
+ buf.append("xmlns=\"");
+ buf.append(InBandBytestreamManager.NAMESPACE);
+ buf.append("\" ");
+ buf.append("block-size=\"");
+ buf.append(blockSize);
+ buf.append("\" ");
+ buf.append("sid=\"");
+ buf.append(sessionID);
+ buf.append("\" ");
+ buf.append("stanza=\"");
+ buf.append(stanza.toString().toLowerCase());
+ buf.append("\"");
+ buf.append("/>");
+ return buf.toString();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/provider/CloseIQProvider.java b/src/org/jivesoftware/smackx/bytestreams/ibb/provider/CloseIQProvider.java
new file mode 100644
index 0000000..566724c
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/provider/CloseIQProvider.java
@@ -0,0 +1,33 @@
+/**
+ * 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.ibb.provider;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smackx.bytestreams.ibb.packet.Close;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Parses a close In-Band Bytestream packet.
+ *
+ * @author Henning Staib
+ */
+public class CloseIQProvider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ String sid = parser.getAttributeValue("", "sid");
+ return new Close(sid);
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/provider/DataPacketProvider.java b/src/org/jivesoftware/smackx/bytestreams/ibb/provider/DataPacketProvider.java
new file mode 100644
index 0000000..5abed08
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/provider/DataPacketProvider.java
@@ -0,0 +1,45 @@
+/**
+ * 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.ibb.provider;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smackx.bytestreams.ibb.packet.Data;
+import org.jivesoftware.smackx.bytestreams.ibb.packet.DataPacketExtension;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Parses an In-Band Bytestream data packet which can be a packet extension of
+ * either an IQ stanza or a message stanza.
+ *
+ * @author Henning Staib
+ */
+public class DataPacketProvider implements PacketExtensionProvider, IQProvider {
+
+ public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+ String sessionID = parser.getAttributeValue("", "sid");
+ long seq = Long.parseLong(parser.getAttributeValue("", "seq"));
+ String data = parser.nextText();
+ return new DataPacketExtension(sessionID, seq, data);
+ }
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ DataPacketExtension data = (DataPacketExtension) parseExtension(parser);
+ IQ iq = new Data(data);
+ return iq;
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/bytestreams/ibb/provider/OpenIQProvider.java b/src/org/jivesoftware/smackx/bytestreams/ibb/provider/OpenIQProvider.java
new file mode 100644
index 0000000..3cc725a
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/ibb/provider/OpenIQProvider.java
@@ -0,0 +1,45 @@
+/**
+ * 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.ibb.provider;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager.StanzaType;
+import org.jivesoftware.smackx.bytestreams.ibb.packet.Open;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Parses an In-Band Bytestream open packet.
+ *
+ * @author Henning Staib
+ */
+public class OpenIQProvider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ String sessionID = parser.getAttributeValue("", "sid");
+ int blockSize = Integer.parseInt(parser.getAttributeValue("", "block-size"));
+
+ String stanzaValue = parser.getAttributeValue("", "stanza");
+ StanzaType stanza = null;
+ if (stanzaValue == null) {
+ stanza = StanzaType.IQ;
+ }
+ else {
+ stanza = StanzaType.valueOf(stanzaValue.toUpperCase());
+ }
+
+ return new Open(sessionID, blockSize, stanza);
+ }
+
+}