rename OTAMan → SIMple (v2.3.0)

- package pysim_otaman_server → pysim_simple_server; distribution and
  console script pysim-otaman-server → pysim-simple-server
- PWA branding SIMple: title, header, manifest, help EN/RU, GitHub links
- localStorage keys otaman_* → simple_* (clean break, no migration)
- export filenames simple-cards.json / simple-custom-files.json
- service worker cache simple-v189
- README/docs/setup/start scripts updated; no 'otaman' left in the tree
This commit is contained in:
2026-09-19 08:25:20 +03:00
parent 9c64da86a3
commit ac18c3e2cf
35 changed files with 207 additions and 207 deletions
+13 -13
View File
@@ -1,8 +1,8 @@
# OTAMan — SIM OTA toolkit: PWA + local card server
# SIMple — SIM OTA toolkit: PWA + local card server
OTAMan is an offline HTML/JS PWA for building APDU commands (SIM, USIM, GlobalPlatform RAM), assembling SCP80 secured packets per ETSI TS 102 225, and constructing Expanded Remote Application data format APDU per ETSI TS 102 226. A bundled [`pysim-otaman-server`](pysim_otaman_server/) exposes a local HTTP API over pySim for live card operations: file manager, raw APDU, SIM Toolkit menu browsing, and OTA (SCP80) delivery.
SIMple is an offline HTML/JS PWA for building APDU commands (SIM, USIM, GlobalPlatform RAM), assembling SCP80 secured packets per ETSI TS 102 225, and constructing Expanded Remote Application data format APDU per ETSI TS 102 226. A bundled [`pysim-simple-server`](pysim_simple_server/) exposes a local HTTP API over pySim for live card operations: file manager, raw APDU, SIM Toolkit menu browsing, and OTA (SCP80) delivery.
**Demo:** [otaman.atroshin.ru](https://otaman.atroshin.ru) — the PWA alone, for experimenting. Install the server (below) for card-reader functions.
**Demo:** [simple.atroshin.ru](https://simple.atroshin.ru) — the PWA alone, for experimenting. Install the server (below) for card-reader functions.
## Quick start
@@ -11,8 +11,8 @@ OTAMan is an offline HTML/JS PWA for building APDU commands (SIM, USIM, GlobalPl
**Full (PWA + card server):**
```sh
git clone https://github.com/anttro/otaman.git
cd otaman
git clone https://github.com/anttro/simple.git
cd simple
./setup.sh # or setup.bat on Windows — creates .venv, installs pysim + server
./start.sh # or start.bat — starts the server (it serves the PWA too)
```
@@ -480,9 +480,9 @@ Delete confirms via a browser prompt before sending the GP `DELETE` command via
## Card Reader (pySim integration)
Connects to the bundled [`pysim-otaman-server`](pysim_otaman_server/) for live card operations.
Connects to the bundled [`pysim-simple-server`](pysim_simple_server/) for live card operations.
> **Browser restriction:** when the PWA is served from a public HTTPS host, reaching the local server (`http://127.0.0.1:8080`) requires two things: the server must send `Access-Control-Allow-Private-Network: true` (pysim-otaman-server ≥ 1.6.1 does this automatically), and the browser must be allowed to access the local network — in Chrome/Edge/Vivaldi: Site settings → Local network access → allow the site (or accept the permission prompt). Without the browser permission, the request to `127.0.0.1` is blocked before any preflight is sent.
> **Browser restriction:** when the PWA is served from a public HTTPS host, reaching the local server (`http://127.0.0.1:8080`) requires two things: the server must send `Access-Control-Allow-Private-Network: true` (pysim-simple-server ≥ 1.6.1 does this automatically), and the browser must be allowed to access the local network — in Chrome/Edge/Vivaldi: Site settings → Local network access → allow the site (or accept the permission prompt). Without the browser permission, the request to `127.0.0.1` is blocked before any preflight is sent.
### File Browser
@@ -605,7 +605,7 @@ The state line shows the listener, the negotiated identity and live channels (by
### Scripts
The **Scripts** pill manages named APDU lists stored in `localStorage` (`otaman_scripts`) and sent to the server when a listener starts. Each list is a sequence of hex C-APDUs, one per line (`#`/`;` comments allowed). **New** creates one from a template:
The **Scripts** pill manages named APDU lists stored in `localStorage` (`simple_scripts`) and sent to the server when a listener starts. Each list is a sequence of hex C-APDUs, one per line (`#`/`;` comments allowed). **New** creates one from a template:
| Template | What it builds |
|---|---|
@@ -618,7 +618,7 @@ The table lists each script with kind, APDU count and creation time; **Edit** op
## PWA
OTAMan is a Progressive Web App and can be installed for offline use. Use the **INSTALL PWA** button in the header, or use the browser's install prompt.
SIMple is a Progressive Web App and can be installed for offline use. Use the **INSTALL PWA** button in the header, or use the browser's install prompt.
- Service worker pre-caches all assets on first visit
- App icons at 192×192 and 512×512
@@ -631,9 +631,9 @@ A dark theme is included. It follows the system preference and can be toggled ma
The interface is in English with Russian support. The language is detected from `navigator.language`; the header toggle (EN/RU) stores the choice in `localStorage`. Switching the language also re-renders visible dynamic views (profile lists, check reports, snapshots, cards, proactive views).
## Server (pysim-otaman-server)
## Server (pysim-simple-server)
The bundled Python server wraps [pySim](https://osmocom.org/projects/pysim/wiki) and serves both the OTAMan PWA (from `frontend/`) and a JSON API under `/api/*`.
The bundled Python server wraps [pySim](https://osmocom.org/projects/pysim/wiki) and serves both the SIMple PWA (from `frontend/`) and a JSON API under `/api/*`.
### Prerequisites
@@ -657,7 +657,7 @@ python3 -m venv .venv
source .venv/bin/activate # Linux/macOS (Windows: .venv\Scripts\activate)
pip install git+https://github.com/osmocom/pysim.git
pip install -e . # editable — serves frontend/ from the source tree
pysim-otaman-server --http-port 8080
pysim-simple-server --http-port 8080
```
### CLI options
@@ -699,7 +699,7 @@ The UI is in English with Russian language support. Language is detected from th
## Version compatibility
| PWA (OTAMan) | Server | Status |
| PWA (SIMple) | Server | Status |
|-------------|--------|--------|
| 1.x.x | 1.x.x | ✅ Compatible |
| 1.x.x | 0.x.x | ❌ Outdated — update server |
+12 -12
View File
@@ -1,8 +1,8 @@
# OTAMan — SIM OTA toolkit: PWA + локальный сервер карт
# SIMple — SIM OTA toolkit: PWA + локальный сервер карт
OTAMan — автономный offline-PWA (HTML/JS) для создания APDU-команд (SIM, USIM, GlobalPlatform RAM), сборки защищённых пакетов SCP80 по ETSI TS 102 225 и построения Expanded Remote Application data format APDU по ETSI TS 102 226. В комплекте — [`pysim-otaman-server`](pysim_otaman_server/) — локальный HTTP-сервер поверх pySim для работы с картой: файловый менеджер, сырые APDU, меню SIM Toolkit и доставка OTA.
SIMple — автономный offline-PWA (HTML/JS) для создания APDU-команд (SIM, USIM, GlobalPlatform RAM), сборки защищённых пакетов SCP80 по ETSI TS 102 225 и построения Expanded Remote Application data format APDU по ETSI TS 102 226. В комплекте — [`pysim-simple-server`](pysim_simple_server/) — локальный HTTP-сервер поверх pySim для работы с картой: файловый менеджер, сырые APDU, меню SIM Toolkit и доставка OTA.
**Демо:** [otaman.atroshin.ru](https://otaman.atroshin.ru) — только PWA, для экспериментов. Для функций картридера установите сервер (ниже).
**Демо:** [simple.atroshin.ru](https://simple.atroshin.ru) — только PWA, для экспериментов. Для функций картридера установите сервер (ниже).
## Быстрый старт
@@ -11,8 +11,8 @@ OTAMan — автономный offline-PWA (HTML/JS) для создания AP
**Полная установка (PWA + сервер карт):**
```sh
git clone https://github.com/anttro/otaman.git
cd otaman
git clone https://github.com/anttro/simple.git
cd simple
./setup.sh # или setup.bat в Windows — создаёт .venv, ставит pysim + сервер
./start.sh # или start.bat — запускает сервер (он же раздаёт PWA)
```
@@ -454,9 +454,9 @@ Delivery PoR (SPI2 `01`) проще — карта возвращает PoR на
## Card Reader (интеграция с pySim)
Подключение к встроенному [`pysim-otaman-server`](pysim_otaman_server/) для работы с картой.
Подключение к встроенному [`pysim-simple-server`](pysim_simple_server/) для работы с картой.
> **Ограничение браузера:** если PWA раздаётся с публичного HTTPS-хоста, для доступа к локальному серверу (`http://127.0.0.1:8080`) нужны два условия: сервер должен отправлять `Access-Control-Allow-Private-Network: true` (pysim-otaman-server ≥ 1.6.1 делает это автоматически), и браузеру должно быть разрешено обращаться к локальной сети — в Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети → разрешить сайт (или подтвердить запрос). Без разрешения браузера запрос к `127.0.0.1` блокируется ещё до отправки preflight.
> **Ограничение браузера:** если PWA раздаётся с публичного HTTPS-хоста, для доступа к локальному серверу (`http://127.0.0.1:8080`) нужны два условия: сервер должен отправлять `Access-Control-Allow-Private-Network: true` (pysim-simple-server ≥ 1.6.1 делает это автоматически), и браузеру должно быть разрешено обращаться к локальной сети — в Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети → разрешить сайт (или подтвердить запрос). Без разрешения браузера запрос к `127.0.0.1` блокируется ещё до отправки preflight.
### Файловый менеджер
@@ -575,7 +575,7 @@ Delivery PoR (SPI2 `01`) проще — карта возвращает PoR на
### Скрипты
Подвкладка **Scripts** управляет именованными списками APDU, которые хранятся в `localStorage` (`otaman_scripts`) и передаются серверу при старте слушателя. Каждый список — последовательность hex C-APDU, по одной в строке (допускаются комментарии `#`/`;`). Кнопка **New** создаёт список из шаблона:
Подвкладка **Scripts** управляет именованными списками APDU, которые хранятся в `localStorage` (`simple_scripts`) и передаются серверу при старте слушателя. Каждый список — последовательность hex C-APDU, по одной в строке (допускаются комментарии `#`/`;`). Кнопка **New** создаёт список из шаблона:
| Шаблон | Что строит |
|---|---|
@@ -588,7 +588,7 @@ Delivery PoR (SPI2 `01`) проще — карта возвращает PoR на
## PWA
OTAMan — Progressive Web App. Можно установить для offline-использования через кнопку **INSTALL PWA** или через браузер.
SIMple — Progressive Web App. Можно установить для offline-использования через кнопку **INSTALL PWA** или через браузер.
- Service worker кеширует все ресурсы при первом посещении
- Иконки 192×192 и 512×512
@@ -603,7 +603,7 @@ OTAMan — Progressive Web App. Можно установить для offline-
## Совместимость версий
| PWA (OTAMan) | Сервер | Статус |
| PWA (SIMple) | Сервер | Статус |
|---|---|---|
| 1.x.x | 1.x.x | ✅ Совместимы |
| 1.x.x | 0.x.x | ❌ Сервер устарел |
@@ -613,7 +613,7 @@ PWA проверяет версию сервера при подключении
---
## Сервер (pysim-otaman-server)
## Сервер (pysim-simple-server)
Встроенный Python-сервер оборачивает [pySim](https://osmocom.org/projects/pysim/wiki) и раздаёт как PWA (из `frontend/`), так и JSON API по `/api/*`.
@@ -639,7 +639,7 @@ python3 -m venv .venv
source .venv/bin/activate # Linux/macOS (Windows: .venv\Scripts\activate)
pip install git+https://github.com/osmocom/pysim.git
pip install -e . # editable — раздаёт frontend/ из исходного дерева
pysim-otaman-server --http-port 8080
pysim-simple-server --http-port 8080
```
### Параметры CLI
+2 -2
View File
@@ -1,4 +1,4 @@
# pysim-otaman-server — HTTP API reference
# pysim-simple-server — HTTP API reference
The server exposes a JSON HTTP API under `/api/*`. All responses carry
`Access-Control-Allow-Origin: *` (plus `Access-Control-Allow-Private-Network: true`
@@ -6,7 +6,7 @@ on the preflight), so the API is reachable from a separately-hosted PWA.
## Version compatibility
| Server | PWA (OTAMan) | Status |
| Server | PWA (SIMple) | Status |
|--------|-------------|--------|
| 1.x.x | 1.x.x | ✅ Compatible |
| 0.x.x | 1.x.x | ❌ Outdated — update server |
+1 -1
View File
@@ -5,7 +5,7 @@ Purpose: record **every attempted configuration and its outcome**, so the same
variations are not repeated. Add rows as tests are run; keep the confirmed
rules section current.
Setup: `pysim_otaman_server` with a PC/SC reader, the PWA SCP81 tab (or
Setup: `pysim_simple_server` with a PC/SC reader, the PWA SCP81 tab (or
`POST /api/scp81/bip`), the card triggered by its SMS-PP push / the Location
status event. Server log at `GET /api/scp81/log`, script state at
`GET /api/scp81/script`, proactive history at `GET /api/proactive-log`.
+16 -16
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OTAMan — Справка</title>
<title>SIMple — Справка</title>
<link rel="stylesheet" href="style.css">
</head>
<body class="bg-neutral-50 dark:bg-slate-900 text-gray-800 dark:text-slate-200">
@@ -16,12 +16,12 @@
</aside>
<main class="flex-1 min-w-0 px-6 py-8">
<h1 class="text-2xl font-bold text-heading mb-6">OTAMan <span class="text-xs text-gray-400 dark:text-slate-500 ml-2">Документация</span></h1>
<h1 class="text-2xl font-bold text-heading mb-6">SIMple <span class="text-xs text-gray-400 dark:text-slate-500 ml-2">Документация</span></h1>
<section class="mb-10">
<h2 id="overview" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">1. Обзор</h2>
<p class="mb-3">OTAMan — Progressive Web App (PWA) для построения APDU-команд, сборки защищённых пакетов SCP80, просмотра меню SIM Toolkit (STK) и симуляции реальной сетевой среды для тестирования SIM/USIM/UICC-карт через PC/SC-ридер.</p>
<p class="mb-3">Приложение — один статический файл <code class="font-mono text-sm">index.html</code>. Все вычисления выполняются в браузере; доступ к карте — через локальный HTTP-сервер (<code class="font-mono text-sm">pysim-otaman-server</code>), оборачивающий библиотеку <a href="https://osmocom.org/projects/pysim" class="text-blue-600 dark:text-blue-400 hover:underline">pySim</a>.</p>
<pre class="font-mono text-xs bg-gray-100 dark:bg-slate-800 rounded p-3 mb-3">Браузер (OTAMan PWA) &rarr; HTTP :8080 &rarr; pysim-otaman-server &rarr; pySim &rarr; PC/SC &rarr; ридер &rarr; UICC/SIM</pre>
<p class="mb-3">SIMple — Progressive Web App (PWA) для построения APDU-команд, сборки защищённых пакетов SCP80, просмотра меню SIM Toolkit (STK) и симуляции реальной сетевой среды для тестирования SIM/USIM/UICC-карт через PC/SC-ридер.</p>
<p class="mb-3">Приложение — один статический файл <code class="font-mono text-sm">index.html</code>. Все вычисления выполняются в браузере; доступ к карте — через локальный HTTP-сервер (<code class="font-mono text-sm">pysim-simple-server</code>), оборачивающий библиотеку <a href="https://osmocom.org/projects/pysim" class="text-blue-600 dark:text-blue-400 hover:underline">pySim</a>.</p>
<pre class="font-mono text-xs bg-gray-100 dark:bg-slate-800 rounded p-3 mb-3">Браузер (SIMple PWA) &rarr; HTTP :8080 &rarr; pysim-simple-server &rarr; pySim &rarr; PC/SC &rarr; ридер &rarr; UICC/SIM</pre>
<p class="mb-3">Стандарты, на которые опирается приложение:</p>
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
<li>ETSI TS 102 221 — интерфейс UICC-терминал (CLA 00, файлы USIM)</li>
@@ -331,7 +331,7 @@
<p class="mb-2"><strong>Сценарий</strong> выбирает список команд, отдаваемый в сессии — <strong>Нет</strong> (оставить настроенный на сервере скрипт) или один из скриптов подвкладки <strong>&laquo;Скрипты&raquo;</strong>; выбранный список передаётся серверу при старте слушателя.</p>
<p class="text-sm mb-3">Строка состояния показывает слушатель, согласованный идентификатор и активные каналы (байты in/out). Панель <strong>Script results (R-APDUs)</strong> перечисляет каждый отданный C-APDU с его R-APDU/SW и прогрессом <em>done/total</em>; журнал <strong>HTTP OTA log</strong> фиксирует OPEN/CLOSE CHANNEL, SEND/RECEIVE DATA и каждый шаг TLS/HTTP/скрипта (<code class="font-mono text-sm">tls-handshake</code>, <code class="font-mono text-sm">tls-request</code>, <code class="font-mono text-sm">script-send</code>, <code class="font-mono text-sm">script-rapdu</code>, <code class="font-mono text-sm">script-page</code>, <code class="font-mono text-sm">script-done</code>, <code class="font-mono text-sm">data-available</code>, <code class="font-mono text-sm">peer-close</code>), кнопка <strong>Clear</strong> очищает его.</p>
<h3 id="scp81-scripts" class="text-lg font-medium mb-2">5.2 Скрипты</h3>
<p class="text-sm mb-2">Именованные списки APDU хранятся локально (<code class="font-mono text-sm">otaman_scripts</code>) и передаются серверу при старте слушателя. Каждый список — последовательность hex C-APDU (по одной в строке; допускаются комментарии <code class="font-mono text-sm">#</code>/<code class="font-mono text-sm">;</code>). Кнопка <strong>Новый</strong> создаёт список из шаблона:</p>
<p class="text-sm mb-2">Именованные списки APDU хранятся локально (<code class="font-mono text-sm">simple_scripts</code>) и передаются серверу при старте слушателя. Каждый список — последовательность hex C-APDU (по одной в строке; допускаются комментарии <code class="font-mono text-sm">#</code>/<code class="font-mono text-sm">;</code>). Кнопка <strong>Новый</strong> создаёт список из шаблона:</p>
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
<li><strong>Empty</strong> — начать с пустого списка.</li>
<li><strong>Explore</strong> — эталонная административная последовательность (<code class="font-mono text-sm">GET DATA FF21</code>, листинги GET STATUS ISD/ELF/приложений, <code class="font-mono text-sm">GET DATA 0085</code>); длинные листинги автоматически продолжаются страницами <code class="font-mono text-sm">SW 6310/CAFE</code>.</li>
@@ -343,7 +343,7 @@
<section class="mb-10">
<h2 id="card-reader" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">6. Вкладка &laquo;Картридер&raquo; (pySim)</h2>
<p class="mb-3">Подключение к локальному <a href="https://github.com/anttro/otaman" class="text-blue-600 dark:text-blue-400 hover:underline">pysim-otaman-server</a> для работы с картой: введите URL сервера (по умолчанию <code class="font-mono text-sm">http://127.0.0.1:8080</code>) и нажмите <strong>Подключиться</strong>. Область статуса показывает состояние ридера/карты, а <strong>Подключить карту</strong> (пере)инициализирует карту после вставки. Подвкладки: <strong>Файловый менеджер</strong>, <strong>Командная строка pySim</strong> и <strong>Отправка APDU</strong>. <strong>&laquo;Профайлер&raquo;</strong> и <strong>&laquo;Симулятор телефона&raquo;</strong> — отдельные вкладки верхнего уровня.</p>
<p class="mb-3">Подключение к локальному <a href="https://github.com/anttro/simple" class="text-blue-600 dark:text-blue-400 hover:underline">pysim-simple-server</a> для работы с картой: введите URL сервера (по умолчанию <code class="font-mono text-sm">http://127.0.0.1:8080</code>) и нажмите <strong>Подключиться</strong>. Область статуса показывает состояние ридера/карты, а <strong>Подключить карту</strong> (пере)инициализирует карту после вставки. Подвкладки: <strong>Файловый менеджер</strong>, <strong>Командная строка pySim</strong> и <strong>Отправка APDU</strong>. <strong>&laquo;Профайлер&raquo;</strong> и <strong>&laquo;Симулятор телефона&raquo;</strong> — отдельные вкладки верхнего уровня.</p>
<h3 id="file-manager" class="text-lg font-medium mb-2">6.1 Файловый менеджер</h3>
<p class="text-sm mb-2">Дерево файловой системы отображается слева; выбор файла открывает панель деталей справа. Элементы сгруппированы: DF выше EF, сортировка по <strong>FID</strong> или символьному <strong>имени</strong> (пиллы и кнопка <strong>«Проверить все файлы»</strong> закреплены над прокручиваемым деревом; выбор сохраняется в <code class="font-mono text-sm">localStorage</code>). При выборе файла над содержимым также показываются FID, тип файла, размер / структура записей и декодированный FCI.</p>
@@ -478,7 +478,7 @@
<section class="mb-10">
<h2 id="server" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">9. Установка сервера</h2>
<p class="mb-3">Для работы с картой (вкладка &laquo;Картридер&raquo;, &laquo;Симулятор телефона&raquo;, доставка OTA) нужен локальный <a href="https://github.com/anttro/otaman" class="text-blue-600 dark:text-blue-400 hover:underline">pysim-otaman-server</a> — встроенный в OTAMan HTTP-сервер, оборачивающий pySim, работающий с ридером через PC/SC или serial и раздающий сам PWA (откройте <code class="font-mono text-sm">http://127.0.0.1:8080</code>).</p>
<p class="mb-3">Для работы с картой (вкладка &laquo;Картридер&raquo;, &laquo;Симулятор телефона&raquo;, доставка OTA) нужен локальный <a href="https://github.com/anttro/simple" class="text-blue-600 dark:text-blue-400 hover:underline">pysim-simple-server</a> — встроенный в SIMple HTTP-сервер, оборачивающий pySim, работающий с ридером через PC/SC или serial и раздающий сам PWA (откройте <code class="font-mono text-sm">http://127.0.0.1:8080</code>).</p>
<h3 id="prerequisites" class="text-lg font-medium mb-2">9.1 Требования</h3>
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
@@ -489,15 +489,15 @@
</ul>
<h3 id="quickstart-linux" class="text-lg font-medium mb-2">9.2 Быстрый старт — Linux / macOS</h3>
<pre class="font-mono text-xs bg-gray-100 dark:bg-slate-800 rounded p-3 mb-3">git clone https://github.com/anttro/otaman.git
cd otaman
<pre class="font-mono text-xs bg-gray-100 dark:bg-slate-800 rounded p-3 mb-3">git clone https://github.com/anttro/simple.git
cd simple
chmod +x setup.sh start.sh
./setup.sh # создаёт .venv, устанавливает pysim + сервер (однократно)
./start.sh # запускает сервер (PWA + API, автоопределение ридера)</pre>
<h3 id="quickstart-windows" class="text-lg font-medium mb-2">9.3 Быстрый старт — Windows</h3>
<pre class="font-mono text-xs bg-gray-100 dark:bg-slate-800 rounded p-3 mb-3">git clone https://github.com/anttro/otaman.git
cd otaman
<pre class="font-mono text-xs bg-gray-100 dark:bg-slate-800 rounded p-3 mb-3">git clone https://github.com/anttro/simple.git
cd simple
setup.bat # создаёт .venv, устанавливает pysim + сервер (однократно)
start.bat # запускает сервер (PWA + API)</pre>
@@ -528,19 +528,19 @@ source .venv/bin/activate # Linux/macOS
# Установить pysim
pip install git+https://github.com/osmocom/pysim.git
# Установить pysim-otaman-server (editable — раздаёт встроенный PWA)
# Установить pysim-simple-server (editable — раздаёт встроенный PWA)
pip install -e .
# Запустить сервер (PWA + API)
pysim-otaman-server --http-port 8080</pre>
pysim-simple-server --http-port 8080</pre>
<p class="text-sm mb-3">Подключите PC/SC-ридер с SIM-картой и откройте <code class="font-mono text-sm">http://127.0.0.1:8080</code> — PWA и API на одном origin, поэтому CORS не требуется.</p>
<p class="text-sm mb-3">Если PWA раздаётся с публичного HTTPS-хоста (например, <code class="font-mono text-sm">https://otaman.example.com</code>), для доступа к локальному серверу карт нужны два условия: (1) сервер отвечает на preflight заголовком <code class="font-mono text-sm">Access-Control-Allow-Private-Network: true</code> (pysim-otaman-server &ge; 1.6.1 делает это автоматически), и (2) браузеру должно быть разрешено обращаться к локальной сети &mdash; в Chrome/Edge/Vivaldi: Настройки сайта &rarr; Доступ к локальной сети &rarr; разрешить сайт (или подтвердить запрос). Без разрешения браузера запрос к <code class="font-mono text-sm">127.0.0.1</code> блокируется ещё до отправки preflight.</p>
<p class="text-sm mb-3">Если PWA раздаётся с публичного HTTPS-хоста (например, <code class="font-mono text-sm">https://simple.example.com</code>), для доступа к локальному серверу карт нужны два условия: (1) сервер отвечает на preflight заголовком <code class="font-mono text-sm">Access-Control-Allow-Private-Network: true</code> (pysim-simple-server &ge; 1.6.1 делает это автоматически), и (2) браузеру должно быть разрешено обращаться к локальной сети &mdash; в Chrome/Edge/Vivaldi: Настройки сайта &rarr; Доступ к локальной сети &rarr; разрешить сайт (или подтвердить запрос). Без разрешения браузера запрос к <code class="font-mono text-sm">127.0.0.1</code> блокируется ещё до отправки preflight.</p>
<section class="mb-10">
<h2 id="compatibility" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">10. Совместимость версий</h2>
<table class="w-full text-sm mb-3 border-collapse">
<thead><tr class="border-b border-gray-300 dark:border-slate-700"><th class="text-left py-1 px-2">PWA (OTAMan)</th><th class="text-left py-1 px-2">Сервер</th><th class="text-left py-1 px-2">Статус</th></tr></thead>
<thead><tr class="border-b border-gray-300 dark:border-slate-700"><th class="text-left py-1 px-2">PWA (SIMple)</th><th class="text-left py-1 px-2">Сервер</th><th class="text-left py-1 px-2">Статус</th></tr></thead>
<tbody>
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2 font-mono">1.x.x</td><td class="py-1 px-2 font-mono">1.x.x</td><td class="py-1 px-2">&#9989; Совместимы</td></tr>
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2 font-mono">1.x.x</td><td class="py-1 px-2 font-mono">0.x.x</td><td class="py-1 px-2">&#10060; Устарел &mdash; обновите сервер</td></tr>
+16 -16
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OTAMan — Help</title>
<title>SIMple — Help</title>
<link rel="stylesheet" href="style.css">
</head>
<body class="bg-neutral-50 dark:bg-slate-900 text-gray-800 dark:text-slate-200">
@@ -16,12 +16,12 @@
</aside>
<main class="flex-1 min-w-0 px-6 py-8">
<h1 class="text-2xl font-bold text-heading mb-6">OTAMan <span class="text-xs text-gray-400 dark:text-slate-500 ml-2">Documentation</span></h1>
<h1 class="text-2xl font-bold text-heading mb-6">SIMple <span class="text-xs text-gray-400 dark:text-slate-500 ml-2">Documentation</span></h1>
<section class="mb-10">
<h2 id="overview" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">1. Overview</h2>
<p class="mb-3">OTAMan is a Progressive Web App (PWA) for building APDU commands, assembling SCP80 secured packets, browsing the SIM Toolkit (STK) menu, and simulating a real network environment against a SIM/USIM/UICC card via a PC/SC reader.</p>
<p class="mb-3">The application is a single static <code class="font-mono text-sm">index.html</code> file. All computation runs in the browser; the card is accessed through a local HTTP server (<code class="font-mono text-sm">pysim-otaman-server</code>) that wraps the <a href="https://osmocom.org/projects/pysim" class="text-blue-600 dark:text-blue-400 hover:underline">pySim</a> library.</p>
<pre class="font-mono text-xs bg-gray-100 dark:bg-slate-800 rounded p-3 mb-3">Browser (OTAMan PWA) &rarr; HTTP :8080 &rarr; pysim-otaman-server &rarr; pySim &rarr; PC/SC &rarr; card reader &rarr; UICC/SIM</pre>
<p class="mb-3">SIMple is a Progressive Web App (PWA) for building APDU commands, assembling SCP80 secured packets, browsing the SIM Toolkit (STK) menu, and simulating a real network environment against a SIM/USIM/UICC card via a PC/SC reader.</p>
<p class="mb-3">The application is a single static <code class="font-mono text-sm">index.html</code> file. All computation runs in the browser; the card is accessed through a local HTTP server (<code class="font-mono text-sm">pysim-simple-server</code>) that wraps the <a href="https://osmocom.org/projects/pysim" class="text-blue-600 dark:text-blue-400 hover:underline">pySim</a> library.</p>
<pre class="font-mono text-xs bg-gray-100 dark:bg-slate-800 rounded p-3 mb-3">Browser (SIMple PWA) &rarr; HTTP :8080 &rarr; pysim-simple-server &rarr; pySim &rarr; PC/SC &rarr; card reader &rarr; UICC/SIM</pre>
<p class="mb-3">Standards referenced across the application:</p>
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
<li>ETSI TS 102 221 — UICC-Terminal interface (CLA 00, USIM files)</li>
@@ -331,7 +331,7 @@
<p class="mb-2"><strong>Script</strong> selects the command list served over the session — <strong>None</strong> (leave the server's configured script) or one of the scripts from the <strong>Scripts</strong> pill; the chosen list is sent to the server when the listener starts.</p>
<p class="text-sm mb-3">The state line shows the listener, the negotiated identity and live channels (bytes in/out). <strong>Script results (R-APDUs)</strong> lists each served C-APDU with its R-APDU/SW and a <em>done/total</em> progress; the <strong>HTTP OTA log</strong> records OPEN/CLOSE CHANNEL, SEND/RECEIVE DATA and every TLS/HTTP/script step (<code class="font-mono text-sm">tls-handshake</code>, <code class="font-mono text-sm">tls-request</code>, <code class="font-mono text-sm">script-send</code>, <code class="font-mono text-sm">script-rapdu</code>, <code class="font-mono text-sm">script-page</code>, <code class="font-mono text-sm">script-done</code>, <code class="font-mono text-sm">data-available</code>, <code class="font-mono text-sm">peer-close</code>), with <strong>Clear</strong>.</p>
<h3 id="scp81-scripts" class="text-lg font-medium mb-2">5.2 Scripts</h3>
<p class="text-sm mb-2">Named APDU lists stored locally (<code class="font-mono text-sm">otaman_scripts</code>) and sent to the server when a listener starts. Each list is a sequence of hex C-APDUs (one per line; <code class="font-mono text-sm">#</code>/<code class="font-mono text-sm">;</code> comments allowed). <strong>New</strong> creates one from a template:</p>
<p class="text-sm mb-2">Named APDU lists stored locally (<code class="font-mono text-sm">simple_scripts</code>) and sent to the server when a listener starts. Each list is a sequence of hex C-APDUs (one per line; <code class="font-mono text-sm">#</code>/<code class="font-mono text-sm">;</code> comments allowed). <strong>New</strong> creates one from a template:</p>
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
<li><strong>Empty</strong> — start from scratch.</li>
<li><strong>Explore</strong> — the reference administration sequence (<code class="font-mono text-sm">GET DATA FF21</code>, GET STATUS ISD/ELF/application listings, <code class="font-mono text-sm">GET DATA 0085</code>); long listings auto-continue through the <code class="font-mono text-sm">SW 6310/CAFE</code> pages.</li>
@@ -343,7 +343,7 @@
<section class="mb-10">
<h2 id="card-reader" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">6. Card reader (pySim) tab</h2>
<p class="mb-3">Connects to a local <a href="https://github.com/anttro/otaman" class="text-blue-600 dark:text-blue-400 hover:underline">pysim-otaman-server</a> for live card operations: enter the server URL (default <code class="font-mono text-sm">http://127.0.0.1:8080</code>) and press <strong>Connect</strong>. The status area shows the reader/card state, and <strong>Equip card</strong> (re)initializes the card after insertion. Sub-tabs: <strong>File manager</strong>, <strong>pySim command line</strong>, and <strong>Raw APDU</strong>. The <strong>Profiler</strong> and <strong>Phone simulator</strong> are separate top-level tabs.</p>
<p class="mb-3">Connects to a local <a href="https://github.com/anttro/simple" class="text-blue-600 dark:text-blue-400 hover:underline">pysim-simple-server</a> for live card operations: enter the server URL (default <code class="font-mono text-sm">http://127.0.0.1:8080</code>) and press <strong>Connect</strong>. The status area shows the reader/card state, and <strong>Equip card</strong> (re)initializes the card after insertion. Sub-tabs: <strong>File manager</strong>, <strong>pySim command line</strong>, and <strong>Raw APDU</strong>. The <strong>Profiler</strong> and <strong>Phone simulator</strong> are separate top-level tabs.</p>
<h3 id="file-manager" class="text-lg font-medium mb-2">6.1 File manager</h3>
<p class="text-sm mb-2">The file system tree is displayed on the left; selecting a file opens its detail pane on the right. Entries are grouped with DFs above EFs and sorted by <strong>FID</strong> or symbolic <strong>Name</strong> (pills pinned above the scrolling tree together with <strong>Probe all files</strong>; the choice is remembered in <code class="font-mono text-sm">localStorage</code>). Selecting a file also shows its FID, file type, size / record layout and the decoded FCI above the content pane.</p>
@@ -478,7 +478,7 @@
<section class="mb-10">
<h2 id="server" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">9. Server installation</h2>
<p class="mb-3">Live card operations (Card reader tab, Phone simulator, OTA delivery) require the local <a href="https://github.com/anttro/otaman" class="text-blue-600 dark:text-blue-400 hover:underline">pysim-otaman-server</a> — a small HTTP server bundled with OTAMan that wraps pySim, talks to the reader over PC/SC or serial, and also serves the PWA itself (open <code class="font-mono text-sm">http://127.0.0.1:8080</code>).</p>
<p class="mb-3">Live card operations (Card reader tab, Phone simulator, OTA delivery) require the local <a href="https://github.com/anttro/simple" class="text-blue-600 dark:text-blue-400 hover:underline">pysim-simple-server</a> — a small HTTP server bundled with SIMple that wraps pySim, talks to the reader over PC/SC or serial, and also serves the PWA itself (open <code class="font-mono text-sm">http://127.0.0.1:8080</code>).</p>
<h3 id="prerequisites" class="text-lg font-medium mb-2">9.1 Prerequisites</h3>
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
@@ -489,15 +489,15 @@
</ul>
<h3 id="quickstart-linux" class="text-lg font-medium mb-2">9.2 Quick start — Linux / macOS</h3>
<pre class="font-mono text-xs bg-gray-100 dark:bg-slate-800 rounded p-3 mb-3">git clone https://github.com/anttro/otaman.git
cd otaman
<pre class="font-mono text-xs bg-gray-100 dark:bg-slate-800 rounded p-3 mb-3">git clone https://github.com/anttro/simple.git
cd simple
chmod +x setup.sh start.sh
./setup.sh # creates .venv, installs pysim + server (run once)
./start.sh # starts the server (serves PWA + API, auto-detects reader)</pre>
<h3 id="quickstart-windows" class="text-lg font-medium mb-2">9.3 Quick start — Windows</h3>
<pre class="font-mono text-xs bg-gray-100 dark:bg-slate-800 rounded p-3 mb-3">git clone https://github.com/anttro/otaman.git
cd otaman
<pre class="font-mono text-xs bg-gray-100 dark:bg-slate-800 rounded p-3 mb-3">git clone https://github.com/anttro/simple.git
cd simple
setup.bat # creates .venv, installs pysim + server (run once)
start.bat # starts the server (serves PWA + API)</pre>
@@ -528,19 +528,19 @@ source .venv/bin/activate # Linux/macOS
# Install pysim
pip install git+https://github.com/osmocom/pysim.git
# Install pysim-otaman-server (editable, so it serves the bundled PWA)
# Install pysim-simple-server (editable, so it serves the bundled PWA)
pip install -e .
# Start the server (serves PWA + API)
pysim-otaman-server --http-port 8080</pre>
pysim-simple-server --http-port 8080</pre>
<p class="text-sm mb-3">Connect a PC/SC reader with a SIM card, then open <code class="font-mono text-sm">http://127.0.0.1:8080</code> — the PWA and API share one origin, so no CORS is involved.</p>
<p class="text-sm mb-3">If the PWA is served from a public HTTPS host (e.g. <code class="font-mono text-sm">https://otaman.example.com</code>), two things are required to reach a local card server: (1) the server must answer the preflight with <code class="font-mono text-sm">Access-Control-Allow-Private-Network: true</code> (pysim-otaman-server &ge; 1.6.1 does this automatically), and (2) the browser must be allowed to access the local network &mdash; in Chrome/Edge/Vivaldi: Site settings &rarr; Local network access &rarr; allow the site (or accept the permission prompt). Without the browser permission, the request to <code class="font-mono text-sm">127.0.0.1</code> is blocked before any preflight is sent.</p>
<p class="text-sm mb-3">If the PWA is served from a public HTTPS host (e.g. <code class="font-mono text-sm">https://simple.example.com</code>), two things are required to reach a local card server: (1) the server must answer the preflight with <code class="font-mono text-sm">Access-Control-Allow-Private-Network: true</code> (pysim-simple-server &ge; 1.6.1 does this automatically), and (2) the browser must be allowed to access the local network &mdash; in Chrome/Edge/Vivaldi: Site settings &rarr; Local network access &rarr; allow the site (or accept the permission prompt). Without the browser permission, the request to <code class="font-mono text-sm">127.0.0.1</code> is blocked before any preflight is sent.</p>
<section class="mb-10">
<h2 id="compatibility" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">10. Version compatibility</h2>
<table class="w-full text-sm mb-3 border-collapse">
<thead><tr class="border-b border-gray-300 dark:border-slate-700"><th class="text-left py-1 px-2">PWA (OTAMan)</th><th class="text-left py-1 px-2">Server</th><th class="text-left py-1 px-2">Status</th></tr></thead>
<thead><tr class="border-b border-gray-300 dark:border-slate-700"><th class="text-left py-1 px-2">PWA (SIMple)</th><th class="text-left py-1 px-2">Server</th><th class="text-left py-1 px-2">Status</th></tr></thead>
<tbody>
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2 font-mono">1.x.x</td><td class="py-1 px-2 font-mono">1.x.x</td><td class="py-1 px-2">&#9989; Compatible</td></tr>
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2 font-mono">1.x.x</td><td class="py-1 px-2 font-mono">0.x.x</td><td class="py-1 px-2">&#10060; Outdated &mdash; update server</td></tr>
+28 -28
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OTAMan</title>
<title>SIMple</title>
<link rel="stylesheet" href="style.css">
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="icon" type="image/png" href="sim.png" sizes="462x462">
@@ -18,7 +18,7 @@
<div class="max-w-7xl mx-auto px-6 py-2">
<div class="flex items-center justify-between mb-3">
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.2.21</span></h1>
<h1 class="text-2xl font-bold text-heading">SIMple <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.3.0</span></h1>
<div class="flex items-center gap-4">
<span id="state-indicator" class="flex items-center select-none" style="cursor:default" title="Connecting...">
<span id="state-indicator-dot" class="text-xs text-gray-400" title="Connecting..."></span>
@@ -26,7 +26,7 @@
<span id="state-indicator-adm" class="hidden ml-0.5 text-xs font-mono" title=""></span>
</span>
<button id="install-btn" class="px-2 py-1 text-xs rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700" style="display:none">INSTALL PWA [for offline use]</button>
<a href="https://github.com/anttro/otaman" target="_blank" class="text-xs text-gray-400 hover:text-gray-600 dark:text-slate-500 dark:hover:text-slate-300">github</a>
<a href="https://github.com/anttro/simple" target="_blank" class="text-xs text-gray-400 hover:text-gray-600 dark:text-slate-500 dark:hover:text-slate-300">github</a>
<a id="help-link" href="help.html" target="_blank" rel="noopener" class="text-xs text-gray-400 hover:text-gray-600 dark:text-slate-500 dark:hover:text-slate-300" data-l10n="help">help</a>
<button onclick="toggleLang()" id="lang-btn" class="px-2 py-1 text-xs rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700">EN</button>
<button onclick="toggleTheme()" id="theme-btn" class="px-2 py-1 text-sm rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700">🌙</button>
@@ -815,8 +815,8 @@
</div>
</div>
<div id="pysim-connect-info" class="mb-3 text-xs text-gray-500 dark:text-slate-400 leading-relaxed">
<p data-l10n="To access local card reader, you need pysim-otaman-server running locally.">To access local card reader, you need pysim-otaman-server running locally.</p>
<p><span data-l10n="To connect, install">To connect, install</span> <a href="https://github.com/anttro/otaman" target="_blank" class="text-blue-600 dark:text-blue-400 underline">pysim-otaman-server</a>: <code class="font-mono bg-gray-100 dark:bg-slate-700 px-1 rounded">git clone https://github.com/anttro/otaman.git</code> <span data-l10n="and start it.">and start it.</span><br><span data-l10n="Refer to pysim-otaman-server README files for details.">Refer to pysim-otaman-server README files for details.</span> <span data-l10n="Click">Click</span> <b data-l10n="Connect">Connect</b> <span data-l10n="when pysim-otaman-server is ready">when pysim-otaman-server is ready</span></p>
<p data-l10n="To access local card reader, you need pysim-simple-server running locally.">To access local card reader, you need pysim-simple-server running locally.</p>
<p><span data-l10n="To connect, install">To connect, install</span> <a href="https://github.com/anttro/simple" target="_blank" class="text-blue-600 dark:text-blue-400 underline">pysim-simple-server</a>: <code class="font-mono bg-gray-100 dark:bg-slate-700 px-1 rounded">git clone https://github.com/anttro/simple.git</code> <span data-l10n="and start it.">and start it.</span><br><span data-l10n="Refer to pysim-simple-server README files for details.">Refer to pysim-simple-server README files for details.</span> <span data-l10n="Click">Click</span> <b data-l10n="Connect">Connect</b> <span data-l10n="when pysim-simple-server is ready">when pysim-simple-server is ready</span></p>
<div id="pysim-connect-msg" class="text-xs"></div>
</div>
<div id="pysim-connected-row" class="mb-3 hidden">
@@ -4679,7 +4679,7 @@ function decodeResponse() {
// ===== pysim integration =====
// Default to a local server on 127.0.0.1:8080. If this PWA is served by
// pysim-otaman-server itself (same origin), detect the co-located API once
// pysim-simple-server itself (same origin), detect the co-located API once
// and switch to a relative base; otherwise keep the default and wait for the
// user to point at a server.
let pysimBase = 'http://127.0.0.1:8080';
@@ -4913,13 +4913,13 @@ async function pysimConnect() {
_pysimServerAvailable = true;
const sv = (ver.version || '').split('.').map(Number);
if (sv[0] < 1 || (sv[0] === 1 && sv[1] < 0)) {
msgEl.innerHTML = '<span class="text-red-500">Server version ' + esc(ver.version) + ' is outdated. Please update pysim-otaman-server.</span>';
msgEl.innerHTML = '<span class="text-red-500">Server version ' + esc(ver.version) + ' is outdated. Please update pysim-simple-server.</span>';
btn.textContent = t('Connect');
btn.disabled = false;
return;
}
if (sv[0] > 1) {
msgEl.innerHTML = '<span class="text-yellow-500">Warning: server version ' + esc(ver.version) + ' is newer than expected. Update OTAMan for full compatibility.</span>';
msgEl.innerHTML = '<span class="text-yellow-500">Warning: server version ' + esc(ver.version) + ' is newer than expected. Update SIMple for full compatibility.</span>';
}
const status = await pysimFetch('/api/status');
if (!status.app_ready) {
@@ -5947,7 +5947,7 @@ let _cardsEditIdx = null;
function cardsLoad() {
try {
const d = localStorage.getItem('otaman_cards');
const d = localStorage.getItem('simple_cards');
cards = d ? JSON.parse(d) : [];
} catch (e) { cards = []; }
cardsRebuildSelect();
@@ -5955,7 +5955,7 @@ function cardsLoad() {
}
function cardsSave() {
localStorage.setItem('otaman_cards', JSON.stringify(cards));
localStorage.setItem('simple_cards', JSON.stringify(cards));
cardsRebuildSelect();
cardsRender();
scp81PushPskMap();
@@ -6225,7 +6225,7 @@ function downloadJson(name, obj) {
}
function cardsExportFile() {
downloadJson('otaman-cards.json', cards);
downloadJson('simple-cards.json', cards);
}
async function cardsImport(text) {
@@ -6511,7 +6511,7 @@ function pysimCustomRefreshTree() {
pysimFsRenderTree();
}
let pysimFsSort = localStorage.getItem('otaman_fs_sort') === 'name' ? 'name' : 'fid';
let pysimFsSort = localStorage.getItem('simple_fs_sort') === 'name' ? 'name' : 'fid';
function pysimFsSortChildren(children, mode) {
return (children || []).slice().sort((a, b) => {
@@ -6536,7 +6536,7 @@ function pysimFsSortChildren(children, mode) {
function pysimFsSetSort(mode) {
pysimFsSort = mode === 'name' ? 'name' : 'fid';
localStorage.setItem('otaman_fs_sort', pysimFsSort);
localStorage.setItem('simple_fs_sort', pysimFsSort);
document.querySelectorAll('.pysim-fs-sort-pill').forEach(p => {
const active = p.dataset.fsSort === pysimFsSort;
p.classList.toggle('bg-blue-600', active);
@@ -8036,7 +8036,7 @@ function scp81OptionsBadge() {
function scp81OptionsLoad() {
let saved = {};
try { saved = JSON.parse(localStorage.getItem('otaman_scp81_opts') || '{}') || {}; } catch (e) { saved = {}; }
try { saved = JSON.parse(localStorage.getItem('simple_scp81_opts') || '{}') || {}; } catch (e) { saved = {}; }
const opt = Object.assign({}, SCP81_OPT_DEFAULTS, saved);
if (saved.nextUriOn === undefined) opt.nextUriOn = (opt.nextUri || '') !== '';
const set = (id, v) => { const el = document.getElementById(id); if (el) el.value = v; };
@@ -8057,13 +8057,13 @@ function scp81OptionsLoad() {
}
function scp81OptionsPersist() {
localStorage.setItem('otaman_scp81_opts', JSON.stringify(scp81OptionsState()));
localStorage.setItem('simple_scp81_opts', JSON.stringify(scp81OptionsState()));
scp81OptionsSyncDisabled();
scp81OptionsBadge();
}
function scp81OptionsReset() {
localStorage.removeItem('otaman_scp81_opts');
localStorage.removeItem('simple_scp81_opts');
scp81OptionsLoad();
}
@@ -8602,14 +8602,14 @@ let _scriptsNewKind = 'empty';
function scriptsLoad() {
try {
const d = localStorage.getItem('otaman_scripts');
const d = localStorage.getItem('simple_scripts');
scripts = d ? JSON.parse(d) : [];
} catch (e) { scripts = []; }
scriptsRebuildSelect();
}
function scriptsPersist() {
localStorage.setItem('otaman_scripts', JSON.stringify(scripts));
localStorage.setItem('simple_scripts', JSON.stringify(scripts));
scriptsRebuildSelect();
scriptsRender();
}
@@ -9444,7 +9444,7 @@ function pysimCustomExport() {
}
function pysimCustomExportFile() {
downloadJson('otaman-custom-files.json', pysimCustomFiles);
downloadJson('simple-custom-files.json', pysimCustomFiles);
}
async function pysimCustomImport(text) {
@@ -9529,25 +9529,25 @@ const PROFILER_MASK_PREFIX4_FIDS = {
function profilerLoad() {
try {
const d = localStorage.getItem('otaman_profiles');
const d = localStorage.getItem('simple_profiles');
profiles = d ? JSON.parse(d) : [];
} catch (e) { profiles = []; }
}
function profilerSave() {
localStorage.setItem('otaman_profiles', JSON.stringify(profiles));
localStorage.setItem('simple_profiles', JSON.stringify(profiles));
}
function snapshotsLoad() {
try {
const d = localStorage.getItem('otaman_snapshots');
const d = localStorage.getItem('simple_snapshots');
snapshots = d ? JSON.parse(d) : [];
} catch (e) { snapshots = []; }
}
function snapshotsSave() {
try {
localStorage.setItem('otaman_snapshots', JSON.stringify(snapshots));
localStorage.setItem('simple_snapshots', JSON.stringify(snapshots));
} catch (e) {
alert('Snapshot save failed: ' + e.message);
}
@@ -11499,12 +11499,12 @@ const LANG_RU = {
'Install Package (.cap file)': 'Установка пакета (.cap файл)',
'Server URL': 'URL сервера',
'Connect': 'Подключиться',
'To access local card reader, you need pysim-otaman-server running locally.': 'Для доступа к локальному картридеру необходим запущенный pysim-otaman-server.',
'To access local card reader, you need pysim-simple-server running locally.': 'Для доступа к локальному картридеру необходим запущенный pysim-simple-server.',
'To connect, install': 'Для подключения установите',
'and start it.': 'и запустите его.',
'Refer to pysim-otaman-server README files for details.': 'Подробности — в README файлах pysim-otaman-server.',
'Refer to pysim-simple-server README files for details.': 'Подробности — в README файлах pysim-simple-server.',
'Click': 'Нажмите кнопку',
'when pysim-otaman-server is ready': 'когда pysim-otaman-server будет готов',
'when pysim-simple-server is ready': 'когда pysim-simple-server будет готов',
'Equip card': 'Подключить карту',
'Check status': 'Проверить статус',
'Reset card': 'Сброс карты',
@@ -11532,8 +11532,8 @@ const LANG_RU = {
'Checking card...': 'Проверка карты...',
'Connected': 'Подключено',
'Connection failed:': 'Ошибка подключения:',
'pysim-blocked-hint': 'Браузер, вероятно, заблокировал доступ к локальному серверу карт. Разрешите доступ к локальной сети для этого сайта (Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети → Разрешить). Также проверьте, что pysim-otaman-server запущен по адресу {url}.',
'pysim-unreachable-hint': 'Не удалось подключиться к серверу карт. Проверьте, что pysim-otaman-server запущен и адрес указан верно ({url}). Если эта страница открыта по HTTPS, также разрешите доступ к локальной сети в браузере (Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети → Разрешить).',
'pysim-blocked-hint': 'Браузер, вероятно, заблокировал доступ к локальному серверу карт. Разрешите доступ к локальной сети для этого сайта (Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети → Разрешить). Также проверьте, что pysim-simple-server запущен по адресу {url}.',
'pysim-unreachable-hint': 'Не удалось подключиться к серверу карт. Проверьте, что pysim-simple-server запущен и адрес указан верно ({url}). Если эта страница открыта по HTTPS, также разрешите доступ к локальной сети в браузере (Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети → Разрешить).',
'No card detected. Insert card and click Equip card button': 'Карта не обнаружена. Вставьте карту и нажмите Подключить карту',
'Card inserted — press Equip': 'Карта вставлена — нажмите «Подключить карту»',
'Card inserted — initializing...': 'Карта вставлена — инициализация...',
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "OTAMan",
"short_name": "OTAMan",
"name": "SIMple",
"short_name": "SIMple",
"description": "Offline APDU helper for SIM/USIM/RAM commands",
"start_url": "index.html",
"display": "standalone",
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "otaman",
"name": "simple",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "otaman",
"name": "simple",
"version": "1.0.0",
"dependencies": {
"aes-js": "^3.1.2",
+2 -2
View File
@@ -1,5 +1,5 @@
{
"name": "otaman",
"name": "simple",
"version": "1.0.0",
"description": "Standalone offline HTML/JS tool for building APDU commands for SIM, USIM, and GlobalPlatform RAM, plus encoding conversions.",
"main": "index.js",
@@ -10,7 +10,7 @@
},
"repository": {
"type": "git",
"url": "https://github.com/anttro/otaman.git"
"url": "https://github.com/anttro/simple.git"
},
"keywords": [],
"type": "commonjs",
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'otaman-v188';
const CACHE = 'simple-v189';
const URLS = [
'index.html',
'help.html',
+4 -4
View File
@@ -167,9 +167,9 @@ test('load/persist/reset round-trip through localStorage', () => {
'opt-targeted-app': '//aid/A000000151000000'
});
scp81OptionsPersist();
assert.ok(store['otaman_scp81_opts'].includes('"chunkSize":100'));
assert.ok(store['otaman_scp81_opts'].includes('"targetedAppOn":true'));
assert.ok(!store['otaman_scp81_opts'].includes('keepAlive'));
assert.ok(store['simple_scp81_opts'].includes('"chunkSize":100'));
assert.ok(store['simple_scp81_opts'].includes('"targetedAppOn":true'));
assert.ok(!store['simple_scp81_opts'].includes('keepAlive'));
// a reload restores the saved setup
setup({});
scp81OptionsLoad();
@@ -184,7 +184,7 @@ test('load/persist/reset round-trip through localStorage', () => {
assert.strictEqual(get('opt-targeted-app').value, '//aid/A000000151000000');
// reset clears the saved entry and restores the defaults
scp81OptionsReset();
assert.ok(!('otaman_scp81_opts' in store));
assert.ok(!('simple_scp81_opts' in store));
scp81OptionsLoad();
assert.strictEqual(get('opt-chunked').checked, true);
assert.strictEqual(get('opt-next-uri').checked, true);
+5 -5
View File
@@ -3,16 +3,16 @@ requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "pysim-otaman-server"
version = "2.2.21"
description = "HTTP REST server wrapping pysim for the OTAMan PWA"
name = "pysim-simple-server"
version = "2.3.0"
description = "HTTP REST server wrapping pysim for the SIMple PWA"
requires-python = ">=3.8"
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
# Declaring it here would make pip resolve its full tree (including the SMPP
# bridge -> Twisted -> twisted-iocpsupport, which needs MSVC on Windows).
[project.scripts]
pysim-otaman-server = "pysim_otaman_server.__main__:main"
pysim-simple-server = "pysim_simple_server.__main__:main"
[tool.setuptools.packages.find]
include = ["pysim_otaman_server*"]
include = ["pysim_simple_server*"]
@@ -33,11 +33,11 @@ def main():
_server_start = time.time()
mod = load_pysim_app()
parser = mod.option_parser
parser.description = 'pysim-otaman-server — HTTP API for pysim'
parser.description = 'pysim-simple-server — HTTP API for pysim'
parser.add_argument('--http-host', default='127.0.0.1', help='Bind address (default: 127.0.0.1)')
parser.add_argument('--http-port', type=int, default=8080, help='Bind port (default: 8080)')
parser.add_argument('--web-dir', default=_default_web_dir(), metavar='PATH',
help='Directory with the OTAMan PWA static files to serve (default: <repo>/frontend)')
help='Directory with the SIMple PWA static files to serve (default: <repo>/frontend)')
parser.add_argument('--log-requests', action='store_true', default=False, help='Log request/response payloads to stderr')
parser.add_argument('--sms-oa', default='12345', metavar='DIGITS',
help='TP-Originating-Address (SMSC number) for the SMS-DELIVER TPDU (default: 12345)')
@@ -222,14 +222,14 @@ def main():
server.iccid = iccid
server.equipping = False
# Set server reference for polling timer and mark the card session state
import pysim_otaman_server.server
pysim_otaman_server.server._server_ref = server
pysim_otaman_server.server._CARD_CONNECTED = card is not None
import pysim_simple_server.server
pysim_simple_server.server._server_ref = server
pysim_simple_server.server._CARD_CONNECTED = card is not None
if opts.poll_interval is not None:
pysim_otaman_server.server._set_poll_interval(opts.poll_interval)
pysim_simple_server.server._set_poll_interval(opts.poll_interval)
# Auto-enable polling if card initialized successfully (unless interval is 0)
if server.scc and server.card and opts.poll_interval != 0:
pysim_otaman_server.server._poll_enable()
pysim_simple_server.server._poll_enable()
# Start presence monitoring only after the startup init: pyscard reports an
# already-present card as "added" on the first pass, and we must not
# auto-equip over a session we just initialized. If startup init failed,
@@ -237,8 +237,8 @@ def main():
if sl is not None and getattr(sl, '_reader', None) is not None:
start_card_monitor(str(sl._reader))
print("" * 70)
print(" pysim-otaman-server v%s listening on http://%s:%s" % (VERSION, opts.http_host, opts.http_port))
print(" Open http://%s:%s in your browser for the OTAMan UI (served by this server)."
print(" pysim-simple-server v%s listening on http://%s:%s" % (VERSION, opts.http_host, opts.http_port))
print(" Open http://%s:%s in your browser for the SIMple UI (served by this server)."
% (opts.http_host, opts.http_port))
print("" * 70)
try:
@@ -1,4 +1,4 @@
"""Fast card initialization for pysim-otaman-server.
"""Fast card initialization for pysim-simple-server.
pySim's ``init_card()`` performs several physical card resets: one per profile
candidate tried by ``CardProfile.pick()`` plus one at the end of
@@ -11,8 +11,8 @@ from http.server import HTTPServer, BaseHTTPRequestHandler
from io import StringIO
from pySim.transport import ApduTracer, ProactiveHandler
from pySim.cards import UiccCardBase
from pysim_otaman_server import httpota
from pysim_otaman_server import scp81
from pysim_simple_server import httpota
from pysim_simple_server import scp81
from smartcard.CardMonitoring import CardMonitor, CardObserver
import gsm0338 # registers 'gsm03.38' codec
@@ -21,7 +21,7 @@ from osmocom.construct import GsmOrUcs2Adapter
from osmocom.tlv import BER_TLV_IE
VERSION = '2.2.21'
VERSION = '2.3.0'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
+5 -5
View File
@@ -1,12 +1,12 @@
@echo off
REM pysim-otaman-server setup script for Windows
REM pysim-simple-server setup script for Windows
REM Creates a venv and installs pysim and its dependencies.
setlocal enabledelayedexpansion
set VENV_DIR=%~dp0.venv
echo === pysim-otaman-server setup ===
echo === pysim-simple-server setup ===
echo.
REM Check Python
@@ -71,11 +71,11 @@ if %errorlevel% neq 0 (
)
echo.
REM Install pysim-otaman-server
echo === Installing pysim-otaman-server ===
REM Install pysim-simple-server
echo === Installing pysim-simple-server ===
pip install -e "%~dp0."
if %errorlevel% neq 0 (
echo Error: Failed to install pysim-otaman-server.
echo Error: Failed to install pysim-simple-server.
pause
exit /b 1
)
+3 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -e
# pysim-otaman-server setup script
# pysim-simple-server setup script
# Creates a venv and installs pysim and its dependencies.
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
@@ -30,9 +30,9 @@ echo ""
echo "=== Installing pysim ==="
$PIP install git+https://github.com/osmocom/pysim.git
# Install pysim-otaman-server
# Install pysim-simple-server
echo ""
echo "=== Installing pysim-otaman-server ==="
echo "=== Installing pysim-simple-server ==="
$PIP install -e "$SCRIPT_DIR"
# Check for PC/SC
+12 -12
View File
@@ -1,33 +1,33 @@
@echo off
REM pysim-otaman-server start script for Windows
REM pysim-simple-server start script for Windows
REM Starts the server, preferring the venv if it exists.
REM PC/SC is built into Windows, so defaults to reader 0.
set VENV_DIR=%~dp0.venv
if exist "%VENV_DIR%\Scripts\pysim-otaman-server.exe" (
echo Starting pysim-otaman-server from venv on http://127.0.0.1:8080
if exist "%VENV_DIR%\Scripts\pysim-simple-server.exe" (
echo Starting pysim-simple-server from venv on http://127.0.0.1:8080
echo Press Ctrl+C to stop.
"%VENV_DIR%\Scripts\pysim-otaman-server.exe" --http-port 8080 -p 0
"%VENV_DIR%\Scripts\pysim-simple-server.exe" --http-port 8080 -p 0
goto :eof
)
if exist "%~dp0pysim_otaman_server\__main__.py" (
echo Starting pysim-otaman-server from source on http://127.0.0.1:8080
if exist "%~dp0pysim_simple_server\__main__.py" (
echo Starting pysim-simple-server from source on http://127.0.0.1:8080
echo Press Ctrl+C to stop.
python -m pysim_otaman_server --http-port 8080 -p 0
python -m pysim_simple_server --http-port 8080 -p 0
goto :eof
)
where pysim-otaman-server >nul 2>&1
where pysim-simple-server >nul 2>&1
if %errorlevel% equ 0 (
echo Starting pysim-otaman-server on http://127.0.0.1:8080
echo Starting pysim-simple-server on http://127.0.0.1:8080
echo Press Ctrl+C to stop.
pysim-otaman-server --http-port 8080 -p 0
pysim-simple-server --http-port 8080 -p 0
goto :eof
)
echo Error: pysim-otaman-server not installed.
echo Error: pysim-simple-server not installed.
echo Run setup.bat first or install manually:
echo pip install pysim-otaman-server
echo pip install pysim-simple-server
pause
+11 -11
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -e
# pysim-otaman-server start script
# pysim-simple-server start script
# Starts the server, preferring the venv if it exists.
# Auto-detects PC/SC reader if available.
@@ -21,21 +21,21 @@ if command -v pcscd > /dev/null 2>&1; then
fi
SERVER=""
if [ -f "$VENV_DIR/bin/pysim-otaman-server" ]; then
SERVER="$VENV_DIR/bin/pysim-otaman-server"
elif command -v pysim-otaman-server &> /dev/null; then
SERVER="pysim-otaman-server"
elif [ -f "$SCRIPT_DIR/pysim_otaman_server/__main__.py" ]; then
echo "Starting pysim-otaman-server from source on http://127.0.0.1:8080"
cd "$SCRIPT_DIR" && python3 -m pysim_otaman_server --http-port 8080 $READER_ARGS
if [ -f "$VENV_DIR/bin/pysim-simple-server" ]; then
SERVER="$VENV_DIR/bin/pysim-simple-server"
elif command -v pysim-simple-server &> /dev/null; then
SERVER="pysim-simple-server"
elif [ -f "$SCRIPT_DIR/pysim_simple_server/__main__.py" ]; then
echo "Starting pysim-simple-server from source on http://127.0.0.1:8080"
cd "$SCRIPT_DIR" && python3 -m pysim_simple_server --http-port 8080 $READER_ARGS
exit $?
else
echo "Error: pysim-otaman-server not installed."
echo "Error: pysim-simple-server not installed."
echo "Run setup.sh first or install manually:"
echo " pip install pysim-otaman-server"
echo " pip install pysim-simple-server"
exit 1
fi
echo "Starting pysim-otaman-server on http://127.0.0.1:8080"
echo "Starting pysim-simple-server on http://127.0.0.1:8080"
echo "Press Ctrl+C to stop."
$SERVER --http-port 8080 $READER_ARGS
+1 -1
View File
@@ -13,7 +13,7 @@ PY_SIM = PROJECTS / 'pysim'
if str(PY_SIM) not in sys.path:
sys.path.insert(0, str(PY_SIM))
import pysim_otaman_server.server as S
import pysim_simple_server.server as S
class TestClassifyApdu(unittest.TestCase):
+1 -1
View File
@@ -12,7 +12,7 @@ PY_SIM = PROJECTS / 'pysim'
if str(PY_SIM) not in sys.path:
sys.path.insert(0, str(PY_SIM))
import pysim_otaman_server.server as S
import pysim_simple_server.server as S
class FakeCard:
+2 -2
View File
@@ -15,8 +15,8 @@ if str(PY_SIM) not in sys.path:
from pySim.exceptions import SwMatchError
from pySim.ts_102_221 import CardProfileUICC
import pysim_otaman_server.fastinit as fastinit
from pysim_otaman_server.fastinit import (
import pysim_simple_server.fastinit as fastinit
from pysim_simple_server.fastinit import (
FastRuntimeState,
do_reset_fast,
pick_profile_no_reset,
+3 -3
View File
@@ -20,8 +20,8 @@ PY_SIM = PROJECTS / 'pysim'
if str(PY_SIM) not in sys.path:
sys.path.insert(0, str(PY_SIM))
from pysim_otaman_server import httpota
import pysim_otaman_server.server as server
from pysim_simple_server import httpota
import pysim_simple_server.server as server
OPEN_LOCALHOST = bytes.fromhex(
@@ -480,7 +480,7 @@ if __name__ == '__main__':
# A >127-byte channel data TLV must use the BER long form (0x81 len),
# as the reference terminal traces do (`36 81 ed` for 237 bytes).
import types
server = __import__('pysim_otaman_server.server', fromlist=['x'])
server = __import__('pysim_simple_server.server', fromlist=['x'])
big = bytes(range(256)) * 1 # 256 bytes; take a slice below
ch = types.SimpleNamespace(rx=bytearray(b'\xAA' * 237))
class FakeBip:
+2 -2
View File
@@ -18,8 +18,8 @@ PY_SIM = PROJECTS / 'pysim'
if str(PY_SIM) not in sys.path:
sys.path.insert(0, str(PY_SIM))
from pysim_otaman_server import server
from pysim_otaman_server.server import _decode_iccid, _read_iccid
from pysim_simple_server import server
from pysim_simple_server.server import _decode_iccid, _read_iccid
class FakeFile:
+1 -1
View File
@@ -12,7 +12,7 @@ PY_SIM = PROJECTS / 'pysim'
if str(PY_SIM) not in sys.path:
sys.path.insert(0, str(PY_SIM))
import pysim_otaman_server.server as S
import pysim_simple_server.server as S
class TestMenuTimeout(unittest.TestCase):
+36 -36
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Unit tests for the OTA helper functions in pysim_otaman_server.server.
"""Unit tests for the OTA helper functions in pysim_simple_server.server.
Reference vectors are key-free: synthetic dummy keys plus the already-public
sysmocom sample-key vectors that ship in pySim's own tests/unittests/test_ota.py.
@@ -19,7 +19,7 @@ PY_SIM = PROJECTS / 'pysim'
if str(PY_SIM) not in sys.path:
sys.path.insert(0, str(PY_SIM))
from pysim_otaman_server.server import (
from pysim_simple_server.server import (
_build_sms_tpdu,
_build_tr,
_decode_cmd,
@@ -106,7 +106,7 @@ class TestBuildSmsTpdu(unittest.TestCase):
SCTS = bytes.fromhex('24051215173000')
def _build(self, *args, **kwargs):
with mock.patch('pysim_otaman_server.server._encode_scts', return_value=self.SCTS):
with mock.patch('pysim_simple_server.server._encode_scts', return_value=self.SCTS):
return _build_sms_tpdu(*args, **kwargs)
def test_single_message_with_cpi(self):
@@ -273,7 +273,7 @@ class TestProactiveDecode(unittest.TestCase):
"""Server-side proactive command/TR decode helpers (v1.8.0 log feature)."""
def setUp(self):
import pysim_otaman_server.server as srv
import pysim_simple_server.server as srv
srv._PROACTIVE_SESSION_START = 1234.0
srv._PLI_DATA[0x00] = '93055210011000'
@@ -397,14 +397,14 @@ class TestProactiveDecode(unittest.TestCase):
self.assertEqual(r[1], {'label': 'Data bytes', 'value': '3'})
def test_parse_proactive_header_plain_tags(self):
import pysim_otaman_server.server as srv
import pysim_simple_server.server as srv
raw = bytes.fromhex('d00c010301270102028182240103')
self.assertEqual(srv._parse_proactive_header(raw), (1, 0x27, 0x81, 0x82, 0x01))
def test_default_handler_logs_timer_management(self):
# pySim's auto-handler path: the parsed command object (not the empty
# collection) is re-encoded for the log and used for the response.
import pysim_otaman_server.server as srv
import pysim_simple_server.server as srv
from pySim.cat import ProactiveCommand
from pySim.utils import h2b
srv._PROACTIVE_LOG.clear()
@@ -420,7 +420,7 @@ class TestProactiveDecode(unittest.TestCase):
self.assertEqual(entry['tr_result'], '00')
def test_default_handler_pli_includes_dict_data(self):
import pysim_otaman_server.server as srv
import pysim_simple_server.server as srv
from pySim.cat import ProactiveCommand
from pySim.utils import h2b
srv._PROACTIVE_LOG.clear()
@@ -529,7 +529,7 @@ class TestEventDownload(unittest.TestCase):
"""ENVELOPE (EVENT DOWNLOAD) assembly, TS 102 223 7.5.11."""
def _send(self, event_type, event_data):
import pysim_otaman_server.server as srv
import pysim_simple_server.server as srv
calls = []
class Tp:
@@ -565,30 +565,30 @@ class TestTimerManagement(unittest.TestCase):
START = bytes.fromhex('d011810301270082028182a40101a503001000')
def tearDown(self):
import pysim_otaman_server.server as srv
import pysim_simple_server.server as srv
srv._timer_cancel()
def test_hms_bcd_roundtrip(self):
import pysim_otaman_server.server as srv
import pysim_simple_server.server as srv
self.assertEqual(srv._hms_bcd(60).hex(), '001000')
self.assertEqual(srv._hms_bcd(3723).hex(), '102030')
self.assertEqual([srv._bcd_swap(b) for b in srv._hms_bcd(3723)], [1, 2, 3])
def test_start_returns_result_only_and_arms_timer(self):
import pysim_otaman_server.server as srv
import pysim_simple_server.server as srv
tr = srv._handle_timer_command(1, 0x27, 0x00, self.START, 0x81, 0x82)
self.assertEqual(tr.hex(), '810301270082028281030100')
remaining = srv._timer_remaining(1)
self.assertTrue(55 <= remaining <= 60, remaining)
def test_get_returns_remaining_value(self):
import pysim_otaman_server.server as srv
import pysim_simple_server.server as srv
srv._handle_timer_command(1, 0x27, 0x00, self.START, 0x81, 0x82)
tr = srv._handle_timer_command(1, 0x27, 0x02, self.START, 0x81, 0x82)
self.assertEqual(tr.hex(), '810301270282028281a40101a503001000030100')
def test_deactivate_stops_and_reports_value(self):
import pysim_otaman_server.server as srv
import pysim_simple_server.server as srv
srv._handle_timer_command(1, 0x27, 0x00, self.START, 0x81, 0x82)
tr = srv._handle_timer_command(1, 0x27, 0x01, self.START, 0x81, 0x82)
self.assertTrue(tr.hex().startswith('8103012701'), tr.hex())
@@ -596,12 +596,12 @@ class TestTimerManagement(unittest.TestCase):
self.assertIsNone(srv._timer_remaining(1))
def test_get_on_stopped_timer_is_contradiction(self):
import pysim_otaman_server.server as srv
import pysim_simple_server.server as srv
tr = srv._handle_timer_command(1, 0x27, 0x02, self.START, 0x81, 0x82)
self.assertEqual(tr.hex(), '810301270282028281030124')
def test_timer_expiration_envelope(self):
import pysim_otaman_server.server as srv
import pysim_simple_server.server as srv
calls = []
class Tp:
@@ -618,7 +618,7 @@ class TestTimerManagement(unittest.TestCase):
self.assertEqual(calls, ['80c200000ed70c82028281a40101a503001000'])
def test_cancelled_timer_does_not_report(self):
import pysim_otaman_server.server as srv
import pysim_simple_server.server as srv
calls = []
class Tp:
@@ -704,7 +704,7 @@ class TestSmsConcat(unittest.TestCase):
def test_no_udh_sms_submit(self):
"""SMS-SUBMIT without TP-UDHI: entire UD is payload."""
from pysim_otaman_server.server import _parse_sms_concat
from pysim_simple_server.server import _parse_sms_concat
# First octet 0x01: MTI=01 (SUBMIT), no UDH, no VP
# MR=00, DA_len=05, DA_type=90, DA=2143F5, PID=00, DCS=04, UDL=03, UD=AABBCC
tpdu = bytes.fromhex('0100' # first octet + MR
@@ -722,7 +722,7 @@ class TestSmsConcat(unittest.TestCase):
def test_8bit_concat_iei_0x00(self):
"""SMS-SUBMIT with IEI 0x00 (8-bit reference concatenation)."""
from pysim_otaman_server.server import _parse_sms_concat
from pysim_simple_server.server import _parse_sms_concat
# First octet 0x41: MTI=01 (SUBMIT), TP-UDHI=1, no VP
# MR=00, DA_len=05, DA_type=90, DA=2143F5, PID=00, DCS=04
# UDL=09, UDHL=05, UDH: 00 03 04 04 01 (concat IE), payload=AABBCC
@@ -746,7 +746,7 @@ class TestSmsConcat(unittest.TestCase):
def test_16bit_concat_iei_0x08(self):
"""SMS-SUBMIT with IEI 0x08 (16-bit reference concatenation)."""
from pysim_otaman_server.server import _parse_sms_concat
from pysim_simple_server.server import _parse_sms_concat
# First octet 0x41: MTI=01, TP-UDHI=1
# UDH: 06 (UDHL) 08 04 01 02 03 04 (16-bit concat: ref=0x0102, total=3, num=4)
# payload=FF
@@ -770,7 +770,7 @@ class TestSmsConcat(unittest.TestCase):
def test_udh_with_cpi(self):
"""UDH with concatenation IE + CPI IE (0x70)."""
from pysim_otaman_server.server import _parse_sms_concat
from pysim_simple_server.server import _parse_sms_concat
# First octet 0x41: MTI=01, TP-UDHI=1
# UDHL=07, UDH: 00 03 04 04 01 (concat) + 70 00 (CPI)
tpdu = bytes.fromhex('4100'
@@ -794,7 +794,7 @@ class TestSmsConcat(unittest.TestCase):
def test_empty_payload(self):
"""Segment with empty payload after UDH."""
from pysim_otaman_server.server import _parse_sms_concat
from pysim_simple_server.server import _parse_sms_concat
# First octet 0x41: MTI=01, TP-UDHI=1
tpdu = bytes.fromhex('4100'
'05'
@@ -815,7 +815,7 @@ class TestSmsConcat(unittest.TestCase):
def test_short_tpdu(self):
"""Truncated TPDU returns gracefully."""
from pysim_otaman_server.server import _parse_sms_concat
from pysim_simple_server.server import _parse_sms_concat
ref, total, num, payload = _parse_sms_concat(b'\x01')
self.assertIsNone(ref)
self.assertIsNone(total)
@@ -823,7 +823,7 @@ class TestSmsConcat(unittest.TestCase):
def test_none_input(self):
"""None input returns empty payload."""
from pysim_otaman_server.server import _parse_sms_concat
from pysim_simple_server.server import _parse_sms_concat
ref, total, num, payload = _parse_sms_concat(None)
self.assertIsNone(ref)
self.assertIsNone(total)
@@ -832,7 +832,7 @@ class TestSmsConcat(unittest.TestCase):
def test_short_tpdu(self):
"""Truncated TPDU returns gracefully."""
from pysim_otaman_server.server import _parse_sms_concat
from pysim_simple_server.server import _parse_sms_concat
ref, total, num, payload = _parse_sms_concat(b'\x44')
self.assertIsNone(ref)
self.assertIsNone(total)
@@ -840,7 +840,7 @@ class TestSmsConcat(unittest.TestCase):
def test_none_input(self):
"""None input returns empty payload."""
from pysim_otaman_server.server import _parse_sms_concat
from pysim_simple_server.server import _parse_sms_concat
ref, total, num, payload = _parse_sms_concat(None)
self.assertIsNone(ref)
self.assertIsNone(total)
@@ -853,7 +853,7 @@ class TestSmsReassembly(unittest.TestCase):
def test_single_segment_no_concat(self):
"""Single segment without UDH → submit_tpdu_hex is set directly."""
from pysim_otaman_server.server import PoRSubmitHandler, _find_sms_tpdu, _parse_sms_concat
from pysim_simple_server.server import PoRSubmitHandler, _find_sms_tpdu, _parse_sms_concat
handler = PoRSubmitHandler()
# Build a simple D0 with tag 8B containing an SMS-SUBMIT without UDH
sms_tpdu = bytes.fromhex('040005902143F50004' # SMS-SUBMIT header
@@ -874,7 +874,7 @@ class TestSmsReassembly(unittest.TestCase):
def test_multi_segment_reassembly(self):
"""3 segments with IEI 0x00 in random order → assembled in correct order."""
from pysim_otaman_server.server import PoRSubmitHandler
from pysim_simple_server.server import PoRSubmitHandler
handler = PoRSubmitHandler()
# Segment payloads (after UDH)
@@ -897,7 +897,7 @@ class TestSmsReassembly(unittest.TestCase):
def test_independent_references(self):
"""Two different reference numbers are independent."""
from pysim_otaman_server.server import PoRSubmitHandler
from pysim_simple_server.server import PoRSubmitHandler
handler = PoRSubmitHandler()
# Ref 0x01: 2 segments
@@ -947,7 +947,7 @@ class CapApduSequenceTest(unittest.TestCase):
return buf.getvalue().hex().upper()
def test_cap_parse(self):
from pysim_otaman_server.server import _cap_parse
from pysim_simple_server.server import _cap_parse
loadfile_aid, module_aid, data = _cap_parse(self._mini_cap())
self.assertEqual(loadfile_aid, 'A00000010001')
self.assertEqual(module_aid, 'A000000100')
@@ -956,7 +956,7 @@ class CapApduSequenceTest(unittest.TestCase):
self.assertIn('03000A01', data)
def test_sequence_install_load_install(self):
from pysim_otaman_server.server import _cap_apdu_sequence
from pysim_simple_server.server import _cap_apdu_sequence
seq = _cap_apdu_sequence('A00000010001', 'A000000100', 'AABBCCDD')
# INSTALL [for load]: lv(pkg aid) + lv(ISD) + 000000
self.assertEqual(seq[0],
@@ -969,7 +969,7 @@ class CapApduSequenceTest(unittest.TestCase):
self.assertIn('06A00000010001' + '05A000000100' + '05A000000100' + '0100', seq[2])
def test_load_blocks_split_and_counter(self):
from pysim_otaman_server.server import _cap_apdu_sequence, _ber_len as _ber_len_lower
from pysim_simple_server.server import _cap_apdu_sequence, _ber_len as _ber_len_lower
data = ''.join('%02X' % (i % 256) for i in range(700))
seq = _cap_apdu_sequence('A00000010001', 'A000000100', data)
self.assertEqual(len(seq), 5) # INSTALL + 3 LOAD + INSTALL
@@ -991,7 +991,7 @@ class CapApduSequenceTest(unittest.TestCase):
# A smaller block size (SCP80: fit one SMS) slices the load file TLV
# into consecutive chunks of that size, the last block marked P1=0x80
# with the block counter in P2.
from pysim_otaman_server.server import _cap_apdu_sequence
from pysim_simple_server.server import _cap_apdu_sequence
data = ''.join('%02X' % (i % 256) for i in range(700)) # TLV = 704 bytes
seq = _cap_apdu_sequence('A00000010001', 'A000000100', data, block_size=100)
self.assertEqual(len(seq), 10) # INSTALL + 8 LOAD + INSTALL
@@ -1011,7 +1011,7 @@ class CapApduSequenceTest(unittest.TestCase):
def test_gen_install_returns_the_apdu_list(self):
# /api/scp81/gen-install: build the INSTALL/LOAD/INSTALL list for a
# .cap without touching any listener or script state.
from pysim_otaman_server.server import _scp81_gen_install
from pysim_simple_server.server import _scp81_gen_install
resp = _scp81_gen_install({'cap_hex': self._mini_cap(), 'privileges': '01'})
self.assertTrue(resp['ok'], resp)
self.assertEqual(resp['load_file_aid'], 'A00000010001')
@@ -1023,7 +1023,7 @@ class CapApduSequenceTest(unittest.TestCase):
self.assertNotIn('queued', resp) # generation only, no queueing
def test_gen_install_rejects_bad_input(self):
from pysim_otaman_server.server import _scp81_gen_install
from pysim_simple_server.server import _scp81_gen_install
self.assertFalse(_scp81_gen_install({})['ok'])
resp = _scp81_gen_install({'cap_hex': '00'})
self.assertFalse(resp['ok'])
@@ -1034,7 +1034,7 @@ class TerminalProfileTest(unittest.TestCase):
"""Runtime TERMINAL PROFILE: hex validation and re-send."""
def test_validate_tp_hex(self):
from pysim_otaman_server.server import _validate_tp_hex
from pysim_simple_server.server import _validate_tp_hex
self.assertEqual(_validate_tp_hex('ff 00 80'), ('FF0080', None))
self.assertEqual(_validate_tp_hex('80FF'), ('80FF', None))
for bad in ('', ' ', 'F', 'XYZ', 'FF0', 'FF' * 256):
@@ -1044,7 +1044,7 @@ class TerminalProfileTest(unittest.TestCase):
def test_resend_terminal_profile_resets_state_and_sends(self):
import types
from pysim_otaman_server import server as srv
from pysim_simple_server import server as srv
server_obj = types.SimpleNamespace(
terminal_profile='FF00', stk_pending={'type': 'display_text'},
menu_active=True, event_list=[0x03], sim_menu='old')
+1 -1
View File
@@ -11,7 +11,7 @@ PY_SIM = PROJECTS / 'pysim'
if str(PY_SIM) not in sys.path:
sys.path.insert(0, str(PY_SIM))
import pysim_otaman_server.server as S
import pysim_simple_server.server as S
class TestPollInterval(unittest.TestCase):
+6 -6
View File
@@ -19,8 +19,8 @@ PY_SIM = PROJECTS / 'pysim'
if str(PY_SIM) not in sys.path:
sys.path.insert(0, str(PY_SIM))
from pysim_otaman_server import scp81
import pysim_otaman_server.server as server
from pysim_simple_server import scp81
import pysim_simple_server.server as server
PSK = bytes.fromhex('00112233445566778899aabbccddeeff')
IDENT = '89012345678901234567'
@@ -194,7 +194,7 @@ class PskTlsServerTest(unittest.TestCase):
tls = self._connect(srv)
tls.sendall(b'POST /api/scp81 HTTP/1.1\r\nHost: 127.0.0.1\r\n'
b'X-Admin-Protocol: globalplatform-remote-admin/1.0\r\n'
b'X-Admin-From: otaman\r\n\r\n')
b'X-Admin-From: simple\r\n\r\n')
reply = self._read_http(tls)
self.assertIn(b'HTTP/1.1 200 OK', reply)
self.assertIn(b'X-Admin-Next-URI: /api/scp81?req=1', reply)
@@ -1194,7 +1194,7 @@ class QueueScriptTest(unittest.TestCase):
class ScriptBodyLengthTest(unittest.TestCase):
def test_long_c_apdu_uses_ber_long_form(self):
from pysim_otaman_server.server import _scp81_command_body
from pysim_simple_server.server import _scp81_command_body
apdu = '80E80000F0' + 'AB' * 239 + '00' # exactly 245-byte LOAD
body = _scp81_command_body(apdu)
# AE 80 22 81 F5 <245 bytes> 00 00
@@ -1203,12 +1203,12 @@ class ScriptBodyLengthTest(unittest.TestCase):
self.assertEqual(len(body), 5 + 245 + 2)
def test_short_apdu_stays_short_form(self):
from pysim_otaman_server.server import _scp81_command_body
from pysim_simple_server.server import _scp81_command_body
self.assertEqual(_scp81_command_body('80CAFF2100').hex().upper(),
'AE80220580CAFF21000000')
def test_definite_variant_ber_lengths(self):
from pysim_otaman_server.server import _scp81_command_body
from pysim_simple_server.server import _scp81_command_body
apdu = 'AB' * 130
body = _scp81_command_body(apdu, definite=True)
# AA 81 85 22 81 82 <130 bytes> (outer 1+2+130 = 133 = 0x85)
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Unit tests for the parent-scoped select helpers in pysim_otaman_server.server.
"""Unit tests for the parent-scoped select helpers in pysim_simple_server.server.
The helpers must resolve every model-known file strictly within the requested
parent (no pySim global selectables, no probe_file model injection) and must
@@ -16,7 +16,7 @@ PY_SIM = PROJECTS / 'pysim'
if str(PY_SIM) not in sys.path:
sys.path.insert(0, str(PY_SIM))
from pysim_otaman_server.server import (
from pysim_simple_server.server import (
_app_by_sel,
_fid4,
_file_by_sel,
+4 -4
View File
@@ -21,8 +21,8 @@ if str(PY_SIM) not in sys.path:
from pySim.exceptions import NoCardError, SwMatchError
from pysim_otaman_server import __main__ as srv_main
from pysim_otaman_server.server import _LineFilter
from pysim_simple_server import __main__ as srv_main
from pysim_simple_server.server import _LineFilter
CARDLESS_LINE = 'INIT: no card in the reader — server ready; insert a card or press Equip'
@@ -84,7 +84,7 @@ class StartupNoCardTest(unittest.TestCase):
fake_fastinit.install = mock.Mock()
fake_http.return_value.serve_forever.side_effect = KeyboardInterrupt
try:
with mock.patch('sys.argv', ['pysim-otaman-server']):
with mock.patch('sys.argv', ['pysim-simple-server']):
srv_main.main()
except (KeyboardInterrupt, SystemExit):
pass
@@ -135,7 +135,7 @@ class StartupNoCardTest(unittest.TestCase):
fake_fastinit.install = mock.Mock()
fake_http.return_value.serve_forever.side_effect = KeyboardInterrupt
try:
with mock.patch('sys.argv', ['pysim-otaman-server']):
with mock.patch('sys.argv', ['pysim-simple-server']):
srv_main.main()
except (KeyboardInterrupt, SystemExit):
pass