mod_xxxx/verse/plugins/jingle_ibb.lua

215 lines
5.4 KiB
Lua

local verse = require "verse";
local base64 = require "util.encodings".base64;
local uuid_generate = require "util.uuid".generate;
local xmlns_jingle_ibb = "urn:xmpp:jingle:transports:ibb:1";
local xmlns_ibb = "http://jabber.org/protocol/ibb";
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 ibb_conn = {};
local ibb_conn_mt = { __index = ibb_conn };
local 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