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:
@@ -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
@@ -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
@@ -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 |
|
||||
|
||||
@@ -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
@@ -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) → HTTP :8080 → pysim-otaman-server → pySim → PC/SC → ридер → 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) → HTTP :8080 → pysim-simple-server → pySim → PC/SC → ридер → 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>«Скрипты»</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. Вкладка «Картридер» (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>«Профайлер»</strong> и <strong>«Симулятор телефона»</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>«Профайлер»</strong> и <strong>«Симулятор телефона»</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">Для работы с картой (вкладка «Картридер», «Симулятор телефона», доставка 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">Для работы с картой (вкладка «Картридер», «Симулятор телефона», доставка 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 ≥ 1.6.1 делает это автоматически), и (2) браузеру должно быть разрешено обращаться к локальной сети — в Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети → разрешить сайт (или подтвердить запрос). Без разрешения браузера запрос к <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 ≥ 1.6.1 делает это автоматически), и (2) браузеру должно быть разрешено обращаться к локальной сети — в Chrome/Edge/Vivaldi: Настройки сайта → Доступ к локальной сети → разрешить сайт (или подтвердить запрос). Без разрешения браузера запрос к <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">✅ Совместимы</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">❌ Устарел — обновите сервер</td></tr>
|
||||
|
||||
+16
-16
@@ -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) → HTTP :8080 → pysim-otaman-server → pySim → PC/SC → card reader → 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) → HTTP :8080 → pysim-simple-server → pySim → PC/SC → card reader → 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 ≥ 1.6.1 does this automatically), and (2) 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 <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 ≥ 1.6.1 does this automatically), and (2) 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 <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">✅ 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">❌ Outdated — update server</td></tr>
|
||||
|
||||
+28
-28
@@ -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...': 'Карта вставлена — инициализация...',
|
||||
|
||||
@@ -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",
|
||||
|
||||
Generated
+2
-2
@@ -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",
|
||||
|
||||
@@ -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
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'otaman-v188';
|
||||
const CACHE = 'simple-v189';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user