Files view: name the phonebook files from the card's EF_PBR (v2.7.7)
- EF_PBR (TS 31.102 §4.4.2.1) is the card's declaration of its phonebook:
the constructed tags A8/A9/AA announce the type-1/2/3 groups and each nested
primitive tag C0…CC names the file kind plus its FID — and, in a three-byte
object, its SFI (length 2 = FID only). pySim has no parser for it (its
EF_PBR class still carries a "# TODO: a80ac0034f3a…" stub), so the decoder
follows the spec: new simtrace_analyser/pbr.py
- the tracker decodes every EF_PBR record it observes (a READ RECORD's data
or an UPDATE RECORD's body) and uses it for the DF it belongs to: the
phonebook files get their symbolic names (4FA1 = EF_PBC, 4F3A = EF_ADN,
4F01 = EF_IAP, 4F4A = EF_EXT1, 4F3D = EF_CCP1, 4F61 = EF_EMAIL, …), an
earlier placeholder name is upgraded when the PBR arrives later, and the
declared SFIs feed the DF's SFI→FID map (so SFI-addressed reads resolve
without waiting for an FCP)
- verified on the live sessions 47/193:
DF_TELECOM/DF_PHONEBOOK/EF_PBC (4FA1) and the SFI map
{2→4F3A, 4→4FA1, 6→4F4A, 7→4F4F}
- also fixed: the "SFI = FID's five low bits" fallback (TS 102 221
§11.1.1.4.8) no longer applies to a DF/ADF's FCP, which has no SFI
- tests: tests/test_pbr.py (the two real records, SFI-less objects, padding,
truncation, merging, the shape check) and tracker cases (names, path,
SFI map, the later-PBR upgrade); 537 Python + 76 Node tests
- README/README_RUS document the PBR naming; PWA_VERSION/pyproject/
version.py 2.7.7, SW cache simtrace-analyser-v87
This commit is contained in:
1 parent
5dea6bceb8
commit
ac424e67e3
13 files changed
+283
-9
No files matched your search
@@ -323,10 +323,14 @@ class TestFcpResponse(unittest.TestCase):
|
||||
self.assertIsNone(fcp['sfi'])
|
||||
|
||||
def test_fcp_sfi_absent_uses_fid_lsbs(self):
|
||||
# No SFI tag → SFI = 5 LSBs of the FID (TS 102 221 §11.1.2).
|
||||
# No SFI tag → SFI = 5 LSBs of the FID (TS 102 221 §11.1.1.4.8).
|
||||
from simtrace_analyser.decode import _decode_fcp
|
||||
fcp = _decode_fcp(bytes.fromhex('8202412183022f06'))
|
||||
self.assertEqual(fcp['sfi'], 0x06)
|
||||
# …but only for an EF: a DF/ADF's FCP carries no SFI.
|
||||
df = _decode_fcp(bytes.fromhex('82017883025f3a'))
|
||||
self.assertEqual(df['file_descriptor']['file_type'], 'DF or ADF')
|
||||
self.assertNotIn('sfi', df)
|
||||
|
||||
def test_fcp_summary_transparent(self):
|
||||
# GET RESPONSE (FCP) for a transparent EF of 9 bytes
|
||||
|
||||
@@ -560,6 +560,37 @@ class TestDfChain(TrackerCase):
|
||||
self.assertEqual(entry['n_records'], 100)
|
||||
|
||||
|
||||
class TestPbrNames(TrackerCase):
|
||||
def test_pbr_names_the_phonebook_files(self):
|
||||
# The card's EF_PBR declares the phonebook files' FIDs, roles and SFIs
|
||||
# (TS 31.102 §4.4.2.1): 4FA1 = EF_PBC with SFI 4.
|
||||
pbr_rec = 'a80ac0034f3a02c5034fa104aa0ac2034f4a06cb034f4f07'
|
||||
data = self.track(
|
||||
sel_path('7f105f3a'), gr(fcp('5f3a')),
|
||||
sel_fid('4f30'), gr(fcp('4f30', rec_len=24, n_rec=1)),
|
||||
read_record(1, pbr_rec),
|
||||
sel_fid('4fa1'), gr(fcp('4fa1', rec_len=44, n_rec=100)),
|
||||
)
|
||||
entry = data['files']['5f3a/4fa1']
|
||||
self.assertEqual(entry['name'], 'EF_PBC')
|
||||
self.assertEqual(entry['path'],
|
||||
'DF_TELECOM/DF_PHONEBOOK/EF_PBC (4FA1)')
|
||||
# The PBR's SFI feeds the DF's SFI relation map.
|
||||
self.assertEqual(data['sfi_relations'].get('5f3a', {}).get('4'), '4fa1')
|
||||
|
||||
def test_pbr_name_upgrades_an_earlier_placeholder(self):
|
||||
# A file seen before its PBR record is named once the record arrives.
|
||||
pbr_rec = 'a80ac0034f3a02c5034fa104aa0ac2034f4a06cb034f4f07'
|
||||
data = self.track(
|
||||
sel_path('7f105f3a'), gr(fcp('5f3a')),
|
||||
sel_fid('4fa1'), gr(fcp('4fa1', rec_len=44, n_rec=100)),
|
||||
sel_fid('4f30'), gr(fcp('4f30', rec_len=24, n_rec=1)),
|
||||
read_record(1, pbr_rec),
|
||||
)
|
||||
entry = data['files']['5f3a/4fa1']
|
||||
self.assertEqual(entry['name'], 'EF_PBC')
|
||||
|
||||
|
||||
class TestFilePath(unittest.TestCase):
|
||||
def test_paths(self):
|
||||
from simtrace_analyser.filetrack import _file_path
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"""EF_PBR record decoding (TS 31.102 §4.4.2.1)."""
|
||||
|
||||
import unittest
|
||||
|
||||
from simtrace_analyser.pbr import (looks_like_pbr, parse_pbr,
|
||||
parse_pbr_record)
|
||||
|
||||
# The records seen in the local test cards' traces.
|
||||
REC_SESSION_47 = bytes.fromhex(
|
||||
'a80fc0034f3a04c1034f0108c5034fa11ea905ca034f6113'
|
||||
'aa0ac2034f4a06cb034f3d12' + 'ff' * 12)
|
||||
REC_SESSION_193 = bytes.fromhex(
|
||||
'a80ac0034f3a02c5034fa104' 'aa0ac2034f4a06cb034f4f07')
|
||||
|
||||
|
||||
class TestPbrRecord(unittest.TestCase):
|
||||
def test_session_47_record(self):
|
||||
# A8 = type 1 (ADN 4F3A/SFI 4, IAP 4F01/SFI 8, PBC 4FA1/SFI 30);
|
||||
# A9 = type 2 (EMAIL 4F61/SFI 19); AA = type 3 (EXT1 4F4A/SFI 6,
|
||||
# CCP1 4F3D/SFI 18). The FF padding ends the record.
|
||||
parsed = parse_pbr_record(REC_SESSION_47)
|
||||
self.assertEqual(parsed['master'], '4f3a')
|
||||
types = [g['type'] for g in parsed['groups']]
|
||||
self.assertEqual(types, [1, 2, 3])
|
||||
type1 = {f['tag']: (f['fid'], f.get('sfi'))
|
||||
for f in parsed['groups'][0]['files']}
|
||||
self.assertEqual(type1['C0'], ('4f3a', 4))
|
||||
self.assertEqual(type1['C1'], ('4f01', 8))
|
||||
self.assertEqual(type1['C5'], ('4fa1', 30))
|
||||
self.assertEqual(parsed['groups'][1]['files'][0]['name'], 'EF_EMAIL')
|
||||
self.assertEqual(parsed['groups'][2]['files'][0]['name'], 'EF_EXT1')
|
||||
|
||||
def test_session_193_record(self):
|
||||
parsed = parse_pbr_record(REC_SESSION_193)
|
||||
self.assertEqual(parsed['master'], '4f3a')
|
||||
files = {f['fid']: f for g in parsed['groups'] for f in g['files']}
|
||||
self.assertEqual(files['4fa1']['name'], 'EF_PBC')
|
||||
self.assertEqual(files['4fa1']['sfi'], 4)
|
||||
self.assertEqual(files['4f4a']['name'], 'EF_EXT1')
|
||||
self.assertEqual(files['4f4f']['name'], 'EF_CCP1')
|
||||
|
||||
def test_objects_without_an_sfi(self):
|
||||
# Length 2 = FID only (TS 31.102 §4.4.2.1): no SFI is declared.
|
||||
parsed = parse_pbr_record(bytes.fromhex('a808c0024f3ac5024fa1'))
|
||||
files = parsed['groups'][0]['files']
|
||||
self.assertEqual([(f['fid'], f.get('sfi')) for f in files],
|
||||
[('4f3a', None), ('4fa1', None)])
|
||||
|
||||
def test_truncated_and_unknown(self):
|
||||
# A truncated record ends parsing instead of inventing objects.
|
||||
self.assertEqual(parse_pbr_record(bytes.fromhex('a80ac0034f3a')),
|
||||
{'groups': [], 'master': None})
|
||||
self.assertEqual(parse_pbr_record(bytes.fromhex('a804c0034f3a'))['groups'],
|
||||
[{'type': 1, 'tag': 'A8', 'files': []}])
|
||||
# Unknown primitive tags are skipped; 'FF' padding ends the record.
|
||||
parsed = parse_pbr_record(bytes.fromhex('a809c0034f3a04d102abcd'))
|
||||
self.assertEqual([f['fid'] for f in parsed['groups'][0]['files']],
|
||||
['4f3a'])
|
||||
self.assertEqual(parse_pbr_record(b'\xff' * 8)['groups'], [])
|
||||
self.assertEqual(parse_pbr_record(b''),
|
||||
{'groups': [], 'master': None})
|
||||
|
||||
def test_parse_pbr_merges_records(self):
|
||||
parsed = parse_pbr([REC_SESSION_47, REC_SESSION_193])
|
||||
self.assertEqual(parsed['master'], '4f3a')
|
||||
self.assertEqual(parsed['files']['4fa1']['name'], 'EF_PBC')
|
||||
self.assertEqual(parsed['files']['4f01']['name'], 'EF_IAP')
|
||||
# A later record may add files; the first declaration is kept.
|
||||
self.assertEqual(parsed['files']['4fa1']['sfi'], 30)
|
||||
|
||||
def test_shape_check(self):
|
||||
self.assertTrue(looks_like_pbr(REC_SESSION_193))
|
||||
self.assertFalse(looks_like_pbr(b''))
|
||||
self.assertFalse(looks_like_pbr(bytes.fromhex('c0034f3a')))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -224,7 +224,7 @@ class TestImportEndpoint(unittest.TestCase):
|
||||
class TestVersion(unittest.TestCase):
|
||||
def test_package_version(self):
|
||||
from simtrace2_pysniff import __version__
|
||||
self.assertEqual(__version__, '2.7.6')
|
||||
self.assertEqual(__version__, '2.7.7')
|
||||
|
||||
def test_status_includes_version(self):
|
||||
import os
|
||||
|
||||
Reference in new issue
Block a user