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