16 Commits
Author SHA1 Message Date
catarrh 5a7768e6a9 README: stop framing the runtime zip as Windows-only
The zip is just an archive: it extracts and runs on any OS, and the release's
Source code assets are offered as zip as well as tar.gz, so users pick the
format they prefer.  The section is now "Zip (no installer)" (RU "Zip (без
установки)") and says the archive works on any OS; the Windows/Linux start
commands stay as instructions, not as the section's rationale.  The
source-archive note names both formats, and the release-notes template
follows.
2026-10-27 11:10:55 +03:00
catarrh 7801c62ca3 README: show the launcher forms in the Usage capture modes
The Usage server section only showed the console script and mentioned the
launchers in prose.  It now lists the three capture modes for ./start.sh
(Linux/macOS) and start.bat (Windows) right after the console-script block,
with the pass-through example (--port).
2026-10-27 11:08:11 +03:00
catarrh f18a11edaf README: say UI/frontend except where PWA actually matters
"PWA" appeared throughout the READMEs although most readers don't know the
term and it is usually irrelevant: the server just serves a web interface.
The text now says UI (web UI) for the app and frontend for the static files -
in the quick start, the install/zip/clone paths, the server and --web-dir
docs, the components list and the decoder notes.  The one place where it
matters - the feature list's installable PWA - keeps the term and unpacks it
(installs to the desktop or home screen and works offline); the hosted copy is
now "Hosted UI" since the point there is connecting to a local server.

Both languages; docs only, 557 Python + 80 Node tests.
2026-10-27 11:06:08 +03:00
catarrh 6fbe57052f README: drop the CORS aside from Quick start, keep it where origins differ
The first-run step explained that the PWA and the API share an origin and no
CORS/Private-Network-Access setup is involved - noise for the default setup,
which always serves both from the same server.  The line is gone (both
languages), the Usage server intro no longer repeats it either, and the one
place where it matters - the hosted PWA, which talks to a local server from
another origin - now says so explicitly before the browser requirements.
2026-10-27 11:00:27 +03:00
catarrh 9872913965 README: mark the SIMtrace2 USB prerequisites as conditional
The prerequisites read as if PyUSB, libusb and SIMtrace2 hardware were needed
generally.  They are only for `--capture direct` (and the bundled
`simtrace2-pysniff` utility): the analyser starts and captures GSMTAP with
just Python 3.9+ - verified in a fresh venv without PyUSB, where the server
served the PWA and a GSMTAP ATR+APDU landed in the session (2 messages).

Both READMEs split the list into "Required" (Python 3.9+) and "Only for
capturing from Osmocom SIMtrace2 hardware", and state that GSMTAP capture
(the source may even run on another machine), --capture disabled and PCAP
imports need none of it.  No code change; 557 Python + 80 Node tests.
2026-10-27 10:49:45 +03:00
catarrh a863f66860 README: quick start first, the runtime guide under Usage
The startup instructions were buried after Components/Features and mixed
release/download variants with runtime detail; a reader had to scroll through
the usage guide to find how to launch it, and the start scripts were only
mentioned in passing.

Both READMEs now lead with Prerequisites then a Quick start with three clear
paths - wheel (pip), runtime zip (start.bat / ./start.sh) and a git clone
(launchers, optional pip install -e .) - followed by First run (URL, capture
modes, Ctrl+C).  The detailed runtime guide moves under `Usage`: the server
and PWA, COMPRION import, the Files view, the hosted PWA, the capture utility,
the utility CLI options, output formats, the daemon, library usage and
recovery.  Components and Features follow the usage guide; USB device access
keeps its anchor.

Only the Analysis server intro gained the launcher note; every moved section
is byte-identical (checked old vs new).  557 Python + 80 Node tests.
2026-10-27 05:20:55 +03:00
catarrh 6ed3a7b7d6 README: link the project website next to the Telegram channel
Project news now lives on https://atroshin.ru as well; both links sit on the
same line near the top, in the English and the Russian README.
2026-10-26 22:12:05 +03:00
catarrh 903ead9791 GSMTAP: --gsmtap-bind to listen beyond loopback (containers)
A container's 127.0.0.1 is its own loopback, so a GSMTAP feed sent to the
container's bridge address never reached the listener: it was hard-bound to
127.0.0.1.

- `simtrace-analyser-server --gsmtap-bind ADDR` (default 127.0.0.1) is passed
  through GsmtapListener to GsmtapReceiver (whose bind_ip already existed),
  including the receiver re-created by a start() after close.  The startup
  banner prints the effective bind address next to the port.
- READMEs (EN/RU) document the option, naming the container case.
- tests: the listener forwards the address on construction and on start(),
  binds 0.0.0.0, and keeps the loopback default.

Verified end-to-end: with --gsmtap-bind 0.0.0.0 the socket listens on
0.0.0.0:14732 and an ATR + APDU sent to 127.0.0.1:14732 landed in the capture
session (2 messages); without the flag it listens on 127.0.0.1.
557 Python + 80 Node tests.
2026-10-26 14:45:58 +03:00
catarrh c810ff50cf README: link the project's Telegram channel
Adds the channel (project news and release announcements) right after the
introduction, in both the English and the Russian README.
2026-10-23 21:44:13 +03:00
catarrh 5fcd8e9fd6 Relicense to MIT (v2.7.13)
Release / release (push) Waiting to run
The project's only runtime dependency is PyUSB (BSD-3-Clause) and it bundles
no third-party code (the PWA's DES/3DES is written from the FIPS 46-3 tables,
AES uses WebCrypto, the decoders come from the ISO 7816/ETSI/3GPP specs), so
nothing forced GPL; the GPL-2.0-or-later declaration is replaced with MIT.

- LICENSE: the MIT text, Copyright (c) 2026 Anton Troshin.
- pyproject: license = "MIT" + license-files = ["LICENSE"] (PEP 639), and the
  build requirement moves to setuptools>=77 for the SPDX form.  The wheel now
  carries dist-info/licenses/LICENSE and License-Expression: MIT, the sdist
  and the runtime zip carry LICENSE at the top.
- READMEs (EN/RU): a License section (original decoders/DES implementation,
  PyUSB as the only runtime dependency).
- tests: the packaging test pins the MIT declaration and the LICENSE text;
  the release-zip test requires LICENSE in the archive.
- version 2.7.13; SW cache v95.

Copies published before this tag (v2.7.10-v2.7.12) remain under the license
they were released with.
2026-10-21 01:03:55 +03:00
catarrh e00a96b918 Release a Windows-friendly runtime zip (v2.7.12)
Release / release (push) Waiting to run
The releases carried the wheel and the sdist; the only zip was GitHub's source
archive of the whole repository (tests, CI, tools).  A Windows user just wants
to extract a folder and double-click start.bat.

- scripts/build_release_zip.py builds simtrace-analyser-<version>.zip with a
  single top-level directory: the two Python packages, the PWA runtime files
  (frontend/tests and package.json stay out), the four launchers, the udev
  rule, the READMEs and the screenshot.  The .bat entries are written with
  CRLF line endings (cmd.exe mishandles bare-LF labels/goto), the .sh files
  keep their executable bit; LICENSE is included automatically once the repo
  has one.
- tests/test_release_zip.py pins the archive shape, the exclusions, the sw.js
  PRECACHE drift (every precached asset must travel), the CRLF/LF line endings
  and start.sh's mode.
- the release workflow builds the zip, checks it carries start.bat and the
  PWA, uploads it with the run and attaches it to the release.
- READMEs (EN/RU) and the release-notes template offer the zip as the
  Windows-friendly option; version 2.7.12, PWA cache v94.

Verified: the built zip (34 entries, 658 KB compressed) extracted and started
on Linux serves the same index.html; 553 Python + 80 Node tests.
2026-10-21 00:17:06 +03:00
catarrh b19abbd514 CI: install pyusb before the tests
tests/test_capture.py builds a DirectSniffer, whose lazy import reaches
simtrace2_pysniff.device -> usb.core; the runner has no PyUSB, so two tests
failed there while passing on a workstation that already had it.  The job now
installs the declared runtime dependency next to build.
2026-10-20 23:29:07 +03:00
catarrh 0b3c483c0b CI: a tag push builds, tests and publishes the release
The artifacts and the GitHub release were made by hand (`python -m build` plus
`gh release create`).  `.github/workflows/release.yml` now does it from the
pushed tag: Python and PWA tests, the sdist/wheel build, a check that the
wheel carries the PWA, an install-and-serve smoke test of that wheel, and
`gh release create` with the generated notes (prepended by
`.github/release-notes.md`) and the three assets (wheel, sdist, udev rule).

A `workflow_dispatch` run does everything except publish, so the pipeline can
be exercised without minting a release; the artifacts land in the run.  A tag
that does not match version.py/PWA_VERSION fails before anything is published.
2026-10-20 23:28:13 +03:00
catarrh 9e46c64490 README: pip is the install command (pipx dropped)
The release notes and the README's local-wheel examples still led with
`pipx install`; only the direct-URL form had been switched to pip earlier.
Both READMEs now show a plain `pip install <wheel>` (with the PEP 668 note to
use a virtual environment when the system Python is externally managed), and
the `gh release download` example installs with pip too.  The v2.7.10 and
v2.7.11 release bodies are edited to match.
2026-10-20 23:25:24 +03:00
catarrh efcb6c6c3f Version 2.7.11: the Windows launcher and install-doc fixes
Ships the launcher/doc commit on top of v2.7.10 as a release:
- start.bat passes --db/DB to the server (it only stored the value before) and
  sends --gsmtap-port only for gsmtap capture, like start.sh;
- both .bat launchers quote their values and keep the quoting of pass-through
  options, so paths with spaces work (--db, --web-dir, --pcap, --output);
- the READMEs describe both install paths (release artifacts or a git clone)
  and document the launchers and the Windows python-on-PATH requirement.

Version strings in version.py, pyproject.toml, PWA_VERSION and the version
test move together; SW cache simtrace-analyser-v93.
2026-10-20 23:18:57 +03:00
catarrh 65c8ab215b README: two explicit install paths; Windows launchers pass --db and quote paths
- the Install section now leads with the release artifacts (the wheel via
  pipx/pip, the direct release URL, `gh release download` for the
  version-agnostic case, and the source archive) and keeps the git clone as
  the development path.  It finally names the launchers (./start.sh,
  start.bat): a clone needs no install, but the README never said how to start
  it while the Running section only showed the console script that an install
  provides.
- Windows: the USB section now says the .bat launchers mirror the shell ones
  and call the `python` command (Python 3 must be on PATH); the Install section
  carries the same note.
- start.bat: --db/DB was parsed and then never passed to the server; it is
  passed now, and --gsmtap-port is sent only for gsmtap capture (as start.sh
  already did).  Both .bat files passed values unquoted and stripped the
  quotes of pass-through options, so paths with spaces broke
  (--db "C:\My Data\...", --web-dir, --pcap, --output); values are quoted now
  and pass-through keeps the original quoting (argument assembly through
  delayed expansion).
- the launcher usage comments include the `disabled` capture mode.
- verified the argument assembly with Wine cmd.exe and an echo harness: the
  quoted paths survive, --db arrives, --gsmtap-port appears only for gsmtap;
  547 Python + 80 Node tests.
2026-10-20 23:15:54 +03:00
20 changed files with 1028 additions and 380 deletions

No files matched your search

+22
View File
@@ -0,0 +1,22 @@
Install the wheel below — the PWA is bundled inside it:
```
pip install ./simtrace_analyser-*.whl
simtrace-analyser-server --capture gsmtap
```
The UI is then served at http://127.0.0.1:8081/. If your system Python is
externally managed (PEP 668 — common on Arch and Fedora), install into a
virtual environment first (`python3 -m venv .venv && . .venv/bin/activate`).
The source archive is the same tree a clone would have: unzip it and run
`./start.sh` (Linux/macOS) or `start.bat` (Windows). Direct SIMtrace2 capture
on Linux additionally needs the attached `70-simtrace2-pysniff.rules` udev rule.
**Runtime zip (no installer)** — `simtrace-analyser-*.zip`
is the runtime tree only (packages, PWA, launchers, docs — no tests, no git):
extract it anywhere and run `start.bat` (Windows) or `./start.sh`
(Linux/macOS). Python 3 has to be on `PATH`; `pip install pyusb` is only
needed for direct SIMtrace2 capture.
The project is MIT-licensed (see `LICENSE`).
+115
View File
@@ -0,0 +1,115 @@
name: Release
# A pushed v* tag publishes a GitHub release with the wheel, the sdist and the
# udev rule. A manual run (workflow_dispatch) does everything except publish,
# so the pipeline can be exercised without minting a release.
on:
push:
tags: ['v*']
workflow_dispatch:
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: The tag matches the version strings
if: startsWith(github.ref, 'refs/tags/')
run: |
version=$(python -c "from simtrace_analyser.version import __version__; print(__version__)")
if [ "$GITHUB_REF_NAME" != "v$version" ]; then
echo "tag $GITHUB_REF_NAME does not match version.py ($version)" >&2
exit 1
fi
grep -q "PWA_VERSION = '$version'" frontend/index.html
- name: Install build tooling and the runtime dependency
run: python -m pip install --upgrade pip pyusb build
- name: Python tests
run: python -m unittest discover -s tests
- name: PWA tests
working-directory: frontend
run: node --test
- name: Build the sdist and the wheel
run: python -m build --sdist --wheel
- name: Build the runtime zip
run: python scripts/build_release_zip.py --output dist
- name: The runtime zip is complete
run: |
python - <<'PY'
import glob
import zipfile
archive = glob.glob('dist/*.zip')[0]
names = zipfile.ZipFile(archive).namelist()
for required in ('start.bat', 'frontend/index.html'):
assert any(n.endswith('/' + required) for n in names), \
'%s is missing from %s' % (required, archive)
print('%s: %d entries, launchers and PWA bundled' % (archive, len(names)))
PY
- name: The wheel bundles the PWA
run: |
python - <<'PY'
import glob
import zipfile
wheel = glob.glob('dist/*.whl')[0]
names = zipfile.ZipFile(wheel).namelist()
for required in ('simtrace_analyser/web/index.html',
'simtrace_analyser/web/sw.js'):
assert required in names, '%s is missing from %s' % (required, wheel)
print('%s: %d entries, PWA bundled' % (wheel, len(names)))
PY
- name: The installed wheel serves the PWA
run: |
python -m venv /tmp/smoke
/tmp/smoke/bin/pip install --no-deps dist/*.whl
cd /tmp
/tmp/smoke/bin/simtrace-analyser-server --capture disabled \
--db /tmp/smoke.db --port 8099 &
server=$!
for _ in $(seq 1 20); do
if curl -fsS http://127.0.0.1:8099/ -o /tmp/served-index.html; then
break
fi
sleep 0.5
done
kill "$server"
cmp /tmp/served-index.html "$GITHUB_WORKSPACE/frontend/index.html"
/tmp/smoke/bin/python -c \
"import simtrace_analyser.__main__ as m; p = m._default_web_dir(); assert p.endswith('/simtrace_analyser/web'), p"
- name: Keep the artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/*
- name: Publish the release
if: startsWith(github.ref, 'refs/tags/')
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--generate-notes \
--notes-file .github/release-notes.md \
dist/*.whl dist/*.tar.gz dist/*.zip 70-simtrace2-pysniff.rules
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Anton Troshin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1
View File
@@ -1,3 +1,4 @@
graft frontend graft frontend
include LICENSE
include README.md README_RUS.md 70-simtrace2-pysniff.rules include README.md README_RUS.md 70-simtrace2-pysniff.rules
include start.sh start.bat sniff-start.sh sniff-start.bat include start.sh start.bat sniff-start.sh sniff-start.bat
+273 -175
View File
@@ -9,134 +9,126 @@ also bundles **simtrace2-pysniff**, a Python replacement for
`simtrace2-sniff` that captures from Osmocom SIMtrace2 hardware and can feed `simtrace2-sniff` that captures from Osmocom SIMtrace2 hardware and can feed
GSMTAP into this analyser. GSMTAP into this analyser.
![SIMtrace Analyser PWA](frontend.png) 📢 Project news and updates: [atroshin.ru](https://atroshin.ru) | [Telegram channel](https://t.me/youeyeseesee)
## Components ![SIMtrace Analyser UI](frontend.png)
- **Analysis server + PWA** (`simtrace-analyser-server`) — stores captured
traffic in SQLite, decodes it, and serves the PWA ([`frontend/`](frontend/))
plus the HTTP API on a single origin. Three capture modes: a GSMTAP
listener, direct SIMtrace2 hardware capture, or disabled (browse existing
captures only).
- **Capture utility** (`simtrace2-pysniff`) — captures SIMtrace2 hardware
traffic and writes it to stdout, GSMTAP (Wireshark), or PCAP files. It is
one GSMTAP source of several that the analyser accepts (see also
[sigrok_iso7816_stream](https://github.com/anttro/sigrok_iso7816_stream)).
- **PWA** ([`frontend/`](frontend/)) — trace browser with decoded APDU,
SIM Toolkit, SMS and SCP80 OTA detail; sessions, PCAP and COMPRION trace
import, EN/RU UI.
## Features
**APDU decoder** (server-side, rendered by the PWA):
- Full APDU decode — CLA per ISO 7816-4 / ETSI coding with the **logical
channel 0-19** (standard channels from b2-b1, the extended channels 4-19
from b4-b1 per TS 102 221 §10.3), secure messaging and command chaining,
INS names from a spec table, SFI-aware P1/P2 for READ/UPDATE BINARY and
record commands; MANAGE CHANNEL replies show the channel the card
assigned
- Status words — full ISO 7816-4 set plus UICC-specific values:
`91XX` (proactive command pending), `62F1/F2/F3`, `63F1/F2` (more data),
`9300` (SAT busy), `9850`, `9862–64`
- SIM Toolkit — SAT (GSM 11.14), CAT (TS 102 223) and USAT (TS 31.111)
proactive commands with decoded qualifiers, TERMINAL RESPONSE, ENVELOPE
(Menu Selection, Call Control, SMS-PP download…); tolerates known
non-compliant legacy-card quirks and preserves unrecognized TLVs
- TERMINAL CAPABILITY (TS 102 221 §11.1.19) — terminal power supply
(voltage class, maximum current, clock), extended logical channels,
additional interfaces (UICC-CLF) and eUICC device capabilities
(SGP.22/SGP.32)
- SMS TPDU — SMS-DELIVER/SUBMIT, SCTS timestamps, UDH information elements
- SCP80 OTA secured packets — SPI bits (ciphering, PoR requirement/mode,
RC/CC/DS, counter), KIc/KID algorithm + key set, TAR, CNTR/PCNTR;
Response Packet (PoR) decode on GET RESPONSE; **optional client-side
decryption** of ciphered packets — paste the card's 3DES/AES key in the
detail view to reveal the real CNTR and secured application data (the
key stays in the browser, in-memory only; ciphered on-wire CNTR/PCNTR/RC
are ciphertext and labeled as such); the secured application message is
decoded as a stream of **C-APDUs** (SELECT, UPDATE BINARY, …) with
full CLA/INS/P1/P2/Lc/Le breakdown — plaintext immediately, or after
decryption for ciphered packets
- ATR parse (clock-rate conversion, Fi/Di, T-bitmask, truncated-ATR safe)
and FCP/FCI TLV; the historical bytes are decoded as the UICC's
COMPACT-TLV objects (TS 102 221 §6.3.1) — card service data, the **card
capabilities** (ISO 7816-4 §8.1.1.2.7 Table 88: the assignment methods
and the announced maximum number of logical channels, `8 or more` for the
extended-capable cards) and the trailing status indicator (LCS/SW)
**SIMtrace Analyser PWA** ([`frontend/`](frontend/)):
- Trace view with Map/List modes — proactive, TERMINAL RESPONSE, ENVELOPE
and AUTH commands get distinctive styling in map view
- Sessions with deep links (`#session=N`), PCAP import (`.pcap/.pcapng/.cap`),
COMPRION trace import (the Device Manager *ISO Protocol Layer* text export),
search and a **multi-select command filter** — pick any number of commands
and/or the conversation presets (*Proactive sessions*: FETCH, TERMINAL
PROFILE and every command answered with SW 91xx; *File operations*: SELECT,
READ/UPDATE BINARY and READ/UPDATE RECORD, with the GET RESPONSE carrying a
selected command's data)
- Installable PWA, dark theme, English/Russian UI
## Prerequisites ## Prerequisites
- **Python 3.9+** **Required**
- **PyUSB** — `pip install pyusb` (needed only for capture from SIMtrace2
hardware; GSMTAP/UDP capture and PCAP import work without it) - **Python 3.9+** — GSMTAP capture, PCAP import and browsing use only the
standard library.
**Only for capturing from Osmocom SIMtrace2 hardware** (`--capture direct`, and
the bundled `simtrace2-pysniff` capture utility):
- **PyUSB** — `pip install pyusb`
- **libusb** — system library, present by default on Linux; on Windows the - **libusb** — system library, present by default on Linux; on Windows the
SIMtrace2 driver must be swapped (see [USB Device Access](#usb-device-access)) SIMtrace2 driver must be swapped (see [USB Device Access](#usb-device-access))
- **SIMtrace2 hardware** running firmware in *trace* mode (only for direct - **SIMtrace2 hardware** running firmware in *trace* mode (VID `1d50`, PID
capture; VID `1d50`, PID `60e3`, USB class `ff`, subclass `01`). For `60e3`, USB class `ff`, subclass `01`); building and flashing trace firmware
building and flashing trace firmware, see the is described in the
[upstream SIMtrace2 project](https://gitea.osmocom.org/sim-card/simtrace2). [upstream SIMtrace2 project](https://gitea.osmocom.org/sim-card/simtrace2).
No other dependencies. PCAP, GSMTAP, and hex dump output use only Python stdlib. The analyser needs none of this for `--capture gsmtap` (the GSMTAP source may
even run on another machine), `--capture disabled`, or PCAP imports.
## Install ## Quick start
Three ways to get it running; all of them serve the same web UI at
http://127.0.0.1:8081/.
### 1. Wheel (pip)
The wheel includes the UI, so the server serves it by itself:
```sh
pip install ./simtrace_analyser-2.7.13-py3-none-any.whl
simtrace-analyser-server --capture gsmtap
```
If your system Python is externally managed (PEP 668 — common on Arch and
Fedora), install into a virtual environment first:
`python3 -m venv .venv && . .venv/bin/activate`.
Every artifact is on the
[latest release](https://github.com/anttro/simtrace-analyser/releases/latest).
`pip` can fetch the wheel directly (use the version shown there), or the GitHub
CLI can pick the latest one:
```sh
pip install https://github.com/anttro/simtrace-analyser/releases/download/v2.7.13/simtrace_analyser-2.7.13-py3-none-any.whl
# or:
gh release download --pattern '*.whl'
pip install ./simtrace_analyser-*.whl
```
### 2. Zip (no installer)
Download `simtrace-analyser-2.7.13.zip` from the release page and extract it
anywhere: it is the runtime tree (the Python packages, the web UI, the launchers
and the docs — no tests, no git). The archive works on any OS; then run:
Windows:
```bat
start.bat
```
Linux/macOS:
```sh
./start.sh
```
Python 3 has to be on `PATH`; `pip install pyusb` is only needed for direct
SIMtrace2 capture. The launchers accept the server's options
(`start.bat --capture direct`, `./start.sh --capture direct --port 8080`).
(The release's *Source code (zip / tar.gz)* archives are the full tree
including tests; they work like a clone below.)
### 3. Git clone (development)
```sh ```sh
git clone https://github.com/anttro/simtrace-analyser.git git clone https://github.com/anttro/simtrace-analyser.git
cd simtrace-analyser cd simtrace-analyser
pip install pyusb ./start.sh # Linux/macOS; on Windows: start.bat
``` ```
Or install the package into your environment: The launchers put the checkout on `PYTHONPATH` and run
`python -m simtrace_analyser`, so nothing has to be installed for browsing and
GSMTAP capture; their options are the server's
(`./start.sh --capture direct --port 8080`). Direct hardware capture needs
**PyUSB** (`pip install pyusb`). To get the console scripts
(`simtrace-analyser-server`, `simtrace2-pysniff`) install the package:
```sh ```sh
pip install -e . pip install -e .
# then run: simtrace-analyser-server [opts]
# or: simtrace2-pysniff [opts]
```
### From a release (no git checkout)
The release artifacts bundle the PWA, so an installed server serves the same UI
as a clone. Download the wheel from the
[latest release](https://github.com/anttro/simtrace-analyser/releases/latest)
and install it:
```sh
pipx install ./simtrace_analyser-2.7.10-py3-none-any.whl
# or: pip install ./simtrace_analyser-2.7.10-py3-none-any.whl
# then run (the UI is served at http://127.0.0.1:8081/):
simtrace-analyser-server --capture gsmtap simtrace-analyser-server --capture gsmtap
``` ```
`pip` can also fetch the wheel straight from the release page: ### First run
```sh Open **http://127.0.0.1:8081/** in a browser. `Ctrl+C` stops the server.
pip install https://github.com/anttro/simtrace-analyser/releases/download/v2.7.10/simtrace_analyser-2.7.10-py3-none-any.whl
```
## Running - `--capture gsmtap` (default) — listen for GSMTAP-SIM on UDP 4729, e.g. from
`simtrace2-pysniff` or `sigrok_iso7816_stream`;
- `--capture direct` — read SIMtrace2 hardware directly (needs PyUSB; Windows
also needs the driver swap, see [USB Device Access](#usb-device-access));
- `--capture disabled` — only browse stored captures.
### Analysis server + PWA (`simtrace-analyser-server`) The full runtime guide (server options, the UI views, the capture utility) is
in [Usage](#usage) below.
## Usage
### Analysis server + UI (`simtrace-analyser-server`)
Stores captured APDU traffic in SQLite and serves it over HTTP, together with Stores captured APDU traffic in SQLite and serves it over HTTP, together with
the bundled **SIMtrace Analyser** PWA (`frontend/` in a source checkout, the bundled **SIMtrace Analyser** frontend (`frontend/` in a source checkout,
bundled into the package on install) on the same origin. Three bundled into the package on install) on the same origin. Three capture
capture modes: modes:
```sh ```sh
# Listen for GSMTAP from simtrace2-pysniff (or original simtrace2-sniff): # Listen for GSMTAP from simtrace2-pysniff (or original simtrace2-sniff):
@@ -149,6 +141,27 @@ simtrace-analyser-server --capture direct
simtrace-analyser-server --capture disabled simtrace-analyser-server --capture disabled
``` ```
From a source checkout or the runtime zip, the launchers run the same server
with the same options.
Linux/macOS:
```sh
./start.sh --capture gsmtap
./start.sh --capture direct
./start.sh --capture disabled
```
Windows:
```bat
start.bat --capture gsmtap
start.bat --capture direct
start.bat --capture disabled
```
Options pass through unchanged (`./start.sh --capture direct --port 8080`).
The server logs its capture liveness to stderr (each line timestamped The server logs its capture liveness to stderr (each line timestamped
with the local time): `Capture started` on begin, a periodic with the local time): `Capture started` on begin, a periodic
`Capture alive: session=N messages=M bytes=B dropped=D` heartbeat, and `Capture alive: session=N messages=M bytes=B dropped=D` heartbeat, and
@@ -186,7 +199,7 @@ The **Import COMPRION trace** button accepts the tab-separated **ISO Protocol
Layer** export of COMPRION's Device Manager — the byte-accurate protocol view Layer** export of COMPRION's Device Manager — the byte-accurate protocol view
(ATR, PPS, APDUs with their return codes). The *ISO Application Layer* export (ATR, PPS, APDUs with their return codes). The *ISO Application Layer* export
carries no raw response bytes and is not needed; selecting it answers with a carries no raw response bytes and is not needed; selecting it answers with a
pointer to the Protocol Layer file (the same check runs in the PWA before the pointer to the Protocol Layer file (the same check runs in the UI before the
upload and on the server for API callers). upload and on the server for API callers).
Exchanges are re-encoded into the same wire-shaped TPDUs a GSMTAP capture Exchanges are re-encoded into the same wire-shaped TPDUs a GSMTAP capture
@@ -305,28 +318,32 @@ Server options:
--db FILE SQLite database path (default: ~/.simtrace-analyser/sessions.db) --db FILE SQLite database path (default: ~/.simtrace-analyser/sessions.db)
--capture MODE gsmtap | direct | disabled (default: gsmtap) --capture MODE gsmtap | direct | disabled (default: gsmtap)
--gsmtap-port PORT UDP port for the GSMTAP listener (default: 4729) --gsmtap-port PORT UDP port for the GSMTAP listener (default: 4729)
--web-dir PATH PWA static files directory (default: the PWA bundled in --gsmtap-bind ADDR UDP bind address for the GSMTAP listener (default: 127.0.0.1;
the installed package, or <repo>/frontend from source) use 0.0.0.0 to receive from a Docker bridge or another host)
--web-dir PATH static files directory for the web UI (default: the
frontend bundled in the installed package, or
<repo>/frontend from source)
``` ```
Then open **http://127.0.0.1:8081/** in a browser — the PWA is served by the Then open **http://127.0.0.1:8081/** in a browser. Use `--web-dir PATH` to
server, so the UI and the API share an origin and no CORS/Private-Network-Access serve a different UI directory (default: the bundled frontend, or
setup is involved. Use `--web-dir PATH` to serve a different PWA directory `<repo>/frontend` in a source checkout).
(default: the bundled PWA, or `<repo>/frontend` in a source checkout).
### Hosted PWA (landing page) ### Hosted UI (landing page)
A standalone copy of the PWA is hosted at **https://simtrace.atroshin.ru**. A standalone copy of the UI is hosted at **https://simtrace.atroshin.ru**.
It is a pure frontend: point it (Settings → Server URL) at a locally running It is a pure frontend: point it (Settings → Server URL) at a locally running
`simtrace-analyser-server`. `simtrace-analyser-server`.
> **Browser restriction:** when the PWA is served from a public HTTPS host, > **Browser restriction:** this is the one setup where the UI and the API have
> reaching a local server (`http://127.0.0.1:8081`) requires two things: the > different origins, so the browser applies CORS and Private Network Access
> server must send `Access-Control-Allow-Private-Network: true` (this server > rules. Reaching a local server (`http://127.0.0.1:8081`) requires two
> does), and the browser must be allowed to access the local network — in > things: the server must send `Access-Control-Allow-Private-Network: true`
> Chrome/Edge/Vivaldi: Site settings → Local network access → allow the site > (this server does), and the browser must be allowed to access the local
> (or accept the permission prompt). Without the browser permission, the > network — in Chrome/Edge/Vivaldi: Site settings → Local network access →
> request to `127.0.0.1` is blocked before any preflight is sent. > allow the site (or accept the permission prompt). Without the browser
> permission, the request to `127.0.0.1` is blocked before any preflight is
> sent.
### Capture utility (`simtrace2-pysniff`) ### Capture utility (`simtrace2-pysniff`)
@@ -366,6 +383,71 @@ If installed via `pip install -e .`, use the console script directly:
simtrace2-pysniff --format atr-time simtrace2-pysniff --format atr-time
``` ```
### Utility CLI Options
```
python -m simtrace2_pysniff [OPTIONS]
--format, -f FORMAT Output format (default: hex)
hex — plain hex dump
timestamp — TPDU lines prefixed with [HH:MM:SS.mmm]
atr-time — TPDU lines prefixed with [SSSS.mmm] from last ATR
--gsmtap HOST[:PORT] Send ATR/TPDU as GSMTAP over UDP (default port 4729)
--pcap FILE Write sniffed data to PCAP file for Wireshark
--output, -o FILE Write hex dump to file
--no-stdout Suppress hex dump to stdout
--vendor HEX USB vendor ID (default: 0x1d50)
--product HEX USB product ID (default: auto-detect)
Recovery options:
--no-reconnect Exit on USB disconnect instead of reconnecting
--reconnect-delay-min SEC Minimum reconnect delay (default: 1.0)
--reconnect-delay-max SEC Maximum reconnect delay cap (default: 30.0)
--backoff-factor N Exponential backoff multiplier (default: 1.5)
--inactivity-timeout SEC Reconnect after N seconds of silence (default: disabled)
```
### Output Formats
- **`hex`** (default): plain hex dump — `ATR: 3b 9e ...`, `TPDU: a0 a4 00 00 02 3f 00`
- **`timestamp`**: TPDU lines prefixed with local time — `[14:32:05.123] TPDU: a0 a4 00 00 02 3f 00`
- **`atr-time`**: TPDU lines prefixed with seconds since last ATR — `[0001.234] TPDU: a0 a4 00 00 02 3f 00`
Non-TPDU messages (ATR, PPS, card state changes, Fi/Di) print identically in all formats.
- **GSMTAP**: ATR and APDU messages over UDP (port 4729) to Wireshark
- **PCAP**: ATR/TPDU messages as Ethernet/IP/UDP/GSMTAP packets, openable in Wireshark
### Background / Daemon
SIGHUP is automatically ignored when stdin is not a terminal (i.e. run in
background or via a pipe). Foreground terminal sessions still respond to
SIGHUP normally.
To run persistently in the background with output to files:
```sh
nohup ./sniff-start.sh --output /tmp/sniff.log &> /tmp/sniff-err.log &
# or
FORMAT=timestamp nohup python -m simtrace2_pysniff --output /tmp/sniff.log &> /tmp/sniff-err.log &
```
### Library Usage
```python
from simtrace2_pysniff import SniffSession
session = SniffSession(inactivity_timeout=30.0)
for msg in session.iter_messages():
print(f"{msg.type}: {msg.data.hex()}")
```
### Recovery
The utility survives hardware resets, cable disconnects, and firmware hangs
by default — it reconnects with exponential backoff (1s → 30s cap).
Use `--inactivity-timeout` to also trigger reconnect on silent firmware hangs.
## USB Device Access ## USB Device Access
Required for capture from SIMtrace2 hardware (the utility and the server's Required for capture from SIMtrace2 hardware (the utility and the server's
@@ -397,70 +479,79 @@ Windows does not ship a libusb-compatible driver for SIMtrace2. Use
After that, `python -m simtrace2_pysniff` works identically to Linux. After that, `python -m simtrace2_pysniff` works identically to Linux.
## Utility CLI Options The Windows launchers match their shell counterparts and pass every option
through: `start.bat --capture direct` starts the analyser server and
`sniff-start.bat` the capture utility. Both call the `python` command, so
Python 3 must be on `PATH` (verify with `python --version` in a new command
prompt).
``` ## Components
python -m simtrace2_pysniff [OPTIONS]
--format, -f FORMAT Output format (default: hex) - **Analysis server + UI** (`simtrace-analyser-server`) — stores captured
hex — plain hex dump traffic in SQLite, decodes it, and serves the UI ([`frontend/`](frontend/))
timestamp — TPDU lines prefixed with [HH:MM:SS.mmm] plus the HTTP API on a single origin. Three capture modes: a GSMTAP
atr-time — TPDU lines prefixed with [SSSS.mmm] from last ATR listener, direct SIMtrace2 hardware capture, or disabled (browse existing
--gsmtap HOST[:PORT] Send ATR/TPDU as GSMTAP over UDP (default port 4729) captures only).
--pcap FILE Write sniffed data to PCAP file for Wireshark - **Capture utility** (`simtrace2-pysniff`) — captures SIMtrace2 hardware
--output, -o FILE Write hex dump to file traffic and writes it to stdout, GSMTAP (Wireshark), or PCAP files. It is
--no-stdout Suppress hex dump to stdout one GSMTAP source of several that the analyser accepts (see also
--vendor HEX USB vendor ID (default: 0x1d50) [sigrok_iso7816_stream](https://github.com/anttro/sigrok_iso7816_stream)).
--product HEX USB product ID (default: auto-detect) - **UI** ([`frontend/`](frontend/)) — trace browser with decoded APDU,
SIM Toolkit, SMS and SCP80 OTA detail; sessions, PCAP and COMPRION trace
import, EN/RU UI.
Recovery options: ## Features
--no-reconnect Exit on USB disconnect instead of reconnecting
--reconnect-delay-min SEC Minimum reconnect delay (default: 1.0)
--reconnect-delay-max SEC Maximum reconnect delay cap (default: 30.0)
--backoff-factor N Exponential backoff multiplier (default: 1.5)
--inactivity-timeout SEC Reconnect after N seconds of silence (default: disabled)
```
## Output Formats **APDU decoder** (server-side, rendered by the UI):
- **`hex`** (default): plain hex dump — `ATR: 3b 9e ...`, `TPDU: a0 a4 00 00 02 3f 00` - Full APDU decode — CLA per ISO 7816-4 / ETSI coding with the **logical
- **`timestamp`**: TPDU lines prefixed with local time — `[14:32:05.123] TPDU: a0 a4 00 00 02 3f 00` channel 0-19** (standard channels from b2-b1, the extended channels 4-19
- **`atr-time`**: TPDU lines prefixed with seconds since last ATR — `[0001.234] TPDU: a0 a4 00 00 02 3f 00` from b4-b1 per TS 102 221 §10.3), secure messaging and command chaining,
INS names from a spec table, SFI-aware P1/P2 for READ/UPDATE BINARY and
record commands; MANAGE CHANNEL replies show the channel the card
assigned
- Status words — full ISO 7816-4 set plus UICC-specific values:
`91XX` (proactive command pending), `62F1/F2/F3`, `63F1/F2` (more data),
`9300` (SAT busy), `9850`, `9862–64`
- SIM Toolkit — SAT (GSM 11.14), CAT (TS 102 223) and USAT (TS 31.111)
proactive commands with decoded qualifiers, TERMINAL RESPONSE, ENVELOPE
(Menu Selection, Call Control, SMS-PP download…); tolerates known
non-compliant legacy-card quirks and preserves unrecognized TLVs
- TERMINAL CAPABILITY (TS 102 221 §11.1.19) — terminal power supply
(voltage class, maximum current, clock), extended logical channels,
additional interfaces (UICC-CLF) and eUICC device capabilities
(SGP.22/SGP.32)
- SMS TPDU — SMS-DELIVER/SUBMIT, SCTS timestamps, UDH information elements
- SCP80 OTA secured packets — SPI bits (ciphering, PoR requirement/mode,
RC/CC/DS, counter), KIc/KID algorithm + key set, TAR, CNTR/PCNTR;
Response Packet (PoR) decode on GET RESPONSE; **optional client-side
decryption** of ciphered packets — paste the card's 3DES/AES key in the
detail view to reveal the real CNTR and secured application data (the
key stays in the browser, in-memory only; ciphered on-wire CNTR/PCNTR/RC
are ciphertext and labeled as such); the secured application message is
decoded as a stream of **C-APDUs** (SELECT, UPDATE BINARY, …) with
full CLA/INS/P1/P2/Lc/Le breakdown — plaintext immediately, or after
decryption for ciphered packets
- ATR parse (clock-rate conversion, Fi/Di, T-bitmask, truncated-ATR safe)
and FCP/FCI TLV; the historical bytes are decoded as the UICC's
COMPACT-TLV objects (TS 102 221 §6.3.1) — card service data, the **card
capabilities** (ISO 7816-4 §8.1.1.2.7 Table 88: the assignment methods
and the announced maximum number of logical channels, `8 or more` for the
extended-capable cards) and the trailing status indicator (LCS/SW)
Non-TPDU messages (ATR, PPS, card state changes, Fi/Di) print identically in all formats. **SIMtrace Analyser UI** ([`frontend/`](frontend/)):
- **GSMTAP**: ATR and APDU messages over UDP (port 4729) to Wireshark - Trace view with Map/List modes — proactive, TERMINAL RESPONSE, ENVELOPE
- **PCAP**: ATR/TPDU messages as Ethernet/IP/UDP/GSMTAP packets, openable in Wireshark and AUTH commands get distinctive styling in map view
- Sessions with deep links (`#session=N`), PCAP import (`.pcap/.pcapng/.cap`),
## Background / Daemon COMPRION trace import (the Device Manager *ISO Protocol Layer* text export),
search and a **multi-select command filter** — pick any number of commands
SIGHUP is automatically ignored when stdin is not a terminal (i.e. run in and/or the conversation presets (*Proactive sessions*: FETCH, TERMINAL
background or via a pipe). Foreground terminal sessions still respond to PROFILE and every command answered with SW 91xx; *File operations*: SELECT,
SIGHUP normally. READ/UPDATE BINARY and READ/UPDATE RECORD, with the GET RESPONSE carrying a
selected command's data)
To run persistently in the background with output to files: - Installable **PWA** (progressive web app: installs to the desktop or home
screen and works offline), dark theme, English/Russian UI
```sh
nohup ./sniff-start.sh --output /tmp/sniff.log &> /tmp/sniff-err.log &
# or
FORMAT=timestamp nohup python -m simtrace2_pysniff --output /tmp/sniff.log &> /tmp/sniff-err.log &
```
## Library Usage
```python
from simtrace2_pysniff import SniffSession
session = SniffSession(inactivity_timeout=30.0)
for msg in session.iter_messages():
print(f"{msg.type}: {msg.data.hex()}")
```
## Recovery
The utility survives hardware resets, cable disconnects, and firmware hangs
by default — it reconnects with exponential backoff (1s → 30s cap).
Use `--inactivity-timeout` to also trigger reconnect on silent firmware hangs.
## Related ## Related
@@ -469,3 +560,10 @@ Use `--inactivity-timeout` to also trigger reconnect on silent firmware hangs.
- **[sigrok_iso7816_stream](https://github.com/anttro/sigrok_iso7816_stream)** — - **[sigrok_iso7816_stream](https://github.com/anttro/sigrok_iso7816_stream)** —
FX2LP-based passive ISO 7816 sniffer that emits the same GSMTAP-SIM stream FX2LP-based passive ISO 7816 sniffer that emits the same GSMTAP-SIM stream
on UDP 4729 (accepted by this project's GSMTAP listener). on UDP 4729 (accepted by this project's GSMTAP listener).
## License
MIT — see [LICENSE](LICENSE). The decoders and the UI's DES/3DES
implementation are original, written from the ISO 7816, ETSI and 3GPP
specifications; no third-party code is bundled. PyUSB (BSD-3-Clause) is a
runtime dependency for direct hardware capture.
+279 -182
View File
@@ -9,137 +9,127 @@ SIM/UICC из UDP-потока GSMTAP (или импортирует файлы
на Python, которая захватывает трафик с оборудования Osmocom SIMtrace2 на Python, которая захватывает трафик с оборудования Osmocom SIMtrace2
и может передавать GSMTAP этому анализатору. и может передавать GSMTAP этому анализатору.
![PWA SIMtrace Analyser](frontend.png) 📢 Новости и обновления проекта: [atroshin.ru](https://atroshin.ru) | [Telegram-канал](https://t.me/youeyeseesee)
## Компоненты ![Интерфейс SIMtrace Analyser](frontend.png)
- **Анализирующий сервер + PWA** (`simtrace-analyser-server`) — сохраняет
захваченный трафик в SQLite, декодирует его и отдаёт PWA
([`frontend/`](frontend/)) вместе с HTTP API на одном origin. Три режима
захвата: GSMTAP-листенер, захват напрямую с оборудования SIMtrace2 или
отключённый захват (только просмотр существующих записей).
- **Утилита захвата** (`simtrace2-pysniff`) — захватывает трафик
оборудования SIMtrace2 и выводит его в stdout, GSMTAP (Wireshark) или
PCAP-файлы. Это один из нескольких источников GSMTAP, которые принимает
анализатор (см. также
[sigrok_iso7816_stream](https://github.com/anttro/sigrok_iso7816_stream)).
- **PWA** ([`frontend/`](frontend/)) — окно трассировки с декодированными
APDU, SIM Toolkit, SMS и SCP80 OTA; сессии, импорт PCAP и трасс COMPRION,
интерфейс EN/RU.
## Возможности
**Декодер APDU** (на стороне сервера, отображается в PWA):
- Полное декодирование APDU — CLA по ISO 7816-4 / ETSI с **логическим
каналом 0-19** (стандартные каналы из b2-b1, расширенные 4-19 из b4-b1
по TS 102 221 §10.3), secure messaging и chaining команд, имена INS из
таблицы спецификаций, учёт SFI в P1/P2 для READ/UPDATE BINARY и команд
с записями; ответ MANAGE CHANNEL показывает назначенный картой канал
- Статусные слова — полный набор ISO 7816-4 плюс специфичные для UICC
значения: `91XX` (ожидается proactive-команда), `62F1/F2/F3`,
`63F1/F2` (больше данных), `9300` (SAT busy), `9850`, `9862–64`
- SIM Toolkit — SAT (GSM 11.14), CAT (TS 102 223) и USAT (TS 31.111):
proactive-команды с декодированными квалификаторами, TERMINAL RESPONSE,
ENVELOPE (Menu Selection, Call Control, SMS-PP download…); учитываются
известные несоответствия старых карт, нераспознанные TLV сохраняются
- TERMINAL CAPABILITY (TS 102 221 §11.1.19) — питание терминала (класс
напряжения, максимальный ток, тактовая частота), расширенные логические
каналы, дополнительные интерфейсы (UICC-CLF) и возможности eUICC
(SGP.22/SGP.32)
- SMS TPDU — SMS-DELIVER/SUBMIT, метки времени SCTS, информационные
элементы UDH
- SCP80 OTA secured packets — биты SPI (шифрование, требование/режим PoR,
RC/CC/DS, счётчик), алгоритм KIc/KID + набор ключей, TAR, CNTR/PCNTR;
декодирование Response Packet (PoR) по GET RESPONSE; **опциональная
расшифровка на клиенте** зашифрованных пакетов — вставьте 3DES/AES-ключ
карты в панели деталей, чтобы увидеть настоящие CNTR и защищённые данные
приложения (ключ остаётся в браузере, только в памяти; зашифрованные
в эфире CNTR/PCNTR/RC — это шифротекст, и они так и помечены); защищённое
сообщение приложения декодируется как поток **C-APDU** (SELECT,
UPDATE BINARY, …) с полной разбивкой CLA/INS/P1/P2/Lc/Le — сразу для
открытого текста или после расшифровки для зашифрованных пакетов
- Разбор ATR (пересчёт тактовой частоты, Fi/Di, битовая маска T,
безопасен для усечённых ATR) и FCP/FCI TLV; исторические байты
разбираются как COMPACT-TLV объекты UICC (TS 102 221 §6.3.1) — card
service data, **card capabilities** (ISO 7816-4 §8.1.1.2.7 таблица 88:
способы назначения и заявленное максимальное число логических каналов,
`8 or more` у карт с расширенными каналами) и завершающий индикатор
состояния (LCS/SW)
**PWA SIMtrace Analyser** ([`frontend/`](frontend/)):
- Окно трассировки с режимами «Карта»/«Список» — proactive,
TERMINAL RESPONSE, ENVELOPE и AUTH-команды выделяются в режиме карты
- Сессии с прямыми ссылками (`#session=N`), импорт PCAP
(`.pcap/.pcapng/.cap`), импорт трасс COMPRION (текстовый экспорт
«ISO Protocol Layer» из Device Manager), поиск и **фильтр команд с
множественным выбором** — любые команды и/или готовые пресеты («Проактивные
сессии»: FETCH, TERMINAL PROFILE и все команды с ответом SW 91xx;
«Файловые операции»: SELECT,
READ/UPDATE BINARY и READ/UPDATE RECORD, вместе с GET RESPONSE, несущим
данные выбранной команды)
- Устанавливаемая PWA, тёмная тема, интерфейс на английском/русском
## Требования ## Требования
- **Python 3.9+** **Обязательно**
- **PyUSB** — `pip install pyusb` (нужен только для захвата с оборудования
SIMtrace2; захват GSMTAP/UDP и импорт PCAP работают без него) - **Python 3.9+** — для захвата GSMTAP, импорта PCAP и просмотра достаточно
стандартной библиотеки.
**Только для захвата с оборудования Osmocom SIMtrace2** (`--capture direct` и
встроенная утилита `simtrace2-pysniff`):
- **PyUSB** — `pip install pyusb`
- **libusb** — системная библиотека, обычно уже есть в Linux; в Windows - **libusb** — системная библиотека, обычно уже есть в Linux; в Windows
нужно заменить драйвер SIMtrace2 нужно заменить драйвер SIMtrace2
(см. [Доступ к USB-устройству](#доступ-к-usb-устройству)) (см. [Доступ к USB-устройству](#доступ-к-usb-устройству))
- **Оборудование SIMtrace2** с прошивкой в режиме *trace* (только для - **Оборудование SIMtrace2** с прошивкой в режиме *trace* (VID `1d50`, PID
прямого захвата; VID `1d50`, PID `60e3`, USB class `ff`, subclass `01`). `60e3`, USB class `ff`, subclass `01`); сборка и прошивка trace-прошивки
О сборке и прошивке trace-прошивки см. описаны в
[проект SIMtrace2](https://gitea.osmocom.org/sim-card/simtrace2). [проекте SIMtrace2](https://gitea.osmocom.org/sim-card/simtrace2).
Других зависимостей нет. Вывод PCAP, GSMTAP и hex dump использует только Для `--capture gsmtap` (источник GSMTAP может быть даже на другой машине),
стандартную библиотеку Python. `--capture disabled` и импорта PCAP анализатору ничего из этого не нужно.
## Установка ## Быстрый старт
Три способа запустить анализатор; все они отдают один и тот же веб-интерфейс на
http://127.0.0.1:8081/.
### 1. Wheel (pip)
Wheel содержит интерфейс внутри пакета, поэтому сервер отдаёт его сам:
```sh
pip install ./simtrace_analyser-2.7.13-py3-none-any.whl
simtrace-analyser-server --capture gsmtap
```
Если системный Python управляется извне (PEP 668 — обычное дело в Arch и
Fedora), сначала создайте виртуальное окружение:
`python3 -m venv .venv && . .venv/bin/activate`.
Все артефакты лежат на странице
[последнего релиза](https://github.com/anttro/simtrace-analyser/releases/latest).
`pip` может скачать wheel напрямую (подставьте версию со страницы), а GitHub
CLI — выбрать последнюю:
```sh
pip install https://github.com/anttro/simtrace-analyser/releases/download/v2.7.13/simtrace_analyser-2.7.13-py3-none-any.whl
# или:
gh release download --pattern '*.whl'
pip install ./simtrace_analyser-*.whl
```
### 2. Zip (без установки)
Скачайте `simtrace-analyser-2.7.13.zip` со страницы релиза и распакуйте в любое
место: это рабочее дерево (пакеты Python, интерфейс, скрипты запуска и документация —
без тестов и git). Архив подходит для любой ОС. Затем запустите.
Windows:
```bat
start.bat
```
Linux/macOS:
```sh
./start.sh
```
Python 3 должен быть в `PATH`; `pip install pyusb` нужен только для прямого
захвата с SIMtrace2. Скрипты принимают параметры сервера
(`start.bat --capture direct`, `./start.sh --capture direct --port 8080`).
(Архивы *Source code (zip / tar.gz)* из релиза — это полное дерево вместе с
тестами; они работают так же, как клон ниже.)
### 3. Клон репозитория (для разработки)
```sh ```sh
git clone https://github.com/anttro/simtrace-analyser.git git clone https://github.com/anttro/simtrace-analyser.git
cd simtrace-analyser cd simtrace-analyser
pip install pyusb ./start.sh # Linux/macOS; в Windows: start.bat
``` ```
Или установите пакет в своё окружение: Скрипты запуска добавляют каталог в `PYTHONPATH` и запускают
`python -m simtrace_analyser`, поэтому для просмотра и захвата GSMTAP ничего
устанавливать не нужно; их параметры — это параметры сервера
(`./start.sh --capture direct --port 8080`). Для прямого захвата с
оборудования нужен **PyUSB** (`pip install pyusb`). Чтобы получить консольные
команды (`simtrace-analyser-server`, `simtrace2-pysniff`), установите пакет:
```sh ```sh
pip install -e . pip install -e .
# затем запуск: simtrace-analyser-server [opts]
# или: simtrace2-pysniff [opts]
```
### Из релиза (без клонирования репозитория)
Релизные артефакты содержат PWA внутри пакета, поэтому установленный сервер
отдаёт тот же интерфейс, что и при запуске из клона. Скачайте wheel со
страницы [последнего релиза](https://github.com/anttro/simtrace-analyser/releases/latest)
и установите его:
```sh
pipx install ./simtrace_analyser-2.7.10-py3-none-any.whl
# или: pip install ./simtrace_analyser-2.7.10-py3-none-any.whl
# затем запуск (интерфейс отдаётся на http://127.0.0.1:8081/):
simtrace-analyser-server --capture gsmtap simtrace-analyser-server --capture gsmtap
``` ```
`pip` также может скачать wheel прямо со страницы релиза: ### Первый запуск
```sh Откройте **http://127.0.0.1:8081/** в браузере. `Ctrl+C` останавливает
pip install https://github.com/anttro/simtrace-analyser/releases/download/v2.7.10/simtrace_analyser-2.7.10-py3-none-any.whl сервер.
```
## Запуск - `--capture gsmtap` (по умолчанию) — слушать GSMTAP-SIM на UDP 4729,
например от `simtrace2-pysniff` или `sigrok_iso7816_stream`;
- `--capture direct` — читать оборудование SIMtrace2 напрямую (нужен PyUSB; в
Windows также нужна замена драйвера, см.
[Доступ к USB-устройству](#доступ-к-usb-устройству));
- `--capture disabled` — только просмотр сохранённых захватов.
### Анализирующий сервер + PWA (`simtrace-analyser-server`) Полное руководство по работе (параметры сервера, экраны интерфейса, утилита захвата)
находится в разделе [Использование](#использование).
## Использование
### Анализирующий сервер + интерфейс (`simtrace-analyser-server`)
Сохраняет захваченный APDU-трафик в SQLite и отдаёт его по HTTP вместе Сохраняет захваченный APDU-трафик в SQLite и отдаёт его по HTTP вместе
со встроенной PWA **SIMtrace Analyser** (`frontend/` в исходном клоне, при со встроенным фронтендом **SIMtrace Analyser** (`frontend/` в исходном клоне, при
установке — внутри пакета) на том же origin. установке — внутри пакета) на том же origin.
Три режима захвата: Три режима захвата:
@@ -154,6 +144,27 @@ simtrace-analyser-server --capture direct
simtrace-analyser-server --capture disabled simtrace-analyser-server --capture disabled
``` ```
При запуске из клона или рабочего zip те же режимы доступны через скрипты
запуска.
Linux/macOS:
```sh
./start.sh --capture gsmtap
./start.sh --capture direct
./start.sh --capture disabled
```
Windows:
```bat
start.bat --capture gsmtap
start.bat --capture direct
start.bat --capture disabled
```
Параметры передаются как есть (`./start.sh --capture direct --port 8080`).
Сервер пишет в stderr информацию о жизненном цикле захвата (каждая строка Сервер пишет в stderr информацию о жизненном цикле захвата (каждая строка
с локальным временем): `Capture started` при старте, периодический с локальным временем): `Capture started` при старте, периодический
heartbeat `Capture alive: session=N messages=M bytes=B dropped=D skipped=S` heartbeat `Capture alive: session=N messages=M bytes=B dropped=D skipped=S`
@@ -192,7 +203,7 @@ RST несёт измеренную частоту CLK (декодер v1.9.0+,
(табулированный текст) из COMPRION Device Manager — байтовое представление (табулированный текст) из COMPRION Device Manager — байтовое представление
протокола (ATR, PPS, APDU с кодами возврата). Экспорт *ISO Application протокола (ATR, PPS, APDU с кодами возврата). Экспорт *ISO Application
Layer* не содержит необработанных байтов ответов и не нужен; при его выборе Layer* не содержит необработанных байтов ответов и не нужен; при его выборе
выводится указание на файл Protocol Layer (та же проверка выполняется в PWA выводится указание на файл Protocol Layer (та же проверка выполняется в интерфейсе
до загрузки и на сервере — для вызовов API). до загрузки и на сервере — для вызовов API).
Обмены перекодируются в такие же «проводные» TPDU, какие несёт захват Обмены перекодируются в такие же «проводные» TPDU, какие несёт захват
@@ -311,25 +322,27 @@ TS 51.011), поэтому FID, повторно используемый в р
--db FILE путь к базе SQLite (по умолчанию: ~/.simtrace-analyser/sessions.db) --db FILE путь к базе SQLite (по умолчанию: ~/.simtrace-analyser/sessions.db)
--capture MODE gsmtap | direct | disabled (по умолчанию: gsmtap) --capture MODE gsmtap | direct | disabled (по умолчанию: gsmtap)
--gsmtap-port PORT UDP-порт листенера GSMTAP (по умолчанию: 4729) --gsmtap-port PORT UDP-порт листенера GSMTAP (по умолчанию: 4729)
--web-dir PATH каталог статических файлов PWA (по умолчанию: PWA внутри --gsmtap-bind ADDR UDP-адрес привязки листенера GSMTAP (по умолчанию: 127.0.0.1;
установленного пакета или <repo>/frontend при запуске из исходников) используйте 0.0.0.0, чтобы принимать из Docker-сети или с другого хоста)
--web-dir PATH каталог статических файлов интерфейса (по умолчанию:
фронтенд внутри установленного пакета или
<repo>/frontend при запуске из исходников)
``` ```
Затем откройте **http://127.0.0.1:8081/** в браузере — PWA отдаётся Затем откройте **http://127.0.0.1:8081/** в браузере. Используйте
сервером, поэтому UI и API используют один origin, и настройка `--web-dir PATH`, чтобы отдавать другой каталог интерфейса (по умолчанию:
CORS/Private-Network-Access не требуется. Используйте `--web-dir PATH`, фронтенд внутри пакета или `<repo>/frontend` при запуске из исходников).
чтобы отдавать другой каталог PWA (по умолчанию: PWA внутри пакета или
`<repo>/frontend` при запуске из исходников).
### Размещённая PWA (лендинг) ### Размещённый интерфейс (лендинг)
Автономная копия PWA размещена на **https://simtrace.atroshin.ru**. Автономная копия интерфейса размещена на **https://simtrace.atroshin.ru**.
Это чистый фронтенд: укажите в нём (Settings → Server URL) адрес Это только интерфейс: укажите в нём (Settings → Server URL) адрес
локально запущенного `simtrace-analyser-server`. локально запущенного `simtrace-analyser-server`.
> **Ограничение браузера:** когда PWA отдаётся с публичного HTTPS-хоста, > **Ограничение браузера:** это единственная схема, где интерфейс и API
> доступ к локальному серверу (`http://127.0.0.1:8081`) требует двух > находятся на разных origin, поэтому браузер применяет правила CORS и
> вещей: сервер должен отправлять > Private Network Access. Доступ к локальному серверу
> (`http://127.0.0.1:8081`) требует двух вещей: сервер должен отправлять
> `Access-Control-Allow-Private-Network: true` (этот сервер отправляет), > `Access-Control-Allow-Private-Network: true` (этот сервер отправляет),
> и браузер должен иметь разрешение на доступ к локальной сети — > и браузер должен иметь разрешение на доступ к локальной сети —
> в Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети → > в Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети →
@@ -375,6 +388,77 @@ python -m simtrace2_pysniff --pcap trace.pcap
simtrace2-pysniff --format atr-time simtrace2-pysniff --format atr-time
``` ```
### Параметры CLI утилиты
```
python -m simtrace2_pysniff [OPTIONS]
--format, -f FORMAT Формат вывода (по умолчанию: hex)
hex — обычный hex-дамп
timestamp — строки TPDU с префиксом [ЧЧ:ММ:СС.мс]
atr-time — строки TPDU с префиксом [СССС.мс] от последнего ATR
--gsmtap HOST[:PORT] Отправлять ATR/TPDU как GSMTAP по UDP (порт по умолчанию 4729)
--pcap FILE Записывать перехваченные данные в PCAP для Wireshark
--output, -o FILE Записывать hex-дамп в файл
--no-stdout Не выводить hex-дамп в stdout
--vendor HEX USB vendor ID (по умолчанию: 0x1d50)
--product HEX USB product ID (по умолчанию: автоопределение)
Параметры восстановления:
--no-reconnect Выходить при отключении USB вместо переподключения
--reconnect-delay-min SEC Минимальная задержка переподключения (по умолчанию: 1.0)
--reconnect-delay-max SEC Максимальная задержка переподключения (по умолчанию: 30.0)
--backoff-factor N Множитель экспоненциальной задержки (по умолчанию: 1.5)
--inactivity-timeout SEC Переподключаться после N секунд тишины (по умолчанию: выключено)
```
### Форматы вывода
- **`hex`** (по умолчанию): обычный hex-дамп — `ATR: 3b 9e ...`,
`TPDU: a0 a4 00 00 02 3f 00`
- **`timestamp`**: строки TPDU с локальным временем —
`[14:32:05.123] TPDU: a0 a4 00 00 02 3f 00`
- **`atr-time`**: строки TPDU с секундами от последнего ATR —
`[0001.234] TPDU: a0 a4 00 00 02 3f 00`
Сообщения, не являющиеся TPDU (ATR, PPS, изменения состояния карты, Fi/Di),
выводятся одинаково во всех форматах.
- **GSMTAP**: сообщения ATR и APDU по UDP (порт 4729) в Wireshark
- **PCAP**: сообщения ATR/TPDU как пакеты Ethernet/IP/UDP/GSMTAP,
открываются в Wireshark
### Фоновый режим / демон
SIGHUP автоматически игнорируется, когда stdin не является терминалом
(т.е. при запуске в фоне или через канал). Сессии в терминале на переднем
плане по-прежнему обрабатывают SIGHUP обычным образом.
Для постоянной работы в фоне с выводом в файлы:
```sh
nohup ./sniff-start.sh --output /tmp/sniff.log &> /tmp/sniff-err.log &
# или
FORMAT=timestamp nohup python -m simtrace2_pysniff --output /tmp/sniff.log &> /tmp/sniff-err.log &
```
### Использование как библиотеки
```python
from simtrace2_pysniff import SniffSession
session = SniffSession(inactivity_timeout=30.0)
for msg in session.iter_messages():
print(f"{msg.type}: {msg.data.hex()}")
```
### Восстановление
Утилита по умолчанию переживает сбросы оборудования, отключения кабеля
и зависания прошивки — она переподключается с экспоненциальной задержкой
(1с → 30с). Используйте `--inactivity-timeout`, чтобы также переподключаться
при «тихих» зависаниях прошивки.
## Доступ к USB-устройству ## Доступ к USB-устройству
Нужен для захвата с оборудования SIMtrace2 (утилита и режим Нужен для захвата с оборудования SIMtrace2 (утилита и режим
@@ -407,76 +491,82 @@ sudo udevadm trigger
После этого `python -m simtrace2_pysniff` работает так же, как в Linux. После этого `python -m simtrace2_pysniff` работает так же, как в Linux.
## Параметры CLI утилиты Скрипты запуска для Windows соответствуют shell-версиям и передают все
параметры: `start.bat --capture direct` запускает сервер анализатора, а
`sniff-start.bat` — утилиту захвата. Оба вызывают команду `python`, поэтому
Python 3 должен быть в `PATH` (проверьте `python --version` в новом окне
команды).
``` ## Компоненты
python -m simtrace2_pysniff [OPTIONS]
--format, -f FORMAT Формат вывода (по умолчанию: hex) - **Анализирующий сервер + интерфейс** (`simtrace-analyser-server`) — сохраняет
hex — обычный hex-дамп захваченный трафик в SQLite, декодирует его и отдаёт интерфейс
timestamp — строки TPDU с префиксом [ЧЧ:ММ:СС.мс] ([`frontend/`](frontend/)) вместе с HTTP API на одном origin. Три режима
atr-time — строки TPDU с префиксом [СССС.мс] от последнего ATR захвата: GSMTAP-листенер, захват напрямую с оборудования SIMtrace2 или
--gsmtap HOST[:PORT] Отправлять ATR/TPDU как GSMTAP по UDP (порт по умолчанию 4729) отключённый захват (только просмотр существующих записей).
--pcap FILE Записывать перехваченные данные в PCAP для Wireshark - **Утилита захвата** (`simtrace2-pysniff`) — захватывает трафик
--output, -o FILE Записывать hex-дамп в файл оборудования SIMtrace2 и выводит его в stdout, GSMTAP (Wireshark) или
--no-stdout Не выводить hex-дамп в stdout PCAP-файлы. Это один из нескольких источников GSMTAP, которые принимает
--vendor HEX USB vendor ID (по умолчанию: 0x1d50) анализатор (см. также
--product HEX USB product ID (по умолчанию: автоопределение) [sigrok_iso7816_stream](https://github.com/anttro/sigrok_iso7816_stream)).
- **Интерфейс** ([`frontend/`](frontend/)) — окно трассировки с декодированными
APDU, SIM Toolkit, SMS и SCP80 OTA; сессии, импорт PCAP и трасс COMPRION,
интерфейс EN/RU.
Параметры восстановления: ## Возможности
--no-reconnect Выходить при отключении USB вместо переподключения
--reconnect-delay-min SEC Минимальная задержка переподключения (по умолчанию: 1.0)
--reconnect-delay-max SEC Максимальная задержка переподключения (по умолчанию: 30.0)
--backoff-factor N Множитель экспоненциальной задержки (по умолчанию: 1.5)
--inactivity-timeout SEC Переподключаться после N секунд тишины (по умолчанию: выключено)
```
## Форматы вывода **Декодер APDU** (на стороне сервера, отображается в интерфейсе):
- **`hex`** (по умолчанию): обычный hex-дамп — `ATR: 3b 9e ...`, - Полное декодирование APDU — CLA по ISO 7816-4 / ETSI с **логическим
`TPDU: a0 a4 00 00 02 3f 00` каналом 0-19** (стандартные каналы из b2-b1, расширенные 4-19 из b4-b1
- **`timestamp`**: строки TPDU с локальным временем — по TS 102 221 §10.3), secure messaging и chaining команд, имена INS из
`[14:32:05.123] TPDU: a0 a4 00 00 02 3f 00` таблицы спецификаций, учёт SFI в P1/P2 для READ/UPDATE BINARY и команд
- **`atr-time`**: строки TPDU с секундами от последнего ATR — с записями; ответ MANAGE CHANNEL показывает назначенный картой канал
`[0001.234] TPDU: a0 a4 00 00 02 3f 00` - Статусные слова — полный набор ISO 7816-4 плюс специфичные для UICC
значения: `91XX` (ожидается proactive-команда), `62F1/F2/F3`,
`63F1/F2` (больше данных), `9300` (SAT busy), `9850`, `9862–64`
- SIM Toolkit — SAT (GSM 11.14), CAT (TS 102 223) и USAT (TS 31.111):
proactive-команды с декодированными квалификаторами, TERMINAL RESPONSE,
ENVELOPE (Menu Selection, Call Control, SMS-PP download…); учитываются
известные несоответствия старых карт, нераспознанные TLV сохраняются
- TERMINAL CAPABILITY (TS 102 221 §11.1.19) — питание терминала (класс
напряжения, максимальный ток, тактовая частота), расширенные логические
каналы, дополнительные интерфейсы (UICC-CLF) и возможности eUICC
(SGP.22/SGP.32)
- SMS TPDU — SMS-DELIVER/SUBMIT, метки времени SCTS, информационные
элементы UDH
- SCP80 OTA secured packets — биты SPI (шифрование, требование/режим PoR,
RC/CC/DS, счётчик), алгоритм KIc/KID + набор ключей, TAR, CNTR/PCNTR;
декодирование Response Packet (PoR) по GET RESPONSE; **опциональная
расшифровка на клиенте** зашифрованных пакетов — вставьте 3DES/AES-ключ
карты в панели деталей, чтобы увидеть настоящие CNTR и защищённые данные
приложения (ключ остаётся в браузере, только в памяти; зашифрованные
в эфире CNTR/PCNTR/RC — это шифротекст, и они так и помечены); защищённое
сообщение приложения декодируется как поток **C-APDU** (SELECT,
UPDATE BINARY, …) с полной разбивкой CLA/INS/P1/P2/Lc/Le — сразу для
открытого текста или после расшифровки для зашифрованных пакетов
- Разбор ATR (пересчёт тактовой частоты, Fi/Di, битовая маска T,
безопасен для усечённых ATR) и FCP/FCI TLV; исторические байты
разбираются как COMPACT-TLV объекты UICC (TS 102 221 §6.3.1) — card
service data, **card capabilities** (ISO 7816-4 §8.1.1.2.7 таблица 88:
способы назначения и заявленное максимальное число логических каналов,
`8 or more` у карт с расширенными каналами) и завершающий индикатор
состояния (LCS/SW)
Сообщения, не являющиеся TPDU (ATR, PPS, изменения состояния карты, Fi/Di), **Интерфейс SIMtrace Analyser** ([`frontend/`](frontend/)):
выводятся одинаково во всех форматах.
- **GSMTAP**: сообщения ATR и APDU по UDP (порт 4729) в Wireshark - Окно трассировки с режимами «Карта»/«Список» — proactive,
- **PCAP**: сообщения ATR/TPDU как пакеты Ethernet/IP/UDP/GSMTAP, TERMINAL RESPONSE, ENVELOPE и AUTH-команды выделяются в режиме карты
открываются в Wireshark - Сессии с прямыми ссылками (`#session=N`), импорт PCAP
(`.pcap/.pcapng/.cap`), импорт трасс COMPRION (текстовый экспорт
## Фоновый режим / демон «ISO Protocol Layer» из Device Manager), поиск и **фильтр команд с
множественным выбором** — любые команды и/или готовые пресеты («Проактивные
SIGHUP автоматически игнорируется, когда stdin не является терминалом сессии»: FETCH, TERMINAL PROFILE и все команды с ответом SW 91xx;
(т.е. при запуске в фоне или через канал). Сессии в терминале на переднем «Файловые операции»: SELECT,
плане по-прежнему обрабатывают SIGHUP обычным образом. READ/UPDATE BINARY и READ/UPDATE RECORD, вместе с GET RESPONSE, несущим
данные выбранной команды)
Для постоянной работы в фоне с выводом в файлы: - **PWA** (progressive web app): устанавливается на рабочий стол или домашний
экран и работает офлайн; тёмная тема, интерфейс на английском/русском
```sh
nohup ./sniff-start.sh --output /tmp/sniff.log &> /tmp/sniff-err.log &
# или
FORMAT=timestamp nohup python -m simtrace2_pysniff --output /tmp/sniff.log &> /tmp/sniff-err.log &
```
## Использование как библиотеки
```python
from simtrace2_pysniff import SniffSession
session = SniffSession(inactivity_timeout=30.0)
for msg in session.iter_messages():
print(f"{msg.type}: {msg.data.hex()}")
```
## Восстановление
Утилита по умолчанию переживает сбросы оборудования, отключения кабеля
и зависания прошивки — она переподключается с экспоненциальной задержкой
(1с → 30с). Используйте `--inactivity-timeout`, чтобы также переподключаться
при «тихих» зависаниях прошивки.
## Связанные проекты ## Связанные проекты
@@ -486,3 +576,10 @@ for msg in session.iter_messages():
- **[sigrok_iso7816_stream](https://github.com/anttro/sigrok_iso7816_stream)** — - **[sigrok_iso7816_stream](https://github.com/anttro/sigrok_iso7816_stream)** —
пассивный ISO 7816 сниффер на базе FX2LP, выдающий такой же поток пассивный ISO 7816 сниффер на базе FX2LP, выдающий такой же поток
GSMTAP-SIM на UDP 4729 (принимается GSMTAP-листенером этого проекта). GSMTAP-SIM на UDP 4729 (принимается GSMTAP-листенером этого проекта).
## Лицензия
MIT — см. [LICENSE](LICENSE). Декодеры и реализация DES/3DES в интерфейсе написаны
самостоятельно по спецификациям ISO 7816, ETSI и 3GPP; сторонний код в проект
не включён. PyUSB (BSD-3-Clause) — зависимость для прямого захвата с
оборудования.
+1 -1
View File
@@ -3389,7 +3389,7 @@ function onSearchChange() {
} }
// ──────────────────── Versioning ──────────────────── // ──────────────────── Versioning ────────────────────
const PWA_VERSION = '2.7.10'; const PWA_VERSION = '2.7.13';
const MIN_SERVER_VERSION = '2.3.0'; const MIN_SERVER_VERSION = '2.3.0';
function parseVersion(v) { function parseVersion(v) {
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simtrace-analyser-v92'; const CACHE = 'simtrace-analyser-v95';
const PRECACHE = [ const PRECACHE = [
'index.html', 'index.html',
+4 -3
View File
@@ -1,13 +1,14 @@
[build-system] [build-system]
requires = ["setuptools>=64"] requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project] [project]
name = "simtrace-analyser" name = "simtrace-analyser"
version = "2.7.10" version = "2.7.13"
description = "SIMtrace Analyser — APDU trace analyser for GSMTAP/PCAP captures (bundles the simtrace2-pysniff utility)" description = "SIMtrace Analyser — APDU trace analyser for GSMTAP/PCAP captures (bundles the simtrace2-pysniff utility)"
readme = {file = "README.md", content-type = "text/markdown"} readme = {file = "README.md", content-type = "text/markdown"}
license = {text = "GPL-2.0-or-later"} license = "MIT"
license-files = ["LICENSE"]
requires-python = ">=3.9" requires-python = ">=3.9"
dependencies = [ dependencies = [
"pyusb>=1.2", "pyusb>=1.2",
+132
View File
@@ -0,0 +1,132 @@
"""Build the ready-to-run runtime zip for a GitHub release.
The wheel needs ``pip`` and the GitHub source archive carries the whole
repository (tests, CI, the gitignored ``tools/``). This zip is the runtime
tree only: extract it anywhere, run ``start.bat`` on Windows or
``./start.sh`` elsewhere, and the server serves the bundled PWA. Python 3
must be on ``PATH``; PyUSB is only needed for direct hardware capture.
The build is deliberately simple - the explicit include lists below are the
whole manifest, so nothing from ``tests/``, ``.github/``, ``pyproject.toml``
or a build directory can leak into the archive by accident.
"""
import argparse
import sys
import zipfile
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
# The script lives outside the packages; import the version from the checkout.
sys.path.insert(0, str(REPO))
from simtrace_analyser.version import __version__ # noqa: E402
# The Python packages (all modules, no caches).
PACKAGES = ('simtrace_analyser', 'simtrace2_pysniff')
# The PWA runtime files; ``tests/`` and ``package.json`` are dev-only.
FRONTEND = 'frontend'
FRONTEND_EXCLUDE = ('tests', 'package.json')
# Repo-root files that make the extracted tree usable. LICENSE is included
# once the repository has one.
ROOT_FILES = (
'start.sh',
'start.bat',
'sniff-start.sh',
'sniff-start.bat',
'70-simtrace2-pysniff.rules',
'README.md',
'README_RUS.md',
'frontend.png',
'LICENSE',
)
# Skip editor/byte-code droppings wherever they appear.
SKIP_DIRS = ('__pycache__',)
SKIP_NAMES = ('.DS_Store',)
SKIP_SUFFIXES = ('.pyc',)
def _files(root):
"""Yield the regular files under *root*, sorted and cache-free."""
for path in sorted(root.rglob('*')):
if not path.is_file():
continue
if any(part in SKIP_DIRS for part in path.parts):
continue
if path.name in SKIP_NAMES or path.suffix in SKIP_SUFFIXES:
continue
yield path
def _write_bat(zf, path, arcname):
"""Write a .bat entry with CRLF line endings.
The checkout stores the batch files with LF, and cmd.exe mishandles a
batch file whose lines end in a bare LF (labels and ``goto`` in
particular), so the archive gets the Windows form. The source files
stay untouched. ``writestr`` does not copy the file mode, so set it.
"""
data = path.read_bytes().replace(b'\r\n', b'\n').replace(b'\n', b'\r\n')
info = zipfile.ZipInfo.from_file(path, arcname)
info.external_attr = 0o644 << 16
info.compress_type = zipfile.ZIP_DEFLATED
zf.writestr(info, data)
def build_zip(output_dir, version=None):
"""Write ``simtrace-analyser-<version>.zip`` into *output_dir*.
Returns the archive path. The zip has a single top-level directory
``simtrace-analyser-<version>/`` holding the runtime tree.
"""
version = version or __version__
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
archive = output_dir / ('simtrace-analyser-%s.zip' % version)
top = 'simtrace-analyser-%s' % version
with zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED) as zf:
for package in PACKAGES:
for path in _files(REPO / package):
zf.write(path, '%s/%s' % (top, path.relative_to(REPO).as_posix()))
for path in _files(REPO / FRONTEND):
rel = path.relative_to(REPO / FRONTEND)
if rel.parts[0] in FRONTEND_EXCLUDE:
continue
zf.write(path, '%s/%s' % (top, path.relative_to(REPO).as_posix()))
for name in ROOT_FILES:
path = REPO / name
if not path.is_file():
# LICENSE is not in the repository yet; include it once it is.
continue
arcname = '%s/%s' % (top, name)
if path.suffix == '.bat':
_write_bat(zf, path, arcname)
else:
zf.write(path, arcname)
return archive
def main(argv=None):
parser = argparse.ArgumentParser(
description='Build the runtime zip attached to a release')
parser.add_argument('--output', default='dist', metavar='DIR',
help='directory for the archive (default: dist)')
args = parser.parse_args(argv)
archive = build_zip(args.output)
with zipfile.ZipFile(archive) as zf:
entries = len(zf.namelist())
print(archive)
print('%d entries' % entries)
return 0
if __name__ == '__main__':
sys.exit(main())
+7 -2
View File
@@ -1,7 +1,8 @@
"""simtrace-analyser-server — CLI entry point. """simtrace-analyser-server — CLI entry point.
Usage: Usage:
simtrace-analyser-server --capture gsmtap [--gsmtap-port 4729] [--port 8081] simtrace-analyser-server --capture gsmtap [--gsmtap-bind 0.0.0.0]
[--gsmtap-port 4729] [--port 8081]
simtrace-analyser-server --capture direct [--port 8081] simtrace-analyser-server --capture direct [--port 8081]
""" """
@@ -46,6 +47,9 @@ def main():
help='Capture mode: gsmtap (listen UDP 4729), direct (SIMtrace2 USB), or disabled (no capture)') help='Capture mode: gsmtap (listen UDP 4729), direct (SIMtrace2 USB), or disabled (no capture)')
p.add_argument('--gsmtap-port', type=int, default=4729, p.add_argument('--gsmtap-port', type=int, default=4729,
help='UDP port for GSMTAP listener (default: 4729)') help='UDP port for GSMTAP listener (default: 4729)')
p.add_argument('--gsmtap-bind', default='127.0.0.1', metavar='ADDR',
help='UDP bind address for the GSMTAP listener '
'(default: 127.0.0.1; use 0.0.0.0 in a container)')
p.add_argument('--log-interval', type=float, default=60.0, p.add_argument('--log-interval', type=float, default=60.0,
help='Seconds between capture liveness heartbeat logs ' help='Seconds between capture liveness heartbeat logs '
'(default: 60.0)') '(default: 60.0)')
@@ -61,7 +65,7 @@ def main():
capture = None capture = None
if args.capture == 'gsmtap': if args.capture == 'gsmtap':
backend = GsmtapListener(bind_port=args.gsmtap_port) backend = GsmtapListener(bind_ip=args.gsmtap_bind, bind_port=args.gsmtap_port)
capture = CaptureManager(backend, db, log_interval=args.log_interval) capture = CaptureManager(backend, db, log_interval=args.log_interval)
elif args.capture == 'direct': elif args.capture == 'direct':
backend = DirectSniffer() backend = DirectSniffer()
@@ -78,6 +82,7 @@ def main():
print(f'simtrace-analyser-server v{__version__} — http://{args.host}:{args.port}', file=sys.stderr) print(f'simtrace-analyser-server v{__version__} — http://{args.host}:{args.port}', file=sys.stderr)
print(f' capture mode: {args.capture}', file=sys.stderr) print(f' capture mode: {args.capture}', file=sys.stderr)
if args.capture == 'gsmtap': if args.capture == 'gsmtap':
print(f' GSMTAP bind: {args.gsmtap_bind}', file=sys.stderr)
print(f' GSMTAP port: {args.gsmtap_port}', file=sys.stderr) print(f' GSMTAP port: {args.gsmtap_port}', file=sys.stderr)
print(f' database: {args.db}', file=sys.stderr) print(f' database: {args.db}', file=sys.stderr)
print(f' PWA served at http://{args.host}:{args.port}/', file=sys.stderr) print(f' PWA served at http://{args.host}:{args.port}/', file=sys.stderr)
+5 -3
View File
@@ -39,15 +39,17 @@ def gsmtap_msg_type(sub_type):
class GsmtapListener: class GsmtapListener:
def __init__(self, bind_port=4729): def __init__(self, bind_port=4729, bind_ip='127.0.0.1'):
self._bind_port = bind_port self._bind_port = bind_port
self._receiver = GsmtapReceiver(bind_port=bind_port) self._bind_ip = bind_ip
self._receiver = GsmtapReceiver(bind_ip=bind_ip, bind_port=bind_port)
self._running = False self._running = False
self._dropped = 0 self._dropped = 0
def start(self): def start(self):
if getattr(self._receiver, '_closed', False): if getattr(self._receiver, '_closed', False):
self._receiver = GsmtapReceiver(bind_port=self._bind_port) self._receiver = GsmtapReceiver(bind_ip=self._bind_ip,
bind_port=self._bind_port)
self._running = True self._running = True
self._dropped = 0 self._dropped = 0
+1 -1
View File
@@ -1,3 +1,3 @@
"""Single source of truth for the SIMtrace Analyser version.""" """Single source of truth for the SIMtrace Analyser version."""
__version__ = '2.7.10' __version__ = '2.7.13'
+7 -7
View File
@@ -59,7 +59,7 @@ if "%~1"=="--inactivity-timeout" (
shift shift
goto :parse_args goto :parse_args
) )
set "EXTRA_ARGS=!EXTRA_ARGS! %~1" set "EXTRA_ARGS=!EXTRA_ARGS! %1"
shift shift
goto :parse_args goto :parse_args
@@ -67,11 +67,11 @@ goto :parse_args
echo Starting simtrace2-pysniff... echo Starting simtrace2-pysniff...
set "ARGS=" set "ARGS="
if not "%GSMTAP%"=="" set "ARGS=!ARGS! --gsmtap %GSMTAP%" if not "%GSMTAP%"=="" set "ARGS=!ARGS! --gsmtap "%GSMTAP%""
if not "%PCAP%"=="" set "ARGS=!ARGS! --pcap %PCAP%" if not "%PCAP%"=="" set "ARGS=!ARGS! --pcap "%PCAP%""
if not "%OUTPUT%"=="" set "ARGS=!ARGS! --output %OUTPUT%" if not "%OUTPUT%"=="" set "ARGS=!ARGS! --output "%OUTPUT%""
if not "%FORMAT%"=="" set "ARGS=!ARGS! --format %FORMAT%" if not "%FORMAT%"=="" set "ARGS=!ARGS! --format "%FORMAT%""
if not "%INACTIVITY_TIMEOUT%"=="0" set "ARGS=!ARGS! --inactivity-timeout %INACTIVITY_TIMEOUT%" if not "%INACTIVITY_TIMEOUT%"=="0" set "ARGS=!ARGS! --inactivity-timeout "%INACTIVITY_TIMEOUT%""
set "PYTHONPATH=%~dp0" set "PYTHONPATH=%~dp0"
python -m simtrace2_pysniff %ARGS% %EXTRA_ARGS% python -m simtrace2_pysniff !ARGS! !EXTRA_ARGS!
+8 -3
View File
@@ -3,7 +3,7 @@ setlocal enabledelayedexpansion
:: simtrace-analyser-server startup script for Windows :: simtrace-analyser-server startup script for Windows
:: ::
:: Usage: start.bat [--capture gsmtap|direct] [--port PORT] [--db PATH] [--gsmtap-port PORT] :: Usage: start.bat [--capture gsmtap|direct|disabled] [--port PORT] [--db PATH] [--gsmtap-port PORT]
:: ::
:: Options set via environment variables act as defaults; CLI flags override them. :: Options set via environment variables act as defaults; CLI flags override them.
@@ -42,7 +42,7 @@ if "%~1"=="--gsmtap-port" (
shift shift
goto :parse_args goto :parse_args
) )
set "EXTRA_ARGS=!EXTRA_ARGS! %~1" set "EXTRA_ARGS=!EXTRA_ARGS! %1"
shift shift
goto :parse_args goto :parse_args
@@ -50,5 +50,10 @@ goto :parse_args
echo Starting simtrace-analyser-server... echo Starting simtrace-analyser-server...
echo PWA (simtrace-analyser) will be served at http://127.0.0.1:%PORT%/ echo PWA (simtrace-analyser) will be served at http://127.0.0.1:%PORT%/
set "ARGS=--capture "%CAPTURE%" --port "%PORT%""
if not "%DB%"=="" set "ARGS=!ARGS! --db "%DB%""
if /i "%CAPTURE%"=="gsmtap" set "ARGS=!ARGS! --gsmtap-port "%GSMTAP_PORT%""
set "ARGS=!ARGS! !EXTRA_ARGS!"
set "PYTHONPATH=%~dp0" set "PYTHONPATH=%~dp0"
python -m simtrace_analyser --capture %CAPTURE% --port %PORT% --gsmtap-port %GSMTAP_PORT% %EXTRA_ARGS% python -m simtrace_analyser !ARGS!
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# simtrace-analyser-server startup script # simtrace-analyser-server startup script
# #
# Usage: ./start.sh [--capture gsmtap|direct] [--port PORT] [--db PATH] [--gsmtap-port PORT] # Usage: ./start.sh [--capture gsmtap|direct|disabled] [--port PORT] [--db PATH] [--gsmtap-port PORT]
# #
# Options set via environment variables act as defaults; CLI flags override them. # Options set via environment variables act as defaults; CLI flags override them.
+35
View File
@@ -206,6 +206,41 @@ class TestGsmtapListenerRobustness(unittest.TestCase):
self.assertEqual(listener.dropped, 1) self.assertEqual(listener.dropped, 1)
class TestGsmtapListenerBind(unittest.TestCase):
def test_bind_ip_reaches_the_receiver(self):
from simtrace_analyser import capture
calls = []
class _FakeReceiver:
def __init__(self, bind_ip='127.0.0.1', bind_port=4729):
calls.append((bind_ip, bind_port))
def close(self):
pass
real = capture.GsmtapReceiver
capture.GsmtapReceiver = _FakeReceiver
try:
listener = capture.GsmtapListener(bind_ip='0.0.0.0', bind_port=4729)
listener._receiver._closed = True # force start() to re-create it
listener.start()
finally:
capture.GsmtapReceiver = real
self.assertEqual(calls, [('0.0.0.0', 4729), ('0.0.0.0', 4729)])
def test_all_interfaces_bind(self):
from simtrace_analyser.capture import GsmtapListener
listener = GsmtapListener(bind_ip='0.0.0.0', bind_port=0)
self.addCleanup(listener.stop)
self.assertEqual(listener._receiver._sock.getsockname()[0], '0.0.0.0')
def test_default_bind_is_loopback(self):
from simtrace_analyser.capture import GsmtapListener
listener = GsmtapListener(bind_port=0)
self.addCleanup(listener.stop)
self.assertEqual(listener._receiver._sock.getsockname()[0], '127.0.0.1')
class TestCaptureLoopRobustness(unittest.TestCase): class TestCaptureLoopRobustness(unittest.TestCase):
def test_capture_loop_survives_insert_error(self): def test_capture_loop_survives_insert_error(self):
mgr = CaptureManager(_OneShotBackend(), _NullDb()) mgr = CaptureManager(_OneShotBackend(), _NullDb())
+9
View File
@@ -70,6 +70,15 @@ class TestDistributionMetadata(unittest.TestCase):
# The sdist carries frontend/, so a wheel built from it has the PWA. # The sdist carries frontend/, so a wheel built from it has the PWA.
self.assertIn('graft frontend', self._read('MANIFEST.in')) self.assertIn('graft frontend', self._read('MANIFEST.in'))
def test_license_is_mit(self):
# The project is MIT; the metadata and the file must not drift apart.
pyproject = self._read('pyproject.toml')
self.assertIn('license = "MIT"', pyproject)
self.assertIn('license-files = ["LICENSE"]', pyproject)
license_text = self._read('LICENSE')
self.assertTrue(license_text.startswith('MIT License\n'))
self.assertIn('Permission is hereby granted, free of charge', license_text)
def test_setup_py_bundles_the_pwa(self): def test_setup_py_bundles_the_pwa(self):
setup_py = self._read('setup.py') setup_py = self._read('setup.py')
self.assertIn('frontend', setup_py) self.assertIn('frontend', setup_py)
+1 -1
View File
@@ -224,7 +224,7 @@ class TestImportEndpoint(unittest.TestCase):
class TestVersion(unittest.TestCase): class TestVersion(unittest.TestCase):
def test_package_version(self): def test_package_version(self):
from simtrace2_pysniff import __version__ from simtrace2_pysniff import __version__
self.assertEqual(__version__, '2.7.10') self.assertEqual(__version__, '2.7.13')
def test_status_includes_version(self): def test_status_includes_version(self):
import os import os
+105
View File
@@ -0,0 +1,105 @@
"""Tests for the ready-to-run release zip (scripts/build_release_zip.py)."""
import importlib.util
import re
import tempfile
import unittest
import zipfile
from pathlib import Path
from simtrace_analyser.version import __version__
REPO = Path(__file__).resolve().parents[1]
# scripts/ is not a package, so load the builder from its path.
_spec = importlib.util.spec_from_file_location(
'build_release_zip', REPO / 'scripts' / 'build_release_zip.py')
build_release_zip = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(build_release_zip)
class TestReleaseZip(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls._tmp = tempfile.TemporaryDirectory()
cls.archive = build_release_zip.build_zip(cls._tmp.name)
with zipfile.ZipFile(cls.archive) as zf:
cls.names = zf.namelist()
@classmethod
def tearDownClass(cls):
cls._tmp.cleanup()
def test_name_and_single_top_level_directory(self):
top = 'simtrace-analyser-%s' % __version__
self.assertEqual(self.archive.name, top + '.zip')
for name in self.names:
self.assertTrue(name.startswith(top + '/'),
'%s is outside %s/' % (name, top))
def test_required_entries(self):
top = 'simtrace-analyser-%s' % __version__
for rel in (
'simtrace_analyser/__main__.py',
'simtrace_analyser/version.py',
'simtrace2_pysniff/__main__.py',
'frontend/index.html',
'frontend/sw.js',
'frontend/manifest.json',
'start.sh',
'start.bat',
'sniff-start.sh',
'sniff-start.bat',
'70-simtrace2-pysniff.rules',
'LICENSE',
'README.md'):
self.assertIn('%s/%s' % (top, rel), self.names)
def test_dev_only_entries_are_excluded(self):
for name in self.names:
self.assertNotIn('/tests/', name)
self.assertNotIn('/__pycache__/', name)
self.assertNotIn('.pyc', name)
self.assertNotIn('/.git', name)
self.assertNotIn('/.github/', name)
self.assertNotIn(
'simtrace-analyser-%s/frontend/package.json' % __version__,
self.names)
self.assertNotIn(
'simtrace-analyser-%s/pyproject.toml' % __version__, self.names)
def test_precache_files_are_in_the_zip(self):
# Drift guard: every asset the service worker precaches must travel
# with the PWA, otherwise an offline install fails on a fresh extract.
text = (REPO / 'frontend' / 'sw.js').read_text()
match = re.search(r'PRECACHE\s*=\s*\[(.*?)\]', text, re.S)
self.assertIsNotNone(match, 'no PRECACHE array in frontend/sw.js')
assets = re.findall(r"'([^']+)'", match.group(1))
self.assertTrue(assets)
top = 'simtrace-analyser-%s' % __version__
for asset in assets:
self.assertIn('%s/frontend/%s' % (top, asset), self.names,
'precached %s is missing from the zip' % asset)
def test_batch_files_use_crlf(self):
with zipfile.ZipFile(self.archive) as zf:
bats = [n for n in self.names if n.endswith('.bat')]
self.assertTrue(bats)
for name in bats:
data = zf.read(name)
self.assertIn(b'\r\n', data, name)
self.assertNotIn(b'\n', data.replace(b'\r\n', b''),
'%s has a bare LF' % name)
def test_shell_script_is_lf_and_executable(self):
name = 'simtrace-analyser-%s/start.sh' % __version__
with zipfile.ZipFile(self.archive) as zf:
info = zf.getinfo(name)
data = zf.read(name)
self.assertNotIn(b'\r\n', data)
self.assertTrue(info.external_attr >> 16 & 0o100,
'start.sh lost its executable bit')
if __name__ == '__main__':
unittest.main()