mirror of git://git.psyced.org/git/pypsyc
334 lines
12 KiB
Python
334 lines
12 KiB
Python
"""
|
|
pypsyc.protocol
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Protocol classes for PSYC :class:`~pypsyc.server.Entity`\s.
|
|
|
|
:copyright: 2010 by Manuel Jacob
|
|
:license: MIT
|
|
"""
|
|
import hmac
|
|
from hashlib import sha256
|
|
from itertools import izip
|
|
from os import urandom
|
|
|
|
from pypsyc.core.psyc import PSYCPacket
|
|
|
|
|
|
class Error(Exception):
|
|
pass
|
|
|
|
class ServerProtocol(object):
|
|
def __init__(self, package):
|
|
self.package = package
|
|
|
|
def sendmsg(self, *args, **kwds):
|
|
return self.package.entity.sendmsg(*args, **kwds)
|
|
|
|
def castmsg(self, *args, **kwds):
|
|
return self.package.entity.castmsg(*args, **kwds)
|
|
|
|
def state_set(self, *args, **kwds):
|
|
return self.package.entity.context_master.state_set(*args, **kwds)
|
|
|
|
def state_add(self, *args, **kwds):
|
|
return self.package.entity.context_master.state_add(*args, **kwds)
|
|
|
|
def state_remove(self, *args, **kwds):
|
|
return self.package.entity.context_master.state_remove(*args, **kwds)
|
|
|
|
class ClientProtocol(object):
|
|
def __init__(self, package):
|
|
self.package = package
|
|
|
|
def sendmsg(self, *args, **kwds):
|
|
return self.package.psyc.sendmsg(*args, **kwds)
|
|
|
|
|
|
class UnknownTargetError(Error):
|
|
pass
|
|
|
|
class DeliveryFailedError(Error):
|
|
pass
|
|
|
|
def check_response(packet, default_mc):
|
|
if packet.mc == '_error_unknown_target':
|
|
raise UnknownTargetError(packet.cvars['_uni'])
|
|
if packet.mc == '_failure_unsuccessful_delivery':
|
|
raise DeliveryFailedError(packet.data, packet.cvars['_uni'])
|
|
assert packet.mc == default_mc, "response mc is %s" % packet.mc
|
|
|
|
|
|
class RoutingErrorRoot(ServerProtocol):
|
|
def send_unknown_target_error(self, target, uni):
|
|
self.sendmsg(target, mc='_error_unknown_target', _uni=uni)
|
|
|
|
def send_delivery_failed_error(self, target, uni, message):
|
|
self.sendmsg(target, mc='_failure_unsuccessful_delivery', data=message,
|
|
_uni=uni)
|
|
|
|
|
|
class RoutingErrorRelaying(ServerProtocol):
|
|
def handle_error_unknown_target(self, packet):
|
|
self.package.unknown_target_error(packet.cvars['_uni'])
|
|
|
|
def handle_failure_unsuccessful_delivery(self, packet):
|
|
self.package.delivery_failed_error(packet.cvars['_uni'], packet.data)
|
|
|
|
def relay_unknown_target_error(self, target, uni):
|
|
self.sendmsg(target, mc='_error_unknown_target', _uni=uni)
|
|
|
|
def relay_delivery_failed_error(self, target, uni, message):
|
|
self.sendmsg(target, mc='_failure_unsuccessful_delivery', data=message,
|
|
_uni=uni)
|
|
|
|
|
|
class RoutingErrorClient(ClientProtocol):
|
|
def handle_error_unknown_target(self, packet):
|
|
self.package.unknown_target_error(packet.cvars['_uni'])
|
|
|
|
def handle_failure_unsuccessful_delivery(self, packet):
|
|
self.package.delivery_failed_error(packet.cvars['_uni'], packet.data)
|
|
|
|
|
|
class AuthenticationError(Error):
|
|
pass
|
|
|
|
class LinkingServer(ServerProtocol):
|
|
def __init__(self, package):
|
|
ServerProtocol.__init__(self, package)
|
|
self.attempts = {}
|
|
|
|
def handle_request_link(self, p):
|
|
password = p.cvars.get('_password')
|
|
if not password:
|
|
nonce = urandom(20)
|
|
self.attempts[p.header.source] = (nonce, p.cvars['_resource'])
|
|
return PSYCPacket.from_kwds(mc='_query_password', _nonce=nonce)
|
|
|
|
else:
|
|
nonce, resource = self.attempts[p.header.source]
|
|
try:
|
|
self.package.authenticate(('hmac', password, nonce),
|
|
p.header.source, resource)
|
|
except AuthenticationError as e:
|
|
return PSYCPacket(mc='_error_authentication', data=e.args[0])
|
|
return PSYCPacket(mc='_echo_link')
|
|
|
|
class LinkingClient(ClientProtocol):
|
|
def link(self, target, resource, password):
|
|
r = self.sendmsg(target, mc='_request_link', _resource=resource)
|
|
check_response(r, '_query_password')
|
|
|
|
digest = hmac.new(password, r.cvars['_nonce'], sha256).digest()
|
|
r = self.sendmsg(target, mc='_request_link', _password=digest)
|
|
if r.mc == '_error_authentication':
|
|
raise AuthenticationError(r.data)
|
|
check_response(r, '_echo_link')
|
|
|
|
|
|
class Messaging(ClientProtocol):
|
|
def send_private_message(self, target, message, relay=None):
|
|
packet = PSYCPacket(mc='_message_private', data=message)
|
|
if relay:
|
|
self.sendmsg(relay, packet, {'_target_relay': target})
|
|
else:
|
|
self.sendmsg(target, packet)
|
|
|
|
def handle_message_private(self, packet):
|
|
self.package.private_message(packet.header.source, packet.data)
|
|
|
|
class MessageRelaying(ServerProtocol):
|
|
def handle_message_private(self, packet):
|
|
target_relay = packet.header.get('_target_relay')
|
|
if target_relay is not None:
|
|
self.package.private_message_relay(packet.header.source,
|
|
target_relay,
|
|
packet.data)
|
|
else:
|
|
self.package.private_message(packet.header.source, packet.data)
|
|
|
|
def send_private_message(self, target, message):
|
|
self.sendmsg(target, mc='_message_private', data=message)
|
|
|
|
def relay_private_message(self, source, target, message):
|
|
self.sendmsg(target, header={'_source_relay': source},
|
|
mc='_message_private', data=message)
|
|
|
|
|
|
class ConferencingServer(ServerProtocol):
|
|
def cast_member_entered(self, member, nick):
|
|
self.state_add(_list_members=member, _list_members_nick=nick)
|
|
|
|
def cast_member_left(self, member):
|
|
self.state_remove(_list_members=member)
|
|
|
|
def handle_message_public(self, packet):
|
|
self.package.public_message(packet.header.source, packet.data)
|
|
|
|
def cast_public_message(self, source, message):
|
|
self.castmsg(mc='_message_public', data=message, _member=source)
|
|
|
|
class ConferencingClient(ClientProtocol):
|
|
def state_set_list_members(self, context, vars):
|
|
for args in izip(vars['_list_members'].split('|'),
|
|
vars['_list_members_nick'].split('|')):
|
|
self.package.member_entered(context, *args)
|
|
|
|
def state_add_list_members(self, context, vars):
|
|
self.package.member_entered(context, vars['_list_members'],
|
|
vars['_list_members_nick'])
|
|
|
|
def state_remove_list_members(self, context, vars):
|
|
self.package.member_left(context, vars['_list_members'])
|
|
|
|
def send_public_message(self, target, message):
|
|
self.sendmsg(target, mc='_message_public', data=message)
|
|
|
|
def handle_message_public(self, packet):
|
|
self.package.public_message(
|
|
packet.header.context, packet.cvars['_member'], packet.data)
|
|
|
|
|
|
class EntryDeniedError(Error):
|
|
pass
|
|
|
|
class ContextMaster(ServerProtocol):
|
|
def handle_request_context_enter(self, packet):
|
|
try:
|
|
state = self.package.enter_request(packet.header.source)
|
|
except EntryDeniedError as e:
|
|
return PSYCPacket(mc='_error_illegal_entry', data=e.args[0])
|
|
return PSYCPacket({'=': state}, '_echo_context_enter')
|
|
|
|
def handle_request_context_leave(self, packet):
|
|
self.package.leave_context(packet.header.source)
|
|
return PSYCPacket(mc='_echo_context_leave')
|
|
|
|
class ContextSlave(ServerProtocol):
|
|
def enter(self, target):
|
|
r = self.sendmsg(target, mc='_request_context_enter')
|
|
if r.mc == '_error_illegal_entry':
|
|
raise EntryDeniedError(r.data)
|
|
check_response(r, '_echo_context_enter')
|
|
return r.modifiers.get('=')
|
|
|
|
def leave(self, target):
|
|
r = self.sendmsg(target, mc='_request_context_leave')
|
|
check_response(r, '_echo_context_leave')
|
|
|
|
|
|
class UnauthorizedError(Error):
|
|
pass
|
|
|
|
class ClientInterfaceServer(ServerProtocol):
|
|
client_interface = {
|
|
'subscribe': lambda p: (p.cvars['_group'], p.header.source),
|
|
'unsubscribe': lambda p: (p.cvars['_group'], p.header.source),
|
|
'presence': lambda p: (int(p.cvars['_degree_availability']),),
|
|
'add_friend': lambda p: (p.cvars['_person'],),
|
|
'remove_friend': lambda p: (p.cvars['_person'],)
|
|
}
|
|
|
|
def handle_request_do(self, packet):
|
|
if not packet.header.source.is_descendant_of(self.package.entity.uni):
|
|
return PSYCPacket(mc='_error_illegal_source')
|
|
action = packet.mc[12:]
|
|
method = self.client_interface.get(action, None)
|
|
try:
|
|
getattr(self.package, 'client_' + action)(*method(packet))
|
|
except UnknownTargetError as e:
|
|
return PSYCPacket.from_kwds(mc='_error_unknown_target',
|
|
_uni=e.args[0])
|
|
except DeliveryFailedError as e:
|
|
return PSYCPacket.from_kwds(mc='_failure_unsuccessful_delivery',
|
|
data=e.args[0], _uni=e.args[1])
|
|
return PSYCPacket(mc='_echo_do_' + action)
|
|
|
|
class ClientInterfaceClient(ClientProtocol):
|
|
def subscribe(self, uni):
|
|
self._request_do('subscribe', _group=uni)
|
|
|
|
def unsubscribe(self, uni):
|
|
self._request_do('unsubscribe', _group=uni)
|
|
|
|
def cast_presence(self, availability):
|
|
self._request_do('presence', _degree_availability=str(availability))
|
|
|
|
def add_friend(self, uni):
|
|
self._request_do('add_friend', _person=uni)
|
|
|
|
def remove_friend(self, uni):
|
|
self._request_do('remove_friend', _person=uni)
|
|
|
|
def _request_do(self, command, **kwds):
|
|
r = self.sendmsg(self.package.uni, mc='_request_do_' + command, **kwds)
|
|
if r.mc == '_error_illegal_source':
|
|
raise UnauthorizedError
|
|
check_response(r, '_echo_do_' + command)
|
|
|
|
|
|
class FriendshipPendingError(Error):
|
|
pass
|
|
|
|
class FriendshipEstablishedError(Error):
|
|
pass
|
|
|
|
class FriendshipServer(ServerProtocol):
|
|
def handle_request_friendship(self, packet):
|
|
state = self.package.friendship_request(packet.header.source)
|
|
return PSYCPacket(mc='_echo_friendship_' + state)
|
|
|
|
def establish(self, uni):
|
|
r = self.sendmsg(uni, mc='_request_friendship')
|
|
if r.mc == '_echo_friendship_pending':
|
|
return 'pending'
|
|
if r.mc == '_echo_friendship_established':
|
|
return 'established'
|
|
else:
|
|
raise Exception("unknown mc: %s" % r.mc)
|
|
|
|
def handle_request_friendship_remove(self, packet):
|
|
self.package.friendship_cancel(packet.header.source)
|
|
return PSYCPacket(mc='_echo_friendship_removed')
|
|
|
|
def remove(self, uni):
|
|
r = self.sendmsg(uni, mc='_request_friendship_remove')
|
|
check_response(r, '_echo_friendship_removed')
|
|
|
|
def send_friendships(self, target, friendships):
|
|
unis = friendships.iterkeys()
|
|
states = friendships.itervalues()
|
|
self.sendmsg(target, mc='_notice_list_friendships',
|
|
_list_friendships='|'.join(unis),
|
|
_list_friendships_state='|'.join(i['state'] for i
|
|
in states))
|
|
|
|
def send_updated_friendship(self, target, uni, fs):
|
|
self.sendmsg(target, mc='_notice_friendship_updated',
|
|
_person=uni, _state=fs['state'])
|
|
|
|
def send_removed_friendship(self, target, uni):
|
|
self.sendmsg(target, mc='_notice_friendship_removed', _person=uni)
|
|
|
|
def cast_presence(self, availability):
|
|
self.state_set(_degree_availability=str(availability))
|
|
|
|
class FriendshipClient(ClientProtocol):
|
|
def handle_notice_list_friendships(self, packet):
|
|
if packet.cvars['_list_friendships'] == '':
|
|
return
|
|
for args in izip(packet.cvars['_list_friendships'].split('|'),
|
|
packet.cvars['_list_friendships_state'].split('|')):
|
|
self.package.friendship(*args)
|
|
|
|
def handle_notice_friendship_updated(self, packet):
|
|
self.package.friendship(packet.cvars['_person'],
|
|
packet.cvars['_state'])
|
|
|
|
def handle_notice_friendship_removed(self, packet):
|
|
self.package.friendship_removed(packet.cvars['_person'])
|
|
|
|
def state_set_degree_availability(self, context, vars):
|
|
self.package.presence(context, int(vars['_degree_availability']))
|