Query protocol

This commit is contained in:
Emily 2020-03-29 23:05:00 +02:00
parent 0c0a8d327c
commit c1ad95d233
5 changed files with 119 additions and 22 deletions

View File

@ -5,16 +5,13 @@ from . import base
from . import query from . import query
from . import player from . import player
from ..struct.server import ServerConfig
STATE_UNKNOWN = (0, base.BaseClient) STATE_UNKNOWN = (0, base.BaseClient)
STATE_QUERY = (1, query.QueryClient) STATE_QUERY = (1, query.QueryClient)
STATE_PLAYER = (2, player.PlayerClient) STATE_PLAYER = (2, player.PlayerClient)
class Client: class Client:
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int): def __init__(self, server: "__ServerInstance__", ip: str, port: int):
self.config = config self.server = server
self.socket = socket
self.ip = ip self.ip = ip
self.port = port self.port = port
@ -22,7 +19,7 @@ class Client:
def set_state(self, state: tuple): def set_state(self, state: tuple):
self.state = state self.state = state
self.client = self.state[1](self.config, self.socket, self.ip, self.port) self.client = self.state[1](self.server, self.ip, self.port)
async def on_packet(self, packet: bytes): async def on_packet(self, packet: bytes):
if self.state == STATE_UNKNOWN: if self.state == STATE_UNKNOWN:

View File

@ -1,15 +1,12 @@
import socket import socket
import struct import struct
from ..struct.server import ServerConfig
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class BaseClient: class BaseClient:
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int): def __init__(self, server: "__ServerInstance__", ip: str, port: int):
self.config = config self.server = server
self.socket = socket
self.ip = ip self.ip = ip
self.port = port self.port = port
@ -17,3 +14,7 @@ class BaseClient:
async def on_packet(self, packet: bytes): async def on_packet(self, packet: bytes):
logger.debug("on_packet(%s)" % packet) logger.debug("on_packet(%s)" % packet)
async def send(self, packet: bytes):
sock: socket.socket = self.server.socket
sock.sendto(packet, (self.ip, self.port))

View File

@ -1,14 +1,13 @@
import socket import socket
from .base import BaseClient from .base import BaseClient
from ..struct.server import ServerConfig
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class PlayerClient(BaseClient): class PlayerClient(BaseClient):
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int): def __init__(self, server: "__ServerInstance__", ip: str, port: int):
super().__init__(config, socket, ip, port) super().__init__(server, ip, port)
logger.debug("Client resolved to PlayerClient") logger.debug("Client resolved to PlayerClient")
async def on_packet(self, packet: bytes): async def on_packet(self, packet: bytes):

View File

@ -2,22 +2,111 @@ import socket
import struct import struct
from .base import BaseClient from .base import BaseClient
from ..struct.server import ServerConfig
from ..shared import glob from ..shared import glob
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class QueryClient(BaseClient): class QueryClient(BaseClient):
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int): def __init__(self, server: "__ServerInstance__", ip: str, port: int):
super().__init__(config, socket, ip, port) super().__init__(server, ip, port)
logger.debug("Client resolved to QueryClient") logger.debug("Client resolved to QueryClient")
self.handlers = {
b"i": self.query_i,
b"r": self.query_r,
b"c": self.query_c,
b"d": self.query_d
}
async def on_packet(self, packet: bytes): async def on_packet(self, packet: bytes):
logger.debug("on_packet(%s)" % packet) logger.debug("on_packet(%s)" % packet)
async def query_i(self): """
len_hostname = len(self.config.hostname) if len(packet) != 11: # Just a ping with some random numbers at the end (14 bytes in total)
len_mode = len(self.config.mode) await self.send(packet)
len_language = len(self.config.language) return
"""
# ip ~~and port~~ the client connected to; (would be server ip ~~and port~~ if not though proxy)
# This could be used to check if client connected remotely in some way though a proxy
# SA:MP default server drops the connection if port does not match (when its client (not query protocol))
#server_ip = ".".join((str(x) for x in struct.unpack(b"<4B", packet[4:8])))
#server_port, = struct.unpack(b"<H", packet[8:10]) # This is not port, the wiki is wrong: https://wiki.sa-mp.com/wiki/Query
#logger.debug("[%s:%d] via %s:%d" % (self.ip, self.port, server_ip, server_port))
if len(packet) <= 10: # Invalid
return
if packet[10:11] in self.handlers:
packet = await self.handlers[packet[10:11]](packet)
if len(packet): # Send packet back if not 0
await self.send(packet)
async def query_i(self, packet: bytes) -> bytes:
len_hostname = len(self.server.config.hostname)
len_mode = len(self.server.config.mode)
len_language = len(self.server.config.language)
return packet + struct.pack(b"<?HHI%dsI%dsI%ds" % (len_hostname, len_mode, len_language),
len(self.server.config.password) != 0,
len(await self.server.get_online_players()),
self.server.config.max_players,
len_hostname,
self.server.config.hostname.encode(),
len_mode,
self.server.config.mode.encode(),
len_language,
self.server.config.language.encode()
)
async def query_r(self, packet: bytes) -> bytes:
data = []
rules = await self.server.get_rules()
data.append(len(rules))
for k, v in rules.items():
data += [
len(k), k,
len(v), v
]
return packet + struct.pack(b"<H" + (b"B%dsB%ds" * data[0]) % tuple(len(y) for x in rules.items() for y in x), *data)
async def query_c(self, packet: bytes) -> bytes:
data = []
scores = await self.server.get_players_scores()
data.append(len(scores))
for k, v in scores.items():
data += [
len(k), k,
v
]
return packet + struct.pack(b"<H" + (b"B%dsI" * data[0]) % tuple(len(x) for x in scores.keys()), *data)
async def query_d(self, packet: bytes) -> bytes:
data = []
players = await self.server.get_online_players()
data.append(len(players))
for p in players:
data += [
p["id"],
len(p["nick"]), p["nick"],
p["score"],
p["ping"]
]
return packet + struct.pack(b"<H" + (b"cc%dsII" * data[0]) % tuple(len(p["nick"]) for p in players), *data)
async def query_p(self, packet: bytes) -> bytes:
return packet

View File

@ -23,6 +23,17 @@ class Server:
async def on_command(self, cmd: str): async def on_command(self, cmd: str):
logger.debug("on_command(%s)" % cmd) logger.debug("on_command(%s)" % cmd)
async def get_online_players(self): # TODO: Get data from server's client objects
return [
{"nick": b"Sunpy", "score": 64, "ping": 8, "id": 1} # replace id with function to get player's id
]
async def get_rules(self): # TODO
return {b"Rule name sample": b"Rule value", b"weburl": b"https://git.osufx.com/Sunpy/sampy"}
async def get_players_scores(self): # TODO
return {b"Sunpy": 64, b"username": 123}
async def main(self): async def main(self):
await self.create_socket() await self.create_socket()
@ -34,7 +45,7 @@ class Server:
if addr not in self.clients: if addr not in self.clients:
ip, port = addr ip, port = addr
self.clients[addr] = Client(self.config, self.socket, ip, port) self.clients[addr] = Client(self, ip, port)
await self.clients[addr].on_packet(data) await self.clients[addr].on_packet(data)
await asyncio.sleep(0) await asyncio.sleep(0)