# HG changeset patch # User Kim Alvefur # Date 1317352626 -7200 # Node ID ce8ed17710cb52229d71c53c7948d7bd3535170a # Parent 39af184385cd4596c04ad70d46d6bfd3facded5d plugins.jingle_ibb: In-Band Bytestreams, initial commit. diff -r 39af184385cd -r ce8ed17710cb plugins/jingle_ibb.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/jingle_ibb.lua Fri Sep 30 05:17:06 2011 +0200 @@ -0,0 +1,212 @@ +local xmlns_jingle_ibb = "urn:xmpp:jingle:transports:ibb:1"; +local xmlns_ibb = "http://jabber.org/protocol/ibb"; +local base64 = require "util.encodings".base64; +assert(base64.encode("This is a test.") == "VGhpcyBpcyBhIHRlc3Qu", "Base64 encoding failed"); +assert(base64.decode("VGhpcyBpcyBhIHRlc3Qu") == "This is a test.", "Base64 decoding failed"); +local t_concat = table.concat +local uuid_generate = require "util.uuid".generate; + +local ibb_conn = {}; +local ibb_conn_mt = { __index = ibb_conn }; + +function new_ibb(stream) + local conn = setmetatable({ stream = stream }, ibb_conn_mt) + conn = verse.eventable(conn); + return conn; +end + +function ibb_conn:initiate(peer, sid, stanza) + self.block = 2048; -- ignored for now + self.stanza = stanza or 'iq'; + self.peer = peer; + self.sid = sid or tostring(self):match("%x+$"); + self.iseq = 0; + self.oseq = 0; + local feeder = function(stanza) + return self:feed(stanza) + end + self.feeder = feeder; + print("Hooking incomming IQs"); + local stream = self.stream; + stream:hook("iq/".. xmlns_ibb, feeder) + if stanza == "message" then + stream:hook("message", feeder) + end +end + +function ibb_conn:open(callback) + self.stream:send_iq(verse.iq{ to = self.peer, type = "set" } + :tag("open", { + xmlns = xmlns_ibb, + ["block-size"] = self.block, + sid = self.sid, + stanza = self.stanza + }) + , function(reply) + if callback then + if reply.attr.type ~= "error" then + callback(true) + else + callback(false, reply:get_error()) + end + end + end); +end + +function ibb_conn:send(data) + local stanza = self.stanza; + local st; + if stanza == "iq" then + st = verse.iq{ type = "set", to = self.peer } + elseif stanza == "message" then + st = verse.message{ to = self.peer } + end + + local seq = self.oseq; + self.oseq = seq + 1; + + st:tag("data", { xmlns = xmlns_ibb, sid = self.sid, seq = seq }) + :text(base64.encode(data)); + + if stanza == "iq" then + self.stream:send_iq(st, function(reply) + self:event(reply.attr.type == "result" and "drained" or "error"); + end) + else + stream:send(st) + self:event("drained"); + end +end + +function ibb_conn:feed(stanza) + if stanza.attr.from ~= self.peer then return end + local child = stanza[1]; + if child.attr.sid ~= self.sid then return end + local ok; + if child.name == "open" then + self:event("connected"); + self.stream:send(verse.reply(stanza)) + return true + elseif child.name == "data" then + local bdata = stanza:get_child_text("data", xmlns_ibb); + local seq = tonumber(child.attr.seq); + local expected_seq = self.iseq; + if bdata and seq then + if seq ~= expected_seq then + self.stream:send(verse.error_reply(stanza, "cancel", "not-acceptable", "Wrong sequence. Packet lost?")) + self:close(); + self:event("error"); + return true; + end + self.iseq = seq + 1; + local data = base64.decode(bdata); + if self.stanza == "iq" then + self.stream:send(verse.reply(stanza)) + end + self:event("incoming-raw", data); + return true; + end + elseif child.name == "close" then + self.stream:send(verse.reply(stanza)) + self:close(); + return true + end +end + +--[[ FIXME some day +function ibb_conn:receive(patt) + -- is this even used? + print("ibb_conn:receive("..tostring(patt)..")"); + assert(patt == "*a" or tonumber(patt)); + local data = t_concat(self.ibuffer):sub(self.pos, tonumber(patt) or nil); + self.pos = self.pos + #data; + return data +end + +function ibb_conn:dirty() + print("ibb_conn:dirty()"); + return false -- ???? +end +function ibb_conn:getfd() + return 0 +end +function ibb_conn:settimeout(n) + -- ignore? +end +-]] + +function ibb_conn:close() + self.stream:unhook("iq/".. xmlns_ibb, self.feeder) + self:event("disconnected"); +end + +function verse.plugins.jingle_ibb(stream) + stream:hook("ready", function () + stream:add_disco_feature(xmlns_jingle_ibb); + end, 10); + + local ibb = {}; + + function ibb:_setup() + local conn = new_ibb(self.stream); + conn.sid = self.sid or conn.sid; + conn.stanza = self.stanza or conn.stanza; + conn.block = self.block or conn.block; + conn:initiate(self.peer, self.sid, self.stanza); + self.conn = conn; + end + function ibb:generate_initiate() + print("ibb:generate_initiate() as ".. self.role); + local sid = uuid_generate(); + self.sid = sid; + self.stanza = 'iq'; + self.block = 2048; + local transport = verse.stanza("transport", { xmlns = xmlns_jingle_ibb, + sid = self.sid, stanza = self.stanza, ["block-size"] = self.block }); + return transport; + end + function ibb:generate_accept(initiate_transport) + print("ibb:generate_accept() as ".. self.role); + local attr = initiate_transport.attr; + self.sid = attr.sid or self.sid; + self.stanza = attr.stanza or self.stanza; + self.block = attr["block-size"] or self.block; + self:_setup(); + return initiate_transport; + end + function ibb:connect(callback) + if not self.conn then + self:_setup(); + end + local conn = self.conn; + print("ibb:connect() as ".. self.role); + if self.role == "initiator" then + conn:open(function(ok, ...) + assert(ok, table.concat({...}, ", ")); + callback(conn); + end); + else + callback(conn); + end + end + function ibb:info_received(jingle_tag) + print("ibb:info_received()"); + -- TODO, what exactly? + end + function ibb:disconnect() + if self.conn then + self.conn:close() + end + end + function ibb:handle_accepted(jingle_tag) end + + local ibb_mt = { __index = ibb }; + stream:hook("jingle/transport/"..xmlns_jingle_ibb, function (jingle) + return setmetatable({ + role = jingle.role, + peer = jingle.peer, + stream = jingle.stream, + jingle = jingle, + }, ibb_mt); + end); +end