|
4 | 4 | # |
5 | 5 | # MIT License |
6 | 6 | # |
7 | | -# Copyright (c) 2011-2022 Michael Truog <mjtruog at protonmail dot com> |
| 7 | +# Copyright (c) 2011-2023 Michael Truog <mjtruog at protonmail dot com> |
8 | 8 | # |
9 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a |
10 | 10 | # copy of this software and associated documentation files (the "Software"), |
|
27 | 27 | """ |
28 | 28 | Erlang External Term Format Encoding/Decoding |
29 | 29 | """ |
| 30 | +# pylint: disable=too-many-lines |
30 | 31 |
|
31 | 32 | import sys |
32 | 33 | import struct |
@@ -62,11 +63,11 @@ def b_ord(character): |
62 | 63 |
|
63 | 64 | __all__ = ['OtpErlangAtom', |
64 | 65 | 'OtpErlangBinary', |
65 | | - 'OtpErlangFunction', |
66 | 66 | 'OtpErlangList', |
67 | 67 | 'OtpErlangPid', |
68 | 68 | 'OtpErlangPort', |
69 | 69 | 'OtpErlangReference', |
| 70 | + 'OtpErlangFunction', |
70 | 71 | 'binary_to_term', |
71 | 72 | 'term_to_binary', |
72 | 73 | 'set_undefined', |
@@ -108,6 +109,8 @@ def b_ord(character): |
108 | 109 | _TAG_FUN_EXT = 117 |
109 | 110 | _TAG_ATOM_UTF8_EXT = 118 |
110 | 111 | _TAG_SMALL_ATOM_UTF8_EXT = 119 |
| 112 | +_TAG_V4_PORT_EXT = 120 |
| 113 | +_TAG_LOCAL_EXT = 121 |
111 | 114 |
|
112 | 115 | # Erlang term classes listed alphabetically |
113 | 116 |
|
@@ -197,30 +200,6 @@ def __hash__(self): |
197 | 200 | def __eq__(self, other): |
198 | 201 | return self.binary() == other.binary() |
199 | 202 |
|
200 | | -class OtpErlangFunction(object): |
201 | | - """ |
202 | | - OtpErlangFunction |
203 | | - """ |
204 | | - # pylint: disable=useless-object-inheritance |
205 | | - # pylint: disable=too-few-public-methods |
206 | | - def __init__(self, tag, value): |
207 | | - self.tag = tag |
208 | | - self.value = value |
209 | | - def binary(self): |
210 | | - """ |
211 | | - return encoded representation |
212 | | - """ |
213 | | - return b_chr(self.tag) + self.value |
214 | | - def __repr__(self): |
215 | | - return '%s(%s,%s)' % ( |
216 | | - self.__class__.__name__, |
217 | | - repr(self.tag), repr(self.value) |
218 | | - ) |
219 | | - def __hash__(self): |
220 | | - return hash(self.binary()) |
221 | | - def __eq__(self, other): |
222 | | - return self.binary() == other.binary() |
223 | | - |
224 | 203 | class OtpErlangList(object): |
225 | 204 | """ |
226 | 205 | OtpErlangList |
@@ -318,17 +297,23 @@ def binary(self): |
318 | 297 | """ |
319 | 298 | return encoded representation |
320 | 299 | """ |
321 | | - creation_size = len(self.creation) |
322 | | - if creation_size == 1: |
| 300 | + id_size = len(self.id) |
| 301 | + if id_size == 8: |
323 | 302 | return ( |
324 | | - b_chr(_TAG_PORT_EXT) + |
| 303 | + b_chr(_TAG_V4_PORT_EXT) + |
325 | 304 | self.node.binary() + self.id + self.creation |
326 | 305 | ) |
| 306 | + creation_size = len(self.creation) |
327 | 307 | if creation_size == 4: |
328 | 308 | return ( |
329 | 309 | b_chr(_TAG_NEW_PORT_EXT) + |
330 | 310 | self.node.binary() + self.id + self.creation |
331 | 311 | ) |
| 312 | + if creation_size == 1: |
| 313 | + return ( |
| 314 | + b_chr(_TAG_PORT_EXT) + |
| 315 | + self.node.binary() + self.id + self.creation |
| 316 | + ) |
332 | 317 | raise OutputException('unknown port type') |
333 | 318 | def __repr__(self): |
334 | 319 | return '%s(%s,%s,%s)' % ( |
@@ -387,6 +372,30 @@ def __hash__(self): |
387 | 372 | def __eq__(self, other): |
388 | 373 | return self.binary() == other.binary() |
389 | 374 |
|
| 375 | +class OtpErlangFunction(object): |
| 376 | + """ |
| 377 | + OtpErlangFunction |
| 378 | + """ |
| 379 | + # pylint: disable=useless-object-inheritance |
| 380 | + # pylint: disable=too-few-public-methods |
| 381 | + def __init__(self, tag, value): |
| 382 | + self.tag = tag |
| 383 | + self.value = value |
| 384 | + def binary(self): |
| 385 | + """ |
| 386 | + return encoded representation |
| 387 | + """ |
| 388 | + return b_chr(self.tag) + self.value |
| 389 | + def __repr__(self): |
| 390 | + return '%s(%s,%s)' % ( |
| 391 | + self.__class__.__name__, |
| 392 | + repr(self.tag), repr(self.value) |
| 393 | + ) |
| 394 | + def __hash__(self): |
| 395 | + return hash(self.binary()) |
| 396 | + def __eq__(self, other): |
| 397 | + return self.binary() == other.binary() |
| 398 | + |
390 | 399 | # dependency to support Erlang maps as map keys in python |
391 | 400 |
|
392 | 401 | class frozendict(dict): |
@@ -505,19 +514,25 @@ def _binary_to_term(i, data): |
505 | 514 | if tag == _TAG_FLOAT_EXT: |
506 | 515 | value = float(data[i:i + 31].partition(b_chr(0))[0]) |
507 | 516 | return (i + 31, value) |
508 | | - if tag in (_TAG_NEW_PORT_EXT, _TAG_REFERENCE_EXT, _TAG_PORT_EXT): |
| 517 | + if tag in (_TAG_V4_PORT_EXT, _TAG_NEW_PORT_EXT, |
| 518 | + _TAG_REFERENCE_EXT, _TAG_PORT_EXT): |
509 | 519 | i, node = _binary_to_atom(i, data) |
510 | | - id_value = data[i:i + 4] |
511 | | - i += 4 |
512 | | - if tag == _TAG_NEW_PORT_EXT: |
| 520 | + if tag == _TAG_V4_PORT_EXT: |
| 521 | + id_value = data[i:i + 8] |
| 522 | + i += 8 |
| 523 | + else: |
| 524 | + id_value = data[i:i + 4] |
| 525 | + i += 4 |
| 526 | + if tag in (_TAG_V4_PORT_EXT, _TAG_NEW_PORT_EXT): |
513 | 527 | creation = data[i:i + 4] |
514 | 528 | i += 4 |
515 | 529 | else: |
516 | 530 | creation = data[i:i + 1] |
517 | 531 | i += 1 |
518 | 532 | if tag == _TAG_REFERENCE_EXT: |
519 | 533 | return (i, OtpErlangReference(node, id_value, creation)) |
520 | | - # tag == _TAG_NEW_PORT_EXT or tag == _TAG_PORT_EXT |
| 534 | + # tag == _TAG_V4_PORT_EXT or tag == _TAG_NEW_PORT_EXT or |
| 535 | + # tag == _TAG_PORT_EXT |
521 | 536 | return (i, OtpErlangPort(node, id_value, creation)) |
522 | 537 | if tag in (_TAG_NEW_PID_EXT, _TAG_PID_EXT): |
523 | 538 | i, node = _binary_to_atom(i, data) |
@@ -607,9 +622,9 @@ def _binary_to_term(i, data): |
607 | 622 | def to_immutable(value): |
608 | 623 | if isinstance(value, dict): |
609 | 624 | return frozendict(key) |
610 | | - elif isinstance(value, list): |
| 625 | + if isinstance(value, list): |
611 | 626 | return OtpErlangList(value) |
612 | | - elif isinstance(value, tuple): |
| 627 | + if isinstance(value, tuple): |
613 | 628 | return tuple(to_immutable(v) for v in value) |
614 | 629 | return value |
615 | 630 |
|
@@ -674,6 +689,8 @@ def to_immutable(value): |
674 | 689 | if i_new != size_uncompressed: |
675 | 690 | raise ParseException('unparsed data') |
676 | 691 | return (i + j, term) |
| 692 | + if tag == _TAG_LOCAL_EXT: |
| 693 | + raise ParseException('LOCAL_EXT is opaque') |
677 | 694 | raise ParseException('invalid tag') |
678 | 695 |
|
679 | 696 | def _binary_to_term_sequence(i, length, data): |
@@ -870,8 +887,12 @@ def _bignum_to_binary(term): |
870 | 887 | def _float_to_binary(term): |
871 | 888 | return b_chr(_TAG_NEW_FLOAT_EXT) + struct.pack(b'>d', term) |
872 | 889 |
|
873 | | -# Elixir use can set to b'nil' |
874 | 890 | def set_undefined(value): |
| 891 | + """ |
| 892 | + Set the 'undefined' atom that is decoded as None |
| 893 | + (Elixir use may want to use 'nil' instead of 'undefined') |
| 894 | + """ |
| 895 | + # pylint: disable=global-statement |
875 | 896 | assert isinstance(value, bytes) |
876 | 897 | global _UNDEFINED |
877 | 898 | _UNDEFINED = value |
|
0 commit comments