Skip to content

Commit 13c6c79

Browse files
authored
Merge pull request #88 from dlech/protocol-tests
Protocol tests
2 parents eb42491 + 4217633 commit 13c6c79

File tree

7 files changed

+374
-10
lines changed

7 files changed

+374
-10
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ cache: pip
55

66
python:
77
- 2.7
8-
- 3.4
98
- 3.5
109
- 3.6
1110
- 3.7

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
'License :: OSI Approved :: MIT License',
3333
'Operating System :: POSIX',
3434
'Programming Language :: Python :: 2.7',
35-
'Programming Language :: Python :: 3.4',
3635
'Programming Language :: Python :: 3.5',
3736
'Programming Language :: Python :: 3.6',
3837
'Programming Language :: Python :: 3.7',

tests/test_protocol.py

Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
import struct
2+
3+
from twisted.trial import unittest
4+
5+
from txdbus import protocol
6+
7+
8+
class TestTransport:
9+
def __init__(self):
10+
self.data = b''
11+
self.fds = []
12+
13+
def sendFileDescriptor(self, fd):
14+
# twisted queues fds to be sent later
15+
self.fds.append(fd)
16+
17+
def write(self, data):
18+
self.data += data
19+
return len(data)
20+
21+
22+
class TestProtocol(protocol.BasicDBusProtocol):
23+
def __init__(self):
24+
self._receivedFDs = []
25+
self._toBeSentFDs = []
26+
self.transport = TestTransport()
27+
self.mcalls = []
28+
self.mrets = []
29+
self.merrs = []
30+
self.msigs = []
31+
32+
def methodCallReceived(self, mcall):
33+
self.mcalls.append(mcall)
34+
35+
def methodReturnReceived(self, mret):
36+
self.mrets.append(mret)
37+
38+
def errorReceived(self, merr):
39+
self.merrs.append(merr)
40+
41+
def signalReceived(self, msig):
42+
self.msigs.append(msig)
43+
44+
45+
class Endian:
46+
LITTLE = ord('l')
47+
BIG = ord('B')
48+
49+
50+
class MsgType:
51+
INVALID = 0
52+
METHOD_CALL = 1
53+
METHOD_RETURN = 2
54+
ERROR = 3
55+
SIGNAL = 4
56+
57+
58+
class Flags:
59+
NONE = 0x0
60+
NO_REPLY_EXPECTED = 0x1
61+
NO_AUTO_START = 0x2
62+
ALLOW_INTERACTIVE_AUTHORIZATION = 0x4
63+
64+
65+
class Version:
66+
ONE = 1
67+
68+
69+
class DataType:
70+
INVALID = 0
71+
BYTE = ord('y')
72+
BOOLEAN = ord('b')
73+
INT16 = ord('n')
74+
UINT16 = ord('q')
75+
INT32 = ord('i')
76+
UINT32 = ord('u')
77+
INT64 = ord('x')
78+
UINT64 = ord('t')
79+
DOUBLE = ord('d')
80+
STRING = ord('s')
81+
OBJECT_PATH = ord('o')
82+
SIGNATURE = ord('g')
83+
ARRAY = ord('a')
84+
STRUCT_BEGIN = ord('(')
85+
STRUCT_END = ord(')')
86+
VARIANT = ord('v')
87+
DICT_ENTRY = ord('e')
88+
UNIX_FD = ord('h')
89+
90+
91+
class HeaderField:
92+
INVALID = 0
93+
PATH = 1
94+
INTERFACE = 2
95+
MEMBER = 3
96+
ERROR_NAME = 4
97+
REPLY_SERIAL = 5
98+
DESTINATION = 6
99+
SENDER = 7
100+
SIGNATURE = 8
101+
UNIX_FDS = 9
102+
103+
104+
def encode_signature(*data_types):
105+
data = bytearray(data_types)
106+
data.append(0)
107+
data.insert(0, len(data_types))
108+
return data
109+
110+
111+
def align(align, offset):
112+
count = offset % align
113+
if not count:
114+
return b''
115+
return b'\x00' * (align - count)
116+
117+
118+
def create_basic_method(path, member):
119+
"""Creates raw D-Bus message` with `path` for method `member having no
120+
parameters
121+
"""
122+
serial = 1
123+
path = path.encode()
124+
member = member.encode()
125+
126+
headers = bytearray()
127+
headers.append(HeaderField.PATH)
128+
headers += encode_signature(DataType.OBJECT_PATH)
129+
headers += align(4, len(headers))
130+
headers += struct.pack('<I', len(path))
131+
headers += path
132+
headers.append(0)
133+
headers += align(8, len(headers))
134+
135+
headers.append(HeaderField.MEMBER)
136+
headers += encode_signature(DataType.STRING)
137+
headers += align(4, len(headers))
138+
headers += struct.pack('<I', len(member))
139+
headers += member
140+
headers.append(0)
141+
142+
body = bytearray()
143+
144+
data = bytearray()
145+
data.append(Endian.LITTLE)
146+
data.append(MsgType.METHOD_CALL)
147+
data.append(Flags.NONE)
148+
data.append(Version.ONE)
149+
data += struct.pack('<I', len(body))
150+
data += struct.pack('<I', serial)
151+
data += struct.pack('<I', len(headers))
152+
data += headers
153+
data += align(8, len(data))
154+
data += body
155+
156+
return bytes(data)
157+
158+
159+
def create_one_unix_fd_method(path, member, fd_index):
160+
"""Creates raw D-Bus message` with `path` for method `member having one
161+
UNIX file descriptor input parameter
162+
"""
163+
164+
serial = 1
165+
path = path.encode()
166+
member = member.encode()
167+
signature = bytearray([DataType.UNIX_FD])
168+
num_fds = 1
169+
170+
headers = bytearray()
171+
headers.append(HeaderField.PATH)
172+
headers += encode_signature(DataType.OBJECT_PATH)
173+
headers += align(4, len(headers))
174+
headers += struct.pack('<I', len(path))
175+
headers += path
176+
headers.append(0)
177+
headers += align(8, len(headers))
178+
179+
headers.append(HeaderField.MEMBER)
180+
headers += encode_signature(DataType.STRING)
181+
headers += align(4, len(headers))
182+
headers += struct.pack('<I', len(member))
183+
headers += member
184+
headers.append(0)
185+
headers += align(8, len(headers))
186+
187+
headers.append(HeaderField.SIGNATURE)
188+
headers += encode_signature(DataType.SIGNATURE)
189+
headers += struct.pack('B', len(signature))
190+
headers += signature
191+
headers.append(0)
192+
headers += align(8, len(headers))
193+
194+
headers.append(HeaderField.UNIX_FDS)
195+
headers += encode_signature(DataType.UINT32)
196+
headers += align(4, len(headers))
197+
headers += struct.pack('<I', num_fds)
198+
199+
body = bytearray()
200+
body += struct.pack('<I', fd_index)
201+
202+
data = bytearray()
203+
data.append(Endian.LITTLE)
204+
data.append(MsgType.METHOD_CALL)
205+
data.append(Flags.NONE)
206+
data.append(Version.ONE)
207+
data += struct.pack('<I', len(body))
208+
data += struct.pack('<I', serial)
209+
data += struct.pack('<I', len(headers))
210+
data += headers
211+
data += align(8, len(data))
212+
data += body
213+
214+
return bytes(data)
215+
216+
217+
class ProtocolTester(unittest.TestCase):
218+
def test_dataReceived_simple_method_call(self):
219+
p = TestProtocol()
220+
p._authenticated = True
221+
222+
path = '/test/path'
223+
member = 'testMethod'
224+
data = create_basic_method(path, member)
225+
226+
self.assertIsNone(p.dataReceived(data))
227+
self.assertEqual(len(p.mcalls), 1)
228+
self.assertEqual(p.mcalls[0].path, path)
229+
self.assertEqual(p.mcalls[0].member, member)
230+
self.assertIsNone(p.mcalls[0].body)
231+
self.assertEqual(len(p.mrets), 0)
232+
self.assertEqual(len(p.merrs), 0)
233+
self.assertEqual(len(p.msigs), 0)
234+
235+
def test_dataReceived_method_call_with_unix_fd(self):
236+
p = TestProtocol()
237+
p._authenticated = True
238+
239+
path = '/test/path'
240+
member = 'testMethod'
241+
fd = 99
242+
data = create_one_unix_fd_method(path, member, 0)
243+
244+
p.fileDescriptorReceived(fd)
245+
self.assertIsNone(p.dataReceived(data))
246+
self.assertEqual(len(p.mcalls), 1)
247+
self.assertEqual(p.mcalls[0].path, path)
248+
self.assertEqual(p.mcalls[0].member, member)
249+
self.assertEqual(p.mcalls[0].body, [fd])
250+
self.assertEqual(len(p.mrets), 0)
251+
self.assertEqual(len(p.merrs), 0)
252+
self.assertEqual(len(p.msigs), 0)
253+
254+
def test_dataReceived_method_call_with_unix_fd_race(self):
255+
p = TestProtocol()
256+
p._authenticated = True
257+
258+
# message 1
259+
path1 = '/test/path1'
260+
member1 = 'testMethod1'
261+
fd1 = 99
262+
data1 = create_one_unix_fd_method(path1, member1, 0)
263+
264+
# message 2
265+
path2 = '/test/path2'
266+
member2 = 'testMethod2'
267+
data2 = create_basic_method(path2, member2)
268+
269+
# possible race condition, file descriptor for message 1 is received
270+
p.fileDescriptorReceived(fd1)
271+
# then unrelated message 2 is received
272+
self.assertIsNone(p.dataReceived(data2))
273+
# then message 1, which is expecting file descriptor, is received
274+
self.assertIsNone(p.dataReceived(data1))
275+
276+
self.assertEqual(len(p.mcalls), 2)
277+
self.assertEqual(p.mcalls[0].path, path2)
278+
self.assertEqual(p.mcalls[0].member, member2)
279+
self.assertIsNone(p.mcalls[0].body)
280+
self.assertEqual(p.mcalls[1].path, path1)
281+
self.assertEqual(p.mcalls[1].member, member1)
282+
self.assertEqual(p.mcalls[1].body, [fd1])
283+
self.assertEqual(len(p.mrets), 0)
284+
self.assertEqual(len(p.merrs), 0)
285+
self.assertEqual(len(p.msigs), 0)
286+
287+
def test_dataReceived_method_call_with_unix_fd_race2(self):
288+
p = TestProtocol()
289+
p._authenticated = True
290+
291+
# message 1
292+
path1 = '/test/path1'
293+
member1 = 'testMethod1'
294+
fd1 = 99
295+
data1 = create_one_unix_fd_method(path1, member1, 0)
296+
297+
# message 2
298+
path2 = '/test/path2'
299+
member2 = 'testMethod2'
300+
fd2 = 88
301+
data2 = create_one_unix_fd_method(path2, member2, 0)
302+
303+
# possible race condition, file descriptor for message 1 is received
304+
p.fileDescriptorReceived(fd1)
305+
# then file descriptor for message 2 is received
306+
p.fileDescriptorReceived(fd2)
307+
# then message 1 is received
308+
self.assertIsNone(p.dataReceived(data1))
309+
# then message 2 is received
310+
self.assertIsNone(p.dataReceived(data2))
311+
312+
self.assertEqual(len(p.mcalls), 2)
313+
self.assertEqual(p.mcalls[0].path, path1)
314+
self.assertEqual(p.mcalls[0].member, member1)
315+
self.assertEqual(p.mcalls[0].body, [fd1])
316+
self.assertEqual(p.mcalls[1].path, path2)
317+
self.assertEqual(p.mcalls[1].member, member2)
318+
self.assertEqual(p.mcalls[1].body, [fd2])
319+
self.assertEqual(len(p.mrets), 0)
320+
self.assertEqual(len(p.merrs), 0)
321+
self.assertEqual(len(p.msigs), 0)
322+
323+
def test_dataReceived_method_call_with_unix_fd_race3(self):
324+
raise unittest.SkipTest('It is not possible to fix this in txdbus - '
325+
'it must be fixed in twisted.')
326+
327+
p = TestProtocol()
328+
p._authenticated = True
329+
330+
# message 1
331+
path1 = '/test/path1'
332+
member1 = 'testMethod1'
333+
fd1 = 99
334+
data1 = create_one_unix_fd_method(path1, member1, 0)
335+
336+
# message 2
337+
path2 = '/test/path2'
338+
member2 = 'testMethod2'
339+
fd2 = 88
340+
data2 = create_one_unix_fd_method(path2, member2, 0)
341+
342+
# possible race condition, file descriptor for message 1 is received
343+
p.fileDescriptorReceived(fd1)
344+
# then file descriptor for message 2 is received
345+
p.fileDescriptorReceived(fd2)
346+
# then message 2 is received
347+
self.assertIsNone(p.dataReceived(data2))
348+
# then message 1 is received
349+
self.assertIsNone(p.dataReceived(data1))
350+
351+
self.assertEqual(len(p.mcalls), 2)
352+
self.assertEqual(p.mcalls[0].path, path2)
353+
self.assertEqual(p.mcalls[0].member, member2)
354+
self.assertEqual(p.mcalls[0].body, [fd2])
355+
self.assertEqual(p.mcalls[1].path, path1)
356+
self.assertEqual(p.mcalls[1].member, member1)
357+
self.assertEqual(p.mcalls[1].body, [fd1])
358+
self.assertEqual(len(p.mrets), 0)
359+
self.assertEqual(len(p.merrs), 0)
360+
self.assertEqual(len(p.msigs), 0)

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py27, py34, py35, py36, py37
2+
envlist = py27, py35, py36, py37
33
skip_missing_interpreters = True
44

55
[testenv]

txdbus/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ def callRemote(self, objectPath, methodName,
556556
body=body,
557557
expectReply=expectReply,
558558
autoStart=autoStart,
559-
oobFDs=self._toBeSentFDs,
559+
oobFDs=[],
560560
)
561561

562562
d = self.callRemoteMessage(mcall, timeout)

0 commit comments

Comments
 (0)