1 ;; jabber-tox.el - Jingle support using TOX
3 ;; Copyright (C) 2008 - Magnus Henoch - mange@freemail.hu
5 ;; This file is (soon) a part of jabber.el.
7 ;; This program is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation; either version 2 of the License, or
10 ;; (at your option) any later version.
12 ;; This program is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program; if not, write to the Free Software
19 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 (defconst jingle-ns
"urn:xmpp:jingle:0"
26 "Jingle namespace (XEP-0166)")
28 (defconst jingle-error-ns
"urn:xmpp:jingle:errors:0"
29 "Jingle error namespace (XEP-0166)")
31 (defconst jingle-rtp-ns
"urn:xmpp:jingle:apps:rtp:0"
32 "Jingle RTP Sessions namespace (XEP-0167)")
34 (defconst jingle-ice-udp-ns
"urn:xmpp:jingle:transports:ice-udp:0"
35 "Jingle ICE namespace (XEP-0176)")
37 (defvar jingle-acct-sid-map
(make-hash-table :test
'equal
)
38 "Mapping from pairs of JIDs and Jingle SIDs to FSMs.
39 The JID is the full JID of the account using the session.")
41 (defconst tox-name
"net.sourceforge.emacs-jabber.Tox"
42 "Well-known D-BUS name of the tox service.")
44 (defconst tox-path
"/net/sourceforge/emacs_jabber/Tox"
45 "Well-known path of the main Tox object.")
47 (defconst tox-interface
"net.sourceforge.emacs_jabber.Tox"
48 "Interface of main Tox object.")
50 (defconst tox-session-interface
"net.sourceforge.emacs_jabber.ToxSession"
51 "Interface of ToxSession object.")
54 :start
((jc sid role jid
) "Start a Jingle FSM.
55 \(Specifically, for Jingle Audio, as that's all we support for now.)
56 JC is the account we're using.
57 SID is a string, the session ID.
58 ROLE is either :initiator or :target.
59 JID is the full JID of the partner."
60 (let ((state-data (list :jc jc
:jid jid
:sid sid
:role role
)))
61 (setq state-data
(jingle-create-tox-session fsm state-data
))
63 (ecase (plist-get state-data
:role
)
70 (defun jingle-create-tox-session (fsm state-data
)
71 "Helper function to create a Tox session.
72 Accepts, modifies, and returns STATE-DATA."
73 ;; XXX: should this always be bidirectional?
75 (dbus-call-method :session tox-name tox-path tox-interface
77 :byte
3)) ;3=bidirectional stream
78 ;; Find the codecs that we support
80 (dbus-call-method :session tox-name tox-session tox-session-interface
82 (setq state-data
(plist-put state-data
:tox-session tox-session
))
83 (setq state-data
(plist-put state-data
:our-codecs our-codecs
))
85 (fsm-debug-output "tox-session: %S, our-codecs: %S" tox-session our-codecs
)
86 ;; Set up the pipeline, so we can search for transport candidates.
87 (fsm-debug-output "About to call SetDefaultAudioSink")
88 (dbus-call-method :session tox-name tox-session tox-session-interface
89 "SetDefaultAudioSink")
90 (fsm-debug-output "About to call SetOggVorbisAudioSource")
91 (dbus-call-method :session tox-name tox-session tox-session-interface
92 "SetOggVorbisAudioSource"
93 "/home/magnus/musik/computervirus.ogg")
94 ;; There, now we just wait for the NativeCandidatesPrepared signal...
95 ;; NO! Don't do like those dead people do! That signal will never
96 ;; come. I don't know why, though...
97 (fsm-debug-output "About to register signal")
98 (dbus-register-signal :session tox-name tox-session tox-session-interface
99 "NativeCandidatesPrepared"
100 (lexical-let ((fsm fsm
))
104 (cons :native-candidates-prepared components
)))))
105 ;; This is more like it. At least it will provide us with some
106 ;; debugging information.
107 (dbus-register-signal :session tox-name tox-session tox-session-interface
109 (lexical-let ((fsm fsm
))
113 (cons :new-native-candidate components
)))))
114 (fsm-debug-output "Waiting")
117 (define-enter-state jingle nil
120 (let ((tox-session (plist-get state-data
:tox-session
)))
123 (dbus-call-method :session tox-name tox-session tox-session-interface
125 (remhash (cons (jabber-connection-jid (plist-get state-data
:jc
))
126 (plist-get state-data
:sid
))
130 (define-enter-state jingle
:initiate
132 (let ((jc (plist-get state-data
:jc
))
133 (jid (plist-get state-data
:jid
))
134 (sid (plist-get state-data
:sid
))
135 (payload-types (mapcar
138 ((id .
,(number-to-string (nth 0 codec
)))
139 (name .
,(nth 1 codec
))
140 ;; (nth 2 codec) is media type;
141 ;; should we filter out
142 ;; non-audio codecs? then
143 ;; again, the value seems to be
145 (clockrate .
,(number-to-string (nth 3 codec
)))
146 ,@(let ((channels (nth 4 codec
)))
147 (unless (= channels
0)
148 `((channels .
,(number-to-string channels
))))))
152 ((name .
,(nth 0 param
))
153 (value .
,(nth 1 param
)))))
155 (plist-get state-data
:our-codecs
))))
156 (jabber-send-iq jc jid
"set"
157 `(jingle ((xmlns .
,jingle-ns
)
158 (action .
"session-initiate")
159 (initiator .
,(jabber-connection-jid jc
))
162 ((creator .
"initiator")
164 (senders .
"initiator"))
166 ((xmlns .
,jingle-rtp-ns
)
169 (transport ((xmlns .
,jingle-ice-udp-ns
)))))
171 (fsm-send-sync fsm
(cons :iq-result iq
)))
174 (fsm-send-sync fsm
(cons :iq-error iq
)))
176 (list state-data nil
)))
178 (define-state jingle
:initiate
179 (fsm state-data event callback
)
180 (case (car-safe event
)
182 ;; Receiver provisionally accepted the session request. Move on
184 (list :pending state-data
))
186 (message "Couldn't initiate Jingle audio session: %s"
187 (jabber-parse-error (jabber-iq-error (cdr event
))))
188 (list nil state-data
))
189 (:new-native-candidate
190 (let ((components (cdr event
)))
191 ;; XXX: keep them all
192 (setq state-data
(plist-put state-data
:native-candidates
(list (cdr event
))))
193 (jingle-send-native-candidate state-data components
)
194 (list :initiate state-data
)))))
196 (define-state jingle
:wait-for-initiate
197 (fsm state-data event callback
)
198 (case (car-safe event
)
200 (let* ((jc (plist-get state-data
:jc
))
202 (from (jabber-xml-get-attribute iq
'from
))
203 (id (jabber-xml-get-attribute iq
'id
))
204 (jingle (jabber-iq-query iq
))
205 ;; XXX: could be more than one...
206 (content (car (jabber-xml-get-children jingle
'content
)))
207 ;; XXX: is it really audio?
208 (audio-content (find jingle-rtp-ns
(jabber-xml-node-children content
)
210 :key
'jabber-xml-get-xmlns
))
211 (payload-types (jabber-xml-get-children audio-content
'payload-type
)))
212 ;; There are very few reasons for which we should not send an
213 ;; acknowledgement here; see section 6.3.2 of XEP-0166.
214 ;; Notably, we might want to check that there is a presence
216 (jabber-send-iq jc from
"result" ()
220 ;; Make sure audio is in the list of contents. We can
221 ;; negotiate away other content types later.
222 ((null audio-content
)
223 (jingle-send-iq state-data
"session-terminate"
224 '(reason () (unsupported-applications)))
225 (list nil state-data
))
227 ;; Make sure ICE is in the list of transports.
228 ((not (member* jingle-ice-udp-ns
229 (jabber-xml-get-children content
'transport
)
231 :key
'jabber-xml-get-xmlns
))
232 (jingle-send-iq state-data
"session-terminate"
233 '(reason () (unsupported-transports)))
234 (list nil state-data
))
237 (let ((tox-session (plist-get state-data
:tox-session
)))
238 ;; Tell tox what codecs the remote side supports
240 :session tox-name tox-session tox-session-interface
242 ;;'((array (struct int32 string byte uint32 uint32 (array (dict-entry string string)))))
245 (jabber-xml-let-attributes
246 (id name clockrate channels
) pt
248 :int32
(string-to-number id
)
258 (jabber-xml-let-attributes
260 (list :dict-entry
:string name
:string value
)))
261 (jabber-xml-get-children pt
'parameter
))
262 (list :signature
"{ss}"))))))
265 ;; Check if we have any codecs in common
266 (let ((codec-intersection
268 :session tox-name tox-session tox-session-interface
269 "GetCodecIntersection")))
273 :codec-intersection codec-intersection
))
275 (if codec-intersection
276 ;; So, now we know that we stand a basic chance of fulfilling
277 ;; the request. Let's move on to PENDING.
278 (list :pending state-data
)
280 ;; Or, it might turn out that we don't have any codecs
281 ;; in common with our partner.
282 (jingle-send-iq state-data
"session-terminate"
283 '(reason () (media-error)))
284 (list nil state-data
))))))))))
286 (define-state jingle
:wait-for-codec-intersection
287 (fsm state-data event callback
)
289 ;; ;; Good enough, provisionally accept.
290 ;; (jabber-send-iq jc from "result" ()
291 ;; nil nil nil nil id)
292 ;; (list :pending state-data)))))))
296 (defun jingle-send-iq (state-data action payload
)
297 "Send a Jingle IQ stanza from within a Jingle FSM.
298 STATE-DATA is the state data plist of the FSM.
299 ACTION is the value of the action attribute of the <jingle/>
301 PAYLOAD is a list of XML elements to include as children
302 of the <jingle/> element.
303 The recipient and the SID are determined from STATE-DATA."
304 (let ((jc (plist-get state-data
:jc
))
305 (jid (plist-get state-data
:jid
))
306 (role (plist-get state-data
:role
))
307 (sid (plist-get state-data
:sid
)))
310 `(jingle ((xmlns .
,jingle-ns
)
315 (jabber-connection-jid jc
))
320 ;; XXX: we probably want error checking, to see if our partner
324 (defun jingle-send-native-candidate (state-data candidate
)
325 "Send a native candidate for ICE-UDP.
326 The CANDIDATE is a list of components, as provided by the
327 NewNativeCandidate signal of Tox."
328 (jingle-send-iq state-data
"transport-info"
330 ((creator .
"initiator")
333 ((xmlns .
,jingle-ice-udp-ns
))
337 ((component .
,(number-to-string (nth 1 c
)))
342 (port .
,(number-to-string (nth 3 c
)))
343 (protocol .
,(nth 4 c
))
344 (priority .
,(nth 7 c
))
345 ;; how to translate type?
349 (add-to-list 'jabber-iq-set-xmlns-alist
350 (cons jingle-ns
'jabber-jingle-incoming-iq
))
351 (defun jabber-jingle-incoming-iq (jc iq
)
352 (jabber-xml-let-attributes
353 (sid action
) (jabber-iq-query iq
)
354 (unless (and sid action
)
355 (jabber-signal-error "modify" 'bad-request
))
356 (let ((fsm (gethash (cons (jabber-connection-jid jc
) sid
) jingle-acct-sid-map
)))
359 (fsm-send-sync fsm
(cons :iq-set iq
)))
360 ((string= action
"session-initiate")
362 (setq fsm
(start-jingle jc sid
:target
(jabber-xml-get-attribute iq
'from
)))
364 (jabber-signal-error "wait" 'internal-server-error
365 (concat "Couldn't accept Jingle session: "
366 (error-message-string e
)))))
367 (puthash (cons (jabber-connection-jid jc
) sid
) fsm jingle-acct-sid-map
)
368 (fsm-send-sync fsm
(cons :iq-set iq
)))
370 (jabber-signal-error "modify" 'bad-request
371 (format "Session \"%s\" unknown" sid
)
372 `((unknown-session ((xmlns .
,jingle-error-ns
))))))))))
374 (defun jabber-jingle-start-audio-session (jc jid
)
376 (list (jabber-read-account)
377 (jabber-read-jid-completing "Voice call to: " nil nil nil
'full
)))
378 (let* ((sid (apply 'format
"emacs-sid-%d.%d.%d" (current-time)))
379 (fsm (start-jingle jc sid
:initiator jid
)))
380 (puthash (cons (jabber-connection-jid jc
) sid
) fsm jingle-acct-sid-map
)))
383 (provide 'jabber-tox
)