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 new file mode 100644 index 000000000..63267c204 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferManager.java @@ -0,0 +1,215 @@ +/** + * + * 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.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +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.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.jingle.JingleDescriptionManager; +import org.jivesoftware.smackx.jingle.JingleManager; +import org.jivesoftware.smackx.jingle.JingleTransportManager; +import org.jivesoftware.smackx.jingle.component.JingleContent; +import org.jivesoftware.smackx.jingle.component.JingleSession; +import org.jivesoftware.smackx.jingle.component.JingleTransport; +import org.jivesoftware.smackx.jingle.element.JingleContentElement; +import org.jivesoftware.smackx.jingle.util.Role; +import org.jivesoftware.smackx.jingle_filetransfer.adapter.JingleFileTransferAdapter; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFile; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransfer; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleIncomingFileOffer; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleIncomingFileRequest; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleOutgoingFileOffer; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleOutgoingFileRequest; +import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileRequestController; +import org.jivesoftware.smackx.jingle_filetransfer.listener.IncomingFileOfferListener; +import org.jivesoftware.smackx.jingle_filetransfer.listener.IncomingFileRequestListener; +import org.jivesoftware.smackx.jingle_filetransfer.provider.JingleFileTransferProvider; + +import org.jxmpp.jid.FullJid; + +/** + * Created by vanitas on 22.07.17. + */ +public final class JingleFileTransferManager extends Manager implements JingleDescriptionManager { + + private static final Logger LOGGER = Logger.getLogger(JingleFileTransferManager.class.getName()); + + private static final WeakHashMap INSTANCES = new WeakHashMap<>(); + private final JingleManager jingleManager; + + private final List offerListeners = + Collections.synchronizedList(new ArrayList()); + + private final List requestListeners = + Collections.synchronizedList(new ArrayList()); + + static { + JingleManager.addJingleDescriptionAdapter(new JingleFileTransferAdapter()); + JingleManager.addJingleDescriptionProvider(new JingleFileTransferProvider()); + } + + private JingleFileTransferManager(XMPPConnection connection) { + super(connection); + ServiceDiscoveryManager.getInstanceFor(connection).addFeature(getNamespace()); + jingleManager = JingleManager.getInstanceFor(connection); + jingleManager.addJingleDescriptionManager(this); + } + + public static JingleFileTransferManager getInstanceFor(XMPPConnection connection) { + JingleFileTransferManager manager = INSTANCES.get(connection); + + if (manager == null) { + manager = new JingleFileTransferManager(connection); + INSTANCES.put(connection, manager); + } + + return manager; + } + + public OutgoingFileOfferController sendFile(File file, FullJid to) + throws SmackException.NotConnectedException, InterruptedException, XMPPException.XMPPErrorException, + SmackException.NoResponseException, SmackException.FeatureNotSupportedException, IOException, NoSuchAlgorithmException { + return sendFile(file, JingleFile.fromFile(file, null, null, null), to); + } + + public OutgoingFileOfferController sendFile(File file, JingleFile metadata, FullJid to) throws SmackException.FeatureNotSupportedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, FileNotFoundException { + if (file == null || !file.exists()) { + throw new IllegalArgumentException("File MUST NOT be null and MUST exist."); + } + + if (!ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(to, getNamespace())) { + throw new SmackException.FeatureNotSupportedException(getNamespace(), to); + } + + JingleSession session = jingleManager.createSession(Role.initiator, to); + + JingleContent content = new JingleContent(JingleContentElement.Creator.initiator, JingleContentElement.Senders.initiator); + session.addContent(content); + + JingleOutgoingFileOffer offer = new JingleOutgoingFileOffer(file, metadata); + content.setDescription(offer); + + JingleTransportManager transportManager = jingleManager.getBestAvailableTransportManager(to); + JingleTransport transport = transportManager.createTransportForInitiator(content); + content.setTransport(transport); + + session.sendInitiate(connection()); + + return offer; + } + + public OutgoingFileOfferController sendStream(final InputStream stream, JingleFile metadata, FullJid recipient) throws SmackException.FeatureNotSupportedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { + if (!ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(recipient, getNamespace())) { + throw new SmackException.FeatureNotSupportedException(getNamespace(), recipient); + } + + JingleSession session = jingleManager.createSession(Role.initiator, recipient); + + JingleContent content = new JingleContent(JingleContentElement.Creator.initiator, JingleContentElement.Senders.initiator); + session.addContent(content); + + JingleOutgoingFileOffer outgoingFileOffer = new JingleOutgoingFileOffer(stream, metadata); + + content.setDescription(outgoingFileOffer); + + JingleTransportManager transportManager = jingleManager.getBestAvailableTransportManager(recipient); + JingleTransport transport = transportManager.createTransportForInitiator(content); + content.setTransport(transport); + + session.sendInitiate(connection()); + + return outgoingFileOffer; + } + + public OutgoingFileRequestController requestFile(JingleFile metadata, FullJid from) { + JingleOutgoingFileRequest request = new JingleOutgoingFileRequest(metadata); + + //TODO at some point. + + return request; + } + + public void addIncomingFileOfferListener(IncomingFileOfferListener listener) { + offerListeners.add(listener); + } + + public void removeIncomingFileOfferListener(IncomingFileOfferListener listener) { + offerListeners.remove(listener); + } + + public void notifyIncomingFileOfferListeners(JingleIncomingFileOffer offer) { + LOGGER.log(Level.INFO, "Incoming File transfer: [" + offer.getNamespace() + ", " + + offer.getParent().getTransport().getNamespace() + ", " + + (offer.getParent().getSecurity() != null ? offer.getParent().getSecurity().getNamespace() : "") + "]"); + for (IncomingFileOfferListener l : offerListeners) { + l.onIncomingFileOffer(offer); + } + } + + public void addIncomingFileRequestListener(IncomingFileRequestListener listener) { + requestListeners.add(listener); + } + + public void removeIncomingFileRequestListener(IncomingFileRequestListener listener) { + requestListeners.remove(listener); + } + + public void notifyIncomingFileRequestListeners(JingleIncomingFileRequest request) { + for (IncomingFileRequestListener l : requestListeners) { + l.onIncomingFileRequest(request); + } + } + + @Override + public String getNamespace() { + return JingleFileTransfer.NAMESPACE; + } + + private void notifyTransfer(JingleFileTransfer transfer) { + if (transfer.isOffer()) { + notifyIncomingFileOfferListeners((JingleIncomingFileOffer) transfer); + } else { + notifyIncomingFileRequestListeners((JingleIncomingFileRequest) transfer); + } + } + + @Override + public void notifySessionInitiate(JingleSession session) { + JingleContent content = session.getSoleContentOrThrow(); + notifyTransfer((JingleFileTransfer) content.getDescription()); + } + + @Override + public void notifyContentAdd(JingleSession session, JingleContent content) { + notifyTransfer((JingleFileTransfer) content.getDescription()); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/adapter/JingleFileTransferAdapter.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/adapter/JingleFileTransferAdapter.java new file mode 100644 index 000000000..76f016cb2 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/adapter/JingleFileTransferAdapter.java @@ -0,0 +1,64 @@ +/** + * + * 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.adapter; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smackx.jingle.adapter.JingleDescriptionAdapter; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionElement; +import org.jivesoftware.smackx.jingle.element.JingleContentElement; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransfer; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleIncomingFileOffer; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleIncomingFileRequest; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferElement; + +/** + * Created by vanitas on 28.07.17. + */ +public class JingleFileTransferAdapter implements JingleDescriptionAdapter { + private static final Logger LOGGER = Logger.getLogger(JingleFileTransferAdapter.class.getName()); + + @Override + public JingleFileTransfer descriptionFromElement(JingleContentElement.Creator creator, JingleContentElement.Senders senders, + String contentName, String contentDisposition, JingleContentDescriptionElement element) { + JingleFileTransferElement description = (JingleFileTransferElement) element; + List children = description.getJingleContentDescriptionChildren(); + assert children.size() == 1; + JingleFileTransferChildElement file = (JingleFileTransferChildElement) children.get(0); + + if (senders == JingleContentElement.Senders.initiator) { + return new JingleIncomingFileOffer(file); + } else if (senders == JingleContentElement.Senders.responder) { + return new JingleIncomingFileRequest(file); + } else { + if (senders == null) { + LOGGER.log(Level.INFO, "Senders is null. Gajim workaround: assume 'initiator'."); + return new JingleIncomingFileOffer(file); + } + throw new AssertionError("Senders attribute MUST be either initiator or responder. Is: " + senders); + } + } + + @Override + public String getNamespace() { + return JingleFileTransfer.NAMESPACE; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/adapter/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/adapter/package-info.java new file mode 100644 index 000000000..3cf5e866f --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/adapter/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. + * Adapters. + */ +package org.jivesoftware.smackx.jingle_filetransfer.adapter; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/AbstractJingleFileOffer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/AbstractJingleFileOffer.java new file mode 100644 index 000000000..e53fd55d9 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/AbstractJingleFileOffer.java @@ -0,0 +1,27 @@ +/** + * + * 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.component; + +/** + * Created by vanitas on 22.07.17. + */ +public abstract class AbstractJingleFileOffer extends JingleFileTransfer { + + AbstractJingleFileOffer(JingleFile fileTransferFile) { + super(fileTransferFile); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/AbstractJingleFileRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/AbstractJingleFileRequest.java new file mode 100644 index 000000000..94f3b50c2 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/AbstractJingleFileRequest.java @@ -0,0 +1,27 @@ +/** + * + * 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.component; + +/** + * Created by vanitas on 22.07.17. + */ +public abstract class AbstractJingleFileRequest extends JingleFileTransfer { + + AbstractJingleFileRequest(JingleFile fileTransferFile) { + super(fileTransferFile); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleFile.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleFile.java new file mode 100644 index 000000000..f809f848c --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleFile.java @@ -0,0 +1,151 @@ +/** + * + * 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.component; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Date; + +import org.jivesoftware.smackx.hashes.HashManager; +import org.jivesoftware.smackx.hashes.element.HashElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; + +/** + * Represent a file sent in a file transfer. + * This can be both LocalFile (available to the client), or RemoteFile (file not yet available). + */ +public class JingleFile { + + private String name, description, mediaType; + private long size; + private Date date; + private HashElement hashElement; + + public static JingleFile fromFile(File file, String description, String mediaType, HashManager.ALGORITHM hashAlgorithm) throws NoSuchAlgorithmException, IOException { + + HashElement hashElement = null; + if (hashAlgorithm != null) { + hashElement = calculateHash(file, hashAlgorithm); + } + + return new JingleFile(file.getName(), description, file.length(), mediaType, new Date(file.lastModified()), hashElement); + } + + public JingleFile(String name, String description, long size, String mediaType, Date date, HashElement hashElement) { + this.name = name; + this.description = description; + this.size = size; + this.mediaType = mediaType; + this.date = date; + this.hashElement = hashElement; + } + + public JingleFile(JingleFileTransferChildElement element) { + this.name = element.getName(); + this.description = element.getDescription(); + this.size = element.getSize(); + this.mediaType = element.getMediaType(); + this.date = element.getDate(); + this.hashElement = element.getHash(); + } + + public static HashElement calculateHash(File file, HashManager.ALGORITHM algorithm) throws NoSuchAlgorithmException, IOException { + if (file == null || !file.exists()) { + throw new IllegalArgumentException("File MUST NOT be null and MUST exist."); + } + + MessageDigest digest = HashManager.getMessageDigest(algorithm); + if (digest == null) { + throw new NoSuchAlgorithmException("No algorithm for " + algorithm + " found."); + } + + FileInputStream fi = new FileInputStream(file); + DigestInputStream di = new DigestInputStream(fi, digest); + + while (di.available() > 0) { + di.read(); + } + + byte[] d = di.getMessageDigest().digest(); + + return new HashElement(algorithm, d); + } + + public JingleFileTransferChildElement getElement() { + JingleFileTransferChildElement.Builder builder = JingleFileTransferChildElement.getBuilder(); + builder.setDate(getDate()); + builder.setSize(getSize()); + builder.setName(getName()); + builder.setDescription(getDescription()); + builder.setMediaType(getMediaType()); + builder.setHash(getHashElement()); + + return builder.build(); + } + + public Date getDate() { + return date; + } + + public long getSize() { + return size; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getMediaType() { + return mediaType; + } + + public HashElement getHashElement() { + return hashElement; + } + + public void setName(String name) { + this.name = name; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setMediaType(String mediaType) { + this.mediaType = mediaType; + } + + public void setSize(long size) { + this.size = size; + } + + public void setDate(Date date) { + this.date = date; + } + + public void setHashElement(HashElement hashElement) { + this.hashElement = hashElement; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleFileTransfer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleFileTransfer.java new file mode 100644 index 000000000..358ae8b23 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleFileTransfer.java @@ -0,0 +1,123 @@ +/** + * + * 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.component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.jingle.component.JingleDescription; +import org.jivesoftware.smackx.jingle.component.JingleSession; +import org.jivesoftware.smackx.jingle.element.JingleElement; +import org.jivesoftware.smackx.jingle.element.JingleReasonElement; +import org.jivesoftware.smackx.jingle_filetransfer.controller.JingleFileTransferController; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferElement; +import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener; + +/** + * Created by vanitas on 22.07.17. + */ +public abstract class JingleFileTransfer extends JingleDescription implements JingleFileTransferController { + + public static final String NAMESPACE_V5 = "urn:xmpp:jingle:apps:file-transfer:5"; + public static final String NAMESPACE = NAMESPACE_V5; + + protected State state; + protected JingleFile metadata; + + private final List progressListeners = Collections.synchronizedList(new ArrayList()); + + JingleFileTransfer(JingleFile metadata) { + this.metadata = metadata; + } + + public abstract boolean isOffer(); + + public abstract boolean isRequest(); + + @Override + public void addProgressListener(ProgressListener listener) { + progressListeners.add(listener); + //TODO: Notify new listener? + } + + @Override + public void removeProgressListener(ProgressListener listener) { + progressListeners.remove(listener); + } + + @Override + public void cancel(XMPPConnection connection) throws SmackException.NotConnectedException, InterruptedException { + JingleSession session = getParent().getParent(); + switch (state) { + case pending: + if (session.isResponder()) { + connection.createStanzaCollectorAndSend(JingleElement.createSessionTerminate(session.getPeer(), session.getSessionId(), JingleReasonElement.Reason.decline)); + } else { + connection.createStanzaCollectorAndSend(JingleElement.createSessionTerminate(session.getPeer(), session.getSessionId(), JingleReasonElement.Reason.cancel)); + } + break; + + case active: + connection.createStanzaCollectorAndSend(JingleElement.createSessionTerminate(session.getPeer(), session.getSessionId(), JingleReasonElement.Reason.cancel)); + break; + + default: break; + } + getParent().onContentCancel(); + } + + public void notifyProgressListeners(float progress) { + for (ProgressListener p : progressListeners) { + p.progress(progress); + } + } + + public void notifyProgressListenersFinished() { + for (ProgressListener p : progressListeners) { + p.finished(); + } + } + + public void notifyProgressListenersStarted() { + for (ProgressListener p : progressListeners) { + p.started(); + } + } + + @Override + public String getNamespace() { + return JingleFileTransfer.NAMESPACE; + } + + @Override + public JingleFileTransferElement getElement() { + return new JingleFileTransferElement(metadata.getElement()); + } + + @Override + public State getState() { + return state; + } + + @Override + public JingleFile getMetadata() { + return metadata; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleIncomingFileOffer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleIncomingFileOffer.java new file mode 100644 index 000000000..d8f0bf5ef --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleIncomingFileOffer.java @@ -0,0 +1,174 @@ +/** + * + * 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.component; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.bytestreams.BytestreamSession; +import org.jivesoftware.smackx.hashes.HashManager; +import org.jivesoftware.smackx.hashes.element.HashElement; +import org.jivesoftware.smackx.jingle.component.JingleSession; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionInfoElement; +import org.jivesoftware.smackx.jingle.element.JingleElement; +import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; + +/** + * Behind the scenes logic of an incoming Jingle file offer. + * Created by vanitas on 26.07.17. + */ +public class JingleIncomingFileOffer extends AbstractJingleFileOffer implements IncomingFileOfferController { + + private static final Logger LOGGER = Logger.getLogger(JingleIncomingFileOffer.class.getName()); + private OutputStream target; + + public JingleIncomingFileOffer(JingleFileTransferChildElement offer) { + super(new JingleFile(offer)); + this.state = State.pending; + } + + @Override + public JingleElement handleDescriptionInfo(JingleContentDescriptionInfoElement info) { + return null; + } + + @Override + public void onBytestreamReady(BytestreamSession bytestreamSession) { + if (target == null) { + throw new IllegalStateException("Target OutputStream is null"); + } + + state = State.active; + + HashElement hashElement = metadata.getHashElement(); + MessageDigest digest = null; + if (hashElement != null) { + digest = HashManager.getMessageDigest(hashElement.getAlgorithm()); + LOGGER.log(Level.INFO, "File offer had checksum: " + digest.toString()); + } + + LOGGER.log(Level.INFO, "Receive file"); + + InputStream inputStream = null; + try { + inputStream = bytestreamSession.getInputStream(); + + if (digest != null) { + inputStream = new DigestInputStream(inputStream, digest); + } + + int length = 0; + int read = 0; + byte[] bufbuf = new byte[4096]; + while ((length = inputStream.read(bufbuf)) >= 0) { + target.write(bufbuf, 0, length); + read += length; + LOGGER.log(Level.INFO, "Read " + read + " (" + length + ") of " + metadata.getSize() + " bytes."); + if (read == (int) metadata.getSize()) { + break; + } + } + LOGGER.log(Level.INFO, "Reading/Writing finished."); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Cannot get InputStream from BytestreamSession: " + e, e); + } finally { + state = State.ended; + if (inputStream != null) { + try { + inputStream.close(); + LOGGER.log(Level.INFO, "CipherInputStream closed."); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Could not close InputStream: " + e, e); + } + } + + if (target != null) { + try { + target.close(); + LOGGER.log(Level.INFO, "FileOutputStream closed."); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Could not close OutputStream: " + e, e); + } + } + } + + if (digest != null) { + byte[] mDigest = ((DigestInputStream) inputStream).getMessageDigest().digest(); + if (!Arrays.equals(hashElement.getHash(), mDigest)) { + LOGGER.log(Level.WARNING, "CHECKSUM MISMATCH!"); + } else { + LOGGER.log(Level.INFO, "CHECKSUM MATCHED :)"); + } + } + notifyProgressListenersFinished(); + getParent().onContentFinished(); + } + + @Override + public boolean isOffer() { + return true; + } + + @Override + public boolean isRequest() { + return false; + } + + @Override + public void accept(XMPPConnection connection, File target) + throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, + SmackException.NoResponseException, IOException { + state = State.negotiating; + + if (!target.exists()) { + target.createNewFile(); + } + + this.target = new FileOutputStream(target); + + JingleSession session = getParent().getParent(); + if (session.getSessionState() == JingleSession.SessionState.pending) { + session.sendAccept(connection); + } + } + + @Override + public void accept(XMPPConnection connection, OutputStream stream) + throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, + SmackException.NoResponseException { + state = State.negotiating; + + target = stream; + + JingleSession session = getParent().getParent(); + if (session.getSessionState() == JingleSession.SessionState.pending) { + session.sendAccept(connection); + } + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleIncomingFileRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleIncomingFileRequest.java new file mode 100644 index 000000000..54e39dce3 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleIncomingFileRequest.java @@ -0,0 +1,60 @@ +/** + * + * 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.component; + +import org.jivesoftware.smackx.bytestreams.BytestreamSession; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionInfoElement; +import org.jivesoftware.smackx.jingle.element.JingleElement; +import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileRequestController; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferElement; + +/** + * Created by vanitas on 27.07.17. + * TODO: RemoteFile???? + */ +public class JingleIncomingFileRequest extends AbstractJingleFileRequest implements IncomingFileRequestController { + + public JingleIncomingFileRequest(JingleFileTransferChildElement request) { + super(new JingleFile(request)); + } + + @Override + public JingleFileTransferElement getElement() { + return null; + } + + @Override + public JingleElement handleDescriptionInfo(JingleContentDescriptionInfoElement info) { + return null; + } + + @Override + public boolean isOffer() { + return false; + } + + @Override + public boolean isRequest() { + return true; + } + + @Override + public void onBytestreamReady(BytestreamSession bytestreamSession) { + + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleOutgoingFileOffer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleOutgoingFileOffer.java new file mode 100644 index 000000000..fc65c4ce4 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleOutgoingFileOffer.java @@ -0,0 +1,103 @@ +/** + * + * 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.component; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +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.smackx.bytestreams.BytestreamSession; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionInfoElement; +import org.jivesoftware.smackx.jingle.element.JingleElement; +import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileOfferController; + +/** + * Created by vanitas on 26.07.17. + */ +public class JingleOutgoingFileOffer extends AbstractJingleFileOffer implements OutgoingFileOfferController { + private static final Logger LOGGER = Logger.getLogger(JingleOutgoingFileOffer.class.getName()); + + private final InputStream source; + + public JingleOutgoingFileOffer(File file, JingleFile metadata) throws FileNotFoundException { + super(metadata); + this.source = new FileInputStream(file); + } + + public JingleOutgoingFileOffer(InputStream inputStream, JingleFile metadata) { + super(metadata); + this.source = inputStream; + } + + @Override + public JingleElement handleDescriptionInfo(JingleContentDescriptionInfoElement info) { + return null; + } + + @Override + public void onBytestreamReady(BytestreamSession bytestreamSession) { + if (source == null) { + throw new IllegalStateException("Source InputStream is null!"); + } + + OutputStream outputStream = null; + + try { + outputStream = bytestreamSession.getOutputStream(); + + byte[] buf = new byte[8192]; + + while (true) { + int r = source.read(buf); + if (r < 0) { + break; + } + outputStream.write(buf, 0, r); + } + + outputStream.flush(); + outputStream.close(); + + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Exception while sending file: " + e, e); + } finally { + + try { + source.close(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Could not close FileInputStream: " + e, e); + } + } + + notifyProgressListenersFinished(); + } + + @Override + public boolean isOffer() { + return true; + } + + @Override + public boolean isRequest() { + return false; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleOutgoingFileRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleOutgoingFileRequest.java new file mode 100644 index 000000000..ccae40b5f --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleOutgoingFileRequest.java @@ -0,0 +1,52 @@ +/** + * + * 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.component; + +import org.jivesoftware.smackx.bytestreams.BytestreamSession; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionInfoElement; +import org.jivesoftware.smackx.jingle.element.JingleElement; +import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileRequestController; + +/** + * Created by vanitas on 27.07.17. + */ +public class JingleOutgoingFileRequest extends AbstractJingleFileRequest implements OutgoingFileRequestController { + + public JingleOutgoingFileRequest(JingleFile file) { + super(file); + } + + @Override + public JingleElement handleDescriptionInfo(JingleContentDescriptionInfoElement info) { + return null; + } + + @Override + public boolean isOffer() { + return false; + } + + @Override + public boolean isRequest() { + return true; + } + + @Override + public void onBytestreamReady(BytestreamSession bytestreamSession) { + + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/package-info.java new file mode 100644 index 000000000..beda2d84e --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/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. + * Internal classes. + */ +package org.jivesoftware.smackx.jingle_filetransfer.component; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/IncomingFileOfferController.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/IncomingFileOfferController.java new file mode 100644 index 000000000..95ee6a113 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/IncomingFileOfferController.java @@ -0,0 +1,35 @@ +/** + * + * 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.controller; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; + +/** + * User interface for an incoming Jingle file offer. + */ +public interface IncomingFileOfferController extends JingleFileTransferController { + + void accept(XMPPConnection connection, File target) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, IOException; + + void accept(XMPPConnection connection, OutputStream outputStream) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, IOException; +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/IncomingFileRequestController.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/IncomingFileRequestController.java new file mode 100644 index 000000000..8d27963de --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/IncomingFileRequestController.java @@ -0,0 +1,24 @@ +/** + * + * 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.controller; + +/** + * Created by vanitas on 27.07.17. + */ +public interface IncomingFileRequestController extends JingleFileTransferController { + //TODO: Declare methods. +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/JingleFileTransferController.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/JingleFileTransferController.java new file mode 100644 index 000000000..887fa85c6 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/JingleFileTransferController.java @@ -0,0 +1,37 @@ +/** + * + * 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.controller; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.jingle.JingleDescriptionController; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFile; +import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener; + +/** + * User interface for Jingle file transfers. + */ +public interface JingleFileTransferController extends JingleDescriptionController { + + void addProgressListener(ProgressListener listener); + + void removeProgressListener(ProgressListener listener); + + JingleFile getMetadata(); + + void cancel(XMPPConnection connection) throws SmackException.NotConnectedException, InterruptedException; +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/OutgoingFileOfferController.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/OutgoingFileOfferController.java new file mode 100644 index 000000000..6a84b125a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/OutgoingFileOfferController.java @@ -0,0 +1,24 @@ +/** + * + * 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.controller; + +/** + * Created by vanitas on 27.07.17. + */ +public interface OutgoingFileOfferController extends JingleFileTransferController { + //TODO: Declare methods. +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/OutgoingFileRequestController.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/OutgoingFileRequestController.java new file mode 100644 index 000000000..be3452b94 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/OutgoingFileRequestController.java @@ -0,0 +1,24 @@ +/** + * + * 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.controller; + +/** + * Created by vanitas on 27.07.17. + */ +public interface OutgoingFileRequestController extends JingleFileTransferController { + //TODO: Declare methods. +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/package-info.java new file mode 100644 index 000000000..b2711989f --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/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. + * Controller. + */ +package org.jivesoftware.smackx.jingle_filetransfer.controller; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/ChecksumElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/ChecksumElement.java new file mode 100644 index 000000000..3f7cbc5d1 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/ChecksumElement.java @@ -0,0 +1,65 @@ +/** + * + * 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.element; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.jingle.element.JingleContentElement; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransfer; + +/** + * Checksum element. + */ +public class ChecksumElement implements ExtensionElement { + + public static final String ELEMENT = "checksum"; + public static final String ATTR_CREATOR = "creator"; + public static final String ATTR_NAME = "name"; + + private final JingleContentElement.Creator creator; + private final String name; + private final JingleFileTransferChildElement file; + + public ChecksumElement(JingleContentElement.Creator creator, String name, JingleFileTransferChildElement file) { + this.creator = creator; + this.name = name; + this.file = Objects.requireNonNull(file, "file MUST NOT be null."); + Objects.requireNonNull(file.getHash(), "file MUST contain at least one hash element."); + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public CharSequence toXML() { + XmlStringBuilder sb = new XmlStringBuilder(this); + sb.optAttribute(ATTR_CREATOR, creator); + sb.optAttribute(ATTR_NAME, name); + sb.rightAngleBracket(); + sb.element(file); + sb.closeElement(this); + return sb; + } + + @Override + public String getNamespace() { + return JingleFileTransfer.NAMESPACE; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferChildElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferChildElement.java new file mode 100644 index 000000000..500f0ad50 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferChildElement.java @@ -0,0 +1,176 @@ +/** + * + * 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.element; + +import java.io.File; +import java.util.Date; + +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.hashes.element.HashElement; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionChildElement; + +/** + * Content of type File. + */ +public class JingleFileTransferChildElement extends JingleContentDescriptionChildElement { + + public static final String ELEMENT = "file"; + public static final String ELEM_DATE = "date"; + public static final String ELEM_DESC = "desc"; + public static final String ELEM_MEDIA_TYPE = "media-type"; + public static final String ELEM_NAME = "name"; + public static final String ELEM_SIZE = "size"; + + private final Date date; + private final String desc; + private final HashElement hash; + private final String mediaType; + private final String name; + private final long size; + private final Range range; + + public JingleFileTransferChildElement(Date date, String desc, HashElement hash, String mediaType, String name, long size, Range range) { + this.date = date; + this.desc = desc; + this.hash = hash; + this.mediaType = mediaType; + this.name = name; + this.size = size; + this.range = range; + } + + public Date getDate() { + return date; + } + + public String getDescription() { + return desc; + } + + public HashElement getHash() { + return hash; + } + + public String getMediaType() { + return mediaType; + } + + public String getName() { + return name; + } + + public long getSize() { + return size; + } + + public Range getRange() { + return range; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public CharSequence toXML() { + XmlStringBuilder sb = new XmlStringBuilder(this); + sb.rightAngleBracket(); + + sb.optElement(ELEM_DATE, date); + sb.optElement(ELEM_DESC, desc); + sb.optElement(ELEM_MEDIA_TYPE, mediaType); + sb.optElement(ELEM_NAME, name); + sb.optElement(range); + if (size > 0) { + sb.element(ELEM_SIZE, Long.toString(size)); + } + sb.optElement(hash); + sb.closeElement(this); + return sb; + } + + public static Builder getBuilder() { + return new Builder(); + } + + public static final class Builder { + private Date date; + private String desc; + private HashElement hash; + private String mediaType; + private String name; + private long size; + private Range range; + + private Builder() { + } + + public Builder setDate(Date date) { + this.date = date; + return this; + } + + public Builder setDescription(String desc) { + this.desc = desc; + return this; + } + + public Builder setHash(HashElement hash) { + this.hash = hash; + return this; + } + + /** + * Set the media type of the file. + * This is a MIME type from this list: + * https://www.iana.org/assignments/media-types/media-types.xhtml + * Default should be application/octet-stream. + * @param mediaType new media type. + * @return builder. + */ + public Builder setMediaType(String mediaType) { + this.mediaType = mediaType; + return this; + } + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Builder setSize(long size) { + this.size = size; + return this; + } + + public Builder setRange(Range range) { + this.range = range; + return this; + } + + public JingleFileTransferChildElement build() { + return new JingleFileTransferChildElement(date, desc, hash, mediaType, name, size, range); + } + + public Builder setFile(File file) { + return setDate(new Date(file.lastModified())) + .setName(file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf("/") + 1)) + .setSize((int) file.length()); + } + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferElement.java new file mode 100644 index 000000000..b667b20ec --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferElement.java @@ -0,0 +1,43 @@ +/** + * + * 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.element; + +import java.util.Collections; +import java.util.List; + +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionChildElement; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionElement; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransfer; + +/** + * File element. + */ +public class JingleFileTransferElement extends JingleContentDescriptionElement { + + public JingleFileTransferElement(JingleContentDescriptionChildElement payload) { + this(Collections.singletonList(payload)); + } + + public JingleFileTransferElement(List payloads) { + super(payloads); + } + + @Override + public String getNamespace() { + return JingleFileTransfer.NAMESPACE; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Range.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Range.java new file mode 100644 index 000000000..811685f9a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Range.java @@ -0,0 +1,131 @@ +/** + * + * 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.element; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.hashes.element.HashElement; + +/** + * RangeElement which specifies, which range of a file shall be transferred. + */ +public class Range implements NamedElement { + + public static final String ELEMENT = "range"; + public static final String ATTR_OFFSET = "offset"; + public static final String ATTR_LENGTH = "length"; + + private final Long offset, length; + private final HashElement hash; + + /** + * Create a Range element with default values. + */ + public Range() { + this(null, null, null); + } + + /** + * Create a Range element with specified length. + * @param length length of the transmitted data in bytes. + */ + public Range(Long length) { + this(null, length, null); + } + + /** + * Create a Range element with specified offset and length. + * @param offset offset in bytes from the beginning of the transmitted data. + * @param length number of bytes that shall be transferred. + */ + public Range(Long offset, Long length) { + this(offset, length, null); + } + + /** + * Create a Range element with specified offset, length and hash. + * @param offset offset in bytes from the beginning of the transmitted data. + * @param length number of bytes that shall be transferred. + * @param hash hash of the bytes in the specified range. + */ + public Range(Long offset, Long length, HashElement hash) { + this.offset = offset; + this.length = length; + this.hash = hash; + } + + /** + * Return the index of the offset. + * This marks the begin of the specified range. + * @return offset + */ + public Long getOffset() { + return offset; + } + + /** + * Return the length of the range. + * @return length + */ + public Long getLength() { + return length; + } + + /** + * Return the hash element that contains a checksum of the bytes specified in the range. + * @return hash element + */ + public HashElement getHash() { + return hash; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public CharSequence toXML() { + XmlStringBuilder sb = new XmlStringBuilder(this); + + sb.optAttribute(ATTR_OFFSET, offset); + sb.optAttribute(ATTR_LENGTH, length); + + if (hash != null) { + sb.rightAngleBracket(); + sb.element(hash); + sb.closeElement(this); + } else { + sb.closeEmptyElement(); + } + return sb; + } + + @Override + public boolean equals(Object other) { + if (other == null || !(other instanceof Range)) { + return false; + } + + return this.hashCode() == other.hashCode(); + } + + @Override + public int hashCode() { + return toXML().toString().hashCode(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/package-info.java new file mode 100644 index 000000000..da2e22582 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/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. + * Elements. + */ +package org.jivesoftware.smackx.jingle_filetransfer.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/IncomingFileOfferListener.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/IncomingFileOfferListener.java new file mode 100644 index 000000000..450f753c8 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/IncomingFileOfferListener.java @@ -0,0 +1,27 @@ +/** + * + * 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.listener; + +import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileOfferController; + +/** + * Created by vanitas on 26.07.17. + */ +public interface IncomingFileOfferListener { + + void onIncomingFileOffer(IncomingFileOfferController offer); +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/IncomingFileRequestListener.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/IncomingFileRequestListener.java new file mode 100644 index 000000000..19698089b --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/IncomingFileRequestListener.java @@ -0,0 +1,27 @@ +/** + * + * 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.listener; + +import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileRequestController; + +/** + * Created by vanitas on 27.07.17. + */ +public interface IncomingFileRequestListener { + + void onIncomingFileRequest(IncomingFileRequestController request); +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/ProgressListener.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/ProgressListener.java new file mode 100644 index 000000000..9ef8e2478 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/ProgressListener.java @@ -0,0 +1,29 @@ +/** + * + * 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.listener; + +/** + * Created by vanitas on 27.07.17. + */ +public interface ProgressListener { + + void started(); + + void progress(float percent); + + void finished(); +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/package-info.java new file mode 100644 index 000000000..438b31b61 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/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. + * Listeners. + */ +package org.jivesoftware.smackx.jingle_filetransfer.listener; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/package-info.java new file mode 100644 index 000000000..86fb45ecc --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ +package org.jivesoftware.smackx.jingle_filetransfer; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/ChecksumProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/ChecksumProvider.java new file mode 100644 index 000000000..ef9cba413 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/ChecksumProvider.java @@ -0,0 +1,91 @@ +/** + * + * 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.provider; + +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.hashes.element.HashElement; +import org.jivesoftware.smackx.hashes.provider.HashElementProvider; +import org.jivesoftware.smackx.jingle.element.JingleContentElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.ChecksumElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.Range; + +import org.xmlpull.v1.XmlPullParser; + + +/** + * Provider for the Checksum element. + */ +public class ChecksumProvider extends ExtensionElementProvider { + + @Override + public ChecksumElement parse(XmlPullParser parser, int initialDepth) throws Exception { + JingleContentElement.Creator creator = null; + String creatorString = parser.getAttributeValue(null, ChecksumElement.ATTR_CREATOR); + if (creatorString != null) { + creator = JingleContentElement.Creator.valueOf(creatorString); + } + String name = parser.getAttributeValue(null, ChecksumElement.ATTR_NAME); + + + JingleFileTransferChildElement.Builder cb = JingleFileTransferChildElement.getBuilder(); + HashElement hashElement = null; + Range range = null; + + boolean go = true; + while (go) { + int tag = parser.nextTag(); + String n = parser.getName(); + + if (tag == START_TAG) { + switch (n) { + case HashElement.ELEMENT: + hashElement = new HashElementProvider().parse(parser); + break; + + case Range.ELEMENT: + Long offset = ParserUtils.getLongAttribute(parser, Range.ATTR_OFFSET); + Long length = ParserUtils.getLongAttribute(parser, Range.ATTR_LENGTH); + range = new Range(offset, length); + } + } else if (tag == END_TAG) { + switch (n) { + case Range.ELEMENT: + if (hashElement != null && range != null) { + range = new Range(range.getOffset(), range.getLength(), hashElement); + hashElement = null; + } + break; + + case JingleFileTransferChildElement.ELEMENT: + if (hashElement != null) { + cb.setHash(hashElement); + } + if (range != null) { + cb.setRange(range); + } + go = false; + } + } + } + return new ChecksumElement(creator, name, cb.build()); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/JingleFileTransferProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/JingleFileTransferProvider.java new file mode 100644 index 000000000..8db762b7b --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/JingleFileTransferProvider.java @@ -0,0 +1,122 @@ +/** + * + * 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.provider; + +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import java.util.ArrayList; + +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.hashes.element.HashElement; +import org.jivesoftware.smackx.hashes.provider.HashElementProvider; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionChildElement; +import org.jivesoftware.smackx.jingle.provider.JingleContentDescriptionProvider; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransfer; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.Range; + +import org.xmlpull.v1.XmlPullParser; + +/** + * Provider for JingleContentDescriptionFileTransfer elements. + */ +public class JingleFileTransferProvider + extends JingleContentDescriptionProvider { + + @Override + public JingleFileTransferElement parse(XmlPullParser parser, int initialDepth) throws Exception { + ArrayList payloads = new ArrayList<>(); + boolean inRange = false; + JingleFileTransferChildElement.Builder builder = JingleFileTransferChildElement.getBuilder(); + HashElement inRangeHash = null; + Long length = null, offset = null; + while (true) { + + int tag = parser.nextTag(); + String elem = parser.getName(); + + if (tag == START_TAG) { + switch (elem) { + case JingleFileTransferChildElement.ELEM_DATE: + //builder.setDate(XmppDateTime.parseXEP0082Date(parser.nextText())); + parser.nextText(); + break; + + case JingleFileTransferChildElement.ELEM_DESC: + builder.setDescription(parser.nextText()); + break; + + case JingleFileTransferChildElement.ELEM_MEDIA_TYPE: + builder.setMediaType(parser.nextText()); + break; + + case JingleFileTransferChildElement.ELEM_NAME: + builder.setName(parser.nextText()); + break; + + case JingleFileTransferChildElement.ELEM_SIZE: + builder.setSize(Integer.parseInt(parser.nextText())); + break; + + case Range.ELEMENT: + inRange = true; + offset = ParserUtils.getLongAttribute(parser, Range.ATTR_OFFSET); + length = ParserUtils.getLongAttribute(parser, Range.ATTR_LENGTH); + + if (parser.isEmptyElementTag()) { + inRange = false; + builder.setRange(new Range(offset, length)); + } + break; + + case HashElement.ELEMENT: + if (inRange) { + inRangeHash = new HashElementProvider().parse(parser); + } else { + builder.setHash(new HashElementProvider().parse(parser)); + } + break; + } + + } else if (tag == END_TAG) { + switch (elem) { + + case Range.ELEMENT: + inRange = false; + builder.setRange(new Range(offset, length, inRangeHash)); + inRangeHash = null; + break; + + case JingleFileTransferChildElement.ELEMENT: + payloads.add(builder.build()); + builder = JingleFileTransferChildElement.getBuilder(); + break; + + case JingleFileTransferElement.ELEMENT: + return new JingleFileTransferElement(payloads); + } + } + } + } + + @Override + public String getNamespace() { + return JingleFileTransfer.NAMESPACE; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/package-info.java new file mode 100644 index 000000000..85163fdeb --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/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. + * Providers. + */ +package org.jivesoftware.smackx.jingle_filetransfer.provider; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_filetransfer/ChecksumTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_filetransfer/ChecksumTest.java new file mode 100644 index 000000000..f0fccfb66 --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_filetransfer/ChecksumTest.java @@ -0,0 +1,67 @@ +/** + * + * 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 static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; + +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smack.test.util.TestUtils; +import org.jivesoftware.smackx.hashes.HashManager; +import org.jivesoftware.smackx.hashes.element.HashElement; +import org.jivesoftware.smackx.jingle.element.JingleContentElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.ChecksumElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.Range; +import org.jivesoftware.smackx.jingle_filetransfer.provider.ChecksumProvider; + +import org.junit.Test; + +/** + * Created by vanitas on 12.07.17. + */ +public class ChecksumTest extends SmackTestSuite { + + @Test + public void parserTest() throws Exception { + HashElement hash = new HashElement(HashManager.ALGORITHM.SHA_256, "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk="); + JingleFileTransferChildElement file = new JingleFileTransferChildElement(null, null, hash, null, null, -1, null); + ChecksumElement checksum = new ChecksumElement(JingleContentElement.Creator.initiator, "name", file); + + String xml = "" + + "" + + "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=" + + "" + + ""; + + assertXMLEqual(xml, checksum.toXML().toString()); + assertXMLEqual(xml, new ChecksumProvider().parse(TestUtils.getParser(xml)).toXML().toString()); + + Range range = new Range(12L,34L); + file = new JingleFileTransferChildElement(null, null, hash, null, null, -1, range); + checksum = new ChecksumElement(JingleContentElement.Creator.initiator, "name", file); + + xml = "" + + "" + + "" + + "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=" + + "" + + ""; + assertXMLEqual(xml, checksum.toXML().toString()); + assertXMLEqual(xml, new ChecksumProvider().parse(TestUtils.getParser(xml)).toXML().toString()); + + } +} diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_filetransfer/IncomingFileTransferTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_filetransfer/IncomingFileTransferTest.java new file mode 100644 index 000000000..17e14966b --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_filetransfer/IncomingFileTransferTest.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_filetransfer; + +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; + +import java.util.Date; + +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleIncomingFileOffer; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; + +import org.junit.Test; + +public class IncomingFileTransferTest extends SmackTestSuite { + + @Test + public void incomingFileOfferTest() { + Date date = new Date(); + JingleFileTransferChildElement offerElement = new JingleFileTransferChildElement(date, "description", null, "application/octet-stream", "name", 1234, null); + JingleIncomingFileOffer offer = new JingleIncomingFileOffer(offerElement); + assertTrue(offer.isOffer()); + assertFalse(offer.isRequest()); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferIntegrationTest.java new file mode 100644 index 000000000..5a41968dc --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferIntegrationTest.java @@ -0,0 +1,178 @@ +/** + * + * 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 static junit.framework.TestCase.fail; +import static org.junit.Assert.assertArrayEquals; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.concurrent.Future; +import java.util.logging.Level; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5Proxy; +import org.jivesoftware.smackx.jingle.transport.jingle_ibb.JingleIBBTransportManager; +import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.listener.IncomingFileOfferListener; +import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener; + +import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; +import org.junit.AfterClass; +import org.jxmpp.jid.FullJid; + +/** + * Created by vanitas on 29.06.17. + */ +public class JingleFileTransferIntegrationTest extends AbstractSmackIntegrationTest { + + private static final File tempDir; + + static { + String userHome = System.getProperty("user.home"); + if (userHome != null) { + File f = new File(userHome); + tempDir = new File(f, ".config/smack-integration-test/"); + } else { + tempDir = new File("int_test_jingle"); + } + } + + public JingleFileTransferIntegrationTest(SmackIntegrationTestEnvironment environment) { + super(environment); + } + + @SmackIntegrationTest + public void basicFileTransferTest() throws Exception { + JingleIBBTransportManager.getInstanceFor(conOne); + JingleIBBTransportManager.getInstanceFor(conTwo); + + + final SimpleResultSyncPoint resultSyncPoint1 = new SimpleResultSyncPoint(); + final SimpleResultSyncPoint resultSyncPoint2 = new SimpleResultSyncPoint(); + + FullJid alice = conOne.getUser().asFullJidOrThrow(); + FullJid bob = conTwo.getUser().asFullJidOrThrow(); + + File source = prepareNewTestFile("source"); + final File target = new File(tempDir, "target"); + + JingleFileTransferManager aftm = JingleFileTransferManager.getInstanceFor(conOne); + JingleFileTransferManager bftm = JingleFileTransferManager.getInstanceFor(conTwo); + + final ArrayList> receiveFuture = new ArrayList<>(); //Uglaay + + bftm.addIncomingFileOfferListener(new IncomingFileOfferListener() { + @Override + public void onIncomingFileOffer(IncomingFileOfferController offer) { + LOGGER.log(Level.INFO, "INCOMING FILE TRANSFER!"); + + offer.addProgressListener(new ProgressListener() { + @Override + public void started() { + + } + + @Override + public void progress(float percent) { + + } + + @Override + public void finished() { + resultSyncPoint2.signal(); + } + }); + + try { + offer.accept(conTwo, target); + } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException | IOException e) { + fail(e.toString()); + } + } + }); + + OutgoingFileOfferController sending = aftm.sendFile(source, bob); + + sending.addProgressListener(new ProgressListener() { + @Override + public void started() { + + } + + @Override + public void progress(float percent) { + + } + + @Override + public void finished() { + resultSyncPoint1.signal(); + } + }); + + resultSyncPoint1.waitForResult(60 * 1000); + resultSyncPoint2.waitForResult(60 * 1000); + + byte[] sBytes = new byte[(int) source.length()]; + byte[] tBytes = new byte[(int) target.length()]; + try { + FileInputStream fi = new FileInputStream(source); + fi.read(sBytes); + fi.close(); + fi = new FileInputStream(target); + fi.read(tBytes); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Could not read files."); + fail(); + } + + assertArrayEquals(sBytes, tBytes); + LOGGER.log(Level.INFO, "SUCCESSFULLY SENT AND RECEIVED"); + + } + + public static File prepareNewTestFile(String name) { + File testFile = new File(tempDir, name); + try { + if (!testFile.exists()) { + testFile.createNewFile(); + } + FileOutputStream fo = new FileOutputStream(testFile); + byte[] rand = new byte[16000]; + INSECURE_RANDOM.nextBytes(rand); + fo.write(rand); + fo.close(); + return testFile; + } catch (IOException e) { + return null; + } + } + + @AfterClass + public static void cleanup() { + Socks5Proxy.getSocks5Proxy().stop(); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferTransportFallbackIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferTransportFallbackIntegrationTest.java new file mode 100644 index 000000000..6f720ac4a --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferTransportFallbackIntegrationTest.java @@ -0,0 +1,192 @@ +/** + * + * 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 static junit.framework.TestCase.fail; +import static org.junit.Assert.assertArrayEquals; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.logging.Level; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5Proxy; +import org.jivesoftware.smackx.jingle.transport.jingle_ibb.JingleIBBTransportManager; +import org.jivesoftware.smackx.jingle.transport.jingle_s5b.JingleS5BTransportManager; +import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.listener.IncomingFileOfferListener; +import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener; + +import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.jxmpp.jid.FullJid; + +public class JingleFileTransferTransportFallbackIntegrationTest extends AbstractSmackIntegrationTest { + + private static final File tempDir; + + static { + String userHome = System.getProperty("user.home"); + if (userHome != null) { + File f = new File(userHome); + tempDir = new File(f, ".config/smack-integration-test/"); + } else { + tempDir = new File("int_test_jingle"); + } + } + + public JingleFileTransferTransportFallbackIntegrationTest(SmackIntegrationTestEnvironment environment) { + super(environment); + } + + @Before + public void crippleS5B() { + // Manipulate the Manager so that it'll fail. + JingleS5BTransportManager.useExternalCandidates = false; + JingleS5BTransportManager.useLocalCandidates = false; + // *evil super villain laughter* + } + + @SmackIntegrationTest + public void S5BtoIBBfallbackTest() throws Exception { + + JingleS5BTransportManager.getInstanceFor(conOne); + JingleS5BTransportManager.getInstanceFor(conTwo); + + // Use Jingle IBB Transport as fallback. + JingleIBBTransportManager.getInstanceFor(conOne); + JingleIBBTransportManager.getInstanceFor(conTwo); + + + final SimpleResultSyncPoint resultSyncPoint1 = new SimpleResultSyncPoint(); + final SimpleResultSyncPoint resultSyncPoint2 = new SimpleResultSyncPoint(); + + FullJid alice = conOne.getUser().asFullJidOrThrow(); + FullJid bob = conTwo.getUser().asFullJidOrThrow(); + + File source = prepareNewTestFile("source"); + final File target = new File(tempDir, "target"); + + JingleFileTransferManager aftm = JingleFileTransferManager.getInstanceFor(conOne); + JingleFileTransferManager bftm = JingleFileTransferManager.getInstanceFor(conTwo); + + bftm.addIncomingFileOfferListener(new IncomingFileOfferListener() { + @Override + public void onIncomingFileOffer(IncomingFileOfferController offer) { + LOGGER.log(Level.INFO, "INCOMING FILE TRANSFER!"); + + offer.addProgressListener(new ProgressListener() { + @Override + public void started() { + + } + + @Override + public void progress(float percent) { + + } + + @Override + public void finished() { + resultSyncPoint2.signal(); + } + }); + + try { + offer.accept(conTwo, target); + } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException | IOException e) { + fail(e.toString()); + } + } + }); + + OutgoingFileOfferController sending = aftm.sendFile(source, bob); + + sending.addProgressListener(new ProgressListener() { + @Override + public void started() { + + } + + @Override + public void progress(float percent) { + + } + + @Override + public void finished() { + resultSyncPoint1.signal(); + } + }); + + resultSyncPoint1.waitForResult(60 * 1000); + resultSyncPoint2.waitForResult(60 * 1000); + + byte[] sBytes = new byte[(int) source.length()]; + byte[] tBytes = new byte[(int) target.length()]; + try { + FileInputStream fi = new FileInputStream(source); + fi.read(sBytes); + fi.close(); + fi = new FileInputStream(target); + fi.read(tBytes); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Could not read files."); + fail(); + } + + assertArrayEquals(sBytes, tBytes); + LOGGER.log(Level.INFO, "SUCCESSFULLY SENT AND RECEIVED"); + } + + @After + public void cureS5B() { + JingleS5BTransportManager.useExternalCandidates = true; + JingleS5BTransportManager.useLocalCandidates = true; + } + + public static File prepareNewTestFile(String name) { + File testFile = new File(tempDir, name); + try { + if (!testFile.exists()) { + testFile.createNewFile(); + } + FileOutputStream fo = new FileOutputStream(testFile); + byte[] rand = new byte[16000]; + INSECURE_RANDOM.nextBytes(rand); + fo.write(rand); + fo.close(); + return testFile; + } catch (IOException e) { + return null; + } + } + + @AfterClass + public void cleanup() { + Socks5Proxy.getSocks5Proxy().stop(); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/package-info.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/package-info.java new file mode 100644 index 000000000..1c0d4d68c --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Tests for XEP-0234 - Jingle File Transfer. + */ +package org.jivesoftware.smackx.jingle_filetransfer;