diff --git a/data/glade/manage_proxies_window.glade b/data/glade/manage_proxies_window.glade
index b28beef97..9580b12e3 100644
--- a/data/glade/manage_proxies_window.glade
+++ b/data/glade/manage_proxies_window.glade
@@ -235,6 +235,7 @@ BOSH
0
True
+ ●
False
@@ -305,7 +306,7 @@ BOSH
True
False
- 5
+ 8
2
False
6
@@ -314,7 +315,7 @@ BOSH
True
- _Port:
+ Proxy _Port:
True
False
GTK_JUSTIFY_LEFT
@@ -333,8 +334,8 @@ BOSH
0
1
- 1
- 2
+ 4
+ 5
fill
@@ -349,14 +350,15 @@ BOSH
0
True
+ ●
False
1
2
- 0
- 1
+ 3
+ 4
@@ -370,14 +372,15 @@ BOSH
0
True
+ ●
False
1
2
- 1
- 2
+ 4
+ 5
@@ -385,7 +388,7 @@ BOSH
True
- _Host:
+ Proxy _Host:
True
False
GTK_JUSTIFY_LEFT
@@ -404,8 +407,8 @@ BOSH
0
1
- 0
- 1
+ 3
+ 4
fill
@@ -433,8 +436,8 @@ BOSH
0
1
- 4
- 5
+ 7
+ 8
fill
@@ -462,8 +465,8 @@ BOSH
0
1
- 3
- 4
+ 6
+ 7
fill
@@ -478,14 +481,15 @@ BOSH
0
True
+ ●
False
1
2
- 4
- 5
+ 7
+ 8
@@ -499,14 +503,15 @@ BOSH
0
True
+ ●
False
1
2
- 3
- 4
+ 6
+ 7
@@ -515,7 +520,7 @@ BOSH
True
True
- Use authentication
+ Use proxy authentication
True
GTK_RELIEF_NORMAL
True
@@ -524,6 +529,80 @@ BOSH
True
+
+ 0
+ 2
+ 5
+ 6
+ fill
+
+
+
+
+
+
+ True
+ _BOSH URL:
+ True
+ False
+ GTK_JUSTIFY_LEFT
+ False
+ False
+ 0
+ 0.5
+ 0
+ 0
+ proxyhost_entry
+ PANGO_ELLIPSIZE_NONE
+ -1
+ False
+ 0
+
+
+ 0
+ 1
+ 0
+ 1
+ fill
+
+
+
+
+
+
+ True
+ True
+ True
+ True
+ 0
+
+ True
+ ●
+ False
+
+
+
+ 1
+ 2
+ 0
+ 1
+
+
+
+
+
+
+ True
+ True
+ Use HTTP proxy
+ True
+ GTK_RELIEF_NORMAL
+ True
+ False
+ False
+ True
+
+
0
2
@@ -533,6 +612,57 @@ BOSH
+
+
+
+ True
+ B_OSH Port:
+ True
+ False
+ GTK_JUSTIFY_LEFT
+ False
+ False
+ 0
+ 0.5
+ 0
+ 0
+ proxyhost_entry
+ PANGO_ELLIPSIZE_NONE
+ -1
+ False
+ 0
+
+
+ 0
+ 1
+ 1
+ 2
+ fill
+
+
+
+
+
+
+ True
+ True
+ True
+ True
+ 0
+
+ True
+ ●
+ False
+
+
+
+ 1
+ 2
+ 1
+ 2
+
+
+
diff --git a/src/common/config.py b/src/common/config.py
index 9b950705a..4a52f9eae 100644
--- a/src/common/config.py
+++ b/src/common/config.py
@@ -337,8 +337,17 @@ class Config:
'type': [ opt_str, 'http' ],
'host': [ opt_str, '' ],
'port': [ opt_int, 3128 ],
+ 'useauth': [ opt_bool, False ],
'user': [ opt_str, '' ],
'pass': [ opt_str, '' ],
+ 'bosh_uri': [ opt_str, '' ],
+ 'bosh_port': [ opt_int, 80 ],
+ 'bosh_useproxy': [ opt_bool, False ],
+ 'bosh_wait': [ opt_int, 30 ],
+ 'bosh_hold': [ opt_int, 2 ],
+ 'bosh_content': [ opt_str, 'text/xml; charset=utf-8' ],
+ 'bosh_http_pipelining': [ opt_bool, False ],
+ 'bosh_wait_for_restart_response': [ opt_bool, False ],
}, {}),
'themes': ({
'accounttextcolor': [ opt_color, 'black', '', True ],
diff --git a/src/common/connection.py b/src/common/connection.py
index 08d7bcaae..7df8b4a71 100644
--- a/src/common/connection.py
+++ b/src/common/connection.py
@@ -428,11 +428,11 @@ class Connection(ConnectionHandlers):
# create connection if it doesn't already exist
self.connected = 1
if p and p in gajim.config.get_per('proxies'):
- proxy = {'host': gajim.config.get_per('proxies', p, 'host')}
- proxy['port'] = gajim.config.get_per('proxies', p, 'port')
- proxy['user'] = gajim.config.get_per('proxies', p, 'user')
- proxy['password'] = gajim.config.get_per('proxies', p, 'pass')
- proxy['type'] = gajim.config.get_per('proxies', p, 'type')
+ proxy = {}
+ proxyptr = gajim.config.get_per('proxies',p)
+ for key in proxyptr.keys(): proxy[key]=proxyptr[key][1]
+ print proxy
+
elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'):
try:
try:
@@ -546,11 +546,11 @@ class Connection(ConnectionHandlers):
con.RegisterDisconnectHandler(self._on_new_account)
# FIXME: BOSH properties should be loaded from config
- if self._proxy and self._proxy['type'] == 'bosh':
- self._proxy['bosh_hold'] = '1'
- self._proxy['bosh_wait'] = '60'
- self._proxy['bosh_content'] = 'text/xml; charset=utf-8'
- self._proxy['wait_for_restart_response'] = False
+ #if self._proxy and self._proxy['type'] == 'bosh':
+ # self._proxy['bosh_hold'] = '2'
+ # self._proxy['bosh_wait'] = '10'
+ # self._proxy['bosh_content'] = 'text/xml; charset=utf-8'
+ # self._proxy['wait_for_restart_response'] = False
log.info('Connecting to %s: [%s:%d]', self.name,
@@ -1003,7 +1003,7 @@ class Connection(ConnectionHandlers):
self.connection.RegisterDisconnectHandler(self._on_disconnected)
self.connection.send(p, now=True)
- self.connection.StreamTerminate()
+ self.connection.start_disconnect()
#self.connection.start_disconnect(p, self._on_disconnected)
else:
self.time_to_reconnect = None
diff --git a/src/common/xmpp/bosh.py b/src/common/xmpp/bosh.py
index 4ce71cd68..c29fa7294 100644
--- a/src/common/xmpp/bosh.py
+++ b/src/common/xmpp/bosh.py
@@ -1,16 +1,20 @@
import locale, random
-from transports_nb import NonBlockingTransport, NonBlockingHTTP, CONNECTED, CONNECTING, DISCONNECTED
+from transports_nb import NonBlockingTransport, NonBlockingHTTPBOSH,\
+ CONNECTED, CONNECTING, DISCONNECTED, DISCONNECTING,\
+ urisplit
from protocol import BOSHBody
from simplexml import Node
+import sha
import logging
log = logging.getLogger('gajim.c.x.bosh')
+KEY_COUNT = 10
FAKE_DESCRIPTOR = -1337
'''Fake file descriptor - it's used for setting read_timeout in idlequeue for
-BOSH Transport. Timeouts in queue are saved by socket descriptor.
+BOSH Transport.
In TCP-derived transports it is file descriptor of socket'''
@@ -19,12 +23,6 @@ class NonBlockingBOSH(NonBlockingTransport):
bosh_dict):
NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue)
- # with 50-bit random initial rid, session would have to go up
- # to 7881299347898368 messages to raise rid over 2**53
- # (see http://www.xmpp.org/extensions/xep-0124.html#rids)
- r = random.Random()
- r.seed()
- self.bosh_rid = r.getrandbits(50)
self.bosh_sid = None
if locale.getdefaultlocale()[0]:
self.bosh_xml_lang = locale.getdefaultlocale()[0].split('_')[0]
@@ -33,25 +31,30 @@ class NonBlockingBOSH(NonBlockingTransport):
self.http_version = 'HTTP/1.1'
self.http_persistent = True
- self.http_pipelining = False
+ self.http_pipelining = bosh_dict['bosh_http_pipelining']
self.bosh_to = domain
self.route_host, self.route_port = xmpp_server
self.bosh_wait = bosh_dict['bosh_wait']
self.bosh_hold = bosh_dict['bosh_hold']
- self.bosh_host = bosh_dict['host']
- self.bosh_port = bosh_dict['port']
+ self.bosh_requests = self.bosh_hold
+ self.bosh_uri = bosh_dict['bosh_uri']
+ self.bosh_port = bosh_dict['bosh_port']
self.bosh_content = bosh_dict['bosh_content']
+ self.wait_cb_time = None
self.http_socks = []
- self.stanzas_to_send = []
- self.prio_bosh_stanza = None
+ self.stanza_buffer = []
+ self.prio_bosh_stanzas = []
self.current_recv_handler = None
+ self.current_recv_socket = None
+ self.key_stack = None
+ self.ack_checker = None
+ self.after_init = False
# if proxy_host .. do sth about HTTP proxy etc.
-
def connect(self, conn_5tuple, on_connect, on_connect_failure):
NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure)
@@ -59,14 +62,20 @@ class NonBlockingBOSH(NonBlockingTransport):
FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1
self.fd = FAKE_DESCRIPTOR
- self.http_persistent = True
- self.http_socks.append(self.get_http_socket())
+ self.stanza_buffer = []
+ self.prio_bosh_stanzas = []
+
+ self.key_stack = KeyStack(KEY_COUNT)
+ self.ack_checker = AckChecker()
+ self.after_init = True
+
+ self.http_socks.append(self.get_new_http_socket())
self.tcp_connection_started()
- # this connect() is not needed because sockets can be connected on send but
- # we need to know if host is reachable in order to invoke callback for
- # connecting failurei eventually (it's different than callback for errors
- # occurring after connection is etabilished)
+ # following connect() is not necessary because sockets can be connected on
+ # send but we need to know if host is reachable in order to invoke callback
+ # for connecting failure eventually (the callback is different than callback
+ # for errors occurring after connection is etabilished)
self.http_socks[0].connect(
conn_5tuple = conn_5tuple,
on_connect = lambda: self._on_connect(self.http_socks[0]),
@@ -83,121 +92,209 @@ class NonBlockingBOSH(NonBlockingTransport):
Called after HTTP response is received - another request is possible.
There should be always one pending request on BOSH CM.
'''
- log.info('on_http_req possible state:\n%s' % self.get_current_state())
- # if one of sockets is connecting, sth is about to be sent
- # if there is a pending request, we shouldn't send another one
+ log.info('on_http_req possible, state:\n%s' % self.get_current_state())
+ if self.state == DISCONNECTING:
+ self.disconnect()
+ return
+ self.send_BOSH(None)
+
+
+ def get_socket_in(self, state):
for s in self.http_socks:
- if s.state==CONNECTING or s.pending_requests>0: return
- self.flush_stanzas()
+ if s.state==state: return s
+ return None
-
- def flush_stanzas(self):
- # another to-be-locked candidate
- log.info('flushing stanzas')
- if self.prio_bosh_stanza:
- tmp = self.prio_bosh_stanza
- self.prio_bosh_stanza = None
+ def get_free_socket(self):
+ if self.http_pipelining:
+ assert( len(self.http_socks) == 1 )
+ return self.get_socket_in(CONNECTED)
else:
- if self.stanzas_to_send:
- tmp = self.stanzas_to_send.pop(0)
- else:
- tmp = []
- self.send_http(tmp)
+ last_recv_time, tmpsock = 0, None
+ for s in self.http_socks:
+ # we're interested only into CONNECTED socket with no req pending
+ if s.state==CONNECTED and s.pending_requests==0:
+ # if there's more of them, we want the one with less recent data receive
+ # (lowest last_recv_time)
+ if (last_recv_time==0) or (s.last_recv_time < last_recv_time):
+ last_recv_time = s.last_recv_time
+ tmpsock = s
+ if tmpsock:
+ return tmpsock
+ else:
+ return None
+
+
+ def send_BOSH(self, payload):
+ total_pending_reqs = sum([s.pending_requests for s in self.http_socks])
+
+ # when called after HTTP response when there are some pending requests and
+ # no data to send, we do nothing and disccard the payload
+ if payload is None and \
+ total_pending_reqs > 0 and \
+ self.stanza_buffer == [] and \
+ self.prio_bosh_stanzas == [] or \
+ self.state==DISCONNECTED:
+ return
+
+ # now the payload is put to buffer and will be sent at some point
+ self.append_stanza(payload)
+
+ # if we're about to make more requests than allowed, we don't send - stanzas will be
+ # sent after HTTP response from CM, exception is when we're disconnecting - then we
+ # send anyway
+ if total_pending_reqs >= self.bosh_requests and self.state!=DISCONNECTING:
+ log.warn('attemp to make more requests than allowed by Connection Manager:\n%s' %
+ self.get_current_state())
+ return
+
+ # when there's free CONNECTED socket, we flush the data
+ if self.get_free_socket():
+ self.plug_socket()
+ return
+
+ # if there is a connecting socket, we just wait for when it connects,
+ # payload will be sent in a sec when the socket connects
+ if self.get_socket_in(CONNECTING): return
+
+ # being here means there are either DISCONNECTED sockets or all sockets are
+ # CONNECTED with too many pending requests
+ s = self.get_socket_in(DISCONNECTED)
+
+ # if we have DISCONNECTED socket, lets connect it and ...
+ if s:
+ self.connect_and_flush(s)
+ else:
+ if len(self.http_socks) > 1: return
+ ss = self.get_new_http_socket()
+ self.http_socks.append(ss)
+ self.connect_and_flush(ss)
+ return
+
+ def plug_socket(self):
+ stanza = None
+ s = self.get_free_socket()
+ if s:
+ s._plug_idle(writable=True, readable=True)
+ else:
+ log.error('=====!!!!!!!!====> Couldnt get free socket in plug_socket())')
+
+ def build_stanza(self, socket):
+ if self.prio_bosh_stanzas:
+ stanza, add_payload = self.prio_bosh_stanzas.pop(0)
+ if add_payload:
+ stanza.setPayload(self.stanza_buffer)
+ self.stanza_buffer = []
+ else:
+ stanza = self.boshify_stanzas(self.stanza_buffer)
+ self.stanza_buffer = []
+
+ stanza = self.ack_checker.backup_stanza(stanza, socket)
+
+ key, newkey = self.key_stack.get()
+ if key:
+ stanza.setAttr('key', key)
+ if newkey:
+ stanza.setAttr('newkey', newkey)
+
+
+ log.info('sending msg with rid=%s to sock %s' % (stanza.getAttr('rid'), id(socket)))
+ socket.send(stanza)
+ self.renew_bosh_wait_timeout()
+ return stanza
+
+
+ def on_bosh_wait_timeout(self):
+ log.error('Connection Manager didn\'t respond within % seconds --> forcing \
+ disconnect' % self.bosh_wait)
+ self.disconnect()
+
+
+ def renew_bosh_wait_timeout(self):
+ if self.wait_cb_time is not None:
+ self.remove_bosh_wait_timeout()
+ sched_time = self.idlequeue.set_alarm(self.on_bosh_wait_timeout, self.bosh_wait+10)
+ self.wait_cb_time = sched_time
+
+ def remove_bosh_wait_timeout(self):
+ self.idlequeue.remove_alarm(
+ self.on_bosh_wait_timeout,
+ self.wait_cb_time)
+
+ def on_persistent_fallback(self):
+ log.warn('Fallback to nonpersistent HTTP (no pipelining as well)')
+ self.http_persistent = False
+ self.http_pipelining = False
+
+
+ def handle_body_attrs(self, stanza_attrs):
+ self.remove_bosh_wait_timeout()
+
+ if self.after_init:
+ self.after_init = False
+ if stanza_attrs.has_key('sid'):
+ # session ID should be only in init response
+ self.bosh_sid = stanza_attrs['sid']
+
+ if stanza_attrs.has_key('requests'):
+ #self.bosh_requests = int(stanza_attrs['requests'])
+ self.bosh_requests = int(stanza_attrs['wait'])
+
+ if stanza_attrs.has_key('wait'):
+ self.bosh_wait = int(stanza_attrs['wait'])
+
+ ack = None
+ if stanza_attrs.has_key('ack'):
+ ack = stanza_attrs['ack']
+ self.ack_checker.process_incoming_ack(ack=ack,
+ socket=self.current_recv_socket)
+
+ if stanza_attrs.has_key('type'):
+ if stanza_attrs['type'] in ['terminate', 'terminal']:
+ condition = 'n/a'
+ if stanza_attrs.has_key('condition'):
+ condition = stanza_attrs['condition']
+ log.error('Received terminating stanza: %s - %s' % (condition, bosh_errors[condition]))
+ self.set_state(DISCONNECTING)
+
+ if stanza_attrs['type'] == 'error':
+ # recoverable error
+ pass
+ return
+
+
+ def append_stanza(self, stanza):
+ if stanza:
+ if isinstance(stanza, tuple):
+ # tuple of BOSH stanza and True/False for whether to add payload
+ self.prio_bosh_stanzas.append(stanza)
+ else:
+ self.stanza_buffer.append(stanza)
+
def send(self, stanza, now=False):
- # body tags should be send only via send_http()
+ # body tags should be send only via send_BOSH()
assert(not isinstance(stanza, BOSHBody))
- self.send_http([stanza])
+ self.send_BOSH(stanza)
- def send_http(self, payload):
- # "Protocol" and string/unicode stanzas should be sent via send()
- # (only initiating and terminating BOSH stanzas should be send via send_http)
- assert(isinstance(payload, list) or isinstance(payload, BOSHBody))
- log.warn('send_http: stanzas: %s\n%s' % (payload, self.get_current_state()))
-
- if isinstance(payload, list):
- bosh_stanza = self.boshify_stanzas(payload)
- else:
- # bodytag_payload is
, we don't boshify, only add the rid
- bosh_stanza = payload
- picked_sock = self.pick_socket()
- if picked_sock:
- log.info('sending to socket %s' % id(picked_sock))
- bosh_stanza.setAttr('rid', self.get_rid())
- picked_sock.send(bosh_stanza)
- else:
- # no socket was picked but one is about to connect - save the stanza and
- # return
- log.info('send_http: no free socket:\n%s' % self.get_current_state())
- if self.prio_bosh_stanza:
- payload = self.merge_stanzas(payload, self.prio_bosh_stanza)
- if payload is None:
- # if we cant merge the stanzas (both are BOSH ), add the current to
- # queue to be sent later
- self.stanzas_to_send.append(bosh_stanza)
- log.warn('in BOSH send_http - unable to send %s because %s\
- is already about to be sent' % (str(payload), str(self.prio_bosh_stanza)))
- return
- self.prio_bosh_stanza = payload
-
- def merge_stanzas(self, s1, s2):
- if isinstance(s1, BOSHBody):
- if isinstance(s2, BOSHBody):
- # both are boshbodies
- return
- else:
- s1.setPayload(s2, add=True)
- return s1
- elif isinstance(s2, BOSHBody):
- s2.setPayload(s1, add=True)
- return s2
- else:
- #both are lists
- s1.extend(s2)
- return s1
-
def get_current_state(self):
t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n'
for s in self.http_socks:
t = '%s------ %s\t%s\t%s\n' % (t,id(s), s.state, s.pending_requests)
- t = '%s------ prio stanza to send: %s, queued stanzas: %s' \
- % (t, self.prio_bosh_stanza, self.stanzas_to_send)
+ t = '%s------ prio stanzas: %s, queued XMPP stanzas: %s, not_acked stanzas: %s' \
+ % (t, self.prio_bosh_stanzas, self.stanza_buffer,
+ self.ack_checker.get_not_acked_rids())
return t
- def pick_socket(self):
- # try to pick connected socket with no pending reqs
- for s in self.http_socks:
- if s.state == CONNECTED and s.pending_requests == 0:
- return s
-
- # try to connect some disconnected socket
- for s in self.http_socks:
- if s.state==DISCONNECTED:
- self.connect_and_flush(s)
- return
-
- # if there is any just-connecting socket, it will send the data in its
- # connect callback
- for s in self.http_socks:
- if s.state==CONNECTING:
- return
- # being here means there are only CONNECTED scokets with pending requests.
- # Lets create and connect another one
- if len(self.http_socks) < 2:
- s = self.get_http_socket()
- self.http_socks.append(s)
- self.connect_and_flush(s)
- return
def connect_and_flush(self, socket):
socket.connect(
conn_5tuple = self.conn_5tuple,
- on_connect = self.flush_stanzas,
+ on_connect = lambda :self.send_BOSH(None),
on_connect_failure = self.disconnect)
@@ -209,65 +306,163 @@ class NonBlockingBOSH(NonBlockingTransport):
return tag
- def get_initial_bodytag(self, after_SASL=False):
- return BOSHBody(
- attrs={'content': self.bosh_content,
- 'hold': str(self.bosh_hold),
- 'route': '%s:%s' % (self.route_host, self.route_port),
- 'to': self.bosh_to,
- 'wait': str(self.bosh_wait),
- 'xml:lang': self.bosh_xml_lang,
- 'xmpp:version': '1.0',
- 'ver': '1.6',
- 'xmlns:xmpp': 'urn:xmpp:xbosh'})
+ def send_init(self, after_SASL=False):
+ if after_SASL:
+ t = BOSHBody(
+ attrs={ 'to': self.bosh_to,
+ 'sid': self.bosh_sid,
+ 'xml:lang': self.bosh_xml_lang,
+ 'xmpp:restart': 'true',
+ 'xmlns:xmpp': 'urn:xmpp:xbosh'})
+ else:
+ t = BOSHBody(
+ attrs={ 'content': self.bosh_content,
+ 'hold': str(self.bosh_hold),
+ 'route': '%s:%s' % (self.route_host, self.route_port),
+ 'to': self.bosh_to,
+ 'wait': str(self.bosh_wait),
+ 'xml:lang': self.bosh_xml_lang,
+ 'xmpp:version': '1.0',
+ 'ver': '1.6',
+ 'xmlns:xmpp': 'urn:xmpp:xbosh'})
+ self.send_BOSH((t,True))
- def get_after_SASL_bodytag(self):
- return BOSHBody(
- attrs={ 'to': self.bosh_to,
- 'sid': self.bosh_sid,
- 'xml:lang': self.bosh_xml_lang,
- 'xmpp:restart': 'true',
- 'xmlns:xmpp': 'urn:xmpp:xbosh'})
-
- def get_closing_bodytag(self):
- return BOSHBody(attrs={'sid': self.bosh_sid, 'type': 'terminate'})
-
- def get_rid(self):
- self.bosh_rid = self.bosh_rid + 1
- return str(self.bosh_rid)
+ def start_disconnect(self):
+ NonBlockingTransport.start_disconnect(self)
+ self.send_BOSH(
+ (BOSHBody(attrs={'sid': self.bosh_sid, 'type': 'terminate'}), True))
- def get_http_socket(self):
- s = NonBlockingHTTP(
+ def get_new_http_socket(self):
+ s = NonBlockingHTTPBOSH(
raise_event=self.raise_event,
on_disconnect=self.disconnect,
idlequeue = self.idlequeue,
on_http_request_possible = self.on_http_request_possible,
- http_uri = self.bosh_host,
+ http_uri = self.bosh_uri,
http_port = self.bosh_port,
http_version = self.http_version,
- http_persistent = self.http_persistent)
- if self.current_recv_handler:
- s.onreceive(self.current_recv_handler)
+ http_persistent = self.http_persistent,
+ on_persistent_fallback = self.on_persistent_fallback)
+ s.onreceive(self.on_received_http)
+ s.set_stanza_build_cb(self.build_stanza)
return s
+
def onreceive(self, recv_handler):
if recv_handler is None:
recv_handler = self._owner.Dispatcher.ProcessNonBlocking
self.current_recv_handler = recv_handler
- for s in self.http_socks:
- s.onreceive(recv_handler)
- def http_socket_disconnect(self, socket):
- if self.http_persistent:
- self.disconnect()
+ def on_received_http(self, data, socket):
+ self.current_recv_socket = socket
+ self.current_recv_handler(data)
def disconnect(self, do_callback=True):
+ self.remove_bosh_wait_timeout()
if self.state == DISCONNECTED: return
self.fd = -1
for s in self.http_socks:
s.disconnect(do_callback=False)
NonBlockingTransport.disconnect(self, do_callback)
+
+def get_rand_number():
+ # with 50-bit random initial rid, session would have to go up
+ # to 7881299347898368 messages to raise rid over 2**53
+ # (see http://www.xmpp.org/extensions/xep-0124.html#rids)
+ # it's also used for sequence key initialization
+ r = random.Random()
+ r.seed()
+ return r.getrandbits(50)
+
+
+
+class AckChecker():
+ def __init__(self):
+ self.rid = get_rand_number()
+ self.ack = 1
+ self.last_rids = {}
+ self.not_acked = []
+
+
+ def get_not_acked_rids(self): return [rid for rid, st in self.not_acked]
+
+ def backup_stanza(self, stanza, socket):
+ socket.pending_requests += 1
+ rid = self.get_rid()
+ self.not_acked.append((rid, stanza))
+ stanza.setAttr('rid', str(rid))
+ self.last_rids[socket]=rid
+
+ if self.rid != self.ack + 1:
+ stanza.setAttr('ack', str(self.ack))
+ return stanza
+
+ def process_incoming_ack(self, socket, ack=None):
+ socket.pending_requests -= 1
+ if ack:
+ ack = int(ack)
+ else:
+ ack = self.last_rids[socket]
+
+ i = len([rid for rid, st in self.not_acked if ack >= rid])
+ self.not_acked = self.not_acked[i:]
+
+ self.ack = ack
+
+
+ def get_rid(self):
+ self.rid = self.rid + 1
+ return self.rid
+
+
+
+
+
+class KeyStack():
+ def __init__(self, count):
+ self.count = count
+ self.keys = []
+ self.reset()
+ self.first_call = True
+
+ def reset(self):
+ seed = str(get_rand_number())
+ self.keys = [sha.new(seed).hexdigest()]
+ for i in range(self.count-1):
+ curr_seed = self.keys[i]
+ self.keys.append(sha.new(curr_seed).hexdigest())
+
+ def get(self):
+ if self.first_call:
+ self.first_call = False
+ return (None, self.keys.pop())
+
+ if len(self.keys)>1:
+ return (self.keys.pop(), None)
+ else:
+ last_key = self.keys.pop()
+ self.reset()
+ new_key = self.keys.pop()
+ return (last_key, new_key)
+
+# http://www.xmpp.org/extensions/xep-0124.html#errorstatus-terminal
+bosh_errors = {
+ 'n/a': 'none or unknown condition in terminating body stanza',
+ 'bad-request': 'The format of an HTTP header or binding element received from the client is unacceptable (e.g., syntax error), or Script Syntax is not supported.',
+ 'host-gone': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is no longer serviced by the connection manager.',
+ 'host-unknown': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is unknown to the connection manager.',
+ 'improper-addressing': 'The initialization element lacks a "to" or "route" attribute (or the attribute has no value) but the connection manager requires one.',
+ 'internal-server-error': 'The connection manager has experienced an internal error that prevents it from servicing the request.',
+ 'item-not-found': '(1) "sid" is not valid, (2) "stream" is not valid, (3) "rid" is larger than the upper limit of the expected window, (4) connection manager is unable to resend response, (5) "key" sequence is invalid',
+ 'other-request': 'Another request being processed at the same time as this request caused the session to terminate.',
+ 'policy-violation': 'The client has broken the session rules (polling too frequently, requesting too frequently, too many simultaneous requests).',
+ 'remote-connection-failed': 'The connection manager was unable to connect to, or unable to connect securely to, or has lost its connection to, the server.',
+ 'remote-stream-error': 'Encapsulates an error in the protocol being transported.',
+ 'see-other-uri': 'The connection manager does not operate at this URI (e.g., the connection manager accepts only SSL or TLS connections at some https: URI rather than the http: URI requested by the client). The client may try POSTing to the URI in the content of the child element.',
+ 'system-shutdown': 'The connection manager is being shut down. All active HTTP sessions are being terminated. No new sessions can be created.',
+ 'undefined-condition': 'The error is not one of those defined herein; the connection manager SHOULD include application-specific information in the content of the