lot of cleanup in jingle part

Daniel Gultsch created

Change summary

src/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java         | 138 
src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java        | 209 
src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java |  27 
src/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java |   4 
src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java         |  42 
src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java         |  26 
6 files changed, 326 insertions(+), 120 deletions(-)

Detailed changes

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<JingleCandidate> parse(List<Element> canditates) {
+		List<JingleCandidate> parsedCandidates = new ArrayList<JingleCandidate>();
+		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()+")";
+	}
+}

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<Element> candidates = new ArrayList<Element>();
-	private List<String> candidatesUsedByCounterpart = new ArrayList<String>();
+	private List<JingleCandidate> candidates = new ArrayList<JingleCandidate>();
 	private HashMap<String, SocksConnection> connections = new HashMap<String, SocksConnection>();
-	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<Element> getCandidatesAsElements() {
+		List<Element> elements = new ArrayList<Element>();
+		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<Entry<String, SocksConnection>> it = this.connections.entrySet().iterator();
+	    while (it.hasNext()) {
+	    	Entry<String, SocksConnection> 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()<currentConnection.getCandidate().getPriority()) {
+	        			connection = currentConnection;
+	        		} else if (connection.getCandidate().getPriority()==currentConnection.getCandidate().getPriority()) {
+	        			Log.d("xmppService","found two candidates with same priority");
+	        			if (initiator.equals(account.getFullJid())) {
+	        				if (currentConnection.getCandidate().isOurs()) {
+	        					connection = currentConnection;
+	        				}
+	        			} else {
+	        				if (!currentConnection.getCandidate().isOurs()) {
+	        					connection = currentConnection;
+	        				}
+	        			}
+	        		}
+	        	}
+	        }
+	        it.remove();
+	    }
+	    Log.d("xmppService","chose candidate: "+connection.getCandidate().getHost());
+		return connection;
+	}
+
 	private void sendSuccess() {
 		JinglePacket packet = bootstrapPacket("session-terminate");
 		Reason reason = new Reason();
@@ -311,20 +362,40 @@ public class JingleConnection {
 		this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_REJECTED);
 	}
 	
+	private void openOurCandidates() {
+		for(JingleCandidate candidate : this.candidates) {
+			if (candidate.isOurs()) {
+				final SocksConnection socksConnection = new SocksConnection(this,candidate);
+				connections.put(candidate.getCid(), socksConnection);
+				socksConnection.connect(new OnSocksConnection() {
+					
+					@Override
+					public void failed() {
+						Log.d("xmppService","connection to our candidate failed");
+					}
+					
+					@Override
+					public void established() {
+						Log.d("xmppService","connection to our candidate was successful");
+					}
+				});
+			}
+		}
+	}
+	
 	private void connectNextCandidate() {
-		for(Element candidate : this.candidates) {
-			String cid = candidate.getAttribute("cid");
-			if (!connections.containsKey(cid)) {
+		for(JingleCandidate candidate : this.candidates) {
+			if ((!connections.containsKey(candidate.getCid())&&(!candidate.isOurs()))) {
 				this.connectWithCandidate(candidate);
-				break;
+				return;
 			}
 		}
+		this.sendCandidateError();
 	}
 	
-	private void connectWithCandidate(Element candidate) {
-		boolean initating = candidate.getAttribute("cid").equals(mJingleConnectionManager.getPrimaryCandidateId(account));
-		final SocksConnection socksConnection = new SocksConnection(this,candidate,initating);
-		connections.put(socksConnection.getCid(), socksConnection);
+	private void connectWithCandidate(final JingleCandidate candidate) {
+		final SocksConnection socksConnection = new SocksConnection(this,candidate);
+		connections.put(candidate.getCid(), socksConnection);
 		socksConnection.connect(new OnSocksConnection() {
 			
 			@Override
@@ -334,14 +405,10 @@ public class JingleConnection {
 			
 			@Override
 			public void established() {
-				if (candidatesUsedByCounterpart.contains(socksConnection.getCid())) {
-					if (status==STATUS_ACCEPTED) {
-						connect(socksConnection);
-					} else {
-						Log.d("xmppService","ignoring cuz already transmitting");
-					}
-				} else {
-					sendCandidateUsed(socksConnection.getCid());
+				sendCandidateUsed(candidate.getCid());
+				if ((receivedCandidateError)&&(status == STATUS_ACCEPTED)) {
+					Log.d("xmppService","received candidate error before. trying to connect");
+					connect();
 				}
 			}
 		});
@@ -359,21 +426,25 @@ public class JingleConnection {
 	private void sendCandidateUsed(final String cid) {
 		JinglePacket packet = bootstrapPacket("transport-info");
 		Content content = new Content();
-		content.setUsedCandidate(this.content.getTransportId(), cid);
+		//TODO: put these into actual variables
+		content.setAttribute("creator", "initiator");
+		content.setAttribute("name", "a-file-offer");
+		content.setUsedCandidate(this.transportId, cid);
 		packet.setContent(content);
 		Log.d("xmppService","send using candidate: "+cid);
-		this.account.getXmppConnection().sendIqPacket(packet, new OnIqPacketReceived() {
-			
-			@Override
-			public void onIqPacketReceived(Account account, IqPacket packet) {
-				Log.d("xmppService","got ack for our candidate used");
-				if (status==STATUS_ACCEPTED) {
-					connect(connections.get(cid));
-				} else {
-					Log.d("xmppService","ignoring cuz already transmitting");
-				}
-			}
-		});
+		this.account.getXmppConnection().sendIqPacket(packet,responseListener);
+	}
+	
+	private void sendCandidateError() {
+		JinglePacket packet = bootstrapPacket("transport-info");
+		Content content = new Content();
+		//TODO: put these into actual variables
+		content.setAttribute("creator", "initiator");
+		content.setAttribute("name", "a-file-offer");
+		content.setCandidateError(this.transportId);
+		packet.setContent(content);
+		Log.d("xmppService","send candidate error");
+		this.account.getXmppConnection().sendIqPacket(packet,responseListener);
 	}
 
 	public String getInitiator() {
@@ -388,33 +459,33 @@ public class JingleConnection {
 		return this.status;
 	}
 	
-	private boolean equalCandidateExists(Element candidate) {
-		for(Element c : this.candidates) {
-			if (c.getAttribute("host").equals(candidate.getAttribute("host"))&&(c.getAttribute("port").equals(candidate.getAttribute("port")))) {
+	private boolean equalCandidateExists(JingleCandidate candidate) {
+		for(JingleCandidate c : this.candidates) {
+			if (c.equalValues(candidate)) {
 				return true;
 			}
 		}
 		return false;
 	}
 	
-	private void mergeCandidate(Element candidate) {
-		for(Element c : this.candidates) {
-			if (c.getAttribute("cid").equals(candidate.getAttribute("cid"))) {
+	private void mergeCandidate(JingleCandidate candidate) {
+		for(JingleCandidate c : this.candidates) {
+			if (c.equals(candidate)) {
 				return;
 			}
 		}
 		this.candidates.add(candidate);
 	}
 	
-	private void mergeCandidates(List<Element> candidates) {
-		for(Element c : candidates) {
+	private void mergeCandidates(List<JingleCandidate> 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;
 			}
 		}

src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java 🔗

@@ -23,7 +23,7 @@ public class JingleConnectionManager {
 	private List<JingleConnection> connections = new ArrayList<JingleConnection>(); // make
 																					// concurrent
 
-	private ConcurrentHashMap<String, Element> primaryCandidates = new ConcurrentHashMap<String, Element>();
+	private ConcurrentHashMap<String, JingleCandidate> primaryCandidates = new ConcurrentHashMap<String, JingleCandidate>();
 
 	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);

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);
 }

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;
+	}
 }

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");
+	}
 }