Files

208 строки
7.4 KiB
Python
Исполняемый файл

import logging
from abc import ABC, abstractmethod
from .auth import IPeerAuth
from .dmrproto import DMRPBasePacket
from .dmrproto import DMRPPacketConfig, DMRPPacketPing, DMRPPacketPong
from .dmrproto import DMRPPacketData, DMRPPacketTalkerAlias
from .dmrproto import DMRPPacketLogin, DMRPPacketSalt, DMRPPacketAuth
from .dmrproto import DMRPPacketMasterClose, DMRPPacketRepeaterClose
from .dmrproto import DMRPPacketMasterNoAck, DMRPPacketAck
from .peer import Peer, PeerKeeper
from .pphex import hexdump
class IPCDispatcher(ABC):
"""
Dispatcher interface to pass into controller to call back
"""
@abstractmethod
def send_dg(self, data: bytes, addr: tuple) -> None: ...
@abstractmethod
def get_peer_keeper(self) -> PeerKeeper: ...
@abstractmethod
def get_peer_auth(self) -> IPeerAuth: ...
class PeerController:
def __init__(self, peer: Peer, dispatcher: IPCDispatcher) -> None:
self.peer: Peer = peer
self.dispatcher: IPCDispatcher = dispatcher
self.peer_auth: IPeerAuth = dispatcher.get_peer_auth()
def process_packet(self, p: DMRPBasePacket) -> bool:
"""
Process packet in context of the peer
Returns:
True - continue processing with dispatcher (for data)
False - stop processing (peer cmds)
"""
# CLOSE packet
if type(p) is DMRPPacketRepeaterClose:
logging.info(f"Peer {self.peer.logname} disconnected")
self.peer.die()
return False
# DATA packet
if type(p) is DMRPPacketData:
if not self.peer.status.is_applicable(Peer.Status.ACTIVE):
logging.debug(
f"Got DMRPPacketData "
f"while status is {self.peer.status.name}")
logging.error(f"Data from inactive peer {self.peer.logname}. "
f"Closing connection.")
self.peer.die()
self.send_close(p.peer_id)
return False
self.peer.update_active()
self.peer.update_unit(p.src_id)
return True
# PING packet
if type(p) is DMRPPacketPing:
if not self.peer.status.is_applicable(Peer.Status.ACTIVE):
logging.debug(
f"Got DMRPPacketPing "
f"while status is {self.peer.status.name}")
logging.error(f"Ping from inactive peer {self.peer.logname}. "
f"Closing connection.")
self.peer.die()
self.send_close(p.peer_id)
return False
logging.debug(f"Ping-pong {self.peer.logname}.")
self.peer.update_active()
self.send_pong()
return False
# LOGIN sequence
if type(p) is DMRPPacketLogin:
if not self.peer.status.is_applicable(Peer.Status.LOGIN):
logging.debug(
f"Got DMRPPacketLogin "
f"while status is {self.peer.status.name}")
logging.error(f"Bad login sequence from {self.peer.logname}. "
f"Closing connection.")
self.peer.die()
self.send_close(p.peer_id)
return False
peer_keeper: PeerKeeper = self.dispatcher.get_peer_keeper()
if len(peer_keeper.get_by_id(p.peer_id)) > 0:
logging.error(f"Auth {self.peer.logname}: "
f"peer id {p.peer_id} is already connected")
self.peer.die()
self.send_close(p.peer_id)
return False
if not self.peer_auth.allow_peer_id(p.peer_id):
logging.error(f"Auth {self.peer.logname}: "
f"peer id {p.peer_id} disabled")
self.peer.die()
self.send_close(p.peer_id)
return False
self.peer.status = Peer.Status.AUTH
logging.info(f"Login request from {self.peer.logname}"
f" ({p.peer_id})")
self.send_salt()
return False
if type(p) is DMRPPacketAuth:
if not self.peer.status.is_applicable(Peer.Status.AUTH):
logging.debug(
f"Got DMRPPacketAuth "
f"while status is {self.peer.status.name}")
logging.error(f"Bad login sequence from {self.peer.logname}. "
f"Closing connection.")
self.peer.die()
self.send_close(p.peer_id)
return False
if (self.peer.auth_salt is None or
not self.peer_auth.check_password(p.peer_id,
self.peer.auth_salt,
p.pass_hash)):
logging.error(
f"Auth {self.peer.logname}: password incorrect")
self.peer.die()
self.send_close(p.peer_id)
return False
self.peer.peer_id = p.peer_id
self.peer.status = Peer.Status.CONFIG
logging.info(f"Auth success {self.peer.logname}")
self.send_ack_ok()
return False
if type(p) is DMRPPacketConfig:
if not self.peer.status.is_applicable(Peer.Status.CONFIG):
logging.debug(
f"Got DMRPPacketConfig "
f"while status is {self.peer.status.name}")
logging.error(f"Bad login sequence from {self.peer.logname}. "
f"Closing connection.")
self.peer.die()
self.send_close(p.peer_id)
return False
config = {
'callsign': p.callsign,
'rx_freq': p.rx_freq,
'tx_freq': p.tx_freq,
'power': p.power,
'color_code': p.color_code,
'lat': p.lat,
'lon': p.lon,
'height': p.height,
'location': p.location,
'description': p.description,
'slots': p.slots,
'url': p.url,
'software_id': p.software_id,
'package_id': p.package_id,
}
logging.info(f"Config from {self.peer.logname}: " +
", ".join(f"{k}={v}" for k, v in config.items()));
self.peer.status = Peer.Status.ACTIVE
self.peer.config = config
self.send_ack_ok()
return False
return True
def send_data(self, p: DMRPBasePacket) -> None:
data: bytes = p.get_data()
logging.debug(
f"Sending packet to {self.peer.logname} | {len(data)} bytes:\n"
f"{hexdump(data)}\n"
f"{str(p)}\n")
self.dispatcher.send_dg(p.get_data(), self.peer.addr)
def send_close(self, peer_id: int = 0) -> None:
p = DMRPPacketMasterClose()
p.peer_id = peer_id if peer_id != 0 else self.peer.peer_id
self.send_data(p)
def send_salt(self) -> None:
p = DMRPPacketSalt()
p.set_random_salt()
self.peer.auth_salt = p.salt
self.send_data(p)
def send_ack_ok(self) -> None:
p = DMRPPacketAck()
p.peer_id = self.peer.peer_id
self.send_data(p)
def send_pong(self) -> None:
p = DMRPPacketPong()
p.peer_id = self.peer.peer_id
self.send_data(p)