File

plugins/jingle_ibb.lua @ 445:b119dc4d8bc2

plugins.smacks: Don't warn about zero stanzas acked It's only if the count somehow goes backwards that something is really wrong. An ack for zero stanzas is fine and we don't need to do anything.
author Kim Alvefur <zash@zash.se>
date Thu, 10 Jun 2021 11:58:23 +0200
parent 428:bde804b01f28
child 457:73d4eb93657b
line wrap: on
line source

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 incoming 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