EF.SST: tri-state editor (deleted / allocated-off / on)
Give EF.SST its own per-file editor for the 2-bit SIM service table
(TS 51.011 10.3.7: allocated + activated per service):
- efEncSst encodes the pair layout, preserving unmodelled pairs and the
file length; validates service number, fit and state.
- A new `bits2` field kind renders a three-state select per service and
collects {n, name, allocated, activated}; registered as EF_SST_FIELDS
with the encoder (df '7F20'), and SST leaves EF_READONLY_NAMES.
- i18n: Deleted/Off/On + the state-error and hint strings (EN/RU).
Docs/help updated: SST is no longer read-only. Frontend 1093 + Python
1090 tests green. (Version/sw bump still deferred for the unreleased
decoded-editing batch.)
This commit is contained in:
1 parent
5ebcd0773d
commit
fc27feba8e
6 files changed
+296
-30
No files matched your search
@@ -64,7 +64,7 @@ editing candidates.
|
|||||||
| EF.IMSI | 6F07 | TS 31.102 4.2.2 | 9 B | length byte + parity/type nibble ('9' odd / '1' even) + BCD | S3 | **3.34** |
|
| EF.IMSI | 6F07 | TS 31.102 4.2.2 | 9 B | length byte + parity/type nibble ('9' odd / '1' even) + BCD | S3 | **3.34** |
|
||||||
| EF.IST | 6F07 | TS 31.102 4.2.2 (SAIP) | 1–2 B | service bits | S6 | **3.34** |
|
| EF.IST | 6F07 | TS 31.102 4.2.2 (SAIP) | 1–2 B | service bits | S6 | **3.34** |
|
||||||
| EF.UST | 6F38 | TS 31.102 4.2.8 | X ≥ 1 | service bits | S6 | **3.34** |
|
| EF.UST | 6F38 | TS 31.102 4.2.8 | X ≥ 1 | service bits | S6 | **3.34** |
|
||||||
| EF.SST | 6F38 | TS 51.011 10.3.7 | X ≥ 1 | **2 bits/service** (allocated + activated) | S6 (pending) | decoder fixed; editor later |
|
| EF.SST | 6F38 | TS 51.011 10.3.7 | X ≥ 1 | **2 bits/service** (allocated + activated) | S6 | **shipped** — tri-state editor (deleted / allocated-off / on) |
|
||||||
| EF.EST | 6F56 | TS 31.102 4.2.47 | X ≥ 1 | enabled-service bits (3 services) | S6 | **3.34** |
|
| EF.EST | 6F56 | TS 31.102 4.2.47 | X ≥ 1 | enabled-service bits (3 services) | S6 | **3.34** |
|
||||||
| EF.LI | 6F05 | TS 31.102 4.2.1 | 2n | ISO 639 letters (2 B each) | S6 | 3.35 |
|
| EF.LI | 6F05 | TS 31.102 4.2.1 | 2n | ISO 639 letters (2 B each) | S6 | 3.35 |
|
||||||
| EF.LP | 6F05 | TS 51.011 10.3.1 | n | **1 byte per language** (SMS 7-bit) | S6 | 3.35 |
|
| EF.LP | 6F05 | TS 51.011 10.3.1 | n | **1 byte per language** (SMS 7-bit) | S6 | 3.35 |
|
||||||
@@ -194,8 +194,8 @@ OPLMNwAcT/OPL/PNN/EHPLMN/FPLMN/SPDI/IMG are USIM-only (no SIM counterpart).
|
|||||||
8. **EF.SST is a 2-bit table** (TS 51.011 10.3.7): 4 services per byte, first
|
8. **EF.SST is a 2-bit table** (TS 51.011 10.3.7): 4 services per byte, first
|
||||||
bit of the pair = allocated, second = activated. The shared 1-bit
|
bit of the pair = allocated, second = activated. The shared 1-bit
|
||||||
UST/EST codec was used for it, so SIM service tables were both misread and
|
UST/EST codec was used for it, so SIM service tables were both misread and
|
||||||
miswritten; the decoder is fixed in v3.34.0, editing waits for an
|
miswritten; the decoder is fixed in v3.34.0, and the tri-state editor
|
||||||
allocated/activated UI.
|
(deleted / allocated-off / on) shipped with the DF-context work.
|
||||||
9. **EF.ACC class numbering** (TS 31.102 4.2.15): byte 1 b8…b1 = classes 15…8
|
9. **EF.ACC class numbering** (TS 31.102 4.2.15): byte 1 b8…b1 = classes 15…8
|
||||||
and byte 2 = 7…0 (class 10 is written as 0); the old decoder counted from
|
and byte 2 = 7…0 (class 10 is written as 0); the old decoder counted from
|
||||||
class 0 at the MSB. Fixed in v3.34.0.
|
class 0 at the MSB. Fixed in v3.34.0.
|
||||||
@@ -228,7 +228,7 @@ OPLMNwAcT/OPL/PNN/EHPLMN/FPLMN/SPDI/IMG are USIM-only (no SIM counterpart).
|
|||||||
(filterable bit grid), ACC, PHASE, HPPLMN, AD, the PLMN-only lists
|
(filterable bit grid), ACC, PHASE, HPPLMN, AD, the PLMN-only lists
|
||||||
(PLMNsel/FPLMN/EHPLMN), ICCID/IMSI (behind a confirmation) and
|
(PLMNsel/FPLMN/EHPLMN), ICCID/IMSI (behind a confirmation) and
|
||||||
CBMI/CBMID/CBMIR. Decoder fixes: ACC class numbering, the AD operation
|
CBMI/CBMID/CBMIR. Decoder fixes: ACC class numbering, the AD operation
|
||||||
mode / MNC-length rules, the SST 2-bit layout (read-only for now) and the
|
mode / MNC-length rules, the SST 2-bit layout and the
|
||||||
IMSI parity nibble.
|
IMSI parity nibble.
|
||||||
- **v3.35.0 (shipped)** — EF.SMSP as the first S10 dedicated builder: the full
|
- **v3.35.0 (shipped)** — EF.SMSP as the first S10 dedicated builder: the full
|
||||||
parameter form (alpha; TP-DA and TS-SCA with their own length conventions
|
parameter form (alpha; TP-DA and TS-SCA with their own length conventions
|
||||||
@@ -267,9 +267,10 @@ OPLMNwAcT/OPL/PNN/EHPLMN/FPLMN/SPDI/IMG are USIM-only (no SIM counterpart).
|
|||||||
|
|
||||||
A file-agnostic service editor for the Card reader tab, in addition to the
|
A file-agnostic service editor for the Card reader tab, in addition to the
|
||||||
per-file decoded editors (which stay independent). It presents the four
|
per-file decoded editors (which stay independent). It presents the four
|
||||||
service tables side by side and writes them back consistently, so EF.SST's
|
service tables side by side and writes them back consistently. EF.SST also
|
||||||
allocated/activated layout no longer needs its own per-file grid (SST stays
|
has its own per-file tri-state editor (deleted / allocated-off / on); the
|
||||||
read-only in the per-file editor).
|
Services editor's SIM column only toggles on / allocated-off and never
|
||||||
|
de-allocates.
|
||||||
|
|
||||||
- **Scope**: EF.SST (DF.GSM, 2 bits/service: allocated + activated), EF.UST
|
- **Scope**: EF.SST (DF.GSM, 2 bits/service: allocated + activated), EF.UST
|
||||||
(ADF.USIM, 1 bit), EF.EST (ADF.USIM, a 3-bit *enabled* subset of UST:
|
(ADF.USIM, 1 bit), EF.EST (ADF.USIM, a 3-bit *enabled* subset of UST:
|
||||||
|
|||||||
@@ -409,7 +409,7 @@
|
|||||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||||
<li><strong>Читать сырые данные / Декодировать</strong> — чтение выбранного файла в виде hex-дампа или таблицы декодированных полей (клиентские декодеры EF: IMSI, ICCID, SPN, списки PLMN, LOCI/PSLOCI/EPSLOCI, ADN/MSISDN, таблицы сервисов, SUME, …); таблица декодируется в браузере из тех же сырых байт (без серверного декодирования). Подсвеченная кнопка — текущий вид; нажатие любой из них (пере)читывает файл. «Декодировать» недоступна, если для файла нет клиентского декодера (проверка по имени/FID до чтения).</li>
|
<li><strong>Читать сырые данные / Декодировать</strong> — чтение выбранного файла в виде hex-дампа или таблицы декодированных полей (клиентские декодеры EF: IMSI, ICCID, SPN, списки PLMN, LOCI/PSLOCI/EPSLOCI, ADN/MSISDN, таблицы сервисов, SUME, …); таблица декодируется в браузере из тех же сырых байт (без серверного декодирования). Подсвеченная кнопка — текущий вид; нажатие любой из них (пере)читывает файл. «Декодировать» недоступна, если для файла нет клиентского декодера (проверка по имени/FID до чтения).</li>
|
||||||
<li><strong>Редактировать сырые данные</strong> — изменение hex-данных, <strong>Сохранить</strong> для записи (или <strong>Отмена</strong>); transparent-файлы редактируются в textarea, record-файлы — по одному полю на запись с флажками выбора. «Редактировать сырые данные» сначала переключает на hex-вид и при необходимости читает файл.</li>
|
<li><strong>Редактировать сырые данные</strong> — изменение hex-данных, <strong>Сохранить</strong> для записи (или <strong>Отмена</strong>); transparent-файлы редактируются в textarea, record-файлы — по одному полю на запись с флажками выбора. «Редактировать сырые данные» сначала переключает на hex-вид и при необходимости читает файл.</li>
|
||||||
<li><strong>Редактировать декодированные</strong> — редактирование файла в его собственной декодированной форме; кодирование выполняется в браузере, запись идёт обычным путём обновления. Кодировщики покрывают <strong>EF.SPN</strong>, <strong>EF.PNN</strong>, списки PLMN (<strong>PLMNsel / FPLMN / EHPLMN</strong> и списки с технологиями доступа <strong>PLMNwAcT / OPLMNwAcT / HPLMNwAcT</strong>), файлы номеров (<strong>ADN / FDN / SDN / MSISDN / LND / BDN</strong>), таблицы сервисов (<strong>UST / IST / EST</strong>; <strong>SST</strong> остаётся только для чтения до появления редактора allocated/activated), <strong>ACC</strong>, <strong>PHASE</strong>, <strong>HPPLMN</strong>, <strong>AD</strong>, <strong>ICCID / IMSI</strong> (с подтверждением — запись меняет идентичность карты) списки cell broadcast (<strong>CBMI / CBMID / CBMIR</strong>) и <strong>EF.SMSP</strong> (полная форма SMS-параметров: alpha, TP-DA и TS-SCA с их правилами адресации и TON, TP-PID, TP-DCS и относительный TP-VP; у каждого параметра — флажок <em>«отсутствует (действует значение ME по умолчанию)»</em> и живые подсказки по DCS и сроку действия). Для transparent-файла — одна форма (поля SPN, строки PLMN с добавлением/удалением, элементы PNN с добавлением/удалением); для record-файла — по форме на запись с флажком выбора, который ставится автоматически при изменении поля, поэтому записываются только изменённые записи. <strong>Сохранить</strong> проверяет все выбранные записи до записи; для файлов без кодировщика кнопка недоступна с подсказкой. Указатели расширений (EF.EXT1–EF.EXT5) и байт метода сравнения BDN сохраняются; редактирование номера, продолжение которого лежит в файле расширения, отклоняется.</li>
|
<li><strong>Редактировать декодированные</strong> — редактирование файла в его собственной декодированной форме; кодирование выполняется в браузере, запись идёт обычным путём обновления. Кодировщики покрывают <strong>EF.SPN</strong>, <strong>EF.PNN</strong>, списки PLMN (<strong>PLMNsel / FPLMN / EHPLMN</strong> и списки с технологиями доступа <strong>PLMNwAcT / OPLMNwAcT / HPLMNwAcT</strong>), файлы номеров (<strong>ADN / FDN / SDN / MSISDN / LND / BDN</strong>), таблицы сервисов (<strong>UST / IST / EST</strong> и <strong>SST</strong> — SIM-таблица, редактируется по каждой службе как удалена / выделена-выключена / включена), <strong>ACC</strong>, <strong>PHASE</strong>, <strong>HPPLMN</strong>, <strong>AD</strong>, <strong>ICCID / IMSI</strong> (с подтверждением — запись меняет идентичность карты) списки cell broadcast (<strong>CBMI / CBMID / CBMIR</strong>) и <strong>EF.SMSP</strong> (полная форма SMS-параметров: alpha, TP-DA и TS-SCA с их правилами адресации и TON, TP-PID, TP-DCS и относительный TP-VP; у каждого параметра — флажок <em>«отсутствует (действует значение ME по умолчанию)»</em> и живые подсказки по DCS и сроку действия). Для transparent-файла — одна форма (поля SPN, строки PLMN с добавлением/удалением, элементы PNN с добавлением/удалением); для record-файла — по форме на запись с флажком выбора, который ставится автоматически при изменении поля, поэтому записываются только изменённые записи. <strong>Сохранить</strong> проверяет все выбранные записи до записи; для файлов без кодировщика кнопка недоступна с подсказкой. Указатели расширений (EF.EXT1–EF.EXT5) и байт метода сравнения BDN сохраняются; редактирование номера, продолжение которого лежит в файле расширения, отклоняется.</li>
|
||||||
<li><strong>Проверить все файлы</strong> — обход всего дерева (включая пользовательские файлы) с пометкой каждого элемента: есть (обычный вид) или нет (красный ✗, без стрелки разворачивания); существующие пустые DF показывают <code class="font-mono text-sm">(пусто)</code>. Отображается прогресс <em>N / всего</em>, обход можно остановить; в конце — сводка «есть/нет». Файлы проверяются только при разворачивании или проверке — просмотр остаётся ленивым.</li>
|
<li><strong>Проверить все файлы</strong> — обход всего дерева (включая пользовательские файлы) с пометкой каждого элемента: есть (обычный вид) или нет (красный ✗, без стрелки разворачивания); существующие пустые DF показывают <code class="font-mono text-sm">(пусто)</code>. Отображается прогресс <em>N / всего</em>, обход можно остановить; в конце — сводка «есть/нет». Файлы проверяются только при разворачивании или проверке — просмотр остаётся ленивым.</li>
|
||||||
<li><strong>ADM</strong> — файлы, требующие администраторский PIN, возвращают <code class="font-mono text-sm">6982</code>/<code class="font-mono text-sm">9804</code>; если в подходящем пресете карты (тот же ICCID) есть ключ ADM, рядом с ошибкой появляется кнопка <strong>«Проверить ADM»</strong>, а значок в заголовке (<code class="font-mono text-sm">ADM ✓/✗ ⚿</code>) становится кликабельным. Каждый неверный ключ расходует попытку: остаток показывается, а повторная попытка требует подтверждения. Заблокированный ADM здесь не восстановить — нужен ключ разблокировки карты.</li>
|
<li><strong>ADM</strong> — файлы, требующие администраторский PIN, возвращают <code class="font-mono text-sm">6982</code>/<code class="font-mono text-sm">9804</code>; если в подходящем пресете карты (тот же ICCID) есть ключ ADM, рядом с ошибкой появляется кнопка <strong>«Проверить ADM»</strong>, а значок в заголовке (<code class="font-mono text-sm">ADM ✓/✗ ⚿</code>) становится кликабельным. Каждый неверный ключ расходует попытку: остаток показывается, а повторная попытка требует подтверждения. Заблокированный ADM здесь не восстановить — нужен ключ разблокировки карты.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
+1
-1
@@ -408,7 +408,7 @@
|
|||||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||||
<li><strong>Read raw / Read decoded</strong> — read the selected file as a hex dump or as a decoded field table (client-side EF decoders: IMSI, ICCID, SPN, PLMN lists, LOCI/PSLOCI/EPSLOCI, ADN/MSISDN, service tables, SUME, …); the decoded table is built in the browser from the same raw bytes (no server-side decode). The highlighted pill is the current view; clicking either pill (re-)reads the file. <strong>Read decoded</strong> is disabled when the client-side decoders do not cover the file (name/FID check, before reading).</li>
|
<li><strong>Read raw / Read decoded</strong> — read the selected file as a hex dump or as a decoded field table (client-side EF decoders: IMSI, ICCID, SPN, PLMN lists, LOCI/PSLOCI/EPSLOCI, ADN/MSISDN, service tables, SUME, …); the decoded table is built in the browser from the same raw bytes (no server-side decode). The highlighted pill is the current view; clicking either pill (re-)reads the file. <strong>Read decoded</strong> is disabled when the client-side decoders do not cover the file (name/FID check, before reading).</li>
|
||||||
<li><strong>Edit raw</strong> — edit the raw hex data, <strong>Save</strong> to write back (or <strong>Cancel</strong>); transparent files use a textarea, record files one input per record with selection checkboxes. <strong>Edit raw</strong> switches to the hex view first and reads the file if needed.</li>
|
<li><strong>Edit raw</strong> — edit the raw hex data, <strong>Save</strong> to write back (or <strong>Cancel</strong>); transparent files use a textarea, record files one input per record with selection checkboxes. <strong>Edit raw</strong> switches to the hex view first and reads the file if needed.</li>
|
||||||
<li><strong>Edit decoded</strong> — edit the file through its own decoded form, encoded in the browser and written with the normal update path. The encoders cover <strong>EF.SPN</strong>, <strong>EF.PNN</strong>, the PLMN lists (<strong>PLMNsel / FPLMN / EHPLMN</strong> and the access-technology lists <strong>PLMNwAcT / OPLMNwAcT / HPLMNwAcT</strong>), the dialling-number files (<strong>ADN / FDN / SDN / MSISDN / LND / BDN</strong>), the service tables (<strong>UST / IST / EST</strong>; <strong>SST</strong> stays read-only until the SIM allocated/activated editor exists), <strong>ACC</strong>, <strong>PHASE</strong>, <strong>HPPLMN</strong>, <strong>AD</strong>, <strong>ICCID / IMSI</strong> (behind a confirmation — writing them changes the card identity) the cell-broadcast lists (<strong>CBMI / CBMID / CBMIR</strong>) and <strong>EF.SMSP</strong> (the full SMS-parameter form: alpha, TP-DA and TS-SCA with their addressing rules and TON, TP-PID, TP-DCS and the relative TP-VP, each with an <em>absent (the ME default applies)</em> toggle and live DCS/validity hints). A transparent file gets one form (SPN fields, PLMN rows with add/remove, PNN items with add/remove); a record file gets one form per record with a selection checkbox that is auto-checked when a field changes, so only the edited records are written. <strong>Save</strong> validates every selected record before writing anything; files without an encoder keep the button disabled with a tooltip. Extension pointers (EF.EXT1–EF.EXT5) and the BDN comparison-method byte are preserved, and editing a number that continues in an extension file is refused.</li>
|
<li><strong>Edit decoded</strong> — edit the file through its own decoded form, encoded in the browser and written with the normal update path. The encoders cover <strong>EF.SPN</strong>, <strong>EF.PNN</strong>, the PLMN lists (<strong>PLMNsel / FPLMN / EHPLMN</strong> and the access-technology lists <strong>PLMNwAcT / OPLMNwAcT / HPLMNwAcT</strong>), the dialling-number files (<strong>ADN / FDN / SDN / MSISDN / LND / BDN</strong>), the service tables (<strong>UST / IST / EST</strong> and <strong>SST</strong> — the SIM table, edited per service as deleted / allocated-off / on), <strong>ACC</strong>, <strong>PHASE</strong>, <strong>HPPLMN</strong>, <strong>AD</strong>, <strong>ICCID / IMSI</strong> (behind a confirmation — writing them changes the card identity) the cell-broadcast lists (<strong>CBMI / CBMID / CBMIR</strong>) and <strong>EF.SMSP</strong> (the full SMS-parameter form: alpha, TP-DA and TS-SCA with their addressing rules and TON, TP-PID, TP-DCS and the relative TP-VP, each with an <em>absent (the ME default applies)</em> toggle and live DCS/validity hints). A transparent file gets one form (SPN fields, PLMN rows with add/remove, PNN items with add/remove); a record file gets one form per record with a selection checkbox that is auto-checked when a field changes, so only the edited records are written. <strong>Save</strong> validates every selected record before writing anything; files without an encoder keep the button disabled with a tooltip. Extension pointers (EF.EXT1–EF.EXT5) and the BDN comparison-method byte are preserved, and editing a number that continues in an extension file is refused.</li>
|
||||||
<li><strong>Probe all files</strong> — walks the whole tree (including custom files) and marks every entry present (normal) or absent (red ✗, no expand arrow); empty-but-present DFs show <code class="font-mono text-sm">(empty)</code>. Shows progress <em>N / total</em>, can be stopped, and finishes with a present/absent summary. Files are only verified when expanded or probed — browsing stays lazy.</li>
|
<li><strong>Probe all files</strong> — walks the whole tree (including custom files) and marks every entry present (normal) or absent (red ✗, no expand arrow); empty-but-present DFs show <code class="font-mono text-sm">(empty)</code>. Shows progress <em>N / total</em>, can be stopped, and finishes with a present/absent summary. Files are only verified when expanded or probed — browsing stays lazy.</li>
|
||||||
<li><strong>ADM</strong> — files that need the administrator PIN fail with <code class="font-mono text-sm">6982</code>/<code class="font-mono text-sm">9804</code>; if the matching card preset (same ICCID) carries an ADM key, a <strong>Verify ADM</strong> button appears next to the error and the header badge (<code class="font-mono text-sm">ADM ✓/✗ ⚿</code>) becomes clickable. Every wrong key consumes an attempt: the remaining attempts are shown and a retry asks for confirmation. A blocked ADM cannot be recovered here — it needs the card’s unblock key.</li>
|
<li><strong>ADM</strong> — files that need the administrator PIN fail with <code class="font-mono text-sm">6982</code>/<code class="font-mono text-sm">9804</code>; if the matching card preset (same ICCID) carries an ADM key, a <strong>Verify ADM</strong> button appears next to the error and the header badge (<code class="font-mono text-sm">ADM ✓/✗ ⚿</code>) becomes clickable. Every wrong key consumes an attempt: the remaining attempts are shown and a retry asks for confirmation. A blocked ADM cannot be recovered here — it needs the card’s unblock key.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
+115
-5
@@ -23988,7 +23988,7 @@ const EF_DECODERS = [
|
|||||||
{ name: 'EF.IMSI', fid: '6f07', fn: efDecImsi },
|
{ name: 'EF.IMSI', fid: '6f07', fn: efDecImsi },
|
||||||
{ name: 'EF.IST', fid: '6f07', fn: b => efDecServiceTable(b, EF_IST_SERVICES) },
|
{ name: 'EF.IST', fid: '6f07', fn: b => efDecServiceTable(b, EF_IST_SERVICES) },
|
||||||
{ name: 'EF.UST', fid: '6f38', fn: b => efDecServiceTable(b, EF_UST_SERVICES) },
|
{ name: 'EF.UST', fid: '6f38', fn: b => efDecServiceTable(b, EF_UST_SERVICES) },
|
||||||
{ name: 'EF.SST', fid: '6f38', fn: efDecSst },
|
{ name: 'EF.SST', df: '7F20', fid: '6f38', fn: efDecSst },
|
||||||
{ name: 'EF.EST', fid: '6f56', fn: b => efDecServiceTable(b, EF_EST_SERVICES) },
|
{ name: 'EF.EST', fid: '6f56', fn: b => efDecServiceTable(b, EF_EST_SERVICES) },
|
||||||
{ name: 'EF.HPPLMN', fid: '6f31', fn: efDecHpplmn },
|
{ name: 'EF.HPPLMN', fid: '6f31', fn: efDecHpplmn },
|
||||||
{ name: 'EF.LI', fid: '6f05', fn: efDecLi },
|
{ name: 'EF.LI', fid: '6f05', fn: efDecLi },
|
||||||
@@ -24467,6 +24467,51 @@ function efEncServiceTable(values, origHex, table) {
|
|||||||
return efHex(out);
|
return efHex(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EF.SST (TS 51.011 10.3.7, the SIM service table): two bits per service,
|
||||||
|
// four services per byte - the first bit of the pair (b1/b3/b5/b7) is
|
||||||
|
// allocated, the second (b2/b4/b6/b8) activated, so a pair is 00 deleted,
|
||||||
|
// 10 allocated but not activated or 11 allocated and activated. The original
|
||||||
|
// bytes are the base: both pair bits of every modelled service are cleared
|
||||||
|
// and the listed ones set again, so a pair of a service the 59-entry table
|
||||||
|
// does not name (the decoder reports it as RFU) keeps its original bits and
|
||||||
|
// the file length survives an edit. An entry is the decoder's own shape
|
||||||
|
// {n, name, allocated, activated} (the collector produces it), so
|
||||||
|
// enc(decode(orig), orig) reproduces the file of a spec-canonical card;
|
||||||
|
// activated without allocated is not a legal pair (10.3.7) and is refused.
|
||||||
|
function efEncSst(values, origHex) {
|
||||||
|
const orig = efBytes(origHex || '');
|
||||||
|
const out = new Uint8Array(orig.length);
|
||||||
|
out.set(orig, 0);
|
||||||
|
for (const key of Object.keys(EF_SST_SERVICES)) {
|
||||||
|
const n = Number(key);
|
||||||
|
if (n < 1 || (n - 1) >= out.length * 4) continue;
|
||||||
|
out[(n - 1) >> 2] &= ~(3 << (2 * ((n - 1) & 3)));
|
||||||
|
}
|
||||||
|
for (const item of (values && values.services) || []) {
|
||||||
|
const n = Number(item && typeof item === 'object' ? item.n : item);
|
||||||
|
// the table names services 1-59; a pair beyond it belongs to a longer
|
||||||
|
// file (the decoder reports those as RFU too), so only a number that
|
||||||
|
// cannot be a service at all is unknown
|
||||||
|
if (!Number.isInteger(n) || n < 1) {
|
||||||
|
throw efEncError('Unknown service', ': ' + (item && item.n));
|
||||||
|
}
|
||||||
|
if ((n - 1) >= out.length * 4) {
|
||||||
|
throw efEncError('The service table is too short for service', ' ' + n);
|
||||||
|
}
|
||||||
|
if (!item || typeof item !== 'object') {
|
||||||
|
throw efEncError('The service state must be deleted, off or on', ': ' + n);
|
||||||
|
}
|
||||||
|
const allocated = !!item.allocated;
|
||||||
|
const activated = !!item.activated;
|
||||||
|
if (activated && !allocated) {
|
||||||
|
throw efEncError('The service state must be deleted, off or on', ': ' + n);
|
||||||
|
}
|
||||||
|
const pair = activated ? 3 : (allocated ? 1 : 0);
|
||||||
|
out[(n - 1) >> 2] |= pair << (2 * ((n - 1) & 3));
|
||||||
|
}
|
||||||
|
return efHex(out);
|
||||||
|
}
|
||||||
|
|
||||||
// EF.ACC (TS 31.102 4.2.15): class c is bit c of the 16-bit value (class 15 is
|
// EF.ACC (TS 31.102 4.2.15): class c is bit c of the 16-bit value (class 15 is
|
||||||
// the MSB); ACC 10 is not used and is written as 0.
|
// the MSB); ACC 10 is not used and is written as 0.
|
||||||
function efEncAcc(values, origHex) {
|
function efEncAcc(values, origHex) {
|
||||||
@@ -25118,6 +25163,13 @@ const EF_ACC_CLASSES = {
|
|||||||
const EF_UST_FIELDS = [{ key: 'services', label: 'Services', kind: 'bits', items: EF_UST_SERVICES }];
|
const EF_UST_FIELDS = [{ key: 'services', label: 'Services', kind: 'bits', items: EF_UST_SERVICES }];
|
||||||
const EF_IST_FIELDS = [{ key: 'services', label: 'Services', kind: 'bits', items: EF_IST_SERVICES }];
|
const EF_IST_FIELDS = [{ key: 'services', label: 'Services', kind: 'bits', items: EF_IST_SERVICES }];
|
||||||
const EF_EST_FIELDS = [{ key: 'services', label: 'Services', kind: 'bits', items: EF_EST_SERVICES }];
|
const EF_EST_FIELDS = [{ key: 'services', label: 'Services', kind: 'bits', items: EF_EST_SERVICES }];
|
||||||
|
// EF.SST is the SIM table with two bits per service: the tri-state grid
|
||||||
|
// (deleted 00 / off 10 / on 11) instead of the 1-bit checkbox grid.
|
||||||
|
const EF_SST_FIELDS = [{
|
||||||
|
key: 'services', label: 'Services', kind: 'bits2', items: EF_SST_SERVICES,
|
||||||
|
hint: 'Two bits per service (TS 51.011 10.3.7): deleted = not allocated (00), '
|
||||||
|
+ 'off = allocated but not activated (10), on = allocated and activated (11).',
|
||||||
|
}];
|
||||||
|
|
||||||
const EF_ACC_FIELDS = [{ key: 'access_classes', label: 'Access classes', kind: 'bits', items: EF_ACC_CLASSES }];
|
const EF_ACC_FIELDS = [{ key: 'access_classes', label: 'Access classes', kind: 'bits', items: EF_ACC_CLASSES }];
|
||||||
|
|
||||||
@@ -25186,6 +25238,7 @@ const EF_ENCODERS = [
|
|||||||
confirm: 'Writing the IMSI changes the card identity and may break network registration. Continue?' },
|
confirm: 'Writing the IMSI changes the card identity and may break network registration. Continue?' },
|
||||||
{ name: 'EF.IST', fid: '6f07', enc: (v, o) => efEncServiceTable(v, o, EF_IST_SERVICES), fields: EF_IST_FIELDS },
|
{ name: 'EF.IST', fid: '6f07', enc: (v, o) => efEncServiceTable(v, o, EF_IST_SERVICES), fields: EF_IST_FIELDS },
|
||||||
{ name: 'EF.UST', fid: '6f38', enc: (v, o) => efEncServiceTable(v, o, EF_UST_SERVICES), fields: EF_UST_FIELDS },
|
{ name: 'EF.UST', fid: '6f38', enc: (v, o) => efEncServiceTable(v, o, EF_UST_SERVICES), fields: EF_UST_FIELDS },
|
||||||
|
{ name: 'EF.SST', df: '7F20', fid: '6f38', enc: efEncSst, fields: EF_SST_FIELDS },
|
||||||
{ name: 'EF.EST', fid: '6f56', enc: (v, o) => efEncServiceTable(v, o, EF_EST_SERVICES), fields: EF_EST_FIELDS },
|
{ name: 'EF.EST', fid: '6f56', enc: (v, o) => efEncServiceTable(v, o, EF_EST_SERVICES), fields: EF_EST_FIELDS },
|
||||||
{ name: 'EF.ACC', fid: '6f78', enc: efEncAcc, fields: EF_ACC_FIELDS },
|
{ name: 'EF.ACC', fid: '6f78', enc: efEncAcc, fields: EF_ACC_FIELDS },
|
||||||
{ name: 'EF.PHASE', fid: '6fae', enc: efEncPhase, model: efPhaseModel, fields: EF_PHASE_FIELDS },
|
{ name: 'EF.PHASE', fid: '6fae', enc: efEncPhase, model: efPhaseModel, fields: EF_PHASE_FIELDS },
|
||||||
@@ -25217,9 +25270,10 @@ const EF_ENCODERS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Decoder-only names that share a FID with an editable file: the FID fallback
|
// Decoder-only names that share a FID with an editable file: the FID fallback
|
||||||
// must not pick the wrong layout (EF.SST is not EF.UST on 6F38 - its 2-bit
|
// must not pick the wrong layout. (The list is empty now - EF.SST got its
|
||||||
// SIM layout has no editor yet).
|
// own 2-bit editor on 6F38 - but the guard stays for a future decoder-only
|
||||||
const EF_READONLY_NAMES = ['EF.SST'];
|
// collision.)
|
||||||
|
const EF_READONLY_NAMES = [];
|
||||||
|
|
||||||
function efFindEncoder(name, fid, df) {
|
function efFindEncoder(name, fid, df) {
|
||||||
let n = (name || '').toUpperCase();
|
let n = (name || '').toUpperCase();
|
||||||
@@ -25500,6 +25554,31 @@ function efDecFieldHtml(f, value) {
|
|||||||
'<span class="font-mono text-gray-500 dark:text-slate-400">' + n + '</span>' +
|
'<span class="font-mono text-gray-500 dark:text-slate-400">' + n + '</span>' +
|
||||||
'<span class="break-all">' + esc(t(name)) + '</span></label>';
|
'<span class="break-all">' + esc(t(name)) + '</span></label>';
|
||||||
}).join('') + '</div></div>';
|
}).join('') + '</div></div>';
|
||||||
|
} else if (f.kind === 'bits2') {
|
||||||
|
// the tri-state grid (EF.SST): one select per service, the decoder's
|
||||||
|
// entries preselected (an absent service is a deleted pair)
|
||||||
|
const states = {};
|
||||||
|
for (const v of (value || [])) {
|
||||||
|
if (!v || typeof v !== 'object') continue;
|
||||||
|
states[String(v.n)] = v.activated && v.allocated ? 'on' : (v.allocated ? 'off' : 'deleted');
|
||||||
|
}
|
||||||
|
const nums = Object.keys(f.items || {}).map(Number).sort((a, b) => a - b);
|
||||||
|
const states3 = [['deleted', t('Deleted')], ['off', t('Off')], ['on', t('On')]];
|
||||||
|
input = '<div class="pysim-dec-bits border border-gray-200 dark:border-slate-700 rounded p-2">' +
|
||||||
|
'<input type="text" class="pysim-dec-bits-filter w-full mb-1 border border-gray-300 dark:border-slate-600 rounded px-1 bg-white dark:bg-slate-800 text-gray-800 dark:text-slate-200" placeholder="' +
|
||||||
|
esc(t('Filter services')) + '" oninput="pysimFsDecBits2Filter(this)">' +
|
||||||
|
'<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(17rem,1fr));max-height:12rem;overflow-y:auto">' +
|
||||||
|
nums.map(n => {
|
||||||
|
const name = String(f.items[n]);
|
||||||
|
const cur = states[String(n)] || 'deleted';
|
||||||
|
return '<label class="pysim-dec-bit2-row flex items-center gap-1" data-label="' + esc(n + ' ' + name) + '">' +
|
||||||
|
'<span class="font-mono text-gray-500 dark:text-slate-400">' + n + '</span>' +
|
||||||
|
'<span class="break-all flex-1">' + esc(t(name)) + '</span>' +
|
||||||
|
'<select data-bit2="' + n + '" data-bit2-name="' + esc(name) + '" onchange="pysimFsDecRowCheck(this)" class="border border-gray-300 dark:border-slate-600 rounded px-1 bg-white dark:bg-slate-800 text-gray-800 dark:text-slate-200">' +
|
||||||
|
states3.map(o => '<option value="' + o[0] + '"' + (o[0] === cur ? ' selected' : '') + '>' +
|
||||||
|
esc(o[1]) + '</option>').join('') +
|
||||||
|
'</select></label>';
|
||||||
|
}).join('') + '</div></div>';
|
||||||
} else if (f.kind === 'select') {
|
} else if (f.kind === 'select') {
|
||||||
const cur = String(value === undefined || value === null ? '' : value);
|
const cur = String(value === undefined || value === null ? '' : value);
|
||||||
const opts = (f.options || []).slice();
|
const opts = (f.options || []).slice();
|
||||||
@@ -25518,7 +25597,7 @@ function efDecFieldHtml(f, value) {
|
|||||||
' oninput="pysimFsDecRowCheck(this)" class="border border-gray-300 dark:border-slate-600 rounded px-1 bg-white dark:bg-slate-800 text-gray-800 dark:text-slate-200">';
|
' oninput="pysimFsDecRowCheck(this)" class="border border-gray-300 dark:border-slate-600 rounded px-1 bg-white dark:bg-slate-800 text-gray-800 dark:text-slate-200">';
|
||||||
}
|
}
|
||||||
const body = '<span class="text-gray-500 dark:text-slate-400">' + esc(t(f.label)) + '</span>' + input;
|
const body = '<span class="text-gray-500 dark:text-slate-400">' + esc(t(f.label)) + '</span>' + input;
|
||||||
const wide = f.kind === 'techs' || f.kind === 'bits';
|
const wide = f.kind === 'techs' || f.kind === 'bits' || f.kind === 'bits2';
|
||||||
if (!f.hint) {
|
if (!f.hint) {
|
||||||
return wide
|
return wide
|
||||||
? '<div class="pysim-dec-item flex flex-wrap items-center gap-x-2 gap-y-1">' + body + '</div>'
|
? '<div class="pysim-dec-item flex flex-wrap items-center gap-x-2 gap-y-1">' + body + '</div>'
|
||||||
@@ -25634,6 +25713,20 @@ function efDecCollectValues(root, fields) {
|
|||||||
name: cb.getAttribute('data-bit-name') || '' });
|
name: cb.getAttribute('data-bit-name') || '' });
|
||||||
});
|
});
|
||||||
out[f.key] = items;
|
out[f.key] = items;
|
||||||
|
} else if (f.kind === 'bits2') {
|
||||||
|
// the tri-state grid: every select is collected, a deleted pair as
|
||||||
|
// {allocated: false, activated: false} (the encoder clears it anyway)
|
||||||
|
const items = [];
|
||||||
|
root.querySelectorAll('[data-bit2]').forEach(sel => {
|
||||||
|
const state = String(sel.value || '');
|
||||||
|
items.push({
|
||||||
|
n: parseInt(sel.getAttribute('data-bit2'), 10),
|
||||||
|
name: sel.getAttribute('data-bit2-name') || '',
|
||||||
|
allocated: state === 'off' || state === 'on',
|
||||||
|
activated: state === 'on',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
out[f.key] = items;
|
||||||
} else {
|
} else {
|
||||||
const el = root.querySelector('[data-key="' + f.key + '"]');
|
const el = root.querySelector('[data-key="' + f.key + '"]');
|
||||||
if (!el) { out[f.key] = f.kind === 'bool' ? false : ''; continue; }
|
if (!el) { out[f.key] = f.kind === 'bool' ? false : ''; continue; }
|
||||||
@@ -25690,6 +25783,19 @@ function pysimFsDecBitsFilter(input) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The tri-state grid's text filter (EF.SST): hide the rows whose service name
|
||||||
|
// does not match. The query is matched against the row's label only - a
|
||||||
|
// select's textContent would carry every one of its three state options.
|
||||||
|
function pysimFsDecBits2Filter(input) {
|
||||||
|
const box = input && input.closest ? input.closest('.pysim-dec-bits') : null;
|
||||||
|
if (!box) return;
|
||||||
|
const q = String(input.value || '').toLowerCase();
|
||||||
|
box.querySelectorAll('.pysim-dec-bit2-row').forEach(row => {
|
||||||
|
const label = String(row.getAttribute('data-label') || '').toLowerCase();
|
||||||
|
row.style.display = label.indexOf(q) === -1 ? 'none' : '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function pysimFsDecAddRow() {
|
function pysimFsDecAddRow() {
|
||||||
if (!pysimFsSelected) return;
|
if (!pysimFsSelected) return;
|
||||||
const node = pysimFsFindNodeByPath(pysimFsSelected);
|
const node = pysimFsFindNodeByPath(pysimFsSelected);
|
||||||
@@ -26424,6 +26530,10 @@ const LANG_RU = {
|
|||||||
'The number continues in EF.EXT5 - editing it is not supported': 'Номер продолжается в EF.EXT5 — редактирование не поддерживается',
|
'The number continues in EF.EXT5 - editing it is not supported': 'Номер продолжается в EF.EXT5 — редактирование не поддерживается',
|
||||||
'Services': 'Сервисы',
|
'Services': 'Сервисы',
|
||||||
'Filter services': 'Фильтр сервисов',
|
'Filter services': 'Фильтр сервисов',
|
||||||
|
'Off': 'Выключен',
|
||||||
|
'On': 'Включён',
|
||||||
|
'The service state must be deleted, off or on': 'Состояние сервиса должно быть: удалён, выключен или включён',
|
||||||
|
'Two bits per service (TS 51.011 10.3.7): deleted = not allocated (00), off = allocated but not activated (10), on = allocated and activated (11).': 'Два бита на сервис (TS 51.011 10.3.7): удалён — не выделен (00), выключен — выделен, но не активирован (10), включён — выделен и активирован (11).',
|
||||||
'Access classes': 'Классы доступа',
|
'Access classes': 'Классы доступа',
|
||||||
'Phase': 'Фаза',
|
'Phase': 'Фаза',
|
||||||
'Interval': 'Интервал',
|
'Interval': 'Интервал',
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ const FNS = [
|
|||||||
'efEncPlmnList', 'efListRows', 'efPlmnRowOrig', 'efEncAdn', 'efEncAdnExt', 'efPnnNameBytes', 'efPnnHexBytes', 'efPnnOrigTlv', 'efEncPnn',
|
'efEncPlmnList', 'efListRows', 'efPlmnRowOrig', 'efEncAdn', 'efEncAdnExt', 'efPnnNameBytes', 'efPnnHexBytes', 'efPnnOrigTlv', 'efEncPnn',
|
||||||
'efFindEncoder', 'efHasEncoder', 'gsm7Decode', 'gsm7TextToSeptets',
|
'efFindEncoder', 'efHasEncoder', 'gsm7Decode', 'gsm7TextToSeptets',
|
||||||
'gsm7Encode', 'efEncError', 'efDecModelValues',
|
'gsm7Encode', 'efEncError', 'efDecModelValues',
|
||||||
'efDecServiceTable', 'efDecSst', 'efDecAcc', 'efDecPhase', 'efDecHpplmn', 'efDecAd', 'efDecCbmi', 'efDecCbmir', 'efDecIccid', 'efDecImsi', 'efDecPlmnList', 'efDecFplmn', 'efEncServiceTable', 'efEncAcc', 'efPhaseValue', 'efPhaseModel', 'efEncPhase', 'efEncHpplmn', 'efAdOpValue', 'efAdModel', 'efEncAd', 'efEncPlmnOnly', 'efEncIccid', 'efEncImsi', 'efCbmiModel', 'efEncCbmi', 'efCbmirModel', 'efEncCbmir', 'efSmspAddressModel', 'efSmspModel', 'efSmspTonOptions', 'efSmspParamRow', 'efSmspHtml', 'efSmspCollect', 'pysimFsSmspToggle', 'pysimFsSmspHexInput', 'efSmspHexByte', 'efSmspAddressBytes', 'efSmspAddrUnchanged', 'efSmspByteUnchanged', 'efEncSmsp', 'smspT', 'smspDcsHint', 'smspVpHint', 'smspDurationText', 'smspPluralIndex', 'smspCount', 'efSmspDaNumber',
|
'efDecServiceTable', 'efDecSst', 'efDecAcc', 'efDecPhase', 'efDecHpplmn', 'efDecAd', 'efDecCbmi', 'efDecCbmir', 'efDecIccid', 'efDecImsi', 'efDecPlmnList', 'efDecFplmn', 'efEncServiceTable', 'efEncSst', 'efEncAcc', 'efPhaseValue', 'efPhaseModel', 'efEncPhase', 'efEncHpplmn', 'efAdOpValue', 'efAdModel', 'efEncAd', 'efEncPlmnOnly', 'efEncIccid', 'efEncImsi', 'efCbmiModel', 'efEncCbmi', 'efCbmirModel', 'efEncCbmir', 'efSmspAddressModel', 'efSmspModel', 'efSmspTonOptions', 'efSmspParamRow', 'efSmspHtml', 'efSmspCollect', 'pysimFsSmspToggle', 'pysimFsSmspHexInput', 'efSmspHexByte', 'efSmspAddressBytes', 'efSmspAddrUnchanged', 'efSmspByteUnchanged', 'efEncSmsp', 'smspT', 'smspDcsHint', 'smspVpHint', 'smspDurationText', 'smspPluralIndex', 'smspCount', 'efSmspDaNumber',
|
||||||
];
|
];
|
||||||
|
|
||||||
let code = '';
|
let code = '';
|
||||||
for (const f of FNS) code += extractFunc(html, f) + '\n';
|
for (const f of FNS) code += extractFunc(html, f) + '\n';
|
||||||
for (const c of ['GSM7_ALPHABET', 'GSM7_EXT_MAP', 'EF_ACT_TECHS', 'EF_SPN_FIELDS',
|
for (const c of ['GSM7_ALPHABET', 'GSM7_EXT_MAP', 'EF_ACT_TECHS', 'EF_SPN_FIELDS',
|
||||||
'EF_ADN_FIELDS', 'EF_PLMN_LIST', 'EF_PNN_ITEMS', 'EF_UST_SERVICES', 'EF_SST_SERVICES', 'EF_IST_SERVICES', 'EF_EST_SERVICES', 'EF_PHASE_NAMES', 'EF_ACC_CLASSES', 'EF_UST_FIELDS', 'EF_IST_FIELDS', 'EF_EST_FIELDS', 'EF_ACC_FIELDS', 'EF_PHASE_FIELDS', 'EF_HPPLMN_FIELDS', 'EF_AD_OP_MODES', 'EF_AD_FIELDS', 'EF_PLMN_ONLY_LIST', 'EF_ICCID_FIELDS', 'EF_IMSI_FIELDS', 'EF_CBMI_LIST', 'EF_CBMIR_LIST', 'EF_READONLY_NAMES', 'EF_SMSP_PARAMS', 'EF_SMSP_CUSTOM', 'EF_ENCODERS']) {
|
'EF_ADN_FIELDS', 'EF_PLMN_LIST', 'EF_PNN_ITEMS', 'EF_UST_SERVICES', 'EF_SST_SERVICES', 'EF_IST_SERVICES', 'EF_EST_SERVICES', 'EF_PHASE_NAMES', 'EF_ACC_CLASSES', 'EF_UST_FIELDS', 'EF_IST_FIELDS', 'EF_EST_FIELDS', 'EF_SST_FIELDS', 'EF_ACC_FIELDS', 'EF_PHASE_FIELDS', 'EF_HPPLMN_FIELDS', 'EF_AD_OP_MODES', 'EF_AD_FIELDS', 'EF_PLMN_ONLY_LIST', 'EF_ICCID_FIELDS', 'EF_IMSI_FIELDS', 'EF_CBMI_LIST', 'EF_CBMIR_LIST', 'EF_READONLY_NAMES', 'EF_SMSP_PARAMS', 'EF_SMSP_CUSTOM', 'EF_ENCODERS']) {
|
||||||
code += extractConst(html, c);
|
code += extractConst(html, c);
|
||||||
}
|
}
|
||||||
eval(code);
|
eval(code);
|
||||||
@@ -233,12 +233,19 @@ test('the encoder resolver is DF-first: every EF.AD form shares one encoder', ()
|
|||||||
// without a DF the historical name-first lookup answers (DF.GSM's entry)
|
// without a DF the historical name-first lookup answers (DF.GSM's entry)
|
||||||
assert.strictEqual(efFindEncoder('EF.AD', '6fad'), sim);
|
assert.strictEqual(efFindEncoder('EF.AD', '6fad'), sim);
|
||||||
assert.strictEqual(efFindEncoder('EF.AD', '6fad', 'ADF.NOPE'), sim);
|
assert.strictEqual(efFindEncoder('EF.AD', '6fad', 'ADF.NOPE'), sim);
|
||||||
// EF.ECC stays read-only in either DF, EF.SST in the SIM layout, and the
|
// EF.ECC stays read-only in either DF; the shared 6F38 FID resolves to
|
||||||
// shared 6F38 FID must not hand SST the UST encoder
|
// EF.SST under DF.GSM and to EF.UST under ADF.USIM (the FID fallback
|
||||||
|
// without a DF keeps the historical UST answer - SST's entry is DF-scoped)
|
||||||
assert.strictEqual(efHasEncoder('EF.ECC', '6fb7', '7F20'), false);
|
assert.strictEqual(efHasEncoder('EF.ECC', '6fb7', '7F20'), false);
|
||||||
assert.strictEqual(efHasEncoder('EF.ECC', '6fb7', 'ADF.USIM'), false);
|
assert.strictEqual(efHasEncoder('EF.ECC', '6fb7', 'ADF.USIM'), false);
|
||||||
assert.strictEqual(efHasEncoder('EF.SST', '6f38', '7F20'), false);
|
assert.strictEqual(efHasEncoder('EF.SST', '6f38', '7F20'), true);
|
||||||
assert.strictEqual(efHasEncoder('EF.UST', '6f38', 'ADF.USIM'), true);
|
assert.strictEqual(efHasEncoder('EF.UST', '6f38', 'ADF.USIM'), true);
|
||||||
|
assert.strictEqual(efFindEncoder('EF.SST', '6f38', '7F20').enc, efEncSst);
|
||||||
|
assert.strictEqual(efFindEncoder('EF.UST', '6f38', 'ADF.USIM').enc, efFindEncoder('EF.UST', '6f38').enc);
|
||||||
|
assert.strictEqual(efFindEncoder(null, '6f38', '7F20').name, 'EF.SST');
|
||||||
|
assert.strictEqual(efFindEncoder(null, '6f38', 'ADF.USIM').name, 'EF.UST');
|
||||||
|
assert.strictEqual(efFindEncoder(null, '6f38').name, 'EF.UST');
|
||||||
|
assert.strictEqual(efFindEncoder(null, '6f38', 'ADF.USIM').enc !== efEncSst, true);
|
||||||
assert.strictEqual(efFindEncoder('EF.NOPE', 'abcd', 'ADF.USIM'), null);
|
assert.strictEqual(efFindEncoder('EF.NOPE', 'abcd', 'ADF.USIM'), null);
|
||||||
// the SIM 3+X and USIM 4+X forms keep their length on an untouched save
|
// the SIM 3+X and USIM 4+X forms keep their length on an untouched save
|
||||||
for (const hex of ['00' + '0000', '00' + '0000' + '02' + 'AABB', AD_HEX, 'FF'.repeat(6)]) {
|
for (const hex of ['00' + '0000', '00' + '0000' + '02' + 'AABB', AD_HEX, 'FF'.repeat(6)]) {
|
||||||
@@ -626,10 +633,13 @@ test('service tables: encode/decode round-trips and unmodelled bits', () => {
|
|||||||
const services = efDecServiceTable(efBytes(hex), table).services;
|
const services = efDecServiceTable(efBytes(hex), table).services;
|
||||||
assert.strictEqual(entry.enc({ services: services }, hex), hex, name + ' ' + hex);
|
assert.strictEqual(entry.enc({ services: services }, hex), hex, name + ' ' + hex);
|
||||||
}
|
}
|
||||||
// EF.SST has no editor (its 2-bit SIM layout needs a dedicated UI)
|
// EF.SST has its own 2-bit editor on the shared 6F38 FID (the DF-scoped
|
||||||
assert.strictEqual(efFindEncoder('EF.SST', '6f38'), null);
|
// entry keeps the UST answer for a bare FID / ADF context)
|
||||||
assert.strictEqual(efHasEncoder('EF.SST', '6f38'), false);
|
assert.strictEqual(efFindEncoder('EF.SST', '6f38').enc, efEncSst);
|
||||||
|
assert.strictEqual(efHasEncoder('EF.SST', '6f38'), true);
|
||||||
assert.strictEqual(efFindEncoder('EF.UST', '6f38').name, 'EF.UST');
|
assert.strictEqual(efFindEncoder('EF.UST', '6f38').name, 'EF.UST');
|
||||||
|
assert.strictEqual(efFindEncoder(null, '6f38').name, 'EF.UST');
|
||||||
|
assert.strictEqual(efFindEncoder(null, '6f38', '7F20').name, 'EF.SST');
|
||||||
// EST defines services 1-3; all-FF keeps the other bits too
|
// EST defines services 1-3; all-FF keeps the other bits too
|
||||||
assert.deepStrictEqual(efDecServiceTable(efBytes('07'), EF_EST_SERVICES).services,
|
assert.deepStrictEqual(efDecServiceTable(efBytes('07'), EF_EST_SERVICES).services,
|
||||||
[{ n: 1, name: 'Fixed Dialling Numbers (FDN)' },
|
[{ n: 1, name: 'Fixed Dialling Numbers (FDN)' },
|
||||||
@@ -659,6 +669,64 @@ test('service tables: unknown services and a too-short table are refused', () =>
|
|||||||
assert.throws(() => efEncServiceTable({ services: [{ n: 0 }] }, '00', EF_UST_SERVICES), /Unknown service/);
|
assert.throws(() => efEncServiceTable({ services: [{ n: 0 }] }, '00', EF_UST_SERVICES), /Unknown service/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// EF.SST (TS 51.011 10.3.7): two bits per service, four per byte. 15 bytes =
|
||||||
|
// services 1-60; C7/1C/C1 carry on (11), off (10) and deleted (00) pairs and
|
||||||
|
// the last byte's bits 7-8 are service 60, above the 59-entry table.
|
||||||
|
const SST_MIX = 'C71CC1' + '00'.repeat(11) + 'C0';
|
||||||
|
|
||||||
|
test('EF.SST: the 2-bit encoder round-trips the decoded file', () => {
|
||||||
|
const dec = efDecSst(efBytes(SST_MIX));
|
||||||
|
assert.deepStrictEqual(dec.services.map(s => [s.n, s.allocated, s.activated]), [
|
||||||
|
[1, true, true], [2, true, false], [4, true, true], [6, true, true],
|
||||||
|
[7, true, false], [9, true, false], [12, true, true], [60, true, true],
|
||||||
|
]);
|
||||||
|
// a deleted (00) pair is omitted by the decoder, so decode->encode is exact
|
||||||
|
assert.strictEqual(efEncSst(dec, SST_MIX), SST_MIX);
|
||||||
|
// every state on: all-FF, unmodelled service 60 included
|
||||||
|
assert.strictEqual(efEncSst(efDecSst(efBytes('FF'.repeat(15))), 'FF'.repeat(15)), 'FF'.repeat(15));
|
||||||
|
// no service allocated at all
|
||||||
|
assert.strictEqual(efEncSst(efDecSst(efBytes('00'.repeat(15))), '00'.repeat(15)), '00'.repeat(15));
|
||||||
|
// clearing every modelled service keeps service 60's pair (byte 14 C0)
|
||||||
|
assert.strictEqual(efEncSst({ services: [] }, SST_MIX), '000000' + '00'.repeat(11) + 'C0');
|
||||||
|
// the file length is kept
|
||||||
|
assert.strictEqual(efEncSst(efDecSst(efBytes('0C')), '0C'), '0C');
|
||||||
|
assert.strictEqual(efEncSst(efDecSst(efBytes('0C00')), '0C00'), '0C00');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('EF.SST: deleted clears the pair, off sets 10 and on sets 11', () => {
|
||||||
|
const on = n => ({ n: n, allocated: true, activated: true });
|
||||||
|
const off = n => ({ n: n, allocated: true, activated: false });
|
||||||
|
const del = n => ({ n: n, allocated: false, activated: false });
|
||||||
|
// service 2 (byte 0, pair 2)
|
||||||
|
assert.strictEqual(efEncSst({ services: [on(2)] }, '00'), '0C');
|
||||||
|
assert.strictEqual(efEncSst({ services: [off(2)] }, '00'), '04');
|
||||||
|
assert.strictEqual(efEncSst({ services: [del(2)] }, '0C'), '00');
|
||||||
|
// every modelled pair is rebuilt from the list, the rest of the file stays
|
||||||
|
assert.strictEqual(efEncSst({ services: [on(2)] }, 'FF'.repeat(15)),
|
||||||
|
'0C' + '00'.repeat(13) + 'C0');
|
||||||
|
assert.strictEqual(efEncSst({ services: [on(2), off(4)] }, SST_MIX).slice(0, 2), '4C');
|
||||||
|
// a pair above the table is written like any other (a longer file)
|
||||||
|
const long = '00'.repeat(16);
|
||||||
|
assert.strictEqual(efEncSst({ services: [on(62)] }, long), '00'.repeat(15) + '0C');
|
||||||
|
// the decoder's illegal '08' pair (activated, not allocated) is refused
|
||||||
|
assert.deepStrictEqual(efDecSst(efBytes('08')), { services: [
|
||||||
|
{ n: 2, name: 'Abbreviated Dialling Numbers (ADN)', allocated: false, activated: true },
|
||||||
|
] });
|
||||||
|
assert.throws(() => efEncSst(efDecSst(efBytes('08')), '08'), /must be deleted, off or on/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('EF.SST: unknown services, an invalid state and a too-short file are refused', () => {
|
||||||
|
assert.throws(() => efEncSst({ services: [{ n: 5, allocated: true, activated: true }] }, '00'),
|
||||||
|
/too short for service/);
|
||||||
|
assert.throws(() => efEncSst({ services: [{ n: 0, allocated: true, activated: true }] }, '00'),
|
||||||
|
/Unknown service/);
|
||||||
|
assert.throws(() => efEncSst({ services: [{ n: 'x' }] }, '00'), /Unknown service/);
|
||||||
|
assert.throws(() => efEncSst({ services: [{ n: 2, allocated: false, activated: true }] }, '00'),
|
||||||
|
/must be deleted, off or on/);
|
||||||
|
// a bare service number carries no state
|
||||||
|
assert.throws(() => efEncSst({ services: [2] }, '00'), /must be deleted, off or on/);
|
||||||
|
});
|
||||||
|
|
||||||
test('F-ACC: the class numbering and the ACC10/encoder rules', () => {
|
test('F-ACC: the class numbering and the ACC10/encoder rules', () => {
|
||||||
assert.deepStrictEqual(efDecAcc(efBytes('8000')), { access_classes: [15] });
|
assert.deepStrictEqual(efDecAcc(efBytes('8000')), { access_classes: [15] });
|
||||||
assert.deepStrictEqual(efDecAcc(efBytes('0001')), { access_classes: [0] });
|
assert.deepStrictEqual(efDecAcc(efBytes('0001')), { access_classes: [0] });
|
||||||
|
|||||||
@@ -51,19 +51,19 @@ const FNS = [
|
|||||||
'efDecRowHtml', 'efDecGridHtml', 'efDecItemRowHtml', 'efDecItemsHtml',
|
'efDecRowHtml', 'efDecGridHtml', 'efDecItemRowHtml', 'efDecItemsHtml',
|
||||||
'efDecRecordsHtml', 'efDecEditHtml',
|
'efDecRecordsHtml', 'efDecEditHtml',
|
||||||
'efDecCollectValues', 'efDecCollectRows', 'efDecCollectItems',
|
'efDecCollectValues', 'efDecCollectRows', 'efDecCollectItems',
|
||||||
'efBitsSelected', 'pysimFsDecBitsFilter', 'pysimFsDecRowCheck', 'pysimFsDecItemTag', 'pysimFsDecAddItem',
|
'efBitsSelected', 'pysimFsDecBitsFilter', 'pysimFsDecBits2Filter', 'pysimFsDecRowCheck', 'pysimFsDecItemTag', 'pysimFsDecAddItem',
|
||||||
'pysimFsDecRemoveItem', 'pysimFsFindNodeByPath', 'pysimCustomNormPath', 'pysimFsNodePath', 'pysimFsFallbackNode', 'efFidFromPath', 'efDfFromPath',
|
'pysimFsDecRemoveItem', 'pysimFsFindNodeByPath', 'pysimCustomNormPath', 'pysimFsNodePath', 'pysimFsFallbackNode', 'efFidFromPath', 'efDfFromPath',
|
||||||
'pysimFsHasDecoder', 'pysimFsHasEncoder', 'pysimFsDecSave', 'pysimFsDecEdit',
|
'pysimFsHasDecoder', 'pysimFsHasEncoder', 'pysimFsDecSave', 'pysimFsDecEdit',
|
||||||
'pysimFsPillsEnabled', 'pysimFsSyncContentVisibility', 'gsm7Decode',
|
'pysimFsPillsEnabled', 'pysimFsSyncContentVisibility', 'gsm7Decode',
|
||||||
'gsm7TextToSeptets', 'gsm7Encode',
|
'gsm7TextToSeptets', 'gsm7Encode',
|
||||||
'efDecServiceTable', 'efDecSst', 'efDecAcc', 'efDecPhase', 'efDecHpplmn', 'efDecAd', 'efDecCbmi', 'efDecCbmir', 'efDecIccid', 'efDecImsi', 'efDecPlmnList', 'efDecFplmn', 'efEncServiceTable', 'efEncAcc', 'efPhaseValue', 'efPhaseModel', 'efEncPhase', 'efEncHpplmn', 'efAdOpValue', 'efAdModel', 'efEncAd', 'efEncPlmnOnly', 'efEncIccid', 'efEncImsi', 'efCbmiModel', 'efEncCbmi', 'efCbmirModel', 'efEncCbmir', 'efSmspAddressModel', 'efSmspModel', 'efSmspTonOptions', 'efSmspParamRow', 'efSmspHtml', 'efSmspCollect', 'pysimFsSmspToggle', 'pysimFsSmspHexInput', 'efSmspHexByte', 'efSmspAddressBytes', 'efSmspAddrUnchanged', 'efSmspByteUnchanged', 'efEncSmsp', 'smspT', 'smspDcsHint', 'smspVpHint', 'smspDurationText', 'smspPluralIndex', 'smspCount', 'efSmspDaNumber',
|
'efDecServiceTable', 'efDecSst', 'efDecAcc', 'efDecPhase', 'efDecHpplmn', 'efDecAd', 'efDecCbmi', 'efDecCbmir', 'efDecIccid', 'efDecImsi', 'efDecPlmnList', 'efDecFplmn', 'efEncServiceTable', 'efEncSst', 'efEncAcc', 'efPhaseValue', 'efPhaseModel', 'efEncPhase', 'efEncHpplmn', 'efAdOpValue', 'efAdModel', 'efEncAd', 'efEncPlmnOnly', 'efEncIccid', 'efEncImsi', 'efCbmiModel', 'efEncCbmi', 'efCbmirModel', 'efEncCbmir', 'efSmspAddressModel', 'efSmspModel', 'efSmspTonOptions', 'efSmspParamRow', 'efSmspHtml', 'efSmspCollect', 'pysimFsSmspToggle', 'pysimFsSmspHexInput', 'efSmspHexByte', 'efSmspAddressBytes', 'efSmspAddrUnchanged', 'efSmspByteUnchanged', 'efEncSmsp', 'smspT', 'smspDcsHint', 'smspVpHint', 'smspDurationText', 'smspPluralIndex', 'smspCount', 'efSmspDaNumber',
|
||||||
];
|
];
|
||||||
|
|
||||||
let code = 'var pysimFsSelected = null;\nvar pysimFsTreeRoot = null;\nvar pysimFsDecModel = null;\n'
|
let code = 'var pysimFsSelected = null;\nvar pysimFsTreeRoot = null;\nvar pysimFsDecModel = null;\n'
|
||||||
+ 'var pysimFsDecEditMode = false;\nvar pysimFsEditMode = false;\n';
|
+ 'var pysimFsDecEditMode = false;\nvar pysimFsEditMode = false;\n';
|
||||||
for (const f of FNS) code += extractFunc(html, f) + '\n';
|
for (const f of FNS) code += extractFunc(html, f) + '\n';
|
||||||
for (const c of ['GSM7_ALPHABET', 'GSM7_EXT_MAP', 'EF_ACT_TECHS', 'EF_SPN_FIELDS',
|
for (const c of ['GSM7_ALPHABET', 'GSM7_EXT_MAP', 'EF_ACT_TECHS', 'EF_SPN_FIELDS',
|
||||||
'EF_ADN_FIELDS', 'EF_PLMN_LIST', 'EF_PNN_ITEMS', 'EF_UST_SERVICES', 'EF_SST_SERVICES', 'EF_IST_SERVICES', 'EF_EST_SERVICES', 'EF_PHASE_NAMES', 'EF_ACC_CLASSES', 'EF_UST_FIELDS', 'EF_IST_FIELDS', 'EF_EST_FIELDS', 'EF_ACC_FIELDS', 'EF_PHASE_FIELDS', 'EF_HPPLMN_FIELDS', 'EF_AD_OP_MODES', 'EF_AD_FIELDS', 'EF_PLMN_ONLY_LIST', 'EF_ICCID_FIELDS', 'EF_IMSI_FIELDS', 'EF_CBMI_LIST', 'EF_CBMIR_LIST', 'EF_READONLY_NAMES', 'EF_SMSP_PARAMS', 'EF_SMSP_CUSTOM', 'EF_ENCODERS', 'EF_ADF_RIDS']) {
|
'EF_ADN_FIELDS', 'EF_PLMN_LIST', 'EF_PNN_ITEMS', 'EF_UST_SERVICES', 'EF_SST_SERVICES', 'EF_IST_SERVICES', 'EF_EST_SERVICES', 'EF_PHASE_NAMES', 'EF_ACC_CLASSES', 'EF_UST_FIELDS', 'EF_IST_FIELDS', 'EF_EST_FIELDS', 'EF_SST_FIELDS', 'EF_ACC_FIELDS', 'EF_PHASE_FIELDS', 'EF_HPPLMN_FIELDS', 'EF_AD_OP_MODES', 'EF_AD_FIELDS', 'EF_PLMN_ONLY_LIST', 'EF_ICCID_FIELDS', 'EF_IMSI_FIELDS', 'EF_CBMI_LIST', 'EF_CBMIR_LIST', 'EF_READONLY_NAMES', 'EF_SMSP_PARAMS', 'EF_SMSP_CUSTOM', 'EF_ENCODERS', 'EF_ADF_RIDS']) {
|
||||||
code += extractConst(html, c);
|
code += extractConst(html, c);
|
||||||
}
|
}
|
||||||
eval(code);
|
eval(code);
|
||||||
@@ -271,12 +271,14 @@ test('pysimFsHasEncoder resolves the selected file by its path or FID', () => {
|
|||||||
assert.strictEqual(pysimFsHasEncoder(), true);
|
assert.strictEqual(pysimFsHasEncoder(), true);
|
||||||
pysimFsSelected = 'MF/6F7E';
|
pysimFsSelected = 'MF/6F7E';
|
||||||
assert.strictEqual(pysimFsHasEncoder(), false);
|
assert.strictEqual(pysimFsHasEncoder(), false);
|
||||||
// the SIM service table shares 6F38 with UST but has no editor
|
// the SIM service table shares 6F38 with UST but has its own 2-bit editor
|
||||||
pysimFsTreeRoot = { name: 'MF', children: [] };
|
pysimFsTreeRoot = { name: 'MF', children: [] };
|
||||||
const sst = { name: 'EF.SST', fid: '6f38', parent: pysimFsTreeRoot };
|
const gsm = { name: 'DF.GSM', fid: '7F20', parent: pysimFsTreeRoot, children: [] };
|
||||||
pysimFsTreeRoot.children.push(sst);
|
pysimFsTreeRoot.children.push(gsm);
|
||||||
pysimFsSelected = 'MF/6F38';
|
const sst = { name: 'EF.SST', fid: '6f38', parent: gsm };
|
||||||
assert.strictEqual(pysimFsHasEncoder(), false);
|
gsm.children.push(sst);
|
||||||
|
pysimFsSelected = 'MF/7F20/6F38';
|
||||||
|
assert.strictEqual(pysimFsHasEncoder(), true);
|
||||||
pysimFsSelected = null;
|
pysimFsSelected = null;
|
||||||
assert.strictEqual(pysimFsHasEncoder(), false);
|
assert.strictEqual(pysimFsHasEncoder(), false);
|
||||||
});
|
});
|
||||||
@@ -749,6 +751,91 @@ test('the bits filter hides the non-matching rows', () => {
|
|||||||
assert.strictEqual(rows[1].style.display, '');
|
assert.strictEqual(rows[1].style.display, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the EF.SST grid renders one tri-state select per service', () => {
|
||||||
|
// 4C: service 2 on (11), service 4 off (10), the rest of the pairs deleted
|
||||||
|
const model = efDecEditModel('EF.SST', '6f38', { kind: 'transparent', data: '4C' }, '7F20');
|
||||||
|
assert.deepStrictEqual(model.values.services, [
|
||||||
|
{ n: 2, name: 'Abbreviated Dialling Numbers (ADN)', allocated: true, activated: true },
|
||||||
|
{ n: 4, name: 'Short Message Storage (SMS)', allocated: true, activated: false },
|
||||||
|
]);
|
||||||
|
const out = efDecEditHtml(model, '4C');
|
||||||
|
assert.match(out, /class="pysim-dec-bits /);
|
||||||
|
assert.match(out, /pysim-dec-bits-filter/);
|
||||||
|
assert.match(out, /data-bit2="2" data-bit2-name="Abbreviated Dialling Numbers \(ADN\)"/);
|
||||||
|
// the whole SST table is rendered (59 services, RFU 8/20/50 included)
|
||||||
|
assert.strictEqual((out.match(/data-bit2="/g) || []).length, 59);
|
||||||
|
assert.match(out, /data-bit2="8" data-bit2-name="RFU"/);
|
||||||
|
assert.match(out, /data-bit2="20" data-bit2-name="RFU"/);
|
||||||
|
assert.match(out, /data-bit2="50" data-bit2-name="Reserved and shall be ignored"/);
|
||||||
|
// each select is preselected from the decoded state, an absent pair is deleted
|
||||||
|
const sel = n => {
|
||||||
|
const m = new RegExp('<select data-bit2="' + n + '"[^>]*>([\\s\\S]*?)</select>').exec(out);
|
||||||
|
assert.ok(m, 'select for service ' + n);
|
||||||
|
return m[1];
|
||||||
|
};
|
||||||
|
assert.match(sel(2), /<option value="on" selected>On<\/option>/);
|
||||||
|
assert.match(sel(4), /<option value="off" selected>Off<\/option>/);
|
||||||
|
assert.match(sel(1), /<option value="deleted" selected>Deleted<\/option>/);
|
||||||
|
assert.match(sel(59), /<option value="deleted" selected>Deleted<\/option>/);
|
||||||
|
for (const n of [2, 4, 1, 59]) {
|
||||||
|
assert.match(sel(n), />Deleted<\/option>/);
|
||||||
|
assert.match(sel(n), />Off<\/option>/);
|
||||||
|
assert.match(sel(n), />On<\/option>/);
|
||||||
|
}
|
||||||
|
// an all-FF file preselects every modelled service 'on'
|
||||||
|
const ff = efDecEditHtml(efDecEditModel('EF.SST', '6f38',
|
||||||
|
{ kind: 'transparent', data: 'FF'.repeat(15) }, '7F20'), 'FF'.repeat(15));
|
||||||
|
assert.strictEqual((ff.match(/<option value="on" selected>/g) || []).length, 59);
|
||||||
|
assert.ok(!/<option value="(deleted|off)" selected>/.test(ff));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the EF.SST collector yields {n, name, allocated, activated}', () => {
|
||||||
|
const row = (n, name, state) => ({
|
||||||
|
value: state,
|
||||||
|
getAttribute: k => k === 'data-bit2' ? String(n) : name,
|
||||||
|
});
|
||||||
|
const root = {
|
||||||
|
querySelectorAll: sel => sel === '[data-bit2]' ? [
|
||||||
|
row(2, 'Abbreviated Dialling Numbers (ADN)', 'on'),
|
||||||
|
row(1, 'CHV1 disable function', 'deleted'),
|
||||||
|
row(4, 'Short Message Storage (SMS)', 'off'),
|
||||||
|
] : [],
|
||||||
|
};
|
||||||
|
assert.deepStrictEqual(efDecCollectValues(root, EF_SST_FIELDS), { services: [
|
||||||
|
{ n: 2, name: 'Abbreviated Dialling Numbers (ADN)', allocated: true, activated: true },
|
||||||
|
{ n: 1, name: 'CHV1 disable function', allocated: false, activated: false },
|
||||||
|
{ n: 4, name: 'Short Message Storage (SMS)', allocated: true, activated: false },
|
||||||
|
] });
|
||||||
|
// an untouched form re-encodes to the original bytes
|
||||||
|
const dec = efDecSst(efBytes('4C'));
|
||||||
|
const values = efDecCollectValues({
|
||||||
|
querySelectorAll: () => [2, 4].map(n => row(n, EF_SST_SERVICES[n],
|
||||||
|
dec.services.find(s => s.n === n).activated ? 'on' : 'off')),
|
||||||
|
}, EF_SST_FIELDS);
|
||||||
|
assert.strictEqual(efFindEncoder('EF.SST', '6f38', '7F20').enc(values, '4C'), '4C');
|
||||||
|
// the EF.SST field is the tri-state kind, the 1-bit tables are untouched
|
||||||
|
assert.strictEqual(EF_SST_FIELDS[0].kind, 'bits2');
|
||||||
|
assert.strictEqual(EF_UST_FIELDS[0].kind, 'bits');
|
||||||
|
assert.strictEqual(EF_EST_FIELDS[0].kind, 'bits');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the tri-state grid filter matches the row label, not the select options', () => {
|
||||||
|
const rows = [
|
||||||
|
{ getAttribute: k => k === 'data-label' ? '1 CHV1 disable function' : '', style: {} },
|
||||||
|
{ getAttribute: k => k === 'data-label' ? '4 Short Message Storage (SMS)' : '', style: {} },
|
||||||
|
];
|
||||||
|
const box = { querySelectorAll: sel => sel === '.pysim-dec-bit2-row' ? rows : [] };
|
||||||
|
const input = { value: 'sms', closest: sel => sel === '.pysim-dec-bits' ? box : null };
|
||||||
|
pysimFsDecBits2Filter(input);
|
||||||
|
assert.strictEqual(rows[0].style.display, 'none');
|
||||||
|
assert.strictEqual(rows[1].style.display, '');
|
||||||
|
// 'off' is a state option of every row, never part of a service name
|
||||||
|
pysimFsDecBits2Filter({ value: 'off', closest: () => box });
|
||||||
|
assert.strictEqual(rows[0].style.display, 'none');
|
||||||
|
assert.strictEqual(rows[1].style.display, 'none');
|
||||||
|
pysimFsDecBits2Filter(null);
|
||||||
|
});
|
||||||
|
|
||||||
test('the select keeps an unknown current value as an option', () => {
|
test('the select keeps an unknown current value as an option', () => {
|
||||||
const model = efDecEditModel('EF.PHASE', '6fae', { kind: 'transparent', data: '0F' });
|
const model = efDecEditModel('EF.PHASE', '6fae', { kind: 'transparent', data: '0F' });
|
||||||
assert.deepStrictEqual(model.values, { phase_hex: '0F' });
|
assert.deepStrictEqual(model.values, { phase_hex: '0F' });
|
||||||
|
|||||||
Reference in new issue
Block a user