Diff

plugins/jingle_ibb.lua @ 219:ce8ed17710cb

plugins.jingle_ibb: In-Band Bytestreams, initial commit.
author Kim Alvefur <zash@zash.se>
date Fri, 30 Sep 2011 05:17:06 +0200
child 250:a5ac643a7fd6
line wrap: on
line diff
--- /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