diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferManager.java index a2c232018..9c8990520 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferManager.java @@ -17,48 +17,34 @@ package org.jivesoftware.smackx.jingle_filetransfer; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; +import java.util.Collections; import java.util.HashSet; import java.util.WeakHashMap; -import java.util.logging.Level; import java.util.logging.Logger; import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; -import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.packet.IQ; -import org.jivesoftware.smack.packet.XMPPError; -import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamListener; -import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; -import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamRequest; -import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamSession; +import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.hash.HashManager; import org.jivesoftware.smackx.hash.element.HashElement; import org.jivesoftware.smackx.jingle.JingleHandler; import org.jivesoftware.smackx.jingle.JingleManager; -import org.jivesoftware.smackx.jingle.JingleSession; -import org.jivesoftware.smackx.jingle.JingleSessionHandler; import org.jivesoftware.smackx.jingle.element.Jingle; -import org.jivesoftware.smackx.jingle.element.JingleAction; import org.jivesoftware.smackx.jingle.element.JingleContent; import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionChildElement; -import org.jivesoftware.smackx.jingle.element.JingleError; -import org.jivesoftware.smackx.jingle.element.JingleReason; import org.jivesoftware.smackx.jingle.provider.JingleContentProviderManager; import org.jivesoftware.smackx.jingle_filetransfer.callback.IncomingJingleFileTransferCallback; import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferContentDescription; import org.jivesoftware.smackx.jingle_filetransfer.listener.IncomingJingleFileTransferListener; import org.jivesoftware.smackx.jingle_filetransfer.provider.JingleFileTransferContentDescriptionProvider; -import org.jivesoftware.smackx.jingle_ibb.JingleInBandByteStreamManager; -import org.jivesoftware.smackx.jingle_ibb.element.JingleInBandByteStreamTransport; +import org.jivesoftware.smackx.jingle_ibb.JingleInBandBytestreamTransportManager; +import org.jivesoftware.smackx.jingle_ibb.element.JingleInBandBytestreamTransport; import org.jxmpp.jid.FullJid; /** @@ -66,15 +52,15 @@ import org.jxmpp.jid.FullJid; * * @author Paul Schaub */ -public final class JingleFileTransferManager extends Manager implements JingleHandler, JingleSessionHandler { +public final class JingleFileTransferManager extends Manager implements JingleHandler { private static final Logger LOGGER = Logger.getLogger(JingleFileTransferManager.class.getName()); public static final String NAMESPACE_V5 = "urn:xmpp:jingle:apps:file-transfer:5"; + private final JingleManager jingleManager; private static final WeakHashMap INSTANCES = new WeakHashMap<>(); private final HashSet incomingJingleFileTransferListeners = new HashSet<>(); - private final HashMap sessions = new HashMap<>(); /** * Private constructor. This registers a JingleContentDescriptionFileTransferProvider with the @@ -85,11 +71,12 @@ public final class JingleFileTransferManager extends Manager implements JingleHa super(connection); ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection); sdm.addFeature(NAMESPACE_V5); - JingleManager.getInstanceFor(connection).registerDescriptionHandler( + jingleManager = JingleManager.getInstanceFor(connection); + jingleManager.registerDescriptionHandler( NAMESPACE_V5, this); JingleContentProviderManager.addJingleContentDescriptionProvider( NAMESPACE_V5, new JingleFileTransferContentDescriptionProvider()); - + JingleInBandBytestreamTransportManager.getInstanceFor(connection); } /** @@ -115,170 +102,10 @@ public final class JingleFileTransferManager extends Manager implements JingleHa incomingJingleFileTransferListeners.remove(listener); } - public JingleFileTransferChildElement.Builder fileTransferPayloadBuilderFromFile(File file) { - JingleFileTransferChildElement.Builder payloadBuilder = JingleFileTransferChildElement.getBuilder(); - payloadBuilder.setDate(new Date(file.lastModified())); - payloadBuilder.setName(file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf(File.pathSeparator) + 1)); - payloadBuilder.setSize((int) file.length()); - return payloadBuilder; - } - - @Override - public IQ handleJingleRequest(final Jingle jingle) { - LOGGER.log(Level.INFO, "handleJingleRequest"); - final JingleSession session = new JingleSession(jingle.getInitiator(), jingle.getResponder(), jingle.getSid()); - sessions.put(jingle.getSid(), session); - JingleManager.getInstanceFor(connection()).registerJingleSessionHandler(jingle.getInitiator(), session.getSid(), this); - + void notifyIncomingFileTransferListeners(Jingle jingle, IncomingJingleFileTransferCallback callback) { for (IncomingJingleFileTransferListener l : incomingJingleFileTransferListeners) { - l.onIncomingJingleFileTransfer(jingle, new IncomingJingleFileTransferCallback() { - @Override - public void acceptFileTransfer(final File target) throws SmackException.NotConnectedException, InterruptedException { - - InBandBytestreamManager.getByteStreamManager(connection()).addIncomingBytestreamListener(new InBandBytestreamListener() { - @Override - public void incomingBytestreamRequest(InBandBytestreamRequest request) { - try { - if (!target.exists()) { - target.createNewFile(); - } - JingleFileTransferChildElement payload = (JingleFileTransferChildElement) jingle.getContents().get(0).getDescription().getJingleContentDescriptionChildren().get(0); - JingleInBandByteStreamTransport transport = (JingleInBandByteStreamTransport) jingle.getContents().get(0).getJingleTransports().get(0); - int s = payload.getSize(); - int bs = transport.getBlockSize(); - byte[] recv = new byte[s]; - - FileOutputStream o = new FileOutputStream(target); - InBandBytestreamSession ibs = request.accept(); - InputStream i = ibs.getInputStream(); - int read = 0; - int count = 0; - while (read > -1 && read < s) { - byte[] buf = new byte[bs]; - int r = i.read(buf); - read += r; - - LOGGER.log(Level.INFO, "Read " + r + " (" + read + ") bytes of " + s + " (" + count + " of size " + bs + ")"); - System.arraycopy(buf, 0, recv,bs * count, r); - count++; - } - i.close(); - o.write(recv); - o.close(); - } catch (IOException | SmackException.NotConnectedException | InterruptedException e) { - LOGGER.log(Level.SEVERE, e.getMessage(), e); - } - } - }); - - Jingle.Builder jb = Jingle.getBuilder(); - jb.setAction(JingleAction.session_accept) - .setSessionId(jingle.getSid()) - .setInitiator(jingle.getInitiator()) - .setResponder(jingle.getResponder()) - .addJingleContent(jingle.getContents().get(0)); - Jingle j = jb.build(); - j.setTo(jingle.getFrom()); - j.setType(IQ.Type.set); - connection().sendStanza(j); - } - - @Override - public void cancelFileTransfer() throws SmackException.NotConnectedException, InterruptedException { - Jingle.Builder jb = Jingle.getBuilder(); - jb.setInitiator(jingle.getInitiator()) - .setResponder(jingle.getResponder()) - .setSessionId(jingle.getSid()) - .setAction(JingleAction.session_terminate) - .setReason(JingleReason.Reason.decline); - connection().sendStanza(jb.build()); - } - }); + l.onIncomingJingleFileTransfer(jingle, callback); } - - return IQ.createResultIQ(jingle); - } - - @Override - public IQ handleJingleSessionRequest(Jingle jingle, String sessionId) { - LOGGER.log(Level.INFO, "handleJingleSessionRequest"); - JingleSession session = sessions.get(sessionId); - - if (session == null) { - // Handle unknown session (XEP-0166 §10) - XMPPError.Builder errorBuilder = XMPPError.getBuilder(); - errorBuilder.setCondition(XMPPError.Condition.item_not_found) - .addExtension(JingleError.UNKNOWN_SESSION); - return IQ.createErrorResponse(jingle, errorBuilder); - } - - for (int i = 0; i < jingle.getContents().size() && i < 1; i++) { //TODO: Remove && i<1 later - switch (jingle.getAction()) { - case session_initiate: - throw new AssertionError("Session is already initiated."); - case session_accept: - try { - connection().sendStanza(IQ.createResultIQ(jingle)); - } catch (SmackException.NotConnectedException | InterruptedException e) { - LOGGER.log(Level.SEVERE, e.getMessage(), e); - } - LOGGER.log(Level.INFO, "Received session-accept"); - // Remote accepts our session-initiate - InBandBytestreamManager ibm = InBandBytestreamManager.getByteStreamManager(connection()); - ibm.setMaximumBlockSize(4096); - InBandBytestreamSession ibs; - try { - ibs = ibm.establishSession(jingle.getResponder(), sessionId); - } catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { - LOGGER.log(Level.SEVERE, "Fail in handle request: " + e, e); - return null; - } - - try { - LOGGER.log(Level.INFO, "Writing bytes..."); - OutgoingJingleFileTransferSession outgoing = (OutgoingJingleFileTransferSession) session; - ibs.getOutputStream().write(outgoing.getBytes()); - ibs.close(); - LOGGER.log(Level.INFO, "Bytes written."); - } catch (IOException e) { - LOGGER.log(Level.SEVERE, "Fail while writing: " + e, e); - } - // ACK - return IQ.createResultIQ(jingle); - case session_info: - // Remote sends session-info (eg. hash) - case session_terminate: - // Remote wants to terminate our current session - sessions.remove(sessionId); - return IQ.createResultIQ(jingle); - - case content_accept: - // Remote accepts our content-add request. - case content_add: - // Remote wants to add content to the session. - case content_modify: - // Remote wants to change the directionality of the session - case content_reject: - // Remote rejects our content-add request - case content_remove: - // Remote wants to remove a content from the session/abort a single transfer - case description_info: - // Additional parameters of exchanged media - case security_info: - // Remote wants to exchange security information - case transport_accept: - // Remote accepts our transport-replace - case transport_info: - // Remote exchanges transport methods - case transport_reject: - // Remote rejects our transport-replace - case transport_replace: - // Remote wants to replace the transport - default: - return IQ.createErrorResponse(jingle, XMPPError.Condition.feature_not_implemented); - } - } - return null; } /** @@ -286,42 +113,40 @@ public final class JingleFileTransferManager extends Manager implements JingleHa * @param file */ public void sendFile(File file, final FullJid recipient) throws IOException, SmackException.NotConnectedException, InterruptedException { - final byte[] bytes = new byte[(int) file.length()]; - HashElement hashElement = FileAndHashReader.readAndCalculateHash(file, bytes, HashManager.ALGORITHM.SHA_256); - Date lastModified = new Date(file.lastModified()); - JingleFileTransferChildElement payload = new JingleFileTransferChildElement( - lastModified, "A file", hashElement, - "application/octet-stream", file.getName(), (int) file.length(), null); - ArrayList payloadTypes = new ArrayList<>(); - payloadTypes.add(payload); + JingleFileTransferSession session = new JingleFileTransferSession(connection(), recipient, connection().getUser(), recipient); + JingleFileTransferChildElement.Builder b = JingleFileTransferChildElement.getBuilder(); + b.setFile(file); + byte[] buf = new byte[(int) file.length()]; + HashElement hashElement = FileAndHashReader.readAndCalculateHash(file, buf, HashManager.ALGORITHM.SHA_256); + b.setHash(hashElement); + b.setDescription("File"); + b.setMediaType("text/plain"); - JingleFileTransferContentDescription descriptionFileTransfer = new JingleFileTransferContentDescription(payloadTypes); - final JingleInBandByteStreamTransport transport = new JingleInBandByteStreamTransport(); - JingleContent.Builder cb = JingleContent.getBuilder(); - cb.setDescription(descriptionFileTransfer) - .addTransport(transport) + session.setBytes(buf); + JingleManager.getInstanceFor(connection()).registerJingleSession(session); + + ArrayList payloads = new ArrayList<>(); + payloads.add(b.build()); + + JingleContent.Builder bb = JingleContent.getBuilder(); + bb.setDescription(new JingleFileTransferContentDescription(payloads)) .setCreator(JingleContent.Creator.initiator) - .setSenders(JingleContent.Senders.initiator) - .setName("file"); - JingleContent content = cb.build(); + .setName(StringUtils.randomString(24)) + .addTransport(new JingleInBandBytestreamTransport()); - final String sid = JingleInBandByteStreamManager.generateSessionId(); - - Jingle.Builder jb = Jingle.getBuilder(); - jb.setInitiator(connection().getUser()) - .setResponder(recipient) - .setAction(JingleAction.session_initiate) - .addJingleContent(content) - .setSessionId(sid); - Jingle jingle = jb.build(); + Jingle jingle = (Jingle) session.initiate(Collections.singletonList(bb.build())); jingle.setTo(recipient); - jingle.setType(IQ.Type.set); - - OutgoingJingleFileTransferSession session = new OutgoingJingleFileTransferSession(jingle); - session.setBytes(bytes); - sessions.put(sid, session); - JingleManager.getInstanceFor(connection()).registerJingleSessionHandler(jingle.getResponder(), session.getSid(), this); - connection().sendStanza(jingle); } + + public FullJid ourJid() { + return connection().getUser(); + } + + @Override + public IQ handleJingleRequest(Jingle jingle) { + JingleFileTransferSession session = new JingleFileTransferSession(connection(), jingle); + JingleManager.getInstanceFor(connection()).registerJingleSession(session); + return session.handleRequest(jingle); + } } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferSession.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferSession.java new file mode 100644 index 000000000..4ebeac468 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferSession.java @@ -0,0 +1,145 @@ +/** + * + * Copyright 2017 Paul Schaub + * + * 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.jingle_filetransfer; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.jingle.JingleInputStream; +import org.jivesoftware.smackx.jingle.JingleSession; +import org.jivesoftware.smackx.jingle.JingleTransportInputStreamCallback; +import org.jivesoftware.smackx.jingle.element.Jingle; +import org.jivesoftware.smackx.jingle_filetransfer.callback.IncomingJingleFileTransferCallback; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; +import org.jivesoftware.smackx.jingle_ibb.JingleInBandBytestreamTransportManager; +import org.jxmpp.jid.FullJid; + +/** + * Represent a jingle file transfer session. + */ +public class JingleFileTransferSession extends JingleSession { + + private static final Logger LOGGER = Logger.getLogger(JingleFileTransferSession.class.getName()); + + private byte[] buffer; + + public JingleFileTransferSession(XMPPConnection connection, FullJid remote, FullJid initiator, FullJid responder, String sid) { + super(connection, remote, initiator, responder, sid); + } + + public JingleFileTransferSession(XMPPConnection connection, FullJid remote, FullJid initiator, FullJid responder) { + super(connection, remote, initiator, responder); + } + + public JingleFileTransferSession(XMPPConnection connection, Jingle initiate) { + super(connection, initiate); + } + + public void setBytes(byte[] bytes) { + this.buffer = bytes; + } + + public byte[] getBytes() { + return buffer; + } + + @Override + public void onSessionInitiate(final Jingle jingle) { + JingleFileTransferManager jfm = JingleFileTransferManager.getInstanceFor(connection); + jfm.notifyIncomingFileTransferListeners(jingle, new IncomingJingleFileTransferCallback() { + @Override + public void acceptFileTransfer(final File target) throws SmackException.NotConnectedException, InterruptedException { + connection.sendStanza(accept(jingle)); + JingleInBandBytestreamTransportManager.getInstanceFor(connection).acceptInputStream(jingle, new JingleTransportInputStreamCallback() { + @Override + public void onInputStream(JingleInputStream inputStream) { + receive(inputStream, target); + } + }); + } + + @Override + public void cancelFileTransfer() throws SmackException.NotConnectedException, InterruptedException { + connection.sendStanza(terminateFormally()); + } + }); + } + + @Override + public void onAccept(Jingle jingle) { + this.contents = jingle.getContents(); + JingleInBandBytestreamTransportManager jibb = JingleInBandBytestreamTransportManager.getInstanceFor(connection); + OutputStream outputStream = jibb.createOutputStream(jingle); + + if (outputStream == null) { + LOGGER.log(Level.SEVERE, "OutputStream is null!"); + return; + } + send(outputStream); + } + + void send(OutputStream outputStream) { + try { + outputStream.write(buffer); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Caught exception while writing to output stream: " + e, e); + } finally { + try { + outputStream.close(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Could not close output stream: " + e, e); + } + } + } + + void receive(JingleInputStream in, File file) { + JingleFileTransferChildElement payload = (JingleFileTransferChildElement) contents.get(0).getDescription().getJingleContentDescriptionChildren().get(0); + InputStream inputStream = in.getInputStream(); + byte[] fileBuffer = new byte[payload.getSize()]; + byte[] packetBuffer = new byte[in.getBlockSize()]; + + try { + FileOutputStream outputStream = new FileOutputStream(file); + int read = 0, count = 0; + while (read > -1 && read < fileBuffer.length) { + int r = inputStream.read(packetBuffer); + read += r; + System.arraycopy(packetBuffer, 0, fileBuffer, packetBuffer.length * count, r); + count++; + } + + inputStream.close(); + outputStream.write(fileBuffer); + outputStream.close(); + + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Caught exception while receiving and writing file: " + e, e); + } + } + + @Override + public void onTerminate(Jingle jingle) { + + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/JingleInBandBytestreamTransportManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/JingleInBandBytestreamTransportManager.java new file mode 100644 index 000000000..e8f79364b --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/JingleInBandBytestreamTransportManager.java @@ -0,0 +1,128 @@ +/** + * + * Copyright 2017 Paul Schaub + * + * 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.jingle_ibb; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamListener; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamRequest; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamSession; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.jingle.JingleContentTransportManager; +import org.jivesoftware.smackx.jingle.JingleInputStream; +import org.jivesoftware.smackx.jingle.JingleTransportInputStreamCallback; +import org.jivesoftware.smackx.jingle.element.Jingle; +import org.jivesoftware.smackx.jingle.element.JingleContent; +import org.jivesoftware.smackx.jingle.element.JingleContentTransport; +import org.jivesoftware.smackx.jingle.provider.JingleContentProviderManager; +import org.jivesoftware.smackx.jingle_ibb.element.JingleInBandBytestreamTransport; +import org.jivesoftware.smackx.jingle_ibb.provider.JingleInBandByteStreamTransportProvider; + +/** + * Manager for Jingle In-Band-Bytestreams. + */ +public final class JingleInBandBytestreamTransportManager extends Manager implements JingleContentTransportManager { + + private static final Logger LOGGER = Logger.getLogger(JingleInBandBytestreamTransportManager.class.getName()); + public static final String NAMESPACE_V1 = "urn:xmpp:jingle:transports:ibb:1"; + + private static final WeakHashMap INSTANCES = new WeakHashMap<>(); + + private JingleInBandBytestreamTransportManager(XMPPConnection connection) { + super(connection); + JingleContentProviderManager.addJingleContentTransportProvider(NAMESPACE_V1, new JingleInBandByteStreamTransportProvider()); + ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE_V1); + } + + public static JingleInBandBytestreamTransportManager getInstanceFor(XMPPConnection connection) { + JingleInBandBytestreamTransportManager manager = INSTANCES.get(connection); + if (manager == null) { + manager = new JingleInBandBytestreamTransportManager(connection); + INSTANCES.put(connection, manager); + } + return manager; + } + + @Override + public void acceptInputStream(final Jingle jingle, final JingleTransportInputStreamCallback callback) { + final int blockSize = ((JingleInBandBytestreamTransport) + jingle.getContents().get(0).getJingleTransports().get(0)).getBlockSize(); + InBandBytestreamListener bytestreamListener = new InBandBytestreamListener() { + @Override + public void incomingBytestreamRequest(InBandBytestreamRequest request) { + if (request.getSessionID().equals(jingle.getSid())) { + try { + InBandBytestreamSession ibs = request.accept(); + InputStream inputStream = ibs.getInputStream(); + callback.onInputStream(new JingleInputStream(inputStream, blockSize)); + } catch (SmackException.NotConnectedException | InterruptedException e) { + LOGGER.log(Level.SEVERE, "Could not accept IBB session: " + e, e); + } + } + } + }; + + InBandBytestreamManager.getByteStreamManager(connection()) + .addIncomingBytestreamListener(bytestreamListener); + } + + @Override + public OutputStream createOutputStream(Jingle jingle) { + JingleInBandBytestreamTransport transport = null; + JingleContent content = jingle.getContents().get(0); + for (JingleContentTransport t : content.getJingleTransports()) { + if (t.getNamespace().equals(NAMESPACE_V1)) { + transport = (JingleInBandBytestreamTransport) t; + } + } + + if (transport == null) { + //TODO: Transport-failed + return null; + } + + InBandBytestreamManager ibm = InBandBytestreamManager.getByteStreamManager(connection()); + ibm.setMaximumBlockSize(transport.getBlockSize()); + InBandBytestreamSession ibs; + try { + ibs = ibm.establishSession(jingle.getFrom(), jingle.getSid()); + } catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { + LOGGER.log(Level.SEVERE, "Fail in handle request: " + e, e); + return null; + } + + return ibs.getOutputStream(); + } + + /** + * Generate a random session id. + * @return + */ + public static String generateSessionId() { + return StringUtils.randomString(24); + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/element/JingleInBandByteStreamTransport.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/element/JingleInBandBytestreamTransport.java similarity index 80% rename from smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/element/JingleInBandByteStreamTransport.java rename to smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/element/JingleInBandBytestreamTransport.java index 6117ca3e7..3bf6ff779 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/element/JingleInBandByteStreamTransport.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/element/JingleInBandBytestreamTransport.java @@ -18,12 +18,12 @@ package org.jivesoftware.smackx.jingle_ibb.element; import org.jivesoftware.smack.util.XmlStringBuilder; import org.jivesoftware.smackx.jingle.element.JingleContentTransport; -import org.jivesoftware.smackx.jingle_ibb.JingleInBandByteStreamManager; +import org.jivesoftware.smackx.jingle_ibb.JingleInBandBytestreamTransportManager; /** * Jingle In-Band-ByteStream transport. */ -public class JingleInBandByteStreamTransport extends JingleContentTransport { +public class JingleInBandBytestreamTransport extends JingleContentTransport { public static final String ATTR_BLOCK_SIZE = "block-size"; public static final String ATTR_SID = "sid"; @@ -32,15 +32,15 @@ public class JingleInBandByteStreamTransport extends JingleContentTransport { private final short blockSize; private final String sid; - public JingleInBandByteStreamTransport() { + public JingleInBandBytestreamTransport() { this(DEFAULT_BLOCK_SIZE); } - public JingleInBandByteStreamTransport(short blockSize) { - this(blockSize, JingleInBandByteStreamManager.generateSessionId()); + public JingleInBandBytestreamTransport(short blockSize) { + this(blockSize, JingleInBandBytestreamTransportManager.generateSessionId()); } - public JingleInBandByteStreamTransport(short blockSize, String sid) { + public JingleInBandBytestreamTransport(short blockSize, String sid) { super(null); if (blockSize > 0) { this.blockSize = blockSize; @@ -66,12 +66,12 @@ public class JingleInBandByteStreamTransport extends JingleContentTransport { @Override public String getNamespace() { - return JingleInBandByteStreamManager.NAMESPACE_V1; + return JingleInBandBytestreamTransportManager.NAMESPACE_V1; } @Override public boolean equals(Object other) { - if (other == null || !(other instanceof JingleInBandByteStreamTransport)) { + if (other == null || !(other instanceof JingleInBandBytestreamTransport)) { return false; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/element/package-info.java similarity index 100% rename from smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/element/package-info.java rename to smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/element/package-info.java diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/package-info.java similarity index 100% rename from smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/package-info.java rename to smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/package-info.java diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/provider/JingleInBandByteStreamTransportProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/provider/JingleInBandByteStreamTransportProvider.java similarity index 81% rename from smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/provider/JingleInBandByteStreamTransportProvider.java rename to smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/provider/JingleInBandByteStreamTransportProvider.java index cbfbe680e..21c4c4c72 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/provider/JingleInBandByteStreamTransportProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/provider/JingleInBandByteStreamTransportProvider.java @@ -17,23 +17,23 @@ package org.jivesoftware.smackx.jingle_ibb.provider; import org.jivesoftware.smackx.jingle.provider.JingleContentTransportProvider; -import org.jivesoftware.smackx.jingle_ibb.element.JingleInBandByteStreamTransport; +import org.jivesoftware.smackx.jingle_ibb.element.JingleInBandBytestreamTransport; import org.xmlpull.v1.XmlPullParser; /** * Parse JingleByteStreamTransport elements. */ -public class JingleInBandByteStreamTransportProvider extends JingleContentTransportProvider { +public class JingleInBandByteStreamTransportProvider extends JingleContentTransportProvider { @Override - public JingleInBandByteStreamTransport parse(XmlPullParser parser, int initialDepth) throws Exception { - String blockSizeString = parser.getAttributeValue(null, JingleInBandByteStreamTransport.ATTR_BLOCK_SIZE); - String sid = parser.getAttributeValue(null, JingleInBandByteStreamTransport.ATTR_SID); + public JingleInBandBytestreamTransport parse(XmlPullParser parser, int initialDepth) throws Exception { + String blockSizeString = parser.getAttributeValue(null, JingleInBandBytestreamTransport.ATTR_BLOCK_SIZE); + String sid = parser.getAttributeValue(null, JingleInBandBytestreamTransport.ATTR_SID); short blockSize = -1; if (blockSizeString != null) { blockSize = Short.valueOf(blockSizeString); } - return new JingleInBandByteStreamTransport(blockSize, sid); + return new JingleInBandBytestreamTransport(blockSize, sid); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/provider/package-info.java similarity index 100% rename from smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/provider/package-info.java rename to smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_ibb/provider/package-info.java diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle_ibb/JingleInBandByteStreamTransportTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_ibb/JingleInBandByteStreamTransportTest.java similarity index 76% rename from smack-extensions/src/test/java/org/jivesoftware/smackx/jingle_ibb/JingleInBandByteStreamTransportTest.java rename to smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_ibb/JingleInBandByteStreamTransportTest.java index dd470bb95..1ebbaf12b 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle_ibb/JingleInBandByteStreamTransportTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_ibb/JingleInBandByteStreamTransportTest.java @@ -16,17 +16,17 @@ */ package org.jivesoftware.smackx.jingle_ibb; -import org.jivesoftware.smack.test.util.SmackTestSuite; -import org.jivesoftware.smack.test.util.TestUtils; -import org.jivesoftware.smackx.jingle_ibb.element.JingleInBandByteStreamTransport; -import org.jivesoftware.smackx.jingle_ibb.provider.JingleInBandByteStreamTransportProvider; -import org.junit.Test; - import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertNotSame; import static junit.framework.TestCase.assertTrue; +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smack.test.util.TestUtils; +import org.jivesoftware.smackx.jingle_ibb.element.JingleInBandBytestreamTransport; +import org.jivesoftware.smackx.jingle_ibb.provider.JingleInBandByteStreamTransportProvider; +import org.junit.Test; + /** * Test JingleInBandByteStreamTransport provider and element. */ @@ -34,37 +34,37 @@ public class JingleInBandByteStreamTransportTest extends SmackTestSuite { @Test public void parserTest() throws Exception { - String sid = JingleInBandByteStreamManager.generateSessionId(); + String sid = JingleInBandBytestreamTransportManager.generateSessionId(); short size = 8192; String xml = ""; - JingleInBandByteStreamTransport transport = new JingleInBandByteStreamTransport(size, sid); + JingleInBandBytestreamTransport transport = new JingleInBandBytestreamTransport(size, sid); assertEquals(xml, transport.toXML().toString()); assertEquals(size, transport.getBlockSize()); assertEquals(sid, transport.getSessionId()); - JingleInBandByteStreamTransport parsed = new JingleInBandByteStreamTransportProvider() + JingleInBandBytestreamTransport parsed = new JingleInBandByteStreamTransportProvider() .parse(TestUtils.getParser(xml)); assertEquals(transport, parsed); assertTrue(transport.equals(parsed)); assertEquals(xml, parsed.toXML().toString()); - JingleInBandByteStreamTransport transport1 = new JingleInBandByteStreamTransport((short) 1024); + JingleInBandBytestreamTransport transport1 = new JingleInBandBytestreamTransport((short) 1024); assertEquals((short) 1024, transport1.getBlockSize()); assertNotSame(transport, transport1); assertNotSame(transport.getSessionId(), transport1.getSessionId()); assertFalse(transport.equals(null)); - JingleInBandByteStreamTransport transport2 = new JingleInBandByteStreamTransport(); - assertEquals(JingleInBandByteStreamTransport.DEFAULT_BLOCK_SIZE, transport2.getBlockSize()); + JingleInBandBytestreamTransport transport2 = new JingleInBandBytestreamTransport(); + assertEquals(JingleInBandBytestreamTransport.DEFAULT_BLOCK_SIZE, transport2.getBlockSize()); assertFalse(transport1.equals(transport2)); - JingleInBandByteStreamTransport transport3 = new JingleInBandByteStreamTransport((short) -1024); - assertEquals(JingleInBandByteStreamTransport.DEFAULT_BLOCK_SIZE, transport3.getBlockSize()); + JingleInBandBytestreamTransport transport3 = new JingleInBandBytestreamTransport((short) -1024); + assertEquals(JingleInBandBytestreamTransport.DEFAULT_BLOCK_SIZE, transport3.getBlockSize()); - assertEquals(transport3.getNamespace(), JingleInBandByteStreamManager.NAMESPACE_V1); + assertEquals(transport3.getNamespace(), JingleInBandBytestreamTransportManager.NAMESPACE_V1); assertEquals(transport3.getElementName(), "transport"); } } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/OutgoingJingleFileTransferSession.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleContentTransportManager.java similarity index 56% rename from smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/OutgoingJingleFileTransferSession.java rename to smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleContentTransportManager.java index 89de952d5..80236f63d 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/OutgoingJingleFileTransferSession.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleContentTransportManager.java @@ -14,27 +14,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jivesoftware.smackx.jingle_filetransfer; +package org.jivesoftware.smackx.jingle; + +import java.io.OutputStream; -import org.jivesoftware.smackx.jingle.JingleSession; import org.jivesoftware.smackx.jingle.element.Jingle; /** - * Created by vanitas on 04.06.17. + * Interface with methods that JingleContentTransportManagers must implement. */ -public class OutgoingJingleFileTransferSession extends JingleSession { +public interface JingleContentTransportManager { - private byte[] bytes; + void acceptInputStream(Jingle jingle, JingleTransportInputStreamCallback callback); - public OutgoingJingleFileTransferSession(Jingle jingle) { - super(jingle.getInitiator(), jingle.getResponder(), jingle.getSid()); - } - - public void setBytes(byte[] bytes) { - this.bytes = bytes; - } - - public byte[] getBytes() { - return bytes; - } + OutputStream createOutputStream(Jingle jingle); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleInputStream.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleInputStream.java new file mode 100644 index 000000000..2dd00d08b --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleInputStream.java @@ -0,0 +1,40 @@ +/** + * + * Copyright 2017 Paul Schaub + * + * 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.jingle; + +import java.io.InputStream; + +/** + * Created by vanitas on 06.06.17. + */ +public class JingleInputStream { + private final InputStream inputStream; + private final int blockSize; + + public JingleInputStream(InputStream inputStream, int blockSize) { + this.inputStream = inputStream; + this.blockSize = blockSize; + } + + public InputStream getInputStream() { + return inputStream; + } + + public int getBlockSize() { + return blockSize; + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleManager.java index 1ccc38ce7..1fccadf27 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleManager.java @@ -16,10 +16,11 @@ */ package org.jivesoftware.smackx.jingle; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; import java.util.logging.Logger; import org.jivesoftware.smack.Manager; @@ -28,11 +29,13 @@ import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler; import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ.Type; +import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smackx.jingle.element.Jingle; import org.jivesoftware.smackx.jingle.element.JingleAction; import org.jivesoftware.smackx.jingle.element.JingleContent; import org.jivesoftware.smackx.jingle.element.JingleContentDescription; -import org.jivesoftware.smackx.jingle_ibb.JingleInBandByteStreamManager; +import org.jivesoftware.smackx.jingle.element.JingleError; +import org.jivesoftware.smackx.jingle.element.JingleReason; import org.jxmpp.jid.FullJid; import org.jxmpp.jid.Jid; @@ -52,66 +55,75 @@ public final class JingleManager extends Manager { } private final Map descriptionHandlers = new ConcurrentHashMap<>(); + private final Map jingleSessions = new ConcurrentHashMap<>(); + private final Map transportManagers = new HashMap<>(); - private final Map jingleSessionHandlers = new ConcurrentHashMap<>(); - - private JingleManager(XMPPConnection connection) { + private JingleManager(final XMPPConnection connection) { super(connection); connection.registerIQRequestHandler( - new AbstractIqRequestHandler(Jingle.ELEMENT, Jingle.NAMESPACE, Type.set, Mode.async) { - @Override - public IQ handleIQRequest(IQ iqRequest) { - LOGGER.log(Level.INFO, "handleIQRequest"); - final Jingle jingle = (Jingle) iqRequest; + new AbstractIqRequestHandler(Jingle.ELEMENT, Jingle.NAMESPACE, Type.set, Mode.async) { + @Override + public IQ handleIQRequest(IQ iqRequest) { + final Jingle jingle = (Jingle) iqRequest; - if (jingle.getAction() != JingleAction.session_initiate) { - Jid from = jingle.getFrom(); - assert (from != null); - FullJid fullFrom = from.asFullJidOrThrow(); - String sid = jingle.getSid(); - FullJidAndSessionId fullJidAndSessionId = new FullJidAndSessionId(fullFrom, sid); - JingleSessionHandler jingleSessionHandler = jingleSessionHandlers.get(fullJidAndSessionId); - if (jingleSessionHandler == null) { - // TODO handle non existing jingle session handler. - return null; - } - return jingleSessionHandler.handleJingleSessionRequest(jingle, sid); - } + if (jingle.getAction() != JingleAction.session_initiate) { - if (jingle.getContents().size() > 1) { - LOGGER.severe("Jingle IQs with more then one content element are currently not supported by Smack"); - return null; - } + Jid from = jingle.getFrom(); + assert (from != null); + FullJid fullFrom = from.asFullJidOrThrow(); + FullJidAndSessionId fullJidAndSessionId = new FullJidAndSessionId(fullFrom, jingle.getSid()); + JingleSession jingleSession = jingleSessions.get(fullJidAndSessionId); - JingleContent content = jingle.getContents().get(0); - JingleContentDescription description = content.getDescription(); - JingleHandler jingleDescriptionHandler = descriptionHandlers.get( - description.getNamespace()); - if (jingleDescriptionHandler == null) { - // TODO handle non existing content description handler. - return null; - } - return jingleDescriptionHandler.handleJingleRequest(jingle); + if (jingleSession == null) { + // Handle unknown session (XEP-0166 §10) + XMPPError.Builder errorBuilder = XMPPError.getBuilder(); + errorBuilder.setCondition(XMPPError.Condition.item_not_found) + .addExtension(JingleError.UNKNOWN_SESSION); + return IQ.createErrorResponse(jingle, errorBuilder); } - }); - JingleInBandByteStreamManager.getInstanceFor(connection); + return jingleSession.handleRequest(jingle); + } + + if (jingle.getContents().size() > 1) { + LOGGER.severe("Jingle IQs with more then one content element are currently not supported by Smack"); + return null; + } + + JingleContent content = jingle.getContents().get(0); + JingleContentDescription description = content.getDescription(); + JingleHandler jingleDescriptionHandler = descriptionHandlers.get( + description.getNamespace()); + if (jingleDescriptionHandler == null) { + //Unsupported Application + Jingle.Builder builder = Jingle.getBuilder(); + builder.setAction(JingleAction.session_terminate) + .setSessionId(jingle.getSid()) + .setReason(JingleReason.Reason.unsupported_applications); + Jingle response = builder.build(); + response.setTo(jingle.getFrom()); + response.setFrom(connection.getUser()); + return response; + } + + return jingleDescriptionHandler.handleJingleRequest(jingle); + } + }); + } + + public void registerJingleSession(JingleSession session) { + FullJidAndSessionId jidAndSid = new FullJidAndSessionId(session.getRemote(), session.getSid()); + jingleSessions.put(jidAndSid, session); + } + + public void unregisterJingleSession(JingleSession session) { + jingleSessions.values().removeAll(Collections.singleton(session)); } public JingleHandler registerDescriptionHandler(String namespace, JingleHandler handler) { return descriptionHandlers.put(namespace, handler); } - public JingleSessionHandler registerJingleSessionHandler(FullJid otherJid, String sessionId, JingleSessionHandler sessionHandler) { - FullJidAndSessionId fullJidAndSessionId = new FullJidAndSessionId(otherJid, sessionId); - return jingleSessionHandlers.put(fullJidAndSessionId, sessionHandler); - } - - public JingleSessionHandler unregisterJingleSessionHandler(FullJid otherJid, String sessionId, JingleSessionHandler sessionHandler) { - FullJidAndSessionId fullJidAndSessionId = new FullJidAndSessionId(otherJid, sessionId); - return jingleSessionHandlers.remove(fullJidAndSessionId); - } - private static final class FullJidAndSessionId { final FullJid fullJid; final String sessionId; @@ -135,7 +147,7 @@ public final class JingleManager extends Manager { } FullJidAndSessionId otherFullJidAndSessionId = (FullJidAndSessionId) other; return fullJid.equals(otherFullJidAndSessionId.fullJid) - && sessionId.equals(otherFullJidAndSessionId.sessionId); + && sessionId.equals(otherFullJidAndSessionId.sessionId); } } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSession.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSession.java index 19b8641d3..f183a40e2 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSession.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSession.java @@ -1,6 +1,6 @@ /** * - * Copyright 2017 Florian Schmaus + * Copyright 2017 Paul Schaub * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,80 +16,232 @@ */ package org.jivesoftware.smackx.jingle; +import java.util.List; + +import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.jingle.element.Jingle; import org.jivesoftware.smackx.jingle.element.JingleAction; +import org.jivesoftware.smackx.jingle.element.JingleContent; +import org.jivesoftware.smackx.jingle.element.JingleError; import org.jivesoftware.smackx.jingle.element.JingleReason; -import org.jxmpp.jid.Jid; +import org.jxmpp.jid.FullJid; -// TODO: Is this class still required? If not, then remove it. -public class JingleSession { +/** + * JingleSession. + */ +public abstract class JingleSession { public enum State { + fresh, pending, - active, - ; + accepted, + terminated, } - private final Jid initiator; + protected final XMPPConnection connection; + protected final FullJid initiator; + protected final FullJid responder; + protected final FullJid ourJid; + protected final FullJid remote; + protected final String sid; + protected State sessionState; + protected List contents; - private final Jid responder; - - private final String sid; - - private State state = State.pending; - - public JingleSession(Jid initiator, Jid responder, String sid) { + public JingleSession(XMPPConnection connection, FullJid remote, FullJid initiator, FullJid responder, String sid) { + this.connection = connection; + this.ourJid = connection.getUser(); + this.remote = remote; this.initiator = initiator; this.responder = responder; this.sid = sid; + this.sessionState = State.fresh; } - @Override - public int hashCode() { - int hashCode = 31 + initiator.hashCode(); - hashCode = 31 * hashCode + responder.hashCode(); - hashCode = 31 * hashCode + sid.hashCode(); - return hashCode; + public JingleSession(XMPPConnection connection, FullJid remote, FullJid initiator, FullJid responder) { + this(connection, remote, initiator, responder, StringUtils.randomString(24)); + } + + public JingleSession(XMPPConnection connection, Jingle initiate) { + if (initiate.getAction() != JingleAction.session_initiate) { + throw new AssertionError("Session cannot be created without session-initiate"); + } + this.connection = connection; + this.ourJid = connection.getUser(); + this.remote = initiate.getFrom().asFullJidIfPossible(); + this.initiator = initiate.getInitiator(); + this.responder = initiate.getResponder(); + this.sid = initiate.getSid(); + this.sessionState = State.fresh; + } + + public FullJid getInitiator() { + return initiator; + } + + public FullJid getResponder() { + return responder; + } + + public FullJid getRemote() { + return remote; + } + + public FullJid getOurJid() { + return ourJid; } public String getSid() { return sid; } - public Jid getInitiator() { - return initiator; + public State getSessionState() { + return sessionState; } - public Jid getResponder() { - return responder; + public IQ handleRequest(Jingle jingle) { + switch (jingle.getAction()) { + case session_initiate: + // Did we already get another session-initiate? + if (sessionState != State.fresh) { + return outOfOrder(jingle); + } + + //Keep local copy of contents + contents = jingle.getContents(); + + onSessionInitiate(jingle); + + return IQ.createResultIQ(jingle); + + case session_accept: + if (sessionState != State.pending) { + return outOfOrder(jingle); + } + + onAccept(jingle); + + return IQ.createResultIQ(jingle); + + case session_terminate: + + onTerminate(jingle); + + return IQ.createResultIQ(jingle); + + case content_add: + //TODO: Inform listeners + return IQ.createResultIQ(jingle); + + default: return IQ.createResultIQ(jingle); + } } - public void setState(State state) { - this.state = state; - } + public abstract void onSessionInitiate(Jingle jingle); - public State getState() { - return state; - } + public abstract void onAccept(Jingle jingle); - @Override - public boolean equals(Object other) { - if (!(other instanceof JingleSession)) { - return false; + public abstract void onTerminate(Jingle jingle); + + public IQ initiate(List contents) { + Jingle.Builder b = Jingle.getBuilder(); + b.setInitiator(initiator) + .setAction(JingleAction.session_initiate) + .setSessionId(sid); + for (JingleContent c : contents) { + b.addJingleContent(c); } - JingleSession otherJingleSession = (JingleSession) other; - return initiator.equals(otherJingleSession.initiator) && responder.equals(otherJingleSession.responder) - && sid.equals(otherJingleSession.sid); + Jingle j = b.build(); + j.setTo(remote); + j.setFrom(ourJid); + this.sessionState = State.pending; + return j; } - IQ terminateSuccessfully() { - Jingle.Builder builder = Jingle.getBuilder(); - builder.setAction(JingleAction.session_terminate); - builder.setSessionId(getSid()); - builder.setReason(JingleReason.Reason.success); + public IQ accept(Jingle jingle) { + Jingle.Builder b = Jingle.getBuilder(); + b.setResponder(ourJid) + .setAction(JingleAction.session_accept) + .setSessionId(sid); + for (JingleContent c : jingle.getContents()) { + b.addJingleContent(c); + } - return builder.build(); + Jingle j = b.build(); + j.setTo(remote); + j.setFrom(ourJid); + this.sessionState = State.accepted; + return j; + } + + public IQ terminate(JingleReason.Reason reason) { + Jingle.Builder b = Jingle.getBuilder(); + b.setAction(JingleAction.session_terminate) + .setSessionId(sid) + .setReason(reason); + Jingle j = b.build(); + j.setTo(remote); + j.setFrom(ourJid); + this.sessionState = State.terminated; + return b.build(); + } + + public IQ terminateFormally() { + return terminate(JingleReason.Reason.decline); + } + + //TODO Fix + public IQ terminateAlternativeSession(String alternative) { + Jingle.Builder b = Jingle.getBuilder(); + b.setAction(JingleAction.session_terminate) + .setSessionId(sid) + .setReason(JingleReason.Reason.alternative_session); //Set alt. sessionId + Jingle j = b.build(); + j.setTo(remote); + j.setFrom(ourJid); + this.sessionState = State.terminated; + return j; + } + + public IQ terminateSuccessfully() { + return terminate(JingleReason.Reason.success); + } + + public IQ terminateBusy() { + return terminate(JingleReason.Reason.busy); + } + + public IQ terminateUnsupportedTransports() { + return terminate(JingleReason.Reason.unsupported_transports); + } + + public IQ terminateFailedTransport() { + return terminate(JingleReason.Reason.failed_transport); + } + + public IQ terminateUnsupportedApplications() { + return terminate(JingleReason.Reason.unsupported_applications); + } + + public IQ terminateFailedApplication() { + return terminate(JingleReason.Reason.failed_application); + } + + public IQ terminateIncompatibleParameters() { + return terminate(JingleReason.Reason.incompatible_parameters); + } + + public IQ unknownInitiator(Jingle jingle) { + return IQ.createErrorResponse(jingle, XMPPError.Condition.service_unavailable); + } + + public IQ outOfOrder(Jingle jingle) { + XMPPError.Builder b = XMPPError.getBuilder(); + b.setCondition(XMPPError.Condition.unexpected_request); + b.addExtension(JingleError.OUT_OF_ORDER); + return IQ.createErrorResponse(jingle, b); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleTransportInputStreamCallback.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleTransportInputStreamCallback.java new file mode 100644 index 000000000..5c5210bd4 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleTransportInputStreamCallback.java @@ -0,0 +1,25 @@ +/** + * + * Copyright 2017 Paul Schaub + * + * 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.jingle; + +/** + * Created by vanitas on 06.06.17. + */ +public interface JingleTransportInputStreamCallback { + + void onInputStream(JingleInputStream inputStream); +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContentTransport.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContentTransport.java index fd6663d0e..c33d1461d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContentTransport.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContentTransport.java @@ -16,12 +16,12 @@ */ package org.jivesoftware.smackx.jingle.element; -import org.jivesoftware.smack.packet.ExtensionElement; -import org.jivesoftware.smack.util.XmlStringBuilder; - import java.util.Collections; import java.util.List; +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.util.XmlStringBuilder; + /** * A jingle transport extension. * @@ -71,5 +71,4 @@ public abstract class JingleContentTransport implements ExtensionElement { return xml; } - } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/JingleInBandByteStreamManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/JingleInBandByteStreamManager.java deleted file mode 100644 index 1d48c5124..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle_ibb/JingleInBandByteStreamManager.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * - * Copyright 2017 Paul Schaub - * - * 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.jingle_ibb; - -import java.util.WeakHashMap; - -import org.jivesoftware.smack.Manager; -import org.jivesoftware.smack.XMPPConnection; -import org.jivesoftware.smack.util.StringUtils; -import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; -import org.jivesoftware.smackx.jingle.provider.JingleContentProviderManager; -import org.jivesoftware.smackx.jingle_ibb.provider.JingleInBandByteStreamTransportProvider; - -/** - * Manager for Jingle In-Band-ByteStreams. - */ -public final class JingleInBandByteStreamManager extends Manager { - - public static final String NAMESPACE_V1 = "urn:xmpp:jingle:transports:ibb:1"; - - private static final WeakHashMap INSTANCES = new WeakHashMap<>(); - - private JingleInBandByteStreamManager(XMPPConnection connection) { - super(connection); - JingleContentProviderManager.addJingleContentTransportProvider(NAMESPACE_V1, new JingleInBandByteStreamTransportProvider()); - ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE_V1); - } - - public static JingleInBandByteStreamManager getInstanceFor(XMPPConnection connection) { - JingleInBandByteStreamManager manager = INSTANCES.get(connection); - if (manager == null) { - manager = new JingleInBandByteStreamManager(connection); - INSTANCES.put(connection, manager); - } - return manager; - } - - /** - * Generate a random session id. - * @return - */ - public static String generateSessionId() { - return StringUtils.randomString(24); - } -} diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleSessionTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleSessionTest.java deleted file mode 100644 index cd12c08ac..000000000 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleSessionTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * - * Copyright 2017 Paul Schaub - * - * 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.jingle; - -import org.jivesoftware.smack.test.util.SmackTestSuite; -import org.jivesoftware.smack.util.StringUtils; -import org.junit.Test; -import org.jxmpp.jid.Jid; -import org.jxmpp.jid.impl.JidCreate; -import org.jxmpp.stringprep.XmppStringprepException; - -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertNotSame; - -/** - * Test JingleSession class. - */ -public class JingleSessionTest extends SmackTestSuite { - - @Test - public void sessionTest() throws XmppStringprepException { - Jid romeo = JidCreate.from("romeo@montague.lit"); - Jid juliet = JidCreate.from("juliet@capulet.lit"); - String sid = StringUtils.randomString(24); - - JingleSession s1 = new JingleSession(romeo, juliet, sid); - JingleSession s2 = new JingleSession(juliet, romeo, sid); - JingleSession s3 = new JingleSession(romeo, juliet, StringUtils.randomString(23)); - JingleSession s4 = new JingleSession(juliet, romeo, sid); - - assertNotSame(s1, s2); - assertNotSame(s1, s3); - assertNotSame(s2, s3); - assertEquals(s2, s4); - assertEquals(s2.hashCode(), s4.hashCode()); - } -} diff --git a/smack-jingle-old/src/demo/java/org/jivesoftware/smackx/jingle/mediaimpl/demo/Demo.java b/smack-jingle-old/src/demo/java/org/jivesoftware/smackx/jingle/mediaimpl/demo/Demo.java index f6e72c26c..bb417e253 100644 --- a/smack-jingle-old/src/demo/java/org/jivesoftware/smackx/jingle/mediaimpl/demo/Demo.java +++ b/smack-jingle-old/src/demo/java/org/jivesoftware/smackx/jingle/mediaimpl/demo/Demo.java @@ -20,7 +20,6 @@ import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.TCPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smackx.jingle.JingleManager; -import org.jivesoftware.smackx.jingle.JingleSession; import org.jivesoftware.smackx.jingle.JingleSessionRequest; import org.jivesoftware.smackx.jingle.listeners.JingleSessionRequestListener; import org.jivesoftware.smackx.jingle.media.JingleMediaManager; diff --git a/smack-jingle-old/src/integration-test/java/org/jivesoftware/smackx/jingle/nat/STUNResolverTest.java b/smack-jingle-old/src/integration-test/java/org/jivesoftware/smackx/jingle/nat/STUNResolverTest.java index 732cb7122..9c0004274 100644 --- a/smack-jingle-old/src/integration-test/java/org/jivesoftware/smackx/jingle/nat/STUNResolverTest.java +++ b/smack-jingle-old/src/integration-test/java/org/jivesoftware/smackx/jingle/nat/STUNResolverTest.java @@ -21,7 +21,6 @@ import de.javawi.jstun.util.UtilityException; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.test.SmackTestCase; import org.jivesoftware.smackx.jingle.JingleManager; -import org.jivesoftware.smackx.jingle.JingleSession; import org.jivesoftware.smackx.jingle.JingleSessionRequest; import org.jivesoftware.smackx.jingle.listeners.JingleSessionListener; import org.jivesoftware.smackx.jingle.listeners.JingleSessionRequestListener;