diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileOffer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileOffer.java index a6eb71b49..9656fccdd 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileOffer.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileOffer.java @@ -16,7 +16,15 @@ */ package org.jivesoftware.smackx.jingle_filetransfer; +import java.io.File; + +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.jingle.JingleManager; import org.jivesoftware.smackx.jingle.Role; +import org.jivesoftware.smackx.jingle.element.Jingle; +import org.jivesoftware.smackx.jingle_filetransfer.callback.IncomingFileOfferCallback; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransfer; import org.jxmpp.jid.FullJid; @@ -25,7 +33,43 @@ import org.jxmpp.jid.FullJid; */ public class JingleFileOffer extends JingleFileTransferSession { - public JingleFileOffer(FullJid initiator, FullJid responder, Role role, String sid) { - super(initiator, responder, role, sid, Type.offer); + public JingleFileOffer(XMPPConnection connection, FullJid initiator, FullJid responder, Role role, String sid) { + super(connection, initiator, responder, role, sid, Type.offer); + } + + public static JingleFileOffer createOutgoingFileOffer(XMPPConnection connection, FullJid recipient) { + return new JingleFileOffer(connection, connection.getUser().asFullJidOrThrow(), recipient, + Role.initiator, JingleManager.randomSid()); + } + + public static JingleFileOffer createIncomingFileOffer(XMPPConnection connection, Jingle request) { + return new JingleFileOffer(connection, request.getInitiator(), connection.getUser().asFullJidOrThrow(), + Role.responder, request.getSid()); + } + + @Override + public IQ handleSessionInitiate(Jingle initiate) { + if (role == Role.initiator) { + + } + + if (getState() != State.fresh) { + return jutil.createErrorOutOfOrder(initiate); + } + + IncomingFileOfferCallback callback = new IncomingFileOfferCallback() { + @Override + public void accept(JingleFileTransfer file, File target) { + + } + + @Override + public void decline() { + + } + }; + + JingleFileTransferManager.getInstanceFor(connection).notifyIncomingFileOffer(initiate, callback); + return jutil.createAck(initiate); } } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileRequest.java index 5c9a433bc..5f34d2908 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileRequest.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileRequest.java @@ -16,7 +16,8 @@ */ package org.jivesoftware.smackx.jingle_filetransfer; -import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.jingle.JingleManager; import org.jivesoftware.smackx.jingle.Role; import org.jivesoftware.smackx.jingle.element.Jingle; @@ -27,12 +28,17 @@ import org.jxmpp.jid.FullJid; */ public class JingleFileRequest extends JingleFileTransferSession { - public JingleFileRequest(FullJid initiator, FullJid responder, Role role, String sid) { - super(initiator, responder, role, sid, Type.request); + public JingleFileRequest(XMPPConnection connection, FullJid initiator, FullJid responder, Role role, String sid) { + super(connection, initiator, responder, role, sid, Type.request); } - @Override - public IQ handleJingleSessionRequest(Jingle jingle) { - return null; + public static JingleFileRequest createOutgoingFileRequest(XMPPConnection connection, FullJid recipient) { + return new JingleFileRequest(connection, connection.getUser().asFullJidOrThrow(), recipient, Role.initiator, + JingleManager.randomSid()); + } + + public static JingleFileRequest createIncomingFileRequest(XMPPConnection connection, Jingle request) { + return new JingleFileRequest(connection, request.getInitiator(), connection.getUser().asFullJidOrThrow(), Role.responder, + request.getSid()); } } 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 e07bc8c14..e1fa492c7 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 @@ -24,10 +24,11 @@ import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.jingle.JingleHandler; import org.jivesoftware.smackx.jingle.JingleManager; -import org.jivesoftware.smackx.jingle.Role; +import org.jivesoftware.smackx.jingle.JingleUtil; 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_filetransfer.callback.IncomingFileOfferCallback; import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransfer; import org.jxmpp.jid.FullJid; @@ -38,12 +39,14 @@ import org.jxmpp.jid.FullJid; public final class JingleFileTransferManager extends Manager implements JingleHandler { private static final WeakHashMap INSTANCES = new WeakHashMap<>(); + private final JingleUtil jutil; private JingleFileTransferManager(XMPPConnection connection) { super(connection); ServiceDiscoveryManager.getInstanceFor(connection).addFeature(JingleFileTransfer.NAMESPACE_V5); JingleManager jingleManager = JingleManager.getInstanceFor(connection); jingleManager.registerDescriptionHandler(JingleFileTransfer.NAMESPACE_V5, this); + jutil = new JingleUtil(connection); } public static JingleFileTransferManager getInstanceFor(XMPPConnection connection) { @@ -60,22 +63,44 @@ public final class JingleFileTransferManager extends Manager implements JingleHa FullJid fullJid = jingle.getFrom().asFullJidOrThrow(); String sid = jingle.getSid(); - JingleFileTransferSession handler = createSession(jingle); + //Get handler + JingleFileTransferSession handler; + try { + handler = createSessionHandler(jingle); + } catch (IllegalArgumentException malformed) { + // If senders is neither initiator, nor responder, consider session malformed. + // See XEP-0166 §6.3 Example 16 and XEP-0234 §4.1 + return jutil.createErrorMalformedRequest(jingle); + } + JingleManager.getInstanceFor(connection()).registerJingleSessionHandler(fullJid, sid, handler); return handler.handleJingleSessionRequest(jingle); } - private JingleFileTransferSession createSession(Jingle request) { + /** + * Create a session handler (FileOffer or FileRequest) for the request. + * @param request + * @return + */ + private JingleFileTransferSession createSessionHandler(Jingle request) { if (request.getAction() != JingleAction.session_initiate) { throw new IllegalArgumentException("Requests action MUST be session-initiate."); } + JingleContent content = request.getContents().get(0); + //File Offer if (content.getSenders() == JingleContent.Senders.initiator) { - return new JingleFileOffer(request.getInitiator(), request.getResponder(), Role.responder, request.getSid()); - } else if (content.getSenders() == JingleContent.Senders.responder) { - return new JingleFileRequest(request.getInitiator(), request.getResponder(), Role.responder, request.getSid()); - } else { + return JingleFileOffer.createIncomingFileOffer(connection(), request); + } //File Request + else if (content.getSenders() == JingleContent.Senders.responder) { + return JingleFileRequest.createIncomingFileRequest(connection(), request); + } //Malformed Request + else { throw new IllegalArgumentException("Requests content.senders MUST be either responder or initiator."); } } + + public void notifyIncomingFileOffer(Jingle initiate, IncomingFileOfferCallback callback) { + + } } 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 index 7417b3c7e..c61bfafb6 100644 --- 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 @@ -16,7 +16,9 @@ */ package org.jivesoftware.smackx.jingle_filetransfer; +import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smackx.jingle.JingleSession; +import org.jivesoftware.smackx.jingle.JingleUtil; import org.jivesoftware.smackx.jingle.Role; import org.jxmpp.jid.FullJid; @@ -33,23 +35,39 @@ public abstract class JingleFileTransferSession extends JingleSession { } public enum State { + fresh, pending, active, terminated, ; } - private final Type type; + protected final XMPPConnection connection; + protected final JingleUtil jutil; - public JingleFileTransferSession(FullJid initiator, FullJid responder, Role role, String sid, Type type) { + private final Type type; + private State state; + + public JingleFileTransferSession(XMPPConnection connection, FullJid initiator, FullJid responder, Role role, String sid, Type type) { super(initiator, responder, role, sid); this.type = type; + this.state = State.fresh; + this.connection = connection; + this.jutil = new JingleUtil(connection); } public Type getType() { return type; } + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + public boolean isOffer() { return this.type == Type.offer; } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/callback/IncomingFileOfferCallback.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/callback/IncomingFileOfferCallback.java new file mode 100644 index 000000000..3cf72c096 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/callback/IncomingFileOfferCallback.java @@ -0,0 +1,31 @@ +/** + * + * 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.callback; + +import java.io.File; + +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransfer; + +/** + * Callback used to accept/decline file offers. + */ +public interface IncomingFileOfferCallback { + + void accept(JingleFileTransfer file, File target); + + void decline(); +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/callback/IncomingFileRequestCallback.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/callback/IncomingFileRequestCallback.java new file mode 100644 index 000000000..a90baae91 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/callback/IncomingFileRequestCallback.java @@ -0,0 +1,31 @@ +/** + * + * 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.callback; + +import java.io.File; + +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransfer; + +/** + * Callback used to accept/decline file requests. + */ +public interface IncomingFileRequestCallback { + + void accept(JingleFileTransfer file, File source); + + void decline(); +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/callback/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/callback/package-info.java new file mode 100644 index 000000000..ecb3e301e --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/callback/package-info.java @@ -0,0 +1,22 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XEP-0234: Jingle File Transfer. + * Callbacks. + */ +package org.jivesoftware.smackx.jingle_filetransfer.callback; 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 a14917774..347ae7ff4 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 @@ -27,6 +27,7 @@ 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.util.StringUtils; import org.jivesoftware.smackx.jingle.element.Jingle; import org.jivesoftware.smackx.jingle.element.JingleAction; import org.jivesoftware.smackx.jingle.element.JingleContent; @@ -73,7 +74,7 @@ public final class JingleManager extends Manager { JingleSessionHandler sessionHandler = jingleSessionHandlers.get(fullJidAndSessionId); if (sessionHandler != null) { //Handle existing session - return sessionHandler.handleJingleSessionRequest(jingle, jingle.getSid()); + return sessionHandler.handleJingleSessionRequest(jingle); } if (jingle.getAction() == JingleAction.session_initiate) { @@ -109,4 +110,8 @@ public final class JingleManager extends Manager { FullJidAndSessionId fullJidAndSessionId = new FullJidAndSessionId(otherJid, sessionId); return jingleSessionHandlers.remove(fullJidAndSessionId); } + + public static String randomSid() { + return StringUtils.randomString(24); + } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java index 97d4419e6..ff3026426 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java @@ -324,6 +324,10 @@ public class JingleUtil { return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); } + public IQ sendContentRejectFileNotAvailable(FullJid recipient, String sessionId, JingleContentDescription description) { + return null; //TODO Later + } + public Jingle createSessionPing(FullJid recipient, String sessionId) { Jingle.Builder jb = Jingle.getBuilder(); jb.setSessionId(sessionId) @@ -343,6 +347,14 @@ public class JingleUtil { return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); } + public IQ createAck(Jingle jingle) { + return IQ.createResultIQ(jingle); + } + + public void sendAck(Jingle jingle) throws SmackException.NotConnectedException, InterruptedException { + connection.sendStanza(createAck(jingle)); + } + public IQ createErrorUnknownSession(Jingle request) { XMPPError.Builder error = XMPPError.getBuilder(); error.setCondition(XMPPError.Condition.item_not_found) @@ -399,4 +411,13 @@ public class JingleUtil { throws SmackException.NotConnectedException, InterruptedException { connection.sendStanza(createErrorOutOfOrder(request)); } + + public IQ createErrorMalformedRequest(Jingle request) { + return IQ.createErrorResponse(request, XMPPError.Condition.bad_request); + } + + public void sendErrorMalformedRequest(Jingle request) + throws SmackException.NotConnectedException, InterruptedException { + connection.sendStanza(createErrorMalformedRequest(request)); + } }