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
include LICENSE
include README.md README_RUS.md 70-simtrace2-pysniff.rules
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
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
- **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
![SIMtrace Analyser UI](frontend.png)
## Prerequisites
- **Python 3.9+**
- **PyUSB** — `pip install pyusb` (needed only for capture from SIMtrace2
hardware; GSMTAP/UDP capture and PCAP import work without it)
**Required**
- **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
SIMtrace2 driver must be swapped (see [USB Device Access](#usb-device-access))
- **SIMtrace2 hardware** running firmware in *trace* mode (only for direct
capture; VID `1d50`, PID `60e3`, USB class `ff`, subclass `01`). For
building and flashing trace firmware, see the
- **SIMtrace2 hardware** running firmware in *trace* mode (VID `1d50`, PID
`60e3`, USB class `ff`, subclass `01`); building and flashing trace firmware
is described in the
[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
git clone https://github.com/anttro/simtrace-analyser.git
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
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
```
`pip` can also fetch the wheel straight from the release page:
### First run
```sh
pip install https://github.com/anttro/simtrace-analyser/releases/download/v2.7.10/simtrace_analyser-2.7.10-py3-none-any.whl
```
Open **http://127.0.0.1:8081/** in a browser. `Ctrl+C` stops the server.
## 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
the bundled **SIMtrace Analyser** PWA (`frontend/` in a source checkout,
bundled into the package on install) on the same origin. Three
capture modes:
the bundled **SIMtrace Analyser** frontend (`frontend/` in a source checkout,
bundled into the package on install) on the same origin. Three capture
modes:
```sh
# Listen for GSMTAP from simtrace2-pysniff (or original simtrace2-sniff):
@@ -149,6 +141,27 @@ simtrace-analyser-server --capture direct
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
with the local time): `Capture started` on begin, a periodic
`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
(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
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).
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)
--capture MODE gsmtap | direct | disabled (default: gsmtap)
--gsmtap-port PORT UDP port for the GSMTAP listener (default: 4729)
--web-dir PATH PWA static files directory (default: the PWA bundled in
the installed package, or <repo>/frontend from source)
--gsmtap-bind ADDR UDP bind address for the GSMTAP listener (default: 127.0.0.1;
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
server, so the UI and the API share an origin and no CORS/Private-Network-Access
setup is involved. Use `--web-dir PATH` to serve a different PWA directory
(default: the bundled PWA, or `<repo>/frontend` in a source checkout).
Then open **http://127.0.0.1:8081/** in a browser. Use `--web-dir PATH` to
serve a different UI directory (default: the bundled frontend, 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
`simtrace-analyser-server`.
> **Browser restriction:** when the PWA is served from a public HTTPS host,
> reaching a local server (`http://127.0.0.1:8081`) requires two things: the
> server must send `Access-Control-Allow-Private-Network: true` (this server
> does), and the browser must be allowed to access the local network — in
> Chrome/Edge/Vivaldi: Site settings → Local network access → 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.
> **Browser restriction:** this is the one setup where the UI and the API have
> different origins, so the browser applies CORS and Private Network Access
> rules. Reaching a local server (`http://127.0.0.1:8081`) requires two
> things: the server must send `Access-Control-Allow-Private-Network: true`
> (this server does), and the browser must be allowed to access the local
> network — in Chrome/Edge/Vivaldi: Site settings → Local network access →
> 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`)
@@ -366,6 +383,71 @@ If installed via `pip install -e .`, use the console script directly:
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
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.
## 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).
```
python -m simtrace2_pysniff [OPTIONS]
## Components
--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)
- **Analysis server + UI** (`simtrace-analyser-server`) — stores captured
traffic in SQLite, decodes it, and serves the UI ([`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)).
- **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:
--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)
```
## Features
## 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`
- **`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`
- 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)
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
- **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.
- 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** (progressive web app: installs to the desktop or home
screen and works offline), dark theme, English/Russian UI
## 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)** —
FX2LP-based passive ISO 7816 sniffer that emits the same GSMTAP-SIM stream
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
и может передавать GSMTAP этому анализатору.
![PWA SIMtrace Analyser](frontend.png)
📢 Новости и обновления проекта: [atroshin.ru](https://atroshin.ru) | [Telegram-канал](https://t.me/youeyeseesee)
## Компоненты
- **Анализирующий сервер + 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, тёмная тема, интерфейс на английском/русском
![Интерфейс SIMtrace Analyser](frontend.png)
## Требования
- **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
нужно заменить драйвер SIMtrace2
(см. [Доступ к USB-устройству](#доступ-к-usb-устройству))
- **Оборудование SIMtrace2** с прошивкой в режиме *trace* (только для
прямого захвата; VID `1d50`, PID `60e3`, USB class `ff`, subclass `01`).
О сборке и прошивке trace-прошивки см.
[проект SIMtrace2](https://gitea.osmocom.org/sim-card/simtrace2).
- **Оборудование SIMtrace2** с прошивкой в режиме *trace* (VID `1d50`, PID
`60e3`, USB class `ff`, subclass `01`); сборка и прошивка trace-прошивки
описаны в
[проекте SIMtrace2](https://gitea.osmocom.org/sim-card/simtrace2).
Других зависимостей нет. Вывод PCAP, GSMTAP и hex dump использует только
стандартную библиотеку Python.
Для `--capture gsmtap` (источник GSMTAP может быть даже на другой машине),
`--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
git clone https://github.com/anttro/simtrace-analyser.git
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
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
```
`pip` также может скачать wheel прямо со страницы релиза:
### Первый запуск
```sh
pip install https://github.com/anttro/simtrace-analyser/releases/download/v2.7.10/simtrace_analyser-2.7.10-py3-none-any.whl
```
Откройте **http://127.0.0.1:8081/** в браузере. `Ctrl+C` останавливает
сервер.
## Запуск
- `--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 вместе
со встроенной PWA **SIMtrace Analyser** (`frontend/` в исходном клоне, при
со встроенным фронтендом **SIMtrace Analyser** (`frontend/` в исходном клоне, при
установке — внутри пакета) на том же origin.
Три режима захвата:
@@ -154,6 +144,27 @@ simtrace-analyser-server --capture direct
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 информацию о жизненном цикле захвата (каждая строка
с локальным временем): `Capture started` при старте, периодический
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 — байтовое представление
протокола (ATR, PPS, APDU с кодами возврата). Экспорт *ISO Application
Layer* не содержит необработанных байтов ответов и не нужен; при его выборе
выводится указание на файл Protocol Layer (та же проверка выполняется в PWA
выводится указание на файл Protocol Layer (та же проверка выполняется в интерфейсе
до загрузки и на сервере — для вызовов API).
Обмены перекодируются в такие же «проводные» TPDU, какие несёт захват
@@ -311,25 +322,27 @@ TS 51.011), поэтому FID, повторно используемый в р
--db FILE путь к базе SQLite (по умолчанию: ~/.simtrace-analyser/sessions.db)
--capture MODE gsmtap | direct | disabled (по умолчанию: gsmtap)
--gsmtap-port PORT UDP-порт листенера GSMTAP (по умолчанию: 4729)
--web-dir PATH каталог статических файлов PWA (по умолчанию: PWA внутри
установленного пакета или <repo>/frontend при запуске из исходников)
--gsmtap-bind ADDR UDP-адрес привязки листенера GSMTAP (по умолчанию: 127.0.0.1;
используйте 0.0.0.0, чтобы принимать из Docker-сети или с другого хоста)
--web-dir PATH каталог статических файлов интерфейса (по умолчанию:
фронтенд внутри установленного пакета или
<repo>/frontend при запуске из исходников)
```
Затем откройте **http://127.0.0.1:8081/** в браузере — PWA отдаётся
сервером, поэтому UI и API используют один origin, и настройка
CORS/Private-Network-Access не требуется. Используйте `--web-dir PATH`,
чтобы отдавать другой каталог PWA (по умолчанию: PWA внутри пакета или
`<repo>/frontend` при запуске из исходников).
Затем откройте **http://127.0.0.1:8081/** в браузере. Используйте
`--web-dir PATH`, чтобы отдавать другой каталог интерфейса (по умолчанию:
фронтенд внутри пакета или `<repo>/frontend` при запуске из исходников).
### Размещённая PWA (лендинг)
### Размещённый интерфейс (лендинг)
Автономная копия PWA размещена на **https://simtrace.atroshin.ru**.
Это чистый фронтенд: укажите в нём (Settings → Server URL) адрес
Автономная копия интерфейса размещена на **https://simtrace.atroshin.ru**.
Это только интерфейс: укажите в нём (Settings → Server URL) адрес
локально запущенного `simtrace-analyser-server`.
> **Ограничение браузера:** когда PWA отдаётся с публичного HTTPS-хоста,
> доступ к локальному серверу (`http://127.0.0.1:8081`) требует двух
> вещей: сервер должен отправлять
> **Ограничение браузера:** это единственная схема, где интерфейс и API
> находятся на разных origin, поэтому браузер применяет правила CORS и
> Private Network Access. Доступ к локальному серверу
> (`http://127.0.0.1:8081`) требует двух вещей: сервер должен отправлять
> `Access-Control-Allow-Private-Network: true` (этот сервер отправляет),
> и браузер должен иметь разрешение на доступ к локальной сети —
> в Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети →
@@ -375,6 +388,77 @@ python -m simtrace2_pysniff --pcap trace.pcap
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-устройству
Нужен для захвата с оборудования SIMtrace2 (утилита и режим
@@ -407,76 +491,82 @@ sudo udevadm trigger
После этого `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)
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 (по умолчанию: автоопределение)
- **Анализирующий сервер + интерфейс** (`simtrace-analyser-server`) — сохраняет
захваченный трафик в SQLite, декодирует его и отдаёт интерфейс
([`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)).
- **Интерфейс** ([`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 ...`,
`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`
- Полное декодирование 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)
Сообщения, не являющиеся TPDU (ATR, PPS, изменения состояния карты, Fi/Di),
выводятся одинаково во всех форматах.
**Интерфейс SIMtrace Analyser** ([`frontend/`](frontend/)):
- **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`, чтобы также переподключаться
при «тихих» зависаниях прошивки.
- Окно трассировки с режимами «Карта»/«Список» — 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** (progressive web app): устанавливается на рабочий стол или домашний
экран и работает офлайн; тёмная тема, интерфейс на английском/русском
## Связанные проекты
@@ -486,3 +576,10 @@ for msg in session.iter_messages():
- **[sigrok_iso7816_stream](https://github.com/anttro/sigrok_iso7816_stream)** —
пассивный ISO 7816 сниффер на базе FX2LP, выдающий такой же поток
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 ────────────────────
const PWA_VERSION = '2.7.10';
const PWA_VERSION = '2.7.13';
const MIN_SERVER_VERSION = '2.3.0';
function parseVersion(v) {
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simtrace-analyser-v92';
const CACHE = 'simtrace-analyser-v95';
const PRECACHE = [
'index.html',
+4 -3
View File
@@ -1,13 +1,14 @@
[build-system]
requires = ["setuptools>=64"]
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"
[project]
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)"
readme = {file = "README.md", content-type = "text/markdown"}
license = {text = "GPL-2.0-or-later"}
license = "MIT"
license-files = ["LICENSE"]
requires-python = ">=3.9"
dependencies = [
"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.
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]
"""
@@ -46,6 +47,9 @@ def main():
help='Capture mode: gsmtap (listen UDP 4729), direct (SIMtrace2 USB), or disabled (no capture)')
p.add_argument('--gsmtap-port', type=int, 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,
help='Seconds between capture liveness heartbeat logs '
'(default: 60.0)')
@@ -61,7 +65,7 @@ def main():
capture = None
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)
elif args.capture == 'direct':
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' capture mode: {args.capture}', file=sys.stderr)
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' database: {args.db}', 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:
def __init__(self, bind_port=4729):
def __init__(self, bind_port=4729, bind_ip='127.0.0.1'):
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._dropped = 0
def start(self):
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._dropped = 0
+1 -1
View File
@@ -1,3 +1,3 @@
"""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
goto :parse_args
)
set "EXTRA_ARGS=!EXTRA_ARGS! %~1"
set "EXTRA_ARGS=!EXTRA_ARGS! %1"
shift
goto :parse_args
@@ -67,11 +67,11 @@ goto :parse_args
echo Starting simtrace2-pysniff...
set "ARGS="
if not "%GSMTAP%"=="" set "ARGS=!ARGS! --gsmtap %GSMTAP%"
if not "%PCAP%"=="" set "ARGS=!ARGS! --pcap %PCAP%"
if not "%OUTPUT%"=="" set "ARGS=!ARGS! --output %OUTPUT%"
if not "%FORMAT%"=="" set "ARGS=!ARGS! --format %FORMAT%"
if not "%INACTIVITY_TIMEOUT%"=="0" set "ARGS=!ARGS! --inactivity-timeout %INACTIVITY_TIMEOUT%"
if not "%GSMTAP%"=="" set "ARGS=!ARGS! --gsmtap "%GSMTAP%""
if not "%PCAP%"=="" set "ARGS=!ARGS! --pcap "%PCAP%""
if not "%OUTPUT%"=="" set "ARGS=!ARGS! --output "%OUTPUT%""
if not "%FORMAT%"=="" set "ARGS=!ARGS! --format "%FORMAT%""
if not "%INACTIVITY_TIMEOUT%"=="0" set "ARGS=!ARGS! --inactivity-timeout "%INACTIVITY_TIMEOUT%""
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
::
:: 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.
@@ -42,7 +42,7 @@ if "%~1"=="--gsmtap-port" (
shift
goto :parse_args
)
set "EXTRA_ARGS=!EXTRA_ARGS! %~1"
set "EXTRA_ARGS=!EXTRA_ARGS! %1"
shift
goto :parse_args
@@ -50,5 +50,10 @@ goto :parse_args
echo Starting simtrace-analyser-server...
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"
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
# 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.
+35
View File
@@ -206,6 +206,41 @@ class TestGsmtapListenerRobustness(unittest.TestCase):
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):
def test_capture_loop_survives_insert_error(self):
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.
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):
setup_py = self._read('setup.py')
self.assertIn('frontend', setup_py)
+1 -1
View File
@@ -224,7 +224,7 @@ class TestImportEndpoint(unittest.TestCase):
class TestVersion(unittest.TestCase):
def test_package_version(self):
from simtrace2_pysniff import __version__
self.assertEqual(__version__, '2.7.10')
self.assertEqual(__version__, '2.7.13')
def test_status_includes_version(self):
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()