208 строки
7.4 KiB
Python
Исполняемый файл
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)
|