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