├── .travis.yml ├── LICENSE ├── README.markdown ├── README.rst ├── erlang.py ├── examples ├── port.py └── test.exs ├── setup.py └── tests ├── __init__.py └── erlang_tests.py /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | - "3.6" 7 | - "3.7" 8 | - "3.8" 9 | - "3.9" 10 | - "pypy" 11 | # - "pypy3" 12 | before_install: 13 | - | 14 | if [ $TRAVIS_PYTHON_VERSION == "3.7" ]; then 15 | pip install setuptools==60.8.2 16 | fi 17 | script: python setup.py test 18 | branches: 19 | only: 20 | - master 21 | notifications: 22 | email: 23 | recipients: 24 | - mjtruog@gmail.com 25 | irc: 26 | channels: 27 | - "irc.oftc.net#cloudi" 28 | template: 29 | - "%{repository_slug} (%{branch} - %{commit}) %{author}: %{commit_message}" 30 | - "View Changes %{compare_url}" 31 | - "Build #%{build_number}: %{message} (%{build_url})" 32 | on_success: change 33 | on_failure: always 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011-2023 Michael Truog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Erlang External Term Format for Python 2 | ====================================== 3 | 4 | [![Build Status](https://app.travis-ci.com/okeuday/erlang_py.svg?branch=master)](https://app.travis-ci.com/okeuday/erlang_py) 5 | 6 | Provides all encoding and decoding for the Erlang External Term Format 7 | (as defined at [https://erlang.org/doc/apps/erts/erl_ext_dist.html](https://erlang.org/doc/apps/erts/erl_ext_dist.html)) 8 | in a single Python module. 9 | 10 | Available as a [Python package at `https://pypi.python.org/pypi/erlang_py/`](https://pypi.python.org/pypi/erlang_py/). 11 | 12 | Tests 13 | ----- 14 | 15 | python setup.py test 16 | 17 | Author 18 | ------ 19 | 20 | Michael Truog (mjtruog at protonmail dot com) 21 | 22 | License 23 | ------- 24 | 25 | MIT License 26 | 27 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Erlang External Term Format for Python 2 | ====================================== 3 | 4 | |Build Status| 5 | 6 | Provides all encoding and decoding for the Erlang External Term Format 7 | (as defined at https://erlang.org/doc/apps/erts/erl_ext_dist.html) in a 8 | single Python module. 9 | 10 | Available as a `Python package at 11 | ``https://pypi.python.org/pypi/erlang_py/`` `__. 12 | 13 | Tests 14 | ----- 15 | 16 | :: 17 | 18 | python setup.py test 19 | 20 | Author 21 | ------ 22 | 23 | Michael Truog (mjtruog at protonmail dot com) 24 | 25 | License 26 | ------- 27 | 28 | MIT License 29 | 30 | .. |Build Status| image:: https://app.travis-ci.com/okeuday/erlang_py.svg?branch=master 31 | :target: https://app.travis-ci.com/okeuday/erlang_py 32 | -------------------------------------------------------------------------------- /erlang.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*-Mode:python;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*- 3 | # ex: set ft=python fenc=utf-8 sts=4 ts=4 sw=4 et nomod: 4 | # 5 | # MIT License 6 | # 7 | # Copyright (c) 2011-2023 Michael Truog 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a 10 | # copy of this software and associated documentation files (the "Software"), 11 | # to deal in the Software without restriction, including without limitation 12 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | # and/or sell copies of the Software, and to permit persons to whom the 14 | # Software is furnished to do so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in 17 | # all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | # DEALINGS IN THE SOFTWARE. 26 | # 27 | """ 28 | Erlang External Term Format Encoding/Decoding 29 | """ 30 | # pylint: disable=too-many-lines 31 | 32 | import sys 33 | import struct 34 | import zlib 35 | import copy 36 | 37 | if sys.version_info[0] >= 3: 38 | TypeLong = int # pylint: disable=invalid-name 39 | TypeUnicode = str # pylint: disable=invalid-name 40 | def b_chr(integer): 41 | """ 42 | bytes chr function 43 | """ 44 | return bytes([integer]) 45 | def b_ord(character): 46 | """ 47 | bytes ord function 48 | """ 49 | return character 50 | else: 51 | TypeLong = long # pylint: disable=invalid-name 52 | TypeUnicode = unicode # pylint: disable=invalid-name 53 | def b_chr(integer): 54 | """ 55 | bytes chr function 56 | """ 57 | return chr(integer) 58 | def b_ord(character): 59 | """ 60 | bytes ord function 61 | """ 62 | return ord(character) 63 | 64 | __all__ = ['OtpErlangAtom', 65 | 'OtpErlangBinary', 66 | 'OtpErlangList', 67 | 'OtpErlangPid', 68 | 'OtpErlangPort', 69 | 'OtpErlangReference', 70 | 'OtpErlangFunction', 71 | 'binary_to_term', 72 | 'term_to_binary', 73 | 'set_undefined', 74 | 'InputException', 75 | 'OutputException', 76 | 'ParseException'] 77 | 78 | _UNDEFINED = b'undefined' # Change with set_undefined 79 | 80 | # tag values here http://www.erlang.org/doc/apps/erts/erl_ext_dist.html 81 | _TAG_VERSION = 131 82 | _TAG_COMPRESSED_ZLIB = 80 83 | _TAG_NEW_FLOAT_EXT = 70 84 | _TAG_BIT_BINARY_EXT = 77 85 | _TAG_ATOM_CACHE_REF = 78 86 | _TAG_NEW_PID_EXT = 88 87 | _TAG_NEW_PORT_EXT = 89 88 | _TAG_NEWER_REFERENCE_EXT = 90 89 | _TAG_SMALL_INTEGER_EXT = 97 90 | _TAG_INTEGER_EXT = 98 91 | _TAG_FLOAT_EXT = 99 92 | _TAG_ATOM_EXT = 100 93 | _TAG_REFERENCE_EXT = 101 94 | _TAG_PORT_EXT = 102 95 | _TAG_PID_EXT = 103 96 | _TAG_SMALL_TUPLE_EXT = 104 97 | _TAG_LARGE_TUPLE_EXT = 105 98 | _TAG_NIL_EXT = 106 99 | _TAG_STRING_EXT = 107 100 | _TAG_LIST_EXT = 108 101 | _TAG_BINARY_EXT = 109 102 | _TAG_SMALL_BIG_EXT = 110 103 | _TAG_LARGE_BIG_EXT = 111 104 | _TAG_NEW_FUN_EXT = 112 105 | _TAG_EXPORT_EXT = 113 106 | _TAG_NEW_REFERENCE_EXT = 114 107 | _TAG_SMALL_ATOM_EXT = 115 108 | _TAG_MAP_EXT = 116 109 | _TAG_FUN_EXT = 117 110 | _TAG_ATOM_UTF8_EXT = 118 111 | _TAG_SMALL_ATOM_UTF8_EXT = 119 112 | _TAG_V4_PORT_EXT = 120 113 | _TAG_LOCAL_EXT = 121 114 | 115 | # Erlang term classes listed alphabetically 116 | 117 | class OtpErlangAtom(object): 118 | """ 119 | OtpErlangAtom 120 | """ 121 | # pylint: disable=useless-object-inheritance 122 | # pylint: disable=too-few-public-methods 123 | def __init__(self, value): 124 | self.value = value 125 | def binary(self): 126 | """ 127 | return encoded representation 128 | """ 129 | if isinstance(self.value, int): 130 | return b_chr(_TAG_ATOM_CACHE_REF) + b_chr(self.value) 131 | if isinstance(self.value, TypeUnicode): 132 | value_encoded = self.value.encode('utf-8') 133 | length = len(value_encoded) 134 | if length <= 255: 135 | return ( 136 | b_chr(_TAG_SMALL_ATOM_UTF8_EXT) + 137 | b_chr(length) + value_encoded 138 | ) 139 | if length <= 65535: 140 | return ( 141 | b_chr(_TAG_ATOM_UTF8_EXT) + 142 | struct.pack(b'>H', length) + value_encoded 143 | ) 144 | raise OutputException('uint16 overflow') 145 | if isinstance(self.value, bytes): 146 | # deprecated 147 | # (not used in Erlang/OTP 26, i.e., minor_version 2) 148 | length = len(self.value) 149 | if length <= 255: 150 | return b_chr(_TAG_SMALL_ATOM_EXT) + b_chr(length) + self.value 151 | if length <= 65535: 152 | return ( 153 | b_chr(_TAG_ATOM_EXT) + 154 | struct.pack(b'>H', length) + self.value 155 | ) 156 | raise OutputException('uint16 overflow') 157 | raise OutputException('unknown atom type') 158 | def __repr__(self): 159 | return '%s(%s)' % (self.__class__.__name__, repr(self.value)) 160 | def __hash__(self): 161 | return hash(self.binary()) 162 | def __eq__(self, other): 163 | return self.binary() == other.binary() 164 | 165 | class OtpErlangBinary(object): 166 | """ 167 | OtpErlangBinary 168 | """ 169 | # pylint: disable=useless-object-inheritance 170 | # pylint: disable=too-few-public-methods 171 | def __init__(self, value, bits=8): 172 | self.value = value 173 | self.bits = bits # bits in last byte 174 | def binary(self): 175 | """ 176 | return encoded representation 177 | """ 178 | if isinstance(self.value, bytes): 179 | length = len(self.value) 180 | if length > 4294967295: 181 | raise OutputException('uint32 overflow') 182 | if self.bits != 8: 183 | return ( 184 | b_chr(_TAG_BIT_BINARY_EXT) + 185 | struct.pack(b'>I', length) + 186 | b_chr(self.bits) + self.value 187 | ) 188 | return ( 189 | b_chr(_TAG_BINARY_EXT) + 190 | struct.pack(b'>I', length) + 191 | self.value 192 | ) 193 | raise OutputException('unknown binary type') 194 | def __repr__(self): 195 | return '%s(%s,bits=%s)' % ( 196 | self.__class__.__name__, repr(self.value), repr(self.bits) 197 | ) 198 | def __hash__(self): 199 | return hash(self.binary()) 200 | def __eq__(self, other): 201 | return self.binary() == other.binary() 202 | 203 | class OtpErlangList(object): 204 | """ 205 | OtpErlangList 206 | """ 207 | # pylint: disable=useless-object-inheritance 208 | # pylint: disable=too-few-public-methods 209 | def __init__(self, value, improper=False): 210 | self.value = value 211 | self.improper = improper # no empty list tail? 212 | def binary(self): 213 | """ 214 | return encoded representation 215 | """ 216 | if isinstance(self.value, list): 217 | length = len(self.value) 218 | if length == 0: 219 | return b_chr(_TAG_NIL_EXT) 220 | if length > 4294967295: 221 | raise OutputException('uint32 overflow') 222 | if self.improper: 223 | return ( 224 | b_chr(_TAG_LIST_EXT) + 225 | struct.pack(b'>I', length - 1) + 226 | b''.join([_term_to_binary(element) 227 | for element in self.value]) 228 | ) 229 | return ( 230 | b_chr(_TAG_LIST_EXT) + 231 | struct.pack(b'>I', length) + 232 | b''.join([_term_to_binary(element) 233 | for element in self.value]) + 234 | b_chr(_TAG_NIL_EXT) 235 | ) 236 | raise OutputException('unknown list type') 237 | def __repr__(self): 238 | return '%s(%s,improper=%s)' % ( 239 | self.__class__.__name__, repr(self.value), repr(self.improper) 240 | ) 241 | def __hash__(self): 242 | return hash(self.binary()) 243 | def __eq__(self, other): 244 | return self.binary() == other.binary() 245 | 246 | class OtpErlangPid(object): 247 | """ 248 | OtpErlangPid 249 | """ 250 | # pylint: disable=useless-object-inheritance 251 | # pylint: disable=too-few-public-methods 252 | def __init__(self, node, id_value, serial, creation): 253 | # pylint: disable=invalid-name 254 | self.node = node 255 | self.id = id_value 256 | self.serial = serial 257 | self.creation = creation 258 | def binary(self): 259 | """ 260 | return encoded representation 261 | """ 262 | creation_size = len(self.creation) 263 | if creation_size == 1: 264 | return ( 265 | b_chr(_TAG_PID_EXT) + 266 | self.node.binary() + self.id + self.serial + self.creation 267 | ) 268 | if creation_size == 4: 269 | return ( 270 | b_chr(_TAG_NEW_PID_EXT) + 271 | self.node.binary() + self.id + self.serial + self.creation 272 | ) 273 | raise OutputException('unknown pid type') 274 | def __repr__(self): 275 | return '%s(%s,%s,%s,%s)' % ( 276 | self.__class__.__name__, 277 | repr(self.node), repr(self.id), repr(self.serial), 278 | repr(self.creation) 279 | ) 280 | def __hash__(self): 281 | return hash(self.binary()) 282 | def __eq__(self, other): 283 | return self.binary() == other.binary() 284 | 285 | class OtpErlangPort(object): 286 | """ 287 | OtpErlangPort 288 | """ 289 | # pylint: disable=useless-object-inheritance 290 | # pylint: disable=too-few-public-methods 291 | def __init__(self, node, id_value, creation): 292 | # pylint: disable=invalid-name 293 | self.node = node 294 | self.id = id_value 295 | self.creation = creation 296 | def binary(self): 297 | """ 298 | return encoded representation 299 | """ 300 | id_size = len(self.id) 301 | if id_size == 8: 302 | return ( 303 | b_chr(_TAG_V4_PORT_EXT) + 304 | self.node.binary() + self.id + self.creation 305 | ) 306 | creation_size = len(self.creation) 307 | if creation_size == 4: 308 | return ( 309 | b_chr(_TAG_NEW_PORT_EXT) + 310 | self.node.binary() + self.id + self.creation 311 | ) 312 | if creation_size == 1: 313 | return ( 314 | b_chr(_TAG_PORT_EXT) + 315 | self.node.binary() + self.id + self.creation 316 | ) 317 | raise OutputException('unknown port type') 318 | def __repr__(self): 319 | return '%s(%s,%s,%s)' % ( 320 | self.__class__.__name__, 321 | repr(self.node), repr(self.id), repr(self.creation) 322 | ) 323 | def __hash__(self): 324 | return hash(self.binary()) 325 | def __eq__(self, other): 326 | return self.binary() == other.binary() 327 | 328 | class OtpErlangReference(object): 329 | """ 330 | OtpErlangReference 331 | """ 332 | # pylint: disable=useless-object-inheritance 333 | # pylint: disable=too-few-public-methods 334 | def __init__(self, node, id_value, creation): 335 | # pylint: disable=invalid-name 336 | self.node = node 337 | self.id = id_value 338 | self.creation = creation 339 | def binary(self): 340 | """ 341 | return encoded representation 342 | """ 343 | length = int(len(self.id) / 4) 344 | if length == 0: 345 | return ( 346 | b_chr(_TAG_REFERENCE_EXT) + 347 | self.node.binary() + self.id + self.creation 348 | ) 349 | if length <= 65535: 350 | creation_size = len(self.creation) 351 | if creation_size == 1: 352 | return ( 353 | b_chr(_TAG_NEW_REFERENCE_EXT) + 354 | struct.pack(b'>H', length) + 355 | self.node.binary() + self.creation + self.id 356 | ) 357 | if creation_size == 4: 358 | return ( 359 | b_chr(_TAG_NEWER_REFERENCE_EXT) + 360 | struct.pack(b'>H', length) + 361 | self.node.binary() + self.creation + self.id 362 | ) 363 | raise OutputException('unknown reference type') 364 | raise OutputException('uint16 overflow') 365 | def __repr__(self): 366 | return '%s(%s,%s,%s)' % ( 367 | self.__class__.__name__, 368 | repr(self.node), repr(self.id), repr(self.creation) 369 | ) 370 | def __hash__(self): 371 | return hash(self.binary()) 372 | def __eq__(self, other): 373 | return self.binary() == other.binary() 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 | 399 | # dependency to support Erlang maps as map keys in python 400 | 401 | class frozendict(dict): 402 | """ 403 | frozendict is under the PSF (Python Software Foundation) License 404 | (from http://code.activestate.com/recipes/414283-frozen-dictionaries/) 405 | """ 406 | # pylint: disable=invalid-name 407 | def _blocked_attribute(self): 408 | # pylint: disable=no-self-use 409 | raise AttributeError('A frozendict cannot be modified.') 410 | _blocked_attribute = property(_blocked_attribute) 411 | __delitem__ = __setitem__ = clear = _blocked_attribute 412 | pop = popitem = setdefault = update = _blocked_attribute 413 | def __new__(cls, *args, **kw): 414 | # pylint: disable=unused-argument 415 | # pylint: disable=too-many-nested-blocks 416 | new = dict.__new__(cls) 417 | args_ = [] 418 | for arg in args: 419 | if isinstance(arg, dict): 420 | arg = copy.copy(arg) 421 | for k, v in arg.items(): 422 | if isinstance(v, dict): 423 | arg[k] = frozendict(v) 424 | elif isinstance(v, list): 425 | v_ = list() 426 | for elm in v: 427 | if isinstance(elm, dict): 428 | v_.append(frozendict(elm)) 429 | else: 430 | v_.append(elm) 431 | arg[k] = tuple(v_) 432 | args_.append(arg) 433 | else: 434 | args_.append(arg) 435 | dict.__init__(new, *args_, **kw) 436 | return new 437 | def __init__(self, *args, **kw): 438 | # pylint: disable=unused-argument 439 | # pylint: disable=super-init-not-called 440 | self.__cached_hash = None 441 | def __hash__(self): 442 | if self.__cached_hash is None: 443 | self.__cached_hash = hash(frozenset(self.items())) 444 | return self.__cached_hash 445 | def __repr__(self): 446 | return "frozendict(%s)" % dict.__repr__(self) 447 | 448 | # core functionality 449 | 450 | def binary_to_term(data): 451 | """ 452 | Decode Erlang terms within binary data into Python types 453 | """ 454 | if not isinstance(data, bytes): 455 | raise ParseException('not bytes input') 456 | size = len(data) 457 | if size <= 1: 458 | raise ParseException('null input') 459 | if b_ord(data[0]) != _TAG_VERSION: 460 | raise ParseException('invalid version') 461 | try: 462 | i, term = _binary_to_term(1, data) 463 | if i != size: 464 | raise ParseException('unparsed data') 465 | return term 466 | except struct.error: 467 | raise ParseException('missing data') 468 | except IndexError: 469 | raise ParseException('missing data') 470 | 471 | def term_to_binary(term, compressed=False): 472 | """ 473 | Encode Python types into Erlang terms in binary data 474 | """ 475 | data_uncompressed = _term_to_binary(term) 476 | if compressed is False: 477 | return b_chr(_TAG_VERSION) + data_uncompressed 478 | if compressed is True: 479 | compressed = 6 480 | if compressed < 0 or compressed > 9: 481 | raise InputException('compressed in [0..9]') 482 | data_compressed = zlib.compress(data_uncompressed, compressed) 483 | size_uncompressed = len(data_uncompressed) 484 | if size_uncompressed > 4294967295: 485 | raise OutputException('uint32 overflow') 486 | return ( 487 | b_chr(_TAG_VERSION) + b_chr(_TAG_COMPRESSED_ZLIB) + 488 | struct.pack(b'>I', size_uncompressed) + data_compressed 489 | ) 490 | 491 | # binary_to_term implementation functions 492 | 493 | def _binary_to_term(i, data): 494 | # pylint: disable=too-many-locals 495 | # pylint: disable=too-many-return-statements 496 | # pylint: disable=too-many-branches 497 | # pylint: disable=too-many-statements 498 | tag = b_ord(data[i]) 499 | i += 1 500 | if tag == _TAG_NEW_FLOAT_EXT: 501 | return (i + 8, struct.unpack(b'>d', data[i:i + 8])[0]) 502 | if tag == _TAG_BIT_BINARY_EXT: 503 | j = struct.unpack(b'>I', data[i:i + 4])[0] 504 | i += 4 505 | bits = b_ord(data[i]) 506 | i += 1 507 | return (i + j, OtpErlangBinary(data[i:i + j], bits)) 508 | if tag == _TAG_ATOM_CACHE_REF: 509 | return (i + 1, OtpErlangAtom(b_ord(data[i]))) 510 | if tag == _TAG_SMALL_INTEGER_EXT: 511 | return (i + 1, b_ord(data[i])) 512 | if tag == _TAG_INTEGER_EXT: 513 | return (i + 4, struct.unpack(b'>i', data[i:i + 4])[0]) 514 | if tag == _TAG_FLOAT_EXT: 515 | value = float(data[i:i + 31].partition(b_chr(0))[0]) 516 | return (i + 31, value) 517 | if tag in (_TAG_V4_PORT_EXT, _TAG_NEW_PORT_EXT, 518 | _TAG_REFERENCE_EXT, _TAG_PORT_EXT): 519 | i, node = _binary_to_atom(i, data) 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): 527 | creation = data[i:i + 4] 528 | i += 4 529 | else: 530 | creation = data[i:i + 1] 531 | i += 1 532 | if tag == _TAG_REFERENCE_EXT: 533 | return (i, OtpErlangReference(node, id_value, creation)) 534 | # tag == _TAG_V4_PORT_EXT or tag == _TAG_NEW_PORT_EXT or 535 | # tag == _TAG_PORT_EXT 536 | return (i, OtpErlangPort(node, id_value, creation)) 537 | if tag in (_TAG_NEW_PID_EXT, _TAG_PID_EXT): 538 | i, node = _binary_to_atom(i, data) 539 | id_value = data[i:i + 4] 540 | i += 4 541 | serial = data[i:i + 4] 542 | i += 4 543 | if tag == _TAG_NEW_PID_EXT: 544 | creation = data[i:i + 4] 545 | i += 4 546 | elif tag == _TAG_PID_EXT: 547 | creation = data[i:i + 1] 548 | i += 1 549 | return (i, OtpErlangPid(node, id_value, serial, creation)) 550 | if tag in (_TAG_SMALL_TUPLE_EXT, _TAG_LARGE_TUPLE_EXT): 551 | if tag == _TAG_SMALL_TUPLE_EXT: 552 | length = b_ord(data[i]) 553 | i += 1 554 | elif tag == _TAG_LARGE_TUPLE_EXT: 555 | length = struct.unpack(b'>I', data[i:i + 4])[0] 556 | i += 4 557 | i, tuple_value = _binary_to_term_sequence(i, length, data) 558 | return (i, tuple(tuple_value)) 559 | if tag == _TAG_NIL_EXT: 560 | return (i, []) 561 | if tag == _TAG_STRING_EXT: 562 | j = struct.unpack(b'>H', data[i:i + 2])[0] 563 | i += 2 564 | return (i + j, data[i:i + j]) 565 | if tag == _TAG_LIST_EXT: 566 | length = struct.unpack(b'>I', data[i:i + 4])[0] 567 | i += 4 568 | i, list_value = _binary_to_term_sequence(i, length, data) 569 | i, tail = _binary_to_term(i, data) 570 | if not isinstance(tail, list) or tail != []: 571 | list_value.append(tail) 572 | list_value = OtpErlangList(list_value, improper=True) 573 | return (i, list_value) 574 | if tag == _TAG_BINARY_EXT: 575 | j = struct.unpack(b'>I', data[i:i + 4])[0] 576 | i += 4 577 | return (i + j, OtpErlangBinary(data[i:i + j], 8)) 578 | if tag in (_TAG_SMALL_BIG_EXT, _TAG_LARGE_BIG_EXT): 579 | if tag == _TAG_SMALL_BIG_EXT: 580 | j = b_ord(data[i]) 581 | i += 1 582 | elif tag == _TAG_LARGE_BIG_EXT: 583 | j = struct.unpack(b'>I', data[i:i + 4])[0] 584 | i += 4 585 | sign = b_ord(data[i]) 586 | bignum = 0 587 | for bignum_index in range(j): 588 | digit = b_ord(data[i + j - bignum_index]) 589 | bignum = bignum * 256 + int(digit) 590 | if sign == 1: 591 | bignum *= -1 592 | i += 1 593 | return (i + j, bignum) 594 | if tag == _TAG_NEW_FUN_EXT: 595 | length = struct.unpack(b'>I', data[i:i + 4])[0] 596 | return (i + length, OtpErlangFunction(tag, data[i:i + length])) 597 | if tag == _TAG_EXPORT_EXT: 598 | old_i = i 599 | i, _ = _binary_to_atom(i, data) 600 | i, _ = _binary_to_atom(i, data) 601 | if b_ord(data[i]) != _TAG_SMALL_INTEGER_EXT: 602 | raise ParseException('invalid small integer tag') 603 | i += 1 604 | _ = b_ord(data[i]) 605 | i += 1 606 | return (i, OtpErlangFunction(tag, data[old_i:i])) 607 | if tag in (_TAG_NEWER_REFERENCE_EXT, _TAG_NEW_REFERENCE_EXT): 608 | j = struct.unpack(b'>H', data[i:i + 2])[0] * 4 609 | i += 2 610 | i, node = _binary_to_atom(i, data) 611 | if tag == _TAG_NEWER_REFERENCE_EXT: 612 | creation = data[i:i + 4] 613 | i += 4 614 | elif tag == _TAG_NEW_REFERENCE_EXT: 615 | creation = data[i:i + 1] 616 | i += 1 617 | return (i + j, OtpErlangReference(node, data[i: i + j], creation)) 618 | if tag == _TAG_MAP_EXT: 619 | length = struct.unpack(b'>I', data[i:i + 4])[0] 620 | i += 4 621 | pairs = {} 622 | def to_immutable(value): 623 | if isinstance(value, dict): 624 | return frozendict(key) 625 | if isinstance(value, list): 626 | return OtpErlangList(value) 627 | if isinstance(value, tuple): 628 | return tuple(to_immutable(v) for v in value) 629 | return value 630 | 631 | for _ in range(length): 632 | i, key = _binary_to_term(i, data) 633 | i, value = _binary_to_term(i, data) 634 | pairs[to_immutable(key)] = value 635 | return (i, pairs) 636 | if tag == _TAG_FUN_EXT: 637 | old_i = i 638 | numfree = struct.unpack(b'>I', data[i:i + 4])[0] 639 | i += 4 640 | i, _ = _binary_to_pid(i, data) 641 | i, _ = _binary_to_atom(i, data) 642 | i, _ = _binary_to_integer(i, data) 643 | i, _ = _binary_to_integer(i, data) 644 | i, _ = _binary_to_term_sequence(i, numfree, data) 645 | return (i, OtpErlangFunction(tag, data[old_i:i])) 646 | if tag in (_TAG_ATOM_UTF8_EXT, _TAG_ATOM_EXT): 647 | j = struct.unpack(b'>H', data[i:i + 2])[0] 648 | i += 2 649 | atom_name = data[i:i + j] 650 | i = i + j 651 | if atom_name == b'true': 652 | return (i, True) 653 | if atom_name == b'false': 654 | return (i, False) 655 | if atom_name == _UNDEFINED: 656 | return (i, None) 657 | if tag == _TAG_ATOM_UTF8_EXT: 658 | atom_name = TypeUnicode( 659 | atom_name, encoding='utf-8', errors='strict' 660 | ) 661 | return (i, OtpErlangAtom(atom_name)) 662 | if tag in (_TAG_SMALL_ATOM_UTF8_EXT, _TAG_SMALL_ATOM_EXT): 663 | j = b_ord(data[i]) 664 | i += 1 665 | atom_name = data[i:i + j] 666 | i = i + j 667 | if atom_name == b'true': 668 | return (i, True) 669 | if atom_name == b'false': 670 | return (i, False) 671 | if atom_name == _UNDEFINED: 672 | return (i, None) 673 | if tag == _TAG_SMALL_ATOM_UTF8_EXT: 674 | atom_name = TypeUnicode( 675 | atom_name, encoding='utf-8', errors='strict' 676 | ) 677 | return (i, OtpErlangAtom(atom_name)) 678 | if tag == _TAG_COMPRESSED_ZLIB: 679 | size_uncompressed = struct.unpack(b'>I', data[i:i + 4])[0] 680 | if size_uncompressed == 0: 681 | raise ParseException('compressed data null') 682 | i += 4 683 | data_compressed = data[i:] 684 | j = len(data_compressed) 685 | data_uncompressed = zlib.decompress(data_compressed) 686 | if size_uncompressed != len(data_uncompressed): 687 | raise ParseException('compression corrupt') 688 | (i_new, term) = _binary_to_term(0, data_uncompressed) 689 | if i_new != size_uncompressed: 690 | raise ParseException('unparsed data') 691 | return (i + j, term) 692 | if tag == _TAG_LOCAL_EXT: 693 | raise ParseException('LOCAL_EXT is opaque') 694 | raise ParseException('invalid tag') 695 | 696 | def _binary_to_term_sequence(i, length, data): 697 | sequence = [] 698 | for _ in range(length): 699 | i, element = _binary_to_term(i, data) 700 | sequence.append(element) 701 | return (i, sequence) 702 | 703 | # (binary_to_term Erlang term primitive type functions) 704 | 705 | def _binary_to_integer(i, data): 706 | tag = b_ord(data[i]) 707 | i += 1 708 | if tag == _TAG_SMALL_INTEGER_EXT: 709 | return (i + 1, b_ord(data[i])) 710 | if tag == _TAG_INTEGER_EXT: 711 | return (i + 4, struct.unpack(b'>i', data[i:i + 4])[0]) 712 | raise ParseException('invalid integer tag') 713 | 714 | def _binary_to_pid(i, data): 715 | tag = b_ord(data[i]) 716 | i += 1 717 | if tag == _TAG_NEW_PID_EXT: 718 | i, node = _binary_to_atom(i, data) 719 | id_value = data[i:i + 4] 720 | i += 4 721 | serial = data[i:i + 4] 722 | i += 4 723 | creation = data[i:i + 4] 724 | i += 4 725 | return (i, OtpErlangPid(node, id_value, serial, creation)) 726 | if tag == _TAG_PID_EXT: 727 | i, node = _binary_to_atom(i, data) 728 | id_value = data[i:i + 4] 729 | i += 4 730 | serial = data[i:i + 4] 731 | i += 4 732 | creation = data[i:i + 1] 733 | i += 1 734 | return (i, OtpErlangPid(node, id_value, serial, creation)) 735 | raise ParseException('invalid pid tag') 736 | 737 | def _binary_to_atom(i, data): 738 | tag = b_ord(data[i]) 739 | i += 1 740 | if tag == _TAG_ATOM_EXT: 741 | j = struct.unpack(b'>H', data[i:i + 2])[0] 742 | i += 2 743 | return (i + j, OtpErlangAtom(data[i:i + j])) 744 | if tag == _TAG_ATOM_CACHE_REF: 745 | return (i + 1, OtpErlangAtom(b_ord(data[i]))) 746 | if tag == _TAG_SMALL_ATOM_EXT: 747 | j = b_ord(data[i]) 748 | i += 1 749 | return (i + j, OtpErlangAtom(data[i:i + j])) 750 | if tag == _TAG_ATOM_UTF8_EXT: 751 | j = struct.unpack(b'>H', data[i:i + 2])[0] 752 | i += 2 753 | atom_name = TypeUnicode( 754 | data[i:i + j], encoding='utf-8', errors='strict' 755 | ) 756 | return (i + j, OtpErlangAtom(atom_name)) 757 | if tag == _TAG_SMALL_ATOM_UTF8_EXT: 758 | j = b_ord(data[i]) 759 | i += 1 760 | atom_name = TypeUnicode( 761 | data[i:i + j], encoding='utf-8', errors='strict' 762 | ) 763 | return (i + j, OtpErlangAtom(atom_name)) 764 | raise ParseException('invalid atom tag') 765 | 766 | # term_to_binary implementation functions 767 | 768 | def _term_to_binary(term): 769 | # pylint: disable=too-many-return-statements 770 | # pylint: disable=too-many-branches 771 | if isinstance(term, bytes): 772 | return _string_to_binary(term) 773 | if isinstance(term, TypeUnicode): 774 | return _string_to_binary( 775 | term.encode(encoding='utf-8', errors='strict') 776 | ) 777 | if isinstance(term, list): 778 | return OtpErlangList(term).binary() 779 | if isinstance(term, tuple): 780 | return _tuple_to_binary(term) 781 | if isinstance(term, bool): 782 | if term: 783 | atom_name = b'true' 784 | else: 785 | atom_name = b'false' 786 | return OtpErlangAtom(TypeUnicode(atom_name, encoding='utf-8')).binary() 787 | if isinstance(term, (int, TypeLong)): 788 | return _long_to_binary(term) 789 | if isinstance(term, float): 790 | return _float_to_binary(term) 791 | if isinstance(term, dict): 792 | return _dict_to_binary(term) 793 | if term is None: 794 | return OtpErlangAtom(TypeUnicode(_UNDEFINED, encoding='utf-8')).binary() 795 | if isinstance(term, OtpErlangAtom): 796 | return term.binary() 797 | if isinstance(term, OtpErlangList): 798 | return term.binary() 799 | if isinstance(term, OtpErlangBinary): 800 | return term.binary() 801 | if isinstance(term, OtpErlangFunction): 802 | return term.binary() 803 | if isinstance(term, OtpErlangReference): 804 | return term.binary() 805 | if isinstance(term, OtpErlangPort): 806 | return term.binary() 807 | if isinstance(term, OtpErlangPid): 808 | return term.binary() 809 | raise OutputException('unknown python type') 810 | 811 | # (term_to_binary Erlang term composite type functions) 812 | 813 | def _string_to_binary(term): 814 | length = len(term) 815 | if length == 0: 816 | return b_chr(_TAG_NIL_EXT) 817 | if length <= 65535: 818 | return b_chr(_TAG_STRING_EXT) + struct.pack(b'>H', length) + term 819 | if length <= 4294967295: 820 | return ( 821 | b_chr(_TAG_LIST_EXT) + struct.pack(b'>I', length) + 822 | b''.join([b_chr(_TAG_SMALL_INTEGER_EXT) + b_chr(b_ord(c)) 823 | for c in term]) + 824 | b_chr(_TAG_NIL_EXT) 825 | ) 826 | raise OutputException('uint32 overflow') 827 | 828 | def _tuple_to_binary(term): 829 | length = len(term) 830 | if length <= 255: 831 | return ( 832 | b_chr(_TAG_SMALL_TUPLE_EXT) + b_chr(length) + 833 | b''.join([_term_to_binary(element) for element in term]) 834 | ) 835 | if length <= 4294967295: 836 | return ( 837 | b_chr(_TAG_LARGE_TUPLE_EXT) + struct.pack(b'>I', length) + 838 | b''.join([_term_to_binary(element) for element in term]) 839 | ) 840 | raise OutputException('uint32 overflow') 841 | 842 | def _dict_to_binary(term): 843 | length = len(term) 844 | if length <= 4294967295: 845 | return ( 846 | b_chr(_TAG_MAP_EXT) + struct.pack(b'>I', length) + 847 | b''.join([_term_to_binary(key) + _term_to_binary(value) 848 | for key, value in term.items()]) 849 | ) 850 | raise OutputException('uint32 overflow') 851 | 852 | # (term_to_binary Erlang term primitive type functions) 853 | 854 | def _integer_to_binary(term): 855 | if 0 <= term <= 255: 856 | return b_chr(_TAG_SMALL_INTEGER_EXT) + b_chr(term) 857 | return b_chr(_TAG_INTEGER_EXT) + struct.pack(b'>i', term) 858 | 859 | def _long_to_binary(term): 860 | if -2147483648 <= term <= 2147483647: 861 | return _integer_to_binary(term) 862 | return _bignum_to_binary(term) 863 | 864 | def _bignum_to_binary(term): 865 | bignum = abs(term) 866 | if term < 0: 867 | sign = b_chr(1) 868 | else: 869 | sign = b_chr(0) 870 | value = [] 871 | while bignum > 0: 872 | value.append(b_chr(bignum & 255)) 873 | bignum >>= 8 874 | length = len(value) 875 | if length <= 255: 876 | return ( 877 | b_chr(_TAG_SMALL_BIG_EXT) + 878 | b_chr(length) + sign + b''.join(value) 879 | ) 880 | if length <= 4294967295: 881 | return ( 882 | b_chr(_TAG_LARGE_BIG_EXT) + 883 | struct.pack(b'>I', length) + sign + b''.join(value) 884 | ) 885 | raise OutputException('uint32 overflow') 886 | 887 | def _float_to_binary(term): 888 | return b_chr(_TAG_NEW_FLOAT_EXT) + struct.pack(b'>d', term) 889 | 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 896 | assert isinstance(value, bytes) 897 | global _UNDEFINED 898 | _UNDEFINED = value 899 | 900 | # Exception classes listed alphabetically 901 | 902 | class InputException(ValueError): 903 | """ 904 | InputError describes problems with function input parameters 905 | """ 906 | def __init__(self, s): 907 | ValueError.__init__(self) 908 | self.__s = str(s) 909 | def __str__(self): 910 | return self.__s 911 | 912 | class OutputException(TypeError): 913 | """ 914 | OutputError describes problems with creating function output data 915 | """ 916 | def __init__(self, s): 917 | TypeError.__init__(self) 918 | self.__s = str(s) 919 | def __str__(self): 920 | return self.__s 921 | 922 | class ParseException(SyntaxError): 923 | """ 924 | ParseError provides specific parsing failure information 925 | """ 926 | def __init__(self, s): 927 | SyntaxError.__init__(self) 928 | self.__s = str(s) 929 | def __str__(self): 930 | return self.__s 931 | 932 | def consult(string_in): 933 | """ 934 | provide file:consult/1 functionality with python types 935 | """ 936 | # pylint: disable=eval-used 937 | # pylint: disable=too-many-branches 938 | # pylint: disable=too-many-statements 939 | 940 | # manually parse textual erlang data to avoid external dependencies 941 | list_out = [] 942 | tuple_binary = False # binaries become tuples of integers 943 | quoted_string = False # strings become python string 944 | atom_string = False # atoms become python string 945 | number = False 946 | whitespace = frozenset(('\n', '\t', ' ')) 947 | i = 0 948 | while i < len(string_in): 949 | character = string_in[i] 950 | if character == ',': 951 | if atom_string: 952 | list_out.append('"') 953 | atom_string = False 954 | list_out.append(',') 955 | number = string_in[i + 1].isdigit() 956 | elif character == '{': 957 | list_out.append('(') 958 | number = string_in[i + 1].isdigit() 959 | elif character == '}': 960 | if atom_string: 961 | list_out.append('"') 962 | atom_string = False 963 | list_out.append(')') 964 | number = False 965 | elif character == '[': 966 | list_out.append('[') 967 | number = string_in[i + 1].isdigit() 968 | elif character == ']': 969 | if atom_string: 970 | list_out.append('"') 971 | atom_string = False 972 | list_out.append(']') 973 | number = False 974 | elif character == '<' and string_in[i + 1] == '<': 975 | list_out.append('(') 976 | tuple_binary = True 977 | i += 1 978 | elif character == '>' and string_in[i + 1] == '>': 979 | list_out.append(')') 980 | tuple_binary = False 981 | i += 1 982 | elif not quoted_string and not atom_string and character in whitespace: 983 | number = string_in[i + 1].isdigit() 984 | elif tuple_binary or number: 985 | list_out.append(character) 986 | elif character == '"': 987 | if quoted_string: 988 | quoted_string = False 989 | else: 990 | quoted_string = True 991 | list_out.append('"') 992 | elif character == "'": 993 | if atom_string: 994 | atom_string = False 995 | else: 996 | atom_string = True 997 | list_out.append('"') 998 | elif not quoted_string and not atom_string: 999 | atom_string = True 1000 | list_out.append('"') 1001 | list_out.append(character) 1002 | else: 1003 | list_out.append(character) 1004 | i += 1 1005 | return eval(''.join(list_out)) 1006 | -------------------------------------------------------------------------------- /examples/port.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This is free and unencumbered software released into the public domain. 3 | 4 | import erlang, os, struct 5 | 6 | def send(term, stream): 7 | """Write an Erlang term to an output stream.""" 8 | payload = erlang.term_to_binary(term) 9 | header = struct.pack('!I', len(payload)) 10 | stream.write(header) 11 | stream.write(payload) 12 | stream.flush() 13 | 14 | def recv(stream): 15 | """Read an Erlang term from an input stream.""" 16 | header = stream.read(4) 17 | if len(header) != 4: 18 | return None # EOF 19 | (length,) = struct.unpack('!I', header) 20 | payload = stream.read(length) 21 | if len(payload) != length: 22 | return None 23 | term = erlang.binary_to_term(payload) 24 | return term 25 | 26 | def recv_loop(stream): 27 | """Yield Erlang terms from an input stream.""" 28 | message = recv(stream) 29 | while message: 30 | yield message 31 | message = recv(stream) 32 | 33 | if __name__ == '__main__': 34 | input, output = os.fdopen(3, 'rb'), os.fdopen(4, 'wb') 35 | for message in recv_loop(input): 36 | send(message, output) # echo the message back 37 | -------------------------------------------------------------------------------- /examples/test.exs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env elixir 2 | # This is free and unencumbered software released into the public domain. 3 | 4 | port = Port.open({:spawn_executable, "/usr/bin/env"}, 5 | [:binary, {:packet, 4}, {:args, ["python", "-u", "port.py"]}, :nouse_stdio]) 6 | 7 | defmodule Python do 8 | def call(port, request) do 9 | Port.command(port, :erlang.term_to_binary(request)) 10 | receive do 11 | {^port, {:data, response}} -> :erlang.binary_to_term(response) 12 | end 13 | end 14 | end 15 | 16 | IO.inspect Python.call(port, {:ping}) 17 | 18 | Port.close(port) 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #-*-Mode:python;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*- 2 | # ex: set ft=python fenc=utf-8 sts=4 ts=4 sw=4 et: 3 | 4 | try: 5 | from setuptools import setup, Command 6 | except ImportError: 7 | from distutils.core import setup, Command 8 | 9 | class PyTest(Command): 10 | user_options = [] 11 | def initialize_options(self): 12 | pass 13 | def finalize_options(self): 14 | pass 15 | def run(self): 16 | import tests.erlang_tests 17 | import unittest 18 | suite = unittest.TestSuite() 19 | suite.addTests(tests.erlang_tests.get_suite()) 20 | unittest.TextTestRunner().run(suite) 21 | 22 | long_description = open('README.rst', 'r').read() 23 | setup( 24 | name='erlang_py', 25 | py_modules=['erlang'], 26 | cmdclass={'test': PyTest}, 27 | license='MIT', 28 | classifiers=[ 29 | 'Development Status :: 5 - Production/Stable', 30 | 'Intended Audience :: Developers', 31 | 'License :: OSI Approved :: MIT License', 32 | 'Operating System :: OS Independent', 33 | 'Programming Language :: Python', 34 | 'Programming Language :: Python :: 3', 35 | 'Programming Language :: Erlang', 36 | 'Topic :: Software Development :: Libraries :: Python Modules', 37 | 'Topic :: System :: Distributed Computing', 38 | ], 39 | version='2.0.7', 40 | description='Erlang External Term Format for Python', 41 | long_description=long_description, 42 | long_description_content_type='text/x-rst', 43 | author='Michael Truog', 44 | author_email='mjtruog@protonmail.com', 45 | url='https://github.com/okeuday/erlang_py', 46 | ) 47 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okeuday/erlang_py/bff65287a11c14a11e1dcc53d928a6f9710a677f/tests/__init__.py -------------------------------------------------------------------------------- /tests/erlang_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*-Mode:python;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*- 3 | # ex: set ft=python fenc=utf-8 sts=4 ts=4 sw=4 et nomod: 4 | # 5 | # MIT License 6 | # 7 | # Copyright (c) 2014-2023 Michael Truog 8 | # Copyright (c) 2009-2013 Dmitry Vasiliev 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a 11 | # copy of this software and associated documentation files (the "Software"), 12 | # to deal in the Software without restriction, including without limitation 13 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 14 | # and/or sell copies of the Software, and to permit persons to whom the 15 | # Software is furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies or substantial portions of the Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | # DEALINGS IN THE SOFTWARE. 27 | # 28 | """ 29 | Erlang External Term Format Encoding/Decoding Tests 30 | """ 31 | 32 | import sys 33 | import os 34 | sys.path.insert(0, 35 | os.path.sep.join( 36 | os.path.dirname( 37 | os.path.abspath(__file__) 38 | ).split(os.path.sep)[:-1] 39 | ) 40 | ) 41 | import unittest 42 | import erlang 43 | from collections import OrderedDict 44 | try: 45 | import coverage 46 | except ImportError: 47 | coverage = None 48 | 49 | # many of the test cases were adapted 50 | # from erlport (https://github.com/hdima/erlport) 51 | # to make the tests more exhaustive 52 | 53 | # pylint: disable=missing-docstring 54 | 55 | class AtomTestCase(unittest.TestCase): 56 | def test_atom(self): 57 | atom1 = erlang.OtpErlangAtom('test') 58 | self.assertEqual(erlang.OtpErlangAtom, type(atom1)) 59 | self.assertEqual(erlang.OtpErlangAtom('test'), atom1) 60 | self.assertEqual("OtpErlangAtom('test')", repr(atom1)) 61 | self.assertTrue(isinstance(atom1, erlang.OtpErlangAtom)) 62 | atom2 = erlang.OtpErlangAtom('test2') 63 | atom1_new = erlang.OtpErlangAtom('test') 64 | self.assertFalse(atom1 is atom2) 65 | self.assertNotEqual(hash(atom1), hash(atom2)) 66 | self.assertFalse(atom1 is atom1_new) 67 | self.assertEqual(hash(atom1), hash(atom1_new)) 68 | self.assertEqual('X' * 255, erlang.OtpErlangAtom('X' * 255).value) 69 | self.assertEqual('X' * 256, erlang.OtpErlangAtom('X' * 256).value) 70 | def test_invalid_atom(self): 71 | self.assertRaises(erlang.OutputException, 72 | erlang.OtpErlangAtom.binary, 73 | erlang.OtpErlangAtom([1, 2])) 74 | 75 | class ListTestCase(unittest.TestCase): 76 | def test_list(self): 77 | lst = erlang.OtpErlangList([116, 101, 115, 116]) 78 | self.assertTrue(isinstance(lst, erlang.OtpErlangList)) 79 | self.assertEqual(erlang.OtpErlangList([116, 101, 115, 116]), lst) 80 | self.assertEqual([116, 101, 115, 116], lst.value) 81 | self.assertEqual('OtpErlangList([116, 101, 115, 116],improper=False)', 82 | repr(lst)) 83 | 84 | class ImproperListTestCase(unittest.TestCase): 85 | def test_improper_list(self): 86 | lst = erlang.OtpErlangList([1, 2, 3, 4], improper=True) 87 | self.assertTrue(isinstance(lst, erlang.OtpErlangList)) 88 | self.assertEqual([1, 2, 3, 4], lst.value) 89 | self.assertEqual(4, lst.value[-1]) 90 | self.assertEqual('OtpErlangList([1, 2, 3, 4],improper=True)', 91 | repr(lst)) 92 | def test_comparison(self): 93 | lst = erlang.OtpErlangList([1, 2, 3, 4], improper=True) 94 | self.assertEqual(lst, lst) 95 | self.assertEqual(lst, 96 | erlang.OtpErlangList([1, 2, 3, 4], improper=True)) 97 | self.assertNotEqual(lst, 98 | erlang.OtpErlangList([1, 2, 3, 5], improper=True)) 99 | self.assertNotEqual(lst, 100 | erlang.OtpErlangList([1, 2, 3], improper=True)) 101 | def test_errors(self): 102 | self.assertRaises(erlang.OutputException, 103 | erlang.OtpErlangList.binary, 104 | erlang.OtpErlangList("invalid", improper=True)) 105 | 106 | class DecodeTestCase(unittest.TestCase): 107 | # pylint: disable=invalid-name 108 | def test_binary_to_term(self): 109 | self.assertRaises(erlang.ParseException, 110 | erlang.binary_to_term, b'') 111 | self.assertRaises(erlang.ParseException, 112 | erlang.binary_to_term, b'\0') 113 | self.assertRaises(erlang.ParseException, 114 | erlang.binary_to_term, b'\x83') 115 | self.assertRaises(erlang.ParseException, 116 | erlang.binary_to_term, b'\x83z') 117 | def test_binary_to_term_atom(self): 118 | self.assertRaises(erlang.ParseException, 119 | erlang.binary_to_term, b'\x83d') 120 | self.assertRaises(erlang.ParseException, 121 | erlang.binary_to_term, b'\x83d\0') 122 | self.assertRaises(erlang.ParseException, 123 | erlang.binary_to_term, b'\x83d\0\1') 124 | self.assertEqual(erlang.OtpErlangAtom(b''), 125 | erlang.binary_to_term(b'\x83d\0\0')) 126 | self.assertEqual(erlang.OtpErlangAtom(b''), 127 | erlang.binary_to_term(b'\x83s\0')) 128 | self.assertEqual(erlang.OtpErlangAtom(b'test'), 129 | erlang.binary_to_term(b'\x83d\0\4test')) 130 | self.assertEqual(erlang.OtpErlangAtom(b'test'), 131 | erlang.binary_to_term(b'\x83s\4test')) 132 | self.assertEqual(erlang.OtpErlangAtom(u'name'), 133 | erlang.binary_to_term(b'\x83w\x04name')) 134 | def test_binary_to_term_predefined_atoms(self): 135 | self.assertEqual(True, erlang.binary_to_term(b'\x83s\4true')) 136 | self.assertEqual(False, erlang.binary_to_term(b'\x83s\5false')) 137 | self.assertEqual(False, erlang.binary_to_term(b'\x83w\5false')) 138 | self.assertEqual(None, erlang.binary_to_term(b'\x83s\11undefined')) 139 | self.assertEqual(None, erlang.binary_to_term(b'\x83w\11undefined')) 140 | self.assertEqual(None, erlang.binary_to_term(b'\x83d\0\11undefined')) 141 | self.assertEqual(None, erlang.binary_to_term(b'\x83v\0\11undefined')) 142 | def test_binary_to_term_empty_list(self): 143 | self.assertEqual([], erlang.binary_to_term(b'\x83j')) 144 | def test_binary_to_term_string_list(self): 145 | self.assertRaises(erlang.ParseException, 146 | erlang.binary_to_term, b'\x83k') 147 | self.assertRaises(erlang.ParseException, 148 | erlang.binary_to_term, b'\x83k\0') 149 | self.assertRaises(erlang.ParseException, 150 | erlang.binary_to_term, b'\x83k\0\1') 151 | self.assertEqual(b'', erlang.binary_to_term(b'\x83k\0\0')) 152 | self.assertEqual(b'test', erlang.binary_to_term(b'\x83k\0\4test')) 153 | def test_binary_to_term_list(self): 154 | self.assertRaises(erlang.ParseException, 155 | erlang.binary_to_term, b'\x83l') 156 | self.assertRaises(erlang.ParseException, 157 | erlang.binary_to_term, b'\x83l\0') 158 | self.assertRaises(erlang.ParseException, 159 | erlang.binary_to_term, b'\x83l\0\0') 160 | self.assertRaises(erlang.ParseException, 161 | erlang.binary_to_term, b'\x83l\0\0\0') 162 | self.assertRaises(erlang.ParseException, 163 | erlang.binary_to_term, b'\x83l\0\0\0\0') 164 | self.assertEqual([], erlang.binary_to_term(b'\x83l\0\0\0\0j')) 165 | self.assertEqual([[], []], erlang.binary_to_term(b'\x83l\0\0\0\2jjj')) 166 | def test_binary_to_term_improper_list(self): 167 | self.assertRaises(erlang.ParseException, 168 | erlang.binary_to_term, b'\x83l\0\0\0\0k') 169 | lst = erlang.binary_to_term(b'\x83l\0\0\0\1jd\0\4tail') 170 | self.assertEqual(isinstance(lst, erlang.OtpErlangList), True) 171 | self.assertEqual([[], erlang.OtpErlangAtom(b'tail')], lst.value) 172 | self.assertEqual(True, lst.improper) 173 | def test_binary_to_term_map(self): 174 | # represents #{ {at,[hello]} => ok} 175 | binary = b"\x83t\x00\x00\x00\x01h\x02d\x00\x02atl\x00\x00\x00\x01d\x00\x05hellojd\x00\x02ok" 176 | map_with_list = erlang.binary_to_term(binary) 177 | self.assertEqual(isinstance(map_with_list, dict), True) 178 | def test_binary_to_term_small_tuple(self): 179 | self.assertRaises(erlang.ParseException, 180 | erlang.binary_to_term, b'\x83h') 181 | self.assertRaises(erlang.ParseException, 182 | erlang.binary_to_term, b'\x83h\1') 183 | self.assertEqual((), erlang.binary_to_term(b'\x83h\0')) 184 | self.assertEqual(([], []), erlang.binary_to_term(b'\x83h\2jj')) 185 | def test_binary_to_term_large_tuple(self): 186 | self.assertRaises(erlang.ParseException, 187 | erlang.binary_to_term, b'\x83i') 188 | self.assertRaises(erlang.ParseException, 189 | erlang.binary_to_term, b'\x83i\0') 190 | self.assertRaises(erlang.ParseException, 191 | erlang.binary_to_term, b'\x83i\0\0') 192 | self.assertRaises(erlang.ParseException, 193 | erlang.binary_to_term, b'\x83i\0\0\0') 194 | self.assertRaises(erlang.ParseException, 195 | erlang.binary_to_term, b'\x83i\0\0\0\1') 196 | self.assertEqual((), erlang.binary_to_term(b'\x83i\0\0\0\0')) 197 | self.assertEqual(([], []), erlang.binary_to_term(b'\x83i\0\0\0\2jj')) 198 | def test_binary_to_term_small_integer(self): 199 | self.assertRaises(erlang.ParseException, 200 | erlang.binary_to_term, b'\x83a') 201 | self.assertEqual(0, erlang.binary_to_term(b'\x83a\0')) 202 | self.assertEqual(255, erlang.binary_to_term(b'\x83a\xff')) 203 | def test_binary_to_term_integer(self): 204 | self.assertRaises(erlang.ParseException, 205 | erlang.binary_to_term, b'\x83b') 206 | self.assertRaises(erlang.ParseException, 207 | erlang.binary_to_term, b'\x83b\0') 208 | self.assertRaises(erlang.ParseException, 209 | erlang.binary_to_term, b'\x83b\0\0') 210 | self.assertRaises(erlang.ParseException, 211 | erlang.binary_to_term, b'\x83b\0\0\0') 212 | self.assertEqual(0, erlang.binary_to_term(b'\x83b\0\0\0\0')) 213 | self.assertEqual(2147483647, 214 | erlang.binary_to_term(b'\x83b\x7f\xff\xff\xff')) 215 | self.assertEqual(-2147483648, 216 | erlang.binary_to_term(b'\x83b\x80\0\0\0')) 217 | self.assertEqual(-1, erlang.binary_to_term(b'\x83b\xff\xff\xff\xff')) 218 | def test_binary_to_term_binary(self): 219 | self.assertRaises(erlang.ParseException, 220 | erlang.binary_to_term, b'\x83m') 221 | self.assertRaises(erlang.ParseException, 222 | erlang.binary_to_term, b'\x83m\0') 223 | self.assertRaises(erlang.ParseException, 224 | erlang.binary_to_term, b'\x83m\0\0') 225 | self.assertRaises(erlang.ParseException, 226 | erlang.binary_to_term, b'\x83m\0\0\0') 227 | self.assertRaises(erlang.ParseException, 228 | erlang.binary_to_term, b'\x83m\0\0\0\1') 229 | self.assertEqual(erlang.OtpErlangBinary(b''), 230 | erlang.binary_to_term(b'\x83m\0\0\0\0')) 231 | self.assertEqual(erlang.OtpErlangBinary(b'data'), 232 | erlang.binary_to_term(b'\x83m\0\0\0\4data')) 233 | def test_binary_to_term_float(self): 234 | self.assertRaises(erlang.ParseException, 235 | erlang.binary_to_term, b'\x83F') 236 | self.assertRaises(erlang.ParseException, 237 | erlang.binary_to_term, b'\x83F\0') 238 | self.assertRaises(erlang.ParseException, 239 | erlang.binary_to_term, b'\x83F\0\0') 240 | self.assertRaises(erlang.ParseException, 241 | erlang.binary_to_term, b'\x83F\0\0\0') 242 | self.assertRaises(erlang.ParseException, 243 | erlang.binary_to_term, b'\x83F\0\0\0\0') 244 | self.assertRaises(erlang.ParseException, 245 | erlang.binary_to_term, b'\x83F\0\0\0\0\0') 246 | self.assertRaises(erlang.ParseException, 247 | erlang.binary_to_term, b'\x83F\0\0\0\0\0\0') 248 | self.assertRaises(erlang.ParseException, 249 | erlang.binary_to_term, b'\x83F\0\0\0\0\0\0\0') 250 | self.assertEqual(0.0, erlang.binary_to_term(b'\x83F\0\0\0\0\0\0\0\0')) 251 | self.assertEqual(1.5, erlang.binary_to_term(b'\x83F?\xf8\0\0\0\0\0\0')) 252 | def test_binary_to_term_small_big_integer(self): 253 | self.assertRaises(erlang.ParseException, 254 | erlang.binary_to_term, b'\x83n') 255 | self.assertRaises(erlang.ParseException, 256 | erlang.binary_to_term, b'\x83n\0') 257 | self.assertRaises(erlang.ParseException, 258 | erlang.binary_to_term, b'\x83n\1\0') 259 | self.assertEqual(0, erlang.binary_to_term(b'\x83n\0\0')) 260 | self.assertEqual(6618611909121, 261 | erlang.binary_to_term(b'\x83n\6\0\1\2\3\4\5\6')) 262 | self.assertEqual(-6618611909121, 263 | erlang.binary_to_term(b'\x83n\6\1\1\2\3\4\5\6')) 264 | def test_binary_to_term_big_integer(self): 265 | self.assertRaises(erlang.ParseException, 266 | erlang.binary_to_term, b'\x83o') 267 | self.assertRaises(erlang.ParseException, 268 | erlang.binary_to_term, b'\x83o\0') 269 | self.assertRaises(erlang.ParseException, 270 | erlang.binary_to_term, b'\x83o\0\0') 271 | self.assertRaises(erlang.ParseException, 272 | erlang.binary_to_term, b'\x83o\0\0\0') 273 | self.assertRaises(erlang.ParseException, 274 | erlang.binary_to_term, b'\x83o\0\0\0\0') 275 | self.assertRaises(erlang.ParseException, 276 | erlang.binary_to_term, b'\x83o\0\0\0\1\0') 277 | self.assertEqual(0, erlang.binary_to_term(b'\x83o\0\0\0\0\0')) 278 | self.assertEqual(6618611909121, 279 | erlang.binary_to_term(b'\x83o\0\0\0\6\0\1\2\3\4\5\6')) 280 | self.assertEqual(-6618611909121, 281 | erlang.binary_to_term(b'\x83o\0\0\0\6\1\1\2\3\4\5\6')) 282 | def test_binary_to_term_map(self): 283 | self.assertRaises(erlang.ParseException, 284 | erlang.binary_to_term, b'\x83t') 285 | self.assertRaises(erlang.ParseException, 286 | erlang.binary_to_term, b'\x83t\x00') 287 | self.assertRaises(erlang.ParseException, 288 | erlang.binary_to_term, b'\x83t\x00\x00') 289 | self.assertRaises(erlang.ParseException, 290 | erlang.binary_to_term, b'\x83t\x00\x00\x00') 291 | self.assertRaises(erlang.ParseException, 292 | erlang.binary_to_term, b'\x83t\x00\x00\x00\x01') 293 | self.assertEqual({}, erlang.binary_to_term(b'\x83t\x00\x00\x00\x00')) 294 | map1 = { 295 | erlang.OtpErlangAtom(b'a'): 1, 296 | } 297 | map1_binary = b'\x83t\x00\x00\x00\x01s\x01aa\x01' 298 | self.assertEqual(map1, erlang.binary_to_term(map1_binary)) 299 | map2 = { 300 | erlang.OtpErlangBinary(b'\xA8', 6): 301 | erlang.OtpErlangBinary(b'everything'), 302 | None: 303 | erlang.OtpErlangBinary(b'nothing'), 304 | } 305 | map2_binary = ( 306 | b'\x83\x74\x00\x00\x00\x02\x77\x09\x75\x6E\x64\x65\x66\x69' 307 | b'\x6E\x65\x64\x6D\x00\x00\x00\x07\x6E\x6F\x74\x68\x69\x6E' 308 | b'\x67\x4D\x00\x00\x00\x01\x06\xA8\x6D\x00\x00\x00\x0A\x65' 309 | b'\x76\x65\x72\x79\x74\x68\x69\x6E\x67' 310 | ) 311 | self.assertEqual(map2, erlang.binary_to_term(map2_binary)) 312 | def test_binary_to_term_pid(self): 313 | pid_old_binary = ( 314 | b'\x83\x67\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F' 315 | b'\x68\x6F\x73\x74\x00\x00\x00\x4E\x00\x00\x00\x00\x00' 316 | ) 317 | pid_old = erlang.binary_to_term(pid_old_binary) 318 | self.assertTrue(isinstance(pid_old, erlang.OtpErlangPid)) 319 | self.assertEqual(erlang.term_to_binary(pid_old), 320 | b'\x83gs\rnonode@nohost\x00\x00\x00N' 321 | b'\x00\x00\x00\x00\x00') 322 | pid_new_binary = ( 323 | b'\x83\x58\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68' 324 | b'\x6F\x73\x74\x00\x00\x00\x4E\x00\x00\x00\x00\x00\x00\x00\x00' 325 | ) 326 | pid_new = erlang.binary_to_term(pid_new_binary) 327 | self.assertTrue(isinstance(pid_new, erlang.OtpErlangPid)) 328 | self.assertEqual(erlang.term_to_binary(pid_new), 329 | b'\x83Xs\rnonode@nohost\x00\x00\x00N' 330 | b'\x00\x00\x00\x00\x00\x00\x00\x00') 331 | def test_binary_to_term_port(self): 332 | port_old_binary = ( 333 | b'\x83\x66\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68' 334 | b'\x6F\x73\x74\x00\x00\x00\x06\x00' 335 | ) 336 | port_old = erlang.binary_to_term(port_old_binary) 337 | self.assertTrue(isinstance(port_old, erlang.OtpErlangPort)) 338 | self.assertEqual(erlang.term_to_binary(port_old), 339 | b'\x83fs\rnonode@nohost\x00\x00\x00\x06\x00') 340 | port_new_binary = ( 341 | b'\x83\x59\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68' 342 | b'\x6F\x73\x74\x00\x00\x00\x06\x00\x00\x00\x00' 343 | ) 344 | port_new = erlang.binary_to_term(port_new_binary) 345 | self.assertTrue(isinstance(port_new, erlang.OtpErlangPort)) 346 | self.assertEqual(erlang.term_to_binary(port_new), 347 | b'\x83Ys\rnonode@nohost\x00\x00\x00\x06' 348 | b'\x00\x00\x00\x00') 349 | port_v4_binary = ( 350 | b'\x83\x78\x77\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68\x6F' 351 | b'\x73\x74\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00' 352 | ) 353 | port_v4 = erlang.binary_to_term(port_v4_binary) 354 | self.assertTrue(isinstance(port_v4, erlang.OtpErlangPort)) 355 | self.assertEqual(erlang.term_to_binary(port_v4), port_v4_binary) 356 | def test_binary_to_term_ref(self): 357 | ref_new_binary = ( 358 | b'\x83\x72\x00\x03\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E' 359 | b'\x6F\x68\x6F\x73\x74\x00\x00\x03\xE8\x4E\xE7\x68\x00\x02\xA4' 360 | b'\xC8\x53\x40' 361 | ) 362 | ref_new = erlang.binary_to_term(ref_new_binary) 363 | self.assertTrue(isinstance(ref_new, erlang.OtpErlangReference)) 364 | self.assertEqual(erlang.term_to_binary(ref_new), 365 | b'\x83r\x00\x03s\rnonode@nohost\x00\x00\x03\xe8' 366 | b'N\xe7h\x00\x02\xa4\xc8S@') 367 | ref_newer_binary = ( 368 | b'\x83\x5A\x00\x03\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E' 369 | b'\x6F\x68\x6F\x73\x74\x00\x00\x00\x00\x00\x01\xAC\x03\xC7\x00' 370 | b'\x00\x04\xBB\xB2\xCA\xEE' 371 | ) 372 | ref_newer = erlang.binary_to_term(ref_newer_binary) 373 | self.assertTrue(isinstance(ref_newer, erlang.OtpErlangReference)) 374 | self.assertEqual(erlang.term_to_binary(ref_newer), 375 | b'\x83Z\x00\x03s\rnonode@nohost\x00\x00\x00\x00\x00' 376 | b'\x01\xac\x03\xc7\x00\x00\x04\xbb\xb2\xca\xee') 377 | def test_binary_to_term_compressed_term(self): 378 | self.assertRaises(erlang.ParseException, 379 | erlang.binary_to_term, b'\x83P') 380 | self.assertRaises(erlang.ParseException, 381 | erlang.binary_to_term, b'\x83P\0') 382 | self.assertRaises(erlang.ParseException, 383 | erlang.binary_to_term, b'\x83P\0\0') 384 | self.assertRaises(erlang.ParseException, 385 | erlang.binary_to_term, b'\x83P\0\0\0') 386 | self.assertRaises(erlang.ParseException, 387 | erlang.binary_to_term, b'\x83P\0\0\0\0') 388 | self.assertRaises( 389 | erlang.ParseException, erlang.binary_to_term, 390 | b'\x83P\0\0\0\x16\x78\xda\xcb\x66\x10\x49\xc1\2\0\x5d\x60\x08\x50' 391 | ) 392 | self.assertEqual( 393 | b'd' * 20, 394 | erlang.binary_to_term( 395 | b'\x83P\0\0\0\x17\x78\xda\xcb\x66' 396 | b'\x10\x49\xc1\2\0\x5d\x60\x08\x50' 397 | ) 398 | ) 399 | 400 | class EncodeTestCase(unittest.TestCase): 401 | # pylint: disable=invalid-name 402 | def test_term_to_binary_tuple(self): 403 | self.assertEqual(b'\x83h\0', erlang.term_to_binary(())) 404 | self.assertEqual(b'\x83h\2h\0h\0', erlang.term_to_binary(((), ()))) 405 | self.assertEqual(b'\x83h\xff' + b'h\0' * 255, 406 | erlang.term_to_binary(tuple([()] * 255))) 407 | self.assertEqual(b'\x83i\0\0\1\0' + b'h\0' * 256, 408 | erlang.term_to_binary(tuple([()] * 256))) 409 | def test_term_to_binary_empty_list(self): 410 | self.assertEqual(b'\x83j', erlang.term_to_binary([])) 411 | def test_term_to_binary_string_list(self): 412 | self.assertEqual(b'\x83j', erlang.term_to_binary('')) 413 | self.assertEqual(b'\x83k\0\1\0', erlang.term_to_binary('\0')) 414 | value = ( 415 | b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r' 416 | b'\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a' 417 | b'\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>' 418 | b'?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopq' 419 | b'rstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88' 420 | b'\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95' 421 | b'\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2' 422 | b'\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf' 423 | b'\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc' 424 | b'\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9' 425 | b'\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6' 426 | b'\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3' 427 | b'\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0' 428 | b'\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff' 429 | ) 430 | self.assertEqual(b'\x83k\1\0' + value, erlang.term_to_binary(value)) 431 | def test_term_to_binary_list_basic(self): 432 | self.assertEqual(b'\x83\x6A', erlang.term_to_binary([])) 433 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x6A\x6A', 434 | erlang.term_to_binary([''])) 435 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x61\x01\x6A', 436 | erlang.term_to_binary([1])) 437 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x61\xFF\x6A', 438 | erlang.term_to_binary([255])) 439 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x62\x00\x00\x01\x00\x6A', 440 | erlang.term_to_binary([256])) 441 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x62\x7F\xFF\xFF\xFF\x6A', 442 | erlang.term_to_binary([2147483647])) 443 | self.assertEqual( 444 | b'\x83\x6C\x00\x00\x00\x01\x6E\x04\x00\x00\x00\x00\x80\x6A', 445 | erlang.term_to_binary([2147483648]) 446 | ) 447 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x61\x00\x6A', 448 | erlang.term_to_binary([0])) 449 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x62\xFF\xFF\xFF\xFF\x6A', 450 | erlang.term_to_binary([-1])) 451 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x62\xFF\xFF\xFF\x00\x6A', 452 | erlang.term_to_binary([-256])) 453 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x62\xFF\xFF\xFE\xFF\x6A', 454 | erlang.term_to_binary([-257])) 455 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x62\x80\x00\x00\x00\x6A', 456 | erlang.term_to_binary([-2147483648])) 457 | self.assertEqual( 458 | b'\x83\x6C\x00\x00\x00\x01\x6E\x04\x01\x01\x00\x00\x80\x6A', 459 | erlang.term_to_binary([-2147483649]) 460 | ) 461 | self.assertEqual( 462 | b'\x83\x6C\x00\x00\x00\x01\x6B\x00\x04\x74\x65\x73\x74\x6A', 463 | erlang.term_to_binary(['test']) 464 | ) 465 | self.assertEqual( 466 | b'\x83\x6C\x00\x00\x00\x02\x62\x00\x00\x01\x75\x62\x00\x00' 467 | b'\x01\xC7\x6A', 468 | erlang.term_to_binary([373, 455]) 469 | ) 470 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x01\x6A\x6A', 471 | erlang.term_to_binary([[]])) 472 | self.assertEqual(b'\x83\x6C\x00\x00\x00\x02\x6A\x6A\x6A', 473 | erlang.term_to_binary([[], []])) 474 | self.assertEqual( 475 | b'\x83\x6C\x00\x00\x00\x03\x6C\x00\x00\x00\x02\x6B\x00\x04\x74\x68' 476 | b'\x69\x73\x6B\x00\x02\x69\x73\x6A\x6C\x00\x00\x00\x01\x6C\x00\x00' 477 | b'\x00\x01\x6B\x00\x01\x61\x6A\x6A\x6B\x00\x04\x74\x65\x73\x74\x6A', 478 | erlang.term_to_binary([['this', 'is'], [['a']], 'test']) 479 | ) 480 | def test_term_to_binary_list(self): 481 | self.assertEqual(b'\x83l\0\0\0\1jj', erlang.term_to_binary([[]])) 482 | self.assertEqual(b'\x83l\0\0\0\5jjjjjj', 483 | erlang.term_to_binary([[], [], [], [], []])) 484 | self.assertEqual( 485 | b'\x83l\0\0\0\5jjjjjj', 486 | erlang.term_to_binary(erlang.OtpErlangList([ 487 | erlang.OtpErlangList([]), 488 | erlang.OtpErlangList([]), 489 | erlang.OtpErlangList([]), 490 | erlang.OtpErlangList([]), 491 | erlang.OtpErlangList([]) 492 | ])) 493 | ) 494 | def test_term_to_binary_improper_list(self): 495 | self.assertEqual( 496 | b'\x83l\0\0\0\1h\0h\0', 497 | erlang.term_to_binary( 498 | erlang.OtpErlangList([(), ()], improper=True) 499 | ) 500 | ) 501 | self.assertEqual( 502 | b'\x83l\0\0\0\1a\0a\1', 503 | erlang.term_to_binary( 504 | erlang.OtpErlangList([0, 1], improper=True) 505 | ) 506 | ) 507 | def test_term_to_binary_unicode(self): 508 | self.assertEqual(b'\x83j', erlang.term_to_binary('')) 509 | self.assertEqual(b'\x83k\0\4test', erlang.term_to_binary('test')) 510 | self.assertEqual(b'\x83k\0\3\x00\xc3\xbf', 511 | erlang.term_to_binary(b'\x00\xc3\xbf')) 512 | self.assertEqual(b'\x83k\0\2\xc4\x80', 513 | erlang.term_to_binary(b'\xc4\x80')) 514 | self.assertEqual( 515 | b'\x83k\0\x08\xd1\x82\xd0\xb5\xd1\x81\xd1\x82', 516 | erlang.term_to_binary(b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82') 517 | ) 518 | # becomes a list of small integers 519 | self.assertEqual( 520 | b'\x83l\x00\x02\x00\x00' + (b'a\xd0a\x90' * 65536) + b'j', 521 | erlang.term_to_binary(b'\xd0\x90' * 65536) 522 | ) 523 | def test_term_to_binary_atom(self): 524 | self.assertEqual(b'\x83s\0', 525 | erlang.term_to_binary(erlang.OtpErlangAtom(b''))) 526 | self.assertEqual( 527 | b'\x83s\4test', 528 | erlang.term_to_binary(erlang.OtpErlangAtom(b'test')) 529 | ) 530 | def test_term_to_binary_string_basic(self): 531 | self.assertEqual(b'\x83\x6A', erlang.term_to_binary('')) 532 | self.assertEqual(b'\x83\x6B\x00\x04\x74\x65\x73\x74', 533 | erlang.term_to_binary('test')) 534 | self.assertEqual( 535 | b'\x83\x6B\x00\x09\x74\x77\x6F\x20\x77\x6F\x72\x64\x73', 536 | erlang.term_to_binary('two words') 537 | ) 538 | self.assertEqual( 539 | b'\x83\x6B\x00\x16\x74\x65\x73\x74\x69\x6E\x67\x20\x6D\x75\x6C\x74' 540 | b'\x69\x70\x6C\x65\x20\x77\x6F\x72\x64\x73', 541 | erlang.term_to_binary('testing multiple words') 542 | ) 543 | self.assertEqual(b'\x83\x6B\x00\x01\x20', 544 | erlang.term_to_binary(' ')) 545 | self.assertEqual(b'\x83\x6B\x00\x02\x20\x20', 546 | erlang.term_to_binary(' ')) 547 | self.assertEqual(b'\x83\x6B\x00\x01\x31', 548 | erlang.term_to_binary('1')) 549 | self.assertEqual(b'\x83\x6B\x00\x02\x33\x37', 550 | erlang.term_to_binary('37')) 551 | self.assertEqual(b'\x83\x6B\x00\x07\x6F\x6E\x65\x20\x3D\x20\x31', 552 | erlang.term_to_binary('one = 1')) 553 | self.assertEqual( 554 | b'\x83\x6B\x00\x20\x21\x40\x23\x24\x25\x5E\x26\x2A\x28\x29\x5F\x2B' 555 | b'\x2D\x3D\x5B\x5D\x7B\x7D\x5C\x7C\x3B\x27\x3A\x22\x2C\x2E\x2F\x3C' 556 | b'\x3E\x3F\x7E\x60', 557 | erlang.term_to_binary('!@#$%^&*()_+-=[]{}\\|;\':",./<>?~`') 558 | ) 559 | self.assertEqual( 560 | b'\x83\x6B\x00\x09\x22\x08\x0C\x0A\x0D\x09\x0B\x53\x12', 561 | erlang.term_to_binary('\"\b\f\n\r\t\v\123\x12') 562 | ) 563 | def test_term_to_binary_string(self): 564 | self.assertEqual(b'\x83j', erlang.term_to_binary('')) 565 | self.assertEqual(b'\x83k\0\4test', erlang.term_to_binary('test')) 566 | def test_term_to_binary_predefined_atom(self): 567 | self.assertEqual(b'\x83w\4true', erlang.term_to_binary(True)) 568 | self.assertEqual(b'\x83w\5false', erlang.term_to_binary(False)) 569 | self.assertEqual(b'\x83w\11undefined', erlang.term_to_binary(None)) 570 | def test_term_to_binary_short_integer(self): 571 | self.assertEqual(b'\x83a\0', erlang.term_to_binary(0)) 572 | self.assertEqual(b'\x83a\xff', erlang.term_to_binary(255)) 573 | def test_term_to_binary_integer(self): 574 | self.assertEqual(b'\x83b\xff\xff\xff\xff', erlang.term_to_binary(-1)) 575 | self.assertEqual(b'\x83b\x80\0\0\0', 576 | erlang.term_to_binary(-2147483648)) 577 | self.assertEqual(b'\x83b\0\0\1\0', erlang.term_to_binary(256)) 578 | self.assertEqual(b'\x83b\x7f\xff\xff\xff', 579 | erlang.term_to_binary(2147483647)) 580 | def test_term_to_binary_long_integer(self): 581 | self.assertEqual(b'\x83n\4\0\0\0\0\x80', 582 | erlang.term_to_binary(2147483648)) 583 | self.assertEqual(b'\x83n\4\1\1\0\0\x80', 584 | erlang.term_to_binary(-2147483649)) 585 | self.assertEqual(b'\x83o\0\0\1\0\0' + b'\0' * 255 + b'\1', 586 | erlang.term_to_binary(2 ** 2040)) 587 | self.assertEqual(b'\x83o\0\0\1\0\1' + b'\0' * 255 + b'\1', 588 | erlang.term_to_binary(-2 ** 2040)) 589 | def test_term_to_binary_float(self): 590 | self.assertEqual(b'\x83F\0\0\0\0\0\0\0\0', erlang.term_to_binary(0.0)) 591 | self.assertEqual(b'\x83F?\xe0\0\0\0\0\0\0', erlang.term_to_binary(0.5)) 592 | self.assertEqual(b'\x83F\xbf\xe0\0\0\0\0\0\0', 593 | erlang.term_to_binary(-0.5)) 594 | self.assertEqual(b'\x83F@\t!\xfbM\x12\xd8J', 595 | erlang.term_to_binary(3.1415926)) 596 | self.assertEqual(b'\x83F\xc0\t!\xfbM\x12\xd8J', 597 | erlang.term_to_binary(-3.1415926)) 598 | def test_term_to_binary_map(self): 599 | self.assertEqual(b'\x83t\x00\x00\x00\x00', erlang.term_to_binary({})) 600 | map1 = { 601 | erlang.OtpErlangAtom(b'a'): 1, 602 | } 603 | map1_binary = b'\x83t\x00\x00\x00\x01s\x01aa\x01' 604 | self.assertEqual(map1_binary, erlang.term_to_binary(map1)) 605 | map2 = OrderedDict([ 606 | (erlang.OtpErlangAtom(u'undefined'), 607 | erlang.OtpErlangBinary(b'nothing')), 608 | (erlang.OtpErlangBinary(b'\xA8', 6), 609 | erlang.OtpErlangBinary(b'everything')), 610 | ]) 611 | map2_binary = ( 612 | b'\x83\x74\x00\x00\x00\x02\x77\x09\x75\x6E\x64\x65\x66\x69' 613 | b'\x6E\x65\x64\x6D\x00\x00\x00\x07\x6E\x6F\x74\x68\x69\x6E' 614 | b'\x67\x4D\x00\x00\x00\x01\x06\xA8\x6D\x00\x00\x00\x0A\x65' 615 | b'\x76\x65\x72\x79\x74\x68\x69\x6E\x67' 616 | ) 617 | self.assertEqual(map2_binary, erlang.term_to_binary(map2)) 618 | def test_term_to_binary_compressed_term(self): 619 | self.assertEqual(b'\x83P\x00\x00\x00\x15' 620 | b'x\x9c\xcba``\xe0\xcfB\x03\x00B@\x07\x1c', 621 | erlang.term_to_binary([[]] * 15, compressed=True)) 622 | self.assertEqual(b'\x83P\x00\x00\x00\x15' 623 | b'x\x9c\xcba``\xe0\xcfB\x03\x00B@\x07\x1c', 624 | erlang.term_to_binary([[]] * 15, compressed=6)) 625 | self.assertEqual(b'\x83P\x00\x00\x00\x15' 626 | b'x\xda\xcba``\xe0\xcfB\x03\x00B@\x07\x1c', 627 | erlang.term_to_binary([[]] * 15, compressed=9)) 628 | self.assertEqual(b'\x83P\x00\x00\x00\x15' 629 | b'x\x01\x01\x15\x00\xea\xffl\x00\x00\x00' 630 | b'\x0fjjjjjjjjjjjjjjjjB@\x07\x1c', 631 | erlang.term_to_binary([[]] * 15, compressed=0)) 632 | self.assertEqual(b'\x83P\x00\x00\x00\x15' 633 | b'x\x01\xcba``\xe0\xcfB\x03\x00B@\x07\x1c', 634 | erlang.term_to_binary([[]] * 15, 1)) 635 | self.assertEqual(b'\x83P\0\0\0\x17\x78\xda\xcb\x66' 636 | b'\x10\x49\xc1\2\0\x5d\x60\x08\x50', 637 | erlang.term_to_binary('d' * 20, compressed=9)) 638 | 639 | def get_suite(): 640 | load = unittest.TestLoader().loadTestsFromTestCase 641 | suite = unittest.TestSuite() 642 | suite.addTests(load(AtomTestCase)) 643 | suite.addTests(load(ListTestCase)) 644 | suite.addTests(load(ImproperListTestCase)) 645 | suite.addTests(load(DecodeTestCase)) 646 | suite.addTests(load(EncodeTestCase)) 647 | return suite 648 | 649 | def main(): 650 | if coverage is None: 651 | unittest.main() 652 | else: 653 | cov = coverage.coverage() 654 | cov.start() 655 | unittest.main() 656 | cov.stop() 657 | cov.save() 658 | modules = [erlang.__file__, __file__] 659 | cov.report(morfs=modules, show_missing=False) 660 | cov.html_report(morfs=modules, directory='.cover') 661 | 662 | if __name__ == '__main__': 663 | main() 664 | --------------------------------------------------------------------------------