Skip to content

Latest commit

 

History

History
67 lines (45 loc) · 5.05 KB

QCSuper architecture.md

File metadata and controls

67 lines (45 loc) · 5.05 KB

QCSuper can be split up into two building blocks:

  • Inputs are Python classes exposing interfaces to read and optionally send Diag protocol data. We can distinguish:

    • Inputs for communicating live with devices (the --adb input for talking with rooted Android phones, the --usb-modem for talking with USB modems)
    • Inputs for processing saved data (--dlf-read is able to read data in a format providing interoperability with the vendor QXDM tool)
  • Modules are Python classes using inputs to perform specific tasks using the Diag protocol. For example:

    • Capturing raw 2G/3G/4G signalling traffic (--pcap-dump will dump to a PCAP file, --wireshark-live will open directly Wireshark)
    • Gathering generation info about the device (--info)
    • Capturing raw Diag logs information into reusable formats (--json-geo-dump, --dlf-dump)

Internal architecture

QCSuper needs to deal to multiple sources of input:

  • The connected Diag device

    • Which can be different kinds of descriptor:
      • A pseudo-serial USB device
      • A TCP socket communicating with a remote Android device
      • A file to replay from
    • Which can deliver different kinds of received data:
    • When the source is a real device, the device can accept only one Diag client at once
  • An optional interactive prompt (--cli module): needed to provide a handy way to continue capturing on the Diag client while executing other tasks

All this requires a form of concurrency to be acheived: either threading, or a way to poll on descriptors through an event loop.

The design ease/simplicity tradeoff I have chosen was to use threading (but I'm open to rework the architecture if someone has something else to propose). Polling on both a serial port and a featureful command prompt or thread queue (for example) is not doable easily in a multi-platform way, and using asyncio seemed to add some design and syntaxic overhead/external libraries to the equation.

Threading model

QCSuper makes use of different threads:

  • The main thread contains the loop reading from the device, and is the only place where reading is performed (it will also dispatch asynchronous messages to modules, calling the on_log, on_message callbacks which may not write neither read, and calling at teardown the on_deinit callback which may write)
  • A background thread is used for initializing the modules selected through command line arguments (calling the on_init callback which may write)
  • Edge case only: a background thread may be used for the optional interactive prompt (--cli) and initializing the modules called from it (calling the on_init callback which may write)

Modules API

A module is a Python class which may expose different methods:

  • __init__: will receive the input object as its first argument, and optionally other arguments from the command line or interactive prompt (passed in sequence from the entry point qcsuper.py).
  • on_init: called when the connection to the Diag device is established. Not called when the input is not a device but a file containing recorded log data.
  • Callbacks triggered by a read on the input source:
    • on_log: called when an asynchronous response Diag protocol raw "log" is received.
    • on_message: called when an asynchronous response Diag protocol text "message" structure is received.
  • on_deinit: called when the connection to the Diag device ceased establishment, or the user hit Ctrl+C.

The methods composing these callbacks may perform request-response operations (using self.diag_reader.send_recv(opcode, payload), where self.diag_reader is the input object).

When a request-response operation is performed, the thread for the callback is paused for the time the response is received, using a thread synchronization primitive shared with the main thread.

When using the interactive prompt (--cli), the moment where the on_init callbacks ends is the moment where the user is informed that the task continued to background (or is finished, in the case where there is no further callbacks).

Inputs API

An input is a Python class which may expose different methods:

  • __init__: will optionally receive arguments from the command line or interactive prompt (passed in sequence from the entry point qcsuper.py).
  • send_request: this function will be called when a module wants to send a Diag request packet (involving possible utility functions such as hdlc_encapsulate from HdlcMixin).
  • read_loop: Diag responses packets will be read and dispatched from here, involving the use of the private dispatch_received_diag_packet method inherited from BaseInput (and other possible utility functions, such as hdlc_decapsulate from HdlcMixin).

Inputs inherit from the BaseInput class which exposes a method called send_recv, allowing to write a request then read the response, wrapping transparently thread synchronization primitives.

send_recv is most likely the only method of an input to be called directly from a module.