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 |