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