blob: ab44a6397d2fb65601aacb39e8d939635f0fe89c [file] [log] [blame]
Harald Weltec91085e2022-02-10 18:05:45 +01001from construct.lib.containers import Container, ListContainer
2from construct.core import EnumIntegerString
Harald Welte07c7b1f2021-05-28 22:01:29 +02003import typing
Harald Weltee0f9ef12021-04-10 17:22:35 +02004from construct import *
Vadim Yanitskiy05d30eb2022-08-29 20:24:44 +07005from construct.core import evaluate, BitwisableString
Harald Welted0519e02022-02-11 18:05:48 +01006from construct.lib import integertypes
Harald Welte2db5cfb2021-04-10 19:05:37 +02007from pySim.utils import b2h, h2b, swap_nibbles
Robert Falkenbergb07a3e92021-05-07 15:23:20 +02008import gsm0338
Harald Weltee0f9ef12021-04-10 17:22:35 +02009
10"""Utility code related to the integration of the 'construct' declarative parser."""
11
Harald Welted0519e02022-02-11 18:05:48 +010012# (C) 2021-2022 by Harald Welte <laforge@osmocom.org>
Harald Weltee0f9ef12021-04-10 17:22:35 +020013#
14# This program is free software: you can redistribute it and/or modify
15# it under the terms of the GNU General Public License as published by
16# the Free Software Foundation, either version 2 of the License, or
17# (at your option) any later version.
18#
19# This program is distributed in the hope that it will be useful,
20# but WITHOUT ANY WARRANTY; without even the implied warranty of
21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22# GNU General Public License for more details.
23#
24# You should have received a copy of the GNU General Public License
25# along with this program. If not, see <http://www.gnu.org/licenses/>.
26
27
28class HexAdapter(Adapter):
29 """convert a bytes() type to a string of hex nibbles."""
Harald Weltec91085e2022-02-10 18:05:45 +010030
Harald Weltee0f9ef12021-04-10 17:22:35 +020031 def _decode(self, obj, context, path):
32 return b2h(obj)
Harald Weltec91085e2022-02-10 18:05:45 +010033
Harald Weltee0f9ef12021-04-10 17:22:35 +020034 def _encode(self, obj, context, path):
35 return h2b(obj)
36
Harald Weltec91085e2022-02-10 18:05:45 +010037
Harald Welte2db5cfb2021-04-10 19:05:37 +020038class BcdAdapter(Adapter):
39 """convert a bytes() type to a string of BCD nibbles."""
Harald Weltec91085e2022-02-10 18:05:45 +010040
Harald Welte2db5cfb2021-04-10 19:05:37 +020041 def _decode(self, obj, context, path):
42 return swap_nibbles(b2h(obj))
Harald Weltec91085e2022-02-10 18:05:45 +010043
Harald Welte2db5cfb2021-04-10 19:05:37 +020044 def _encode(self, obj, context, path):
45 return h2b(swap_nibbles(obj))
46
Harald Weltebc0e2092022-02-13 10:54:58 +010047class InvertAdapter(Adapter):
48 """inverse logic (false->true, true->false)."""
49 @staticmethod
50 def _invert_bool_in_obj(obj):
51 for k,v in obj.items():
52 # skip all private entries
53 if k.startswith('_'):
54 continue
55 if v == False:
56 obj[k] = True
57 elif v == True:
58 obj[k] = False
59 return obj
60
61 def _decode(self, obj, context, path):
62 return self._invert_bool_in_obj(obj)
63
64 def _encode(self, obj, context, path):
65 return self._invert_bool_in_obj(obj)
Harald Weltec91085e2022-02-10 18:05:45 +010066
Robert Falkenbergb07a3e92021-05-07 15:23:20 +020067class Rpad(Adapter):
68 """
Harald Weltef9a5ba52023-06-09 09:17:05 +020069 Encoder appends padding bytes (b'\\xff') or characters up to target size.
70 Decoder removes trailing padding bytes/characters.
Robert Falkenbergb07a3e92021-05-07 15:23:20 +020071
72 Parameters:
73 subcon: Subconstruct as defined by construct library
74 pattern: set padding pattern (default: b'\\xff')
Harald Weltef9a5ba52023-06-09 09:17:05 +020075 num_per_byte: number of 'elements' per byte. E.g. for hex nibbles: 2
Robert Falkenbergb07a3e92021-05-07 15:23:20 +020076 """
77
Harald Weltef9a5ba52023-06-09 09:17:05 +020078 def __init__(self, subcon, pattern=b'\xff', num_per_byte=1):
Robert Falkenbergb07a3e92021-05-07 15:23:20 +020079 super().__init__(subcon)
80 self.pattern = pattern
Harald Weltef9a5ba52023-06-09 09:17:05 +020081 self.num_per_byte = num_per_byte
Robert Falkenbergb07a3e92021-05-07 15:23:20 +020082
83 def _decode(self, obj, context, path):
84 return obj.rstrip(self.pattern)
85
86 def _encode(self, obj, context, path):
Harald Weltef9a5ba52023-06-09 09:17:05 +020087 target_size = self.sizeof() * self.num_per_byte
88 if len(obj) > target_size:
Harald Weltec91085e2022-02-10 18:05:45 +010089 raise SizeofError("Input ({}) exceeds target size ({})".format(
Harald Weltef9a5ba52023-06-09 09:17:05 +020090 len(obj), target_size))
91 return obj + self.pattern * (target_size - len(obj))
Robert Falkenbergb07a3e92021-05-07 15:23:20 +020092
Harald Welte954ce952023-05-27 20:08:09 +020093class MultiplyAdapter(Adapter):
94 """
95 Decoder multiplies by multiplicator
96 Encoder divides by multiplicator
97
98 Parameters:
99 subcon: Subconstruct as defined by construct library
100 multiplier: Multiplier to apply to raw encoded value
101 """
102
103 def __init__(self, subcon, multiplicator):
104 super().__init__(subcon)
105 self.multiplicator = multiplicator
106
107 def _decode(self, obj, context, path):
108 return obj * 8
109
110 def _encode(self, obj, context, path):
111 return obj // 8
112
Harald Weltec91085e2022-02-10 18:05:45 +0100113
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200114class GsmStringAdapter(Adapter):
115 """Convert GSM 03.38 encoded bytes to a string."""
116
117 def __init__(self, subcon, codec='gsm03.38', err='strict'):
118 super().__init__(subcon)
119 self.codec = codec
120 self.err = err
121
122 def _decode(self, obj, context, path):
123 return obj.decode(self.codec)
124
125 def _encode(self, obj, context, path):
126 return obj.encode(self.codec, self.err)
127
Harald Weltec91085e2022-02-10 18:05:45 +0100128
Harald Weltee0f9ef12021-04-10 17:22:35 +0200129def filter_dict(d, exclude_prefix='_'):
130 """filter the input dict to ensure no keys starting with 'exclude_prefix' remain."""
Harald Welte7fca85b2021-05-29 21:27:46 +0200131 if not isinstance(d, dict):
132 return d
Harald Weltee0f9ef12021-04-10 17:22:35 +0200133 res = {}
134 for (key, value) in d.items():
135 if key.startswith(exclude_prefix):
136 continue
137 if type(value) is dict:
138 res[key] = filter_dict(value)
139 else:
140 res[key] = value
141 return res
142
Harald Welte07c7b1f2021-05-28 22:01:29 +0200143
144def normalize_construct(c):
145 """Convert a construct specific type to a related base type, mostly useful
146 so we can serialize it."""
147 # we need to include the filter_dict as we otherwise get elements like this
148 # in the dict: '_io': <_io.BytesIO object at 0x7fdb64e05860> which we cannot json-serialize
149 c = filter_dict(c)
150 if isinstance(c, Container) or isinstance(c, dict):
Harald Weltec91085e2022-02-10 18:05:45 +0100151 r = {k: normalize_construct(v) for (k, v) in c.items()}
Harald Welte07c7b1f2021-05-28 22:01:29 +0200152 elif isinstance(c, ListContainer):
153 r = [normalize_construct(x) for x in c]
154 elif isinstance(c, list):
155 r = [normalize_construct(x) for x in c]
156 elif isinstance(c, EnumIntegerString):
157 r = str(c)
158 else:
159 r = c
160 return r
161
Harald Weltec91085e2022-02-10 18:05:45 +0100162
163def parse_construct(c, raw_bin_data: bytes, length: typing.Optional[int] = None, exclude_prefix: str = '_'):
Harald Welte07c7b1f2021-05-28 22:01:29 +0200164 """Helper function to wrap around normalize_construct() and filter_dict()."""
165 if not length:
166 length = len(raw_bin_data)
167 parsed = c.parse(raw_bin_data, total_len=length)
168 return normalize_construct(parsed)
169
Harald Weltec91085e2022-02-10 18:05:45 +0100170
Harald Weltee0f9ef12021-04-10 17:22:35 +0200171# here we collect some shared / common definitions of data types
172LV = Prefixed(Int8ub, HexAdapter(GreedyBytes))
Robert Falkenberg9d16fbc2021-04-12 11:43:22 +0200173
174# Default value for Reserved for Future Use (RFU) bits/bytes
175# See TS 31.101 Sec. "3.4 Coding Conventions"
176__RFU_VALUE = 0
177
178# Field that packs Reserved for Future Use (RFU) bit
179FlagRFU = Default(Flag, __RFU_VALUE)
180
181# Field that packs Reserved for Future Use (RFU) byte
182ByteRFU = Default(Byte, __RFU_VALUE)
183
184# Field that packs all remaining Reserved for Future Use (RFU) bytes
185GreedyBytesRFU = Default(GreedyBytes, b'')
186
Harald Weltec91085e2022-02-10 18:05:45 +0100187
Robert Falkenberg9d16fbc2021-04-12 11:43:22 +0200188def BitsRFU(n=1):
189 '''
190 Field that packs Reserved for Future Use (RFU) bit(s)
191 as defined in TS 31.101 Sec. "3.4 Coding Conventions"
192
193 Use this for (currently) unused/reserved bits whose contents
194 should be initialized automatically but should not be cleared
195 in the future or when restoring read data (unlike padding).
196
197 Parameters:
198 n (Integer): Number of bits (default: 1)
199 '''
200 return Default(BitsInteger(n), __RFU_VALUE)
201
Harald Weltec91085e2022-02-10 18:05:45 +0100202
Robert Falkenberg9d16fbc2021-04-12 11:43:22 +0200203def BytesRFU(n=1):
204 '''
205 Field that packs Reserved for Future Use (RFU) byte(s)
206 as defined in TS 31.101 Sec. "3.4 Coding Conventions"
207
208 Use this for (currently) unused/reserved bytes whose contents
209 should be initialized automatically but should not be cleared
210 in the future or when restoring read data (unlike padding).
211
212 Parameters:
213 n (Integer): Number of bytes (default: 1)
214 '''
215 return Default(Bytes(n), __RFU_VALUE)
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200216
Harald Weltec91085e2022-02-10 18:05:45 +0100217
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200218def GsmString(n):
219 '''
220 GSM 03.38 encoded byte string of fixed length n.
221 Encoder appends padding bytes (b'\\xff') to maintain
222 length. Decoder removes those trailing bytes.
223
224 Exceptions are raised for invalid characters
225 and length excess.
226
227 Parameters:
228 n (Integer): Fixed length of the encoded byte string
229 '''
230 return GsmStringAdapter(Rpad(Bytes(n), pattern=b'\xff'), codec='gsm03.38')
Harald Welted0519e02022-02-11 18:05:48 +0100231
232class GreedyInteger(Construct):
233 """A variable-length integer implementation, think of combining GrredyBytes with BytesInteger."""
Philipp Maier541a9152022-06-01 18:21:17 +0200234 def __init__(self, signed=False, swapped=False, minlen=0):
Harald Welted0519e02022-02-11 18:05:48 +0100235 super().__init__()
236 self.signed = signed
237 self.swapped = swapped
Philipp Maier541a9152022-06-01 18:21:17 +0200238 self.minlen = minlen
Harald Welted0519e02022-02-11 18:05:48 +0100239
240 def _parse(self, stream, context, path):
241 data = stream_read_entire(stream, path)
242 if evaluate(self.swapped, context):
243 data = swapbytes(data)
244 try:
Vadim Yanitskiy05d30eb2022-08-29 20:24:44 +0700245 return int.from_bytes(data, byteorder='big', signed=self.signed)
Harald Welted0519e02022-02-11 18:05:48 +0100246 except ValueError as e:
247 raise IntegerError(str(e), path=path)
248
Philipp Maier541a9152022-06-01 18:21:17 +0200249 def __bytes_required(self, i, minlen=0):
Harald Welted0519e02022-02-11 18:05:48 +0100250 if self.signed:
251 raise NotImplementedError("FIXME: Implement support for encoding signed integer")
Philipp Maier541a9152022-06-01 18:21:17 +0200252
253 # compute how many bytes we need
Harald Welted0519e02022-02-11 18:05:48 +0100254 nbytes = 1
255 while True:
256 i = i >> 8
257 if i == 0:
Philipp Maier541a9152022-06-01 18:21:17 +0200258 break
Harald Welted0519e02022-02-11 18:05:48 +0100259 else:
260 nbytes = nbytes + 1
Philipp Maier541a9152022-06-01 18:21:17 +0200261
262 # round up to the minimum number
263 # of bytes we anticipate
264 if nbytes < minlen:
265 nbytes = minlen
266
267 return nbytes
Harald Welted0519e02022-02-11 18:05:48 +0100268
269 def _build(self, obj, stream, context, path):
270 if not isinstance(obj, integertypes):
271 raise IntegerError(f"value {obj} is not an integer", path=path)
Philipp Maier541a9152022-06-01 18:21:17 +0200272 length = self.__bytes_required(obj, self.minlen)
Harald Welted0519e02022-02-11 18:05:48 +0100273 try:
Vadim Yanitskiy05d30eb2022-08-29 20:24:44 +0700274 data = obj.to_bytes(length, byteorder='big', signed=self.signed)
Harald Welted0519e02022-02-11 18:05:48 +0100275 except ValueError as e:
276 raise IntegerError(str(e), path=path)
277 if evaluate(self.swapped, context):
278 data = swapbytes(data)
279 stream_write(stream, data, length, path)
280 return obj
Harald Weltebc0e2092022-02-13 10:54:58 +0100281
282# merged definitions of 24.008 + 23.040
283TypeOfNumber = Enum(BitsInteger(3), unknown=0, international=1, national=2, network_specific=3,
284 short_code=4, alphanumeric=5, abbreviated=6, reserved_for_extension=7)
285NumberingPlan = Enum(BitsInteger(4), unknown=0, isdn_e164=1, data_x121=3, telex_f69=4,
286 sc_specific_5=5, sc_specific_6=6, national=8, private=9,
287 ermes=10, reserved_cts=11, reserved_for_extension=15)
288TonNpi = BitStruct('ext'/Flag, 'type_of_number'/TypeOfNumber, 'numbering_plan_id'/NumberingPlan)