diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java b/src/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java new file mode 100644 index 000000000..80ffeaaac --- /dev/null +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java @@ -0,0 +1,138 @@ +package eu.siacs.conversations.xmpp.jingle; + +import java.util.ArrayList; +import java.util.List; + +import eu.siacs.conversations.xml.Element; + +public class JingleCandidate { + + public static int TYPE_UNKNOWN; + public static int TYPE_DIRECT = 0; + public static int TYPE_PROXY = 1; + + private boolean ours; + private boolean usedByCounterpart = false; + private String cid; + private String host; + private int port; + private int type; + private String jid; + private int priority; + + public JingleCandidate(String cid,boolean ours) { + this.ours = ours; + this.cid = cid; + } + + public String getCid() { + return cid; + } + + public void setHost(String host) { + this.host = host; + } + + public String getHost() { + return this.host; + } + + public void setJid(String jid) { + this.jid = jid; + } + + public String getJid() { + return this.jid; + } + + public void setPort(int port) { + this.port = port; + } + + public int getPort() { + return this.port; + } + + public void setType(int type) { + this.type = type; + } + + public void setType(String type) { + if ("proxy".equals(type)) { + this.type = TYPE_PROXY; + } else if ("direct".equals(type)) { + this.type = TYPE_DIRECT; + } else { + this.type = TYPE_UNKNOWN; + } + } + + public void setPriority(int i) { + this.priority = i; + } + + public int getPriority() { + return this.priority; + } + + public boolean equals(JingleCandidate other) { + return this.getCid().equals(other.getCid()); + } + + public boolean equalValues(JingleCandidate other) { + return other.getHost().equals(this.getHost())&&(other.getPort()==this.getPort()); + } + + public boolean isOurs() { + return ours; + } + + public int getType() { + return this.type; + } + + public static List parse(List canditates) { + List parsedCandidates = new ArrayList(); + for(Element c : canditates) { + parsedCandidates.add(JingleCandidate.parse(c)); + } + return parsedCandidates; + } + + public static JingleCandidate parse(Element candidate) { + JingleCandidate parsedCandidate = new JingleCandidate(candidate.getAttribute("cid"), false); + parsedCandidate.setHost(candidate.getAttribute("host")); + parsedCandidate.setJid(candidate.getAttribute("jid")); + parsedCandidate.setType(candidate.getAttribute("type")); + parsedCandidate.setPriority(Integer.parseInt(candidate.getAttribute("priority"))); + parsedCandidate.setPort(Integer.parseInt(candidate.getAttribute("port"))); + return parsedCandidate; + } + + public Element toElement() { + Element element = new Element("candidate"); + element.setAttribute("cid", this.getCid()); + element.setAttribute("host", this.getHost()); + element.setAttribute("port", ""+this.getPort()); + element.setAttribute("jid", this.getJid()); + element.setAttribute("priority",""+this.getPriority()); + if (this.getType()==TYPE_DIRECT) { + element.setAttribute("type","direct"); + } else if (this.getType()==TYPE_PROXY) { + element.setAttribute("type","proxy"); + } + return element; + } + + public void flagAsUsedByCounterpart() { + this.usedByCounterpart = true; + } + + public boolean isUsedByCounterpart() { + return this.usedByCounterpart; + } + + public String toString() { + return this.getHost()+":"+this.getPort()+" (prio="+this.getPriority()+")"; + } +} diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index 75c2c8e07..a7fd367d0 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -38,12 +38,15 @@ public class JingleConnection { private Account account; private String initiator; private String responder; - private List candidates = new ArrayList(); - private List candidatesUsedByCounterpart = new ArrayList(); + private List candidates = new ArrayList(); private HashMap connections = new HashMap(); - private Content content = new Content(); + + private String transportId; + private Element fileOffer; private JingleFile file = null; + private boolean receivedCandidateError = false; + private OnIqPacketReceived responseListener = new OnIqPacketReceived() { @Override @@ -107,10 +110,11 @@ public class JingleConnection { this.mJingleConnectionManager.getPrimaryCandidate(account, new OnPrimaryCandidateFound() { @Override - public void onPrimaryCandidateFound(boolean success, Element candidate) { + public void onPrimaryCandidateFound(boolean success, JingleCandidate candidate) { if (success) { mergeCandidate(candidate); } + openOurCandidates(); sendInitRequest(); } }); @@ -130,9 +134,10 @@ public class JingleConnection { this.initiator = packet.getFrom(); this.responder = this.account.getFullJid(); this.sessionId = packet.getSessionId(); - this.content = packet.getJingleContent(); - this.mergeCandidates(this.content.getCanditates()); - Element fileOffer = packet.getJingleContent().getFileOffer(); + Content content = packet.getJingleContent(); + this.transportId = content.getTransportId(); + this.mergeCandidates(JingleCandidate.parse(content.getCanditates())); + this.fileOffer = packet.getJingleContent().getFileOffer(); if (fileOffer!=null) { this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message); Element fileSize = fileOffer.findChild("size"); @@ -156,13 +161,14 @@ public class JingleConnection { private void sendInitRequest() { JinglePacket packet = this.bootstrapPacket("session-initiate"); - this.content = new Content(); + Content content = new Content(); if (message.getType() == Message.TYPE_IMAGE) { content.setAttribute("creator", "initiator"); content.setAttribute("name", "a-file-offer"); this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message); content.setFileOffer(this.file); - content.setCandidates(this.mJingleConnectionManager.nextRandomId(),this.candidates); + this.transportId = this.mJingleConnectionManager.nextRandomId(); + content.setCandidates(this.transportId,getCandidatesAsElements()); packet.setContent(content); Log.d("xmppService",packet.toString()); account.getXmppConnection().sendIqPacket(packet, this.responseListener); @@ -170,17 +176,28 @@ public class JingleConnection { } } + private List getCandidatesAsElements() { + List elements = new ArrayList(); + for(JingleCandidate c : this.candidates) { + elements.add(c.toElement()); + } + return elements; + } + private void sendAccept() { this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() { @Override - public void onPrimaryCandidateFound(boolean success, Element candidate) { + public void onPrimaryCandidateFound(boolean success, JingleCandidate candidate) { + Content content = new Content(); + content.setFileOffer(fileOffer); if (success) { if (!equalCandidateExists(candidate)) { mergeCandidate(candidate); - content.addCandidate(candidate); } } + openOurCandidates(); + content.setCandidates(transportId, getCandidatesAsElements()); JinglePacket packet = bootstrapPacket("session-accept"); packet.setContent(content); account.getXmppConnection().sendIqPacket(packet, new OnIqPacketReceived() { @@ -211,7 +228,7 @@ public class JingleConnection { private void accept(JinglePacket packet) { Log.d("xmppService","session-accept: "+packet.toString()); Content content = packet.getJingleContent(); - mergeCandidates(content.getCanditates()); + mergeCandidates(JingleCandidate.parse(content.getCanditates())); this.status = STATUS_ACCEPTED; this.connectNextCandidate(); IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT); @@ -224,26 +241,25 @@ public class JingleConnection { IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT); if (cid!=null) { Log.d("xmppService","candidate used by counterpart:"+cid); - this.candidatesUsedByCounterpart.add(cid); - if (this.connections.containsKey(cid)) { - SocksConnection connection = this.connections.get(cid); - if (connection.isEstablished()) { - if (status==STATUS_ACCEPTED) { - this.connect(connection); - } else { - Log.d("xmppService","ignoring canditate used because we are already transmitting"); - } - } else { - Log.d("xmppService","not yet connected. check when callback comes back"); - } + JingleCandidate candidate = getCandidate(cid); + candidate.flagAsUsedByCounterpart(); + if (status == STATUS_ACCEPTED) { + this.connect(); } else { - Log.d("xmppService","candidate not yet in list of connections"); + Log.d("xmppService","ignoring because file is already in transmission"); + } + } else if (content.hasCandidateError()) { + Log.d("xmppService","received candidate error"); + this.receivedCandidateError = true; + if (status == STATUS_ACCEPTED) { + this.connect(); } } account.getXmppConnection().sendIqPacket(response, null); } - private void connect(final SocksConnection connection) { + private void connect() { + final SocksConnection connection = chooseConnection(); this.status = STATUS_TRANSMITTING; final OnFileTransmitted callback = new OnFileTransmitted() { @@ -256,10 +272,10 @@ public class JingleConnection { Log.d("xmppService","sucessfully transmitted file. sha1:"+file.getSha1Sum()); } }; - if ((connection.isProxy()&&(connection.getCid().equals(mJingleConnectionManager.getPrimaryCandidateId(account))))) { - Log.d("xmppService","candidate "+connection.getCid()+" was our proxy and needs activation"); + if (connection.isProxy()&&(connection.getCandidate().isOurs())) { + Log.d("xmppService","candidate "+connection.getCandidate().getCid()+" was our proxy and needs activation"); IqPacket activation = new IqPacket(IqPacket.TYPE_SET); - activation.setTo(connection.getJid()); + activation.setTo(connection.getCandidate().getJid()); activation.query("http://jabber.org/protocol/bytestreams").setAttribute("sid", this.getSessionId()); activation.query().addChild("activate").setContent(this.getCounterPart()); this.account.getXmppConnection().sendIqPacket(activation, new OnIqPacketReceived() { @@ -287,6 +303,41 @@ public class JingleConnection { } } + private SocksConnection chooseConnection() { + Log.d("xmppService","choosing connection from "+this.connections.size()+" possibilties"); + SocksConnection connection = null; + Iterator> it = this.connections.entrySet().iterator(); + while (it.hasNext()) { + Entry pairs = it.next(); + SocksConnection currentConnection = pairs.getValue(); + Log.d("xmppService","comparing candidate: "+currentConnection.getCandidate().toString()); + if (currentConnection.isEstablished()&&(currentConnection.getCandidate().isUsedByCounterpart()||(!currentConnection.getCandidate().isOurs()))) { + Log.d("xmppService","is usable"); + if (connection==null) { + connection = currentConnection; + } else { + if (connection.getCandidate().getPriority() candidates) { - for(Element c : candidates) { + private void mergeCandidates(List candidates) { + for(JingleCandidate c : candidates) { mergeCandidate(c); } } - private Element getCandidate(String cid) { - for(Element c : this.candidates) { - if (c.getAttribute("cid").equals(cid)) { + private JingleCandidate getCandidate(String cid) { + for(JingleCandidate c : this.candidates) { + if (c.getCid().equals(cid)) { return c; } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 924674a7a..44af51a34 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -23,7 +23,7 @@ public class JingleConnectionManager { private List connections = new ArrayList(); // make // concurrent - private ConcurrentHashMap primaryCandidates = new ConcurrentHashMap(); + private ConcurrentHashMap primaryCandidates = new ConcurrentHashMap(); private SecureRandom random = new SecureRandom(); @@ -89,17 +89,12 @@ public class JingleConnectionManager { if (streamhost != null) { Log.d("xmppService", "streamhost found " + streamhost.toString()); - Element candidate = new Element("candidate"); - candidate.setAttribute("cid", - nextRandomId()); - candidate.setAttribute("host", - streamhost.getAttribute("host")); - candidate.setAttribute("port", - streamhost.getAttribute("port")); - candidate.setAttribute("type", "proxy"); - candidate.setAttribute("jid", proxy); - candidate - .setAttribute("priority", "655360"); + JingleCandidate candidate = new JingleCandidate(nextRandomId(),true); + candidate.setHost(streamhost.getAttribute("host")); + candidate.setPort(Integer.parseInt(streamhost.getAttribute("port"))); + candidate.setType(JingleCandidate.TYPE_PROXY); + candidate.setJid(proxy); + candidate.setPriority(655360+65535); primaryCandidates.put(account.getJid(), candidate); listener.onPrimaryCandidateFound(true, @@ -119,14 +114,6 @@ public class JingleConnectionManager { this.primaryCandidates.get(account.getJid())); } } - - public String getPrimaryCandidateId(Account account) { - if (this.primaryCandidates.containsKey(account.getJid())) { - return this.primaryCandidates.get(account.getJid()).getAttribute("cid"); - } else { - return null; - } - } public String nextRandomId() { return new BigInteger(50, random).toString(32); diff --git a/src/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java b/src/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java index 9e00954ff..b91a90ff5 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java +++ b/src/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java @@ -1,7 +1,5 @@ package eu.siacs.conversations.xmpp.jingle; -import eu.siacs.conversations.xml.Element; - public interface OnPrimaryCandidateFound { - public void onPrimaryCandidateFound(boolean success, Element canditate); + public void onPrimaryCandidateFound(boolean success, JingleCandidate canditate); } diff --git a/src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java b/src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java index affbb5f3c..197b9424c 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java +++ b/src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java @@ -19,29 +19,20 @@ import android.util.Log; import android.widget.Button; public class SocksConnection { - private Socket socket; - private String host; - private String jid; - private String cid; - private int port; - private boolean isProxy = false; + private JingleCandidate candidate; private String destination; private OutputStream outputStream; private InputStream inputStream; private boolean isEstablished = false; + protected Socket socket; - public SocksConnection(JingleConnection jingleConnection, Element candidate, boolean initating) { - this.cid = candidate.getAttribute("cid"); - this.host = candidate.getAttribute("host"); - this.port = Integer.parseInt(candidate.getAttribute("port")); - String type = candidate.getAttribute("type"); - this.jid = candidate.getAttribute("jid"); - this.isProxy = "proxy".equalsIgnoreCase(type); + public SocksConnection(JingleConnection jingleConnection, JingleCandidate candidate) { + this.candidate = candidate; try { MessageDigest mDigest = MessageDigest.getInstance("SHA-1"); StringBuilder destBuilder = new StringBuilder(); destBuilder.append(jingleConnection.getSessionId()); - if (initating) { + if (candidate.isOurs()) { destBuilder.append(jingleConnection.getAccountJid()); destBuilder.append(jingleConnection.getCounterPart()); } else { @@ -62,7 +53,7 @@ public class SocksConnection { @Override public void run() { try { - socket = new Socket(host, port); + socket = new Socket(candidate.getHost(), candidate.getPort()); inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); byte[] login = { 0x05, 0x01, 0x00 }; @@ -78,8 +69,7 @@ public class SocksConnection { inputStream.read(result); int status = result[1]; if (status == 0) { - Log.d("xmppService", "established connection with "+host + ":" + port - + "/" + destination); + Log.d("xmppService", "established connection with "+candidate.getHost()+":"+candidate.getPort()+ "/" + destination); isEstablished = true; callback.established(); } else { @@ -193,24 +183,16 @@ public class SocksConnection { } public boolean isProxy() { - return this.isProxy; - } - - public String getJid() { - return this.jid; - } - - public String getCid() { - return this.cid; + return this.candidate.getType() == JingleCandidate.TYPE_PROXY; } public void disconnect() { if (this.socket!=null) { try { this.socket.close(); - Log.d("xmppService","cloesd socket with "+this.host); + Log.d("xmppService","cloesd socket with "+candidate.getHost()+":"+candidate.getPort()); } catch (IOException e) { - Log.d("xmppService","error closing socket with "+this.host); + Log.d("xmppService","error closing socket with "+candidate.getHost()+":"+candidate.getPort()); } } } @@ -218,4 +200,8 @@ public class SocksConnection { public boolean isEstablished() { return this.isEstablished; } + + public JingleCandidate getCandidate() { + return this.candidate; + } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java index 79e04610a..3cd302516 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java +++ b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java @@ -77,6 +77,14 @@ public class Content extends Element { } } + public boolean hasCandidateError() { + Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1"); + if (transport==null) { + return false; + } + return transport.hasChild("candidate-error"); + } + public void setUsedCandidate(String transportId, String cid) { Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1"); if (transport==null) { @@ -95,4 +103,22 @@ public class Content extends Element { } transport.addChild(candidate); } + + public void setFileOffer(Element fileOffer) { + Element description = this.findChild("description", "urn:xmpp:jingle:apps:file-transfer:3"); + if (description==null) { + description = this.addChild("description", "urn:xmpp:jingle:apps:file-transfer:3"); + } + description.addChild(fileOffer); + } + + public void setCandidateError(String transportId) { + Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1"); + if (transport==null) { + transport = this.addChild("transport", "urn:xmpp:jingle:transports:s5b:1"); + } + transport.setAttribute("sid", transportId); + transport.clearChildren(); + transport.addChild("candidate-error"); + } }