1
0
mirror of synced 2024-12-01 00:57:18 +01:00
bemaniutils/bemani/backend/dispatch.py

175 lines
6.5 KiB
Python
Raw Normal View History

import copy
from typing import Optional, Dict, Any
from bemani.backend.base import Model, Base, Status
from bemani.protocol import Node
from bemani.data import Data
class UnrecognizedPCBIDException(Exception):
def __init__(self, pcbid: str, model: str, ip: str) -> None:
self.pcbid = pcbid
self.model = model
self.ip = ip
class Dispatch:
"""
Dispatch object responsible for taking a decoded tree of Node objects
from a game, looking up config, dispatching it to the correct game
class and then returning a response.
"""
def __init__(self, config: Dict[str, Any], data: Data, verbose: bool) -> None:
"""
Initialize the Dispatch object.
Parameters:
config - A dictionary of configuration used for various settigs.
data - A Data singleton for DB access.
verbose - Whether we get chatty to stdout or not.
"""
self.__verbose = verbose
self.__data = data
self.__config = config
def log(self, msg: str, *args: Any, **kwargs: Any) -> None:
"""
Given a message, format it and print it.
Note that this only prints to stdout if we were initialized with
verbose = True.
Parameters:
msg - A formatstring that should be formatted with any
optional arguments or keyword arguments.
"""
if self.__verbose:
print(msg.format(*args, **kwargs))
def handle(self, tree: Node) -> Optional[Node]:
"""
Given a packet from a game, handle it and return a response.
Parameters:
tree - A Node representing the root of a tree. Expected to
come from an external game.
Returns:
A Node representing the root of a response tree, or None if
we had a problem parsing or generating a response.
"""
self.log("Received request:\n{}", tree)
if tree.name != 'call':
# Invalid request
self.log("Invalid root node {}", tree.name)
return None
if len(tree.children) != 1:
# Invalid request
self.log("Invalid number of children for root node")
return None
modelstring = tree.attribute('model')
model = Model.from_modelstring(modelstring)
pcbid = tree.attribute('srcid')
# If we are enforcing, bail out if we don't recognize thie ID
pcb = self.__data.local.machine.get_machine(pcbid)
if self.__config['server']['enforce_pcbid'] and pcb is None:
self.log("Unrecognized PCBID {}", pcbid)
raise UnrecognizedPCBIDException(pcbid, modelstring, self.__config['client']['address'])
# If we don't have a Machine, but we aren't enforcing, we must create it
if pcb is None:
pcb = self.__data.local.machine.create_machine(pcbid)
request = tree.children[0]
config = copy.copy(self.__config)
config['machine'] = {
'pcbid': pcbid,
'arcade': pcb.arcade,
}
# If the machine we looked up is in an arcade, override the global
# paseli settings with the arcade paseli settings.
if pcb.arcade is not None:
arcade = self.__data.local.machine.get_arcade(pcb.arcade)
if arcade is not None:
config['paseli']['enabled'] = arcade.data.get_bool('paseli_enabled')
config['paseli']['infinite'] = arcade.data.get_bool('paseli_infinite')
if arcade.data.get_bool('mask_services_url'):
# Mask the address, no matter what the server settings are
config['server']['uri'] = None
# If we don't have a server URI, we should add the default
if 'uri' not in config['server']:
config['server']['uri'] = None
game = Base.create(self.__data, config, model)
method = request.attribute('method')
response = None
# If we are enforcing, make sure the PCBID isn't specified to be
# game-specific
if self.__config['server']['enforce_pcbid'] and pcb.game is not None:
if pcb.game != game.game:
self.log("PCBID {} assigned to game {}, but connected from game {}", pcbid, pcb.game, game.game)
raise UnrecognizedPCBIDException(pcbid, modelstring, self.__config['client']['address'])
if pcb.version is not None:
if pcb.version > 0 and pcb.version != game.version:
self.log(
"PCBID {} assigned to game {} version {}, but connected from game {} version {}",
pcbid,
pcb.game,
pcb.version,
game.game,
game.version,
)
raise UnrecognizedPCBIDException(pcbid, modelstring, self.__config['client']['address'])
if pcb.version < 0 and (-pcb.version) < game.version:
self.log(
"PCBID {} assigned to game {} maximum version {}, but connected from game {} version {}",
pcbid,
pcb.game,
-pcb.version,
game.game,
game.version,
)
raise UnrecognizedPCBIDException(pcbid, modelstring, self.__config['client']['address'])
# First, try to handle with specific service/method function
try:
handler = getattr(game, f'handle_{request.name}_{method}_request')
except AttributeError:
handler = None
if handler is not None:
response = handler(request)
if response is None:
# Now, try to pass it off to a generic service handler
try:
handler = getattr(game, f'handle_{request.name}_request')
except AttributeError:
handler = None
if handler is not None:
response = handler(request)
if response is None:
# Unrecognized handler
self.log(f"Unrecognized service {request.name} method {method}")
return None
# Make sure we have a status value if one wasn't provided
if 'status' not in response.attributes:
response.set_attribute('status', str(Status.SUCCESS))
root = Node.void('response')
root.add_child(response)
root.set_attribute('dstid', pcbid)
self.log("Sending response:\n{}", root)
return root