comparison mod_smacks/mod_smacks.lua @ 603:efc9d88b70ab

merged.
author Marco Cirillo <maranda@lightwitch.org>
date Thu, 09 Feb 2012 00:54:39 +0000
parents 7693724881b3
children ce39df945de1
comparison
equal deleted inserted replaced
601:00590d492a5b 603:efc9d88b70ab
5 local math_min = math.min; 5 local math_min = math.min;
6 local os_time = os.time; 6 local os_time = os.time;
7 local tonumber, tostring = tonumber, tostring; 7 local tonumber, tostring = tonumber, tostring;
8 local add_filter = require "util.filters".add_filter; 8 local add_filter = require "util.filters".add_filter;
9 local timer = require "util.timer"; 9 local timer = require "util.timer";
10 local datetime = require "util.datetime";
10 11
11 local xmlns_sm = "urn:xmpp:sm:2"; 12 local xmlns_sm = "urn:xmpp:sm:2";
12 local xmlns_errors = "urn:ietf:params:xml:ns:xmpp-stanzas"; 13 local xmlns_errors = "urn:ietf:params:xml:ns:xmpp-stanzas";
14 local xmlns_delay = "urn:xmpp:delay";
13 15
14 local sm_attr = { xmlns = xmlns_sm }; 16 local sm_attr = { xmlns = xmlns_sm };
15 17
16 local resume_timeout = module:get_option("smacks_hibernation_time", 300); 18 local resume_timeout = module:get_option("smacks_hibernation_time", 300);
17 local s2s_smacks = module:get_option_boolean("smacks_enabled_s2s", false); 19 local s2s_smacks = module:get_option_boolean("smacks_enabled_s2s", false);
18 local max_unacked_stanzas = 0; 20 local max_unacked_stanzas = 0;
19 21
20 local session_registry = {}; 22 local session_registry = {};
21 23
24 local function can_do_smacks(session, advertise_only)
25 if session.smacks then return false, "unexpected-request", "Stream management is already enabled"; end
26
27 local session_type = session.type;
28 if session_type == "c2s" then
29 if not(advertise_only) and not(session.resource) then -- Fail unless we're only advertising sm
30 return false, "unexpected-request", "Client must bind a resource before enabling stream management";
31 end
32 return true;
33 elseif s2s_smacks and (session_type == "s2sin" or session_type == "s2sout") then
34 return true;
35 end
36 return false, "service-unavailable", "Stream management is not available for this stream";
37 end
38
22 module:hook("stream-features", 39 module:hook("stream-features",
23 function (event) 40 function (event)
24 event.features:tag("sm", sm_attr):tag("optional"):up():up(); 41 if can_do_smacks(event.origin, true) then
42 event.features:tag("sm", sm_attr):tag("optional"):up():up();
43 end
25 end); 44 end);
26 45
27 module:hook("s2s-stream-features", 46 module:hook("s2s-stream-features",
28 function (event) 47 function (event)
29 local origin = event.origin; 48 if can_do_smacks(event.origin, true) then
30 if s2s_smacks and (origin.type == "s2sin" or origin.type == "s2sout") then
31 event.features:tag("sm", sm_attr):tag("optional"):up():up(); 49 event.features:tag("sm", sm_attr):tag("optional"):up():up();
32 end 50 end
33 end); 51 end);
34 52
35 module:hook_stanza("http://etherx.jabber.org/streams", "features", 53 module:hook_stanza("http://etherx.jabber.org/streams", "features",
36 function (session, stanza) 54 function (session, stanza)
37 if s2s_smacks and (session.type == "s2sin" or session.type == "s2sout") 55 if can_do_smacks(session) and stanza:get_child("sm", xmlns_sm) then
38 and not session.smacks and stanza:get_child("sm", xmlns_sm) then
39 session.sends2s(st.stanza("enable", sm_attr)); 56 session.sends2s(st.stanza("enable", sm_attr));
40 end 57 end
41 end); 58 end);
42 59
43 local function wrap_session(session, resume) 60 local function wrap_session(session, resume)
53 70
54 local _send = session.sends2s or session.send; 71 local _send = session.sends2s or session.send;
55 local function new_send(stanza) 72 local function new_send(stanza)
56 local attr = stanza.attr; 73 local attr = stanza.attr;
57 if attr and not attr.xmlns then -- Stanza in default stream namespace 74 if attr and not attr.xmlns then -- Stanza in default stream namespace
58 queue[#queue+1] = st.clone(stanza); 75 local cached_stanza = st.clone(stanza);
76
77 if cached_stanza and cached_stanza:get_child("delay", xmlns_delay) == nil then
78 cached_stanza = cached_stanza:tag("delay", { xmlns = xmlns_delay, from = session.host, stamp = datetime.datetime()});
79 end
80
81 queue[#queue+1] = cached_stanza;
59 end 82 end
60 local ok, err = _send(stanza); 83 local ok, err = _send(stanza);
61 if ok and #queue > max_unacked_stanzas and not session.awaiting_ack then 84 if ok and #queue > max_unacked_stanzas and not session.awaiting_ack then
62 session.awaiting_ack = true; 85 session.awaiting_ack = true;
63 return _send(st.stanza("r", sm_attr)); 86 return _send(st.stanza("r", sm_attr));
84 107
85 return session; 108 return session;
86 end 109 end
87 110
88 module:hook_stanza(xmlns_sm, "enable", function (session, stanza) 111 module:hook_stanza(xmlns_sm, "enable", function (session, stanza)
112 local ok, err, err_text = can_do_smacks(session);
113 if not ok then
114 session.log("warn", "Failed to enable smacks: %s", err_text); -- TODO: XEP doesn't say we can send error text, should it?
115 session.send(st.stanza("failed", { xmlns = xmlns_sm }):tag(err, { xmlns = xmlns_errors}));
116 return true;
117 end
118
89 module:log("debug", "Enabling stream management"); 119 module:log("debug", "Enabling stream management");
90 session.smacks = true; 120 session.smacks = true;
91 121
92 wrap_session(session); 122 wrap_session(session);
93 123
179 module:log("warn", "::%s", tostring(queue[i])); 209 module:log("warn", "::%s", tostring(queue[i]));
180 end 210 end
181 handle_unacked_stanzas(session); 211 handle_unacked_stanzas(session);
182 end 212 end
183 else 213 else
214 session.log("debug", "mod_smacks hibernating session for up to %d seconds", resume_timeout);
184 local hibernate_time = os_time(); -- Track the time we went into hibernation 215 local hibernate_time = os_time(); -- Track the time we went into hibernation
185 session.hibernating = hibernate_time; 216 session.hibernating = hibernate_time;
186 local resumption_token = session.resumption_token; 217 local resumption_token = session.resumption_token;
187 timer.add_task(resume_timeout, function () 218 timer.add_task(resume_timeout, function ()
219 session.log("debug", "mod_smacks hibernation timeout reached...");
188 -- We need to check the current resumption token for this resource 220 -- We need to check the current resumption token for this resource
189 -- matches the smacks session this timer is for in case it changed 221 -- matches the smacks session this timer is for in case it changed
190 -- (for example, the client may have bound a new resource and 222 -- (for example, the client may have bound a new resource and
191 -- started a new smacks session, or not be using smacks) 223 -- started a new smacks session, or not be using smacks)
192 local curr_session = hosts[session.host].sessions[session.username].sessions[session.resource]; 224 local curr_session = hosts[session.host].sessions[session.username].sessions[session.resource];
193 if curr_session.resumption_token == resumption_token 225 if curr_session.resumption_token == resumption_token
194 -- Check the hibernate time still matches what we think it is, 226 -- Check the hibernate time still matches what we think it is,
195 -- otherwise the session resumed and re-hibernated. 227 -- otherwise the session resumed and re-hibernated.
196 and session.hibernating == hibernate_time then 228 and session.hibernating == hibernate_time then
229 session.log("debug", "Destroying session for hibernating too long");
197 session_registry[session.resumption_token] = nil; 230 session_registry[session.resumption_token] = nil;
198 session.resumption_token = nil; 231 session.resumption_token = nil;
199 -- This recursion back into our destroy handler is to 232 -- This recursion back into our destroy handler is to
200 -- make sure we still handle any queued stanzas 233 -- make sure we still handle any queued stanzas
201 sessionmanager.destroy_session(session); 234 sessionmanager.destroy_session(session);
235 else
236 session.log("debug", "Session resumed before hibernation timeout, all is well")
202 end 237 end
203 end); 238 end);
204 return; -- Postpone destruction for now 239 return; -- Postpone destruction for now
205 end 240 end
206 241
210 245
211 module:hook_stanza(xmlns_sm, "resume", function (session, stanza) 246 module:hook_stanza(xmlns_sm, "resume", function (session, stanza)
212 local id = stanza.attr.previd; 247 local id = stanza.attr.previd;
213 local original_session = session_registry[id]; 248 local original_session = session_registry[id];
214 if not original_session then 249 if not original_session then
250 session.log("debug", "Tried to resume non-existent session with id %s", id);
215 session.send(st.stanza("failed", sm_attr) 251 session.send(st.stanza("failed", sm_attr)
216 :tag("item-not-found", { xmlns = xmlns_errors }) 252 :tag("item-not-found", { xmlns = xmlns_errors })
217 ); 253 );
218 elseif session.username == original_session.username 254 elseif session.username == original_session.username
219 and session.host == original_session.host then 255 and session.host == original_session.host then
256 session.log("debug", "mod_smacks resuming existing session...");
220 -- TODO: All this should move to sessionmanager (e.g. session:replace(new_session)) 257 -- TODO: All this should move to sessionmanager (e.g. session:replace(new_session))
221 original_session.ip = session.ip; 258 original_session.ip = session.ip;
222 original_session.conn = session.conn; 259 original_session.conn = session.conn;
223 original_session.send = session.send; 260 original_session.send = session.send;
224 original_session.stream = session.stream; 261 original_session.stream = session.stream;