Software /
code /
verse
Comparison
plugins/jingle.lua @ 100:e45883a3f39a
verse.plugins.jingle: XEP-0166 Jingle plugin
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Sat, 21 Aug 2010 14:55:02 +0100 |
child | 118:d076948cd0e1 |
comparison
equal
deleted
inserted
replaced
99:0f5a8d530fcd | 100:e45883a3f39a |
---|---|
1 local sha1 = require "util.sha1".sha1; | |
2 local st = require "util.stanza"; | |
3 local timer = require "util.timer"; | |
4 local uuid_generate = require "util.uuid".generate; | |
5 | |
6 local xmlns_jingle = "urn:xmpp:jingle:1"; | |
7 local xmlns_jingle_errors = "urn:xmpp:jingle:errors:1"; | |
8 | |
9 local jingle_mt = {}; | |
10 jingle_mt.__index = jingle_mt; | |
11 | |
12 local registered_transports = {}; | |
13 local registered_content_types = {}; | |
14 | |
15 function verse.plugins.jingle(stream) | |
16 stream:hook("ready", function () | |
17 stream:add_disco_feature(xmlns_jingle); | |
18 end, 10); | |
19 | |
20 function stream:jingle(to) | |
21 return verse.eventable(setmetatable(base or { | |
22 role = "initiator"; | |
23 peer = to; | |
24 sid = uuid_generate(); | |
25 stream = stream; | |
26 }, jingle_mt)); | |
27 end | |
28 | |
29 function stream:register_jingle_transport(transport) | |
30 -- transport is a function that receives a | |
31 -- <transport> element, and returns a connection | |
32 -- We wait for 'connected' on that connection, | |
33 -- and use :send() and 'incoming-raw'. | |
34 end | |
35 | |
36 function stream:register_jingle_content_type(content) | |
37 -- Call content() for every 'incoming-raw'? | |
38 -- I think content() returns the object we return | |
39 -- on jingle:accept() | |
40 end | |
41 | |
42 local function handle_incoming_jingle(stanza) | |
43 local jingle_tag = stanza:get_child("jingle", xmlns_jingle); | |
44 local sid = jingle_tag.attr.sid; | |
45 local action = jingle_tag.attr.action; | |
46 local result = stream:event("jingle/"..sid, stanza); | |
47 if result == true then | |
48 -- Ack | |
49 stream:send(verse.reply(stanza)); | |
50 return true; | |
51 end | |
52 -- No existing Jingle object handled this action, our turn... | |
53 if action ~= "session-initiate" then | |
54 -- Trying to send a command to a session we don't know | |
55 local reply = st.error_reply(stanza, "cancel", "item-not-found") | |
56 :tag("unknown-session", { xmlns = xmlns_jingle_errors }):up(); | |
57 stream:send(reply); | |
58 return; | |
59 end | |
60 | |
61 -- Ok, session-initiate, new session | |
62 | |
63 -- Create new Jingle object | |
64 local sid = jingle_tag.attr.sid; | |
65 | |
66 local jingle = verse.eventable{ | |
67 role = "receiver"; | |
68 peer = stanza.attr.from; | |
69 sid = sid; | |
70 stream = stream; | |
71 }; | |
72 | |
73 setmetatable(jingle, jingle_mt); | |
74 | |
75 local content_tag; | |
76 local content, transport; | |
77 for tag in jingle_tag:childtags() do | |
78 if tag.name == "content" and tag.attr.xmlns == xmlns_jingle then | |
79 local description_tag = tag:child_with_name("description"); | |
80 local description_xmlns = description_tag.attr.xmlns; | |
81 if description_xmlns then | |
82 local desc_handler = stream:event("jingle/content/"..description_xmlns, jingle, description_tag); | |
83 if desc_handler then | |
84 content = desc_handler; | |
85 end | |
86 end | |
87 | |
88 local transport_tag = tag:child_with_name("transport"); | |
89 local transport_xmlns = transport_tag.attr.xmlns; | |
90 | |
91 transport = stream:event("jingle/transport/"..transport_xmlns, jingle, transport_tag); | |
92 if content and transport then | |
93 content_tag = tag; | |
94 break; | |
95 end | |
96 end | |
97 end | |
98 if not content then | |
99 -- FIXME: Fail, no content | |
100 stream:send(st.error_reply(stanza, "cancel", "feature-not-implemented", "The specified content is not supported")); | |
101 return; | |
102 end | |
103 | |
104 if not transport then | |
105 -- FIXME: Refuse session, no transport | |
106 stream:send(st.error_reply(stanza, "cancel", "feature-not-implemented", "The specified transport is not supported")); | |
107 return; | |
108 end | |
109 | |
110 stream:send(st.reply(stanza)); | |
111 | |
112 jingle.content_tag = content_tag; | |
113 jingle.creator, jingle.name = content_tag.attr.creator, content_tag.attr.name; | |
114 jingle.content, jingle.transport = content, transport; | |
115 | |
116 function jingle:decline() | |
117 -- FIXME: Decline session | |
118 end | |
119 | |
120 stream:hook("jingle/"..sid, function (stanza) | |
121 if stanza.attr.from ~= jingle.peer then | |
122 return false; | |
123 end | |
124 local jingle_tag = stanza:get_child("jingle", xmlns_jingle); | |
125 return jingle:handle_command(jingle_tag); | |
126 end); | |
127 | |
128 stream:event("jingle", jingle); | |
129 return true; | |
130 end | |
131 | |
132 function jingle_mt:handle_command(jingle_tag) | |
133 local action = jingle_tag.attr.action; | |
134 stream:debug("Handling Jingle command: %s", action); | |
135 if action == "session-terminate" then | |
136 self:destroy(); | |
137 elseif action == "session-accept" then | |
138 -- Yay! | |
139 self:handle_accepted(jingle_tag); | |
140 elseif action == "transport-info" then | |
141 stream:debug("Handling transport-info"); | |
142 self.transport:info_received(jingle_tag); | |
143 elseif action == "transport-replace" then | |
144 -- FIXME: Used for IBB fallback | |
145 stream:error("Peer wanted to swap transport, not implemented"); | |
146 else | |
147 -- FIXME: Reply unhandled command | |
148 stream:warn("Unhandled Jingle command: %s", action); | |
149 return nil; | |
150 end | |
151 return true; | |
152 end | |
153 | |
154 function jingle_mt:send_command(command, element, callback) | |
155 local stanza = st.iq({ to = self.peer, type = "set" }) | |
156 :tag("jingle", { | |
157 xmlns = xmlns_jingle, | |
158 sid = self.sid, | |
159 action = command, | |
160 initiator = self.role == "initiator" and self.stream.jid or nil, | |
161 responder = self.role == "responder" and self.jid or nil, | |
162 }) | |
163 :tag("content", { creator = self.creator, name = self.name }) | |
164 :add_child(element); | |
165 if not callback then | |
166 self.stream:send(stanza); | |
167 else | |
168 self.stream:send_iq(stanza, callback); | |
169 end | |
170 end | |
171 | |
172 function jingle_mt:accept(options) | |
173 local accept_stanza = st.iq({ to = self.peer, type = "set" }) | |
174 :tag("jingle", { | |
175 xmlns = xmlns_jingle, | |
176 sid = self.sid, | |
177 action = "session-accept", | |
178 responder = stream.jid, | |
179 }) | |
180 :tag("content", { creator = self.creator, name = self.name }); | |
181 | |
182 local content_accept_tag = self.content:generate_accept(self.content_tag:child_with_name("description"), options); | |
183 accept_stanza:add_child(content_accept_tag); | |
184 | |
185 local transport_accept_tag = self.transport:generate_accept(self.content_tag:child_with_name("transport"), options); | |
186 accept_stanza:add_child(transport_accept_tag); | |
187 | |
188 local jingle = self; | |
189 stream:send_iq(accept_stanza, function (result) | |
190 if result.attr.type == "error" then | |
191 local type, condition, text = result:get_error(); | |
192 stream:error("session-accept rejected: %s", condition); -- FIXME: Notify | |
193 return false; | |
194 end | |
195 jingle.transport:connect(function (conn) | |
196 stream:warn("CONNECTED (receiver)!!!"); | |
197 jingle.state = "active"; | |
198 jingle:event("connected", conn); | |
199 end); | |
200 end); | |
201 end | |
202 | |
203 | |
204 stream:hook("iq/"..xmlns_jingle, handle_incoming_jingle); | |
205 return true; | |
206 end | |
207 | |
208 function jingle_mt:offer(name, content) | |
209 local session_initiate = st.iq({ to = self.peer, type = "set" }) | |
210 :tag("jingle", { xmlns = xmlns_jingle, action = "session-initiate", | |
211 initiator = self.stream.jid, sid = self.sid }); | |
212 | |
213 -- Content tag | |
214 session_initiate:tag("content", { creator = self.role, name = name }); | |
215 | |
216 -- Need description element from someone who can turn 'content' into XML | |
217 local description = self.stream:event("jingle/describe/"..name, content); | |
218 | |
219 if not description then | |
220 return false, "Unknown content type"; | |
221 end | |
222 | |
223 session_initiate:add_child(description); | |
224 | |
225 -- FIXME: Sort transports by 1) recipient caps 2) priority (SOCKS vs IBB, etc.) | |
226 -- Fixed to s5b in the meantime | |
227 local transport = self.stream:event("jingle/transport/".."urn:xmpp:jingle:transports:s5b:1", self); | |
228 self.transport = transport; | |
229 | |
230 session_initiate:add_child(transport:generate_initiate()); | |
231 | |
232 self.stream:debug("Hooking %s", "jingle/"..self.sid); | |
233 self.stream:hook("jingle/"..self.sid, function (stanza) | |
234 if stanza.attr.from ~= self.peer then | |
235 return false; | |
236 end | |
237 local jingle_tag = stanza:get_child("jingle", xmlns_jingle); | |
238 return self:handle_command(jingle_tag) | |
239 end); | |
240 | |
241 self.stream:send_iq(session_initiate, function (result) | |
242 if result.type == "error" then | |
243 self.state = "terminated"; | |
244 local type, condition, text = result:get_error(); | |
245 return self:event("error", { type = type, condition = condition, text = text }); | |
246 end | |
247 end); | |
248 self.state = "pending"; | |
249 end | |
250 | |
251 function jingle_mt:terminate(reason) | |
252 local reason_tag = verse.stanza("reason"):tag(reason or "success"); | |
253 self:send_command("session-terminate", reason_tag, function (result) | |
254 self.state = "terminated"; | |
255 self.transport:disconnect(); | |
256 self:destroy(); | |
257 end); | |
258 end | |
259 | |
260 function jingle_mt:destroy() | |
261 self.stream:unhook("jingle/"..self.sid, self.handle_command); | |
262 end | |
263 | |
264 function jingle_mt:handle_accepted(jingle_tag) | |
265 local transport_tag = jingle_tag:child_with_name("transport"); | |
266 self.transport:handle_accepted(transport_tag); | |
267 self.transport:connect(function (conn) | |
268 print("CONNECTED (initiator)!") | |
269 -- Connected, send file | |
270 self.state = "active"; | |
271 self:event("connected", conn); | |
272 end); | |
273 end | |
274 | |
275 function jingle_mt:set_source(source, auto_close) | |
276 local function pump() | |
277 local chunk, err = source(); | |
278 if chunk and chunk ~= "" then | |
279 self.transport.conn:send(chunk); | |
280 elseif chunk == "" then | |
281 return pump(); -- We need some data! | |
282 elseif chunk == nil then | |
283 if auto_close then | |
284 self:terminate(); | |
285 end | |
286 self.transport.conn:unhook("drained", pump); | |
287 source = nil; | |
288 end | |
289 end | |
290 self.transport.conn:hook("drained", pump); | |
291 pump(); | |
292 end | |
293 | |
294 function jingle_mt:set_sink(sink) | |
295 self.transport.conn:hook("incoming-raw", sink); | |
296 self.transport.conn:hook("disconnected", function (event) | |
297 self.stream:debug("Closing sink..."); | |
298 local reason = event.reason; | |
299 if reason == "closed" then reason = nil; end | |
300 sink(nil, reason); | |
301 end); | |
302 end |