pypsyc/mjacob2/pypsyc/protocol.py

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']))