v3.13.1: expanded-registry pages try every command format
The explorer's next-occurrence page (P2=03) is retried in the other command formats (ramPageFormats: the expanded codings first - the serving one first when it is expanded - and the compact command string last, because a compact page attempt can consume the page without delivering it) and the format that serves a page is kept for the following ones. Live 2026-10-02 (test-eSIM card): a compact page 2 answered 61B6 and the chained GET RESPONSE 6A86, after which the AA and AE 80 retries both saw the occurrence already gone; the AA-wrapped page returns 182 B with the remaining app instances' CE/CC/EA, while AE 80 is refused. A page that no format serves ends the listing. Only an explicit 6310 promise counts for the "(first page only)" flag (ramExpandedMore): a probe after a 61xx page that every format refuses is the card's final answer, so that card's ELF listing is no longer flagged (its expanded registry has three entries; the fourth package exists only in the compact listing - the ELF tag-list forms and per-AID queries are refused in every format). In Auto the explorer also queries entries the bulk listing left without expanded fields individually by AID (ramAppsMissingDetails, <= 6 packets, the compact command string carries the GET RESPONSE for 61xx). Docs: help EN/RU, repo AGENTS (v3.13.1 note). sw cache simple-v371. Tests: 667 Python + 794 frontend green.
This commit is contained in:
@@ -314,7 +314,7 @@
|
||||
<p class="text-sm mb-3">Кнопка <strong>Проверить в pySim</strong> сверяет собранный пакет с эталонной реализацией <code class="font-mono text-sm">OtaDialectSms.encode_cmd</code>. Кнопка <strong>Отправить на карту</strong> доставляет пакет через ENVELOPE SMS-PP-DOWNLOAD (при подключении к серверу). 2-байтовый префикс CPL передаётся, когда пакет зашифрован или несёт RC/CC/DS (входит в их входные данные), а также при конкатенации SMS; одиночное незащищённое SMS сохраняет форму pySim, начинающуюся с CHL (TS 31.115 Table 1 NOTE). Под полем пакета показывается его размер и число SMS; пакет, не помещающийся в одно SMS, отправляется <strong>конкатенированной</strong> загрузкой (TS 31.115 §4.3) — пакет делится на части SMS-пользовательских данных (первое SMS 132 октета, последующие по 134; первое дополнительно несёт информационные элементы конкатенации и CPI), и сегменты отправляются по порядку. Более 5 сегментов отклоняется: это практический предел буфера конкатенации карты. Полученный Proof of Receipt декодируется и показывается строкой статуса PoR (статус, TAR, счётчик, сырой PoR); статусное слово и данные ответа последней команды подставляются в подвкладку <strong>«Разбор R-APDU»</strong> (Remote APDU → «Разбор»), а успешный PoR увеличивает счётчик повторов (когда в SPI1 есть проверка счётчика — b5 b4 ≠ 00; без неё счётчик не увеличивается и не сохраняется) и очищает пакет.</p>
|
||||
|
||||
<h3 id="ram" class="text-lg font-medium mb-2">3.2 RAM</h3>
|
||||
<p class="mb-2">Выполняет операции удалённого управления приложениями (Remote Application Management) как защищённые пакеты SCP80 через SMS-PP-DOWNLOAD ENVELOPE. Карта должна поддерживать SCP03 (AES или 3DES). Пресет карты со вкладки <strong>Карты</strong> обеспечивает ключи, таблицу TAR и счётчик (RAM использует <strong>ISD TAR</strong> пресета, по умолчанию <code class="font-mono text-sm">000000</code>; SPI1 берётся из MSL этого TAR). Селектор <strong>Keyset</strong> рядом с выбором пресета указывает, из какого keyset берутся ключи и счётчик операции. Селектор недоступен, пока не выбран пресет карты. Кнопка запуска недоступна, пока не выбран пресет карты и один из его keysets. Селектор <strong>Формат</strong> фиксирует формат удалённой команды: <strong>Авто (определение)</strong> опрашивает read-only команду GET STATUS в компактном виде и в обоих expanded-кодированиях и оставляет первое, которое карта выполнила; <strong>Компактный</strong> отправляет голую C-APDU; <strong>Expanded (AA, определённая длина)</strong> / <strong>Expanded (AE 80, неопределённая длина)</strong> заворачивают её в шаблон Command Scripting из TS 102 226 §5.2.1 (на живой карте выполняются обе; шаблон, у которого заявленная длина не совпадает с телом, получает Bad format TLV — операция показывает это как ошибку шага). Запросы листингов и реестра следуют этому формату: компактные листинги несут сцепленный GET RESPONSE, а expanded-прогон отправляет те же запросы P2=00 обёрнутыми и без цепочки (форма эталонного загрузчика TCA; Le=’00’ возвращает листинг сразу), и запрос реестра, отклонённый картой в текущем формате, повторяется в остальных — отчёт называет тот, который сработал. Блок ресурсов карты также называет предполагаемый <strong>релиз ETSI</strong> карты (версия <code class="font-mono text-sm">uicc.toolkit</code> → REL-N, то же сопоставление, что и в анализе CAP; показывается, только если расширенный реестр вернул версии).</p>
|
||||
<p class="mb-2">Выполняет операции удалённого управления приложениями (Remote Application Management) как защищённые пакеты SCP80 через SMS-PP-DOWNLOAD ENVELOPE. Карта должна поддерживать SCP03 (AES или 3DES). Пресет карты со вкладки <strong>Карты</strong> обеспечивает ключи, таблицу TAR и счётчик (RAM использует <strong>ISD TAR</strong> пресета, по умолчанию <code class="font-mono text-sm">000000</code>; SPI1 берётся из MSL этого TAR). Селектор <strong>Keyset</strong> рядом с выбором пресета указывает, из какого keyset берутся ключи и счётчик операции. Селектор недоступен, пока не выбран пресет карты. Кнопка запуска недоступна, пока не выбран пресет карты и один из его keysets. Селектор <strong>Формат</strong> фиксирует формат удалённой команды: <strong>Авто (определение)</strong> опрашивает read-only команду GET STATUS в компактном виде и в обоих expanded-кодированиях и оставляет первое, которое карта выполнила; <strong>Компактный</strong> отправляет голую C-APDU; <strong>Expanded (AA, определённая длина)</strong> / <strong>Expanded (AE 80, неопределённая длина)</strong> заворачивают её в шаблон Command Scripting из TS 102 226 §5.2.1 (на живой карте выполняются обе; шаблон, у которого заявленная длина не совпадает с телом, получает Bad format TLV — операция показывает это как ошибку шага). Запросы листингов и реестра следуют этому формату: компактные листинги несут сцепленный GET RESPONSE, а expanded-прогон отправляет те же запросы P2=00 обёрнутыми и без цепочки (форма эталонного загрузчика TCA; Le=’00’ возвращает листинг сразу), и запрос реестра, отклонённый картой в текущем формате, повторяется в остальных — отчёт называет тот, который сработал. Страница следующего вхождения (<code class="font-mono text-sm">P2=03</code>), отклонённая форматом, тоже повторяется в остальных форматах — сначала expanded-кодирования (первым то, что обслужило предыдущую страницу, если оно expanded), компактная командная строка последней: компактная попытка страницы может израсходовать страницу, не доставив её (карта отвечает <code class="font-mono text-sm">61xx</code>, а сцепленный GET RESPONSE отклоняется — все последующие форматы видят вхождение уже использованным) — некоторые карты отдают expanded-страницу только в шаблоне <code class="font-mono text-sm">AA</code> — и пометка <code class="font-mono text-sm">(только первая страница)</code> ставится, только если карта обещала продолжение (<code class="font-mono text-sm">6310</code>), а ни один формат не смог его получить; в режиме <strong>Авто</strong> проводник также опрашивает по AID записи, оставшиеся без деталей (несколько дополнительных пакетов). Блок ресурсов карты также называет предполагаемый <strong>релиз ETSI</strong> карты (версия <code class="font-mono text-sm">uicc.toolkit</code> → REL-N, то же сопоставление, что и в анализе CAP; показывается, только если расширенный реестр вернул версии).</p>
|
||||
|
||||
<h4 id="ram-operations" class="font-medium mb-1">Операции</h4>
|
||||
<table class="w-full text-sm mb-3 border-collapse">
|
||||
|
||||
+1
-1
@@ -313,7 +313,7 @@
|
||||
<p class="text-sm mb-3">A “Verify vs pySim” button cross-checks the assembled packet against pySim’s reference <code class="font-mono text-sm">OtaDialectSms.encode_cmd</code>. A “Send to Card” button delivers it via SMS-PP-DOWNLOAD ENVELOPE (when connected to the server). The 2-byte CPL prefix is transmitted whenever the packet is ciphered or carries RC/CC/DS (it is part of their input) and whenever the packet is concatenated; a single unprotected SMS keeps pySim’s CHL-first form (TS 31.115 Table 1 NOTE). Under the packet field the app reports its size and how many SMS it takes; a packet that does not fit one SMS is sent as a <strong>concatenated</strong> download (TS 31.115 §4.3) — the packet is split into SMS user-data parts (first SM 132 octets, following ones 134; the first one additionally carries the concatenation and CPI information elements) and the segments are sent in order. More than 5 segments is refused, that being the practical limit of the card’s concatenation buffer. The returned Proof of Receipt is decoded and shown as a PoR status line (status, TAR, counter, raw PoR); the last command’s status word and response data are filled into the <strong>R-APDU parser</strong> panel, and a successful PoR advances the replay counter (when SPI1 carries a counter check — b5 b4 ≠ 00; without one no counter is advanced or saved) and clears the packet. A <strong>low-counter</strong> verdict (<code class="font-mono text-sm">cntr_low</code>, or a warning ENVELOPE answer that still carries such a PoR) is <em>not</em> recoverable by retrying: every SCP80/RAM operation stops immediately. The card never reports its counter (the PoR’s CNTR field is a copy of the packet’s), so the notice offers a bounded <strong>Sync counter</strong> probe — a read-only command with increasing counter values (doubling steps, a 32-bit ceiling, at most 40 packets) until the card accepts one, then accepted+1 is stored — and a <strong>Go to preset</strong> button for raising the counter by hand; nothing else is written automatically.</p>
|
||||
|
||||
<h3 id="ram" class="text-lg font-medium mb-2">3.2 RAM</h3>
|
||||
<p class="mb-2">Delivers Remote Application Management operations as SCP80 secured packets via SMS-PP-DOWNLOAD ENVELOPE. The card must support SCP03 (AES or 3DES). A saved card preset from the <strong>Cards</strong> tab provides the keys, the TAR table and the counter (RAM uses the preset's <strong>ISD TAR</strong>, <code class="font-mono text-sm">000000</code> by default; the SPI1 is derived from that TAR's MSL). The <strong>Keyset</strong> selector next to the preset selector picks the keyset whose keys and counter the operation uses. It is disabled until a card preset is selected. The start button stays disabled until a card preset and one of its keysets are selected. The <strong>Format</strong> selector pins the remote command format: <strong>Auto (detect)</strong> probes the read-only GET STATUS command compact and in both expanded codings and keeps the first that the card executes, <strong>Compact</strong> sends the bare C-APDU, and <strong>Expanded (AA, definite length)</strong> / <strong>Expanded (AE 80, indefinite length)</strong> wrap it in the TS 102 226 §5.2.1 scripting template (both execute on the live ISD; a template whose declared lengths do not match its body draws a Bad format TLV, which the operation shows as the step failure). The listing and registry queries follow that format: compact listings keep the chained GET RESPONSE, while an expanded run sends the same P2=00 queries wrapped and unchained (the reference TCA loader’s form, Le=’00’ returns the listing directly), and a registry query the card refuses in the current format is retried in the other formats — the report names the one that served it. The card-resources block also names the card’s inferred <strong>ETSI release</strong> (<code class="font-mono text-sm">uicc.toolkit</code> version → REL-N, the same mapping as the CAP analysis; shown only when the expanded registry returned the versions).</p>
|
||||
<p class="mb-2">Delivers Remote Application Management operations as SCP80 secured packets via SMS-PP-DOWNLOAD ENVELOPE. The card must support SCP03 (AES or 3DES). A saved card preset from the <strong>Cards</strong> tab provides the keys, the TAR table and the counter (RAM uses the preset's <strong>ISD TAR</strong>, <code class="font-mono text-sm">000000</code> by default; the SPI1 is derived from that TAR's MSL). The <strong>Keyset</strong> selector next to the preset selector picks the keyset whose keys and counter the operation uses. It is disabled until a card preset is selected. The start button stays disabled until a card preset and one of its keysets are selected. The <strong>Format</strong> selector pins the remote command format: <strong>Auto (detect)</strong> probes the read-only GET STATUS command compact and in both expanded codings and keeps the first that the card executes, <strong>Compact</strong> sends the bare C-APDU, and <strong>Expanded (AA, definite length)</strong> / <strong>Expanded (AE 80, indefinite length)</strong> wrap it in the TS 102 226 §5.2.1 scripting template (both execute on the live ISD; a template whose declared lengths do not match its body draws a Bad format TLV, which the operation shows as the step failure). The listing and registry queries follow that format: compact listings keep the chained GET RESPONSE, while an expanded run sends the same P2=00 queries wrapped and unchained (the reference TCA loader’s form, Le=’00’ returns the listing directly), and a registry query the card refuses in the current format is retried in the other formats — the report names the one that served it. A <strong>next-occurrence page</strong> (<code class="font-mono text-sm">P2=03</code>) that a format refuses is retried in the other formats too — the expanded codings first (the serving one first when it is expanded), the compact command string last, because a compact page attempt can consume the page without delivering it (the card answers <code class="font-mono text-sm">61xx</code> and the chained GET RESPONSE fails; every later format then sees the occurrence already gone) — some cards serve the expanded next page only wrapped in the <code class="font-mono text-sm">AA</code> template — and a listing is marked <code class="font-mono text-sm">(first page only)</code> only when the card promised more (<code class="font-mono text-sm">6310</code>) and no format could fetch the follow-up; in <strong>Auto</strong> the explorer also queries entries the bulk listing left without details individually by AID (a few extra packets). The card-resources block also names the card’s inferred <strong>ETSI release</strong> (<code class="font-mono text-sm">uicc.toolkit</code> version → REL-N, the same mapping as the CAP analysis; shown only when the expanded registry returned the versions).</p>
|
||||
|
||||
<h4 id="ram-operations" class="font-medium mb-1">Operations</h4>
|
||||
<table class="w-full text-sm mb-3 border-collapse">
|
||||
|
||||
+121
-31
@@ -1877,7 +1877,7 @@
|
||||
// ===== 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 = '3.13.0';
|
||||
const SIMPLE_VERSION = '3.13.1';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -9564,6 +9564,16 @@ function ramMergeExpanded(collector, parsed) {
|
||||
});
|
||||
}
|
||||
|
||||
// The app entries that still lack expanded-registry fields (pure): the per-AID
|
||||
// completion queries only entries whose AID is known and which carry none of
|
||||
// the expanded-only fields (version / SD AID / package AID / menu entries /
|
||||
// raw objects) - the bulk listing may have skipped or truncated them.
|
||||
function ramAppsMissingDetails(apps) {
|
||||
return (apps || []).filter(e => e && e.aid && !e.version && !e.sdAid &&
|
||||
!e.elfAid && !(e.menuEntries && e.menuEntries.length) &&
|
||||
!(e.rawTlvs && e.rawTlvs.length));
|
||||
}
|
||||
|
||||
// Group the per-listing expanded outcomes (pure): one group per label, best
|
||||
// outcome wins (a pass that returned entries > an accepted-but-empty pass >
|
||||
// a refusal), so a listing queried twice (P1=10 and then the P1=20 fallback)
|
||||
@@ -9599,11 +9609,12 @@ function ramExpandedGroups(report) {
|
||||
return groups;
|
||||
}
|
||||
|
||||
// The "more occurrences" flag of one expanded-registry page pass (pure): a
|
||||
// page that promised more data ('6310', or an accepted chained retry) is
|
||||
// fulfilled by the next accepted page; it stays set when the follow-up was
|
||||
// refused/failed or the page cap stopped the loop - only then is the listing
|
||||
// "first page only".
|
||||
// The "more occurrences" flag of one expanded-registry page pass (pure): only
|
||||
// an explicit '6310' promise counts. The pass tries every command format for
|
||||
// the follow-up pages, so a page that no format serves is the card's final
|
||||
// answer when nothing was promised (the probe after a '61xx' page) - only a
|
||||
// promise that stays unfulfilled (a refused follow-up or the page cap) makes
|
||||
// the listing "first page only".
|
||||
function ramExpandedMore(promised, sw, page) {
|
||||
if (!promised) return false;
|
||||
if (page >= 8) return true;
|
||||
@@ -9885,6 +9896,10 @@ async function ramExplore(sp) {
|
||||
// told to raise the preset counter (v3.6.58). Every send of the run goes
|
||||
// through sendOta() so the verdict is caught wherever it appears.
|
||||
let cntrLow = false;
|
||||
// The user's format selection before the run adopts a detected format: the
|
||||
// exhaustive per-AID completion is an Auto behavior (a pinned format keeps
|
||||
// the pass to that format's reach).
|
||||
const autoFormat = !sp.ram_format || sp.ram_format === 'auto';
|
||||
async function sendOta(apdu, params) {
|
||||
const res = await ramSendOta(apdu, params);
|
||||
if (!cntrLow && spCntrLow(res)) cntrLow = true;
|
||||
@@ -9924,16 +9939,18 @@ async function ramExplore(sp) {
|
||||
let accepted = true;
|
||||
let transportError = false;
|
||||
let usedFmt = fmt || runFmt;
|
||||
const sendOne = async (apdu, what) => {
|
||||
const sendOne = async (apdu, what, useFmt) => {
|
||||
ramShowProgress(label + ' P1=' + p1 + ' ' + what + '...');
|
||||
const params = Object.assign({}, sp, { cntr, spi2 });
|
||||
if (fmt) params.ram_format = fmt;
|
||||
const f = useFmt === undefined ? fmt : useFmt;
|
||||
if (f) params.ram_format = f;
|
||||
const res = await ramSendOta(apdu, params);
|
||||
usedFmt = res.ram_format || fmt || sp.ram_format || usedFmt;
|
||||
usedFmt = res.ram_format || f || sp.ram_format || usedFmt;
|
||||
// an explicit fallback format is not adopted as the run's
|
||||
// format - the selected/pinned format keeps applying to the
|
||||
// other passes
|
||||
if (!fmt && res.ram_format) sp.ram_format = res.ram_format;
|
||||
// other passes (only a ladder send without an explicit
|
||||
// format adopts the one the server detected)
|
||||
if (!f && res.ram_format) sp.ram_format = res.ram_format;
|
||||
if (res.final_cntr) cntr = res.final_cntr;
|
||||
else if (res.success && spPorAccepted(res.por)) cntr = ramIncrementCntr(cntr);
|
||||
if (!res.success || !res.por || !spPorAccepted(res.por)) {
|
||||
@@ -9950,29 +9967,49 @@ async function ramExplore(sp) {
|
||||
let promised = false;
|
||||
while (accepted && page++ < 8) {
|
||||
const apdu = ramExpandedQueryApdu(p1, form, page > 1);
|
||||
const what = (page > 1 ? 'P2=03 (' : 'P2=02 (') + form +
|
||||
(fmt ? ', ' + fmt : '') + ')';
|
||||
data += await sendOne(apdu, what);
|
||||
if (cntrLow || !accepted) break;
|
||||
// '61xx': the card holds the response data - the GET RESPONSE
|
||||
// must ride in the same compact command string (a standalone
|
||||
// one answers 6700 on the live card); the expanded format
|
||||
// has no command string, so there is no retry there.
|
||||
const pending = /^61[0-9A-F]{2}$/.test(sw);
|
||||
if (pending && !ramFormatIsExpanded(usedFmt)) {
|
||||
data += await sendOne(ramChainGetResponse(apdu), what + ' + GET RESPONSE');
|
||||
// A follow-up page is tried in every command format: a card
|
||||
// may serve the next occurrence only in a specific one (the
|
||||
// live card refuses the compact P2=03 and serves it wrapped
|
||||
// in the 'AA' template) - Auto fetches every reachable page.
|
||||
// The expanded codings come first: a compact page attempt can
|
||||
// consume the page without delivering it. The format that
|
||||
// serves a page is kept for the next ones.
|
||||
const pageFmts = page > 1 ? ramPageFormats(usedFmt) : [fmt];
|
||||
let served = false;
|
||||
let pending = false;
|
||||
for (const pf of pageFmts) {
|
||||
if (cntrLow) break;
|
||||
const what = (page > 1 ? 'P2=03 (' : 'P2=02 (') + form +
|
||||
(pf ? ', ' + pf : '') + ')';
|
||||
let chunk = await sendOne(apdu, what, pf);
|
||||
if (cntrLow || !accepted) break;
|
||||
// '61xx': the card holds the response data - the GET
|
||||
// RESPONSE must ride in the same compact command string (a
|
||||
// standalone one answers 6700 on the live card); the
|
||||
// expanded format has no command string, so there is no
|
||||
// retry there.
|
||||
pending = /^61[0-9A-F]{2}$/.test(sw);
|
||||
if (pending && !ramFormatIsExpanded(usedFmt)) {
|
||||
chunk += await sendOne(ramChainGetResponse(apdu),
|
||||
what + ' + GET RESPONSE', pf);
|
||||
if (cntrLow || !accepted) break;
|
||||
}
|
||||
if (!ramRemoteSwOk(sw)) continue; // refused: next format
|
||||
data += chunk;
|
||||
served = true;
|
||||
break;
|
||||
}
|
||||
// the ISD is a single occurrence (GP 11.4.2.2); '6310' means
|
||||
// more occurrences, and a '61xx' page may mask that signal
|
||||
// (the GET RESPONSE's own SW replaces it) - try one more page
|
||||
if (cntrLow || !accepted) break;
|
||||
if (!served) break; // no format serves this page
|
||||
// the ISD is a single occurrence (GP 11.4.2.2)
|
||||
if (p1 === '80') break;
|
||||
// continue only when the answer really says 'more occurrences'
|
||||
// or the chained retry was accepted (a '61xx' page masks the
|
||||
// 6310 signal with the GET RESPONSE's own SW); a refused retry
|
||||
// must not cost another packet (v3.7.0)
|
||||
if (sw !== '6310' && !(pending && ramRemoteSwOk(sw))) break;
|
||||
promised = true;
|
||||
// an explicit '6310' promises more occurrences
|
||||
if (sw === '6310') { promised = true; continue; }
|
||||
// a '61xx' page may mask the 6310 signal (the GET RESPONSE's
|
||||
// own SW replaces it) - probe one more page, but only a real
|
||||
// promise is reported when it stays unfulfilled
|
||||
if (pending && ramRemoteSwOk(sw)) continue;
|
||||
break;
|
||||
}
|
||||
// An unfulfilled 6310 only: a follow-up page that was accepted and
|
||||
// ended the listing fulfils the promise.
|
||||
@@ -10157,6 +10194,42 @@ async function ramExplore(sp) {
|
||||
}
|
||||
await elfTagListRetry();
|
||||
|
||||
// Auto: the bulk listing may still miss expanded details (a card whose
|
||||
// next-occurrence query is refused in every command format, or an entry
|
||||
// the expanded listing skipped). Those entries are queried individually
|
||||
// by AID - the AID search qualifier selects one entity, and the apps tag
|
||||
// list works even where the ELF one is refused. Bounded (<= 6 packets),
|
||||
// one entry per query, only for entries with no expanded fields at all;
|
||||
// the compact command string carries the GET RESPONSE for a 61xx answer.
|
||||
async function appsPerAidDetails() {
|
||||
if (!autoFormat) return;
|
||||
for (const e of ramAppsMissingDetails(apps).slice(0, 6)) {
|
||||
if (cntrLow) break;
|
||||
ramShowProgress(t('Details') + ' ' + e.aid + '...');
|
||||
const apdu = ramExpandedQueryApdu('40', 'tags', false, e.aid);
|
||||
const send = async a => {
|
||||
const res = await sendOta(a, Object.assign({}, sp,
|
||||
{ cntr, spi2: ramListingSpi2('40'), ram_format: 'compact' }));
|
||||
if (res.ram_format) sp.ram_format = res.ram_format;
|
||||
if (res.final_cntr) cntr = res.final_cntr;
|
||||
else if (res.success && spPorAccepted(res.por)) cntr = ramIncrementCntr(cntr);
|
||||
return res;
|
||||
};
|
||||
let res = await send(apdu);
|
||||
if (!res.success || !res.por || !spPorAccepted(res.por)) continue;
|
||||
let sw = ((res.por.decoded && res.por.decoded.last_status_word) || '').toUpperCase();
|
||||
if (/^61[0-9A-F]{2}$/.test(sw)) {
|
||||
res = await send(ramChainGetResponse(apdu));
|
||||
if (!res.success || !res.por || !spPorAccepted(res.por)) continue;
|
||||
}
|
||||
const data = res.por.decoded ? (res.por.decoded.last_response_data || '') : '';
|
||||
const parsed = data ? ramParseAppStatus(data) : [];
|
||||
const hit = parsed.find(x => x.aid && x.aid.toUpperCase() === e.aid.toUpperCase());
|
||||
if (hit) ramMergeExpanded([e], [hit]);
|
||||
}
|
||||
}
|
||||
await appsPerAidDetails();
|
||||
|
||||
// A package whose own listing carries no version may still be known from
|
||||
// its applet instance (the app entry carries the load file version and the
|
||||
// package AID); the rows are annotated when this inference is used.
|
||||
@@ -12664,6 +12737,23 @@ function ramFmtLadder(runFmt, working) {
|
||||
return [working].concat(ladder.filter(f => f !== null && f !== working));
|
||||
}
|
||||
|
||||
// The command formats to try for a next-occurrence page: the expanded codings
|
||||
// first, the compact command string last. A compact next-occurrence attempt
|
||||
// can *consume* the page on a card that processes it but cannot deliver the
|
||||
// pending data (the live card answers '61xx' and the chained GET RESPONSE
|
||||
// fails - every later format then sees the occurrence already consumed), while
|
||||
// the expanded codings return the page inline (the live card serves it wrapped
|
||||
// in the 'AA' template). The format that served the previous page is kept
|
||||
// first when it is one of the expanded codings; the run format ('auto') is
|
||||
// never returned: the server's format detection would cost an extra packet
|
||||
// per page.
|
||||
function ramPageFormats(working) {
|
||||
const w = working && working !== 'auto' ? working : null;
|
||||
const head = (w && w !== 'compact') ? [w] : [];
|
||||
const tail = ['expanded', 'expanded-ae'].filter(f => f !== w);
|
||||
return head.concat(tail, ['compact']);
|
||||
}
|
||||
|
||||
function ramRemoteSwOk(sw) {
|
||||
const s = (sw || '').toUpperCase();
|
||||
return !!s && (s === '9000' || s === 'CAFE' ||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v369';
|
||||
const CACHE = 'simple-v371';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -26,7 +26,7 @@ const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamB
|
||||
'ramFormatIsExpanded', 'ramFmtLadder',
|
||||
'stkParamsBuild', 'ramRemoteSwOk', 'spPorAccepted', 'ramIncrementCntr', 'ramDeleteFromExplorer', 'ramListingSpi2', 'ramRemoveFromExplorer', 'ramHasInstance', 'ramExpandedQueryApdu',
|
||||
'_parseRawElfEntry', '_parseRawAppEntry', 'ramParseElfStatus', 'ramParseAppStatus', 'ramParseModuleAids', 'parseTLV', '_parseE3Entry', '_parseMenuEntries',
|
||||
'ramExpandedReport', 'ramExpandedMore', 'ramExpandedTags', 'ramExpandedGroups', 'ramExpandedElfForm', 'ramElfVersionHint', 'cardEtsiRelease', 'ramChainGetResponse', 'ramDeriveElfVersions', 'ramElfAppletCandidate',
|
||||
'ramExpandedReport', 'ramExpandedMore', 'ramPageFormats', 'ramAppsMissingDetails', 'ramExpandedTags', 'ramExpandedGroups', 'ramExpandedElfForm', 'ramElfVersionHint', 'cardEtsiRelease', 'ramChainGetResponse', 'ramDeriveElfVersions', 'ramElfAppletCandidate',
|
||||
'spCntrLow', 'ramCntrLowHtml', 'ramCntrLowPresetIdx', 'ramCntrLowView', 'ramShowCntrLow',
|
||||
'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
|
||||
'spKeysetOptionsHtml', 'spPresetIdx', 'tarPresetIdx',
|
||||
@@ -1628,4 +1628,30 @@ test('ramExpandedMore marks only an unfulfilled 6310', () => {
|
||||
assert.strictEqual(ramExpandedMore(true, '6310', 8), true);
|
||||
// a transport failure (no remote status word) leaves it unfulfilled
|
||||
assert.strictEqual(ramExpandedMore(true, 'index out of range', 2), true);
|
||||
// a probe after a '61xx' page (nothing promised) never flags the listing
|
||||
assert.strictEqual(ramExpandedMore(false, '6A86', 2), false);
|
||||
});
|
||||
|
||||
test('ramPageFormats tries the expanded codings first, compact last', () => {
|
||||
// the compact command string is last: a compact page attempt can consume
|
||||
// the page without delivering it (the live card's 61xx + failed chain)
|
||||
assert.deepStrictEqual(ramPageFormats('compact'), ['expanded', 'expanded-ae', 'compact']);
|
||||
assert.deepStrictEqual(ramPageFormats('expanded'), ['expanded', 'expanded-ae', 'compact']);
|
||||
assert.deepStrictEqual(ramPageFormats('expanded-ae'), ['expanded-ae', 'expanded', 'compact']);
|
||||
// the run format ('auto') is never a page format: the server's detection
|
||||
// would cost an extra packet per page
|
||||
assert.deepStrictEqual(ramPageFormats('auto'), ['expanded', 'expanded-ae', 'compact']);
|
||||
assert.deepStrictEqual(ramPageFormats(null), ['expanded', 'expanded-ae', 'compact']);
|
||||
});
|
||||
|
||||
test('ramAppsMissingDetails picks only the entries without expanded fields', () => {
|
||||
assert.deepStrictEqual(ramAppsMissingDetails([
|
||||
{ aid: 'A1', version: '1.0' },
|
||||
{ aid: 'A2' },
|
||||
{ aid: 'A3', sdAid: 'AA' },
|
||||
{ aid: 'A4', menuEntries: [{ position: 1 }] },
|
||||
{ aid: 'A5', rawTlvs: ['8F'] },
|
||||
{ aid: '' },
|
||||
]).map(e => e.aid), ['A2']);
|
||||
assert.deepStrictEqual(ramAppsMissingDetails(null), []);
|
||||
});
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-simple-server"
|
||||
version = "3.13.0"
|
||||
version = "3.13.1"
|
||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||
requires-python = ">=3.8"
|
||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||
|
||||
@@ -32,7 +32,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '3.13.0'
|
||||
VERSION = '3.13.1'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
|
||||
Reference in New Issue
Block a user