docs: fix version compatibility (PWA vs server majors), broaden descriptions, drop the header slogan (v2.3.1)

- version check in pysimConnect() now compares the server major against
  SIMPLE_VERSION (2.3.1) instead of the hardcoded 1.x assumption; the header
  version is rendered from the same constant
- removed the 'SIM OTA with a Human Face' slogan (span, i18n key, translate
  hook) to free header space
- broaden the PWA description in manifest.json/package.json and the help
  overview (EN/RU): APDU workbench, OTA lab, CAT/STK simulator, card profiler
- version compatibility tables in README/RUS, help EN/RU and docs/api.md now
  describe the major-version rule
- README/RUS: Cards promoted to a top-level section; EN duplicate
  Theme/Localisation sections removed
- docs: /api/cardinfo documented; SCP81 'expose framing options' next-work
  item dropped (done); local AGENTS.md not-implemented note corrected
- SW cache simple-v190
This commit is contained in:
2026-09-19 08:54:33 +03:00
parent 3dceb5c56b
commit 4f6e48f0e0
12 changed files with 94 additions and 87 deletions
+28 -34
View File
@@ -438,28 +438,6 @@ Delivery PoR (SPI2 `01`) is simpler — the card returns the PoR directly in the
- ISO 9797-1: MAC algorithms - ISO 9797-1: MAC algorithms
- NIST SP 800-38B: CMAC - NIST SP 800-38B: CMAC
### Cards
Stores saved card configurations (presets) in `localStorage`. A preset holds the cryptographic keys, SPI settings, TAR and replay counter for SCP80 operations, plus the **PSK identity / PSK key** pair used by the SCP81 HTTP OTA listener. Cards is a **top-level tab**. When the card is equipped its EF.ICCID is read and the preset with the same ICCID is selected automatically in both SCP80 views.
| Field | Description |
|---|---|
| Name | Human-readable label (required) |
| ICCID | Optional card identifier |
| SPI1 / SPI2 | Security level and PoR settings |
| KIc / KID index | Key version number (required together with the keys) |
| KIc / KID key | Encryption and MAC key hex |
| TAR | Toolkit Application Reference (3 bytes) |
| Counter (CNTR) | 10-digit hex replay counter, auto-incremented after each successful SCP80 send |
| PSK identity | SCP81 HTTP OTA: the identity the card sends in the TLS handshake |
| PSK key | SCP81 HTTP OTA: 32 hex chars (16 bytes); the listener picks it by the identity the card presents |
The **SCP81** column shows whether the preset supplies a usable PSK pair: **✓** (identity and key), **⚠** (only one of the two — the listener ignores such a preset), **—** (no PSK). Identity and key must be set together.
**Add a card:** fill in the name, ICCID (optional — **From card** fills it from the equipped card's EF.ICCID), SPI1/SPI2, KIc/KID keys and indices, TAR, the SCP81 PSK pair (optional) and click **Add**. A duplicate ICCID (compared ignoring spaces and the raw-hex form) is refused, naming the conflicting preset. The card appears in the list and becomes available in the RAM tab's **Card preset** dropdown.
**Edit / remove:** **Edit** loads a preset into the form (the Add button becomes **Save**; **Cancel** clears the form); **Remove** deletes the row from `localStorage`. A successful SCP80 send advances and stores the replay counter, and edits are pushed into a running SCP81 listener automatically.
### RAM ### RAM
All RAM operations are delivered as SCP80 secured packets (ETSI TS 102 225) via SMS-PP-DOWNLOAD ENVELOPE. The card must support SCP03 (AES or 3DES) for secure transport. All RAM operations are delivered as SCP80 secured packets (ETSI TS 102 225) via SMS-PP-DOWNLOAD ENVELOPE. The card must support SCP03 (AES or 3DES) for secure transport.
@@ -485,6 +463,30 @@ Delete confirms via a browser prompt before sending the GP `DELETE` command via
--- ---
## Cards
Stores saved card configurations (presets) in `localStorage`. A preset holds the cryptographic keys, SPI settings, TAR and replay counter for SCP80 operations, plus the **PSK identity / PSK key** pair used by the SCP81 HTTP OTA listener. Cards is a **top-level tab**. When the card is equipped its EF.ICCID is read and the preset with the same ICCID is selected automatically in both SCP80 views.
| Field | Description |
|---|---|
| Name | Human-readable label (required) |
| ICCID | Optional card identifier |
| SPI1 / SPI2 | Security level and PoR settings |
| KIc / KID index | Key version number (required together with the keys) |
| KIc / KID key | Encryption and MAC key hex |
| TAR | Toolkit Application Reference (3 bytes) |
| Counter (CNTR) | 10-digit hex replay counter, auto-incremented after each successful SCP80 send |
| PSK identity | SCP81 HTTP OTA: the identity the card sends in the TLS handshake |
| PSK key | SCP81 HTTP OTA: 32 hex chars (16 bytes); the listener picks it by the identity the card presents |
The **SCP81** column shows whether the preset supplies a usable PSK pair: **✓** (identity and key), **⚠** (only one of the two — the listener ignores such a preset), **—** (no PSK). Identity and key must be set together.
**Add a card:** fill in the name, ICCID (optional — **From card** fills it from the equipped card's EF.ICCID), SPI1/SPI2, KIc/KID keys and indices, TAR, the SCP81 PSK pair (optional) and click **Add**. A duplicate ICCID (compared ignoring spaces and the raw-hex form) is refused, naming the conflicting preset. The card appears in the list and becomes available in the RAM tab's **Card preset** dropdown.
**Edit / remove:** **Edit** loads a preset into the form (the Add button becomes **Save**; **Cancel** clears the form); **Remove** deletes the row from `localStorage`. A successful SCP80 send advances and stores the replay counter, and edits are pushed into a running SCP81 listener automatically.
---
## Card Reader (pySim integration) ## Card Reader (pySim integration)
Connects to the bundled [`pysim-simple-server`](pysim_simple_server/) for live card operations. Connects to the bundled [`pysim-simple-server`](pysim_simple_server/) for live card operations.
@@ -696,20 +698,12 @@ pysim-simple-server --http-port 8080
See [docs/api.md](docs/api.md) for the full endpoint reference. See [docs/api.md](docs/api.md) for the full endpoint reference.
## Theme
Dark theme is supported. The app follows the OS preference on first visit, and a manual toggle button (🌙/☀️) at the top-right corner persists the choice in `localStorage`.
## Localisation
The UI is in English with Russian language support. Language is detected from the browser's `navigator.language` preference. A manual toggle button (EN/RU) in the header persists the choice in `localStorage`.
## Version compatibility ## Version compatibility
| PWA (SIMple) | Server | Status | | PWA (SIMple) | Server | Status |
|-------------|--------|--------| |-------------|--------|--------|
| 1.x.x | 1.x.x | ✅ Compatible | | any | same major | ✅ Compatible |
| 1.x.x | 0.x.x | ❌ Outdated — update server | | any | older major | ❌ Outdated — update server |
| 1.x.x | 2.x.x+ | ⚠️ Server newer — update PWA | | any | newer major | ⚠️ Server newer — update PWA |
The PWA checks the server version on connect via `GET /api/version` and warns if versions are incompatible. The PWA checks the server version on connect via `GET /api/version` and compares the major version (e.g. a 2.x PWA with a 2.x server; a 1.x server is flagged as outdated).
+28 -26
View File
@@ -412,28 +412,6 @@ Delivery PoR (SPI2 `01`) проще — карта возвращает PoR на
- ISO 9797-1 - ISO 9797-1
- NIST SP 800-38B (CMAC) - NIST SP 800-38B (CMAC)
### Карты
Хранит сохранённые конфигурации карт (пресеты) в `localStorage`. Пресет содержит криптографические ключи, настройки SPI, TAR и счётчик повторов для SCP80-операций, а также пару **PSK identity / PSK key** для слушателя SCP81 HTTP OTA. «Карты» — **верхнеуровневая вкладка**. При подключении карты читается её EF.ICCID, и пресет с тем же ICCID автоматически выбирается в обоих видах SCP80.
| Поле | Описание |
|---|---|
| Name | Понятная метка (обязательна) |
| ICCID | Опциональный идентификатор карты; кнопка **С карты** подставляет EF.ICCID подключённой карты (активна, только когда ICCID читается) |
| SPI1 / SPI2 | Уровень безопасности и настройки PoR |
| Индекс KIc / KID | Номер версии ключа (задаётся вместе с ключами) |
| Ключ KIc / KID | Hex-ключи шифрования и MAC |
| TAR | Toolkit Application Reference (3 байта) |
| Счётчик (CNTR) | 10-значный hex-счётчик повторов, автоматически увеличивается после каждой успешной отправки SCP80 |
| PSK identity | SCP81 HTTP OTA: идентификатор, который карта присылает в TLS-рукопожатии |
| PSK key | SCP81 HTTP OTA: 32 hex-символа (16 байт); ключ выбирается по идентификатору, который предъявляет карта |
Столбец **SCP81** показывает, задана ли в пресете рабочая пара PSK: **✓** (идентификатор и ключ), **⚠** (только одно из двух — слушатель такой пресет игнорирует), **—** (PSK нет). Идентификатор и ключ задаются вместе.
**Добавить карту:** заполните имя, ICCID (опционально — кнопка **С карты** подставляет EF.ICCID подключённой карты), SPI1/SPI2, ключи и индексы KIc/KID, TAR, при необходимости пару PSK и нажмите **Add**. Дубликат ICCID (сравнение без пробелов и с учётом сырой hex-формы) отклоняется с указанием конфликтующего пресета. Карта появится в списке и станет доступна в выпадающем списке **Card preset** на вкладке RAM.
**Изменить/удалить:** **Edit** загружает пресет в форму (кнопка Add становится **Save**; **Cancel** очищает форму); **Remove** удаляет строку из `localStorage`. Успешная отправка SCP80 увеличивает и сохраняет счётчик повторов, а изменения сразу передаются работающему слушателю SCP81.
### RAM ### RAM
Все операции RAM отправляются как защищённые пакеты SCP80 (ETSI TS 102 225) через SMS-PP-DOWNLOAD ENVELOPE. Карта должна поддерживать SCP03 (AES или 3DES) для безопасной транспортировки. Все операции RAM отправляются как защищённые пакеты SCP80 (ETSI TS 102 225) через SMS-PP-DOWNLOAD ENVELOPE. Карта должна поддерживать SCP03 (AES или 3DES) для безопасной транспортировки.
@@ -459,6 +437,30 @@ Delivery PoR (SPI2 `01`) проще — карта возвращает PoR на
--- ---
## Карты
Хранит сохранённые конфигурации карт (пресеты) в `localStorage`. Пресет содержит криптографические ключи, настройки SPI, TAR и счётчик повторов для SCP80-операций, а также пару **PSK identity / PSK key** для слушателя SCP81 HTTP OTA. «Карты» — **верхнеуровневая вкладка**. При подключении карты читается её EF.ICCID, и пресет с тем же ICCID автоматически выбирается в обоих видах SCP80.
| Поле | Описание |
|---|---|
| Name | Понятная метка (обязательна) |
| ICCID | Опциональный идентификатор карты; кнопка **С карты** подставляет EF.ICCID подключённой карты (активна, только когда ICCID читается) |
| SPI1 / SPI2 | Уровень безопасности и настройки PoR |
| Индекс KIc / KID | Номер версии ключа (задаётся вместе с ключами) |
| Ключ KIc / KID | Hex-ключи шифрования и MAC |
| TAR | Toolkit Application Reference (3 байта) |
| Счётчик (CNTR) | 10-значный hex-счётчик повторов, автоматически увеличивается после каждой успешной отправки SCP80 |
| PSK identity | SCP81 HTTP OTA: идентификатор, который карта присылает в TLS-рукопожатии |
| PSK key | SCP81 HTTP OTA: 32 hex-символа (16 байт); ключ выбирается по идентификатору, который предъявляет карта |
Столбец **SCP81** показывает, задана ли в пресете рабочая пара PSK: **✓** (идентификатор и ключ), **⚠** (только одно из двух — слушатель такой пресет игнорирует), **—** (PSK нет). Идентификатор и ключ задаются вместе.
**Добавить карту:** заполните имя, ICCID (опционально — кнопка **С карты** подставляет EF.ICCID подключённой карты), SPI1/SPI2, ключи и индексы KIc/KID, TAR, при необходимости пару PSK и нажмите **Add**. Дубликат ICCID (сравнение без пробелов и с учётом сырой hex-формы) отклоняется с указанием конфликтующего пресета. Карта появится в списке и станет доступна в выпадающем списке **Card preset** на вкладке RAM.
**Изменить/удалить:** **Edit** загружает пресет в форму (кнопка Add становится **Save**; **Cancel** очищает форму); **Remove** удаляет строку из `localStorage`. Успешная отправка SCP80 увеличивает и сохраняет счётчик повторов, а изменения сразу передаются работающему слушателю SCP81.
---
## Card Reader (интеграция с pySim) ## Card Reader (интеграция с pySim)
Подключение к встроенному [`pysim-simple-server`](pysim_simple_server/) для работы с картой. Подключение к встроенному [`pysim-simple-server`](pysim_simple_server/) для работы с картой.
@@ -612,11 +614,11 @@ SIMple — Progressive Web App. Можно установить для offline-
| PWA (SIMple) | Сервер | Статус | | PWA (SIMple) | Сервер | Статус |
|---|---|---| |---|---|---|
| 1.x.x | 1.x.x | ✅ Совместимы | | любая | та же мажорная | ✅ Совместимы |
| 1.x.x | 0.x.x | ❌ Сервер устарел | | любая | старее мажорная | ❌ Сервер устарел — обновите сервер |
| 1.x.x | 2.x.x+ | ⚠️ Сервер новее — обновите PWA | | любая | новее мажорная | ⚠️ Сервер новее — обновите PWA |
PWA проверяет версию сервера при подключении через `GET /api/version`. PWA проверяет версию сервера при подключении через `GET /api/version` и сравнивает мажорную версию (например, PWA 2.x с сервером 2.x; сервер 1.x помечается как устаревший).
--- ---
+12 -4
View File
@@ -8,12 +8,13 @@ on the preflight), so the API is reachable from a separately-hosted PWA.
| Server | PWA (SIMple) | Status | | Server | PWA (SIMple) | Status |
|--------|-------------|--------| |--------|-------------|--------|
| 1.x.x | 1.x.x | ✅ Compatible | | same major | any | ✅ Compatible |
| 0.x.x | 1.x.x | ❌ Outdated — update server | | older major | any | ❌ Outdated — update server |
| 2.x.x+ | 1.x.x | ⚠️ Server newer — update PWA | | newer major | any | ⚠️ Server newer — update PWA |
The server reports its version via `GET /api/version`. The PWA checks this on The server reports its version via `GET /api/version`. The PWA checks this on
connect and warns if versions are incompatible. connect and compares the major version (a 1.x server is flagged as outdated by
a 2.x PWA).
## Endpoints ## Endpoints
@@ -23,6 +24,7 @@ connect and warns if versions are incompatible.
| `/api/status` | GET | Card reader + card info + current selection | | `/api/status` | GET | Card reader + card info + current selection |
| `/api/command` | POST | pySim command (equip, status, tree, etc.) | | `/api/command` | POST | pySim command (equip, status, tree, etc.) |
| `/api/commands` | GET | List available pySim commands | | `/api/commands` | GET | List available pySim commands |
| `/api/cardinfo` | GET | pySim `cardinfo` output |
| `/api/tree` | POST | File tree browser for given FID/name | | `/api/tree` | POST | File tree browser for given FID/name |
| `/api/select` | POST | Select a file by name or FID | | `/api/select` | POST | Select a file by name or FID |
| `/api/read` | POST | Read file content | | `/api/read` | POST | Read file content |
@@ -87,6 +89,12 @@ with the same ICCID in both SCP80 views (Secured Packet and RAM).
List all available shell commands for the current card profile. List all available shell commands for the current card profile.
### `GET /api/cardinfo`
Runs pySim's `cardinfo` command and returns its output as `{"output": "..."}`
(card type, ATR, ICCID and other information pySim reports for the equipped
card). A shortcut for `POST /api/command` with `{"cmd": "cardinfo"}`.
### `POST /api/command` ### `POST /api/command`
Execute any pysim-shell command. Execute any pysim-shell command.
+1 -2
View File
@@ -308,8 +308,7 @@ INSTALL [for install] -> registries.
## Next tests / work ## Next tests / work
1. **UI:** group the per-page R-APDUs under their logical command in the 1. **UI:** group the per-page R-APDUs under their logical command in the
SCP81 tab (page merging/decoding for ELF and application listings); SCP81 tab (page merging/decoding for ELF and application listings).
expose the framing options in the tab.
2. **Load/store over SCP81:** implemented - `POST /api/scp81/gen-install` 2. **Load/store over SCP81:** implemented - `POST /api/scp81/gen-install`
takes a `.cap`, expands it with the shared `_cap_apdu_sequence` helper takes a `.cap`, expands it with the shared `_cap_apdu_sequence` helper
(INSTALL [for load] -> 240-byte LOAD blocks -> INSTALL [for install]) and (INSTALL [for load] -> 240-byte LOAD blocks -> INSTALL [for install]) and
+5 -5
View File
@@ -19,7 +19,7 @@
<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> <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"> <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> <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">SIMple — Progressive Web App (PWA) для построения APDU-команд, сборки защищённых пакетов SCP80, просмотра меню SIM Toolkit (STK) и симуляции реальной сетевой среды для тестирования SIM/USIM/UICC-карт через PC/SC-ридер.</p> <p class="mb-3">SIMple — инструментарий для специалистов по UICC/SIM-картам: автономный Progressive Web App (PWA), закрывающий весь цикл лабораторной работы &mdash; <strong>APDU-верстак</strong> (конструкторы SIM/USIM RFM и Expanded Script, команды GlobalPlatform RAM, парсер C-APDU/R-APDU и декодер ответов), <strong>OTA-лаборатория</strong> (защищённые пакеты SCP80 и установка Java Card RAM, а также терминальная сторона HTTP OTA &mdash; SCP81: эмуляция BIP-каналов, PSK-TLS-сервер администрирования и выполнение скриптов APDU), <strong>симулятор телефона</strong> (меню SIM Toolkit, proactive-команды и события, редактирование TERMINAL PROFILE, журнал C-AT-диалога) и <strong>профайлер карты</strong> (профили и неизменяемые снимки с таймингами, точное сравнение снимков, декодер FCP/FCI).</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> <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> <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> <p class="mb-3">Стандарты, на которые опирается приложение:</p>
@@ -542,12 +542,12 @@ pysim-simple-server --http-port 8080</pre>
<table class="w-full text-sm mb-3 border-collapse"> <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 (SIMple)</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> <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">любая</td><td class="py-1 px-2 font-mono">та же мажорная</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> <tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2 font-mono">любая</td><td class="py-1 px-2 font-mono">старее мажорная</td><td class="py-1 px-2">&#10060; Устарел &mdash; обновите сервер</td></tr>
<tr><td class="py-1 px-2 font-mono">1.x.x</td><td class="py-1 px-2 font-mono">2.x.x+</td><td class="py-1 px-2">&#9888;&#65039; Сервер новее &mdash; обновите PWA</td></tr> <tr><td class="py-1 px-2 font-mono">любая</td><td class="py-1 px-2 font-mono">новее мажорная</td><td class="py-1 px-2">&#9888;&#65039; Сервер новее &mdash; обновите PWA</td></tr>
</tbody> </tbody>
</table> </table>
<p class="text-sm">PWA проверяет версию сервера при подключении через <code class="font-mono text-sm">GET /api/version</code> и предупреждает о несовместимости.</p> <p class="text-sm">PWA проверяет версию сервера при подключении через <code class="font-mono text-sm">GET /api/version</code> и сравнивает мажорную версию (например, PWA 2.x с сервером 2.x; сервер 1.x помечается как устаревший).</p>
</main> </main>
+5 -5
View File
@@ -19,7 +19,7 @@
<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> <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"> <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> <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">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">SIMple is a workbench for UICC/SIM card specialists: an offline Progressive Web App (PWA) covering the full lab cycle &mdash; an <strong>APDU workbench</strong> (SIM/USIM RFM and Expanded Script builders, GlobalPlatform RAM commands, a C-APDU/R-APDU parser and a response decoder), an <strong>OTA lab</strong> (SCP80 secured packets and Java Card RAM installation, plus the terminal side of HTTP OTA &mdash; SCP81: BIP channel emulation, a PSK TLS administration server and APDU script execution), a <strong>phone/CAT simulator</strong> (SIM Toolkit menu, proactive commands and events, TERMINAL PROFILE editing, the C-AT dialogue log) and a <strong>card profiler</strong> (profiles and immutable snapshots with timings, exact snapshot comparison, FCP/FCI decoding).</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> <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> <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> <p class="mb-3">Standards referenced across the application:</p>
@@ -542,12 +542,12 @@ pysim-simple-server --http-port 8080</pre>
<table class="w-full text-sm mb-3 border-collapse"> <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 (SIMple)</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> <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">any</td><td class="py-1 px-2 font-mono">same major</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> <tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2 font-mono">any</td><td class="py-1 px-2 font-mono">older major</td><td class="py-1 px-2">&#10060; Outdated &mdash; update server</td></tr>
<tr><td class="py-1 px-2 font-mono">1.x.x</td><td class="py-1 px-2 font-mono">2.x.x+</td><td class="py-1 px-2">&#9888;&#65039; Server newer &mdash; update PWA</td></tr> <tr><td class="py-1 px-2 font-mono">any</td><td class="py-1 px-2 font-mono">newer major</td><td class="py-1 px-2">&#9888;&#65039; Server newer &mdash; update PWA</td></tr>
</tbody> </tbody>
</table> </table>
<p class="text-sm">The PWA checks the server version on connect via <code class="font-mono text-sm">GET /api/version</code> and warns if versions are incompatible.</p> <p class="text-sm">The PWA checks the server version on connect via <code class="font-mono text-sm">GET /api/version</code> and compares the major version (e.g. 2.x PWA with a 2.x server; a 1.x server is flagged as outdated).</p>
</main> </main>
+10 -6
View File
@@ -18,7 +18,7 @@
<div class="max-w-7xl mx-auto px-6 py-2"> <div class="max-w-7xl mx-auto px-6 py-2">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<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> <h1 class="text-2xl font-bold text-heading">SIMple <span id="app-version" class="text-xs text-gray-400 dark:text-slate-500 ml-1"></span></h1>
<div class="flex items-center gap-4"> <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" 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> <span id="state-indicator-dot" class="text-xs text-gray-400" title="Connecting..."></span>
@@ -1302,6 +1302,12 @@
</div> </div>
<script> <script>
// ===== Version =====
// Single source of truth for the PWA version: shown in the header and used
// by the server version check in pysimConnect().
const SIMPLE_VERSION = '2.3.1';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== Tab switching ===== // ===== Tab switching =====
function switchTab(name) { function switchTab(name) {
if (_scp81Timer && name !== 'scp81') { clearInterval(_scp81Timer); _scp81Timer = null; } if (_scp81Timer && name !== 'scp81') { clearInterval(_scp81Timer); _scp81Timer = null; }
@@ -4912,13 +4918,14 @@ async function pysimConnect() {
if (!ver.version) throw new Error('No version'); if (!ver.version) throw new Error('No version');
_pysimServerAvailable = true; _pysimServerAvailable = true;
const sv = (ver.version || '').split('.').map(Number); const sv = (ver.version || '').split('.').map(Number);
if (sv[0] < 1 || (sv[0] === 1 && sv[1] < 0)) { const pv = SIMPLE_VERSION.split('.').map(Number);
if (sv[0] < pv[0]) {
msgEl.innerHTML = '<span class="text-red-500">Server version ' + esc(ver.version) + ' is outdated. Please update pysim-simple-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.textContent = t('Connect');
btn.disabled = false; btn.disabled = false;
return; return;
} }
if (sv[0] > 1) { if (sv[0] > pv[0]) {
msgEl.innerHTML = '<span class="text-yellow-500">Warning: server version ' + esc(ver.version) + ' is newer than expected. Update SIMple 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'); const status = await pysimFetch('/api/status');
@@ -11400,7 +11407,6 @@ const LANG_RU = {
'Response data (hex)': 'Данные ответа (hex)', 'Response data (hex)': 'Данные ответа (hex)',
'— Select command —': '— Выберите команду —', '— Select command —': '— Выберите команду —',
'— select preset —': '— выберите предустановленный —', '— select preset —': '— выберите предустановленный —',
'SIM OTA with a Human Face': 'SIM OTA с человеческим лицом',
'Error: specify SELECT parameters': 'Ошибка: укажите параметры SELECT', 'Error: specify SELECT parameters': 'Ошибка: укажите параметры SELECT',
'Error: unknown command': 'Ошибка: неизвестная команда', 'Error: unknown command': 'Ошибка: неизвестная команда',
'Error: specify data': 'Ошибка: укажите данные', 'Error: specify data': 'Ошибка: укажите данные',
@@ -11890,8 +11896,6 @@ function translatePage() {
const key = el.getAttribute('data-l10n'); const key = el.getAttribute('data-l10n');
el.textContent = t(key); el.textContent = t(key);
}); });
const slogan = document.getElementById('slogan');
if (slogan) slogan.textContent = t('SIM OTA with a Human Face');
} }
function isViewVisible(id) { function isViewVisible(id) {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "SIMple", "name": "SIMple",
"short_name": "SIMple", "short_name": "SIMple",
"description": "Offline APDU helper for SIM/USIM/RAM commands", "description": "UICC/SIM workbench: APDU toolkit, SIM OTA (SCP80/SCP81), STK/CAT simulator, card profiler",
"start_url": "index.html", "start_url": "index.html",
"display": "standalone", "display": "standalone",
"background_color": "#fafafa", "background_color": "#fafafa",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "simple", "name": "simple",
"version": "1.0.0", "version": "1.0.0",
"description": "Standalone offline HTML/JS tool for building APDU commands for SIM, USIM, and GlobalPlatform RAM, plus encoding conversions.", "description": "SIMple — a UICC workbench: APDU toolkit, SCP80/SCP81 OTA lab, STK/CAT (phone) simulator and card profiler.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "npx tailwindcss -i src/style.css -o style.css --config tailwind.config.js && cat src/contrast.css >> style.css", "build": "npx tailwindcss -i src/style.css -o style.css --config tailwind.config.js && cat src/contrast.css >> style.css",
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simple-v189'; const CACHE = 'simple-v190';
const URLS = [ const URLS = [
'index.html', 'index.html',
'help.html', 'help.html',
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "pysim-simple-server" name = "pysim-simple-server"
version = "2.3.0" version = "2.3.1"
description = "HTTP REST server wrapping pysim for the SIMple PWA" description = "HTTP REST server wrapping pysim for the SIMple PWA"
requires-python = ">=3.8" requires-python = ">=3.8"
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh. # pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
+1 -1
View File
@@ -21,7 +21,7 @@ from osmocom.construct import GsmOrUcs2Adapter
from osmocom.tlv import BER_TLV_IE from osmocom.tlv import BER_TLV_IE
VERSION = '2.3.0' VERSION = '2.3.1'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE