192 lines
6.6 KiB
Python
192 lines
6.6 KiB
Python
# This file is a part of sedbgmux, an open source DebugMux client.
|
|
# Copyright (c) 2022 Vadim Yanitskiy <axilirator@gmail.com>
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import logging as log
|
|
import threading
|
|
import socket
|
|
import queue
|
|
import enum
|
|
import abc
|
|
import sys
|
|
|
|
from typing import Any, Optional
|
|
from construct import Container
|
|
from proto import DbgMuxFrame
|
|
|
|
|
|
class DbgMuxFrameHandler(abc.ABC):
|
|
''' Abstract DebugMux frame handler '''
|
|
|
|
_tx_queue: Optional[queue.Queue] = None
|
|
|
|
def send(self, msg_type: DbgMuxFrame.MsgType, msg: Any = b''):
|
|
''' Called by child classes to send some message '''
|
|
assert self._tx_queue is not None
|
|
self._tx_queue.put((msg_type, msg))
|
|
|
|
@abc.abstractmethod
|
|
def _handle_frame(self, frame: Container) -> None:
|
|
''' Handle the given DebugMux frame '''
|
|
|
|
|
|
class ConnState(enum.Enum):
|
|
''' Connection state for DbgMuxConnHandler '''
|
|
NotEstablished = enum.auto()
|
|
Establishing = enum.auto()
|
|
Established = enum.auto()
|
|
|
|
|
|
class DbgMuxConnHandler(DbgMuxFrameHandler):
|
|
''' Abstract DebugMux connection handler '''
|
|
|
|
def __init__(self):
|
|
self.conn_state: ConnState = ConnState.NotEstablished
|
|
self.ConnRef: int = 0xffff
|
|
self.DPRef: int = 0xffff
|
|
|
|
def establish(self, DPRef: int) -> None:
|
|
assert self.conn_state == ConnState.NotEstablished
|
|
log.info("Establishing connection with DPRef=0x%04x", DPRef)
|
|
self.send(DbgMuxFrame.MsgType.ConnEstablish, dict(DPRef=DPRef))
|
|
self.conn_state = ConnState.Establishing
|
|
self.DPRef = DPRef
|
|
|
|
def send_data(self, data: bytes) -> None:
|
|
''' Called by child classes to send connection data '''
|
|
assert self.conn_state == ConnState.Established
|
|
msg = dict(ConnRef=self.ConnRef, Data=data)
|
|
self.send(DbgMuxFrame.MsgType.ConnData, msg)
|
|
|
|
def _match(self, frame: Container,
|
|
msg_type: DbgMuxFrame.MsgType,
|
|
msg_fields: dict = { }) -> bool:
|
|
if frame['MsgType'] != msg_type:
|
|
return False
|
|
for (key, val) in msg_fields.items():
|
|
if key not in frame['Msg']:
|
|
return False
|
|
if frame['Msg'][key] != val:
|
|
return False
|
|
return True
|
|
|
|
def _handle_frame(self, frame: Container) -> None:
|
|
''' Handle the given DebugMux frame '''
|
|
|
|
if self.conn_state == ConnState.Established:
|
|
fields = dict(ConnRef=self.ConnRef)
|
|
if self._match(frame, DbgMuxFrame.MsgType.ConnData, fields):
|
|
self._handle_data(frame['Msg']['Data'])
|
|
self.send(DbgMuxFrame.MsgType.Ack)
|
|
raise StopIteration
|
|
elif self._match(frame, DbgMuxFrame.MsgType.ConnTerminated, fields):
|
|
log.info('Connection terminated (DPRef=0x%04x, ConnRef=0x%04x)',
|
|
self.DPRef, self.ConnRef)
|
|
self.conn_state = ConnState.NotEstablished
|
|
self._handle_terminate()
|
|
self.send(DbgMuxFrame.MsgType.Ack)
|
|
raise StopIteration
|
|
elif self.conn_state == ConnState.Establishing:
|
|
# Match ConnEstablished with our DPRef and any ConnRef
|
|
if self._match(frame, DbgMuxFrame.MsgType.ConnEstablished, dict(DPRef=self.DPRef)):
|
|
log.info('Connection established (DPRef=0x%04x, ConnRef=0x%04x)',
|
|
self.DPRef, frame['Msg']['ConnRef'])
|
|
self.conn_state = ConnState.Established
|
|
self.ConnRef = frame['Msg']['ConnRef']
|
|
self._handle_establish()
|
|
self.send(DbgMuxFrame.MsgType.Ack)
|
|
raise StopIteration
|
|
|
|
@abc.abstractmethod
|
|
def _handle_data(self, data: bytes) -> None:
|
|
''' Called on reciept of connection data '''
|
|
|
|
@abc.abstractmethod
|
|
def _handle_establish(self) -> None:
|
|
''' Called on connection establishment '''
|
|
|
|
@abc.abstractmethod
|
|
def _handle_terminate(self) -> None:
|
|
''' Called on connection termination '''
|
|
|
|
|
|
class DbgMuxConnInteractiveTerminal(DbgMuxConnHandler):
|
|
def __init__(self, *args, **kw):
|
|
self.attached: bool = False
|
|
super().__init__(*args)
|
|
|
|
def attach(self):
|
|
self.attached = True
|
|
while True:
|
|
try:
|
|
line = input()
|
|
if self.conn_state == ConnState.Established:
|
|
self.send_data(bytes(line, 'ascii'))
|
|
except (KeyboardInterrupt, EOFError) as e:
|
|
break
|
|
self.attached = False
|
|
|
|
def _handle_data(self, data: bytes) -> None:
|
|
if self.attached:
|
|
sys.stdout.write(data.decode('ascii'))
|
|
|
|
def _handle_establish(self) -> None:
|
|
pass
|
|
|
|
def _handle_terminate(self) -> None:
|
|
pass
|
|
|
|
|
|
class DbgMuxConnUdpBridge(DbgMuxConnHandler):
|
|
DGRAM_MAX_LEN: int = 1024
|
|
|
|
def __init__(self, *args, **kw):
|
|
super().__init__(*args)
|
|
|
|
self.raddr: str = kw.get('raddr', '127.0.0.1')
|
|
self.rport: int = kw.get('rport', 9999)
|
|
self.laddr: str = kw.get('laddr', '127.0.0.1')
|
|
self.lport: int = kw.get('lport', 8888)
|
|
|
|
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
self._sock.bind((self.laddr, self.lport))
|
|
self._sock.settimeout(0.5)
|
|
|
|
self._thread = threading.Thread(target=self._worker,
|
|
daemon=True)
|
|
self._shutdown = threading.Event()
|
|
|
|
def _worker(self) -> None:
|
|
while not self._shutdown.is_set():
|
|
try:
|
|
(data, addr) = self._sock.recvfrom(self.DGRAM_MAX_LEN)
|
|
if self.conn_state == ConnState.Established:
|
|
self.send_data(data)
|
|
except TimeoutError:
|
|
pass
|
|
|
|
def _handle_data(self, data: bytes) -> None:
|
|
self._sock.sendto(data, (self.raddr, self.rport))
|
|
|
|
def _handle_establish(self) -> None:
|
|
self._shutdown.clear()
|
|
self._thread.start()
|
|
|
|
def _handle_terminate(self) -> None:
|
|
self._shutdown.set()
|