1 from .core import Adapter, AdaptationError, Pass
2 from .lib import int_to_bin, bin_to_int, swap_bytes
3 from .lib import FlagsContainer, HexString
4 from .lib.py3compat import BytesIO, decodebytes
7 #===============================================================================
9 #===============================================================================
10 class BitIntegerError(AdaptationError):
12 class MappingError(AdaptationError):
14 class ConstError(AdaptationError):
16 class ValidationError(AdaptationError):
18 class PaddingError(AdaptationError):
21 #===============================================================================
23 #===============================================================================
24 class BitIntegerAdapter(Adapter):
26 Adapter for bit-integers (converts bitstrings to integers, and vice versa).
30 * subcon - the subcon to adapt
31 * width - the size of the subcon, in bits
32 * swapped - whether to swap byte order (little endian/big endian).
33 default is False (big endian)
34 * signed - whether the value is signed (two's complement). the default
36 * bytesize - number of bits per byte, used for byte-swapping (if swapped).
39 __slots__ = ["width", "swapped", "signed", "bytesize"]
40 def __init__(self, subcon, width, swapped = False, signed = False,
42 Adapter.__init__(self, subcon)
44 self.swapped = swapped
46 self.bytesize = bytesize
47 def _encode(self, obj, context):
48 if obj < 0 and not self.signed:
49 raise BitIntegerError("object is negative, but field is not signed",
51 obj2 = int_to_bin(obj, width = self.width)
53 obj2 = swap_bytes(obj2, bytesize = self.bytesize)
55 def _decode(self, obj, context):
57 obj = swap_bytes(obj, bytesize = self.bytesize)
58 return bin_to_int(obj, signed = self.signed)
60 class MappingAdapter(Adapter):
62 Adapter that maps objects to other objects.
63 See SymmetricMapping and Enum.
66 * subcon - the subcon to map
67 * decoding - the decoding (parsing) mapping (a dict)
68 * encoding - the encoding (building) mapping (a dict)
69 * decdefault - the default return value when the object is not found
70 in the decoding mapping. if no object is given, an exception is raised.
71 if `Pass` is used, the unmapped object will be passed as-is
72 * encdefault - the default return value when the object is not found
73 in the encoding mapping. if no object is given, an exception is raised.
74 if `Pass` is used, the unmapped object will be passed as-is
76 __slots__ = ["encoding", "decoding", "encdefault", "decdefault"]
77 def __init__(self, subcon, decoding, encoding,
78 decdefault = NotImplemented, encdefault = NotImplemented):
79 Adapter.__init__(self, subcon)
80 self.decoding = decoding
81 self.encoding = encoding
82 self.decdefault = decdefault
83 self.encdefault = encdefault
84 def _encode(self, obj, context):
86 return self.encoding[obj]
87 except (KeyError, TypeError):
88 if self.encdefault is NotImplemented:
89 raise MappingError("no encoding mapping for %r [%s]" % (
90 obj, self.subcon.name))
91 if self.encdefault is Pass:
93 return self.encdefault
94 def _decode(self, obj, context):
96 return self.decoding[obj]
97 except (KeyError, TypeError):
98 if self.decdefault is NotImplemented:
99 raise MappingError("no decoding mapping for %r [%s]" % (
100 obj, self.subcon.name))
101 if self.decdefault is Pass:
103 return self.decdefault
105 class FlagsAdapter(Adapter):
107 Adapter for flag fields. Each flag is extracted from the number, resulting
108 in a FlagsContainer object. Not intended for direct usage.
112 * subcon - the subcon to extract
113 * flags - a dictionary mapping flag-names to their value
115 __slots__ = ["flags"]
116 def __init__(self, subcon, flags):
117 Adapter.__init__(self, subcon)
119 def _encode(self, obj, context):
121 for name, value in self.flags.items():
122 if getattr(obj, name, False):
125 def _decode(self, obj, context):
126 obj2 = FlagsContainer()
127 for name, value in self.flags.items():
128 setattr(obj2, name, bool(obj & value))
131 class StringAdapter(Adapter):
133 Adapter for strings. Converts a sequence of characters into a python
134 string, and optionally handles character encoding.
138 * subcon - the subcon to convert
139 * encoding - the character encoding name (e.g., "utf8"), or None to
140 return raw bytes (usually 8-bit ASCII).
142 __slots__ = ["encoding"]
143 def __init__(self, subcon, encoding = None):
144 Adapter.__init__(self, subcon)
145 self.encoding = encoding
146 def _encode(self, obj, context):
148 obj = obj.encode(self.encoding)
150 def _decode(self, obj, context):
152 obj = obj.decode(self.encoding)
155 class PaddedStringAdapter(Adapter):
157 Adapter for padded strings.
161 * subcon - the subcon to adapt
162 * padchar - the padding character. default is "\x00".
163 * paddir - the direction where padding is placed ("right", "left", or
164 "center"). the default is "right".
165 * trimdir - the direction where trimming will take place ("right" or
166 "left"). the default is "right". trimming is only meaningful for
167 building, when the given string is too long.
169 __slots__ = ["padchar", "paddir", "trimdir"]
170 def __init__(self, subcon, padchar = "\x00", paddir = "right",
172 if paddir not in ("right", "left", "center"):
173 raise ValueError("paddir must be 'right', 'left' or 'center'",
175 if trimdir not in ("right", "left"):
176 raise ValueError("trimdir must be 'right' or 'left'", trimdir)
177 Adapter.__init__(self, subcon)
178 self.padchar = padchar
180 self.trimdir = trimdir
181 def _decode(self, obj, context):
182 if self.paddir == "right":
183 obj = obj.rstrip(self.padchar)
184 elif self.paddir == "left":
185 obj = obj.lstrip(self.padchar)
187 obj = obj.strip(self.padchar)
189 def _encode(self, obj, context):
190 size = self._sizeof(context)
191 if self.paddir == "right":
192 obj = obj.ljust(size, self.padchar)
193 elif self.paddir == "left":
194 obj = obj.rjust(size, self.padchar)
196 obj = obj.center(size, self.padchar)
198 if self.trimdir == "right":
204 class LengthValueAdapter(Adapter):
206 Adapter for length-value pairs. It extracts only the value from the
207 pair, and calculates the length based on the value.
208 See PrefixedArray and PascalString.
211 * subcon - the subcon returning a length-value pair
214 def _encode(self, obj, context):
215 return (len(obj), obj)
216 def _decode(self, obj, context):
219 class CStringAdapter(StringAdapter):
221 Adapter for C-style strings (strings terminated by a terminator char).
224 * subcon - the subcon to convert
225 * terminators - a sequence of terminator chars. default is "\x00".
226 * encoding - the character encoding to use (e.g., "utf8"), or None to
227 return raw-bytes. the terminator characters are not affected by the
230 __slots__ = ["terminators"]
231 def __init__(self, subcon, terminators = b"\x00", encoding = None):
232 StringAdapter.__init__(self, subcon, encoding = encoding)
233 self.terminators = terminators
234 def _encode(self, obj, context):
235 return StringAdapter._encode(self, obj, context) + self.terminators[0:1]
236 def _decode(self, obj, context):
237 return StringAdapter._decode(self, b''.join(obj[:-1]), context)
239 class TunnelAdapter(Adapter):
241 Adapter for tunneling (as in protocol tunneling). A tunnel is construct
242 nested upon another (layering). For parsing, the lower layer first parses
243 the data (note: it must return a string!), then the upper layer is called
244 to parse that data (bottom-up). For building it works in a top-down manner;
245 first the upper layer builds the data, then the lower layer takes it and
246 writes it to the stream.
249 * subcon - the lower layer subcon
250 * inner_subcon - the upper layer (tunneled/nested) subcon
253 # a pascal string containing compressed data (zlib encoding), so first
254 # the string is read, decompressed, and finally re-parsed as an array
257 PascalString("data", encoding = "zlib"),
258 GreedyRange(UBInt16("elements"))
261 __slots__ = ["inner_subcon"]
262 def __init__(self, subcon, inner_subcon):
263 Adapter.__init__(self, subcon)
264 self.inner_subcon = inner_subcon
265 def _decode(self, obj, context):
266 return self.inner_subcon._parse(BytesIO(obj), context)
267 def _encode(self, obj, context):
269 self.inner_subcon._build(obj, stream, context)
270 return stream.getvalue()
272 class ExprAdapter(Adapter):
274 A generic adapter that accepts 'encoder' and 'decoder' as parameters. You
275 can use ExprAdapter instead of writing a full-blown class when only a
276 simple expression is needed.
279 * subcon - the subcon to adapt
280 * encoder - a function that takes (obj, context) and returns an encoded
282 * decoder - a function that takes (obj, context) and returns an decoded
286 ExprAdapter(UBInt8("foo"),
287 encoder = lambda obj, ctx: obj / 4,
288 decoder = lambda obj, ctx: obj * 4,
291 __slots__ = ["_encode", "_decode"]
292 def __init__(self, subcon, encoder, decoder):
293 Adapter.__init__(self, subcon)
294 self._encode = encoder
295 self._decode = decoder
297 class HexDumpAdapter(Adapter):
299 Adapter for hex-dumping strings. It returns a HexString, which is a string
301 __slots__ = ["linesize"]
302 def __init__(self, subcon, linesize = 16):
303 Adapter.__init__(self, subcon)
304 self.linesize = linesize
305 def _encode(self, obj, context):
307 def _decode(self, obj, context):
308 return HexString(obj, linesize = self.linesize)
310 class ConstAdapter(Adapter):
312 Adapter for enforcing a constant value ("magic numbers"). When decoding,
313 the return value is checked; when building, the value is substituted in.
316 * subcon - the subcon to validate
317 * value - the expected value
320 Const(Field("signature", 2), "MZ")
322 __slots__ = ["value"]
323 def __init__(self, subcon, value):
324 Adapter.__init__(self, subcon)
326 def _encode(self, obj, context):
327 if obj is None or obj == self.value:
330 raise ConstError("expected %r, found %r" % (self.value, obj))
331 def _decode(self, obj, context):
332 if obj != self.value:
333 raise ConstError("expected %r, found %r" % (self.value, obj))
336 class SlicingAdapter(Adapter):
338 Adapter for slicing a list (getting a slice from that list)
341 * subcon - the subcon to slice
342 * start - start index
343 * stop - stop index (or None for up-to-end)
344 * step - step (or None for every element)
346 __slots__ = ["start", "stop", "step"]
347 def __init__(self, subcon, start, stop = None):
348 Adapter.__init__(self, subcon)
351 def _encode(self, obj, context):
352 if self.start is None:
354 return [None] * self.start + obj
355 def _decode(self, obj, context):
356 return obj[self.start:self.stop]
358 class IndexingAdapter(Adapter):
360 Adapter for indexing a list (getting a single item from that list)
363 * subcon - the subcon to index
364 * index - the index of the list to get
366 __slots__ = ["index"]
367 def __init__(self, subcon, index):
368 Adapter.__init__(self, subcon)
369 if type(index) is not int:
370 raise TypeError("index must be an integer", type(index))
372 def _encode(self, obj, context):
373 return [None] * self.index + [obj]
374 def _decode(self, obj, context):
375 return obj[self.index]
377 class PaddingAdapter(Adapter):
382 * subcon - the subcon to pad
383 * pattern - the padding pattern (character). default is "\x00"
384 * strict - whether or not to verify, during parsing, that the given
385 padding matches the padding pattern. default is False (unstrict)
387 __slots__ = ["pattern", "strict"]
388 def __init__(self, subcon, pattern = "\x00", strict = False):
389 Adapter.__init__(self, subcon)
390 self.pattern = pattern
392 def _encode(self, obj, context):
393 return self._sizeof(context) * self.pattern
394 def _decode(self, obj, context):
396 expected = self._sizeof(context) * self.pattern
398 raise PaddingError("expected %r, found %r" % (expected, obj))
402 #===============================================================================
404 #===============================================================================
405 class Validator(Adapter):
407 Abstract class: validates a condition on the encoded/decoded object.
408 Override _validate(obj, context) in deriving classes.
411 * subcon - the subcon to validate
414 def _decode(self, obj, context):
415 if not self._validate(obj, context):
416 raise ValidationError("invalid object", obj)
418 def _encode(self, obj, context):
419 return self._decode(obj, context)
420 def _validate(self, obj, context):
421 raise NotImplementedError()
423 class OneOf(Validator):
425 Validates that the object is one of the listed values.
427 :param ``Construct`` subcon: object to validate
428 :param iterable valids: a set of valid values
430 >>> OneOf(UBInt8("foo"), [4,5,6,7]).parse("\\x05")
432 >>> OneOf(UBInt8("foo"), [4,5,6,7]).parse("\\x08")
433 Traceback (most recent call last):
435 construct.core.ValidationError: ('invalid object', 8)
437 >>> OneOf(UBInt8("foo"), [4,5,6,7]).build(5)
439 >>> OneOf(UBInt8("foo"), [4,5,6,7]).build(9)
440 Traceback (most recent call last):
442 construct.core.ValidationError: ('invalid object', 9)
444 __slots__ = ["valids"]
445 def __init__(self, subcon, valids):
446 Validator.__init__(self, subcon)
448 def _validate(self, obj, context):
449 return obj in self.valids
451 class NoneOf(Validator):
453 Validates that the object is none of the listed values.
455 :param ``Construct`` subcon: object to validate
456 :param iterable invalids: a set of invalid values
458 >>> NoneOf(UBInt8("foo"), [4,5,6,7]).parse("\\x08")
460 >>> NoneOf(UBInt8("foo"), [4,5,6,7]).parse("\\x06")
461 Traceback (most recent call last):
463 construct.core.ValidationError: ('invalid object', 6)
465 __slots__ = ["invalids"]
466 def __init__(self, subcon, invalids):
467 Validator.__init__(self, subcon)
468 self.invalids = invalids
469 def _validate(self, obj, context):
470 return obj not in self.invalids