246 lines
10 KiB
Python
Executable File
246 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# This file is a part of sedbgmux, an open source DebugMux client.
|
|
# Copyright (c) 2022-2023 Vadim Yanitskiy <fixeria@osmocom.org>
|
|
#
|
|
# 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
|
|
import argparse
|
|
import cmd2
|
|
import sys
|
|
|
|
from typing import List
|
|
|
|
from sedbgmux.io import DbgMuxIOModem
|
|
from sedbgmux.io import DumpIONative
|
|
from sedbgmux import DbgMuxPeer
|
|
from sedbgmux import DbgMuxClient
|
|
|
|
from sedbgmux.ch import DbgMuxConnTerminal
|
|
from sedbgmux.ch import DbgMuxConnFileLogger
|
|
from sedbgmux.ch import DbgMuxConnUdpProxy
|
|
from sedbgmux.ch import DbgMuxConnWalker
|
|
|
|
# local logger for this module
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class SEDbgMuxApp(cmd2.Cmd):
|
|
DESC = 'DebugMux client for [Sony] Ericsson phones and modems'
|
|
|
|
# Command categories
|
|
CATEGORY_CONN = 'Connection management commands'
|
|
CATEGORY_DBGMUX = 'DebugMux specific commands'
|
|
|
|
def __init__(self, argv):
|
|
super().__init__(allow_cli_args=False)
|
|
|
|
if argv.verbose > 0:
|
|
logging.root.setLevel(logging.DEBUG)
|
|
self.debug = True
|
|
|
|
self.intro = cmd2.style('Welcome to %s!' % self.DESC, fg=cmd2.Fg.RED)
|
|
self.prompt = 'DebugMux (\'%s\')> ' % argv.serial_port
|
|
self.default_category = 'Built-in commands'
|
|
self.argv = argv
|
|
|
|
# Init the I/O layer, DebugMux peer and client
|
|
self.io = DbgMuxIOModem(self.argv)
|
|
self.peer = DbgMuxPeer(self.io)
|
|
self.client = DbgMuxClient(self.peer)
|
|
|
|
# Optionally dump DebugMux frames to a file
|
|
if argv.dump_file is not None:
|
|
dump = DumpIONative(argv.dump_file, readonly=False)
|
|
self.peer.enable_dump(dump)
|
|
|
|
# Modem connection state
|
|
self.set_connected(False)
|
|
|
|
def _tab_data_providers(self) -> List[cmd2.CompletionItem]:
|
|
''' Generate a list of DPRef values for tab-completion '''
|
|
return [cmd2.CompletionItem('0x%02x' % DPRef, DPName)
|
|
for DPRef, DPName in self.client.data_providers.items()]
|
|
|
|
def _tab_connections(self) -> List[cmd2.CompletionItem]:
|
|
''' Generate a list of ConnRef values for tab-completion '''
|
|
return [cmd2.CompletionItem('0x%02x' % ConnRef, 'DPRef=%02x %s' % ConnInfo)
|
|
for ConnRef, ConnInfo in self.client.active_conn.items()]
|
|
|
|
def set_connected(self, state: bool) -> None:
|
|
self.connected: bool = state
|
|
if self.connected:
|
|
self.enable_category(self.CATEGORY_DBGMUX)
|
|
else:
|
|
msg = 'You must be connected to use this command'
|
|
self.disable_category(self.CATEGORY_DBGMUX, msg)
|
|
|
|
@cmd2.with_category(CATEGORY_CONN)
|
|
def do_connect(self, opts) -> None:
|
|
''' Connect to the modem and switch it to DebugMux mode '''
|
|
self.io.connect()
|
|
self.peer.start()
|
|
self.client.start()
|
|
self.set_connected(True)
|
|
|
|
@cmd2.with_category(CATEGORY_CONN)
|
|
def do_disconnect(self, opts) -> None:
|
|
''' Disconnect from the modem '''
|
|
self.client.stop()
|
|
self.peer.stop()
|
|
self.io.disconnect()
|
|
self.set_connected(False)
|
|
|
|
@cmd2.with_category(CATEGORY_CONN)
|
|
def do_status(self, opts) -> None:
|
|
''' Print connection info and statistics '''
|
|
if not self.connected:
|
|
self.poutput('Not connected')
|
|
return
|
|
self.poutput('Connected to \'%s\'' % self.argv.serial_port)
|
|
self.poutput('Baudrate: %d' % self.argv.serial_baudrate)
|
|
self.poutput('TxCount (Ns): %d' % self.peer.tx_count)
|
|
self.poutput('RxCount (Nr): %d' % self.peer.rx_count)
|
|
|
|
show_parser = cmd2.Cmd2ArgumentParser()
|
|
show_sparser = show_parser.add_subparsers(dest='command', required=True)
|
|
show_sparser.add_parser('target-info')
|
|
show_sparser.add_parser('data-providers')
|
|
show_sparser.add_parser('connections')
|
|
|
|
@cmd2.with_argparser(show_parser)
|
|
@cmd2.with_category(CATEGORY_CONN)
|
|
def do_show(self, opts) -> None:
|
|
''' Show various information '''
|
|
if opts.command == 'target-info':
|
|
self.poutput('Name: ' + (self.client.target_name or '(unknown)'))
|
|
self.poutput('IMEI: ' + (self.client.target_imei or '(unknown)'))
|
|
elif opts.command == 'data-providers':
|
|
for (DPRef, DPName) in self.client.data_providers.items():
|
|
self.poutput('Data Provider (DPRef=0x%02x): %s' % (DPRef, DPName))
|
|
elif opts.command == 'connections':
|
|
for (ConnRef, ConnInfo) in self.client.active_conn.items():
|
|
(DPRef, ch) = ConnInfo
|
|
self.poutput('Connection (DPRef=0x%02x, ConnRef=0x%02x): %s'
|
|
% (DPRef, ConnRef, str(ch)))
|
|
for (DPRef, ch) in self.client.pending_conn.items():
|
|
self.poutput('Pending Connection (DPRef=0x%02x): %s' % (DPRef, str(ch)))
|
|
|
|
@cmd2.with_category(CATEGORY_DBGMUX)
|
|
def do_enquiry(self, opts) -> None:
|
|
''' Enquiry target identifier and available Data Providers '''
|
|
self.client.enquiry()
|
|
|
|
ping_parser = cmd2.Cmd2ArgumentParser()
|
|
ping_parser.add_argument('-p', '--payload',
|
|
type=str, default='Knock, knock!',
|
|
help='Ping payload')
|
|
|
|
@cmd2.with_argparser(ping_parser)
|
|
@cmd2.with_category(CATEGORY_DBGMUX)
|
|
def do_ping(self, opts) -> None:
|
|
''' Send a Ping to the target, expect Pong '''
|
|
self.client.ping(opts.payload)
|
|
|
|
establish_parser = cmd2.Cmd2ArgumentParser()
|
|
establish_parser.add_argument('DPRef',
|
|
type=lambda v: int(v, 16),
|
|
choices_provider=_tab_data_providers,
|
|
help='DPRef of a Data Provider in hex')
|
|
establish_sparser = establish_parser.add_subparsers(dest='handler', required=True,
|
|
help='Connection handler')
|
|
ch_terminal = establish_sparser.add_parser('terminal',
|
|
help=DbgMuxConnTerminal.__doc__)
|
|
ch_walker = establish_sparser.add_parser('walker',
|
|
help=DbgMuxConnWalker.__doc__)
|
|
ch_file_logger = establish_sparser.add_parser('file-logger',
|
|
help=DbgMuxConnFileLogger.__doc__)
|
|
ch_file_logger.add_argument('FILE', type=argparse.FileType('ab', 0),
|
|
completer=cmd2.Cmd.path_complete,
|
|
help='File name or \'-\' for stdout')
|
|
ch_udp_proxy = establish_sparser.add_parser('udp-proxy',
|
|
help=DbgMuxConnUdpProxy.__doc__)
|
|
ch_udp_proxy.add_argument('-la', '--local-addr', dest='laddr', type=str,
|
|
default=DbgMuxConnUdpProxy.LADDR_DEF[0],
|
|
help='Local address (default: %(default)s)')
|
|
ch_udp_proxy.add_argument('-lp', '--local-port', dest='lport', type=int,
|
|
default=DbgMuxConnUdpProxy.LADDR_DEF[1],
|
|
help='Local port (default: %(default)s)')
|
|
ch_udp_proxy.add_argument('-ra', '--remote-addr', dest='raddr', type=str,
|
|
default=DbgMuxConnUdpProxy.RADDR_DEF[0],
|
|
help='Remote address (default: %(default)s)')
|
|
ch_udp_proxy.add_argument('-rp', '--remote-port', dest='rport', type=int,
|
|
default=DbgMuxConnUdpProxy.RADDR_DEF[1],
|
|
help='Remote port (default: %(default)s)')
|
|
|
|
@cmd2.with_argparser(establish_parser)
|
|
@cmd2.with_category(CATEGORY_DBGMUX)
|
|
def do_establish(self, opts) -> None:
|
|
''' Establish connections with Data Providers '''
|
|
if opts.handler == 'terminal':
|
|
ch = DbgMuxConnTerminal()
|
|
elif opts.handler == 'walker':
|
|
ch = DbgMuxConnWalker()
|
|
elif opts.handler == 'file-logger':
|
|
ch = DbgMuxConnFileLogger(opts.FILE)
|
|
elif opts.handler == 'udp-proxy':
|
|
ch = DbgMuxConnUdpProxy(laddr=(opts.laddr, opts.lport),
|
|
raddr=(opts.raddr, opts.rport))
|
|
self.client.conn_establish(opts.DPRef, ch)
|
|
if opts.handler == 'terminal':
|
|
ch.attach() # blocking until Ctrl + [CD]
|
|
ch.terminate()
|
|
elif opts.handler == 'walker':
|
|
ch.walk() # blocking
|
|
ch.terminate()
|
|
|
|
terminate_parser = cmd2.Cmd2ArgumentParser()
|
|
terminate_parser.add_argument('ConnRef',
|
|
type=lambda v: int(v, 16),
|
|
choices_provider=_tab_connections,
|
|
help='ConnRef in hex')
|
|
|
|
@cmd2.with_argparser(terminate_parser)
|
|
@cmd2.with_category(CATEGORY_DBGMUX)
|
|
def do_terminate(self, opts) -> None:
|
|
''' Terminate connection with a Data Provider '''
|
|
self.client.conn_terminate(opts.ConnRef)
|
|
|
|
|
|
ap = argparse.ArgumentParser(prog='sedbgmux-shell', description=SEDbgMuxApp.DESC)
|
|
|
|
ap.add_argument('-v', '--verbose', action='count', default=0,
|
|
help='print debug logging')
|
|
|
|
group = ap.add_argument_group('connection parameters')
|
|
group.add_argument('-p', '--serial-port', metavar='PORT', type=str, default='/dev/ttyACM0',
|
|
help='serial port path (default %(default)s)')
|
|
group.add_argument('--serial-baudrate', metavar='BAUDRATE', type=int, default=115200,
|
|
help='serial port speed (default %(default)s)')
|
|
group.add_argument('--serial-timeout', metavar='TIMEOUT', type=float, default=0.5,
|
|
help='serial port read timeout (default %(default)s)')
|
|
group.add_argument('--dump-file', metavar='FILE', type=str,
|
|
help='save Rx/Tx DebugMux frames to a file')
|
|
|
|
logging.basicConfig(
|
|
format='\r[%(levelname)s] %(filename)s:%(lineno)d %(message)s', level=logging.INFO)
|
|
|
|
if __name__ == '__main__':
|
|
argv = ap.parse_args()
|
|
app = SEDbgMuxApp(argv)
|
|
sys.exit(app.cmdloop())
|