add PlmnAdapter for decoding PLMN bcd-strings like 262f01 to 262-01

The human representation of a PLMN is usually MCC-MNC like 262-01
or 262-001.  Let's add a PlmnAdapter for use within construct, so we
can properly decode that.

Change-Id: I96f276e6dcdb54a5a3d2bcde5ee6dbaf981ed789
diff --git a/pySim/cat.py b/pySim/cat.py
index e98ce4a..5925bf3 100644
--- a/pySim/cat.py
+++ b/pySim/cat.py
@@ -22,7 +22,7 @@
 from typing import List
 from pySim.utils import b2h, h2b, dec_xplmn_w_act
 from pySim.tlv import TLV_IE, COMPR_TLV_IE, BER_TLV_IE, TLV_IE_Collection
-from pySim.construct import BcdAdapter, HexAdapter, GsmStringAdapter, TonNpi
+from pySim.construct import PlmnAdapter, BcdAdapter, HexAdapter, GsmStringAdapter, TonNpi
 from construct import Int8ub, Int16ub, Byte, Bytes, Bit, Flag, BitsInteger
 from construct import Struct, Enum, Tell, BitStruct, this, Padding, RepeatUntil
 from construct import GreedyBytes, Switch, GreedyRange, FlagsEnum
@@ -597,7 +597,7 @@
 
 # TS 31.111 Section 8.91
 class RoutingAreaIdentification(COMPR_TLV_IE, tag=0xF3):
-    _construct = Struct('mcc_mnc'/BcdAdapter(Bytes(3)),
+    _construct = Struct('mcc_mnc'/PlmnAdapter(Bytes(3)),
                         'lac'/HexAdapter(Bytes(2)),
                         'rac'/Int8ub)
 
@@ -645,7 +645,7 @@
 
 # TS 31.111 Section 8.97
 class PlmnList(COMPR_TLV_IE, tag=0xF9):
-    _construct = GreedyRange('mcc_mnc'/HexAdapter(Bytes(3)))
+    _construct = GreedyRange('mcc_mnc'/PlmnAdapter(Bytes(3)))
 
 # TS 102 223 Section 8.98
 class EcatSequenceNumber(COMPR_TLV_IE, tag=0xA1):
diff --git a/pySim/construct.py b/pySim/construct.py
index cef9557..f78adfe 100644
--- a/pySim/construct.py
+++ b/pySim/construct.py
@@ -58,6 +58,23 @@
     def _encode(self, obj, context, path):
         return h2b(swap_nibbles(obj))
 
+class PlmnAdapter(BcdAdapter):
+    """convert a bytes(3) type to BCD string like 262-02 or 262-002."""
+    def _decode(self, obj, context, path):
+        bcd = super()._decode(obj, context, path)
+        if bcd[3] == 'f':
+            return '-'.join([bcd[:3], bcd[4:]])
+        else:
+            return '-'.join([bcd[:3], bcd[3:]])
+
+    def _encode(self, obj, context, path):
+        l = obj.split('-')
+        if len(l[1]) == 2:
+            bcd = l[0] + 'f' + l[1]
+        else:
+            bcd = l[0] + l[1]
+        return super()._encode(bcd, context, path)
+
 class InvertAdapter(Adapter):
     """inverse logic (false->true, true->false)."""
     @staticmethod
diff --git a/pySim/gsm_r.py b/pySim/gsm_r.py
index cd111d6..db7819c 100644
--- a/pySim/gsm_r.py
+++ b/pySim/gsm_r.py
@@ -202,12 +202,12 @@
 class EF_GsmrPLMN(LinFixedEF):
     """Section 7.7"""
     _test_de_encode = [
-        ( "22f860f86f8d6f8e01", { "plmn": "228f06", "class_of_network": {
+        ( "22f860f86f8d6f8e01", { "plmn": "228-06", "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": {
+        ( "22f810416f8d6f8e02", { "plmn": "228-01", "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",
@@ -216,7 +216,7 @@
     def __init__(self):
         super().__init__(fid='6ff5', sfid=None, name='EF.GsmrPLMN',
                          desc='GSM-R network selection', rec_len=(9, 9))
-        self._construct = Struct('plmn'/BcdAdapter(Bytes(3)),
+        self._construct = Struct('plmn'/PlmnAdapter(Bytes(3)),
                                  'class_of_network'/BitStruct('supported'/FlagsEnum(BitsInteger(5), vbs=1, vgcs=2, emlpp=4, fn=8, eirene=16),
                                                               'preference'/BitsInteger(3)),
                                  'ic_incoming_ref_tbl'/HexAdapter(Bytes(2)),
diff --git a/pySim/ts_31_102.py b/pySim/ts_31_102.py
index b5faf08..16526c2 100644
--- a/pySim/ts_31_102.py
+++ b/pySim/ts_31_102.py
@@ -566,14 +566,14 @@
 class EF_LOCI(TransparentEF):
     _test_de_encode = [
         ( '47d1264a62f21037211e00',
-          { "tmsi": "47d1264a", "lai": { "mcc_mnc": "262f01", "lac": "3721" },
+          { "tmsi": "47d1264a", "lai": { "mcc_mnc": "262-01", "lac": "3721" },
             "rfu": 30, "lu_status": 0 } ),
         ( 'ffffffff62f2200000ff01',
-          {"tmsi": "ffffffff", "lai": {"mcc_mnc": "262f02", "lac": "0000"}, "rfu": 255, "lu_status": 1} ),
+          {"tmsi": "ffffffff", "lai": {"mcc_mnc": "262-02", "lac": "0000"}, "rfu": 255, "lu_status": 1} ),
     ]
     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)))
+        Lai = Struct('mcc_mnc'/PlmnAdapter(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
@@ -905,11 +905,11 @@
 # TS 31.102 Section 4.2.104
 class EF_ePDGSelection(TransparentEF):
     _test_de_encode = [
-        ( '80060001f1000100', {'e_pdg_selection': [{'plmn': '00101f', 'epdg_priority': 1, 'epdg_fqdn_format': 'operator_identified' }] }),
-        ( '800600011000a001', {'e_pdg_selection': [{'plmn': '001001', 'epdg_priority': 160, 'epdg_fqdn_format': 'location_based' }] }),
+        ( '800600f110000100', {'e_pdg_selection': [{'plmn': '001-01', 'epdg_priority': 1, 'epdg_fqdn_format': 'operator_identified' }] }),
+        ( '800600011000a001', {'e_pdg_selection': [{'plmn': '001-001', 'epdg_priority': 160, 'epdg_fqdn_format': 'location_based' }] }),
     ]
     class ePDGSelection(BER_TLV_IE, tag=0x80):
-        _construct = GreedyRange(Struct('plmn'/BcdAdapter(Bytes(3)),
+        _construct = GreedyRange(Struct('plmn'/PlmnAdapter(Bytes(3)),
                                         'epdg_priority'/Int16ub,
                                         'epdg_fqdn_format'/Enum(Int8ub, operator_identified=0, location_based=1)))
 
@@ -980,7 +980,7 @@
 class EF_OPL5G(LinFixedEF):
     def __init__(self, fid='4f08', sfid=0x08, name='EF.OPL5G', desc='5GS Operator PLMN List', **kwargs):
         super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=(10, None), **kwargs)
-        Tai = Struct('mcc_mnc'/BcdAdapter(Bytes(3)), 'tac_min'/HexAdapter(Bytes(3)),
+        Tai = Struct('mcc_mnc'/PlmnAdapter(Bytes(3)), 'tac_min'/HexAdapter(Bytes(3)),
                      'tac_max'/HexAdapter(Bytes(3)))
         self._construct = Struct('tai'/Tai, 'pnn_record_id'/Int8ub)
 
diff --git a/pySim/ts_51_011.py b/pySim/ts_51_011.py
index 0a803b9..422b35e 100644
--- a/pySim/ts_51_011.py
+++ b/pySim/ts_51_011.py
@@ -845,11 +845,11 @@
 class EF_OPL(LinFixedEF):
     _test_de_encode = [
         ( '62f2100000fffe01',
-          { "lai": { "mcc_mnc": "262f01", "lac_min": "0000", "lac_max": "fffe" }, "pnn_record_id": 1 } ),
+          { "lai": { "mcc_mnc": "262-01", "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)),
+        self._construct = Struct('lai'/Struct('mcc_mnc'/PlmnAdapter(Bytes(3)),
                                  'lac_min'/HexAdapter(Bytes(2)), 'lac_max'/HexAdapter(Bytes(2))), 'pnn_record_id'/Int8ub)
 
 # TS 51.011 Section 10.3.44 + TS 31.102 4.2.62
@@ -884,7 +884,7 @@
     # TODO: a305800337f800ffffffffffffffffffffffffffffffffffffffffffffff
     class ServiceProviderPLMN(BER_TLV_IE, tag=0x80):
         # flexible numbers of 3-byte PLMN records
-        _construct = GreedyRange(BcdAdapter(Bytes(3)))
+        _construct = GreedyRange(PlmnAdapter(Bytes(3)))
 
     class SPDI(BER_TLV_IE, tag=0xA3, nested=[ServiceProviderPLMN]):
         pass