JingleConnection.java

  1package eu.siacs.conversations.xmpp.jingle;
  2
  3import java.util.ArrayList;
  4import java.util.HashMap;
  5import java.util.Iterator;
  6import java.util.List;
  7import java.util.Map.Entry;
  8
  9import android.util.Log;
 10
 11import eu.siacs.conversations.entities.Account;
 12import eu.siacs.conversations.entities.Conversation;
 13import eu.siacs.conversations.entities.Message;
 14import eu.siacs.conversations.services.XmppConnectionService;
 15import eu.siacs.conversations.xml.Element;
 16import eu.siacs.conversations.xmpp.OnIqPacketReceived;
 17import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
 18import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
 19import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
 20import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 21
 22public class JingleConnection {
 23
 24	private JingleConnectionManager mJingleConnectionManager;
 25	private XmppConnectionService mXmppConnectionService;
 26	
 27	public static final int STATUS_INITIATED = 0;
 28	public static final int STATUS_ACCEPTED = 1;
 29	public static final int STATUS_TERMINATED = 2;
 30	public static final int STATUS_CANCELED = 3;
 31	public static final int STATUS_FINISHED = 4;
 32	public static final int STATUS_FAILED = 99;
 33	
 34	private int status = -1;
 35	private Message message;
 36	private String sessionId;
 37	private Account account;
 38	private String initiator;
 39	private String responder;
 40	private List<Element> candidates = new ArrayList<Element>();
 41	private List<String> candidatesUsedByCounterpart = new ArrayList<String>();
 42	private HashMap<String, SocksConnection> connections = new HashMap<String, SocksConnection>();
 43	private Content content = new Content();
 44	private JingleFile file = null;
 45	
 46	private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
 47		
 48		@Override
 49		public void onIqPacketReceived(Account account, IqPacket packet) {
 50			if (packet.getType() == IqPacket.TYPE_ERROR) {
 51				mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
 52				status = STATUS_FAILED;
 53			}
 54		}
 55	};
 56	
 57	public JingleConnection(JingleConnectionManager mJingleConnectionManager) {
 58		this.mJingleConnectionManager = mJingleConnectionManager;
 59		this.mXmppConnectionService = mJingleConnectionManager.getXmppConnectionService();
 60	}
 61	
 62	public String getSessionId() {
 63		return this.sessionId;
 64	}
 65	
 66	public String getAccountJid() {
 67		return this.account.getJid();
 68	}
 69	
 70	public String getCounterPart() {
 71		return this.message.getCounterpart();
 72	}
 73	
 74	public void deliverPacket(JinglePacket packet) {
 75		
 76		if (packet.isAction("session-terminate")) {
 77			Reason reason = packet.getReason();
 78			if (reason.hasChild("cancel")) {
 79				this.cancel();
 80			} else if (reason.hasChild("success")) {
 81				this.finish();
 82			}
 83		} else if (packet.isAction("session-accept")) {
 84			accept(packet);
 85		} else if (packet.isAction("transport-info")) {
 86			transportInfo(packet);
 87		} else {
 88			Log.d("xmppService","packet arrived in connection. action was "+packet.getAction());
 89		}
 90	}
 91	
 92	public void init(Message message) {
 93		this.message = message;
 94		this.account = message.getConversation().getAccount();
 95		this.initiator = this.account.getFullJid();
 96		this.responder = this.message.getCounterpart();
 97		this.sessionId = this.mJingleConnectionManager.nextRandomId();
 98		if (this.candidates.size() > 0) {
 99			this.sendInitRequest();
100		} else {
101			this.mJingleConnectionManager.getPrimaryCandidate(account, new OnPrimaryCandidateFound() {
102				
103				@Override
104				public void onPrimaryCandidateFound(boolean success, Element candidate) {
105					if (success) {
106						mergeCandidate(candidate);
107					}
108					sendInitRequest();
109				}
110			});
111		}
112		
113	}
114	
115	public void init(Account account, JinglePacket packet) {
116		Conversation conversation = this.mXmppConnectionService.findOrCreateConversation(account, packet.getFrom().split("/")[0], false);
117		this.message = new Message(conversation, "receiving image file", Message.ENCRYPTION_NONE);
118		this.message.setType(Message.TYPE_IMAGE);
119		this.message.setStatus(Message.STATUS_RECIEVING);
120		String[] fromParts = packet.getFrom().split("/");
121		this.message.setPresence(fromParts[1]);
122		this.account = account;
123		this.initiator = packet.getFrom();
124		this.responder = this.account.getFullJid();
125		this.sessionId = packet.getSessionId();
126		this.content = packet.getJingleContent();
127		this.mergeCandidates(this.content.getCanditates());
128		Element fileOffer = packet.getJingleContent().getFileOffer();
129		if (fileOffer!=null) {
130			this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message);
131			Element fileSize = fileOffer.findChild("size");
132			Element fileName = fileOffer.findChild("name");
133			this.file.setExpectedSize(Long.parseLong(fileSize.getContent()));
134			if (this.file.getExpectedSize()>=this.mJingleConnectionManager.getAutoAcceptFileSize()) {
135				Log.d("xmppService","auto accepting file from "+packet.getFrom());
136				this.sendAccept();
137			} else {
138				Log.d("xmppService","not auto accepting new file offer with size: "+this.file.getExpectedSize()+" allowed size:"+this.mJingleConnectionManager.getAutoAcceptFileSize());
139			}
140		} else {
141			Log.d("xmppService","no file offer was attached. aborting");
142		}
143	}
144	
145	private void sendInitRequest() {
146		JinglePacket packet = this.bootstrapPacket();
147		packet.setAction("session-initiate");
148		this.content = new Content();
149		if (message.getType() == Message.TYPE_IMAGE) {
150			content.setAttribute("creator", "initiator");
151			content.setAttribute("name", "a-file-offer");
152			this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message);
153			content.setFileOffer(this.file);
154			content.setCandidates(this.mJingleConnectionManager.nextRandomId(),this.candidates);
155			packet.setContent(content);
156			Log.d("xmppService",packet.toString());
157			account.getXmppConnection().sendIqPacket(packet, this.responseListener);
158			this.status = STATUS_INITIATED;
159		}
160	}
161	
162	private void sendAccept() {
163		this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() {
164			
165			@Override
166			public void onPrimaryCandidateFound(boolean success, Element candidate) {
167				if (success) {
168					if (mergeCandidate(candidate)) {
169						content.addCandidate(candidate);
170					}
171				}
172				JinglePacket packet = bootstrapPacket();
173				packet.setAction("session-accept");
174				packet.setContent(content);
175				Log.d("xmppService","sending session accept: "+packet.toString());
176				account.getXmppConnection().sendIqPacket(packet, new OnIqPacketReceived() {
177					
178					@Override
179					public void onIqPacketReceived(Account account, IqPacket packet) {
180						if (packet.getType() != IqPacket.TYPE_ERROR) {
181							Log.d("xmppService","opsing side has acked our session-accept");
182							connectWithCandidates();
183						}
184					}
185				});
186			}
187		});
188		
189	}
190	
191	private JinglePacket bootstrapPacket() {
192		JinglePacket packet = new JinglePacket();
193		packet.setFrom(account.getFullJid());
194		packet.setTo(this.message.getCounterpart()); //fixme, not right in all cases;
195		packet.setSessionId(this.sessionId);
196		packet.setInitiator(this.initiator);
197		return packet;
198	}
199	
200	private void accept(JinglePacket packet) {
201		Log.d("xmppService","session-accept: "+packet.toString());
202		Content content = packet.getJingleContent();
203		this.mergeCandidates(content.getCanditates());
204		this.status = STATUS_ACCEPTED;
205		this.connectWithCandidates();
206		IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
207		account.getXmppConnection().sendIqPacket(response, null);
208	}
209
210	private void transportInfo(JinglePacket packet) {
211		Content content = packet.getJingleContent();
212		Log.d("xmppService","transport info : "+content.toString());
213		String cid = content.getUsedCandidate();
214		if (cid!=null) {
215			Log.d("xmppService","candidate used by counterpart:"+cid);
216			this.candidatesUsedByCounterpart.add(cid);
217			if (this.connections.containsKey(cid)) {
218				this.connect(this.connections.get(cid));
219			}
220		}
221		IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
222		account.getXmppConnection().sendIqPacket(response, null);
223	}
224
225	private void connect(final SocksConnection connection) {
226		final OnFileTransmitted callback = new OnFileTransmitted() {
227			
228			@Override
229			public void onFileTransmitted(JingleFile file) {
230				Log.d("xmppService","sucessfully transmitted file. sha1:"+file.getSha1Sum());
231			}
232		};
233		if (connection.isProxy()) {
234			IqPacket activation = new IqPacket(IqPacket.TYPE_SET);
235			activation.setTo(connection.getJid());
236			activation.query("http://jabber.org/protocol/bytestreams").setAttribute("sid", this.getSessionId());
237			activation.query().addChild("activate").setContent(this.getResponder());
238			Log.d("xmppService","connection is proxy. need to activate "+activation.toString());
239			this.account.getXmppConnection().sendIqPacket(activation, new OnIqPacketReceived() {
240				
241				@Override
242				public void onIqPacketReceived(Account account, IqPacket packet) {
243					Log.d("xmppService","activation result: "+packet.toString());
244					if (initiator.equals(account.getFullJid())) {
245						Log.d("xmppService","we were initiating. sending file");
246						connection.send(file,callback);
247					} else {
248						Log.d("xmppService","we were responding. receiving file");
249					}
250					
251				}
252			});
253		} else {
254			if (initiator.equals(account.getFullJid())) {
255				Log.d("xmppService","we were initiating. sending file");
256				connection.send(file,callback);
257			} else {
258				Log.d("xmppService","we were responding. receiving file");
259			}
260		}
261	}
262	
263	private void finish() {
264		this.status = STATUS_FINISHED;
265		this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND);
266		this.disconnect();
267	}
268	
269	public void cancel() {
270		this.disconnect();
271		this.status = STATUS_CANCELED;
272		this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_REJECTED);
273	}
274	
275	private void connectWithCandidates() {
276		for(Element canditate : this.candidates) {
277			
278			String host = canditate.getAttribute("host");
279			int port = Integer.parseInt(canditate.getAttribute("port"));
280			String type = canditate.getAttribute("type");
281			String jid = canditate.getAttribute("jid");
282			SocksConnection socksConnection = new SocksConnection(this, host, jid, port,type);
283			connections.put(canditate.getAttribute("cid"), socksConnection);
284			socksConnection.connect(new OnSocksConnection() {
285				
286				@Override
287				public void failed() {
288					Log.d("xmppService","socks5 failed");
289				}
290				
291				@Override
292				public void established() {
293					Log.d("xmppService","established socks5");
294				}
295			});
296		}
297	}
298	
299	private void disconnect() {
300		Iterator<Entry<String, SocksConnection>> it = this.connections.entrySet().iterator();
301	    while (it.hasNext()) {
302	        Entry<String, SocksConnection> pairs = it.next();
303	        pairs.getValue().disconnect();
304	        it.remove();
305	    }
306	}
307	
308	private void sendCandidateUsed(String cid) {
309		
310	}
311
312	public String getInitiator() {
313		return this.initiator;
314	}
315	
316	public String getResponder() {
317		return this.responder;
318	}
319	
320	public int getStatus() {
321		return this.status;
322	}
323	
324	private boolean mergeCandidate(Element candidate) {
325		for(Element c : this.candidates) {
326			if (c.getAttribute("host").equals(candidate.getAttribute("host"))&&(c.getAttribute("port").equals(candidate.getAttribute("port")))) {
327				return false;
328			}
329		}
330		this.candidates.add(candidate);
331		return true;
332	}
333	
334	private void mergeCandidates(List<Element> canditates) {
335		for(Element c : canditates) {
336			this.mergeCandidate(c);
337		}
338	}
339}