blob: 2a3efd3de57eb40c8a1db24e3711978d4b573dad [file] [log] [blame]
Harald Welte07c7b1f2021-05-28 22:01:29 +02001import typing
Harald Weltee0f9ef12021-04-10 17:22:35 +02002from construct import *
Harald Welte2db5cfb2021-04-10 19:05:37 +02003from pySim.utils import b2h, h2b, swap_nibbles
Robert Falkenbergb07a3e92021-05-07 15:23:20 +02004import gsm0338
Harald Weltee0f9ef12021-04-10 17:22:35 +02005
6"""Utility code related to the integration of the 'construct' declarative parser."""
7
8# (C) 2021 by Harald Welte <laforge@osmocom.org>
9#
10# This program is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program. If not, see <http://www.gnu.org/licenses/>.
22
23
24class HexAdapter(Adapter):
25 """convert a bytes() type to a string of hex nibbles."""
26 def _decode(self, obj, context, path):
27 return b2h(obj)
28 def _encode(self, obj, context, path):
29 return h2b(obj)
30
Harald Welte2db5cfb2021-04-10 19:05:37 +020031class BcdAdapter(Adapter):
32 """convert a bytes() type to a string of BCD nibbles."""
33 def _decode(self, obj, context, path):
34 return swap_nibbles(b2h(obj))
35 def _encode(self, obj, context, path):
36 return h2b(swap_nibbles(obj))
37
Robert Falkenbergb07a3e92021-05-07 15:23:20 +020038class Rpad(Adapter):
39 """
40 Encoder appends padding bytes (b'\\xff') up to target size.
41 Decoder removes trailing padding bytes.
42
43 Parameters:
44 subcon: Subconstruct as defined by construct library
45 pattern: set padding pattern (default: b'\\xff')
46 """
47
48 def __init__(self, subcon, pattern=b'\xff'):
49 super().__init__(subcon)
50 self.pattern = pattern
51
52 def _decode(self, obj, context, path):
53 return obj.rstrip(self.pattern)
54
55 def _encode(self, obj, context, path):
56 if len(obj) > self.sizeof():
57 raise SizeofError("Input ({}) exceeds target size ({})".format(len(obj), self.sizeof()))
58 return obj + self.pattern * (self.sizeof() - len(obj))
59
60class GsmStringAdapter(Adapter):
61 """Convert GSM 03.38 encoded bytes to a string."""
62
63 def __init__(self, subcon, codec='gsm03.38', err='strict'):
64 super().__init__(subcon)
65 self.codec = codec
66 self.err = err
67
68 def _decode(self, obj, context, path):
69 return obj.decode(self.codec)
70
71 def _encode(self, obj, context, path):
72 return obj.encode(self.codec, self.err)
73
Harald Weltee0f9ef12021-04-10 17:22:35 +020074def filter_dict(d, exclude_prefix='_'):
75 """filter the input dict to ensure no keys starting with 'exclude_prefix' remain."""
Harald Welte7fca85b2021-05-29 21:27:46 +020076 if not isinstance(d, dict):
77 return d
Harald Weltee0f9ef12021-04-10 17:22:35 +020078 res = {}
79 for (key, value) in d.items():
80 if key.startswith(exclude_prefix):
81 continue
82 if type(value) is dict:
83 res[key] = filter_dict(value)
84 else:
85 res[key] = value
86 return res
87
Harald Welte07c7b1f2021-05-28 22:01:29 +020088from construct.lib.containers import Container, ListContainer
89from construct.core import EnumIntegerString
90
91def normalize_construct(c):
92 """Convert a construct specific type to a related base type, mostly useful
93 so we can serialize it."""
94 # we need to include the filter_dict as we otherwise get elements like this
95 # in the dict: '_io': <_io.BytesIO object at 0x7fdb64e05860> which we cannot json-serialize
96 c = filter_dict(c)
97 if isinstance(c, Container) or isinstance(c, dict):
98 r = {k : normalize_construct(v) for (k, v) in c.items()}
99 elif isinstance(c, ListContainer):
100 r = [normalize_construct(x) for x in c]
101 elif isinstance(c, list):
102 r = [normalize_construct(x) for x in c]
103 elif isinstance(c, EnumIntegerString):
104 r = str(c)
105 else:
106 r = c
107 return r
108
109def parse_construct(c, raw_bin_data:bytes, length:typing.Optional[int]=None, exclude_prefix:str='_'):
110 """Helper function to wrap around normalize_construct() and filter_dict()."""
111 if not length:
112 length = len(raw_bin_data)
113 parsed = c.parse(raw_bin_data, total_len=length)
114 return normalize_construct(parsed)
115
Harald Weltee0f9ef12021-04-10 17:22:35 +0200116# here we collect some shared / common definitions of data types
117LV = Prefixed(Int8ub, HexAdapter(GreedyBytes))
Robert Falkenberg9d16fbc2021-04-12 11:43:22 +0200118
119# Default value for Reserved for Future Use (RFU) bits/bytes
120# See TS 31.101 Sec. "3.4 Coding Conventions"
121__RFU_VALUE = 0
122
123# Field that packs Reserved for Future Use (RFU) bit
124FlagRFU = Default(Flag, __RFU_VALUE)
125
126# Field that packs Reserved for Future Use (RFU) byte
127ByteRFU = Default(Byte, __RFU_VALUE)
128
129# Field that packs all remaining Reserved for Future Use (RFU) bytes
130GreedyBytesRFU = Default(GreedyBytes, b'')
131
132def BitsRFU(n=1):
133 '''
134 Field that packs Reserved for Future Use (RFU) bit(s)
135 as defined in TS 31.101 Sec. "3.4 Coding Conventions"
136
137 Use this for (currently) unused/reserved bits whose contents
138 should be initialized automatically but should not be cleared
139 in the future or when restoring read data (unlike padding).
140
141 Parameters:
142 n (Integer): Number of bits (default: 1)
143 '''
144 return Default(BitsInteger(n), __RFU_VALUE)
145
146def BytesRFU(n=1):
147 '''
148 Field that packs Reserved for Future Use (RFU) byte(s)
149 as defined in TS 31.101 Sec. "3.4 Coding Conventions"
150
151 Use this for (currently) unused/reserved bytes whose contents
152 should be initialized automatically but should not be cleared
153 in the future or when restoring read data (unlike padding).
154
155 Parameters:
156 n (Integer): Number of bytes (default: 1)
157 '''
158 return Default(Bytes(n), __RFU_VALUE)
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200159
160def GsmString(n):
161 '''
162 GSM 03.38 encoded byte string of fixed length n.
163 Encoder appends padding bytes (b'\\xff') to maintain
164 length. Decoder removes those trailing bytes.
165
166 Exceptions are raised for invalid characters
167 and length excess.
168
169 Parameters:
170 n (Integer): Fixed length of the encoded byte string
171 '''
172 return GsmStringAdapter(Rpad(Bytes(n), pattern=b'\xff'), codec='gsm03.38')