examples/lm/ibb.lua
changeset 68 742878c74b8e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/lm/ibb.lua	Tue Mar 31 18:35:34 2009 +0300
@@ -0,0 +1,188 @@
+
+-- IN-BAND BYTESTREAMS (XEP-0047)
+
+-- TODO bidirectionality
+--        thus, on stream accept we can add our sid to incoming files structure,
+--        as if we received and accepted incoming request.
+--      message stanzas
+
+-- library
+
+local lm     = require 'lm'
+local iq     = require 'lm.iq'
+local base64 = require 'base64'
+
+--
+
+local F = { }
+local M = { }
+M.__index = M
+local O = {
+	handler =
+		function ( accept, reject )
+			reject ()
+		end,
+}
+
+function F.new ( conn, to, bs, sid )
+	local obj = {
+		conn = conn,
+		to   = to,
+		sbs  = bs,
+		bs   = math.floor ( bs * 3 / 4 ),
+		sid  = sid,
+		seq  = 0,
+	}
+	setmetatable ( obj, M )
+	return obj
+end
+
+function M.open ( obj, success, fail )
+	iq.send ( obj.conn, obj.to, 'set',
+		{
+			open = { sid = obj.sid, ['block-size'] = obj.sbs, xmlns = 'http://jabber.org/protocol/ibb' }
+		}, success, fail )
+end
+
+function M.send ( obj, data, success, fail )
+	if data and data ~= '' then
+		local start = 0
+		while start < data:len () do
+			local chunk = base64.encode ( data:sub ( start, obj.bs ) )
+			local cseq  = obj.seq -- local instance
+			iq.send ( obj.conn, obj.to, 'set',
+				{
+					data = { sid = obj.sid, xmlns = 'http://jabber.org/protocol/ibb', seq = cseq,
+						chunk,
+					},
+				},
+				function ()
+					success ( cseq )
+				end,
+				function ( mesg )
+					noerr = false
+					fail ( mesg )
+				end )
+			start   = start + obj.bs
+			obj.seq = obj.seq + 1
+		end
+	end
+end
+
+function M.close ( obj, success, fail )
+	iq.send ( obj.conn, obj.to, 'set',
+		{
+			close = { sid = obj.sid, xmlns = 'http://jabber.org/protocol/ibb' },
+		}, success, fail )
+end
+
+function F.handler ( handler )
+	O.handler = handler
+end
+
+local ibb_files = {}
+
+function F.iq_handler ( conn, mess )
+	local mtype, smtype = mess:type ()
+	if smtype ~= 'set' then
+		return false
+	end
+
+	local child = mess:child ()
+	if not child or child:attribute ( 'xmlns' ) ~= 'http://jabber.org/protocol/ibb' then
+		return false
+	end
+
+	local id     = mess:attribute ( 'id' )
+	local from   = mess:attribute ( 'from' )
+	local action = child:name ()
+	local sid    = child:attribute ( 'sid' )
+
+	if action == 'open' then
+		if not ibb_files[sid] then
+			O.handler ( from,
+				function ( success, fail )
+					ibb_files[sid] = { from = from, success = success, fail = fail }
+					conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
+				end,
+				function ()
+					conn:send (
+						lm.message.create { to = from, mtype = 'iq-error', id = id,
+							error = { code = '405', type = 'cancel',
+								['not-allowed'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
+							},
+						} )
+				end )
+		else
+			conn:send (
+				lm.message.create { to = from, mtype = 'iq-error', id = id,
+					error = { code = '409', type = 'cancel',
+						conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
+					},
+				} )
+		end
+	elseif action == 'data' then
+		local seq = child:attribute ( 'seq' )
+		if ibb_files[sid] and from == ibb_files[sid].from and not ibb_files[sid][tonumber(seq)+1] then
+			local data = child:value ()
+			conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
+			ibb_files[sid][tonumber(seq)+1] = data
+-- XXX		ibb_files[sid].success ( seq )
+		else
+			if ibb_files[sid] then
+				ibb_files[sid].fail ( 'conflict' )
+				ibb_files[sid] = nil -- invalidate session
+				conn:send (
+					lm.message.create { to = from, mtype = 'iq-error', id = id,
+						error = { code = '409', type = 'cancel',
+							conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
+						},
+					} )
+			else
+				conn:send (
+					lm.message.create { to = from, mtype = 'iq-error', id = id,
+						error = { code = '404', type = 'cancel', -- XXX: check
+							['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
+						},
+					} )
+			end
+		end
+	elseif action == 'close' then
+		if ibb_files[sid] and from == ibb_files[sid].from then
+			local data = ''
+			for seq, chunk in ipairs ( ibb_files[sid] ) do
+				data = data .. chunk
+			end
+			local decoded = base64.decode ( data )
+			ibb_files[sid].success ( decoded )
+			conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
+			ibb_files[sid] = nil
+		else
+			if ibb_files[sid] then
+				ibb_files[sid].fail ( 'conflict' )
+				ibb_files[sid] = nil -- invalidate session
+				conn:send (
+					lm.message.create { to = from, mtype = 'iq-error', id = id,
+						error = { code = '409', type = 'cancel',
+							conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
+						},
+					} )
+			else
+				conn:send (
+					lm.message.create { to = from, mtype = 'iq-error', id = id,
+						error = { code = '404', type = 'cancel', -- XXX: check
+							['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
+						},
+					} )
+			end
+		end
+	else
+		return false
+	end
+
+	return true
+end
+
+return F
+
+-- vim: se ts=4: --