Skip to content

Commit 96f580d

Browse files
committed
Add V4_PORT_EXT support. Add related tests.
1 parent 16343d2 commit 96f580d

File tree

3 files changed

+118
-39
lines changed

3 files changed

+118
-39
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2011-2022 Michael Truog <mjtruog at protonmail dot com>
3+
Copyright (c) 2011-2023 Michael Truog <mjtruog at protonmail dot com>
44

55
Permission is hereby granted, free of charge, to any person obtaining a
66
copy of this software and associated documentation files (the "Software"),

erlang.py

Lines changed: 58 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
# MIT License
66
#
7-
# Copyright (c) 2011-2022 Michael Truog <mjtruog at protonmail dot com>
7+
# Copyright (c) 2011-2023 Michael Truog <mjtruog at protonmail dot com>
88
#
99
# Permission is hereby granted, free of charge, to any person obtaining a
1010
# copy of this software and associated documentation files (the "Software"),
@@ -27,6 +27,7 @@
2727
"""
2828
Erlang External Term Format Encoding/Decoding
2929
"""
30+
# pylint: disable=too-many-lines
3031

3132
import sys
3233
import struct
@@ -62,11 +63,11 @@ def b_ord(character):
6263

6364
__all__ = ['OtpErlangAtom',
6465
'OtpErlangBinary',
65-
'OtpErlangFunction',
6666
'OtpErlangList',
6767
'OtpErlangPid',
6868
'OtpErlangPort',
6969
'OtpErlangReference',
70+
'OtpErlangFunction',
7071
'binary_to_term',
7172
'term_to_binary',
7273
'set_undefined',
@@ -108,6 +109,8 @@ def b_ord(character):
108109
_TAG_FUN_EXT = 117
109110
_TAG_ATOM_UTF8_EXT = 118
110111
_TAG_SMALL_ATOM_UTF8_EXT = 119
112+
_TAG_V4_PORT_EXT = 120
113+
_TAG_LOCAL_EXT = 121
111114

112115
# Erlang term classes listed alphabetically
113116

@@ -197,30 +200,6 @@ def __hash__(self):
197200
def __eq__(self, other):
198201
return self.binary() == other.binary()
199202

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-
224203
class OtpErlangList(object):
225204
"""
226205
OtpErlangList
@@ -318,17 +297,23 @@ def binary(self):
318297
"""
319298
return encoded representation
320299
"""
321-
creation_size = len(self.creation)
322-
if creation_size == 1:
300+
id_size = len(self.id)
301+
if id_size == 8:
323302
return (
324-
b_chr(_TAG_PORT_EXT) +
303+
b_chr(_TAG_V4_PORT_EXT) +
325304
self.node.binary() + self.id + self.creation
326305
)
306+
creation_size = len(self.creation)
327307
if creation_size == 4:
328308
return (
329309
b_chr(_TAG_NEW_PORT_EXT) +
330310
self.node.binary() + self.id + self.creation
331311
)
312+
if creation_size == 1:
313+
return (
314+
b_chr(_TAG_PORT_EXT) +
315+
self.node.binary() + self.id + self.creation
316+
)
332317
raise OutputException('unknown port type')
333318
def __repr__(self):
334319
return '%s(%s,%s,%s)' % (
@@ -387,6 +372,30 @@ def __hash__(self):
387372
def __eq__(self, other):
388373
return self.binary() == other.binary()
389374

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+
390399
# dependency to support Erlang maps as map keys in python
391400

392401
class frozendict(dict):
@@ -505,19 +514,25 @@ def _binary_to_term(i, data):
505514
if tag == _TAG_FLOAT_EXT:
506515
value = float(data[i:i + 31].partition(b_chr(0))[0])
507516
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):
509519
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):
513527
creation = data[i:i + 4]
514528
i += 4
515529
else:
516530
creation = data[i:i + 1]
517531
i += 1
518532
if tag == _TAG_REFERENCE_EXT:
519533
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
521536
return (i, OtpErlangPort(node, id_value, creation))
522537
if tag in (_TAG_NEW_PID_EXT, _TAG_PID_EXT):
523538
i, node = _binary_to_atom(i, data)
@@ -607,9 +622,9 @@ def _binary_to_term(i, data):
607622
def to_immutable(value):
608623
if isinstance(value, dict):
609624
return frozendict(key)
610-
elif isinstance(value, list):
625+
if isinstance(value, list):
611626
return OtpErlangList(value)
612-
elif isinstance(value, tuple):
627+
if isinstance(value, tuple):
613628
return tuple(to_immutable(v) for v in value)
614629
return value
615630

@@ -674,6 +689,8 @@ def to_immutable(value):
674689
if i_new != size_uncompressed:
675690
raise ParseException('unparsed data')
676691
return (i + j, term)
692+
if tag == _TAG_LOCAL_EXT:
693+
raise ParseException('LOCAL_EXT is opaque')
677694
raise ParseException('invalid tag')
678695

679696
def _binary_to_term_sequence(i, length, data):
@@ -870,8 +887,12 @@ def _bignum_to_binary(term):
870887
def _float_to_binary(term):
871888
return b_chr(_TAG_NEW_FLOAT_EXT) + struct.pack(b'>d', term)
872889

873-
# Elixir use can set to b'nil'
874890
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
875896
assert isinstance(value, bytes)
876897
global _UNDEFINED
877898
_UNDEFINED = value

tests/erlang_tests.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
# MIT License
66
#
7-
# Copyright (c) 2014-2022 Michael Truog <mjtruog at protonmail dot com>
7+
# Copyright (c) 2014-2023 Michael Truog <mjtruog at protonmail dot com>
88
# Copyright (c) 2009-2013 Dmitry Vasiliev <dima@hlabs.org>
99
#
1010
# Permission is hereby granted, free of charge, to any person obtaining a
@@ -40,6 +40,7 @@
4040
)
4141
import unittest
4242
import erlang
43+
from collections import OrderedDict
4344
try:
4445
import coverage
4546
except ImportError:
@@ -278,6 +279,36 @@ def test_binary_to_term_big_integer(self):
278279
erlang.binary_to_term(b'\x83o\0\0\0\6\0\1\2\3\4\5\6'))
279280
self.assertEqual(-6618611909121,
280281
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))
281312
def test_binary_to_term_pid(self):
282313
pid_old_binary = (
283314
b'\x83\x67\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F'
@@ -315,6 +346,13 @@ def test_binary_to_term_port(self):
315346
self.assertEqual(erlang.term_to_binary(port_new),
316347
b'\x83Ys\rnonode@nohost\x00\x00\x00\x06'
317348
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)
318356
def test_binary_to_term_ref(self):
319357
ref_new_binary = (
320358
b'\x83\x72\x00\x03\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E'
@@ -557,6 +595,26 @@ def test_term_to_binary_float(self):
557595
erlang.term_to_binary(3.1415926))
558596
self.assertEqual(b'\x83F\xc0\t!\xfbM\x12\xd8J',
559597
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))
560618
def test_term_to_binary_compressed_term(self):
561619
self.assertEqual(b'\x83P\x00\x00\x00\x15'
562620
b'x\x9c\xcba``\xe0\xcfB\x03\x00B@\x07\x1c',

0 commit comments

Comments
 (0)