diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index a24a4ba0d..5d6c15aa9 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -20,14 +20,14 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.XmppConnection; -import eu.siacs.conversations.xmpp.jingle.stanzas.Content; +import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription; public abstract class AbstractGenerator { private final String[] FEATURES = { - "urn:xmpp:jingle:1", - Content.Version.FT_3.getNamespace(), - Content.Version.FT_4.getNamespace(), - Content.Version.FT_5.getNamespace(), + Namespace.JINGLE, + FileTransferDescription.Version.FT_3.getNamespace(), + FileTransferDescription.Version.FT_4.getNamespace(), + FileTransferDescription.Version.FT_5.getNamespace(), Namespace.JINGLE_TRANSPORTS_S5B, Namespace.JINGLE_TRANSPORTS_IBB, Namespace.JINGLE_ENCRYPTED_TRANSPORT, diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 38c98082d..f63f31253 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -18,6 +18,8 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.OnIqPacketReceived; +import eu.siacs.conversations.xmpp.jingle.stanzas.Content; +import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.stanzas.IqPacket; import rocks.xmpp.addr.Jid; @@ -34,9 +36,17 @@ public class JingleConnectionManager extends AbstractConnectionManager { public void deliverPacket(final Account account, final JinglePacket packet) { final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, packet); if (packet.getAction() == JinglePacket.Action.SESSION_INITIATE) { //TODO check that id doesn't exist yet - JingleFileTransferConnection connection = new JingleFileTransferConnection(this, id); - connection.init(account, packet); + final Content content = packet.getJingleContent(); + final String descriptionNamespace = content == null ? null : content.getDescriptionNamespace(); + final AbstractJingleConnection connection; + if (FileTransferDescription.NAMESPACES.contains(descriptionNamespace)) { + connection = new JingleFileTransferConnection(this, id); + } else { + //TODO return feature-not-implemented + return; + } connections.put(id, connection); + connection.deliverPacket(packet); } else { final AbstractJingleConnection abstractJingleConnection = connections.get(id); if (abstractJingleConnection != null) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java index 65bc0d5ed..997f84b89 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java @@ -3,6 +3,8 @@ package eu.siacs.conversations.xmpp.jingle; import android.util.Base64; import android.util.Log; +import com.google.common.base.Preconditions; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -36,6 +38,7 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jingle.stanzas.Content; +import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.stanzas.IqPacket; @@ -50,7 +53,6 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple private static final int JINGLE_STATUS_FINISHED = 4; private static final int JINGLE_STATUS_FAILED = 99; private static final int JINGLE_STATUS_OFFERED = -1; - private Content.Version ftVersion = Content.Version.FT_3; private int ibbBlockSize = 8192; @@ -63,7 +65,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple private ConcurrentHashMap connections = new ConcurrentHashMap<>(); private String transportId; - private Element fileOffer; + private FileTransferDescription description; private DownloadableFile file = null; private boolean proxyActivationFailed = false; @@ -130,7 +132,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple id.account.getPgpDecryptionService().decrypt(message, true); } } else { - if (ftVersion == Content.Version.FT_5) { //older Conversations will break when receiving a session-info + if (description.getVersion() == FileTransferDescription.Version.FT_5) { //older Conversations will break when receiving a session-info sendHash(); } if (message.getEncryption() == Message.ENCRYPTION_PGP) { @@ -236,7 +238,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple void deliverPacket(final JinglePacket packet) { final JinglePacket.Action action = packet.getAction(); //TODO switch case - if (action == JinglePacket.Action.SESSION_TERMINATE) { + if (action == JinglePacket.Action.SESSION_INITIATE) { + init(packet); + } else if (action == JinglePacket.Action.SESSION_TERMINATE) { Reason reason = packet.getReason(); if (reason != null) { if (reason.hasChild("cancel")) { @@ -307,6 +311,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple } public void init(final Message message) { + Preconditions.checkArgument(message.isFileOrImage()); if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { Conversation conversation = (Conversation) message.getConversation(); conversation.getAccount().getAxolotlService().prepareKeyTransportMessage(conversation, xmppAxolotlMessage -> { @@ -321,13 +326,13 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple } } - private void init(Message message, XmppAxolotlMessage xmppAxolotlMessage) { + private void init(final Message message, final XmppAxolotlMessage xmppAxolotlMessage) { this.mXmppAxolotlMessage = xmppAxolotlMessage; this.contentCreator = Content.Creator.INITIATOR; this.contentName = JingleConnectionManager.nextRandomId(); this.message = message; final List remoteFeatures = getRemoteFeatures(); - upgradeNamespace(remoteFeatures); + final FileTransferDescription.Version remoteVersion = getAvailableFileTransferVersion(remoteFeatures); this.initialTransport = remoteFeatures.contains(Namespace.JINGLE_TRANSPORTS_S5B) ? Transport.SOCKS : Transport.IBB; this.remoteSupportsOmemoJet = remoteFeatures.contains(Namespace.JINGLE_ENCRYPTED_TRANSPORT_OMEMO); this.message.setTransferable(this); @@ -335,6 +340,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple this.initiator = this.id.account.getJid(); this.responder = this.id.counterPart; this.transportId = JingleConnectionManager.nextRandomId(); + this.setupDescription(remoteVersion); if (this.initialTransport == Transport.IBB) { this.sendInitRequest(); } else { @@ -386,11 +392,13 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple } } - private void upgradeNamespace(List remoteFeatures) { - if (remoteFeatures.contains(Content.Version.FT_5.getNamespace())) { - this.ftVersion = Content.Version.FT_5; - } else if (remoteFeatures.contains(Content.Version.FT_4.getNamespace())) { - this.ftVersion = Content.Version.FT_4; + private FileTransferDescription.Version getAvailableFileTransferVersion(List remoteFeatures) { + if (remoteFeatures.contains(FileTransferDescription.Version.FT_5.getNamespace())) { + return FileTransferDescription.Version.FT_5; + } else if (remoteFeatures.contains(FileTransferDescription.Version.FT_4.getNamespace())) { + return FileTransferDescription.Version.FT_4; + } else { + return FileTransferDescription.Version.FT_3; } } @@ -406,11 +414,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple } } - public void init(Account account, JinglePacket packet) { //should move to deliverPacket + private void init(JinglePacket packet) { //should move to deliverPacket this.mJingleStatus = JINGLE_STATUS_INITIATED; - Conversation conversation = this.xmppConnectionService - .findOrCreateConversation(account, - packet.getFrom().asBareJid(), false, false); + final Conversation conversation = this.xmppConnectionService.findOrCreateConversation(id.account, id.counterPart.asBareJid(), false, false); this.message = new Message(conversation, "", Message.ENCRYPTION_NONE); this.message.setStatus(Message.STATUS_RECEIVED); this.mStatus = Transferable.STATUS_OFFER; @@ -445,14 +451,10 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple return; } } - this.ftVersion = content.getVersion(); - if (ftVersion == null) { - respondToIq(packet, false); - this.fail(); - return; - } - this.fileOffer = content.getFileOffer(this.ftVersion); + this.description = (FileTransferDescription) content.getDescription(); + + final Element fileOffer = this.description.getFileOffer(); if (fileOffer != null) { boolean remoteIsUsingJet = false; @@ -536,71 +538,76 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple } } + private void setupDescription(final FileTransferDescription.Version version) { + this.file = this.xmppConnectionService.getFileBackend().getFile(message, false); + final FileTransferDescription description; + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + this.file.setKey(mXmppAxolotlMessage.getInnerKey()); + this.file.setIv(mXmppAxolotlMessage.getIV()); + //legacy OMEMO encrypted file transfer reported file size of the encrypted file + //JET uses the file size of the plain text file. The difference is only 16 bytes (auth tag) + this.file.setExpectedSize(file.getSize() + (this.remoteSupportsOmemoJet ? 0 : 16)); + if (remoteSupportsOmemoJet) { + description = FileTransferDescription.of(this.file, version, null); + } else { + description = FileTransferDescription.of(this.file, version, this.mXmppAxolotlMessage); + } + } else { + this.file.setExpectedSize(file.getSize()); + description = FileTransferDescription.of(this.file, version, null); + } + this.description = description; + } + private void sendInitRequest() { final JinglePacket packet = this.bootstrapPacket(JinglePacket.Action.SESSION_INITIATE); final Content content = new Content(this.contentCreator, this.contentName); - if (message.isFileOrImage()) { - content.setTransportId(this.transportId); - this.file = this.xmppConnectionService.getFileBackend().getFile(message, false); - if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { - this.file.setKey(mXmppAxolotlMessage.getInnerKey()); - this.file.setIv(mXmppAxolotlMessage.getIV()); - //legacy OMEMO encrypted file transfer reported file size of the encrypted file - //JET uses the file size of the plain text file. The difference is only 16 bytes (auth tag) - this.file.setExpectedSize(file.getSize() + (this.remoteSupportsOmemoJet ? 0 : 16)); - final Element file = content.setFileOffer(this.file, false, this.ftVersion); - if (remoteSupportsOmemoJet) { - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": remote announced support for JET"); - final Element security = new Element("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT); - security.setAttribute("name", this.contentName); - security.setAttribute("cipher", JET_OMEMO_CIPHER); - security.setAttribute("type", AxolotlService.PEP_PREFIX); - security.addChild(mXmppAxolotlMessage.toElement()); - content.addChild(security); - } else { - file.addChild(mXmppAxolotlMessage.toElement()); - } - } else { - this.file.setExpectedSize(file.getSize()); - content.setFileOffer(this.file, false, this.ftVersion); - } - message.resetFileParams(); - try { - this.mFileInputStream = new FileInputStream(file); - } catch (FileNotFoundException e) { - fail(e.getMessage()); - return; - } - content.setTransportId(this.transportId); - if (this.initialTransport == Transport.IBB) { - content.ibbTransport().setAttribute("block-size", Integer.toString(this.ibbBlockSize)); - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": sending IBB offer"); - } else { - final List candidates = getCandidatesAsElements(); - Log.d(Config.LOGTAG, String.format("%s: sending S5B offer with %d candidates", id.account.getJid().asBareJid(), candidates.size())); - content.socks5transport().setChildren(candidates); - } - packet.setJingleContent(content); - this.sendJinglePacket(packet, (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": other party received offer"); - if (mJingleStatus == JINGLE_STATUS_OFFERED) { - mJingleStatus = JINGLE_STATUS_INITIATED; - xmppConnectionService.markMessage(message, Message.STATUS_OFFERED); - } else { - Log.d(Config.LOGTAG, "received ack for offer when status was " + mJingleStatus); - } - } else { - fail(IqParser.extractErrorMessage(response)); - } - }); - + content.setTransportId(this.transportId); + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL && remoteSupportsOmemoJet) { + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": remote announced support for JET"); + final Element security = new Element("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT); + security.setAttribute("name", this.contentName); + security.setAttribute("cipher", JET_OMEMO_CIPHER); + security.setAttribute("type", AxolotlService.PEP_PREFIX); + security.addChild(mXmppAxolotlMessage.toElement()); + content.addChild(security); } + content.setDescription(this.description); + message.resetFileParams(); + try { + this.mFileInputStream = new FileInputStream(file); + } catch (FileNotFoundException e) { + fail(e.getMessage()); + return; + } + content.setTransportId(this.transportId); + if (this.initialTransport == Transport.IBB) { + content.ibbTransport().setAttribute("block-size", Integer.toString(this.ibbBlockSize)); + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": sending IBB offer"); + } else { + final List candidates = getCandidatesAsElements(); + Log.d(Config.LOGTAG, String.format("%s: sending S5B offer with %d candidates", id.account.getJid().asBareJid(), candidates.size())); + content.socks5transport().setChildren(candidates); + } + packet.setJingleContent(content); + this.sendJinglePacket(packet, (account, response) -> { + if (response.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": other party received offer"); + if (mJingleStatus == JINGLE_STATUS_OFFERED) { + mJingleStatus = JINGLE_STATUS_INITIATED; + xmppConnectionService.markMessage(message, Message.STATUS_OFFERED); + } else { + Log.d(Config.LOGTAG, "received ack for offer when status was " + mJingleStatus); + } + } else { + fail(IqParser.extractErrorMessage(response)); + } + }); + } private void sendHash() { - - final Element checksum = new Element("checksum", ftVersion.getNamespace()); + final Element checksum = new Element("checksum", description.getVersion().getNamespace()); checksum.setAttribute("creator", "initiator"); checksum.setAttribute("name", "a-file-offer"); Element hash = checksum.addChild("file").addChild("hash", "urn:xmpp:hashes:2"); @@ -637,7 +644,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple this.jingleConnectionManager.getPrimaryCandidate(this.id.account, initiating(), (success, candidate) -> { final JinglePacket packet = bootstrapPacket(JinglePacket.Action.SESSION_ACCEPT); final Content content = new Content(contentCreator, contentName); - content.setFileOffer(fileOffer, ftVersion); + content.setDescription(this.description); content.setTransportId(transportId); if (success && candidate != null && !equalCandidateExists(candidate)) { final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this, candidate); @@ -677,7 +684,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple this.transport = new JingleInBandTransport(this, this.transportId, this.ibbBlockSize); final JinglePacket packet = bootstrapPacket(JinglePacket.Action.SESSION_ACCEPT); final Content content = new Content(contentCreator, contentName); - content.setFileOffer(fileOffer, ftVersion); + content.setDescription(this.description); content.setTransportId(transportId); content.ibbTransport().setAttribute("block-size", this.ibbBlockSize); packet.setJingleContent(content); @@ -812,7 +819,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple if (connection.needsActivation()) { if (connection.getCandidate().isOurs()) { final String sid; - if (ftVersion == Content.Version.FT_3) { + if (description.getVersion() == FileTransferDescription.Version.FT_3) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": use session ID instead of transport ID to activate proxy"); sid = id.sessionId; } else { @@ -1220,12 +1227,8 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple return this.transportId; } - public Content.Version getFtVersion() { - return this.ftVersion; - } - - public boolean hasTransportId(String sid) { - return sid.equals(this.transportId); + public FileTransferDescription.Version getFtVersion() { + return this.description.getVersion(); } public JingleTransport getTransport() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java index 2c9f11a56..31c40f531 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -24,6 +24,7 @@ import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.SocksSocketFactory; import eu.siacs.conversations.utils.WakeLockHelper; import eu.siacs.conversations.xmpp.jingle.stanzas.Content; +import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription; public class JingleSocks5Transport extends JingleTransport { @@ -52,7 +53,7 @@ public class JingleSocks5Transport extends JingleTransport { this.connection = jingleConnection; this.account = jingleConnection.getId().account; final StringBuilder destBuilder = new StringBuilder(); - if (this.connection.getFtVersion() == Content.Version.FT_3) { + if (this.connection.getFtVersion() == FileTransferDescription.Version.FT_3) { Log.d(Config.LOGTAG, this.account.getJid().asBareJid() + ": using session Id instead of transport Id for proxy destination"); destBuilder.append(this.connection.getId().sessionId); } else { @@ -132,7 +133,7 @@ public class JingleSocks5Transport extends JingleTransport { responseHeader = new byte[]{0x05, 0x00, 0x00, 0x03}; success = true; } else { - Log.d(Config.LOGTAG,this.account.getJid().asBareJid()+": destination mismatch. received "+receivedDestination+" (expected "+this.destination+")"); + Log.d(Config.LOGTAG, this.account.getJid().asBareJid() + ": destination mismatch. received " + receivedDestination + " (expected " + this.destination + ")"); responseHeader = new byte[]{0x05, 0x04, 0x00, 0x03}; success = false; } @@ -143,7 +144,7 @@ public class JingleSocks5Transport extends JingleTransport { outputStream.write(response.array()); outputStream.flush(); if (success) { - Log.d(Config.LOGTAG,this.account.getJid().asBareJid()+": successfully processed connection to candidate "+candidate.getHost()+":"+candidate.getPort()); + Log.d(Config.LOGTAG, this.account.getJid().asBareJid() + ": successfully processed connection to candidate " + candidate.getHost() + ":" + candidate.getPort()); socket.setSoTimeout(0); this.socket = socket; this.inputStream = inputStream; @@ -216,7 +217,7 @@ public class JingleSocks5Transport extends JingleTransport { } } catch (Exception e) { final Account account = this.account; - Log.d(Config.LOGTAG, account.getJid().asBareJid()+": failed sending file after "+transmitted+"/"+file.getExpectedSize()+" ("+ socket.getInetAddress()+":"+socket.getPort()+")", e); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": failed sending file after " + transmitted + "/" + file.getExpectedSize() + " (" + socket.getInetAddress() + ":" + socket.getPort() + ")", e); callback.onFileTransferAborted(); } finally { FileBackend.close(fileInputStream); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java index 5dbeef03d..34c0c706f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java @@ -39,58 +39,35 @@ public class Content extends Element { return Creator.of(getAttribute("creator")); } - public Version getVersion() { - if (hasChild("description", Version.FT_3.namespace)) { - return Version.FT_3; - } else if (hasChild("description", Version.FT_4.namespace)) { - return Version.FT_4; - } else if (hasChild("description", Version.FT_5.namespace)) { - return Version.FT_5; - } - return null; + public Senders getSenders() { + return Senders.of(getAttribute("senders")); } - public Element setFileOffer(DownloadableFile actualFile, boolean otr, Version version) { - Element description = this.addChild("description", version.namespace); - Element file; - if (version == Version.FT_3) { - Element offer = description.addChild("offer"); - file = offer.addChild("file"); - } else { - file = description.addChild("file"); - } - file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize())); - if (otr) { - file.addChild("name").setContent(actualFile.getName() + ".otr"); - } else { - file.addChild("name").setContent(actualFile.getName()); - } - return file; + public void setSenders(Senders senders) { + this.setAttribute("senders", senders.toString()); } - public Element getFileOffer(Version version) { - Element description = this.findChild("description", version.namespace); + public GenericDescription getDescription() { + final Element description = this.findChild("description"); if (description == null) { return null; } - if (version == Version.FT_3) { - Element offer = description.findChild("offer"); - if (offer == null) { - return null; - } - return offer.findChild("file"); + final String xmlns = description.getNamespace(); + if (FileTransferDescription.NAMESPACES.contains(xmlns)) { + return FileTransferDescription.upgrade(description); } else { - return description.findChild("file"); + return GenericDescription.upgrade(description); } } - public void setFileOffer(Element fileOffer, Version version) { - Element description = this.addChild("description", version.namespace); - if (version == Version.FT_3) { - description.addChild("offer").addChild(fileOffer); - } else { - description.addChild(fileOffer); - } + public void setDescription(final GenericDescription description) { + Preconditions.checkNotNull(description); + this.addChild(description); + } + + public String getDescriptionNamespace() { + final Element description = this.findChild("description"); + return description == null ? null : description.getNamespace(); } public String getTransportId() { @@ -132,22 +109,6 @@ public class Content extends Element { return this.hasChild("transport", Namespace.JINGLE_TRANSPORTS_IBB); } - public enum Version { - FT_3("urn:xmpp:jingle:apps:file-transfer:3"), - FT_4("urn:xmpp:jingle:apps:file-transfer:4"), - FT_5("urn:xmpp:jingle:apps:file-transfer:5"); - - private final String namespace; - - Version(String namespace) { - this.namespace = namespace; - } - - public String getNamespace() { - return namespace; - } - } - public enum Creator { INITIATOR, RESPONDER; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java new file mode 100644 index 000000000..8e0f2ebad --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java @@ -0,0 +1,89 @@ +package eu.siacs.conversations.xmpp.jingle.stanzas; + +import com.google.common.base.Preconditions; + +import java.util.Arrays; +import java.util.List; + +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; +import eu.siacs.conversations.entities.DownloadableFile; +import eu.siacs.conversations.xml.Element; + +public class FileTransferDescription extends GenericDescription { + + public static List NAMESPACES = Arrays.asList( + Version.FT_3.namespace, + Version.FT_4.namespace, + Version.FT_5.namespace + ); + + + private FileTransferDescription(String name, String namespace) { + super(name, namespace); + } + + public Version getVersion() { + final String namespace = getNamespace(); + if (namespace.equals(Version.FT_3.namespace)) { + return Version.FT_3; + } else if (namespace.equals(Version.FT_4.namespace)) { + return Version.FT_4; + } else if (namespace.equals(Version.FT_5.namespace)) { + return Version.FT_5; + } else { + throw new IllegalStateException("Unknown namespace"); + } + } + + public Element getFileOffer() { + final Version version = getVersion(); + if (version == Version.FT_3) { + final Element offer = this.findChild("offer"); + return offer == null ? null : offer.findChild("file"); + } else { + return this.findChild("file"); + } + } + + public static FileTransferDescription of(DownloadableFile file, Version version, XmppAxolotlMessage axolotlMessage) { + final FileTransferDescription description = new FileTransferDescription("description", version.getNamespace()); + final Element fileElement; + if (version == Version.FT_3) { + Element offer = description.addChild("offer"); + fileElement = offer.addChild("file"); + } else { + fileElement = description.addChild("file"); + } + fileElement.addChild("size").setContent(Long.toString(file.getExpectedSize())); + fileElement.addChild("name").setContent(file.getName()); + if (axolotlMessage != null) { + fileElement.addChild(axolotlMessage.toElement()); + } + return description; + } + + public static FileTransferDescription upgrade(final Element element) { + Preconditions.checkArgument("description".equals(element.getName()), "Name of provided element is not description"); + Preconditions.checkArgument(NAMESPACES.contains(element.getNamespace()), "Element does not match a file transfer namespace"); + final FileTransferDescription description = new FileTransferDescription("description", element.getNamespace()); + description.setAttributes(element.getAttributes()); + description.setChildren(element.getChildren()); + return description; + } + + public enum Version { + FT_3("urn:xmpp:jingle:apps:file-transfer:3"), + FT_4("urn:xmpp:jingle:apps:file-transfer:4"), + FT_5("urn:xmpp:jingle:apps:file-transfer:5"); + + private final String namespace; + + Version(String namespace) { + this.namespace = namespace; + } + + public String getNamespace() { + return namespace; + } + } +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/GenericDescription.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/GenericDescription.java new file mode 100644 index 000000000..0e3c5a7f1 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/GenericDescription.java @@ -0,0 +1,20 @@ +package eu.siacs.conversations.xmpp.jingle.stanzas; + +import com.google.common.base.Preconditions; + +import eu.siacs.conversations.xml.Element; + +public class GenericDescription extends Element { + + protected GenericDescription(String name, final String namespace) { + super(name, namespace); + } + + public static GenericDescription upgrade(final Element element) { + Preconditions.checkArgument("description".equals(element.getName())); + final GenericDescription description = new GenericDescription("description", element.getNamespace()); + description.setAttributes(element.getAttributes()); + description.setChildren(element.getChildren()); + return description; + } +}