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 player
from ..struct.server import ServerConfig
STATE_UNKNOWN = (0, base.BaseClient)
STATE_QUERY = (1, query.QueryClient)
STATE_PLAYER = (2, player.PlayerClient)
class Client:
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int):
self.config = config
self.socket = socket
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
self.server = server
self.ip = ip
self.port = port
@ -22,7 +19,7 @@ class Client:
def set_state(self, state: tuple):
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):
if self.state == STATE_UNKNOWN:

View File

@ -1,15 +1,12 @@
import socket
import struct
from ..struct.server import ServerConfig
import logging
logger = logging.getLogger(__name__)
class BaseClient:
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int):
self.config = config
self.socket = socket
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
self.server = server
self.ip = ip
self.port = port
@ -17,3 +14,7 @@ class BaseClient:
async def on_packet(self, packet: bytes):
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
from .base import BaseClient
from ..struct.server import ServerConfig
import logging
logger = logging.getLogger(__name__)
class PlayerClient(BaseClient):
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int):
super().__init__(config, socket, ip, port)
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
super().__init__(server, ip, port)
logger.debug("Client resolved to PlayerClient")
async def on_packet(self, packet: bytes):

View File

@ -2,22 +2,111 @@ import socket
import struct
from .base import BaseClient
from ..struct.server import ServerConfig
from ..shared import glob
import logging
logger = logging.getLogger(__name__)
class QueryClient(BaseClient):
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int):
super().__init__(config, socket, ip, port)
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
super().__init__(server, ip, port)
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):
logger.debug("on_packet(%s)" % packet)
async def query_i(self):
len_hostname = len(self.config.hostname)
len_mode = len(self.config.mode)
len_language = len(self.config.language)
"""
if len(packet) != 11: # Just a ping with some random numbers at the end (14 bytes in total)
await self.send(packet)
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):
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):
await self.create_socket()
@ -34,7 +45,7 @@ class Server:
if addr not in self.clients:
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 asyncio.sleep(0)