diff --git a/README.md b/README.md
index 6220b85..d5b9282 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,29 @@
-# simtrace2-pysniff
+# SIMtrace Analyser
English | [Русский](README_RUS.md)
-Python-based replacement for `simtrace2-sniff` — SIM card communication sniffer
-for Osmocom SIMtrace2 hardware (firmware in **trace** mode). It bundles the
-**simtrace-analyser** PWA (in [`frontend/`](frontend/)) and an analysis server
-that both captures APDU traffic and serves that PWA.
+APDU trace analyser for the GSMTAP-SIM format — captures SIM/UICC
+communication from a UDP GSMTAP stream (or imports PCAP/PCAPNG files),
+decodes it, and serves an installable web app for browsing. The repository
+also bundles **simtrace2-pysniff**, a Python replacement for
+`simtrace2-sniff` that captures from Osmocom SIMtrace2 hardware and can feed
+GSMTAP into this analyser.
-**Single dependency**: PyUSB (libusb wrapper). No libosmocore, no libosmosim.
+
-
+## 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 import, EN/RU UI.
## Features
@@ -38,7 +52,7 @@ that both captures APDU traffic and serves that PWA.
decryption for ciphered packets
- ATR parse (clock-rate conversion, Fi/Di, T-bitmask) and FCP/FCI TLV
-**simtrace-analyser PWA** ([`frontend/`](frontend/)):
+**SIMtrace Analyser PWA** ([`frontend/`](frontend/)):
- Trace view with Map/List modes — proactive, TERMINAL RESPONSE, ENVELOPE
and AUTH commands get distinctive styling in map view
@@ -49,20 +63,22 @@ that both captures APDU traffic and serves that PWA.
## Prerequisites
- **Python 3.9+**
-- **PyUSB** — `pip install pyusb`
+- **PyUSB** — `pip install pyusb` (needed only for capture from SIMtrace2
+ hardware; GSMTAP/UDP capture and PCAP import work without it)
- **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
- (VID `1d50`, PID `60e3`, USB class `ff`, subclass `01`). For building and
- flashing trace firmware, see the [upstream SIMtrace2 project](https://gitea.osmocom.org/sim-card/simtrace2).
+- **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
+ [upstream SIMtrace2 project](https://gitea.osmocom.org/sim-card/simtrace2).
No other dependencies. PCAP, GSMTAP, and hex dump output use only Python stdlib.
## Install
```sh
-git clone https://github.com/anttro/simtrace2-pysniff.git
-cd simtrace2-pysniff
+git clone https://github.com/anttro/simtrace-analyser.git
+cd simtrace-analyser
pip install pyusb
```
@@ -70,52 +86,27 @@ Or install the package into your environment:
```sh
pip install -e .
-# then run: simtrace2-pysniff [opts]
+# then run: simtrace-analyser-server [opts]
+# or: simtrace2-pysniff [opts]
```
## Running
-Two tools are provided:
-
-### Sniffer CLI (`simtrace2-pysniff`)
-
-Captures SIMtrace2 traffic and writes it to stdout, GSMTAP (Wireshark), or PCAP files.
-
-### Startup script
-
-`./sniff-start.sh` performs a pre-flight USB permission check (prints udev
-install instructions if the SIMtrace2 device is not writable) and then
-launches the Python module. On Windows, use `sniff-start.bat` with the same
-options (no permission check needed).
-
-```sh
-./sniff-start.sh
-./sniff-start.sh --format timestamp
-./sniff-start.sh --gsmtap 127.0.0.1:4729
-./sniff-start.sh --pcap trace.pcap --format atr-time
-```
-
-OPTIONS can also be set via environment variables:
-
-```sh
-FORMAT=timestamp GSMTAP=127.0.0.1 ./sniff-start.sh
-```
-
-### Analysis server + PWA (`simtrace2-pysniff-server`)
+### Analysis server + PWA (`simtrace-analyser-server`)
Stores captured APDU traffic in SQLite and serves it over HTTP, together with
-the bundled **simtrace-analyser** PWA (`frontend/`) on the same origin. Three
+the bundled **SIMtrace Analyser** PWA (`frontend/`) on the same origin. Three
capture modes:
```sh
# Listen for GSMTAP from simtrace2-pysniff (or original simtrace2-sniff):
-simtrace2-pysniff-server --capture gsmtap
+simtrace-analyser-server --capture gsmtap
# Capture directly from SIMtrace2 hardware (no external tool needed):
-simtrace2-pysniff-server --capture direct
+simtrace-analyser-server --capture direct
# Browse/analyse existing captures without a capture backend:
-simtrace2-pysniff-server --capture disabled
+simtrace-analyser-server --capture disabled
```
The server logs its capture liveness to stderr (each line timestamped
@@ -166,7 +157,7 @@ setup is involved. Use `--web-dir PATH` to serve a different PWA directory
A standalone copy of the PWA is hosted at **https://simtrace.atroshin.ru**.
It is a pure frontend: point it (Settings → Server URL) at a locally running
-`simtrace2-pysniff-server`.
+`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
@@ -176,7 +167,31 @@ It is a pure frontend: point it (Settings → Server URL) at a locally running
> (or accept the permission prompt). Without the browser permission, the
> request to `127.0.0.1` is blocked before any preflight is sent.
-### Python module (cross-platform)
+### Capture utility (`simtrace2-pysniff`)
+
+Captures SIMtrace2 hardware traffic and writes it to stdout, GSMTAP
+(Wireshark), or PCAP files. It is the bundled replacement for the original
+Osmocom `simtrace2-sniff`.
+
+The `./sniff-start.sh` startup script performs a pre-flight USB permission
+check (prints udev install instructions if the SIMtrace2 device is not
+writable) and then launches the Python module. On Windows, use
+`sniff-start.bat` with the same options (no permission check needed).
+
+```sh
+./sniff-start.sh
+./sniff-start.sh --format timestamp
+./sniff-start.sh --gsmtap 127.0.0.1:4729
+./sniff-start.sh --pcap trace.pcap --format atr-time
+```
+
+OPTIONS can also be set via environment variables:
+
+```sh
+FORMAT=timestamp GSMTAP=127.0.0.1 ./sniff-start.sh
+```
+
+Cross-platform module invocation:
```sh
python -m simtrace2_pysniff
@@ -192,6 +207,9 @@ simtrace2-pysniff --format atr-time
## USB Device Access
+Required for capture from SIMtrace2 hardware (the utility and the server's
+`--capture direct` mode); not needed for GSMTAP or PCAP sources.
+
### Linux
A udev rule is required for non-root USB access to the SIMtrace2 device.
@@ -218,7 +236,7 @@ Windows does not ship a libusb-compatible driver for SIMtrace2. Use
After that, `python -m simtrace2_pysniff` works identically to Linux.
-## CLI Options
+## Utility CLI Options
```
python -m simtrace2_pysniff [OPTIONS]
@@ -279,14 +297,14 @@ for msg in session.iter_messages():
## Recovery
-The tool survives hardware resets, cable disconnects, and firmware hangs
+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
- **[simtrace2](https://gitea.osmocom.org/sim-card/simtrace2)** — the upstream
- SIMtrace2 hardware/firmware project that this tool sniffs from.
+ SIMtrace2 hardware/firmware project that the capture utility sniffs from.
- **[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).
diff --git a/README_RUS.md b/README_RUS.md
index 585b4b5..d1b33f5 100644
--- a/README_RUS.md
+++ b/README_RUS.md
@@ -1,16 +1,30 @@
-# simtrace2-pysniff
+# SIMtrace Analyser
[English](README.md) | Русский
-Замена `simtrace2-sniff` на Python — сниффер обмена с SIM-картой для
-оборудования Osmocom SIMtrace2 (прошивка в режиме **trace**). Включает PWA
-**simtrace-analyser** (в [`frontend/`](frontend/)) и анализирующий сервер,
-который и захватывает APDU-трафик, и отдаёт эту PWA.
+Анализатор APDU-трассировок в формате GSMTAP-SIM — захватывает обмен с
+SIM/UICC из UDP-потока GSMTAP (или импортирует файлы PCAP/PCAPNG),
+декодирует его и отдаёт устанавливаемое веб-приложение для просмотра.
+В репозиторий также включён **simtrace2-pysniff** — замена `simtrace2-sniff`
+на Python, которая захватывает трафик с оборудования Osmocom SIMtrace2
+и может передавать GSMTAP этому анализатору.
-**Единственная зависимость**: PyUSB (обёртка над libusb). Без libosmocore,
-без libosmosim.
+
-
+## Компоненты
+
+- **Анализирующий сервер + 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, интерфейс EN/RU.
## Возможности
@@ -41,7 +55,7 @@
- Разбор ATR (пересчёт тактовой частоты, Fi/Di, битовая маска T)
и FCP/FCI TLV
-**PWA simtrace-analyser** ([`frontend/`](frontend/)):
+**PWA SIMtrace Analyser** ([`frontend/`](frontend/)):
- Окно трассировки с режимами «Карта»/«Список» — proactive,
TERMINAL RESPONSE, ENVELOPE и AUTH-команды выделяются в режиме карты
@@ -52,13 +66,14 @@
## Требования
- **Python 3.9+**
-- **PyUSB** — `pip install pyusb`
+- **PyUSB** — `pip install pyusb` (нужен только для захвата с оборудования
+ SIMtrace2; захват GSMTAP/UDP и импорт PCAP работают без него)
- **libusb** — системная библиотека, обычно уже есть в Linux; в Windows
нужно заменить драйвер SIMtrace2
(см. [Доступ к USB-устройству](#доступ-к-usb-устройству))
-- **Оборудование SIMtrace2** с прошивкой в режиме *trace*
- (VID `1d50`, PID `60e3`, USB class `ff`, subclass `01`). О сборке
- и прошивке trace-прошивки см.
+- **Оборудование SIMtrace2** с прошивкой в режиме *trace* (только для
+ прямого захвата; VID `1d50`, PID `60e3`, USB class `ff`, subclass `01`).
+ О сборке и прошивке trace-прошивки см.
[проект SIMtrace2](https://gitea.osmocom.org/sim-card/simtrace2).
Других зависимостей нет. Вывод PCAP, GSMTAP и hex dump использует только
@@ -67,8 +82,8 @@
## Установка
```sh
-git clone https://github.com/anttro/simtrace2-pysniff.git
-cd simtrace2-pysniff
+git clone https://github.com/anttro/simtrace-analyser.git
+cd simtrace-analyser
pip install pyusb
```
@@ -76,53 +91,27 @@ pip install pyusb
```sh
pip install -e .
-# затем запуск: simtrace2-pysniff [opts]
+# затем запуск: simtrace-analyser-server [opts]
+# или: simtrace2-pysniff [opts]
```
## Запуск
-Предоставляются два инструмента:
-
-### CLI сниффера (`simtrace2-pysniff`)
-
-Захватывает трафик SIMtrace2 и выводит его в stdout, GSMTAP (Wireshark)
-или файлы PCAP.
-
-### Скрипт запуска
-
-`./sniff-start.sh` выполняет предварительную проверку прав доступа к USB
-(при неудаче печатает инструкцию по установке udev-правила) и затем
-запускает модуль Python. В Windows используйте `sniff-start.bat` с теми же
-параметрами (проверка прав не требуется).
-
-```sh
-./sniff-start.sh
-./sniff-start.sh --format timestamp
-./sniff-start.sh --gsmtap 127.0.0.1:4729
-./sniff-start.sh --pcap trace.pcap --format atr-time
-```
-
-Параметры также можно задавать через переменные окружения:
-
-```sh
-FORMAT=timestamp GSMTAP=127.0.0.1 ./sniff-start.sh
-```
-
-### Анализирующий сервер + PWA (`simtrace2-pysniff-server`)
+### Анализирующий сервер + PWA (`simtrace-analyser-server`)
Сохраняет захваченный APDU-трафик в SQLite и отдаёт его по HTTP вместе
-со встроенной PWA **simtrace-analyser** (`frontend/`) на том же origin.
+со встроенной PWA **SIMtrace Analyser** (`frontend/`) на том же origin.
Три режима захвата:
```sh
# Слушать GSMTAP от simtrace2-pysniff (или оригинального simtrace2-sniff):
-simtrace2-pysniff-server --capture gsmtap
+simtrace-analyser-server --capture gsmtap
# Захват напрямую с оборудования SIMtrace2 (внешний инструмент не нужен):
-simtrace2-pysniff-server --capture direct
+simtrace-analyser-server --capture direct
# Просмотр/анализ существующих записей без бэкенда захвата:
-simtrace2-pysniff-server --capture disabled
+simtrace-analyser-server --capture disabled
```
Сервер пишет в stderr информацию о жизненном цикле захвата (каждая строка
@@ -174,7 +163,7 @@ CORS/Private-Network-Access не требуется. Используйте `--w
Автономная копия PWA размещена на **https://simtrace.atroshin.ru**.
Это чистый фронтенд: укажите в нём (Settings → Server URL) адрес
-локально запущенного `simtrace2-pysniff-server`.
+локально запущенного `simtrace-analyser-server`.
> **Ограничение браузера:** когда PWA отдаётся с публичного HTTPS-хоста,
> доступ к локальному серверу (`http://127.0.0.1:8081`) требует двух
@@ -185,7 +174,31 @@ CORS/Private-Network-Access не требуется. Используйте `--w
> разрешить сайту (или принять запрос разрешения). Без разрешения
> браузера запрос к `127.0.0.1` блокируется ещё до отправки preflight.
-### Модуль Python (кроссплатформенный)
+### Утилита захвата (`simtrace2-pysniff`)
+
+Захватывает трафик оборудования SIMtrace2 и выводит его в stdout, GSMTAP
+(Wireshark) или PCAP-файлы. Это встроенная замена оригинального
+инструмента Osmocom `simtrace2-sniff`.
+
+Скрипт запуска `./sniff-start.sh` выполняет предварительную проверку прав
+доступа к USB (при неудаче печатает инструкцию по установке udev-правила)
+и затем запускает модуль Python. В Windows используйте `sniff-start.bat`
+с теми же параметрами (проверка прав не требуется).
+
+```sh
+./sniff-start.sh
+./sniff-start.sh --format timestamp
+./sniff-start.sh --gsmtap 127.0.0.1:4729
+./sniff-start.sh --pcap trace.pcap --format atr-time
+```
+
+Параметры также можно задавать через переменные окружения:
+
+```sh
+FORMAT=timestamp GSMTAP=127.0.0.1 ./sniff-start.sh
+```
+
+Кроссплатформенный запуск модуля:
```sh
python -m simtrace2_pysniff
@@ -202,6 +215,9 @@ simtrace2-pysniff --format atr-time
## Доступ к USB-устройству
+Нужен для захвата с оборудования SIMtrace2 (утилита и режим
+`--capture direct` сервера); для источников GSMTAP и PCAP не требуется.
+
### Linux
Для доступа к устройству SIMtrace2 без root нужно udev-правило.
@@ -229,7 +245,7 @@ sudo udevadm trigger
После этого `python -m simtrace2_pysniff` работает так же, как в Linux.
-## Параметры CLI
+## Параметры CLI утилиты
```
python -m simtrace2_pysniff [OPTIONS]
@@ -295,15 +311,15 @@ for msg in session.iter_messages():
## Восстановление
-Инструмент по умолчанию переживает сбросы оборудования, отключения кабеля
-и зависания прошивки — он переподключается с экспоненциальной задержкой
+Утилита по умолчанию переживает сбросы оборудования, отключения кабеля
+и зависания прошивки — она переподключается с экспоненциальной задержкой
(1с → 30с). Используйте `--inactivity-timeout`, чтобы также переподключаться
при «тихих» зависаниях прошивки.
## Связанные проекты
- **[simtrace2](https://gitea.osmocom.org/sim-card/simtrace2)** — вышестоящий
- проект оборудования/прошивки SIMtrace2, с которого этот инструмент
+ проект оборудования/прошивки SIMtrace2, с которого утилита захвата
снимает трафик.
- **[sigrok_iso7816_stream](https://github.com/anttro/sigrok_iso7816_stream)** —
пассивный ISO 7816 сниффер на базе FX2LP, выдающий такой же поток
diff --git a/frontend/index.html b/frontend/index.html
index 0cd46e9..cc93f8b 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -297,7 +297,7 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(-
@@ -676,7 +676,7 @@ function desCbcDecrypt(keyBytes, ivBytes, ctBytes) {
return out;
}
-// ──────────────────── C-APDU parsing (vendored from otaman) ────────────────────
+// ──────────────────── C-APDU parsing (vendored from SIMple) ────────────────────
// Parses C-APDU hex streams (e.g. SCP80 secured-packet application messages)
// into {label, hex, desc, children} trees.
const PARSE_INS = {
@@ -2504,16 +2504,16 @@ function updateVersionWarn(serverVersion) {
const elv = el('version-warn');
const sv = parseVersion(serverVersion);
if (sv === null) {
- elv.textContent = t('Server version unknown — update simtrace2-pysniff-server.');
+ elv.textContent = t('Server version unknown — update simtrace-analyser-server.');
return;
}
const cmpMin = compareVersions(serverVersion, MIN_SERVER_VERSION);
if (cmpMin !== null && cmpMin < 0) {
- elv.textContent = t('Server version {v} is outdated. Please update simtrace2-pysniff-server.').replace('{v}', serverVersion);
+ elv.textContent = t('Server version {v} is outdated. Please update simtrace-analyser-server.').replace('{v}', serverVersion);
return;
}
if (sv[0] > parseVersion(PWA_VERSION)[0]) {
- elv.textContent = t('Server version {v} is newer than expected. Update simtrace2-pysniff for full compatibility.').replace('{v}', serverVersion);
+ elv.textContent = t('Server version {v} is newer than expected. Update SIMtrace Analyser for full compatibility.').replace('{v}', serverVersion);
return;
}
elv.textContent = '';
@@ -2635,9 +2635,9 @@ async function checkConnection() {
el('conn-instructions').innerHTML = `
${t('Setup')}
- - ${t('Clone and install simtrace2-pysniff:')}
- git clone https://github.com/anttro/simtrace2-pysniff.git
- cd simtrace2-pysniff && pip install -e .
+ - ${t('Clone and install SIMtrace Analyser:')}
+ git clone https://github.com/anttro/simtrace-analyser.git
+ cd simtrace-analyser && pip install -e .
- ${t('Start the server in direct mode (recommended):')}
./start.sh --capture direct
${t('Requires SIMtrace2 hardware connected via USB; captures directly from the device.')}
@@ -2681,7 +2681,7 @@ function onFilterChange() {
}
// ──────────────────── Versioning ────────────────────
-const PWA_VERSION = '1.22.21';
+const PWA_VERSION = '2.0.0';
const MIN_SERVER_VERSION = '1.0.0';
function parseVersion(v) {
@@ -2705,8 +2705,8 @@ function compareVersions(a, b) {
// ──────────────────── i18n ────────────────────
let currentLang = 'en';
-const HINT_BLOCKED = 'The browser likely blocked access to the local server. Allow local network access for this site (Chrome/Edge/Vivaldi: Site settings → Local network access → Allow) and confirm simtrace2-pysniff-server is running at {url}.';
-const HINT_UNREACHABLE = 'Couldn\'t reach the server. Check that simtrace2-pysniff-server is running and the URL is correct ({url}). If this page is served over HTTPS, also allow local network access in the browser (Site settings → Local network access → Allow).';
+const HINT_BLOCKED = 'The browser likely blocked access to the local server. Allow local network access for this site (Chrome/Edge/Vivaldi: Site settings → Local network access → Allow) and confirm simtrace-analyser-server is running at {url}.';
+const HINT_UNREACHABLE = 'Couldn\'t reach the server. Check that simtrace-analyser-server is running and the URL is correct ({url}). If this page is served over HTTPS, also allow local network access in the browser (Site settings → Local network access → Allow).';
const LANG_RU = {
'Trace': 'Трассировка',
@@ -2785,10 +2785,10 @@ const LANG_RU = {
'Empty file': 'Пустой файл',
'Install from the address bar:\nclick the install icon (⊕ / monitor icon) at the right\nend of Chrome\'s address bar, then "Install SIMtrace Analyser".':
'Установка из адресной строки:\nнажмите значок установки (⊕ / значок монитора) в правом\nконце адресной строки Chrome, затем «Установить SIMtrace Analyser».',
- [HINT_BLOCKED]: 'Браузер, вероятно, заблокировал доступ к локальному серверу. Разрешите доступ к локальной сети для этого сайта (Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети → Разрешить) и убедитесь, что simtrace2-pysniff-server запущен по адресу {url}.',
- [HINT_UNREACHABLE]: 'Не удалось связаться с сервером. Проверьте, что simtrace2-pysniff-server запущен и URL указан верно ({url}). Если эта страница открыта по HTTPS, также разрешите доступ к локальной сети в браузере (Настройки сайта → Доступ к локальной сети → Разрешить).',
+ [HINT_BLOCKED]: 'Браузер, вероятно, заблокировал доступ к локальному серверу. Разрешите доступ к локальной сети для этого сайта (Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети → Разрешить) и убедитесь, что simtrace-analyser-server запущен по адресу {url}.',
+ [HINT_UNREACHABLE]: 'Не удалось связаться с сервером. Проверьте, что simtrace-analyser-server запущен и URL указан верно ({url}). Если эта страница открыта по HTTPS, также разрешите доступ к локальной сети в браузере (Настройки сайта → Доступ к локальной сети → Разрешить).',
'Setup': 'Настройка',
- 'Clone and install simtrace2-pysniff:': 'Клонируйте и установите simtrace2-pysniff:',
+ 'Clone and install SIMtrace Analyser:': 'Клонируйте и установите SIMtrace Analyser:',
'Start the server in direct mode (recommended):': 'Запустите сервер в режиме direct (рекомендуется):',
'Requires SIMtrace2 hardware connected via USB; captures directly from the device.': 'Требуется оборудование SIMtrace2, подключённое по USB; захват выполняется напрямую с устройства.',
'Then set the URL above to http://127.0.0.1:8081 and return to the Trace tab.': 'Затем укажите URL выше (http://127.0.0.1:8081) и вернитесь на вкладку «Трассировка».',
@@ -2797,9 +2797,9 @@ const LANG_RU = {
'Start the server:': 'Запустите сервер:',
'Start a GSMTAP source in another terminal — either:': 'Запустите источник GSMTAP в другом терминале — либо:',
'(original Osmocom tool)': '(оригинальный инструмент Osmocom)',
- 'Server version unknown — update simtrace2-pysniff-server.': 'Версия сервера неизвестна — обновите simtrace2-pysniff-server.',
- 'Server version {v} is outdated. Please update simtrace2-pysniff-server.': 'Версия сервера {v} устарела. Обновите simtrace2-pysniff-server.',
- 'Server version {v} is newer than expected. Update simtrace2-pysniff for full compatibility.': 'Версия сервера {v} новее ожидаемой. Обновите simtrace2-pysniff для полной совместимости.',
+ 'Server version unknown — update simtrace-analyser-server.': 'Версия сервера неизвестна — обновите simtrace-analyser-server.',
+ 'Server version {v} is outdated. Please update simtrace-analyser-server.': 'Версия сервера {v} устарела. Обновите simtrace-analyser-server.',
+ 'Server version {v} is newer than expected. Update SIMtrace Analyser for full compatibility.': 'Версия сервера {v} новее ожидаемой. Обновите SIMtrace Analyser для полной совместимости.',
'Capture mode: GSMTAP — the server is listening for incoming UDP/GSMTAP traffic. Ensure a GSMTAP source (e.g. simtrace2-pysniff or simtrace2-sniff) is feeding the server.': 'Режим захвата: GSMTAP — сервер ожидает входящий UDP/GSMTAP-трафик. Убедитесь, что источник GSMTAP (например, simtrace2-pysniff или simtrace2-sniff) передаёт данные серверу.',
'Capture mode: direct — the server captures from SIMtrace2 hardware. Keep the device connected.': 'Режим захвата: direct — сервер захватывает данные с устройства SIMtrace2. Не отключайте устройство.',
'Capture is disabled — only pre-captured traces (saved sessions / PCAP import) are available.': 'Захват отключён — доступны только ранее записанные трассировки (сохранённые сессии / импорт PCAP).',
diff --git a/frontend/sw.js b/frontend/sw.js
index 7be20b2..74ec633 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'simtrace-analyser-v53';
+const CACHE = 'simtrace-analyser-v54';
const PRECACHE = [
'index.html',
diff --git a/pyproject.toml b/pyproject.toml
index b7b4be4..34bfb9b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,9 +3,9 @@ requires = ["setuptools>=64"]
build-backend = "setuptools.backends._legacy:_Backend"
[project]
-name = "simtrace2-pysniff"
-version = "1.22.21"
-description = "Python-based sniffing tool for SIMtrace2 hardware (SIM card communication sniffer)"
+name = "simtrace-analyser"
+version = "2.0.0"
+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"}
requires-python = ">=3.9"
@@ -14,8 +14,8 @@ dependencies = [
]
[project.scripts]
+simtrace-analyser-server = "simtrace_analyser.__main__:main"
simtrace2-pysniff = "simtrace2_pysniff.__main__:main"
-simtrace2-pysniff-server = "simtrace2_pysniff.server.__main__:main"
[tool.setuptools.packages.find]
-include = ["simtrace2_pysniff*"]
+include = ["simtrace_analyser*", "simtrace2_pysniff*"]
diff --git a/simtrace2_pysniff/__init__.py b/simtrace2_pysniff/__init__.py
index 317d5e8..40e0857 100644
--- a/simtrace2_pysniff/__init__.py
+++ b/simtrace2_pysniff/__init__.py
@@ -1,12 +1,11 @@
-"""simtrace2-pysniff — Python replacement for simtrace2-sniff."""
+"""simtrace2-pysniff — Python replacement for simtrace2-sniff.
-from .device import SniffSession, DeviceDisconnected, find_sniffer_device
-from .protocol import parse_message, SniffMessage
-from .gsmtap import GsmtapSender, GsmtapReceiver
-from .pcap import PcapWriter
-from .dump import FileDumper, format_message
+Capture utility bundled with SIMtrace Analyser. Hardware access (PyUSB) is
+imported lazily, so the GSMTAP/PCAP codec modules can be used without libusb.
+"""
+
+from simtrace_analyser.version import __version__
-__version__ = '1.22.21'
__all__ = [
'SniffSession',
'DeviceDisconnected',
@@ -20,3 +19,28 @@ __all__ = [
'format_message',
'__version__',
]
+
+_LAZY_IMPORTS = {
+ 'SniffSession': ('.device', 'SniffSession'),
+ 'DeviceDisconnected': ('.device', 'DeviceDisconnected'),
+ 'find_sniffer_device': ('.device', 'find_sniffer_device'),
+ 'parse_message': ('.protocol', 'parse_message'),
+ 'SniffMessage': ('.protocol', 'SniffMessage'),
+ 'GsmtapSender': ('.gsmtap', 'GsmtapSender'),
+ 'GsmtapReceiver': ('.gsmtap', 'GsmtapReceiver'),
+ 'PcapWriter': ('.pcap', 'PcapWriter'),
+ 'FileDumper': ('.dump', 'FileDumper'),
+ 'format_message': ('.dump', 'format_message'),
+}
+
+
+def __getattr__(name):
+ """Import utility symbols on first use (PEP 562)."""
+ try:
+ module_name, attr = _LAZY_IMPORTS[name]
+ except KeyError:
+ raise AttributeError(f'module {__name__!r} has no attribute {name!r}') from None
+ from importlib import import_module
+ value = getattr(import_module(module_name, __name__), attr)
+ globals()[name] = value
+ return value
diff --git a/simtrace2_pysniff/server/__init__.py b/simtrace2_pysniff/server/__init__.py
deleted file mode 100644
index 4bdc37d..0000000
--- a/simtrace2_pysniff/server/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""simtrace2-pysniff server sub-package."""
diff --git a/simtrace_analyser/__init__.py b/simtrace_analyser/__init__.py
new file mode 100644
index 0000000..d671dd0
--- /dev/null
+++ b/simtrace_analyser/__init__.py
@@ -0,0 +1,9 @@
+"""simtrace_analyser — server side of SIMtrace Analyser.
+
+Stores captured GSMTAP/PCAP APDU traffic in SQLite, decodes it, and serves
+the PWA (``frontend/``) plus the HTTP API on a single origin.
+"""
+
+from .version import __version__
+
+__all__ = ['__version__']
diff --git a/simtrace2_pysniff/server/__main__.py b/simtrace_analyser/__main__.py
similarity index 84%
rename from simtrace2_pysniff/server/__main__.py
rename to simtrace_analyser/__main__.py
index b99d51c..506940e 100644
--- a/simtrace2_pysniff/server/__main__.py
+++ b/simtrace_analyser/__main__.py
@@ -1,8 +1,8 @@
-"""simtrace2-pysniff-server — CLI entry point.
+"""simtrace-analyser-server — CLI entry point.
Usage:
- simtrace2-pysniff-server --capture gsmtap [--gsmtap-port 4729] [--port 8081]
- simtrace2-pysniff-server --capture direct [--port 8081]
+ simtrace-analyser-server --capture gsmtap [--gsmtap-port 4729] [--port 8081]
+ simtrace-analyser-server --capture direct [--port 8081]
"""
import argparse
@@ -13,19 +13,19 @@ from http.server import HTTPServer
from .server import RequestHandler
from .database import Database, DEFAULT_DB_PATH
from .capture import CaptureManager, GsmtapListener, DirectSniffer
-from .. import __version__
+from .version import __version__
def _default_web_dir():
# /frontend, whether run from source or an editable install.
- # __file__ is /simtrace2_pysniff/server/__main__.py → up three levels.
- return os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'frontend')
+ # __file__ is /simtrace_analyser/__main__.py → up two levels.
+ return os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'frontend')
def main():
p = argparse.ArgumentParser(
- prog='simtrace2-pysniff-server',
- description='HTTP API + PWA server for SIMtrace2 APDU capture and analysis')
+ prog='simtrace-analyser-server',
+ description='HTTP API + PWA server for APDU trace capture and analysis')
p.add_argument('--host', default='127.0.0.1',
help='HTTP bind address (default: 127.0.0.1)')
p.add_argument('--port', type=int, default=8081,
@@ -63,7 +63,7 @@ def main():
server = HTTPServer((args.host, args.port), RequestHandler)
server.web_dir = args.web_dir
- print(f'simtrace2-pysniff-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)
if args.capture == 'gsmtap':
print(f' GSMTAP port: {args.gsmtap_port}', file=sys.stderr)
diff --git a/simtrace2_pysniff/server/capture.py b/simtrace_analyser/capture.py
similarity index 95%
rename from simtrace2_pysniff/server/capture.py
rename to simtrace_analyser/capture.py
index 6c38a5f..ee217e6 100644
--- a/simtrace2_pysniff/server/capture.py
+++ b/simtrace_analyser/capture.py
@@ -6,9 +6,9 @@ import threading
import time
import traceback
-from ..gsmtap import (GsmtapReceiver, GSMTAP_SIM_ATR,
- GSMTAP_SIM_PPS_REQ, GSMTAP_SIM_PPS_RSP,
- GSMTAP_SIM_RST_EVENT, GSMTAP_SIM_VCC_EVENT)
+from simtrace2_pysniff.gsmtap import (GsmtapReceiver, GSMTAP_SIM_ATR,
+ GSMTAP_SIM_PPS_REQ, GSMTAP_SIM_PPS_RSP,
+ GSMTAP_SIM_RST_EVENT, GSMTAP_SIM_VCC_EVENT)
def _ts():
@@ -76,7 +76,7 @@ class GsmtapListener:
class DirectSniffer:
def __init__(self):
- from ..device import SniffSession
+ from simtrace2_pysniff.device import SniffSession
self._session = SniffSession(
reconnect=True,
reconnect_delay_min=1.0,
@@ -98,7 +98,7 @@ class DirectSniffer:
self._session.close()
def iter_messages(self):
- from ..device import DeviceDisconnected
+ from simtrace2_pysniff.device import DeviceDisconnected
try:
for msg in self._session.iter_messages():
if not self._running:
diff --git a/simtrace2_pysniff/server/database.py b/simtrace_analyser/database.py
similarity index 99%
rename from simtrace2_pysniff/server/database.py
rename to simtrace_analyser/database.py
index df271f6..7c2be8d 100644
--- a/simtrace2_pysniff/server/database.py
+++ b/simtrace_analyser/database.py
@@ -398,7 +398,7 @@ class Database:
# Only card-level changes invalidate the selection. A
# waiting-time timeout interrupts a single TPDU but leaves the
# card selected, so the phone usually just retries.
- from ..protocol import (
+ from simtrace2_pysniff.protocol import (
CHANGE_FLAG_CARD_INSERT, CHANGE_FLAG_CARD_EJECT,
CHANGE_FLAG_RESET_ASSERT, CHANGE_FLAG_RESET_DEASSERT,
)
diff --git a/simtrace2_pysniff/server/decode.py b/simtrace_analyser/decode.py
similarity index 99%
rename from simtrace2_pysniff/server/decode.py
rename to simtrace_analyser/decode.py
index 19ddb41..8fcdd84 100644
--- a/simtrace2_pysniff/server/decode.py
+++ b/simtrace_analyser/decode.py
@@ -4,7 +4,7 @@ Pure Python, no dependencies. Decodes raw TPDU bytes captured by
the SIMtrace2 sniffer into structured dicts for the PWA to display.
"""
-from ..gsmtap import GSMTAP_FLAG_BAD_FCS
+from simtrace2_pysniff.gsmtap import GSMTAP_FLAG_BAD_FCS
# ──────────────────── Status Word names ────────────────────
diff --git a/simtrace2_pysniff/server/server.py b/simtrace_analyser/server.py
similarity index 98%
rename from simtrace2_pysniff/server/server.py
rename to simtrace_analyser/server.py
index 22d85ec..7b45dea 100644
--- a/simtrace2_pysniff/server/server.py
+++ b/simtrace_analyser/server.py
@@ -1,4 +1,4 @@
-"""HTTP API + static PWA server for simtrace2-pysniff."""
+"""HTTP API + static PWA server for SIMtrace Analyser."""
import json
import os
@@ -11,9 +11,9 @@ from urllib.parse import urlparse, parse_qs, quote
from .database import Database
from .capture import CaptureManager, GsmtapListener, DirectSniffer, _log
-from ..gsmtap import build_gsmtap_packet, GSMTAP_SIM_ATR, GSMTAP_SIM_APDU
-from ..pcap import build_pcap, parse_pcap, parse_pcapng
-from .. import __version__
+from simtrace2_pysniff.gsmtap import build_gsmtap_packet, GSMTAP_SIM_ATR, GSMTAP_SIM_APDU
+from simtrace2_pysniff.pcap import build_pcap, parse_pcap, parse_pcapng
+from .version import __version__
# Static file serving (the PWA lives in /frontend, served by this server
diff --git a/simtrace_analyser/version.py b/simtrace_analyser/version.py
new file mode 100644
index 0000000..c35e0e1
--- /dev/null
+++ b/simtrace_analyser/version.py
@@ -0,0 +1,3 @@
+"""Single source of truth for the SIMtrace Analyser version."""
+
+__version__ = '2.0.0'
diff --git a/start.bat b/start.bat
index ab0ab30..78704fb 100644
--- a/start.bat
+++ b/start.bat
@@ -1,7 +1,7 @@
@echo off
setlocal enabledelayedexpansion
-:: simtrace2-pysniff-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]
::
@@ -47,8 +47,8 @@ shift
goto :parse_args
:run
-echo Starting simtrace2-pysniff-server...
+echo Starting simtrace-analyser-server...
echo PWA (simtrace-analyser) will be served at http://127.0.0.1:%PORT%/
set "PYTHONPATH=%~dp0"
-python -m simtrace2_pysniff.server --capture %CAPTURE% --port %PORT% --gsmtap-port %GSMTAP_PORT% %EXTRA_ARGS%
+python -m simtrace_analyser --capture %CAPTURE% --port %PORT% --gsmtap-port %GSMTAP_PORT% %EXTRA_ARGS%
diff --git a/start.sh b/start.sh
index 2160fdf..f4671aa 100755
--- a/start.sh
+++ b/start.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-# simtrace2-pysniff-server startup script
+# simtrace-analyser-server startup script
#
# Usage: ./start.sh [--capture gsmtap|direct] [--port PORT] [--db PATH] [--gsmtap-port PORT]
#
@@ -71,6 +71,6 @@ ARGS+=(--port "$PORT")
[[ "$CAPTURE" == "gsmtap" ]] && ARGS+=(--gsmtap-port "$GSMTAP_PORT")
ARGS+=("${EXTRA_ARGS[@]}")
-echo "Starting simtrace2-pysniff-server..." >&2
+echo "Starting simtrace-analyser-server..." >&2
echo "PWA (simtrace-analyser) will be served at http://127.0.0.1:$PORT/" >&2
-exec env PYTHONPATH="$PROJECT_DIR" python3 -m simtrace2_pysniff.server "${ARGS[@]}"
+exec env PYTHONPATH="$PROJECT_DIR" python3 -m simtrace_analyser "${ARGS[@]}"
diff --git a/tests/test_capture.py b/tests/test_capture.py
index 74868eb..ce8529c 100644
--- a/tests/test_capture.py
+++ b/tests/test_capture.py
@@ -7,8 +7,8 @@ import tempfile
import time
import unittest
-from simtrace2_pysniff.server.capture import CaptureManager, gsmtap_msg_type
-from simtrace2_pysniff.server.database import Database
+from simtrace_analyser.capture import CaptureManager, gsmtap_msg_type
+from simtrace_analyser.database import Database
class TestGsmtapSubtypeMapping(unittest.TestCase):
@@ -67,7 +67,7 @@ class TestCaptureManager(unittest.TestCase):
self.assertEqual(db.count_messages(sid), 1)
def test_direct_sniffer_device_disconnect(self):
- from simtrace2_pysniff.server.capture import DirectSniffer
+ from simtrace_analyser.capture import DirectSniffer
sn = DirectSniffer()
sn._session = _DisconnectingSession()
self.assertEqual(list(sn.iter_messages()), [])
@@ -84,7 +84,7 @@ class TestCaptureManager(unittest.TestCase):
self.assertEqual([m['type'] for m in msgs], ['gap', 'tpdu'])
def test_direct_sniffer_connected(self):
- from simtrace2_pysniff.server.capture import DirectSniffer
+ from simtrace_analyser.capture import DirectSniffer
class _FakeSession:
connected = True
@@ -96,7 +96,7 @@ class TestCaptureManager(unittest.TestCase):
self.assertFalse(sn.connected)
def test_device_connected_gsmtap_none(self):
- from simtrace2_pysniff.server.capture import GsmtapListener
+ from simtrace_analyser.capture import GsmtapListener
listener = GsmtapListener(bind_port=0)
self.addCleanup(listener.stop)
mgr, _ = self._manager(listener)
@@ -170,7 +170,7 @@ class _RaisingBackend:
class TestGsmtapListenerRobustness(unittest.TestCase):
def test_listener_survives_bad_packet(self):
- from simtrace2_pysniff.server.capture import GsmtapListener
+ from simtrace_analyser.capture import GsmtapListener
listener = GsmtapListener(bind_port=0)
listener._receiver.close() # drop the real socket before swapping
listener._receiver = _FlakyReceiver(listener, ['raise_non_sim', 'good'])
@@ -181,7 +181,7 @@ class TestGsmtapListenerRobustness(unittest.TestCase):
self.assertEqual(listener.dropped, 1)
def test_listener_survives_short_packet(self):
- from simtrace2_pysniff.server.capture import GsmtapListener
+ from simtrace_analyser.capture import GsmtapListener
listener = GsmtapListener(bind_port=0)
listener._receiver.close() # drop the real socket before swapping
listener._receiver = _FlakyReceiver(listener, ['raise_short', 'good'])
diff --git a/tests/test_decode.py b/tests/test_decode.py
index 252931e..ddffee6 100644
--- a/tests/test_decode.py
+++ b/tests/test_decode.py
@@ -2,7 +2,7 @@
import unittest
-from simtrace2_pysniff.server.decode import (
+from simtrace_analyser.decode import (
decode_message,
decode_sniff_msg,
CAT_COMMAND_TYPES,
@@ -276,19 +276,19 @@ class TestFcpResponse(unittest.TestCase):
self.assertEqual(resp['life_cycle'], 'operational state (activated)')
def test_ef_descriptor_transparent(self):
- from simtrace2_pysniff.server.decode import _decode_file_descriptor
+ from simtrace_analyser.decode import _decode_file_descriptor
fd = _decode_file_descriptor(bytes.fromhex('0121'))
self.assertEqual(fd['file_type'], 'Working EF')
self.assertEqual(fd['structure'], 'transparent')
def test_ef_descriptor_linear_fixed(self):
- from simtrace2_pysniff.server.decode import _decode_file_descriptor
+ from simtrace_analyser.decode import _decode_file_descriptor
fd = _decode_file_descriptor(bytes.fromhex('0221'))
self.assertEqual(fd['file_type'], 'Working EF')
self.assertEqual(fd['structure'], 'linear fixed')
def test_fcp_file_size(self):
- from simtrace2_pysniff.server.decode import _decode_fcp
+ from simtrace_analyser.decode import _decode_fcp
# FCP: file descriptor (transparent EF) + FID 6F07 + file size 9 + total 9
fcp = _decode_fcp(bytes.fromhex('8202012183026f07800109810109'))
self.assertEqual(fcp['file_descriptor']['file_type'], 'Working EF')
@@ -299,19 +299,19 @@ class TestFcpResponse(unittest.TestCase):
def test_fcp_sfi(self):
# FCP SFI tag 0x88 carries the SFI in bits b8..b4 (value >> 3).
- from simtrace2_pysniff.server.decode import _decode_fcp
+ from simtrace_analyser.decode import _decode_fcp
fcp = _decode_fcp(bytes.fromhex('8202412183026f7e880158'))
self.assertEqual(fcp['sfi'], 11)
def test_fcp_sfi_empty(self):
# FCP SFI tag 0x88 with length 0 → file does not support SFI.
- from simtrace2_pysniff.server.decode import _decode_fcp
+ from simtrace_analyser.decode import _decode_fcp
fcp = _decode_fcp(bytes.fromhex('82054221005a0d83026f068800'))
self.assertIsNone(fcp['sfi'])
def test_fcp_sfi_absent_uses_fid_lsbs(self):
# No SFI tag → SFI = 5 LSBs of the FID (TS 102 221 §11.1.2).
- from simtrace2_pysniff.server.decode import _decode_fcp
+ from simtrace_analyser.decode import _decode_fcp
fcp = _decode_fcp(bytes.fromhex('8202412183022f06'))
self.assertEqual(fcp['sfi'], 0x06)
@@ -334,7 +334,7 @@ class TestFcpResponse(unittest.TestCase):
class TestAuthResponse(unittest.TestCase):
def test_3g_success(self):
- from simtrace2_pysniff.server.decode import _decode_auth
+ from simtrace_analyser.decode import _decode_auth
data = bytes.fromhex('DB084CFA9017FD0DD85A101A2622F60E8ABD2C2497B9A8EFAF55E510CAA393329FF97868B9537369D5266A4F084D417B05ABFAFAEE')
r = _decode_auth(data)
self.assertEqual(r['type'], '3G/EPS/5G')
@@ -344,7 +344,7 @@ class TestAuthResponse(unittest.TestCase):
self.assertEqual(r['ik'], 'CAA393329FF97868B9537369D5266A4F')
def test_3g_sync_fail(self):
- from simtrace2_pysniff.server.decode import _decode_auth
+ from simtrace_analyser.decode import _decode_auth
data = bytes.fromhex('DC0E' + '00' * 14)
r = _decode_auth(data)
self.assertEqual(r['type'], '3G/EPS/5G')
@@ -352,7 +352,7 @@ class TestAuthResponse(unittest.TestCase):
self.assertEqual(r['auts'], '00' * 14)
def test_gsm(self):
- from simtrace2_pysniff.server.decode import _decode_auth
+ from simtrace_analyser.decode import _decode_auth
sres = bytes.fromhex('AABBCCDD')
kc = bytes.fromhex('0011223344556677')
r = _decode_auth(sres + kc)
@@ -368,7 +368,7 @@ class TestTrResult(unittest.TestCase):
self.assertEqual(r['response']['name'], 'Command performed successfully')
def test_session_terminated(self):
- from simtrace2_pysniff.server.decode import _decode_tr_result
+ from simtrace_analyser.decode import _decode_tr_result
r = _decode_tr_result(bytes.fromhex('810301050002028281030110'))
self.assertEqual(r['code'], '0x10')
self.assertEqual(r['name'], 'Proactive UICC session terminated by the user')
@@ -395,7 +395,7 @@ class TestTrAdditionalInfo(unittest.TestCase):
'867231056428280')
def test_pli_datetime(self):
- from simtrace2_pysniff.server.decode import _decode_datetime
+ from simtrace_analyser.decode import _decode_datetime
self.assertEqual(
_decode_datetime(bytes.fromhex('62802141030021')),
'2026-08-12 14:30:00 (UTC+03:00)')
@@ -404,25 +404,25 @@ class TestTrAdditionalInfo(unittest.TestCase):
'2026-08-12 14:30:00 (UTCunknown)')
def test_datetime_invalid_bcd(self):
- from simtrace2_pysniff.server.decode import _decode_datetime
+ from simtrace_analyser.decode import _decode_datetime
self.assertIsNone(_decode_datetime(bytes.fromhex('A505326C333939')))
def test_datetime_invalid_semantics(self):
- from simtrace2_pysniff.server.decode import _decode_datetime
+ from simtrace_analyser.decode import _decode_datetime
self.assertIsNone(_decode_datetime(bytes.fromhex('62310100000000')))
def test_pli_language(self):
- from simtrace2_pysniff.server.decode import _decode_local_info, PLI_LANGUAGE
+ from simtrace_analyser.decode import _decode_local_info, PLI_LANGUAGE
self.assertEqual(_decode_local_info(PLI_LANGUAGE, b'en'),
{'label': 'Language', 'value': 'en'})
def test_pli_battery(self):
- from simtrace2_pysniff.server.decode import _decode_local_info, PLI_BATTERY
+ from simtrace_analyser.decode import _decode_local_info, PLI_BATTERY
self.assertEqual(_decode_local_info(PLI_BATTERY, b'\x05'),
{'label': 'Battery', 'value': 'full'})
def test_pli_access_tech(self):
- from simtrace2_pysniff.server.decode import _decode_local_info, PLI_ACCESS_TECH
+ from simtrace_analyser.decode import _decode_local_info, PLI_ACCESS_TECH
self.assertEqual(_decode_local_info(PLI_ACCESS_TECH, bytes([0x03, 0x07])),
{'label': 'Access technology', 'value': 'UTRAN, E-UTRAN'})
@@ -449,7 +449,7 @@ class TestAuthCommand(unittest.TestCase):
self.assertIn('Specific reference data', p2['bits'])
def test_gsm_rand_only(self):
- from simtrace2_pysniff.server.decode import _decode_auth_cmd
+ from simtrace_analyser.decode import _decode_auth_cmd
r = _decode_auth_cmd(bytes.fromhex('10' + '00' * 16), 0x00)
self.assertNotIn('context', r)
self.assertNotIn('specific_key', r)
@@ -457,7 +457,7 @@ class TestAuthCommand(unittest.TestCase):
self.assertNotIn('autn', r)
def test_sync_fail_auts(self):
- from simtrace2_pysniff.server.decode import _decode_auth
+ from simtrace_analyser.decode import _decode_auth
auts = bytes.fromhex('010203040506' + '0a0b0c0d0e0f1011')
r = _decode_auth(b'\xdc' + bytes([len(auts)]) + auts)
self.assertEqual(r['status'], 'sync fail')
@@ -525,7 +525,7 @@ class TestSmTpdu(unittest.TestCase):
return bytes(out)
def test_deliver_gsm7(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
oa = b'\x91' + self._bcd('79031234567')
tpdu = (b'\x00' + bytes([11]) + oa + b'\x00\x00' + b'\x00' * 7 +
b'\x05' + bytes.fromhex('E8329BFD06'))
@@ -536,7 +536,7 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['text'], 'hello')
def test_deliver_ucs2(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
oa = b'\x91' + self._bcd('79031234567')
ucs2 = '\u041f\u0440\u0438\u0432\u0435\u0442'.encode('utf-16-be')
tpdu = (b'\x00' + bytes([11]) + oa + b'\x00\x08' + b'\x00' * 7 +
@@ -546,7 +546,7 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['text'], '\u041f\u0440\u0438\u0432\u0435\u0442')
def test_8bit_no_text(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
oa = b'\x91' + self._bcd('79031234567')
# Mixed low/high bytes are treated as binary → no text decode.
payload = bytes.fromhex('00018081A0FF07')
@@ -558,7 +558,7 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['payload'], '00018081A0FF07')
def test_sim_data_download(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
oa = b'\x91' + self._bcd('79031234567')
ud = bytes.fromhex('02700000200d000000000000020000000000000010100102a2090804420435044104420600')
tpdu = (b'\x44' + bytes([11]) + oa + b'\x7f\x04' + b'\x00' * 7 +
@@ -575,7 +575,7 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['secured']['data'], '0010100102A2090804420435044104420600')
def test_response_packet(self):
- from simtrace2_pysniff.server.decode import _decode_response_packet
+ from simtrace_analyser.decode import _decode_response_packet
body = bytes.fromhex('001f0a00000000000000500000ab12800101230d08a0000001510000000f9a9000')
r = _decode_response_packet(body)
self.assertEqual(r['rpl'], 31)
@@ -587,11 +587,11 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['data'], 'AB12800101230D08A0000001510000000F9A9000')
def test_secured_packet_fallback(self):
- from simtrace2_pysniff.server.decode import _decode_secured_packet
+ from simtrace_analyser.decode import _decode_secured_packet
self.assertEqual(_decode_secured_packet(bytes.fromhex('0001')), {'raw': '0001'})
def test_send_sm_response_packet(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
tpdu = bytes.fromhex('41000481112200f624027100001f0a00000000000000500000ab12800101230d08a0000001510000000f9a9000')
r = _decode_sm_tpdu(tpdu)
self.assertEqual(r['mti'], 'SMS-SUBMIT')
@@ -603,7 +603,7 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['response_packet']['data'], 'AB12800101230D08A0000001510000000F9A9000')
def test_deliver_scts(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
oa = b'\x91' + self._bcd('79031234567')
scts = bytes.fromhex('62802141030021')
tpdu = (b'\x00' + bytes([11]) + oa + b'\x00\x00' + scts + b'\x00')
@@ -611,14 +611,14 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['scts'], '2026-08-12 14:30:00 (UTC+03:00)')
def test_deliver_pid_name(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
oa = b'\x91' + self._bcd('79031234567')
tpdu = (b'\x00' + bytes([11]) + oa + b'\x48\x00' + b'\x00' * 7 + b'\x00')
r = _decode_sm_tpdu(tpdu)
self.assertEqual(r['pid_name'], 'Device Triggering Short Message')
def test_deliver_dcs_info(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
oa = b'\x91' + self._bcd('79031234567')
tpdu = (b'\x00' + bytes([11]) + oa + b'\x00\x11' + b'\x00' * 7 + b'\x00')
r = _decode_sm_tpdu(tpdu)
@@ -627,7 +627,7 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['dcs_info']['msg_class'], 1)
def test_dcs_mwi(self):
- from simtrace2_pysniff.server.decode import _decode_dcs_full
+ from simtrace_analyser.decode import _decode_dcs_full
d = _decode_dcs_full(0xD0)
self.assertEqual(d['group'], 'Message Waiting Indication')
self.assertEqual(d['action'], 'store')
@@ -635,34 +635,34 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(d['indication'], 'voicemail')
def test_udh_concat_8bit(self):
- from simtrace2_pysniff.server.decode import _decode_udh
+ from simtrace_analyser.decode import _decode_udh
el = _decode_udh(bytes.fromhex('0003A10201'))
self.assertEqual(el[0]['name'], 'Concatenated short messages, 8-bit reference number')
self.assertEqual(el[0]['data'], {'reference': 0xA1, 'max': 2, 'seq': 1})
def test_udh_concat_16bit(self):
- from simtrace2_pysniff.server.decode import _decode_udh
+ from simtrace_analyser.decode import _decode_udh
el = _decode_udh(bytes.fromhex('080403000201'))
self.assertEqual(el[0]['name'], 'Concatenated short message, 16-bit reference number')
self.assertEqual(el[0]['data'], {'reference': 0x0300, 'max': 2, 'seq': 1})
def test_udh_app_port_8bit(self):
- from simtrace2_pysniff.server.decode import _decode_udh
+ from simtrace_analyser.decode import _decode_udh
el = _decode_udh(bytes.fromhex('0402B5A5'))
self.assertEqual(el[0]['data'], {'dest_port': 0xB5, 'orig_port': 0xA5})
def test_udh_app_port_16bit(self):
- from simtrace2_pysniff.server.decode import _decode_udh
+ from simtrace_analyser.decode import _decode_udh
el = _decode_udh(bytes.fromhex('05040B8423F0'))
self.assertEqual(el[0]['data'], {'dest_port': 0x0B84, 'orig_port': 0x23F0})
def test_udh_special_sms(self):
- from simtrace2_pysniff.server.decode import _decode_udh
+ from simtrace_analyser.decode import _decode_udh
el = _decode_udh(bytes.fromhex('01028102'))
self.assertEqual(el[0]['data'], {'store': True, 'indication': 'fax', 'count': 2})
def test_deliver_udh_ucs2(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
oa = b'\x91' + self._bcd('79031234567')
ud = bytes.fromhex('050003A10201') + 'Hi'.encode('utf-16-be')
tpdu = (b'\x40' + bytes([11]) + oa + b'\x00\x08' + b'\x00' * 7 +
@@ -672,7 +672,7 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['text'], 'Hi')
def test_submit_vp_relative(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
da = b'\x81' + self._bcd('999')
tpdu = b'\x09' + b'\x2A' + bytes([len(da)]) + da + b'\x00\x00' + b'\x8F' + b'\x00'
r = _decode_sm_tpdu(tpdu)
@@ -680,7 +680,7 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['vp'], '720 min')
def test_submit_vp_absolute(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
da = b'\x81' + self._bcd('999')
scts = bytes.fromhex('62802141030021')
tpdu = b'\x11' + b'\x2A' + bytes([len(da)]) + da + b'\x00\x00' + scts + b'\x00'
@@ -689,7 +689,7 @@ class TestSmTpdu(unittest.TestCase):
def test_submit_no_ud_encoding(self):
# Real capture: absolute VP with non-BCD bytes, no TP-UD.
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
tpdu = bytes.fromhex('11FF038154F50004A505326C333939')
r = _decode_sm_tpdu(tpdu)
self.assertEqual(r['da'], '455')
@@ -698,7 +698,7 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['vp'], 'A505326C333939')
def test_8bit_ascii_text(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
oa = b'\x91' + self._bcd('79031234567')
payload = b'Hello World'
tpdu = (b'\x00' + bytes([11]) + oa + b'\x00\x04' + b'\x00' * 7 +
@@ -708,7 +708,7 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['text'], 'Hello World')
def test_8bit_ascii_ctrl_replaced(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
oa = b'\x91' + self._bcd('79031234567')
payload = b'Hi\x00There'
tpdu = (b'\x00' + bytes([11]) + oa + b'\x00\x04' + b'\x00' * 7 +
@@ -717,7 +717,7 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['text'], 'Hi\u00b7There')
def test_8bit_high_text(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
oa = b'\x91' + self._bcd('79031234567')
payload = '\u00e9\u00fc\u00f1'.encode('latin-1')
tpdu = (b'\x00' + bytes([11]) + oa + b'\x00\x04' + b'\x00' * 7 +
@@ -726,7 +726,7 @@ class TestSmTpdu(unittest.TestCase):
self.assertEqual(r['text'], '\u00e9\u00fc\u00f1')
def test_8bit_mixed_no_text(self):
- from simtrace2_pysniff.server.decode import _decode_sm_tpdu
+ from simtrace_analyser.decode import _decode_sm_tpdu
oa = b'\x91' + self._bcd('79031234567')
payload = b'\x00\x01\xff\xfe'
tpdu = (b'\x00' + bytes([11]) + oa + b'\x00\x04' + b'\x00' * 7 +
@@ -738,15 +738,15 @@ class TestSmTpdu(unittest.TestCase):
class TestBcdAddress(unittest.TestCase):
def test_international(self):
- from simtrace2_pysniff.server.decode import _decode_bcd_address
+ from simtrace_analyser.decode import _decode_bcd_address
self.assertEqual(_decode_bcd_address(bytes.fromhex('912143658709')), '+1234567890')
def test_national(self):
- from simtrace2_pysniff.server.decode import _decode_bcd_address
+ from simtrace_analyser.decode import _decode_bcd_address
self.assertEqual(_decode_bcd_address(bytes.fromhex('A12143658709')), '1234567890')
def test_odd_digits(self):
- from simtrace2_pysniff.server.decode import _decode_bcd_address
+ from simtrace_analyser.decode import _decode_bcd_address
self.assertEqual(_decode_bcd_address(bytes.fromhex('9121436587F9')), '+123456789')
@@ -808,67 +808,67 @@ class TestProactiveDecode(unittest.TestCase):
class TestAnnexA(unittest.TestCase):
def test_gsm_default(self):
- from simtrace2_pysniff.server.decode import _decode_annex_a
+ from simtrace_analyser.decode import _decode_annex_a
self.assertEqual(_decode_annex_a(b'Alfa Mobile'), 'Alfa Mobile')
def test_ucs2_variant1(self):
- from simtrace2_pysniff.server.decode import _decode_annex_a
+ from simtrace_analyser.decode import _decode_annex_a
self.assertEqual(_decode_annex_a(b'\x80' + 'Привет'.encode('utf_16_be')), 'Привет')
def test_ucs2_variant2_base_ptr(self):
- from simtrace2_pysniff.server.decode import _decode_annex_a
+ from simtrace_analyser.decode import _decode_annex_a
# 'Н' = U+041D, base 0x08<<7 = 0x400, offset 0x1D
self.assertEqual(_decode_annex_a(bytes.fromhex('8101089D')), 'Н')
def test_ucs2_variant_90(self):
- from simtrace2_pysniff.server.decode import _decode_annex_a
+ from simtrace_analyser.decode import _decode_annex_a
self.assertEqual(_decode_annex_a(b'\x90' + 'Привет'.encode('utf_16_be')), 'Привет')
def test_gsm7_packed(self):
- from simtrace2_pysniff.server.decode import _decode_annex_a
+ from simtrace_analyser.decode import _decode_annex_a
self.assertEqual(_decode_annex_a(bytes.fromhex('41b6390c')), 'Alfa')
class TestPnnSpn(unittest.TestCase):
def test_pnn_full_name(self):
- from simtrace2_pysniff.server.decode import _decode_pnn
+ from simtrace_analyser.decode import _decode_pnn
r = _decode_pnn(bytes.fromhex('43058441b6390c' + 'ff' * 13))
self.assertEqual(r['full'], 'Alfa')
def test_pnn_ucs2(self):
- from simtrace2_pysniff.server.decode import _decode_pnn
+ from simtrace_analyser.decode import _decode_pnn
v = b'\x90' + 'Привет'.encode('utf-16-be')
r = _decode_pnn(b'\x43' + bytes([len(v)]) + v)
self.assertEqual(r['full'], 'Привет')
def test_spn_ucs2_90(self):
- from simtrace2_pysniff.server.decode import _decode_spn
+ from simtrace_analyser.decode import _decode_spn
r = _decode_spn(b'\x00' + b'\x90' + 'Привет'.encode('utf-16-be'))
self.assertEqual(r['name'], 'Привет')
def test_spn_gsm7(self):
- from simtrace2_pysniff.server.decode import _decode_spn
+ from simtrace_analyser.decode import _decode_spn
r = _decode_spn(b'\x00' + bytes.fromhex('41b6390c'))
self.assertEqual(r['name'], 'Alfa')
class TestDcsText(unittest.TestCase):
def test_gsm7(self):
- from simtrace2_pysniff.server.decode import _decode_dcs_text
+ from simtrace_analyser.decode import _decode_dcs_text
self.assertEqual(_decode_dcs_text(b'\x00Alfa'), 'Alfa')
def test_ucs2(self):
- from simtrace2_pysniff.server.decode import _decode_dcs_text
+ from simtrace_analyser.decode import _decode_dcs_text
self.assertEqual(_decode_dcs_text(b'\x08' + 'OK'.encode('utf_16_be')), 'OK')
def test_latin1(self):
- from simtrace2_pysniff.server.decode import _decode_dcs_text
+ from simtrace_analyser.decode import _decode_dcs_text
self.assertEqual(_decode_dcs_text(b'\x04caf\xe9'), 'café')
class TestAtrPps(unittest.TestCase):
def test_atr_simple_t0_only(self):
- from simtrace2_pysniff.server.decode import _decode_atr
+ from simtrace_analyser.decode import _decode_atr
# 3B 00: direct, T0=00, no interface bytes, 0 historical bytes, only T=0 → no TCK
r = _decode_atr(bytes.fromhex('3B00'))
self.assertEqual(r['convention'], 'direct')
@@ -877,7 +877,7 @@ class TestAtrPps(unittest.TestCase):
self.assertNotIn('tck', r)
def test_atr_ta1(self):
- from simtrace2_pysniff.server.decode import _decode_atr
+ from simtrace_analyser.decode import _decode_atr
# 3B 12 11 00 00: TA1=11 (Fi=1,Di=1), 2 historical bytes, T=0 → no TCK
r = _decode_atr(bytes.fromhex('3B12110000'))
self.assertEqual(r['convention'], 'direct')
@@ -888,7 +888,7 @@ class TestAtrPps(unittest.TestCase):
self.assertNotIn('tck', r)
def test_atr_data_rate_from_clk(self):
- from simtrace2_pysniff.server.decode import _decode_atr
+ from simtrace_analyser.decode import _decode_atr
# TA1=11 → F=372, D=1; CLK 3.579545 MHz → 9622 bit/s, ETU 103.9 µs
r = _decode_atr(bytes.fromhex('3B12110000'), clk_hz=3579545)
self.assertEqual(r['clk_hz'], 3579545)
@@ -896,28 +896,28 @@ class TestAtrPps(unittest.TestCase):
self.assertEqual(r['etu_us'], 103.9)
def test_atr_data_rate_uses_ta1_fidi(self):
- from simtrace2_pysniff.server.decode import _decode_atr
+ from simtrace_analyser.decode import _decode_atr
# TA1=95 → F=512, D=16; CLK 4 MHz → 125000 bit/s, ETU 8.0 µs
r = _decode_atr(bytes.fromhex('3B1095'), clk_hz=4000000)
self.assertEqual(r['data_rate'], 125000)
self.assertEqual(r['etu_us'], 8.0)
def test_atr_data_rate_defaults_without_ta1(self):
- from simtrace2_pysniff.server.decode import _decode_atr
+ from simtrace_analyser.decode import _decode_atr
# No TA1 → default F=372, D=1
r = _decode_atr(bytes.fromhex('3B00'), clk_hz=4000000)
self.assertEqual(r['data_rate'], 10753)
self.assertEqual(r['etu_us'], 93.0)
def test_atr_no_clk_no_rate(self):
- from simtrace2_pysniff.server.decode import _decode_atr
+ from simtrace_analyser.decode import _decode_atr
r = _decode_atr(bytes.fromhex('3B12110000'))
self.assertNotIn('clk_hz', r)
self.assertNotIn('data_rate', r)
self.assertNotIn('etu_us', r)
def test_atr_td1_t1_has_tck(self):
- from simtrace2_pysniff.server.decode import _decode_atr
+ from simtrace_analyser.decode import _decode_atr
# 3B 80 01 80 31 80 66 ... : construct T=1 with TCK
# T0=80 → TD1 present, 0 historical; TD1=01 (T=1, no more interface); then TCK
# bytes: 3B 80 01 xx where xx = XOR(80,01) = 81
@@ -928,7 +928,7 @@ class TestAtrPps(unittest.TestCase):
self.assertEqual(r['tck'], '81')
def test_atr_inverse_convention(self):
- from simtrace2_pysniff.server.decode import _decode_atr
+ from simtrace_analyser.decode import _decode_atr
# Inverse: TS=3F, then inverted bytes. Take direct ATR "3B 00" and invert body.
# Inverted 0x00 is 0x00, so ATR = 3F 00 → convention inverse, T0=00.
r = _decode_atr(bytes.fromhex('3F00'))
@@ -936,7 +936,7 @@ class TestAtrPps(unittest.TestCase):
self.assertEqual(r['t0'], '00')
def test_atr_historical_life_cycle(self):
- from simtrace2_pysniff.server.decode import _decode_atr
+ from simtrace_analyser.decode import _decode_atr
# T0 = 0x03 (3 historical bytes); historical = 80 00 05 (life cycle: operational)
r = _decode_atr(bytes.fromhex('3B03800005'))
self.assertEqual(r['historical_len'], 3)
@@ -944,7 +944,7 @@ class TestAtrPps(unittest.TestCase):
self.assertEqual(r['historical']['life_cycle'], 'operational state (activated)')
def test_atr_t15_is_pps_capability(self):
- from simtrace2_pysniff.server.decode import _decode_atr
+ from simtrace_analyser.decode import _decode_atr
# Real UICC ATR: T0=9F (TA1+TD1, 15 hist bytes), TA1=95, TD1=80 (T=0),
# TD2=1F (T=15 → PPS capability), TA3=C7 (clock stop + classes A/B/C).
r = _decode_atr(bytes.fromhex('3B9F95801FC78031E073FE21136364035B83079000F7'))
@@ -957,7 +957,7 @@ class TestAtrPps(unittest.TestCase):
self.assertTrue(r['tck_valid'])
def test_pps(self):
- from simtrace2_pysniff.server.decode import _decode_pps
+ from simtrace_analyser.decode import _decode_pps
# FF 10 11 xx: PPS0=10 (T=0, PPS1 present), PPS1=11 (Fi=1,Di=1), PCK=XOR(FF,10,11)=FE
r = _decode_pps(bytes.fromhex('FF1011FE'))
self.assertEqual(r['protocol'], 'T=0')
@@ -971,7 +971,7 @@ class TestPacketSummaries(unittest.TestCase):
"""APDU-list descriptions for ATR/PPS/RST/VCC packets."""
def _dec(self, hexstr, kind, clk_hz=None):
- from simtrace2_pysniff.server.decode import decode_sniff_msg
+ from simtrace_analyser.decode import decode_sniff_msg
return decode_sniff_msg(bytes.fromhex(hexstr), kind, clk_hz=clk_hz)
def test_atr_simple(self):
@@ -1166,54 +1166,54 @@ class TestSelectPath(unittest.TestCase):
class TestFileDecoders(unittest.TestCase):
def test_imsi(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
f = _decode_file_data('6f07', bytes.fromhex('082905102143658709'))
self.assertEqual(f['imsi'], '250011234567890')
def test_imsi_real_card(self):
# Real capture (Mi A1): 08 length byte + 8 bytes, first nibble is the
# TS 24.008 type+parity indicator (9 = IMSI, odd).
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
f = _decode_file_data('6f07', bytes.fromhex('082905917700917259'))
self.assertEqual(f['imsi'], '250197700192795')
def test_iccid(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
f = _decode_file_data('2fe2', bytes.fromhex('98891020000000460012'))
self.assertEqual(f['iccid'], '89980102000000640021')
def test_li(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
f = _decode_file_data('6f05', b'enru')
self.assertEqual(f['languages'], ['en', 'ru'])
def test_ef_impu_empty_record(self):
# EF_IMPU record that is an unused identity: tag 0x80, zero-length
# value, FF erase filler. Must be reported empty, NOT decoded as text.
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
f = _decode_file_data('6f04', bytes.fromhex('8000' + 'ff' * 73))
self.assertIn('empty', f)
self.assertNotIn('text', f)
def test_ef_impu_all_ff(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
f = _decode_file_data('6f04', b'\xff' * 75)
self.assertIn('empty', f)
def test_ef_impu_sip_uri(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
payload = b'\x80' + bytes([len(b'sip:alice@example.com')]) + b'sip:alice@example.com'
f = _decode_file_data('6f04', payload)
self.assertEqual(f.get('text'), 'sip:alice@example.com')
def test_ef_impu_lone_tag(self):
# A record containing only the identity tag (no length/value) is empty.
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
f = _decode_file_data('6f04', b'\x80')
self.assertIn('empty', f)
def test_adn(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
f = _decode_file_data('6f3a', bytes.fromhex(
'42204841203120536963FFFFFFFFFFFF06810628560810FFFFFFFFFFFFFF'))
self.assertEqual(f['name'], 'B HA 1 Sic')
@@ -1221,7 +1221,7 @@ class TestFileDecoders(unittest.TestCase):
def test_adn_ber_tlv_fallback(self):
# Non-ADN data (BER-TLV) must not be decoded as a name/number.
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
raw = bytes.fromhex(
'A0348001078120E823FF53C3E271754A644ED63DEFCF24A916387E3C585F'
'0820CF3E27841852F7820400000000830400000000840102'
@@ -1231,20 +1231,20 @@ class TestFileDecoders(unittest.TestCase):
self.assertIn('raw', f)
def test_ust(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
f = _decode_file_data('6f38', bytes.fromhex('07'))
self.assertEqual([s['n'] for s in f['services']], [1, 2, 3])
self.assertEqual(f['services'][0]['name'], 'Local Phone Book')
def test_plmn_list(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
f = _decode_file_data('6f30', bytes.fromhex('42f095'))
self.assertEqual(len(f['plmns']), 1)
self.assertIn('mcc', f['plmns'][0])
self.assertIn('mnc', f['plmns'][0])
def test_dir(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
f = _decode_file_data('2f00', bytes.fromhex(
'612b4f10a0000000871002fffff00189000001ff50074d656761466f6e'
'730ea00c80011781025f408203454150ffffffffffffffffffffffffffffffffffffff'))
@@ -1256,21 +1256,21 @@ class TestFileDecoders(unittest.TestCase):
self.assertEqual(eap['label'], 'EAP')
def test_nai_empty_record(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
# Unused ISIM record (all FF) must be 'empty', not garbage text.
f = _decode_file_data('6f04', b'\xff' * 75)
self.assertTrue(f['empty'])
self.assertNotIn('text', f)
def test_nai(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
f = _decode_file_data('6f04', bytes.fromhex(
'80357369703a32353030323639333537373333363840696d732e6d6e63'
'3030322e6d63633235302e336770706e6574776f726b2e6f7267'))
self.assertEqual(f['text'], 'sip:250026935773368@ims.mnc002.mcc250.3gppnetwork.org')
def test_plmn_wact(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
# TS 51.011 §10.3.35: 5-byte entries (3 PLMN + 2 access tech).
f = _decode_file_data('6f60', bytes.fromhex('52f020400052f0208000'))
self.assertEqual(f['plmns'][0]['mcc'], '250')
@@ -1279,7 +1279,7 @@ class TestFileDecoders(unittest.TestCase):
self.assertEqual(f['plmns'][1]['access_tech'], 'UTRAN')
def test_sms_record(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
# status 0x07 (MO, to be sent) + empty SMSC (len 0) + SMS-SUBMIT TPDU
rec = bytes.fromhex('0700') + bytes.fromhex('01ff038199f90004024869')
f = _decode_file_data('6f3c', rec)
@@ -1290,7 +1290,7 @@ class TestFileDecoders(unittest.TestCase):
self.assertEqual(f['tpdu']['text'], 'Hi')
def test_loci(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
# EF_LOCI (11 bytes): TMSI 4 + LAI 5 + TMSI_TIME 1 + status 1.
f = _decode_file_data('6f7e', bytes.fromhex('1e731e5752f09969c50000'))
self.assertEqual(f['tmsi'], '1E731E57')
@@ -1300,14 +1300,14 @@ class TestFileDecoders(unittest.TestCase):
self.assertEqual(f['location_update_status'], '0x00')
def test_epsloci(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
# EF_EPSLOCI (18 bytes): GUTI 12 + TAI 5 + status 1.
f = _decode_file_data('6fe3', bytes.fromhex('0bf652f0998001b0fb91192652f0991d9900'))
self.assertEqual(f['guti'], '0BF652F0998001B0FB911926')
self.assertEqual(f['eps_update_status'], '0x00')
def test_psloci(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
# EF_PSLOCI (14 bytes): P-TMSI 4 + sig 3 + RAI 6 + status 1.
f = _decode_file_data('6f73', bytes.fromhex('1111111122222252f0991d990001'))
self.assertEqual(f['p_tmsi'], '11111111')
@@ -1319,7 +1319,7 @@ class TestFileDecoders(unittest.TestCase):
self.assertEqual(f['update_status'], '0x01')
def test_epsnsc(self):
- from simtrace2_pysniff.server.decode import _decode_file_data
+ from simtrace_analyser.decode import _decode_file_data
# EF_EPSNSC record: A0 { 80 KSI_ASME, 81 K_ASME, 82/83 NAS counts, 84 algos }.
f = _decode_file_data('6fe4', bytes.fromhex(
'a0188001018104aabbccdd820400000003830400000004840102'))
@@ -1391,7 +1391,7 @@ class TestFileDecoders(unittest.TestCase):
self.assertIn('18 record(s)', r['summary'])
def test_select_target_fid(self):
- from simtrace2_pysniff.server.decode import select_target_fid
+ from simtrace_analyser.decode import select_target_fid
self.assertEqual(select_target_fid({'p1': {'raw': '00'}, 'body': {'hex': '6f07'}}), '6f07')
self.assertEqual(select_target_fid({'p1': {'raw': '08'}, 'body': {'hex': '7fff6f05'}}), '6f05')
self.assertIsNone(select_target_fid({'p1': {'raw': '04'}, 'body': {'hex': 'a0000000871002'}}))
@@ -1403,7 +1403,7 @@ class TestSelectionTracking(unittest.TestCase):
# GSMTAP BAD_FCS flag stored via insert_message must surface as a
# decode error in the DB → frontend path (not just the direct call).
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
from simtrace2_pysniff.gsmtap import GSMTAP_FLAG_BAD_FCS
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
@@ -1415,7 +1415,7 @@ class TestSelectionTracking(unittest.TestCase):
def test_select_then_read(self):
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
@@ -1429,7 +1429,7 @@ class TestSelectionTracking(unittest.TestCase):
def test_atr_resets_selection(self):
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
@@ -1444,7 +1444,7 @@ class TestSelectionTracking(unittest.TestCase):
# A waiting-time timeout interrupts one TPDU but the card stays
# selected, so the selection must not be reset.
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
@@ -1457,7 +1457,7 @@ class TestSelectionTracking(unittest.TestCase):
def test_reset_assert_resets_selection(self):
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
@@ -1472,7 +1472,7 @@ class TestSelectionTracking(unittest.TestCase):
# A capture gap (device disconnected/reconnected) means messages may
# have been missed, so the selection must be reset.
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
@@ -1486,7 +1486,7 @@ class TestSelectionTracking(unittest.TestCase):
def test_failed_select_keeps_selection(self):
# A SELECT that fails (6A82) must not change the current file.
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
@@ -1502,7 +1502,7 @@ class TestSelectionTracking(unittest.TestCase):
# SELECT a transparent EF, then READ RECORD → selection is stale, so
# we must not decode garbage.
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
@@ -1519,7 +1519,7 @@ class TestSelectionTracking(unittest.TestCase):
# SFI referencing must resolve the target EF (EF_EPSLOCI), not the
# last-selected file.
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
@@ -1537,7 +1537,7 @@ class TestSelectionTracking(unittest.TestCase):
def test_sfi_unknown_not_misattributed(self):
# An SFI not in the map must not fall back to the selected file.
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
@@ -1552,7 +1552,7 @@ class TestSelectionTracking(unittest.TestCase):
# A record command with a valid SFI sets that file as the current EF,
# so a subsequent non-SFI record op targets it (TS 102 221 §11.1.2).
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
@@ -1572,7 +1572,7 @@ class TestSelectionTracking(unittest.TestCase):
def test_channel_selection_not_clobbered(self):
# A SELECT on channel 1 (CLA 01) must not change channel 0's selection.
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
@@ -1610,7 +1610,7 @@ class TestSfiResolution(unittest.TestCase):
self.assertEqual(r['file']['tmsi'], '1E731E57')
def test_selected_df_fid(self):
- from simtrace2_pysniff.server.decode import selected_df_fid, sfi_table
+ from simtrace_analyser.decode import selected_df_fid, sfi_table
self.assertEqual(selected_df_fid({'ins_hex': 'a4', 'p1': {'raw': '08'},
'body': {'hex': '3f007fff'}}), '7fff')
self.assertEqual(selected_df_fid({'ins_hex': 'a4', 'p1': {'raw': '00'},
@@ -1690,12 +1690,12 @@ class TestP1P2(unittest.TestCase):
class TestSwWrongLength(unittest.TestCase):
def test_6c(self):
- from simtrace2_pysniff.server.decode import decode_sw
+ from simtrace_analyser.decode import decode_sw
self.assertEqual(decode_sw(bytes.fromhex('6c38'))['name'],
'Wrong length (Le): correct length is 0x38 (56 bytes)')
def test_62_unknown_is_unnamed(self):
- from simtrace2_pysniff.server.decode import decode_sw
+ from simtrace_analyser.decode import decode_sw
self.assertIsNone(decode_sw(bytes.fromhex('6205'))['name'])
@@ -1910,7 +1910,7 @@ class TestSpecRegistry(unittest.TestCase):
def test_event_names_multi_rat_and_profile_container(self):
# SET UP EVENT LIST with events '14', '19', '1E'
- from simtrace2_pysniff.server.decode import EVENT_TYPES
+ from simtrace_analyser.decode import EVENT_TYPES
self.assertEqual(EVENT_TYPES[0x14],
'Access Technology Change (multiple)')
self.assertEqual(EVENT_TYPES[0x19], 'Profile Container')
@@ -1926,19 +1926,19 @@ class TestSpecRegistry(unittest.TestCase):
'Profile Container', 'CAG cell selection'])
def test_tr_result_me_unable_vs_network(self):
- from simtrace2_pysniff.server.decode import TR_RESULTS
+ from simtrace_analyser.decode import TR_RESULTS
self.assertIn('ME currently unable', TR_RESULTS[0x20])
self.assertIn('Network currently unable', TR_RESULTS[0x21])
def test_tr_result_user_session_codes(self):
- from simtrace2_pysniff.server.decode import TR_RESULTS
+ from simtrace_analyser.decode import TR_RESULTS
self.assertEqual(TR_RESULTS[0x14],
'USSD or SS transaction terminated by the user')
self.assertNotIn(0x0A, TR_RESULTS)
self.assertNotIn(0x0E, TR_RESULTS)
def test_tr_result_permanent_problems(self):
- from simtrace2_pysniff.server.decode import TR_RESULTS
+ from simtrace_analyser.decode import TR_RESULTS
self.assertEqual(TR_RESULTS[0x3A],
'Bearer Independent Protocol error')
self.assertEqual(TR_RESULTS[0x3B],
@@ -1947,7 +1947,7 @@ class TestSpecRegistry(unittest.TestCase):
self.assertEqual(TR_RESULTS[0x3D], 'MMS error')
def test_envelope_types_extended(self):
- from simtrace2_pysniff.server.decode import ENVELOPE_TYPES
+ from simtrace_analyser.decode import ENVELOPE_TYPES
self.assertEqual(ENVELOPE_TYPES[0xD9], 'USSD DOWNLOAD')
self.assertEqual(ENVELOPE_TYPES[0xDD],
'GEOGRAPHICAL LOCATION REPORTING')
@@ -1955,7 +1955,7 @@ class TestSpecRegistry(unittest.TestCase):
self.assertEqual(ENVELOPE_TYPES[0xE0], '5G PROSE REPORT')
def test_lsi_command_type(self):
- from simtrace2_pysniff.server.decode import CAT_COMMAND_TYPES
+ from simtrace_analyser.decode import CAT_COMMAND_TYPES
self.assertEqual(CAT_COMMAND_TYPES[0x79], 'LSI COMMAND')
def test_proprietary_type_fallback(self):
@@ -1964,7 +1964,7 @@ class TestSpecRegistry(unittest.TestCase):
self.assertEqual(r['cmd']['type'], 'Proprietary (0xF2)')
def test_pli_qualifiers_esn_and_supported_rat(self):
- from simtrace2_pysniff.server.decode import PLI_QUALIFIERS
+ from simtrace_analyser.decode import PLI_QUALIFIERS
self.assertEqual(PLI_QUALIFIERS[0x07], 'ESN of the terminal')
self.assertEqual(PLI_QUALIFIERS[0x1A],
'Supported Radio Access Technologies')
@@ -2038,7 +2038,7 @@ class TestEnvelopeDecoders(unittest.TestCase):
def test_timer_value_semi_octet_order(self):
# TS 23.040 semi-octets: first digit in the LOW nibble.
# 90 52 30 → "09:25:03" (pre-fix code read high-nibble first).
- from simtrace2_pysniff.server.decode import _decode_timer_value
+ from simtrace_analyser.decode import _decode_timer_value
self.assertEqual(_decode_timer_value(bytes.fromhex('905230')),
'09:25:03')
# 01 02 03 means "10:20:30", not "01:02:03".
@@ -2120,7 +2120,7 @@ class TestFileDescriptor(unittest.TestCase):
"""Audit: TS 102 221 Table 11.5 file descriptor byte coding."""
def _fdb(self, hexstr):
- from simtrace2_pysniff.server.decode import _decode_file_descriptor
+ from simtrace_analyser.decode import _decode_file_descriptor
return _decode_file_descriptor(bytes.fromhex(hexstr))
def test_df_shareable(self):
@@ -2156,20 +2156,20 @@ class TestFcpTags(unittest.TestCase):
"""Audit: FCP tag registry per ISO T12 / TS 221 §11.1.1.4."""
def test_ab_is_security_attributes(self):
- from simtrace2_pysniff.server.decode import _decode_fcp
+ from simtrace_analyser.decode import _decode_fcp
r = _decode_fcp(bytes.fromhex('6204ab02aabb'))
# outer 6F wrapper → recurse; AB = security attributes template
self.assertIn('security_attr_template', r)
self.assertNotIn('short_ef_id', r)
def test_sfi_tag_88_high_bits(self):
- from simtrace2_pysniff.server.decode import _decode_fcp
+ from simtrace_analyser.decode import _decode_fcp
# SFI 7 in bits b8-b4 → value byte 0x38
r = _decode_fcp(bytes.fromhex('880138'))
self.assertEqual(r['sfi'], 7)
def test_sfi_fallback_fid_low_bits(self):
- from simtrace2_pysniff.server.decode import _decode_fcp
+ from simtrace_analyser.decode import _decode_fcp
r = _decode_fcp(bytes.fromhex('83026f7e'))
self.assertEqual(r['sfi'], 0x1e) # 6F7E & 0x1F = 30
@@ -2178,19 +2178,19 @@ class TestNewFileDecoders(unittest.TestCase):
"""Audit: file decoders added per UICC_FILES.md."""
def test_ecc_bcd_and_esc(self):
- from simtrace2_pysniff.server.decode import _decode_ecc
+ from simtrace_analyser.decode import _decode_ecc
r = _decode_ecc(bytes.fromhex('11f200'), p1=None)
self.assertEqual(r['number'], '112')
self.assertEqual(r['esc'], '0x00')
def test_kc_ksi(self):
- from simtrace2_pysniff.server.decode import _decode_kc
+ from simtrace_analyser.decode import _decode_kc
r = _decode_kc(bytes.fromhex('0011223344556677' + '07'), p1=None)
self.assertEqual(r['kc'], '0011223344556677')
self.assertEqual(r['ksi'], 0)
def test_opl_record(self):
- from simtrace2_pysniff.server.decode import _decode_opl
+ from simtrace_analyser.decode import _decode_opl
# PLMN 250-01 (52 f0 f1), LAC 0000..FFFE, PNN rec 1
r = _decode_opl(bytes.fromhex('52f0100000fffe01'), p1=None)
self.assertEqual(r['mcc'], '250')
@@ -2199,7 +2199,7 @@ class TestNewFileDecoders(unittest.TestCase):
self.assertEqual(r['pnn_record'], 1)
def test_ext1(self):
- from simtrace2_pysniff.server.decode import _decode_ext1
+ from simtrace_analyser.decode import _decode_ext1
raw = bytes.fromhex('03' + '21f39210000000000000' + 'ff' + 'ff')
r = _decode_ext1(raw, p1=None)
self.assertTrue(r['number'].startswith('123'))
@@ -2207,25 +2207,25 @@ class TestNewFileDecoders(unittest.TestCase):
self.assertIsNone(r['ext'])
def test_ad(self):
- from simtrace2_pysniff.server.decode import _decode_ad
+ from simtrace_analyser.decode import _decode_ad
r = _decode_ad(bytes.fromhex('00000003'), p1=None)
self.assertEqual(r['op_mode'], 'normal operation')
self.assertEqual(r['mnc_length'], 3)
def test_acl(self):
- from simtrace2_pysniff.server.decode import _decode_acl
+ from simtrace_analyser.decode import _decode_acl
r = _decode_acl(bytes.fromhex('01dd08696e7465726e6574'), p1=None)
self.assertEqual(r['apns'], ['internet'])
def test_spdi(self):
- from simtrace2_pysniff.server.decode import _decode_spdi
+ from simtrace_analyser.decode import _decode_spdi
# Standard form: A3 wrapping inner '80' TLVs, one PLMN each
r = _decode_spdi(bytes.fromhex('a30a8003' '52f010' '8003' '72f010'), p1=None)
self.assertEqual(r['plmns'], [{'mcc': '250', 'mnc': '01'},
{'mcc': '270', 'mnc': '01'}])
def test_spdi_bare_records_fallback(self):
- from simtrace2_pysniff.server.decode import _decode_spdi
+ from simtrace_analyser.decode import _decode_spdi
r = _decode_spdi(bytes.fromhex('a306' '52f010' '00f110'), p1=None)
self.assertEqual(r['plmns'], [{'mcc': '250', 'mnc': '01'},
{'mcc': '001', 'mnc': '01'}])
@@ -2244,18 +2244,18 @@ class TestNewFileDecoders(unittest.TestCase):
self.assertIn('250/02', r['summary'])
def test_smsp(self):
- from simtrace2_pysniff.server.decode import _decode_smsp
+ from simtrace_analyser.decode import _decode_smsp
raw = bytes.fromhex('00' + '00' + 'ff' * 24 + '000000')
r = _decode_smsp(raw, p1=None)
self.assertEqual(r['param_indicators'], '0x00')
def test_cbmir_multiple_ranges(self):
- from simtrace2_pysniff.server.decode import _decode_cbmir
+ from simtrace_analyser.decode import _decode_cbmir
r = _decode_cbmir(bytes.fromhex('00 01 00 02' .replace(' ', '') + 'fffe0000'), p1=None)
self.assertEqual(r['ranges'], [[1, 2], [65534, 0]])
def test_missing_apdu_spec_names(self):
- from simtrace2_pysniff.server.decode import decode_message
+ from simtrace_analyser.decode import decode_message
for ins, name in [('d0', 'WRITE BINARY'), ('a0', 'SEARCH BINARY'),
('b3', 'READ RECORD (odd INS)'),
('c3', 'ENVELOPE (odd INS)'),
@@ -2271,7 +2271,7 @@ class TestSwUiccSpecific(unittest.TestCase):
"""TS 102 221 UICC-specific SWs (tables 10.7-10.15)."""
def _sw(self, hexstr):
- from simtrace2_pysniff.server.decode import decode_sw
+ from simtrace_analyser.decode import decode_sw
return decode_sw(bytes.fromhex(hexstr))
def test_91xx_proactive_pending(self):
@@ -2331,7 +2331,7 @@ class TestSecuredCiphering(unittest.TestCase):
# stk_test1-style SMS-PP DOWNLOAD secured packet (ciphering on):
# CNTR on the wire is ciphertext; the card's real counter (~200)
# is only recoverable after 3DES decryption.
- from simtrace2_pysniff.server.decode import _decode_secured_packet
+ from simtrace_analyser.decode import _decode_secured_packet
sec = bytes.fromhex('003815160115 15b000011830a44e4191ad45d0ec'
'2a291315986468a539d248fcdce7952c31cd635baec'
'7e93a5d567b59f6283316c0dad8603bf3'.replace(' ', ''))
@@ -2343,7 +2343,7 @@ class TestSecuredCiphering(unittest.TestCase):
def test_unciphered_packet_plain_cntr(self):
# SPI1 = 0x10: counter present, NO ciphering, RC/CC/DS none →
# CNTR is genuine plaintext (e.g. 200 = 0x00000000C8).
- from simtrace2_pysniff.server.decode import _decode_secured_packet
+ from simtrace_analyser.decode import _decode_secured_packet
sec = bytes.fromhex('00100d10011515b0000100000000c800aabb')
r = _decode_secured_packet(sec)
self.assertFalse(r['ciphered'])
@@ -2351,7 +2351,7 @@ class TestSecuredCiphering(unittest.TestCase):
self.assertNotIn('rc_cc_ds', r)
def test_response_packet_ciphered_flag(self):
- from simtrace2_pysniff.server.decode import _decode_response_packet
+ from simtrace_analyser.decode import _decode_response_packet
body = bytes.fromhex('001f0a00000000000000500000ab12800101230d08a0000001510000000f9a9000')
r = _decode_response_packet(body, ciphered=True)
self.assertTrue(r['ciphered'])
@@ -2374,7 +2374,7 @@ class TestSecuredCiphering(unittest.TestCase):
# cipher_block = the on-wire ciphered octets (CNTR..end) and equals
# the reassembled cntr+pcntr+rc+data — what a client-side decryptor
# feeds to 3DES/AES-CBC.
- from simtrace2_pysniff.server.decode import _decode_secured_packet
+ from simtrace_analyser.decode import _decode_secured_packet
sec = bytes.fromhex('00381516011515b000011830a44e4191ad45d0ec'
'2a291315986468a539d248fcdce7952c31cd635baec'
'7e93a5d567b59f6283316c0dad8603bf3'.replace(' ', ''))
@@ -2388,7 +2388,7 @@ class TestSecuredCiphering(unittest.TestCase):
def test_aes_algorithm_recognized(self):
# AES must stay recognized for ciphering (KIc nibble 2) and for the
# CC (KID nibble 2 = AES-CMAC) — the client-side decryptor relies on it.
- from simtrace2_pysniff.server.decode import _KIC_ALGO, _KID_CC_ALGO
+ from simtrace_analyser.decode import _KIC_ALGO, _KID_CC_ALGO
self.assertEqual(_KIC_ALGO[0x02], 'AES-CBC')
self.assertEqual(_KID_CC_ALGO[0x02], 'AES-CMAC')
@@ -2397,7 +2397,7 @@ class TestSpiBits(unittest.TestCase):
"""TS 102 225 §5.1.1: SPI1 b3 = ciphering, SPI2 b5 = PoR ciphering."""
def _spi(self, spi_hex):
- from simtrace2_pysniff.server.decode import _decode_secured_packet
+ from simtrace_analyser.decode import _decode_secured_packet
# CPL(2) CHL(1)=0x0D SPI(2) KIc KID TAR(3) CNTR(5) PCNTR + 1 data byte
body = bytes.fromhex('000d0d' + spi_hex + '1505000001000000000000aa')
return _decode_secured_packet(body)['spi']
@@ -2511,7 +2511,7 @@ class TestClaDecode(unittest.TestCase):
"""ISO 7816-4 §5.1.1 first/further interindustry CLA coding."""
def _cla(self, value):
- from simtrace2_pysniff.server.decode import decode_cla
+ from simtrace_analyser.decode import decode_cla
return decode_cla(value)
def test_basic_channel_iso(self):
@@ -2554,7 +2554,7 @@ class TestLineEvents(unittest.TestCase):
"""sigrok-iso7816-stream RST/VCC line events (GSMTAP 0x10/0x11)."""
def _dec(self, kind, payload):
- from simtrace2_pysniff.server.decode import decode_line_event
+ from simtrace_analyser.decode import decode_line_event
return decode_line_event(bytes.fromhex(payload), kind)
def test_rst_asserted(self):
@@ -2586,7 +2586,7 @@ class TestLineEvents(unittest.TestCase):
# rst/vcc must be decoded server-side (database.py dispatch) so the
# PWA receives decoded.label/event, not just the raw bytes.
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
@@ -2597,23 +2597,23 @@ class TestLineEvents(unittest.TestCase):
self.assertEqual(msgs[1]['decoded']['label'], 'VCC ON (power-up)')
def test_sniff_routing(self):
- from simtrace2_pysniff.server.decode import decode_sniff_msg
+ from simtrace_analyser.decode import decode_sniff_msg
self.assertEqual(decode_sniff_msg(b'\x01\x00\x00', 'rst')['type'], 'rst')
self.assertEqual(decode_sniff_msg(b'\x00\x01\x00', 'vcc')['type'], 'vcc')
def test_bad_fcs_flag_marks_tpdu(self):
- from simtrace2_pysniff.server.decode import decode_sniff_msg
+ from simtrace_analyser.decode import decode_sniff_msg
r = decode_sniff_msg(b'\x80\xf2\x00\x00\x00', 'tpdu', flags=0x01)
self.assertIn('bad FCS (desynced)', r['errors'])
def test_bad_fcs_flag_marks_rst(self):
- from simtrace2_pysniff.server.decode import decode_sniff_msg
+ from simtrace_analyser.decode import decode_sniff_msg
r = decode_sniff_msg(b'\x01\x00\x00', 'rst', flags=0x01)
self.assertIn('bad FCS (desynced)', r['errors'])
self.assertEqual(r['event'], 'reset asserted')
def test_no_fcs_flag_no_error(self):
- from simtrace2_pysniff.server.decode import decode_sniff_msg
+ from simtrace_analyser.decode import decode_sniff_msg
r = decode_sniff_msg(b'\x01\x00\x00', 'rst', flags=0)
self.assertNotIn('errors', r)
@@ -2646,7 +2646,7 @@ class TestLineEvents(unittest.TestCase):
# CLK from RST → data rate on the following ATR; rate consumed once,
# so a second ATR without a preceding RST has no rate.
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
@@ -2662,7 +2662,7 @@ class TestLineEvents(unittest.TestCase):
# Ordering caveat: a short ATR can be emitted before the RST event
# that measured its CLK rate — the rate is backfilled onto it.
import tempfile
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.database import Database
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
db = Database(tmp.name)
sid = db.create_session('capture')
diff --git a/tests/test_pcap.py b/tests/test_pcap.py
index 4d169ba..3541203 100644
--- a/tests/test_pcap.py
+++ b/tests/test_pcap.py
@@ -4,7 +4,7 @@ import struct
import unittest
from simtrace2_pysniff.pcap import build_pcap, PCAP_MAGIC, LINKTYPE_ETHERNET
-from simtrace2_pysniff.server.server import _content_disposition
+from simtrace_analyser.server import _content_disposition
FRAMING = 14 + 20 + 8 # Ethernet + IPv4 + UDP
diff --git a/tests/test_pcap_import.py b/tests/test_pcap_import.py
index 8dc338f..cc09f94 100644
--- a/tests/test_pcap_import.py
+++ b/tests/test_pcap_import.py
@@ -147,8 +147,8 @@ class TestTsresol(unittest.TestCase):
class TestImportEndpoint(unittest.TestCase):
def _handler(self, body):
- from simtrace2_pysniff.server.server import RequestHandler
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.server import RequestHandler
+ from simtrace_analyser.database import Database
class Fake(RequestHandler):
def __init__(self, body, db):
@@ -224,12 +224,12 @@ class TestImportEndpoint(unittest.TestCase):
class TestVersion(unittest.TestCase):
def test_package_version(self):
from simtrace2_pysniff import __version__
- self.assertEqual(__version__, '1.22.21')
+ self.assertEqual(__version__, '2.0.0')
def test_status_includes_version(self):
import os
- from simtrace2_pysniff.server.server import RequestHandler
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.server import RequestHandler
+ from simtrace_analyser.database import Database
from simtrace2_pysniff import __version__
class FakeCapture:
@@ -259,8 +259,8 @@ class TestVersion(unittest.TestCase):
class TestCaptureDisabled(unittest.TestCase):
def _handler(self, capture_mode):
import os
- from simtrace2_pysniff.server.server import RequestHandler
- from simtrace2_pysniff.server.database import Database
+ from simtrace_analyser.server import RequestHandler
+ from simtrace_analyser.database import Database
class Fake(RequestHandler):
def __init__(self, db, capture_mode):