<s> from logging import getLogger <EOL> from threading import Lock <EOL> from time import time <EOL> from hashlib import sha1 <EOL> from hmac import new as hmac_new <EOL> from base64 import urlsafe_b64encode <EOL> from pylons import request , response , config <EOL> from turbulenz_local . tools import get_remote_addr <EOL> from turbulenz_local . lib . servicestatus import ServiceStatus <EOL> from turbulenz_local . controllers import BaseController <EOL> from turbulenz_local . decorators import jsonify , postonly <EOL> from turbulenz_local . models . gamelist import get_game_by_slug <EOL> from turbulenz_local . models . multiplayer import MultiplayerSession , MultiplayerServer <EOL> LOG = getLogger ( __name__ ) <EOL> def _calculate_registration_hmac ( mpserver_secret , ip ) : <EOL> h = hmac_new ( mpserver_secret , str ( ip ) , sha1 ) <EOL> return urlsafe_b64encode ( h . digest ( ) ) . rstrip ( '<STR_LIT:=>' ) <EOL> def _calculate_heartbeat_hmac ( mpserver_secret , ip , num_players , active_sessions ) : <EOL> h = hmac_new ( mpserver_secret , str ( ip ) , sha1 ) <EOL> h . update ( str ( num_players ) ) <EOL> if active_sessions : <EOL> h . update ( str ( active_sessions ) ) <EOL> return urlsafe_b64encode ( h . digest ( ) ) . rstrip ( '<STR_LIT:=>' ) <EOL> def _calculate_client_hmac ( secret , ip , session_id , client_id ) : <EOL> h = hmac_new ( secret , str ( ip ) , sha1 ) <EOL> h . update ( str ( session_id ) ) <EOL> h . update ( str ( client_id ) ) <EOL> return urlsafe_b64encode ( h . digest ( ) ) . rstrip ( '<STR_LIT:=>' ) <EOL> def _calculate_session_hmac ( mpserver_secret , ip , session_id ) : <EOL> h = hmac_new ( mpserver_secret , str ( ip ) , sha1 ) <EOL> h . update ( str ( session_id ) ) <EOL> return urlsafe_b64encode ( h . digest ( ) ) . rstrip ( '<STR_LIT:=>' ) <EOL> class MultiplayerController ( BaseController ) : <EOL> """<STR_LIT>""" <EOL> multiplayer_service = ServiceStatus . check_status_decorator ( '<STR_LIT>' ) <EOL> secret = config . get ( '<STR_LIT>' , None ) <EOL> lock = Lock ( ) <EOL> last_player_id = <NUM_LIT:0> <EOL> last_session_id = <NUM_LIT:0> <EOL> sessions = { } <EOL> servers = { } <EOL> @ classmethod <EOL> @ postonly <EOL> @ multiplayer_service <EOL> @ jsonify <EOL> def create ( cls , slug ) : <EOL> game = get_game_by_slug ( slug ) <EOL> if not game : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> try : <EOL> num_slots = int ( request . params [ '<STR_LIT>' ] ) <EOL> _ = request . params [ '<STR_LIT>' ] <EOL> except ( KeyError , ValueError ) : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> with cls . lock : <EOL> cls . last_player_id += <NUM_LIT:1> <EOL> player_id = str ( cls . last_player_id ) <EOL> sessions = cls . sessions <EOL> cls . last_session_id += <NUM_LIT:1> <EOL> session_id = str ( cls . last_session_id ) <EOL> server_address = None <EOL> secret = None <EOL> if cls . secret is not None : <EOL> stale_time = time ( ) - <NUM_LIT> <EOL> for ip , server in cls . servers . iteritems ( ) : <EOL> if stale_time < server . updated : <EOL> server_address = '<STR_LIT>' % ( ip , server . port ) <EOL> secret = cls . secret <EOL> break <EOL> session = MultiplayerSession ( session_id , slug , num_slots , server_address , secret ) <EOL> LOG . info ( '<STR_LIT>' , session_id , num_slots ) <EOL> sessions [ session_id ] = session <EOL> request_ip = get_remote_addr ( request ) <EOL> session . add_player ( player_id , request_ip ) <EOL> LOG . info ( '<STR_LIT>' , player_id , session_id ) <EOL> info = { '<STR_LIT>' : session . get_player_address ( request . host , request_ip , player_id ) , <EOL> '<STR_LIT>' : session_id , <EOL> '<STR_LIT>' : player_id , <EOL> '<STR_LIT>' : session . get_num_players ( ) } <EOL> return { '<STR_LIT>' : True , '<STR_LIT:data>' : info } <EOL> @ classmethod <EOL> @ postonly <EOL> @ multiplayer_service <EOL> @ jsonify <EOL> def join ( cls ) : <EOL> params = request . params <EOL> try : <EOL> session_id = params [ '<STR_LIT>' ] <EOL> _ = request . params [ '<STR_LIT>' ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> try : <EOL> session = cls . sessions [ session_id ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> with cls . lock : <EOL> session . update_status ( ) <EOL> request_ip = get_remote_addr ( request ) <EOL> player_id = params . get ( '<STR_LIT>' , None ) <EOL> if player_id is None : <EOL> cls . last_player_id += <NUM_LIT:1> <EOL> player_id = str ( cls . last_player_id ) <EOL> else : <EOL> stored_ip = session . get_player_ip ( player_id ) <EOL> if stored_ip is not None and request_ip != stored_ip : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False } <EOL> if session . can_join ( player_id ) : <EOL> session . add_player ( player_id , request_ip ) <EOL> LOG . info ( '<STR_LIT>' , player_id , session_id ) <EOL> info = { '<STR_LIT>' : session . get_player_address ( request . host , request_ip , player_id ) , <EOL> '<STR_LIT>' : session_id , <EOL> '<STR_LIT>' : player_id , <EOL> '<STR_LIT>' : session . get_num_players ( ) } <EOL> return { '<STR_LIT>' : True , '<STR_LIT:data>' : info } <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> @ classmethod <EOL> @ postonly <EOL> @ multiplayer_service <EOL> @ jsonify <EOL> def join_any ( cls , slug ) : <EOL> params = request . params <EOL> try : <EOL> _ = params [ '<STR_LIT>' ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> game = get_game_by_slug ( slug ) <EOL> if not game : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> with cls . lock : <EOL> cls . last_player_id += <NUM_LIT:1> <EOL> player_id = str ( cls . last_player_id ) <EOL> sessions = cls . sessions <EOL> session = session_id = None <EOL> for existing_session in sessions . itervalues ( ) : <EOL> if existing_session . game == slug : <EOL> existing_session . update_status ( ) <EOL> if existing_session . can_join ( player_id ) : <EOL> session = existing_session <EOL> session_id = existing_session . session_id <EOL> break <EOL> if session is not None : <EOL> request_ip = get_remote_addr ( request ) <EOL> session . add_player ( player_id , request_ip ) <EOL> LOG . info ( '<STR_LIT>' , player_id , session_id ) <EOL> info = { '<STR_LIT>' : session . get_player_address ( request . host , request_ip , player_id ) , <EOL> '<STR_LIT>' : session_id , <EOL> '<STR_LIT>' : player_id , <EOL> '<STR_LIT>' : session . get_num_players ( ) } <EOL> else : <EOL> info = { } <EOL> return { '<STR_LIT>' : True , '<STR_LIT:data>' : info } <EOL> @ classmethod <EOL> @ postonly <EOL> @ multiplayer_service <EOL> @ jsonify <EOL> def leave ( cls ) : <EOL> params = request . params <EOL> try : <EOL> session_id = params [ '<STR_LIT>' ] <EOL> player_id = params [ '<STR_LIT>' ] <EOL> _ = params [ '<STR_LIT>' ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> sessions = cls . sessions <EOL> try : <EOL> session = sessions [ session_id ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> with cls . lock : <EOL> if session . has_player ( player_id ) : <EOL> request_ip = get_remote_addr ( request ) <EOL> stored_ip = session . get_player_ip ( player_id ) <EOL> if stored_ip is not None and request_ip != stored_ip : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False } <EOL> LOG . info ( '<STR_LIT>' , player_id , session_id ) <EOL> session . remove_player ( player_id ) <EOL> cls . _clean_empty_sessions ( ) <EOL> return { '<STR_LIT>' : True } <EOL> @ classmethod <EOL> @ postonly <EOL> @ multiplayer_service <EOL> @ jsonify <EOL> def make_public ( cls ) : <EOL> params = request . params <EOL> try : <EOL> session_id = params [ '<STR_LIT>' ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> sessions = cls . sessions <EOL> try : <EOL> session = sessions [ session_id ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> session . public = True <EOL> return { '<STR_LIT>' : True } <EOL> @ classmethod <EOL> @ multiplayer_service <EOL> @ jsonify <EOL> def list_all ( cls ) : <EOL> request_host = request . host <EOL> sessions = [ ] <EOL> for session in cls . sessions . itervalues ( ) : <EOL> session . update_status ( ) <EOL> sessions . append ( session . get_info ( request_host ) ) <EOL> return { '<STR_LIT>' : True , '<STR_LIT:data>' : sessions } <EOL> @ classmethod <EOL> @ jsonify <EOL> def list ( cls , slug ) : <EOL> game = get_game_by_slug ( slug ) <EOL> if not game : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> request_host = request . host <EOL> sessions = [ ] <EOL> for session in cls . sessions . itervalues ( ) : <EOL> if session . game == slug : <EOL> session . update_status ( ) <EOL> sessions . append ( session . get_info ( request_host ) ) <EOL> return { '<STR_LIT>' : True , '<STR_LIT:data>' : sessions } <EOL> @ classmethod <EOL> @ multiplayer_service <EOL> @ jsonify <EOL> def read ( cls ) : <EOL> params = request . params <EOL> try : <EOL> session_id = params [ '<STR_LIT>' ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> try : <EOL> session = cls . sessions [ session_id ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> session . update_status ( ) <EOL> return { '<STR_LIT>' : True , '<STR_LIT:data>' : session . get_info ( request . host ) } <EOL> @ classmethod <EOL> @ postonly <EOL> @ multiplayer_service <EOL> @ jsonify <EOL> def register ( cls ) : <EOL> remote_addr = get_remote_addr ( request ) <EOL> try : <EOL> params = request . params <EOL> host = params . get ( '<STR_LIT:host>' , remote_addr ) <EOL> hmac = params [ '<STR_LIT>' ] <EOL> server = MultiplayerServer ( params ) <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> except ValueError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> calculated_hmac = _calculate_registration_hmac ( cls . secret , remote_addr ) <EOL> if hmac != calculated_hmac : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> LOG . info ( '<STR_LIT>' , remote_addr , host , server . port ) <EOL> cls . servers [ host ] = server <EOL> return { '<STR_LIT>' : True } <EOL> @ classmethod <EOL> @ postonly <EOL> @ multiplayer_service <EOL> @ jsonify <EOL> def heartbeat ( cls ) : <EOL> remote_addr = get_remote_addr ( request ) <EOL> try : <EOL> params = request . params <EOL> host = params . get ( '<STR_LIT:host>' , remote_addr ) <EOL> num_players = params . get ( '<STR_LIT>' ) <EOL> hmac = params [ '<STR_LIT>' ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> calculated_hmac = _calculate_heartbeat_hmac ( cls . secret , remote_addr , num_players , None ) <EOL> if hmac != calculated_hmac : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> try : <EOL> server = cls . servers [ host ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> try : <EOL> server . update ( request . params ) <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> except ValueError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> return { '<STR_LIT>' : True } <EOL> @ classmethod <EOL> @ postonly <EOL> @ multiplayer_service <EOL> @ jsonify <EOL> def unregister ( cls ) : <EOL> remote_addr = get_remote_addr ( request ) <EOL> params = request . params <EOL> host = params . get ( '<STR_LIT:host>' , remote_addr ) <EOL> try : <EOL> hmac = params [ '<STR_LIT>' ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> calculated_hmac = _calculate_registration_hmac ( cls . secret , remote_addr ) <EOL> if hmac != calculated_hmac : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> try : <EOL> server = cls . servers [ host ] <EOL> del cls . servers [ host ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> LOG . info ( '<STR_LIT>' , host , server . port ) <EOL> return { '<STR_LIT>' : True } <EOL> @ classmethod <EOL> @ postonly <EOL> @ multiplayer_service <EOL> @ jsonify <EOL> def client_leave ( cls ) : <EOL> remote_addr = get_remote_addr ( request ) <EOL> params = request . params <EOL> try : <EOL> session_id = params [ '<STR_LIT>' ] <EOL> player_id = params [ '<STR_LIT>' ] <EOL> hmac = params [ '<STR_LIT>' ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> calculated_hmac = _calculate_client_hmac ( cls . secret , remote_addr , session_id , player_id ) <EOL> if hmac != calculated_hmac : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> sessions = cls . sessions <EOL> try : <EOL> session = sessions [ session_id ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> with cls . lock : <EOL> if session . has_player ( player_id ) : <EOL> request_ip = get_remote_addr ( request ) <EOL> stored_ip = session . get_player_ip ( player_id ) <EOL> if stored_ip is not None and request_ip != stored_ip : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False } <EOL> LOG . info ( '<STR_LIT>' , player_id , session_id ) <EOL> session . remove_player ( player_id ) <EOL> cls . _clean_empty_sessions ( ) <EOL> return { '<STR_LIT>' : True } <EOL> @ classmethod <EOL> @ postonly <EOL> @ multiplayer_service <EOL> @ jsonify <EOL> def delete_session ( cls ) : <EOL> remote_addr = get_remote_addr ( request ) <EOL> params = request . params <EOL> try : <EOL> session_id = params [ '<STR_LIT>' ] <EOL> hmac = params [ '<STR_LIT>' ] <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> calculated_hmac = _calculate_session_hmac ( cls . secret , remote_addr , session_id ) <EOL> if hmac != calculated_hmac : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> try : <EOL> with cls . lock : <EOL> del cls . sessions [ session_id ] <EOL> LOG . info ( '<STR_LIT>' , session_id ) <EOL> except KeyError : <EOL> response . status_int = <NUM_LIT> <EOL> return { '<STR_LIT>' : False , '<STR_LIT>' : '<STR_LIT>' } <EOL> return { '<STR_LIT>' : True } <EOL> @ classmethod <EOL> def _clean_empty_sessions ( cls ) : <EOL> sessions = cls . sessions <EOL> to_delete = [ session_id <EOL> for session_id , existing_session in sessions . iteritems ( ) <EOL> if <NUM_LIT:0> == existing_session . get_num_players ( ) ] <EOL> for session_id in to_delete : <EOL> LOG . info ( '<STR_LIT>' , session_id ) <EOL> del sessions [ session_id ] <EOL> @ classmethod <EOL> def remove_player ( cls , session_id , player_id ) : <EOL> try : <EOL> sessions = cls . sessions <EOL> session = sessions [ session_id ] <EOL> with cls . lock : <EOL> if session . has_player ( player_id ) : <EOL> LOG . info ( '<STR_LIT>' , player_id , session_id ) <EOL> session . remove_player ( player_id ) <EOL> cls . _clean_empty_sessions ( ) <EOL> except KeyError : <EOL> pass </s>
