Files
azul/front/oscar/entry.py
T
Athena Funderburg 21f38ee3e1 production init
2026-05-26 16:41:23 +00:00

110 lines
2.6 KiB
Python

import asyncio
import struct
import settings
from core.backend import Backend
from typing import Optional, Callable
from threading import Thread
from util.misc import Logger, ProtocolRunner
from .ctrl import OSCARCtrl
from .proto.snac import SNACMessage
def register(loop: asyncio.AbstractEventLoop, backend: Backend) -> None:
backend.add_runner(ProtocolRunner('0.0.0.0', 5190, ListenerOSCAR, args=['OSCAR', backend, OSCARCtrl], service = 'OSCAR'))
class ListenerOSCAR(asyncio.Protocol):
logger: Logger
backend: Backend
controller: OSCARCtrl
transport: Optional[asyncio.WriteTransport]
buffer: bytes = b''
data_thread: Thread = None
def __init__(self,
logger_prefix: str,
backend: Backend,
controller_factory: Callable[[Logger, str, Backend], OSCARCtrl]) -> None:
super().__init__()
self.logger = Logger(logger_prefix, self)
self.backend = backend
self.controller = controller_factory(self.logger, 'direct', backend)
self.controller.close_callback = self._on_close
self.transport = None
def connection_made(self, transport: asyncio.BaseTransport) -> None:
self.controller.transport = transport
self.controller.on_connect()
self.logger.log_connect()
def connection_lost(self, exc: Optional[Exception]) -> None:
self.controller.close()
self.logger.log_disconnect()
def data_received(self, packet: bytes) -> None:
if self.backend.maintenance_mode:
self.transport.close()
return
self.buffer += packet
if self.data_thread is None or not self.data_thread.is_alive():
self.data_thread = Thread(target=self.parse_buffer)
self.data_thread.start()
def parse_buffer(self) -> None:
while True:
if self.buffer[0] != 0x2A:
break
# TODO(subpurple): Bufferize this?
frame, sequence, length = struct.unpack('>BHH', self.buffer[1:6])
if len(self.buffer) < length + 6:
break
if len(self.buffer) >= length + 6:
data = self.buffer[6:length + 6]
match frame:
# SIGNON
case 0x01:
self.controller.on_signon_frame(data)
# DATA (always contains a SNAC)
case 0x02:
if len(data) < 10:
return
msg = SNACMessage()
msg.unmarshal(data)
self.controller.on_data_frame(msg)
# ERROR
case 0x03:
self.controller.on_error_frame(data)
# SIGNOFF
case 0x04:
self.controller.on_signoff_frame(data)
# KEEP_ALIVE
case 0x05:
pass
case _:
self.logger.info('Recieved unknown frame:', str(frame), 'with data:', data.hex())
self.buffer = self.buffer[length + 6:]
if len(self.buffer) < 6:
break
def _on_close(self) -> None:
if self.transport is not None:
self.transport.close()