plugins/jingle_ibb.lua

1
local verse = require "verse";
2
local base64 = require "prosody.util.encodings".base64;
3
local new_id = require "prosody.util.id".short;
4
 
5
local xmlns_jingle_ibb = "urn:xmpp:jingle:transports:ibb:1";
6
local xmlns_ibb = "http://jabber.org/protocol/ibb";
7
assert(base64.encode("This is a test.") == "VGhpcyBpcyBhIHRlc3Qu", "Base64 encoding failed");
8
assert(base64.decode("VGhpcyBpcyBhIHRlc3Qu") == "This is a test.", "Base64 decoding failed");
9
 
10
local ibb_conn = {};
11
local ibb_conn_mt = { __index = ibb_conn };
12
 
13
local function new_ibb(stream)
14
	local conn = setmetatable({ stream = stream }, ibb_conn_mt)
15
	conn = verse.eventable(conn);
16
	return conn;
17
end
18
 
19
function ibb_conn:initiate(peer, sid, stanza)
20
	self.block = 2048; -- ignored for now
21
	self.stanza = stanza or 'iq';
22
	self.peer = peer;
23
	self.sid = sid or tostring(self):match("%x+$");
24
	self.iseq = 0;
25
	self.oseq = 0;
26
	local feeder = function(stanza)
27
		return self:feed(stanza)
28
	end
29
	self.feeder = feeder;
30
	print("Hooking incoming IQs");
31
	local stream = self.stream;
32
		stream:hook("iq/".. xmlns_ibb, feeder)
33
	if stanza == "message" then
34
		stream:hook("message", feeder)
35
	end
36
end
37
 
38
function ibb_conn:open(callback)
39
	self.stream:send_iq(verse.iq{ to = self.peer, type = "set" }
40
		:tag("open", {
41
			xmlns = xmlns_ibb,
42
			["block-size"] = self.block,
43
			sid = self.sid,
44
			stanza = self.stanza
45
		})
46
	, function(reply)
47
		if callback then
48
			if reply.attr.type ~= "error" then
49
				callback(true)
50
			else
51
				callback(false, reply:get_error())
52
			end
53
		end
54
	end);
55
end
56
 
57
function ibb_conn:send(data)
58
	local stanza = self.stanza;
59
	local st;
60
	if stanza == "iq" then
61
		st = verse.iq{ type = "set", to = self.peer }
62
	elseif stanza == "message" then
63
		st = verse.message{ to = self.peer }
64
	end
65
 
66
	local seq = self.oseq;
67
	self.oseq = seq + 1;
68
 
69
	st:tag("data", { xmlns = xmlns_ibb, sid = self.sid, seq = seq })
70
		:text(base64.encode(data));
71
 
72
	if stanza == "iq" then
73
		self.stream:send_iq(st, function(reply)
74
			self:event(reply.attr.type == "result" and "drained" or "error");
75
		end)
76
	else
77
		stream:send(st)
78
		self:event("drained");
79
	end
80
end
81
 
82
function ibb_conn:feed(stanza)
83
	if stanza.attr.from ~= self.peer then return end
84
	local child = stanza[1];
85
	if child.attr.sid ~= self.sid then return end
86
	local ok;
87
	if child.name == "open" then
88
		self:event("connected");
89
		self.stream:send(verse.reply(stanza))
90
		return true
91
	elseif child.name == "data" then
92
		local bdata = stanza:get_child_text("data", xmlns_ibb);
93
		local seq = tonumber(child.attr.seq);
94
		local expected_seq = self.iseq;
95
		if bdata and seq then
96
			if seq ~= expected_seq then
97
				self.stream:send(verse.error_reply(stanza, "cancel", "not-acceptable", "Wrong sequence. Packet lost?"))
98
				self:close();
99
				self:event("error");
100
				return true;
101
			end
102
			self.iseq = seq + 1;
103
			local data = base64.decode(bdata);
104
			if self.stanza == "iq" then
105
				self.stream:send(verse.reply(stanza))
106
			end
107
			self:event("incoming-raw", data);
108
			return true;
109
		end
110
	elseif child.name == "close" then
111
		self.stream:send(verse.reply(stanza))
112
		self:close();
113
		return true
114
	end
115
end
116
 
117
--[[ FIXME some day
118
function ibb_conn:receive(patt)
119
	-- is this even used?
120
	print("ibb_conn:receive("..tostring(patt)..")");
121
	assert(patt == "*a" or tonumber(patt));
122
	local data = t_concat(self.ibuffer):sub(self.pos, tonumber(patt) or nil);
123
	self.pos = self.pos + #data;
124
	return data
125
end
126
 
127
function ibb_conn:dirty()
128
	print("ibb_conn:dirty()");
129
	return false -- ????
130
end
131
function ibb_conn:getfd()
132
	return 0
133
end
134
function ibb_conn:settimeout(n)
135
	-- ignore?
136
end
137
-]]
138
 
139
function ibb_conn:close()
140
	self.stream:unhook("iq/".. xmlns_ibb, self.feeder)
141
	self:event("disconnected");
142
end
143
 
144
function verse.plugins.jingle_ibb(stream)
145
	stream:hook("ready", function ()
146
		stream:add_disco_feature(xmlns_jingle_ibb);
147
	end, 10);
148
 
149
	local ibb = {};
150
 
151
	function ibb:_setup()
152
		local conn = new_ibb(self.stream);
153
		conn.sid    = self.sid    or conn.sid;
154
		conn.stanza = self.stanza or conn.stanza;
155
		conn.block  = self.block  or conn.block;
156
		conn:initiate(self.peer, self.sid, self.stanza);
157
		self.conn = conn;
158
	end
159
	function ibb:generate_initiate()
160
		print("ibb:generate_initiate() as ".. self.role);
161
		local sid = new_id();
162
		self.sid = sid;
163
		self.stanza = 'iq';
164
		self.block = 2048;
165
		local transport = verse.stanza("transport", { xmlns = xmlns_jingle_ibb,
166
			sid = self.sid, stanza = self.stanza, ["block-size"] = self.block });
167
		return transport;
168
	end
169
	function ibb:generate_accept(initiate_transport)
170
		print("ibb:generate_accept() as ".. self.role);
171
		local attr = initiate_transport.attr;
172
		self.sid    = attr.sid    or self.sid;
173
		self.stanza = attr.stanza or self.stanza;
174
		self.block  = attr["block-size"] or self.block;
175
		self:_setup();
176
		return initiate_transport;
177
	end
178
	function ibb:connect(callback)
179
		if not self.conn then
180
			self:_setup();
181
		end
182
		local conn = self.conn;
183
		print("ibb:connect() as ".. self.role);
184
		if self.role == "initiator" then
185
			conn:open(function(ok, ...)
186
				assert(ok, table.concat({...}, ", "));
187
				callback(conn);
188
			end);
189
		else
190
			callback(conn);
191
		end
192
	end
193
	function ibb:info_received(jingle_tag)
194
		print("ibb:info_received()");
195
		-- TODO, what exactly?
196
	end
197
	function ibb:disconnect()
198
		if self.conn then
199
			self.conn:close()
200
		end
201
	end
202
	function ibb:handle_accepted(jingle_tag) end
203
 
204
	local ibb_mt = { __index = ibb };
205
	stream:hook("jingle/transport/"..xmlns_jingle_ibb, function (jingle)
206
		return setmetatable({
207
			role = jingle.role,
208
			peer = jingle.peer,
209
			stream = jingle.stream,
210
			jingle = jingle,
211
		}, ibb_mt);
212
	end);
213
end