support jingle ft:4 to be compatible with swift

Conversations and Gajim both have an implementation bug that sends the jingle session id instead of the transport id (compare XEP-260 2.2). This commit has a work around for this that remains buggy when using ft:3. If gajim is ever to fix this we will be incompatbile. gajim should implement ft:4 instead. (gajim to gajim is broken as well)
This commit is contained in:
Daniel Gultsch 2016-06-29 17:16:34 +02:00
parent b5caa8fa35
commit 1d79a677c8
4 changed files with 105 additions and 32 deletions

View file

@ -16,11 +16,13 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.utils.PhoneHelper;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
public abstract class AbstractGenerator { public abstract class AbstractGenerator {
private final String[] FEATURES = { private final String[] FEATURES = {
"urn:xmpp:jingle:1", "urn:xmpp:jingle:1",
"urn:xmpp:jingle:apps:file-transfer:3", Content.Version.FT_3.getNamespace(),
Content.Version.FT_4.getNamespace(),
"urn:xmpp:jingle:transports:s5b:1", "urn:xmpp:jingle:transports:s5b:1",
"urn:xmpp:jingle:transports:ibb:1", "urn:xmpp:jingle:transports:ibb:1",
"http://jabber.org/protocol/muc", "http://jabber.org/protocol/muc",

View file

@ -21,6 +21,7 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Presence;
import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.entities.TransferablePlaceholder;
import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.FileBackend;
@ -45,6 +46,8 @@ public class JingleConnection implements Transferable {
protected static final int JINGLE_STATUS_TRANSMITTING = 5; protected static final int JINGLE_STATUS_TRANSMITTING = 5;
protected static final int JINGLE_STATUS_FAILED = 99; protected static final int JINGLE_STATUS_FAILED = 99;
private Content.Version ftVersion = Content.Version.FT_3;
private int ibbBlockSize = 8192; private int ibbBlockSize = 8192;
private int mJingleStatus = -1; private int mJingleStatus = -1;
@ -238,12 +241,14 @@ public class JingleConnection implements Transferable {
this.contentCreator = "initiator"; this.contentCreator = "initiator";
this.contentName = this.mJingleConnectionManager.nextRandomId(); this.contentName = this.mJingleConnectionManager.nextRandomId();
this.message = message; this.message = message;
this.account = message.getConversation().getAccount();
upgradeNamespace();
this.message.setTransferable(this); this.message.setTransferable(this);
this.mStatus = Transferable.STATUS_UPLOADING; this.mStatus = Transferable.STATUS_UPLOADING;
this.account = message.getConversation().getAccount();
this.initiator = this.account.getJid(); this.initiator = this.account.getJid();
this.responder = this.message.getCounterpart(); this.responder = this.message.getCounterpart();
this.sessionId = this.mJingleConnectionManager.nextRandomId(); this.sessionId = this.mJingleConnectionManager.nextRandomId();
this.transportId = this.mJingleConnectionManager.nextRandomId();
if (this.candidates.size() > 0) { if (this.candidates.size() > 0) {
this.sendInitRequest(); this.sendInitRequest();
} else { } else {
@ -287,6 +292,20 @@ public class JingleConnection implements Transferable {
} }
private void upgradeNamespace() {
Jid jid = this.message.getCounterpart();
String resource = jid != null ?jid.getResourcepart() : null;
if (resource != null) {
Presence presence = this.account.getRoster().getContact(jid).getPresences().getPresences().get(resource);
if (presence != null) {
List<String> features = presence.getServiceDiscoveryResult().getFeatures();
if (features.contains(Content.Version.FT_4.getNamespace())) {
this.ftVersion = Content.Version.FT_4;
}
}
}
}
public void init(Account account, JinglePacket packet) { public void init(Account account, JinglePacket packet) {
this.mJingleStatus = JINGLE_STATUS_INITIATED; this.mJingleStatus = JINGLE_STATUS_INITIATED;
Conversation conversation = this.mXmppConnectionService Conversation conversation = this.mXmppConnectionService
@ -307,7 +326,13 @@ public class JingleConnection implements Transferable {
this.contentName = content.getAttribute("name"); this.contentName = content.getAttribute("name");
this.transportId = content.getTransportId(); this.transportId = content.getTransportId();
this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren())); this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren()));
this.fileOffer = packet.getJingleContent().getFileOffer(); this.ftVersion = content.getVersion();
if (ftVersion == null) {
this.sendCancel();
this.fail();
return;
}
this.fileOffer = content.getFileOffer(this.ftVersion);
mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null); mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null);
@ -431,24 +456,23 @@ public class JingleConnection implements Transferable {
this.file.setKeyAndIv(conversation.getSymmetricKey()); this.file.setKeyAndIv(conversation.getSymmetricKey());
pair = AbstractConnectionManager.createInputStream(this.file, false); pair = AbstractConnectionManager.createInputStream(this.file, false);
this.file.setExpectedSize(pair.second); this.file.setExpectedSize(pair.second);
content.setFileOffer(this.file, true); content.setFileOffer(this.file, true, this.ftVersion);
} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
this.file.setKey(mXmppAxolotlMessage.getInnerKey()); this.file.setKey(mXmppAxolotlMessage.getInnerKey());
this.file.setIv(mXmppAxolotlMessage.getIV()); this.file.setIv(mXmppAxolotlMessage.getIV());
pair = AbstractConnectionManager.createInputStream(this.file, true); pair = AbstractConnectionManager.createInputStream(this.file, true);
this.file.setExpectedSize(pair.second); this.file.setExpectedSize(pair.second);
content.setFileOffer(this.file, false).addChild(mXmppAxolotlMessage.toElement()); content.setFileOffer(this.file, false, this.ftVersion).addChild(mXmppAxolotlMessage.toElement());
} else { } else {
pair = AbstractConnectionManager.createInputStream(this.file, false); pair = AbstractConnectionManager.createInputStream(this.file, false);
this.file.setExpectedSize(pair.second); this.file.setExpectedSize(pair.second);
content.setFileOffer(this.file, false); content.setFileOffer(this.file, false, this.ftVersion);
} }
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
cancel(); cancel();
return; return;
} }
this.mFileInputStream = pair.first; this.mFileInputStream = pair.first;
this.transportId = this.mJingleConnectionManager.nextRandomId();
content.setTransportId(this.transportId); content.setTransportId(this.transportId);
content.socks5transport().setChildren(getCandidatesAsElements()); content.socks5transport().setChildren(getCandidatesAsElements());
packet.setContent(content); packet.setContent(content);
@ -488,7 +512,7 @@ public class JingleConnection implements Transferable {
public void onPrimaryCandidateFound(boolean success, final JingleCandidate candidate) { public void onPrimaryCandidateFound(boolean success, final JingleCandidate candidate) {
final JinglePacket packet = bootstrapPacket("session-accept"); final JinglePacket packet = bootstrapPacket("session-accept");
final Content content = new Content(contentCreator,contentName); final Content content = new Content(contentCreator,contentName);
content.setFileOffer(fileOffer); content.setFileOffer(fileOffer, ftVersion);
content.setTransportId(transportId); content.setTransportId(transportId);
if (success && candidate != null && !equalCandidateExists(candidate)) { if (success && candidate != null && !equalCandidateExists(candidate)) {
final JingleSocks5Transport socksConnection = new JingleSocks5Transport( final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
@ -626,13 +650,20 @@ public class JingleConnection implements Transferable {
this.mJingleStatus = JINGLE_STATUS_TRANSMITTING; this.mJingleStatus = JINGLE_STATUS_TRANSMITTING;
if (connection.needsActivation()) { if (connection.needsActivation()) {
if (connection.getCandidate().isOurs()) { if (connection.getCandidate().isOurs()) {
final String sid;
if (ftVersion == Content.Version.FT_3) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": use session ID instead of transport ID to activate proxy");
sid = getSessionId();
} else {
sid = getTransportId();
}
Log.d(Config.LOGTAG, "candidate " Log.d(Config.LOGTAG, "candidate "
+ connection.getCandidate().getCid() + connection.getCandidate().getCid()
+ " was our proxy. going to activate"); + " was our proxy. going to activate");
IqPacket activation = new IqPacket(IqPacket.TYPE.SET); IqPacket activation = new IqPacket(IqPacket.TYPE.SET);
activation.setTo(connection.getCandidate().getJid()); activation.setTo(connection.getCandidate().getJid());
activation.query("http://jabber.org/protocol/bytestreams") activation.query("http://jabber.org/protocol/bytestreams")
.setAttribute("sid", this.getSessionId()); .setAttribute("sid", sid);
activation.query().addChild("activate") activation.query().addChild("activate")
.setContent(this.getCounterPart().toString()); .setContent(this.getCounterPart().toString());
mXmppConnectionService.sendIqPacket(account,activation, mXmppConnectionService.sendIqPacket(account,activation,
@ -975,6 +1006,14 @@ public class JingleConnection implements Transferable {
mXmppConnectionService.updateConversationUi(); mXmppConnectionService.updateConversationUi();
} }
public String getTransportId() {
return this.transportId;
}
public Content.Version getFtVersion() {
return this.ftVersion;
}
interface OnProxyActivated { interface OnProxyActivated {
public void success(); public void success();

View file

@ -22,6 +22,7 @@ import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.SocksSocketFactory; import eu.siacs.conversations.utils.SocksSocketFactory;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
public class JingleSocks5Transport extends JingleTransport { public class JingleSocks5Transport extends JingleTransport {
private JingleCandidate candidate; private JingleCandidate candidate;
@ -40,7 +41,12 @@ public class JingleSocks5Transport extends JingleTransport {
try { try {
MessageDigest mDigest = MessageDigest.getInstance("SHA-1"); MessageDigest mDigest = MessageDigest.getInstance("SHA-1");
StringBuilder destBuilder = new StringBuilder(); StringBuilder destBuilder = new StringBuilder();
destBuilder.append(jingleConnection.getSessionId()); if (jingleConnection.getFtVersion() == Content.Version.FT_3) {
Log.d(Config.LOGTAG,this.connection.getAccount().getJid().toBareJid()+": using session Id instead of transport Id for proxy destination");
destBuilder.append(jingleConnection.getSessionId());
} else {
destBuilder.append(jingleConnection.getTransportId());
}
if (candidate.isOurs()) { if (candidate.isOurs()) {
destBuilder.append(jingleConnection.getAccount().getJid()); destBuilder.append(jingleConnection.getAccount().getJid());
destBuilder.append(jingleConnection.getCounterPart()); destBuilder.append(jingleConnection.getCounterPart());

View file

@ -5,12 +5,23 @@ import eu.siacs.conversations.xml.Element;
public class Content extends Element { public class Content extends Element {
private String transportId; public enum Version {
FT_3("urn:xmpp:jingle:apps:file-transfer:3"),
FT_4("urn:xmpp:jingle:apps:file-transfer:4");
private Content(String name) { private final String namespace;
super(name);
Version(String namespace) {
this.namespace = namespace;
}
public String getNamespace() {
return namespace;
}
} }
private String transportId;
public Content() { public Content() {
super("content"); super("content");
} }
@ -21,15 +32,28 @@ public class Content extends Element {
this.setAttribute("name", name); this.setAttribute("name", name);
} }
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;
}
return null;
}
public void setTransportId(String sid) { public void setTransportId(String sid) {
this.transportId = sid; this.transportId = sid;
} }
public Element setFileOffer(DownloadableFile actualFile, boolean otr) { public Element setFileOffer(DownloadableFile actualFile, boolean otr, Version version) {
Element description = this.addChild("description", Element description = this.addChild("description", version.namespace);
"urn:xmpp:jingle:apps:file-transfer:3"); Element file;
Element offer = description.addChild("offer"); if (version == Version.FT_3) {
Element file = offer.addChild("file"); Element offer = description.addChild("offer");
file = offer.addChild("file");
} else {
file = description.addChild("file");
}
file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize())); file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize()));
if (otr) { if (otr) {
file.addChild("name").setContent(actualFile.getName() + ".otr"); file.addChild("name").setContent(actualFile.getName() + ".otr");
@ -39,27 +63,29 @@ public class Content extends Element {
return file; return file;
} }
public Element getFileOffer() { public Element getFileOffer(Version version) {
Element description = this.findChild("description", Element description = this.findChild("description", version.namespace);
"urn:xmpp:jingle:apps:file-transfer:3");
if (description == null) { if (description == null) {
return null; return null;
} }
Element offer = description.findChild("offer"); if (version == Version.FT_3) {
if (offer == null) { Element offer = description.findChild("offer");
return null; if (offer == null) {
return null;
}
return offer.findChild("file");
} else {
return description.findChild("file");
} }
return offer.findChild("file");
} }
public void setFileOffer(Element fileOffer) { public void setFileOffer(Element fileOffer, Version version) {
Element description = this.findChild("description", Element description = this.addChild("description", version.namespace);
"urn:xmpp:jingle:apps:file-transfer:3"); if (version == Version.FT_3) {
if (description == null) { description.addChild("offer").addChild(fileOffer);
description = this.addChild("description", } else {
"urn:xmpp:jingle:apps:file-transfer:3"); description.addChild(fileOffer);
} }
description.addChild(fileOffer);
} }
public String getTransportId() { public String getTransportId() {