Skip to content

Commit

Permalink
Merge pull request #88 from dlech/protocol-tests
Browse files Browse the repository at this point in the history
Protocol tests
  • Loading branch information
cocagne authored Oct 30, 2019
2 parents eb42491 + 4217633 commit 13c6c79
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 10 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ cache: pip

python:
- 2.7
- 3.4
- 3.5
- 3.6
- 3.7
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
'License :: OSI Approved :: MIT License',
'Operating System :: POSIX',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
Expand Down
360 changes: 360 additions & 0 deletions tests/test_protocol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
import struct

from twisted.trial import unittest

from txdbus import protocol


class TestTransport:
def __init__(self):
self.data = b''
self.fds = []

def sendFileDescriptor(self, fd):
# twisted queues fds to be sent later
self.fds.append(fd)

def write(self, data):
self.data += data
return len(data)


class TestProtocol(protocol.BasicDBusProtocol):
def __init__(self):
self._receivedFDs = []
self._toBeSentFDs = []
self.transport = TestTransport()
self.mcalls = []
self.mrets = []
self.merrs = []
self.msigs = []

def methodCallReceived(self, mcall):
self.mcalls.append(mcall)

def methodReturnReceived(self, mret):
self.mrets.append(mret)

def errorReceived(self, merr):
self.merrs.append(merr)

def signalReceived(self, msig):
self.msigs.append(msig)


class Endian:
LITTLE = ord('l')
BIG = ord('B')


class MsgType:
INVALID = 0
METHOD_CALL = 1
METHOD_RETURN = 2
ERROR = 3
SIGNAL = 4


class Flags:
NONE = 0x0
NO_REPLY_EXPECTED = 0x1
NO_AUTO_START = 0x2
ALLOW_INTERACTIVE_AUTHORIZATION = 0x4


class Version:
ONE = 1


class DataType:
INVALID = 0
BYTE = ord('y')
BOOLEAN = ord('b')
INT16 = ord('n')
UINT16 = ord('q')
INT32 = ord('i')
UINT32 = ord('u')
INT64 = ord('x')
UINT64 = ord('t')
DOUBLE = ord('d')
STRING = ord('s')
OBJECT_PATH = ord('o')
SIGNATURE = ord('g')
ARRAY = ord('a')
STRUCT_BEGIN = ord('(')
STRUCT_END = ord(')')
VARIANT = ord('v')
DICT_ENTRY = ord('e')
UNIX_FD = ord('h')


class HeaderField:
INVALID = 0
PATH = 1
INTERFACE = 2
MEMBER = 3
ERROR_NAME = 4
REPLY_SERIAL = 5
DESTINATION = 6
SENDER = 7
SIGNATURE = 8
UNIX_FDS = 9


def encode_signature(*data_types):
data = bytearray(data_types)
data.append(0)
data.insert(0, len(data_types))
return data


def align(align, offset):
count = offset % align
if not count:
return b''
return b'\x00' * (align - count)


def create_basic_method(path, member):
"""Creates raw D-Bus message` with `path` for method `member having no
parameters
"""
serial = 1
path = path.encode()
member = member.encode()

headers = bytearray()
headers.append(HeaderField.PATH)
headers += encode_signature(DataType.OBJECT_PATH)
headers += align(4, len(headers))
headers += struct.pack('<I', len(path))
headers += path
headers.append(0)
headers += align(8, len(headers))

headers.append(HeaderField.MEMBER)
headers += encode_signature(DataType.STRING)
headers += align(4, len(headers))
headers += struct.pack('<I', len(member))
headers += member
headers.append(0)

body = bytearray()

data = bytearray()
data.append(Endian.LITTLE)
data.append(MsgType.METHOD_CALL)
data.append(Flags.NONE)
data.append(Version.ONE)
data += struct.pack('<I', len(body))
data += struct.pack('<I', serial)
data += struct.pack('<I', len(headers))
data += headers
data += align(8, len(data))
data += body

return bytes(data)


def create_one_unix_fd_method(path, member, fd_index):
"""Creates raw D-Bus message` with `path` for method `member having one
UNIX file descriptor input parameter
"""

serial = 1
path = path.encode()
member = member.encode()
signature = bytearray([DataType.UNIX_FD])
num_fds = 1

headers = bytearray()
headers.append(HeaderField.PATH)
headers += encode_signature(DataType.OBJECT_PATH)
headers += align(4, len(headers))
headers += struct.pack('<I', len(path))
headers += path
headers.append(0)
headers += align(8, len(headers))

headers.append(HeaderField.MEMBER)
headers += encode_signature(DataType.STRING)
headers += align(4, len(headers))
headers += struct.pack('<I', len(member))
headers += member
headers.append(0)
headers += align(8, len(headers))

headers.append(HeaderField.SIGNATURE)
headers += encode_signature(DataType.SIGNATURE)
headers += struct.pack('B', len(signature))
headers += signature
headers.append(0)
headers += align(8, len(headers))

headers.append(HeaderField.UNIX_FDS)
headers += encode_signature(DataType.UINT32)
headers += align(4, len(headers))
headers += struct.pack('<I', num_fds)

body = bytearray()
body += struct.pack('<I', fd_index)

data = bytearray()
data.append(Endian.LITTLE)
data.append(MsgType.METHOD_CALL)
data.append(Flags.NONE)
data.append(Version.ONE)
data += struct.pack('<I', len(body))
data += struct.pack('<I', serial)
data += struct.pack('<I', len(headers))
data += headers
data += align(8, len(data))
data += body

return bytes(data)


class ProtocolTester(unittest.TestCase):
def test_dataReceived_simple_method_call(self):
p = TestProtocol()
p._authenticated = True

path = '/test/path'
member = 'testMethod'
data = create_basic_method(path, member)

self.assertIsNone(p.dataReceived(data))
self.assertEqual(len(p.mcalls), 1)
self.assertEqual(p.mcalls[0].path, path)
self.assertEqual(p.mcalls[0].member, member)
self.assertIsNone(p.mcalls[0].body)
self.assertEqual(len(p.mrets), 0)
self.assertEqual(len(p.merrs), 0)
self.assertEqual(len(p.msigs), 0)

def test_dataReceived_method_call_with_unix_fd(self):
p = TestProtocol()
p._authenticated = True

path = '/test/path'
member = 'testMethod'
fd = 99
data = create_one_unix_fd_method(path, member, 0)

p.fileDescriptorReceived(fd)
self.assertIsNone(p.dataReceived(data))
self.assertEqual(len(p.mcalls), 1)
self.assertEqual(p.mcalls[0].path, path)
self.assertEqual(p.mcalls[0].member, member)
self.assertEqual(p.mcalls[0].body, [fd])
self.assertEqual(len(p.mrets), 0)
self.assertEqual(len(p.merrs), 0)
self.assertEqual(len(p.msigs), 0)

def test_dataReceived_method_call_with_unix_fd_race(self):
p = TestProtocol()
p._authenticated = True

# message 1
path1 = '/test/path1'
member1 = 'testMethod1'
fd1 = 99
data1 = create_one_unix_fd_method(path1, member1, 0)

# message 2
path2 = '/test/path2'
member2 = 'testMethod2'
data2 = create_basic_method(path2, member2)

# possible race condition, file descriptor for message 1 is received
p.fileDescriptorReceived(fd1)
# then unrelated message 2 is received
self.assertIsNone(p.dataReceived(data2))
# then message 1, which is expecting file descriptor, is received
self.assertIsNone(p.dataReceived(data1))

self.assertEqual(len(p.mcalls), 2)
self.assertEqual(p.mcalls[0].path, path2)
self.assertEqual(p.mcalls[0].member, member2)
self.assertIsNone(p.mcalls[0].body)
self.assertEqual(p.mcalls[1].path, path1)
self.assertEqual(p.mcalls[1].member, member1)
self.assertEqual(p.mcalls[1].body, [fd1])
self.assertEqual(len(p.mrets), 0)
self.assertEqual(len(p.merrs), 0)
self.assertEqual(len(p.msigs), 0)

def test_dataReceived_method_call_with_unix_fd_race2(self):
p = TestProtocol()
p._authenticated = True

# message 1
path1 = '/test/path1'
member1 = 'testMethod1'
fd1 = 99
data1 = create_one_unix_fd_method(path1, member1, 0)

# message 2
path2 = '/test/path2'
member2 = 'testMethod2'
fd2 = 88
data2 = create_one_unix_fd_method(path2, member2, 0)

# possible race condition, file descriptor for message 1 is received
p.fileDescriptorReceived(fd1)
# then file descriptor for message 2 is received
p.fileDescriptorReceived(fd2)
# then message 1 is received
self.assertIsNone(p.dataReceived(data1))
# then message 2 is received
self.assertIsNone(p.dataReceived(data2))

self.assertEqual(len(p.mcalls), 2)
self.assertEqual(p.mcalls[0].path, path1)
self.assertEqual(p.mcalls[0].member, member1)
self.assertEqual(p.mcalls[0].body, [fd1])
self.assertEqual(p.mcalls[1].path, path2)
self.assertEqual(p.mcalls[1].member, member2)
self.assertEqual(p.mcalls[1].body, [fd2])
self.assertEqual(len(p.mrets), 0)
self.assertEqual(len(p.merrs), 0)
self.assertEqual(len(p.msigs), 0)

def test_dataReceived_method_call_with_unix_fd_race3(self):
raise unittest.SkipTest('It is not possible to fix this in txdbus - '
'it must be fixed in twisted.')

p = TestProtocol()
p._authenticated = True

# message 1
path1 = '/test/path1'
member1 = 'testMethod1'
fd1 = 99
data1 = create_one_unix_fd_method(path1, member1, 0)

# message 2
path2 = '/test/path2'
member2 = 'testMethod2'
fd2 = 88
data2 = create_one_unix_fd_method(path2, member2, 0)

# possible race condition, file descriptor for message 1 is received
p.fileDescriptorReceived(fd1)
# then file descriptor for message 2 is received
p.fileDescriptorReceived(fd2)
# then message 2 is received
self.assertIsNone(p.dataReceived(data2))
# then message 1 is received
self.assertIsNone(p.dataReceived(data1))

self.assertEqual(len(p.mcalls), 2)
self.assertEqual(p.mcalls[0].path, path2)
self.assertEqual(p.mcalls[0].member, member2)
self.assertEqual(p.mcalls[0].body, [fd2])
self.assertEqual(p.mcalls[1].path, path1)
self.assertEqual(p.mcalls[1].member, member1)
self.assertEqual(p.mcalls[1].body, [fd1])
self.assertEqual(len(p.mrets), 0)
self.assertEqual(len(p.merrs), 0)
self.assertEqual(len(p.msigs), 0)
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py27, py34, py35, py36, py37
envlist = py27, py35, py36, py37
skip_missing_interpreters = True

[testenv]
Expand Down
2 changes: 1 addition & 1 deletion txdbus/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ def callRemote(self, objectPath, methodName,
body=body,
expectReply=expectReply,
autoStart=autoStart,
oobFDs=self._toBeSentFDs,
oobFDs=[],
)

d = self.callRemoteMessage(mcall, timeout)
Expand Down
Loading

0 comments on commit 13c6c79

Please sign in to comment.