filesystem: add unit tests for encoder/decoder methods

Lets add test vectors for the per-record/per-file encode/decode of
our various classes for the Elementary Files.

We keep the test vectors as class variables of the respective EF-classes
to ensure implementation and test vectors are next to each other.

The test classes then iterate over all EF subclasses and execute the
decode/encode functions using the test vectors from the class variables.

Change-Id: I02d884547f4982e0b8ed7ef21b8cda75237942e2
Related: OS#4963
diff --git a/pySim/gsm_r.py b/pySim/gsm_r.py
index 78c9ad9..389a8cb 100644
--- a/pySim/gsm_r.py
+++ b/pySim/gsm_r.py
@@ -58,7 +58,11 @@
 
 class EF_FN(LinFixedEF):
     """Section 7.2"""
-
+    _test_decode = [
+        ( "40315801000010ff01",
+          { "functional_number_and_type": { "functional_number": "04138510000001f",
+                "presentation_of_only_this_fn": True, "permanent_fn": True }, "list_number": 1 } ),
+    ]
     def __init__(self):
         super().__init__(fid='6ff1', sfid=None, name='EF.FN',
                          desc='Functional numbers', rec_len=(9, 9))
@@ -147,7 +151,12 @@
 
 class EF_CallconfC(TransparentEF):
     """Section 7.3"""
-
+    _test_de_encode = [
+        ( "026121ffffffffffff1e000a040a010253600795792426f0",
+          { "pl_conf": 3, "conf_nr": "1612ffffffffffff", "max_rand": 30, "n_ack_max": 10,
+            "pl_ack": 1, "n_nested_max": 10, "train_emergency_gid": 1, "shunting_emergency_gid": 2,
+           "imei": "350670599742620f" } ),
+    ]
     def __init__(self):
         super().__init__(fid='6ff2', sfid=None, name='EF.CallconfC', size=(24, 24),
                          desc='Call Configuration of emergency calls Configuration')
@@ -180,7 +189,9 @@
 
 class EF_Shunting(TransparentEF):
     """Section 7.6"""
-
+    _test_de_encode = [
+        ( "03f8ffffff000000", { "common_gid": 3, "shunting_gid": "f8ffffff000000" } ),
+    ]
     def __init__(self):
         super().__init__(fid='6ff4', sfid=None,
                          name='EF.Shunting', desc='Shunting', size=(8, 8))
@@ -190,7 +201,18 @@
 
 class EF_GsmrPLMN(LinFixedEF):
     """Section 7.7"""
-
+    _test_de_encode = [
+        ( "22f860f86f8d6f8e01", { "plmn": "228f06", "class_of_network": {
+                                    "supported": { "vbs": True, "vgcs": True, "emlpp": True,
+                                    "fn": True, "eirene": True }, "preference": 0 },
+                                  "ic_incoming_ref_tbl": "6f8d", "outgoing_ref_tbl": "6f8e",
+                                  "ic_table_ref": "01" } ),
+        ( "22f810416f8d6f8e02", { "plmn": "228f01", "class_of_network": {
+                                    "supported": { "vbs": False, "vgcs": False, "emlpp": False,
+                                    "fn": True, "eirene": False }, "preference": 1 },
+                                  "ic_incoming_ref_tbl": "6f8d", "outgoing_ref_tbl": "6f8e",
+                                  "ic_table_ref": "02" } ),
+    ]
     def __init__(self):
         super().__init__(fid='6ff5', sfid=None, name='EF.GsmrPLMN',
                          desc='GSM-R network selection', rec_len=(9, 9))
@@ -204,7 +226,12 @@
 
 class EF_IC(LinFixedEF):
     """Section 7.8"""
-
+    _test_de_encode = [
+        ( "f06f8e40f10001", { "next_table_type": "decision", "id_of_next_table": "6f8e",
+                              "ic_decision_value": "041f", "network_string_table_index": 1 } ),
+        ( "ffffffffffffff", { "next_table_type": "empty", "id_of_next_table": "ffff",
+                              "ic_decision_value": "ffff", "network_string_table_index": 65535 } ),
+    ]
     def __init__(self):
         super().__init__(fid='6f8d', sfid=None, name='EF.IC',
                          desc='International Code', rec_len=(7, 7))
@@ -216,7 +243,12 @@
 
 class EF_NW(LinFixedEF):
     """Section 7.9"""
-
+    _test_de_encode = [
+        ( "47534d2d52204348", "GSM-R CH" ),
+        ( "537769737347534d", "SwissGSM" ),
+        ( "47534d2d52204442", "GSM-R DB" ),
+        ( "47534d2d52524649", "GSM-RRFI" ),
+    ]
     def __init__(self):
         super().__init__(fid='6f80', sfid=None, name='EF.NW',
                          desc='Network Name', rec_len=(8, 8))
@@ -225,8 +257,15 @@
 
 class EF_Switching(LinFixedEF):
     """Section 8.4"""
-
-    def __init__(self, fid, name, desc):
+    _test_de_encode = [
+        ( "f26f87f0ff00", { "next_table_type": "num_dial_digits", "id_of_next_table": "6f87",
+                            "decision_value": "0fff", "string_table_index": 0 } ),
+        ( "f06f8ff1ff01", { "next_table_type": "decision", "id_of_next_table": "6f8f",
+                            "decision_value": "1fff", "string_table_index": 1 } ),
+        ( "f16f89f5ff05", { "next_table_type": "predefined", "id_of_next_table": "6f89",
+                            "decision_value": "5fff", "string_table_index": 5 } ),
+    ]
+    def __init__(self, fid='1234', name='Switching', desc=None):
         super().__init__(fid=fid, sfid=None,
                          name=name, desc=desc, rec_len=(6, 6))
         self._construct = Struct('next_table_type'/NextTableType,
@@ -237,13 +276,17 @@
 
 class EF_Predefined(LinFixedEF):
     """Section 8.5"""
+    _test_de_encode = [
+        ( "f26f85", 1, { "next_table_type": "num_dial_digits", "id_of_next_table": "6f85" } ),
+        ( "f0ffc8", 2, { "predefined_value1": "0fff", "string_table_index1": 200 } ),
+    ]
     # header and other records have different structure. WTF !?!
     construct_first = Struct('next_table_type'/NextTableType,
                              'id_of_next_table'/HexAdapter(Bytes(2)))
     construct_others = Struct('predefined_value1'/BcdAdapter(Bytes(2)),
                               'string_table_index1'/Int8ub)
 
-    def __init__(self, fid, name, desc):
+    def __init__(self, fid='1234', name='Predefined', desc=None):
         super().__init__(fid=fid, sfid=None,
                          name=name, desc=desc, rec_len=(3, 3))
 
@@ -263,8 +306,11 @@
 
 class EF_DialledVals(TransparentEF):
     """Section 8.6"""
-
-    def __init__(self, fid, name, desc):
+    _test_de_encode = [
+        ( "ffffff22", { "next_table_type": "empty", "id_of_next_table": "ffff", "dialed_digits": "22" } ),
+        ( "f16f8885", { "next_table_type": "predefined", "id_of_next_table": "6f88", "dialed_digits": "58" }),
+    ]
+    def __init__(self, fid='1234', name='DialledVals', desc=None):
         super().__init__(fid=fid, sfid=None, name=name, desc=desc, size=(4, 4))
         self._construct = Struct('next_table_type'/NextTableType,
                                  'id_of_next_table'/HexAdapter(Bytes(2)),
diff --git a/pySim/ts_102_221.py b/pySim/ts_102_221.py
index e180b28..b8db5c9 100644
--- a/pySim/ts_102_221.py
+++ b/pySim/ts_102_221.py
@@ -574,6 +574,14 @@
 
 # TS 102 221 Section 13.1
 class EF_DIR(LinFixedEF):
+    # FIXME: re-encode failure when changing to _test_de_encode
+    _test_decode = [
+        ( '61294f10a0000000871002ffffffff890709000050055553696d31730ea00c80011781025f608203454150',
+          { "application_template": [ { "application_id": h2b("a0000000871002ffffffff8907090000") },
+                                      { "application_label": "USim1" },
+                                      { "discretionary_template": h2b("a00c80011781025f608203454150") } ] }
+        ),
+    ]
     class ApplicationLabel(BER_TLV_IE, tag=0x50):
         # TODO: UCS-2 coding option as per Annex A of TS 102 221
         _construct = GreedyString('ascii')
@@ -593,6 +601,9 @@
 
 # TS 102 221 Section 13.2
 class EF_ICCID(TransparentEF):
+    _test_de_encode = [
+        ( '988812010000400310f0', { "iccid": "8988211000000430010" } ),
+    ]
     def __init__(self, fid='2fe2', sfid=0x02, name='EF.ICCID', desc='ICC Identification'):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=(10, 10))
 
@@ -604,6 +615,11 @@
 
 # TS 102 221 Section 13.3
 class EF_PL(TransRecEF):
+    _test_de_encode = [
+        ( '6465', "de" ),
+        ( '656e', "en" ),
+        ( 'ffff', None ),
+    ]
     def __init__(self, fid='2f05', sfid=0x05, name='EF.PL', desc='Preferred Languages'):
         super().__init__(fid, sfid=sfid, name=name,
                          desc=desc, rec_len=2, size=(2, None))
@@ -623,6 +639,28 @@
 
 # TS 102 221 Section 13.4
 class EF_ARR(LinFixedEF):
+    _test_de_encode = [
+        ( '800101a40683010a950108800106900080016097008401d4a40683010a950108',
+         [ [ { "access_mode": [ "read_search_compare" ] },
+             { "control_reference_template": "ADM1" } ],
+           [ { "access_mode": [ "write_append", "update_erase" ] },
+             { "always": None } ],
+           [ { "access_mode": [ "delete_file", "terminate_ef" ] },
+             { "never": None } ],
+           [ { "command_header": { "INS": 212 } },
+             { "control_reference_template": "ADM1" } ]
+         ] ),
+        ( '80010190008001029700800118a40683010a9501088401d4a40683010a950108',
+         [ [ { "access_mode": [ "read_search_compare" ] },
+             { "always": None } ],
+           [ { "access_mode": [ "update_erase" ] },
+             { "never": None } ],
+           [ { "access_mode": [ "activate_file_or_record", "deactivate_file_or_record" ] },
+             { "control_reference_template": "ADM1" } ],
+           [ { "command_header": { "INS": 212 } },
+             { "control_reference_template": "ADM1" } ]
+         ] ),
+    ]
     def __init__(self, fid='2f06', sfid=0x06, name='EF.ARR', desc='Access Rule Reference'):
         super().__init__(fid, sfid=sfid, name=name, desc=desc)
         # add those commands to the general commands of a TransparentEF
@@ -705,6 +743,10 @@
 
 # TS 102 221 Section 13.6
 class EF_UMPC(TransparentEF):
+    _test_de_encode = [
+        ( '3cff02', { "max_current_mA": 60, "t_op_s": 255,
+                      "addl_info": { "req_inc_idle_current": False, "support_uicc_suspend": True } } ),
+    ]
     def __init__(self, fid='2f08', sfid=0x08, name='EF.UMPC', desc='UICC Maximum Power Consumption'):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=(5, 5))
         addl_info = FlagsEnum(Byte, req_inc_idle_current=1,
diff --git a/pySim/ts_31_102.py b/pySim/ts_31_102.py
index 4125f4a..3edc6e3 100644
--- a/pySim/ts_31_102.py
+++ b/pySim/ts_31_102.py
@@ -539,6 +539,7 @@
 
 # TS 31.102 Section 4.2.6
 class EF_HPPLMN(TransparentEF):
+    _test_de_encode = [ ( '05', 5 ) ]
     def __init__(self, fid='6f31', sfid=0x12, name='EF.HPPLMN', size=(1, 1),
                  desc='Higher Priority PLMN search period'):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
@@ -595,7 +596,17 @@
 
 # TS 31.103 Section 4.2.7 - *not* the same as DF.GSM/EF.ECC!
 class EF_ECC(LinFixedEF):
-    cc_construct = Rpad(BcdAdapter(Rpad(Bytes(3))), pattern='f')
+    _test_de_encode = [
+        ( '19f1ff01', { "call_code": "911f",
+                        "service_category": { "police": True, "ambulance": False, "fire_brigade": False,
+                                              "marine_guard": False, "mountain_rescue": False,
+                                              "manual_ecall": False, "automatic_ecall": False } } ),
+        ( '19f3ff02', { "call_code": "913f",
+                        "service_category": { "police": False, "ambulance": True, "fire_brigade": False,
+                                              "marine_guard": False, "mountain_rescue": False,
+                                              "manual_ecall": False, "automatic_ecall": False } } ),
+    ]
+    cc_construct = BcdAdapter(Rpad(Bytes(3)))
     category_construct = FlagsEnum(Byte, police=1, ambulance=2, fire_brigade=3, marine_guard=4,
                                    mountain_rescue=5, manual_ecall=6, automatic_ecall=7)
     alpha_construct = GsmStringAdapter(Rpad(GreedyBytes))
@@ -633,12 +644,28 @@
 
 # TS 31.102 Section 4.2.17
 class EF_LOCI(TransparentEF):
+    _test_de_encode = [
+        ( '47d1264a62f21037211e00',
+          { "tmsi": "47d1264a", "lai": { "mcc_mnc": "262f01", "lac": "3721" },
+            "rfu": 30, "lu_status": 0 } ),
+    ]
     def __init__(self, fid='6f7e', sfid=0x0b, name='EF.LOCI', desc='Location information', size=(11, 11)):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
         Lai = Struct('mcc_mnc'/BcdAdapter(Bytes(3)), 'lac'/HexAdapter(Bytes(2)))
         self._construct = Struct('tmsi'/HexAdapter(Bytes(4)), 'lai'/Lai, 'rfu'/Int8ub, 'lu_status'/Int8ub)
+
 # TS 31.102 Section 4.2.18
 class EF_AD(TransparentEF):
+    _test_de_encode = [
+        ( '00000002', { "ms_operation_mode": "normal",
+                        "additional_info": { "ciphering_indicator": False, "csg_display_control": False,
+                                             "prose_services": False, "extended_drx": False },
+                        "rfu": 0, "mnc_len": 2, "extensions": b'' } ),
+        ( '01000102', { "ms_operation_mode": "normal_and_specific_facilities",
+                        "additional_info": { "ciphering_indicator": True, "csg_display_control": False,
+                                             "prose_services": False, "extended_drx": False },
+                        "rfu": 0, "mnc_len": 2, "extensions": b'' } ),
+    ]
     class OP_MODE(enum.IntEnum):
         normal = 0x00
         type_approval = 0x80
@@ -739,6 +766,9 @@
 
 # TS 31.102 Section 4.2.51
 class EF_START_HFN(TransparentEF):
+    _test_de_encode = [
+        ( 'f00000f00000', { "start_cs": 15728640, "start_ps": 15728640 } ),
+    ]
     def __init__(self, fid='6f5b', sfid=0x0f, name='EF.START-HFN', size=(6, 6),
                  desc='Initialisation values for Hyperframe number', **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
@@ -746,6 +776,9 @@
 
 # TS 31.102 Section 4.2.52
 class EF_THRESHOLD(TransparentEF):
+    _test_de_encode = [
+        ( 'f01000', { "max_start": 15732736 } ),
+    ]
     def __init__(self, fid='6f5c', sfid=0x10, name='EF.THRESHOLD', size=(3, 3),
                  desc='Maximum value of START', **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
@@ -820,6 +853,9 @@
 
 # TS 31.102 Section 4.2.85
 class EF_EHPLMNPI(TransparentEF):
+    _test_de_encode = [
+        ( '02', { "presentation_ind": "display_all" } ),
+    ]
     def __init__(self, fid='6fdb', sfid=None, name='EF.EHPLMNPI', size=(1, 1),
                  desc='Equivalent HPLMN Presentation Indication', **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
@@ -905,6 +941,10 @@
 
 # TS 31.102 Section 4.2.96
 class EF_PWS(TransparentEF):
+    _test_de_encode = [
+        ( '00', { "pws_configuration": { "ignore_pws_in_hplmn_and_equivalent": False,
+                                         "ignore_pws_in_vplmn": False } } ),
+    ]
     def __init__(self, fid='6fec', sfid=None, name='EF.PWS', desc='Public Warning System', size=(1, 1), **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
         pws_config = FlagsEnum(
@@ -973,6 +1013,10 @@
 
 # TS 31.102 Section 4.4.11.7
 class EF_UAC_AIC(TransparentEF):
+    _test_de_encode = [
+        ( '03', { "uac_access_id_config": { "multimedia_priority_service": True,
+                                            "mission_critical_service": True } } ),
+    ]
     def __init__(self, fid='4f06', sfid=0x06, name='EF.UAC_AIC', size=(4, 4),
                  desc='UAC Access Identities Configuration', **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
diff --git a/pySim/ts_31_102_telecom.py b/pySim/ts_31_102_telecom.py
index 06317b9..a0098c7 100644
--- a/pySim/ts_31_102_telecom.py
+++ b/pySim/ts_31_102_telecom.py
@@ -124,6 +124,7 @@
 
 # TS 31.102 Section 4.4.2.1
 class EF_PBR(LinFixedEF):
+    # TODO: a80ac0034f3a02c5034f0904aa0acb034f3d07c2034f4a06
     def __init__(self, fid='4F30', name='EF.PBR', desc='Phone Book Reference', **kwargs):
         super().__init__(fid, name=name, desc=desc, **kwargs)
         #self._tlv = FIXME
diff --git a/pySim/ts_31_103.py b/pySim/ts_31_103.py
index ee50ddb..4a66c0d 100644
--- a/pySim/ts_31_103.py
+++ b/pySim/ts_31_103.py
@@ -80,6 +80,11 @@
 
 # TS 31.103 Section 4.2.2
 class EF_IMPI(TransparentEF):
+    # FIXME: re-encode fails with "string encoding failed, expected unicode string"
+    _test_decode = [
+        ( '803137333830303630303030303031303140696d732e6d6e633030302e6d63633733382e336770706e6574776f726b2e6f7267',
+          { "nai": "738006000000101@ims.mnc000.mcc738.3gppnetwork.org" } ),
+    ]
     class nai(BER_TLV_IE, tag=0x80):
         _construct = GreedyString("utf8")
 
@@ -89,6 +94,11 @@
 
 # TS 31.103 Section 4.2.3
 class EF_DOMAIN(TransparentEF):
+    # FIXME: re-encode fails with "string encoding failed, expected unicode string"
+    _test_decode = [
+        ( '8021696d732e6d6e633030302e6d63633733382e336770706e6574776f726b2e6f7267',
+          { "domain": "ims.mnc000.mcc738.3gppnetwork.org" } ),
+    ]
     class domain(BER_TLV_IE, tag=0x80):
         _construct = GreedyString("utf8")
 
@@ -98,6 +108,11 @@
 
 # TS 31.103 Section 4.2.4
 class EF_IMPU(LinFixedEF):
+    # FIXME: re-encode fails with "string encoding failed, expected unicode string"
+    _test_decode = [
+        ( '80357369703a37333830303630303030303031303140696d732e6d6e633030302e6d63633733382e336770706e6574776f726b2e6f7267',
+          { "impu": "sip:738006000000101@ims.mnc000.mcc738.3gppnetwork.org" } ),
+    ]
     class impu(BER_TLV_IE, tag=0x80):
         _construct = GreedyString("utf8")
 
@@ -139,6 +154,10 @@
 
 # TS 31.103 Section 4.2.8
 class EF_PCSCF(LinFixedEF):
+    _test_de_encode = [
+        ( '802c0070637363662e696d732e6d6e633030302e6d63633733382e7075622e336770706e6574776f726b2e6f7267',
+          { "addr": "pcscf.ims.mnc000.mcc738.pub.3gppnetwork.org", "addr_type": "00" } ),
+    ]
     def __init__(self, fid='6f09', sfid=None, name='EF.P-CSCF', desc='P-CSCF Address', **kwargs):
         super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, **kwargs)
 
@@ -163,6 +182,8 @@
 
 # TS 31.103 Section 4.2.11
 class EF_NAFKCA(LinFixedEF):
+    # TODO: 80296273662e696d732e6d6e633030302e6d63633733382e7075622e336770706e6574776f726b2e6f7267ffffffffffffff
+    # TODO: 8030656e65746e61667830312e696d732e6d6e633030302e6d63633733382e7075622e336770706e6574776f726b2e6f7267
     def __init__(self, fid='6fdd', sfid=None, name='EF.NAFKCA', desc='NAF Key Centre Address', **kwargs):
         super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, **kwargs)
 
diff --git a/pySim/ts_51_011.py b/pySim/ts_51_011.py
index 80182af..96de241 100644
--- a/pySim/ts_51_011.py
+++ b/pySim/ts_51_011.py
@@ -358,6 +358,14 @@
 
 # TS 51.011 Section 10.5.1
 class EF_ADN(LinFixedEF):
+    _test_decode = [
+            ( '42204841203120536963ffffffff06810628560810ffffffffffffff',
+              { "alpha_id": "B HA 1 Sic", "len_of_bcd": 6, "ton_npi": { "ext": True, "type_of_number":
+                                                                       "unknown", "numbering_plan_id":
+                                                                       "isdn_e164" }, "dialing_nr":
+               "6082658001", "cap_conf_id": 255, "ext1_record_id": 255 }),
+         ]
+
     def __init__(self, fid='6f3a', sfid=None, name='EF.ADN', desc='Abbreviated Dialing Numbers', ext=1, **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=(14, 30), **kwargs)
         ext_name = 'ext%u_record_id' % ext
@@ -419,6 +427,19 @@
 
 # TS 51.011 Section 10.5.6
 class EF_SMSP(LinFixedEF):
+    # FIXME: re-encode fails / missing alpha_id at start of output
+    _test_decode = [
+        ( '454e6574776f726b73fffffffffffffff1ffffffffffffffffffffffffffffffffffffffffffffffff0000a7',
+          { "alpha_id": "ENetworks", "parameter_indicators": { "tp_dest_addr": False, "tp_sc_addr": True,
+                                                               "tp_pid": True, "tp_dcs": True, "tp_vp": True },
+            "tp_dest_addr": { "length": 255, "ton_npi": { "ext": True, "type_of_number": "reserved_for_extension",
+                                                          "numbering_plan_id": "reserved_for_extension" },
+                              "call_number": "" },
+            "tp_sc_addr": { "length": 255, "ton_npi": { "ext": True, "type_of_number": "reserved_for_extension",
+                                                        "numbering_plan_id": "reserved_for_extension" },
+                            "call_number": "" },
+            "tp_pid": "00", "tp_dcs": "00", "tp_vp_minutes": 1440 } ),
+    ]
     class ValidityPeriodAdapter(Adapter):
         def _decode(self, obj, context, path):
             if obj <= 143:
@@ -530,6 +551,9 @@
 
 # TS 51.011 Section 10.3.1
 class EF_LP(TransRecEF):
+    _test_de_encode = [
+            ( "24", "24"),
+        ]
     def __init__(self, fid='6f05', sfid=None, name='EF.LP', size=(1, None), rec_len=1,
                  desc='Language Preference'):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len)
@@ -542,6 +566,10 @@
 
 # TS 51.011 Section 10.3.2
 class EF_IMSI(TransparentEF):
+    _test_de_encode = [
+            ( "082982608200002080", { "imsi": "228062800000208" } ),
+            ( "082926101160845740", { "imsi": "262011106487504" } ),
+        ]
     def __init__(self, fid='6f07', sfid=None, name='EF.IMSI', desc='IMSI', size=(9, 9)):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
         # add those commands to the general commands of a TransparentEF
@@ -580,6 +608,10 @@
 
 # TS 51.011 Section 10.3.4
 class EF_PLMNsel(TransRecEF):
+    _test_de_encode = [
+            ( "22F860",  { "mcc": "228", "mnc": "06" } ),
+            ( "330420",  { "mcc": "334", "mnc": "020" } ),
+        ]
     def __init__(self, fid='6f30', sfid=None, name='EF.PLMNsel', desc='PLMN selector',
                  size=(24, None), rec_len=3, **kwargs):
         super().__init__(fid, name=name, sfid=sfid, desc=desc, size=size, rec_len=rec_len, **kwargs)
@@ -598,6 +630,9 @@
 
 # TS 51.011 Section 10.3.6
 class EF_ACMmax(TransparentEF):
+    _test_de_encode = [
+            ( "000000", { "acm_max": 0 } ),
+        ]
     def __init__(self, fid='6f37', sfid=None, name='EF.ACMmax', size=(3, 3),
                  desc='ACM maximum value', **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
@@ -655,6 +690,11 @@
 
 # TS 51.011 Section 10.3.11
 class EF_SPN(TransparentEF):
+    _test_de_encode = [
+            ( "0147534d2d52204348ffffffffffffffff",
+              { "rfu": 0, "hide_in_oplmn": False, "show_in_hplmn": True, "spn": "GSM-R CH" } ),
+        ]
+
     def __init__(self, fid='6f46', sfid=None, name='EF.SPN',
                  desc='Service Provider Name', size=(17, 17), **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
@@ -669,6 +709,7 @@
 
 # TS 51.011 Section 10.3.13
 class EF_CBMI(TransRecEF):
+    # TODO: Test vectors
     def __init__(self, fid='6f45', sfid=None, name='EF.CBMI', size=(2, None), rec_len=2,
                  desc='Cell Broadcast message identifier selection', **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len, **kwargs)
@@ -676,6 +717,9 @@
 
 # TS 51.011 Section 10.3.15
 class EF_ACC(TransparentEF):
+    _test_de_encode = [
+            ( "0100", "0100" ),
+        ]
     def __init__(self, fid='6f78', sfid=None, name='EF.ACC',
                  desc='Access Control Class', size=(2, 2), **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
@@ -683,6 +727,10 @@
 
 # TS 51.011 Section 10.3.16
 class EF_LOCI(TransparentEF):
+    _test_de_encode = [
+            ( "7802570222f81009780000",
+              { "tmsi": "78025702", "lai": "22f8100978", "tmsi_time": 0, "lu_status": "updated" } ),
+        ]
     def __init__(self, fid='6f7e', sfid=None, name='EF.LOCI', desc='Location Information', size=(11, 11)):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
         self._construct = Struct('tmsi'/HexAdapter(Bytes(4)), 'lai'/HexAdapter(Bytes(5)), 'tmsi_time'/Int8ub,
@@ -691,6 +739,10 @@
 
 # TS 51.011 Section 10.3.18
 class EF_AD(TransparentEF):
+    _test_de_encode = [
+            ( "00ffff",
+              { "ms_operation_mode": "normal", "rfu1": 255, "rfu2": 127, "ofm": True, "extensions": None } ),
+        ]
     class OP_MODE(enum.IntEnum):
         normal = 0x00
         type_approval = 0x80
@@ -722,6 +774,9 @@
 
 # TS 51.011 Section 10.3.20 / 10.3.22
 class EF_VGCS(TransRecEF):
+    _test_de_encode = [
+            ( "92f9ffff", "299fffff" ),
+        ]
     def __init__(self, fid='6fb1', sfid=None, name='EF.VGCS', size=(4, 200), rec_len=4,
                  desc='Voice Group Call Service', **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len, **kwargs)
@@ -729,6 +784,12 @@
 
 # TS 51.011 Section 10.3.21 / 10.3.23
 class EF_VGCSS(TransparentEF):
+    _test_decode = [
+        ( "010000004540fc",
+          { "flags": [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                       0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ] }
+        ),
+    ]
     def __init__(self, fid='6fb2', sfid=None, name='EF.VGCSS', size=(7, 7),
                  desc='Voice Group Call Service Status', **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
@@ -737,6 +798,13 @@
 
 # TS 51.011 Section 10.3.24
 class EF_eMLPP(TransparentEF):
+    _test_de_encode = [
+        ( "7c04", { "levels": { "A": False, "B": False, "zero": True, "one": True,
+                                "two": True, "three": True, "four": True },
+                    "fast_call_setup_cond": { "A": False, "B": False, "zero": True, "one": False,
+                                              "two": False, "three": False, "four": False }
+                  }),
+    ]
     def __init__(self, fid='6fb5', sfid=None, name='EF.eMLPP', size=(2, 2),
                  desc='enhanced Multi Level Pre-emption and Priority', **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
@@ -747,6 +815,10 @@
 
 # TS 51.011 Section 10.3.25
 class EF_AAeM(TransparentEF):
+    _test_de_encode = [
+        ( "3c", { "auto_answer_prio_levels": { "A": False, "B": False, "zero": True, "one": True,
+                                               "two": True, "three": True, "four": False } } ),
+    ]
     def __init__(self, fid='6fb6', sfid=None, name='EF.AAeM', size=(1, 1),
                  desc='Automatic Answer for eMLPP Service', **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
@@ -816,12 +888,19 @@
 
 # TS 51.011 Section 10.3.32
 class EF_Kc(TransparentEF):
+    _test_de_encode = [
+        ( "837d783609a3858f05", { "kc": "837d783609a3858f", "cksn": 5 } ),
+    ]
     def __init__(self, fid='6f20', sfid=None, name='EF.Kc', desc='Ciphering key Kc', size=(9, 9), **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
         self._construct = Struct('kc'/HexAdapter(Bytes(8)), 'cksn'/Int8ub)
 
 # TS 51.011 Section 10.3.33
 class EF_LOCIGPRS(TransparentEF):
+    _test_de_encode = [
+        ( "ffffffffffffff22f8990000ff01",
+          { "ptmsi": "ffffffff", "ptmsi_sig": "ffffff", "rai": "22f8990000ff", "rau_status": "not_updated" } ),
+    ]
     def __init__(self, fid='6f53', sfid=None, name='EF.LOCIGPRS', desc='GPRS Location Information', size=(14, 14)):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
         self._construct = Struct('ptmsi'/HexAdapter(Bytes(4)), 'ptmsi_sig'/HexAdapter(Bytes(3)),
@@ -831,7 +910,11 @@
 
 # TS 51.011 Section 10.3.35..37
 class EF_xPLMNwAcT(TransRecEF):
-    def __init__(self, fid, sfid=None, name=None, desc=None, size=(40, None), rec_len=5, **kwargs):
+    _test_de_encode = [
+        ( '62F2104000', { "mcc": "262", "mnc": "01", "act": [ "E-UTRAN" ] } ),
+        ( '62F2108000', { "mcc": "262", "mnc": "01", "act": [ "UTRAN" ] } ),
+    ]
+    def __init__(self, fid='1234', sfid=None, name=None, desc=None, size=(40, None), rec_len=5, **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len, **kwargs)
 
     def _decode_record_hex(self, in_hex, **kwargs):
@@ -895,6 +978,8 @@
 
 # TS 51.011 Section 4.2.58
 class EF_PNN(LinFixedEF):
+    # TODO: 430a82d432bbbc7eb75de432450a82d432bbbc7eb75de432ffffffff
+    # TODO: 430a82c596b34cbfbfe5eb39ffffffffffffffffffffffffffffffffffff
     class FullNameForNetwork(BER_TLV_IE, tag=0x43):
         # TS 24.008 10.5.3.5a
         # TODO: proper decode
@@ -914,6 +999,10 @@
 
 # TS 51.011 Section 10.3.42
 class EF_OPL(LinFixedEF):
+    _test_de_encode = [
+        ( '62f2100000fffe01',
+          { "lai": { "mcc_mnc": "262f01", "lac_min": "0000", "lac_max": "fffe" }, "pnn_record_id": 1 } ),
+    ]
     def __init__(self, fid='6fc6', sfid=None, name='EF.OPL', rec_len=(8, 8), desc='Operator PLMN List', **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len, **kwargs)
         self._construct = Struct('lai'/Struct('mcc_mnc'/BcdAdapter(Bytes(3)),
@@ -921,6 +1010,10 @@
 
 # TS 51.011 Section 10.3.44 + TS 31.102 4.2.62
 class EF_MBI(LinFixedEF):
+    _test_de_encode = [
+        ( '0100000000',
+          { "mbi_voicemail": 1, "mbi_fax": 0, "mbi_email": 0, "mbi_other": 0, "mbi_videocall": 0 } ),
+    ]
     def __init__(self, fid='6fc9', sfid=None, name='EF.MBI', rec_len=(4, 5), desc='Mailbox Identifier', **kwargs):
         super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len, **kwargs)
         self._construct = Struct('mbi_voicemail'/Int8ub, 'mbi_fax'/Int8ub, 'mbi_email'/Int8ub,
@@ -938,6 +1031,7 @@
 
 # TS 51.011 Section 10.3.66
 class EF_SPDI(TransparentEF):
+    # TODO: a305800337f800ffffffffffffffffffffffffffffffffffffffffffffff
     class ServiceProviderPLMN(BER_TLV_IE, tag=0x80):
         # flexible numbers of 3-byte PLMN records
         _construct = GreedyRange(BcdAdapter(Bytes(3)))
diff --git a/tests/test_files.py b/tests/test_files.py
new file mode 100755
index 0000000..3fb1062
--- /dev/null
+++ b/tests/test_files.py
@@ -0,0 +1,262 @@
+#!/usr/bin/env python3
+
+# (C) 2023 by Harald Welte <laforge@osmocom.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+import logging
+
+from pySim.utils import *
+from pySim.filesystem import *
+
+import pySim.iso7816_4
+import pySim.ts_102_221
+import pySim.ts_102_222
+import pySim.ts_31_102
+import pySim.ts_31_103
+import pySim.ts_51_011
+import pySim.sysmocom_sja2
+import pySim.gsm_r
+
+def get_qualified_name(c):
+    """return the qualified (by module) name of a class."""
+    return "%s.%s" % (c.__module__, c.__name__)
+
+class LinFixed_Test(unittest.TestCase):
+    classes = all_subclasses(LinFixedEF)
+
+    def test_decode_record(self):
+        """Test the decoder for a linear-fixed EF.  Requires the given LinFixedEF subclass
+        to have an '_test_decode' attribute, containing a list of tuples. Each tuple can
+        either be a
+            * 2-tuple (hexstring, decoded_dict) or a
+            * 3-tuple (hexstring, record_nr, decoded_dict)
+        """
+        for c in self.classes:
+            name = get_qualified_name(c)
+            if hasattr(c, '_test_decode'):
+                for t in c._test_decode:
+                    with self.subTest(name, test_decode=t):
+                        inst = c()
+                        if len(t) == 2:
+                            encoded = t[0]
+                            rec_num = 1
+                            decoded = t[1]
+                        else:
+                            encoded = t[0]
+                            rec_num = t[1]
+                            decoded = t[2]
+                        logging.debug("Testing decode of %s", name)
+                        re_dec = inst.decode_record_hex(encoded, rec_num)
+                        self.assertEqual(decoded, re_dec)
+
+    def test_encode_record(self):
+        """Test the encoder for a linear-fixed EF.  Requires the given LinFixedEF subclass
+        to have an '_test_encode' attribute, containing a list of tuples. Each tuple can
+        either be a
+            * 2-tuple (hexstring, decoded_dict) or a
+            * 3-tuple (hexstring, record_nr, decoded_dict)
+        """
+        for c in self.classes:
+            name = get_qualified_name(c)
+            if hasattr(c, '_test_encode'):
+                for t in c._test_encode:
+                    with self.subTest(name, test_encode=t):
+                        inst = c()
+                        if len(t) == 2:
+                            encoded = t[0]
+                            rec_num = 1
+                            decoded = t[1]
+                        else:
+                            encoded = t[0]
+                            rec_num = t[1]
+                            decoded = t[2]
+                        logging.debug("Testing encode of %s", name)
+                        re_enc = inst.encode_record_hex(decoded, rec_num)
+                        self.assertEqual(encoded, re_enc)
+
+    def test_de_encode_record(self):
+        """Test the decoder and encoder for a linear-fixed EF.  Performs first a decoder
+        test, and then re-encodes the decoded data, comparing the re-encoded data with the
+        initial input data.
+
+        Requires the given LinFixedEF subclass to have a '_test_de_encode' attribute,
+        containing a list of tuples. Each tuple can
+        either be a
+            * 2-tuple (hexstring, decoded_dict) or a
+            * 3-tuple (hexstring, record_nr, decoded_dict)
+        """
+        for c in self.classes:
+            name = get_qualified_name(c)
+            if hasattr(c, '_test_de_encode'):
+                for t in c._test_de_encode:
+                    with self.subTest(name, test_de_encode=t):
+                        inst = c()
+                        if len(t) == 2:
+                            encoded = t[0]
+                            rec_num = 1
+                            decoded = t[1]
+                        else:
+                            encoded = t[0]
+                            rec_num = t[1]
+                            decoded = t[2]
+                        logging.debug("Testing decode of %s", name)
+                        re_dec = inst.decode_record_hex(encoded, rec_num)
+                        self.assertEqual(decoded, re_dec)
+                        # re-encode the decoded data
+                        logging.debug("Testing re-encode of %s", name)
+                        re_enc = inst.encode_record_hex(re_dec, rec_num)
+                        self.assertEqual(encoded, re_enc)
+
+
+class TransRecEF_Test(unittest.TestCase):
+    classes = all_subclasses(TransRecEF)
+
+    def test_decode_record(self):
+        """Test the decoder for a transparent record-oriented EF.  Requires the given TransRecEF subclass
+        to have an '_test_decode' attribute, containing a list of tuples. Each tuple has to be a
+        2-tuple (hexstring, decoded_dict).
+        """
+        for c in self.classes:
+            name = get_qualified_name(c)
+            if hasattr(c, '_test_decode'):
+                for t in c._test_decode:
+                    with self.subTest(name, test_decode=t):
+                        inst = c()
+                        encoded = t[0]
+                        decoded = t[1]
+                        logging.debug("Testing decode of %s", name)
+                        re_dec = inst.decode_record_hex(encoded)
+                        self.assertEqual(decoded, re_dec)
+
+    def test_encode_record(self):
+        """Test the encoder for a transparent record-oriented EF.  Requires the given TransRecEF subclass
+        to have an '_test_encode' attribute, containing a list of tuples. Each tuple has to be a
+        2-tuple (hexstring, decoded_dict).
+        """
+        for c in self.classes:
+            name = get_qualified_name(c)
+            if hasattr(c, '_test_decode'):
+                for t in c._test_decode:
+                    with self.subTest(name, test_decode=t):
+                        inst = c()
+                        encoded = t[0]
+                        decoded = t[1]
+                        logging.debug("Testing decode of %s", name)
+                        re_dec = inst.decode_record_hex(encoded)
+                        self.assertEqual(decoded, re_dec)
+
+
+    def test_de_encode_record(self):
+        """Test the decoder and encoder for a transparent record-oriented EF.  Performs first a decoder
+        test, and then re-encodes the decoded data, comparing the re-encoded data with the
+        initial input data.
+
+        Requires the given TransRecEF subclass to have a '_test_de_encode' attribute,
+        containing a list of tuples. Each tuple has to be a 2-tuple (hexstring, decoded_dict).
+        """
+        for c in self.classes:
+            name = get_qualified_name(c)
+            if hasattr(c, '_test_de_encode'):
+                for t in c._test_de_encode:
+                    with self.subTest(name, test_de_encode=t):
+                        inst = c()
+                        encoded = t[0]
+                        decoded = t[1]
+                        logging.debug("Testing decode of %s", name)
+                        re_dec = inst.decode_record_hex(encoded)
+                        self.assertEqual(decoded, re_dec)
+                        # re-encode the decoded data
+                        logging.debug("Testing re-encode of %s", name)
+                        re_enc = inst.encode_record_hex(re_dec)
+                        self.assertEqual(encoded, re_enc)
+
+
+class TransparentEF_Test(unittest.TestCase):
+    @classmethod
+    def get_classes(cls):
+        """get list of TransparentEF sub-classes which are not a TransRecEF subclass."""
+        classes = all_subclasses(TransparentEF)
+        trans_rec_classes = all_subclasses(TransRecEF)
+        return filter(lambda c: c not in trans_rec_classes, classes)
+
+    @classmethod
+    def setUpClass(cls):
+        """set-up method called once for this class by unittest framework"""
+        cls.classes = cls.get_classes()
+
+    def test_decode_file(self):
+        """Test the decoder for a transparent EF.  Requires the given TransparentEF subclass
+        to have a '_test_decode' attribute, containing a list of tuples. Each tuple
+        is a 2-tuple (hexstring, decoded_dict).
+        """
+        for c in self.classes:
+            name = get_qualified_name(c)
+            if hasattr(c, '_test_decode'):
+                for t in c._test_decode:
+                    with self.subTest(name, test_decode=t):
+                        inst = c()
+                        encoded = t[0]
+                        decoded = t[1]
+                        logging.debug("Testing decode of %s", name)
+                        re_dec = inst.decode_hex(encoded)
+                        self.assertEqual(decoded, re_dec)
+
+    def test_encode_file(self):
+        """Test the encoder for a transparent EF.  Requires the given TransparentEF subclass
+        to have a '_test_encode' attribute, containing a list of tuples. Each tuple
+        is a 2-tuple (hexstring, decoded_dict).
+        """
+        for c in self.classes:
+            name = get_qualified_name(c)
+            if hasattr(c, '_test_encode'):
+                for t in c._test_encode:
+                    with self.subTest(name, test_encode=t):
+                        inst = c()
+                        encoded = t[0]
+                        decoded = t[1]
+                        logging.debug("Testing encode of %s", name)
+                        re_dec = inst.decode_hex(encoded)
+                        self.assertEqual(decoded, re_dec)
+
+    def test_de_encode_file(self):
+        """Test the decoder and encoder for a transparent EF.  Performs first a decoder
+        test, and then re-encodes the decoded data, comparing the re-encoded data with the
+        initial input data.
+
+        Requires the given TransparentEF subclass to have a '_test_de_encode' attribute,
+        containing a list of tuples. Each tuple is a 2-tuple (hexstring, decoded_dict).
+        """
+        for c in self.classes:
+            name = get_qualified_name(c)
+            if hasattr(c, '_test_de_encode'):
+                for t in c._test_de_encode:
+                    with self.subTest(name, test_de_encode=t):
+                        inst = c()
+                        encoded = t[0]
+                        decoded = t[1]
+                        logging.debug("Testing decode of %s", name)
+                        re_dec = inst.decode_hex(encoded)
+                        self.assertEqual(decoded, re_dec)
+                        logging.debug("Testing re-encode of %s", name)
+                        re_dec = inst.decode_hex(encoded)
+                        re_enc = inst.encode_hex(re_dec)
+                        self.assertEqual(encoded, re_enc)
+
+
+if __name__ == '__main__':
+    logger = logging.getLogger()
+    logger.setLevel(logging.DEBUG)
+    unittest.main()