diff options
author | Guillaume Desmottes <guillaume.desmottes@collabora.co.uk> | 2008-11-26 16:44:00 +0000 |
---|---|---|
committer | Guillaume Desmottes <guillaume.desmottes@collabora.co.uk> | 2008-11-26 16:44:00 +0000 |
commit | 4ff131707342763d35e3d66a2a6b81ddbb47e097 (patch) | |
tree | bff02737f142b69bd3ab9d5e1e4969641ada1e3c /tests | |
parent | afa043cca638ef9d55185750ca02f3010f0e21c0 (diff) | |
parent | 4acc915be8b49609ae9a0339c04d6b2fa8c389cd (diff) | |
download | telepathy-salut-4ff131707342763d35e3d66a2a6b81ddbb47e097.tar.gz |
Merge branch 'master' into gibber-crash
Conflicts:
tests/twisted/Makefile.am
Diffstat (limited to 'tests')
17 files changed, 3007 insertions, 2 deletions
diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am index 1b471b94..daebef8b 100644 --- a/tests/twisted/Makefile.am +++ b/tests/twisted/Makefile.am @@ -6,10 +6,25 @@ TWISTED_AVAHI_TESTS = \ avahi/test-register.py \ avahi/test-aliases.py \ avahi/test-close-local-pending-room.py \ + avahi/test-send-file-and-cancel-immediately.py \ + avahi/test-send-file-and-disconnect.py \ + avahi/test-send-file-declined.py \ + avahi/test-send-file-item-not-found.py \ + avahi/test-send-file-provide-immediately.py \ + avahi/test-send-file-to-unknown-contact.py \ + avahi/test-send-file-wait-to-provide.py \ + avahi/test-receive-and-send-file.py \ + avahi/test-receive-file.py \ + avahi/test-receive-file-cancelled-immediately.py \ + avahi/test-receive-file-decline.py \ + avahi/test-receive-file-not-found.py \ avahi/test-text-channel.py \ avahi/test-ichat-composing.py \ avahi/test-ichat-incoming-msg.py \ - avahi/test-room-list.py + avahi/test-room-list.py \ + avahi/test-tube.py \ + avahi/test-two-tubes.py \ + avahi/test-tube-close.py TWISTED_AVAHI_OLPC_TESTS = \ avahi/test-olpc-activity-announcements.py @@ -48,7 +63,8 @@ EXTRA_DIST = \ $(TWISTED_AVAHI_TESTS) \ $(TWISTED_BASIC_TESTS) \ saluttest.py \ - servicetest.py + servicetest.py \ + trivialstream.py CLEANFILES = salut-[1-9]*.log *.pyc */*.pyc diff --git a/tests/twisted/avahi/test-receive-and-send-file.py b/tests/twisted/avahi/test-receive-and-send-file.py new file mode 100644 index 00000000..d74ddb80 --- /dev/null +++ b/tests/twisted/avahi/test-receive-and-send-file.py @@ -0,0 +1,314 @@ +import httplib +import urlparse +import dbus +import socket +import md5 +import avahi +import BaseHTTPServer +import urllib + +from saluttest import exec_test +from avahitest import AvahiAnnouncer, AvahiListener +from avahitest import get_host_name + +from xmppstream import setup_stream_listener, connect_to_stream +from servicetest import make_channel_proxy, EventPattern + +from twisted.words.xish import domish, xpath + +from dbus import PROPERTIES_IFACE + +CONNECTION_INTERFACE_REQUESTS = 'org.freedesktop.Telepathy.Connection.Interface.Requests' +CHANNEL_INTERFACE ='org.freedesktop.Telepathy.Channel' +CHANNEL_TYPE_FILE_TRANSFER = 'org.freedesktop.Telepathy.Channel.Type.FileTransfer.DRAFT' + +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) + +FT_STATE_NONE = 0 +FT_STATE_PENDING = 1 +FT_STATE_ACCEPTED = 2 +FT_STATE_OPEN = 3 +FT_STATE_COMPLETED = 4 +FT_STATE_CANCELLED = 5 + +FT_STATE_CHANGE_REASON_NONE = 0 +FT_STATE_CHANGE_REASON_REQUESTED = 1 + +FILE_HASH_TYPE_NONE = 0 +FILE_HASH_TYPE_MD5 = 1 + +SOCKET_ADDRESS_TYPE_UNIX = 0 +SOCKET_ADDRESS_TYPE_IPV4 = 2 + +SOCKET_ACCESS_CONTROL_LOCALHOST = 0 + +# File to Offer +FILE_DATA = "What a nice file" +FILE_SIZE = len(FILE_DATA) +FILE_NAME = 'The foo.txt' +FILE_CONTENT_TYPE = 'text/plain' +FILE_DESCRIPTION = 'A nice file to test' +FILE_HASH_TYPE = FILE_HASH_TYPE_MD5 +m = md5.new() +m.update(FILE_DATA) +FILE_HASH = m.hexdigest() + +def test(q, bus, conn): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + basic_txt = { "txtvers": "1", "status": "avail" } + + self_handle = conn.GetSelfHandle() + self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0] + + contact_name = "test-file-sender@" + get_host_name() + listener, port = setup_stream_listener(q, contact_name) + + AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt) + + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + + handle = 0 + # Wait until the record shows up in publish + while handle == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact_name: + handle = h + + requests_iface = dbus.Interface(conn, CONNECTION_INTERFACE_REQUESTS) + + # Create a connection to send the FT stanza + AvahiListener(q).listen_for_service("_presence._tcp") + e = q.expect('service-added', name = self_handle_name, + protocol = avahi.PROTO_INET) + service = e.service + service.resolve() + + e = q.expect('service-resolved', service = service) + + outbound = connect_to_stream(q, contact_name, + self_handle_name, str(e.pt), e.port) + + e = q.expect('connection-result') + assert e.succeeded, e.reason + e = q.expect('stream-opened', connection = outbound) + + # Setup the HTTP server + httpd = BaseHTTPServer.HTTPServer(('', 0), HTTPHandler) + + # connected to Salut, now send the IQ + iq = domish.Element((None, 'iq')) + iq['to'] = self_handle_name + iq['from'] = contact_name + iq['type'] = 'set' + iq['id'] = 'gibber-file-transfer-0' + query = iq.addElement(('jabber:iq:oob', 'query')) + url = 'http://127.0.0.1:%u/gibber-file-transfer-0/%s' % (httpd.server_port, urllib.quote(FILE_NAME)) + url_node = query.addElement('url', content=url) + url_node['type'] = 'file' + url_node['size'] = str(FILE_SIZE) + url_node['mimeType'] = FILE_CONTENT_TYPE + query.addElement('desc', content=FILE_DESCRIPTION) + outbound.send(iq) + + e =q.expect('dbus-signal', signal='NewChannels') + channels = e.args[0] + assert len(channels) == 1 + path, props = channels[0] + + # check channel properties + # org.freedesktop.Telepathy.Channel D-Bus properties + assert props[CHANNEL_INTERFACE + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER + assert props[CHANNEL_INTERFACE + '.Interfaces'] == [] + assert props[CHANNEL_INTERFACE + '.TargetHandle'] == handle + assert props[CHANNEL_INTERFACE + '.TargetID'] == contact_name + assert props[CHANNEL_INTERFACE + '.TargetHandleType'] == HT_CONTACT + assert props[CHANNEL_INTERFACE + '.Requested'] == False + assert props[CHANNEL_INTERFACE + '.InitiatorHandle'] == handle + assert props[CHANNEL_INTERFACE + '.InitiatorID'] == contact_name + + # org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.State'] == FT_STATE_PENDING + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentType'] == FILE_CONTENT_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Filename'] == FILE_NAME + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Size'] == FILE_SIZE + # FT's protocol doesn't allow us the send the hash info + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType'] == FILE_HASH_TYPE_NONE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash'] == '' + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Description'] == FILE_DESCRIPTION + # FT's protocol doesn't allow us the send the date info + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Date'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes'] == \ + {SOCKET_ADDRESS_TYPE_UNIX: [SOCKET_ACCESS_CONTROL_LOCALHOST]} + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'] == 0 + + channel = make_channel_proxy(conn, path, 'Channel') + ft_channel = make_channel_proxy(conn, path, 'Channel.Type.FileTransfer.DRAFT') + ft_props = dbus.Interface(bus.get_object(conn.object.bus_name, path), PROPERTIES_IFACE) + + address = ft_channel.AcceptFile(SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, "", 5) + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_ACCEPTED + assert reason == FT_STATE_CHANGE_REASON_REQUESTED + + e = q.expect('dbus-signal', signal='InitialOffsetDefined') + offset = e.args[0] + # We don't support resume + assert offset == 0 + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_OPEN + assert reason == FT_STATE_CHANGE_REASON_NONE + + # Connect to Salut's socket + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(address) + + httpd.handle_request() + + # Receiver inform us he finished to download the file + q.expect('stream-iq', iq_type='result') + + # Read the file from Salut's socket + data = '' + read = 0 + while read < FILE_SIZE: + data += s.recv(FILE_SIZE - read) + read = len(data) + assert data == FILE_DATA + + e = q.expect('dbus-signal', signal='TransferredBytesChanged') + count = e.args[0] + while count < FILE_SIZE: + # Catch TransferredBytesChanged until we transfered all the data + e = q.expect('dbus-signal', signal='TransferredBytesChanged') + count = e.args[0] + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_COMPLETED + assert reason == FT_STATE_CHANGE_REASON_NONE + + channel.Close() + q.expect('dbus-signal', signal='Closed') + + # now send a file. We'll reuse the same XMPP connection + path, props = requests_iface.CreateChannel({ + CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT, + CHANNEL_INTERFACE + '.TargetHandle': handle, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType': FILE_CONTENT_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.Filename': FILE_NAME, + CHANNEL_TYPE_FILE_TRANSFER + '.Size': FILE_SIZE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType': FILE_HASH_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash': FILE_HASH, + CHANNEL_TYPE_FILE_TRANSFER + '.Description': FILE_DESCRIPTION, + CHANNEL_TYPE_FILE_TRANSFER + '.Date': 1225278834, + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset': 0, + }) + + channel = make_channel_proxy(conn, path, 'Channel') + ft_channel = make_channel_proxy(conn, path, 'Channel.Type.FileTransfer.DRAFT') + ft_props = dbus.Interface(bus.get_object(conn.object.bus_name, path), PROPERTIES_IFACE) + + iq_event = q.expect('stream-iq') + + assert iq_event.iq_type == 'set' + assert iq_event.connection == outbound + iq = iq_event.stanza + assert iq['to'] == contact_name + query = iq.firstChildElement() + assert query.uri == 'jabber:iq:oob' + url_node = xpath.queryForNodes("/iq/query/url", iq)[0] + assert url_node['type'] == 'file' + assert url_node['size'] == str(FILE_SIZE) + assert url_node['mimeType'] == FILE_CONTENT_TYPE + url = url_node.children[0] + _, host, file, _, _, _ = urlparse.urlparse(url) + urllib.unquote(file) == FILE_NAME + desc_node = xpath.queryForNodes("/iq/query/desc", iq)[0] + desc = desc_node.children[0] + assert desc == FILE_DESCRIPTION + + address = ft_channel.ProvideFile(SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, "") + + # state is still Pending as remote didn't accept the transfer yet + state = ft_props.Get(CHANNEL_TYPE_FILE_TRANSFER, 'State') + assert state == FT_STATE_PENDING + + # Connect HTTP client to the CM and request the file + http = httplib.HTTPConnection(host) + http.request('GET', file) + + e = q.expect('dbus-signal', signal='InitialOffsetDefined') + offset = e.args[0] + # We don't support resume + assert offset == 0 + + # Channel is open. We can start to send the file + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_OPEN + assert reason == FT_STATE_CHANGE_REASON_NONE + + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(address) + s.send(FILE_DATA) + + e = q.expect('dbus-signal', signal='TransferredBytesChanged') + + count = e.args[0] + while count < FILE_SIZE: + # Catch TransferredBytesChanged until we transfered all the data + e = q.expect('dbus-signal', signal='TransferredBytesChanged') + count = e.args[0] + + response = http.getresponse() + assert (response.status, response.reason) == (200, 'OK') + data = response.read(FILE_SIZE) + # Did we received the right file? + assert data == FILE_DATA + + # Inform sender that we received all the file from the OOB transfer + reply = domish.Element(('', 'iq')) + reply['to'] = iq['from'] + reply['from'] = iq['to'] + reply['type'] = 'result' + reply['id'] = iq['id'] + outbound.send(reply) + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_COMPLETED + assert reason == FT_STATE_CHANGE_REASON_NONE + + channel.Close() + q.expect('dbus-signal', signal='Closed') + + conn.Disconnect() + q.expect('dbus-signal', signal='StatusChanged', args=[2L, 1L]) + +class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + # is that the right file ? + filename = self.path.rsplit('/', 2)[-1] + assert filename == urllib.quote(FILE_NAME) + + self.send_response(200) + self.send_header('Content-type', FILE_CONTENT_TYPE) + self.end_headers() + self.wfile.write(FILE_DATA) + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-receive-file-cancelled-immediately.py b/tests/twisted/avahi/test-receive-file-cancelled-immediately.py new file mode 100644 index 00000000..96f8ee64 --- /dev/null +++ b/tests/twisted/avahi/test-receive-file-cancelled-immediately.py @@ -0,0 +1,198 @@ +import httplib +import urlparse +import dbus +import socket +import md5 +import avahi +import BaseHTTPServer +import urllib + +from saluttest import exec_test +from avahitest import AvahiAnnouncer, AvahiListener +from avahitest import get_host_name + +from xmppstream import setup_stream_listener, connect_to_stream +from servicetest import make_channel_proxy, EventPattern + +from twisted.words.xish import domish + +from dbus import PROPERTIES_IFACE + +CONNECTION_INTERFACE_REQUESTS = 'org.freedesktop.Telepathy.Connection.Interface.Requests' +CHANNEL_INTERFACE ='org.freedesktop.Telepathy.Channel' +CHANNEL_TYPE_FILE_TRANSFER = 'org.freedesktop.Telepathy.Channel.Type.FileTransfer.DRAFT' + +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) + +FT_STATE_NONE = 0 +FT_STATE_PENDING = 1 +FT_STATE_ACCEPTED = 2 +FT_STATE_OPEN = 3 +FT_STATE_COMPLETED = 4 +FT_STATE_CANCELLED = 5 + +FT_STATE_CHANGE_REASON_NONE = 0 +FT_STATE_CHANGE_REASON_REQUESTED = 1 +FT_STATE_CHANGE_REASON_REMOTE_STOPPED = 3 + +FILE_HASH_TYPE_NONE = 0 +FILE_HASH_TYPE_MD5 = 1 + +SOCKET_ADDRESS_TYPE_UNIX = 0 +SOCKET_ADDRESS_TYPE_IPV4 = 2 + +SOCKET_ACCESS_CONTROL_LOCALHOST = 0 + +# File to Offer +FILE_DATA = "What a nice file" +FILE_SIZE = len(FILE_DATA) +FILE_NAME = 'The foo.txt' +FILE_CONTENT_TYPE = 'text/plain' +FILE_DESCRIPTION = 'A nice file to test' +FILE_HASH_TYPE = FILE_HASH_TYPE_MD5 +m = md5.new() +m.update(FILE_DATA) +FILE_HASH = m.hexdigest() + +def test(q, bus, conn): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + basic_txt = { "txtvers": "1", "status": "avail" } + + self_handle = conn.GetSelfHandle() + self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0] + + contact_name = "test-file-sender@" + get_host_name() + listener, port = setup_stream_listener(q, contact_name) + + AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt) + + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + + handle = 0 + # Wait until the record shows up in publish + while handle == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact_name: + handle = h + + requests_iface = dbus.Interface(conn, CONNECTION_INTERFACE_REQUESTS) + + # Create a connection to send the FT stanza + AvahiListener(q).listen_for_service("_presence._tcp") + e = q.expect('service-added', name = self_handle_name, + protocol = avahi.PROTO_INET) + service = e.service + service.resolve() + + e = q.expect('service-resolved', service = service) + + outbound = connect_to_stream(q, contact_name, + self_handle_name, str(e.pt), e.port) + + e = q.expect('connection-result') + assert e.succeeded, e.reason + e = q.expect('stream-opened', connection = outbound) + + # Setup the HTTP server + httpd = BaseHTTPServer.HTTPServer(('', 0), HTTPHandler) + + # connected to Salut, now send the IQ + iq = domish.Element((None, 'iq')) + iq['to'] = self_handle_name + iq['from'] = contact_name + iq['type'] = 'set' + iq['id'] = 'gibber-file-transfer-0' + query = iq.addElement(('jabber:iq:oob', 'query')) + url = 'http://127.0.0.1:%u/gibber-file-transfer-0/%s' % (httpd.server_port, urllib.quote(FILE_NAME)) + url_node = query.addElement('url', content=url) + url_node['type'] = 'file' + url_node['size'] = str(FILE_SIZE) + url_node['mimeType'] = FILE_CONTENT_TYPE + query.addElement('desc', content=FILE_DESCRIPTION) + outbound.send(iq) + + e =q.expect('dbus-signal', signal='NewChannels') + channels = e.args[0] + assert len(channels) == 1 + path, props = channels[0] + + # check channel properties + # org.freedesktop.Telepathy.Channel D-Bus properties + assert props[CHANNEL_INTERFACE + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER + assert props[CHANNEL_INTERFACE + '.Interfaces'] == [] + assert props[CHANNEL_INTERFACE + '.TargetHandle'] == handle + assert props[CHANNEL_INTERFACE + '.TargetID'] == contact_name + assert props[CHANNEL_INTERFACE + '.TargetHandleType'] == HT_CONTACT + assert props[CHANNEL_INTERFACE + '.Requested'] == False + assert props[CHANNEL_INTERFACE + '.InitiatorHandle'] == handle + assert props[CHANNEL_INTERFACE + '.InitiatorID'] == contact_name + + # org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.State'] == FT_STATE_PENDING + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentType'] == FILE_CONTENT_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Filename'] == FILE_NAME + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Size'] == FILE_SIZE + # FT's protocol doesn't allow us the send the hash info + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType'] == FILE_HASH_TYPE_NONE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash'] == '' + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Description'] == FILE_DESCRIPTION + # FT's protocol doesn't allow us the send the date info + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Date'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes'] == \ + {SOCKET_ADDRESS_TYPE_UNIX: [SOCKET_ACCESS_CONTROL_LOCALHOST]} + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'] == 0 + + channel = make_channel_proxy(conn, path, 'Channel') + ft_channel = make_channel_proxy(conn, path, 'Channel.Type.FileTransfer.DRAFT') + ft_props = dbus.Interface(bus.get_object(conn.object.bus_name, path), PROPERTIES_IFACE) + + # sender cancels FT immediately so stop to listen to the HTTP socket + httpd.server_close() + + address = ft_channel.AcceptFile(SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, "", 5) + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_ACCEPTED + assert reason == FT_STATE_CHANGE_REASON_REQUESTED + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_OPEN + assert reason == FT_STATE_CHANGE_REASON_NONE + + # Connect to Salut's socket + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(address) + + # Salut can't connect to download the file + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_CANCELLED + assert reason == FT_STATE_CHANGE_REASON_REMOTE_STOPPED + + channel.Close() + q.expect('dbus-signal', signal='Closed') + +class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + # is that the right file ? + filename = self.path.rsplit('/', 2)[-1] + assert filename == urllib.quote(FILE_NAME) + + self.send_response(200) + self.send_header('Content-type', FILE_CONTENT_TYPE) + self.end_headers() + self.wfile.write(FILE_DATA) + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-receive-file-decline.py b/tests/twisted/avahi/test-receive-file-decline.py new file mode 100644 index 00000000..1c827c0b --- /dev/null +++ b/tests/twisted/avahi/test-receive-file-decline.py @@ -0,0 +1,209 @@ +import httplib +import urlparse +import dbus +import socket +import md5 +import avahi +import BaseHTTPServer +import urllib + +from saluttest import exec_test +from avahitest import AvahiAnnouncer, AvahiListener +from avahitest import get_host_name + +from xmppstream import setup_stream_listener, connect_to_stream +from servicetest import make_channel_proxy, EventPattern + +from twisted.words.xish import domish + +from dbus import PROPERTIES_IFACE + +CONNECTION_INTERFACE_REQUESTS = 'org.freedesktop.Telepathy.Connection.Interface.Requests' +CHANNEL_INTERFACE ='org.freedesktop.Telepathy.Channel' +CHANNEL_TYPE_FILE_TRANSFER = 'org.freedesktop.Telepathy.Channel.Type.FileTransfer.DRAFT' + +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) + +FT_STATE_NONE = 0 +FT_STATE_PENDING = 1 +FT_STATE_ACCEPTED = 2 +FT_STATE_OPEN = 3 +FT_STATE_COMPLETED = 4 +FT_STATE_CANCELLED = 5 + +FT_STATE_CHANGE_REASON_NONE = 0 +FT_STATE_CHANGE_REASON_REQUESTED = 1 +FT_STATE_CHANGE_REASON_LOCAL_STOPPED = 2 + +FILE_HASH_TYPE_NONE = 0 +FILE_HASH_TYPE_MD5 = 1 + +SOCKET_ADDRESS_TYPE_UNIX = 0 +SOCKET_ADDRESS_TYPE_IPV4 = 2 + +SOCKET_ACCESS_CONTROL_LOCALHOST = 0 + +# File to Offer +FILE_DATA = "What a nice file" +FILE_SIZE = len(FILE_DATA) +FILE_NAME = 'The foo.txt' +FILE_CONTENT_TYPE = 'text/plain' +FILE_DESCRIPTION = 'A nice file to test' +FILE_HASH_TYPE = FILE_HASH_TYPE_MD5 +m = md5.new() +m.update(FILE_DATA) +FILE_HASH = m.hexdigest() + +def test(q, bus, conn): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + basic_txt = { "txtvers": "1", "status": "avail" } + + self_handle = conn.GetSelfHandle() + self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0] + + contact_name = "test-file-sender@" + get_host_name() + listener, port = setup_stream_listener(q, contact_name) + + AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt) + + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + + handle = 0 + # Wait until the record shows up in publish + while handle == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact_name: + handle = h + + requests_iface = dbus.Interface(conn, CONNECTION_INTERFACE_REQUESTS) + + # Create a connection to send the FT stanza + AvahiListener(q).listen_for_service("_presence._tcp") + e = q.expect('service-added', name = self_handle_name, + protocol = avahi.PROTO_INET) + service = e.service + service.resolve() + + e = q.expect('service-resolved', service = service) + + outbound = connect_to_stream(q, contact_name, + self_handle_name, str(e.pt), e.port) + + e = q.expect('connection-result') + assert e.succeeded, e.reason + e = q.expect('stream-opened', connection = outbound) + + # Setup the HTTP server + httpd = BaseHTTPServer.HTTPServer(('', 0), HTTPHandler) + + # connected to Salut, now send the IQ + iq = domish.Element((None, 'iq')) + iq['to'] = self_handle_name + iq['from'] = contact_name + iq['type'] = 'set' + iq['id'] = 'gibber-file-transfer-0' + query = iq.addElement(('jabber:iq:oob', 'query')) + url = 'http://127.0.0.1:%u/gibber-file-transfer-0/%s' % (httpd.server_port, urllib.quote(FILE_NAME)) + url_node = query.addElement('url', content=url) + url_node['type'] = 'file' + url_node['size'] = str(FILE_SIZE) + url_node['mimeType'] = FILE_CONTENT_TYPE + query.addElement('desc', content=FILE_DESCRIPTION) + outbound.send(iq) + + e = q.expect('dbus-signal', signal='NewChannels') + channels = e.args[0] + assert len(channels) == 1 + path, props = channels[0] + + # check channel properties + # org.freedesktop.Telepathy.Channel D-Bus properties + assert props[CHANNEL_INTERFACE + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER + assert props[CHANNEL_INTERFACE + '.Interfaces'] == [] + assert props[CHANNEL_INTERFACE + '.TargetHandle'] == handle + assert props[CHANNEL_INTERFACE + '.TargetID'] == contact_name + assert props[CHANNEL_INTERFACE + '.TargetHandleType'] == HT_CONTACT + assert props[CHANNEL_INTERFACE + '.Requested'] == False + assert props[CHANNEL_INTERFACE + '.InitiatorHandle'] == handle + assert props[CHANNEL_INTERFACE + '.InitiatorID'] == contact_name + + # org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.State'] == FT_STATE_PENDING + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentType'] == FILE_CONTENT_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Filename'] == FILE_NAME + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Size'] == FILE_SIZE + # FT's protocol doesn't allow us the send the hash info + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType'] == FILE_HASH_TYPE_NONE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash'] == '' + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Description'] == FILE_DESCRIPTION + # FT's protocol doesn't allow us the send the date info + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Date'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes'] == \ + {SOCKET_ADDRESS_TYPE_UNIX: [SOCKET_ACCESS_CONTROL_LOCALHOST]} + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'] == 0 + + channel = make_channel_proxy(conn, path, 'Channel') + + # decline FT + channel.Close() + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_CANCELLED + assert reason == FT_STATE_CHANGE_REASON_LOCAL_STOPPED + q.expect('dbus-signal', signal='Closed') + + # Re send offer (this is a regression test as Salut used to crash at this + # point) + iq = domish.Element((None, 'iq')) + iq['to'] = self_handle_name + iq['from'] = contact_name + iq['type'] = 'set' + iq['id'] = 'gibber-file-transfer-1' + query = iq.addElement(('jabber:iq:oob', 'query')) + url = 'http://127.0.0.1:%u/gibber-file-transfer-0/%s' % (httpd.server_port, urllib.quote(FILE_NAME)) + url_node = query.addElement('url', content=url) + url_node['type'] = 'file' + url_node['size'] = str(FILE_SIZE) + url_node['mimeType'] = FILE_CONTENT_TYPE + query.addElement('desc', content=FILE_DESCRIPTION) + outbound.send(iq) + + e = q.expect('dbus-signal', signal='NewChannels') + channels = e.args[0] + assert len(channels) == 1 + path, props = channels[0] + + channel = make_channel_proxy(conn, path, 'Channel') + + # decline FT + channel.Close() + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_CANCELLED + assert reason == FT_STATE_CHANGE_REASON_LOCAL_STOPPED + q.expect('dbus-signal', signal='Closed') + +class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + # is that the right file ? + filename = self.path.rsplit('/', 2)[-1] + assert filename == urllib.quote(FILE_NAME) + + self.send_response(200) + self.send_header('Content-type', FILE_CONTENT_TYPE) + self.end_headers() + self.wfile.write(FILE_DATA) + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-receive-file-not-found.py b/tests/twisted/avahi/test-receive-file-not-found.py new file mode 100644 index 00000000..25c1429e --- /dev/null +++ b/tests/twisted/avahi/test-receive-file-not-found.py @@ -0,0 +1,215 @@ +import httplib +import urlparse +import dbus +import socket +import md5 +import avahi +import BaseHTTPServer + +from saluttest import exec_test +from avahitest import AvahiAnnouncer, AvahiListener +from avahitest import get_host_name + +from xmppstream import setup_stream_listener, connect_to_stream +from servicetest import make_channel_proxy, EventPattern + +from twisted.words.xish import domish, xpath + +from dbus import PROPERTIES_IFACE + +CONNECTION_INTERFACE_REQUESTS = 'org.freedesktop.Telepathy.Connection.Interface.Requests' +CHANNEL_INTERFACE ='org.freedesktop.Telepathy.Channel' +CHANNEL_TYPE_FILE_TRANSFER = 'org.freedesktop.Telepathy.Channel.Type.FileTransfer.DRAFT' + +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) + +FT_STATE_NONE = 0 +FT_STATE_PENDING = 1 +FT_STATE_ACCEPTED = 2 +FT_STATE_OPEN = 3 +FT_STATE_COMPLETED = 4 +FT_STATE_CANCELLED = 5 + +FT_STATE_CHANGE_REASON_NONE = 0 +FT_STATE_CHANGE_REASON_REQUESTED = 1 +FT_STATE_CHANGE_REASON_LOCAL_STOPPED = 2 +FT_STATE_CHANGE_REASON_REMOTE_STOPPED = 3 +FT_STATE_CHANGE_REASON_LOCAL_ERROR = 4 +FT_STATE_CHANGE_REASON_REMOTE_ERROR = 5 + +FILE_HASH_TYPE_NONE = 0 +FILE_HASH_TYPE_MD5 = 1 + +SOCKET_ADDRESS_TYPE_UNIX = 0 +SOCKET_ADDRESS_TYPE_IPV4 = 2 + +SOCKET_ACCESS_CONTROL_LOCALHOST = 0 + +# File to Offer +FILE_DATA = "What a nice fileeeeeeeeeeeeeee" +FILE_SIZE = len(FILE_DATA) +FILE_NAME = 'foo.txt' +FILE_CONTENT_TYPE = 'text/plain' +FILE_DESCRIPTION = 'A nice file to test' +FILE_HASH_TYPE = FILE_HASH_TYPE_MD5 +m = md5.new() +m.update(FILE_DATA) +FILE_HASH = m.hexdigest() + +def test(q, bus, conn): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + basic_txt = { "txtvers": "1", "status": "avail" } + + self_handle = conn.GetSelfHandle() + self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0] + + contact_name = "test-file-sender@" + get_host_name() + listener, port = setup_stream_listener(q, contact_name) + + AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt) + + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + + handle = 0 + # Wait until the record shows up in publish + while handle == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact_name: + handle = h + + requests_iface = dbus.Interface(conn, CONNECTION_INTERFACE_REQUESTS) + + # Create a connection to send the FT stanza + AvahiListener(q).listen_for_service("_presence._tcp") + e = q.expect('service-added', name = self_handle_name, + protocol = avahi.PROTO_INET) + service = e.service + service.resolve() + + e = q.expect('service-resolved', service = service) + + outbound = connect_to_stream(q, contact_name, + self_handle_name, str(e.pt), e.port) + + e = q.expect('connection-result') + assert e.succeeded, e.reason + e = q.expect('stream-opened', connection = outbound) + + # Setup the HTTP server + httpd = BaseHTTPServer.HTTPServer(('', 0), HTTPHandler) + + # connected to Salut, now send the IQ + iq = domish.Element((None, 'iq')) + iq['to'] = self_handle_name + iq['from'] = contact_name + iq['type'] = 'set' + iq['id'] = 'gibber-file-transfer-0' + query = iq.addElement(('jabber:iq:oob', 'query')) + url = 'http://127.0.0.1:%u/gibber-file-transfer-0/%s' % (httpd.server_port, FILE_NAME) + url_node = query.addElement('url', content=url) + url_node['type'] = 'file' + url_node['size'] = str(FILE_SIZE) + url_node['mimeType'] = FILE_CONTENT_TYPE + query.addElement('desc', content=FILE_DESCRIPTION) + outbound.send(iq) + + e =q.expect('dbus-signal', signal='NewChannels') + channels = e.args[0] + assert len(channels) == 1 + path, props = channels[0] + + # check channel properties + # org.freedesktop.Telepathy.Channel D-Bus properties + assert props[CHANNEL_INTERFACE + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER + assert props[CHANNEL_INTERFACE + '.Interfaces'] == [] + assert props[CHANNEL_INTERFACE + '.TargetHandle'] == handle + assert props[CHANNEL_INTERFACE + '.TargetID'] == contact_name + assert props[CHANNEL_INTERFACE + '.TargetHandleType'] == HT_CONTACT + assert props[CHANNEL_INTERFACE + '.Requested'] == False + assert props[CHANNEL_INTERFACE + '.InitiatorHandle'] == handle + assert props[CHANNEL_INTERFACE + '.InitiatorID'] == contact_name + + # org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.State'] == FT_STATE_PENDING + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentType'] == FILE_CONTENT_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Filename'] == FILE_NAME + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Size'] == FILE_SIZE + # FT's protocol doesn't allow us the send the hash info + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType'] == FILE_HASH_TYPE_NONE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash'] == '' + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Description'] == FILE_DESCRIPTION + # FT's protocol doesn't allow us the send the date info + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Date'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes'] == \ + {SOCKET_ADDRESS_TYPE_UNIX: [SOCKET_ACCESS_CONTROL_LOCALHOST]} + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'] == 0 + + channel = make_channel_proxy(conn, path, 'Channel') + ft_channel = make_channel_proxy(conn, path, 'Channel.Type.FileTransfer.DRAFT') + ft_props = dbus.Interface(bus.get_object(conn.object.bus_name, path), PROPERTIES_IFACE) + + address = ft_channel.AcceptFile(SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, "", 5) + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_ACCEPTED + assert reason == FT_STATE_CHANGE_REASON_REQUESTED + + e = q.expect('dbus-signal', signal='InitialOffsetDefined') + offset = e.args[0] + # We don't support resume + assert offset == 0 + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_OPEN + assert reason == FT_STATE_CHANGE_REASON_NONE + + # Connect to Salut's socket + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(address) + + httpd.handle_request() + + # Receiver inform us he finished to download the file + e = q.expect('stream-iq', iq_type='error') + iq = e.stanza + error_node = xpath.queryForNodes("/iq/error", iq)[0] + assert error_node['code'] == '404' + assert error_node['type'] == 'cancel' + not_found_node = error_node.firstChildElement() + assert not_found_node.name == 'item-not-found' + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_CANCELLED + assert reason == FT_STATE_CHANGE_REASON_LOCAL_ERROR + + transferred = ft_props.Get(CHANNEL_TYPE_FILE_TRANSFER, 'TransferredBytes') + # no byte has been transferred as the transfer failed + assert transferred == 0 + + channel.Close() + q.expect('dbus-signal', signal='Closed') + +class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + # is that the right file ? + filename = self.path.rsplit('/', 2)[-1] + assert filename == FILE_NAME + + self.send_response(404) + self.end_headers() + self.wfile.write(FILE_DATA) + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-receive-file.py b/tests/twisted/avahi/test-receive-file.py new file mode 100644 index 00000000..03b4e96f --- /dev/null +++ b/tests/twisted/avahi/test-receive-file.py @@ -0,0 +1,218 @@ +import httplib +import urlparse +import dbus +import socket +import md5 +import avahi +import BaseHTTPServer +import urllib + +from saluttest import exec_test +from avahitest import AvahiAnnouncer, AvahiListener +from avahitest import get_host_name + +from xmppstream import setup_stream_listener, connect_to_stream +from servicetest import make_channel_proxy, EventPattern + +from twisted.words.xish import domish + +from dbus import PROPERTIES_IFACE + +CONNECTION_INTERFACE_REQUESTS = 'org.freedesktop.Telepathy.Connection.Interface.Requests' +CHANNEL_INTERFACE ='org.freedesktop.Telepathy.Channel' +CHANNEL_TYPE_FILE_TRANSFER = 'org.freedesktop.Telepathy.Channel.Type.FileTransfer.DRAFT' + +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) + +FT_STATE_NONE = 0 +FT_STATE_PENDING = 1 +FT_STATE_ACCEPTED = 2 +FT_STATE_OPEN = 3 +FT_STATE_COMPLETED = 4 +FT_STATE_CANCELLED = 5 + +FT_STATE_CHANGE_REASON_NONE = 0 +FT_STATE_CHANGE_REASON_REQUESTED = 1 + +FILE_HASH_TYPE_NONE = 0 +FILE_HASH_TYPE_MD5 = 1 + +SOCKET_ADDRESS_TYPE_UNIX = 0 +SOCKET_ADDRESS_TYPE_IPV4 = 2 + +SOCKET_ACCESS_CONTROL_LOCALHOST = 0 + +# File to Offer +FILE_DATA = "What a nice file" +FILE_SIZE = len(FILE_DATA) +FILE_NAME = 'The foo.txt' +FILE_CONTENT_TYPE = 'text/plain' +FILE_DESCRIPTION = 'A nice file to test' +FILE_HASH_TYPE = FILE_HASH_TYPE_MD5 +m = md5.new() +m.update(FILE_DATA) +FILE_HASH = m.hexdigest() + +def test(q, bus, conn): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + basic_txt = { "txtvers": "1", "status": "avail" } + + self_handle = conn.GetSelfHandle() + self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0] + + contact_name = "test-file-sender@" + get_host_name() + listener, port = setup_stream_listener(q, contact_name) + + AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt) + + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + + handle = 0 + # Wait until the record shows up in publish + while handle == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact_name: + handle = h + + requests_iface = dbus.Interface(conn, CONNECTION_INTERFACE_REQUESTS) + + # Create a connection to send the FT stanza + AvahiListener(q).listen_for_service("_presence._tcp") + e = q.expect('service-added', name = self_handle_name, + protocol = avahi.PROTO_INET) + service = e.service + service.resolve() + + e = q.expect('service-resolved', service = service) + + outbound = connect_to_stream(q, contact_name, + self_handle_name, str(e.pt), e.port) + + e = q.expect('connection-result') + assert e.succeeded, e.reason + e = q.expect('stream-opened', connection = outbound) + + # Setup the HTTP server + httpd = BaseHTTPServer.HTTPServer(('', 0), HTTPHandler) + + # connected to Salut, now send the IQ + iq = domish.Element((None, 'iq')) + iq['to'] = self_handle_name + iq['from'] = contact_name + iq['type'] = 'set' + iq['id'] = 'gibber-file-transfer-0' + query = iq.addElement(('jabber:iq:oob', 'query')) + url = 'http://127.0.0.1:%u/gibber-file-transfer-0/%s' % (httpd.server_port, urllib.quote(FILE_NAME)) + url_node = query.addElement('url', content=url) + url_node['type'] = 'file' + url_node['size'] = str(FILE_SIZE) + url_node['mimeType'] = FILE_CONTENT_TYPE + query.addElement('desc', content=FILE_DESCRIPTION) + outbound.send(iq) + + e =q.expect('dbus-signal', signal='NewChannels') + channels = e.args[0] + assert len(channels) == 1 + path, props = channels[0] + + # check channel properties + # org.freedesktop.Telepathy.Channel D-Bus properties + assert props[CHANNEL_INTERFACE + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER + assert props[CHANNEL_INTERFACE + '.Interfaces'] == [] + assert props[CHANNEL_INTERFACE + '.TargetHandle'] == handle + assert props[CHANNEL_INTERFACE + '.TargetID'] == contact_name + assert props[CHANNEL_INTERFACE + '.TargetHandleType'] == HT_CONTACT + assert props[CHANNEL_INTERFACE + '.Requested'] == False + assert props[CHANNEL_INTERFACE + '.InitiatorHandle'] == handle + assert props[CHANNEL_INTERFACE + '.InitiatorID'] == contact_name + + # org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.State'] == FT_STATE_PENDING + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentType'] == FILE_CONTENT_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Filename'] == FILE_NAME + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Size'] == FILE_SIZE + # FT's protocol doesn't allow us the send the hash info + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType'] == FILE_HASH_TYPE_NONE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash'] == '' + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Description'] == FILE_DESCRIPTION + # FT's protocol doesn't allow us the send the date info + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Date'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes'] == \ + {SOCKET_ADDRESS_TYPE_UNIX: [SOCKET_ACCESS_CONTROL_LOCALHOST]} + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'] == 0 + + channel = make_channel_proxy(conn, path, 'Channel') + ft_channel = make_channel_proxy(conn, path, 'Channel.Type.FileTransfer.DRAFT') + ft_props = dbus.Interface(bus.get_object(conn.object.bus_name, path), PROPERTIES_IFACE) + + address = ft_channel.AcceptFile(SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, "", 5) + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_ACCEPTED + assert reason == FT_STATE_CHANGE_REASON_REQUESTED + + e = q.expect('dbus-signal', signal='InitialOffsetDefined') + offset = e.args[0] + # We don't support resume + assert offset == 0 + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_OPEN + assert reason == FT_STATE_CHANGE_REASON_NONE + + # Connect to Salut's socket + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(address) + + httpd.handle_request() + + # Receiver inform us he finished to download the file + q.expect('stream-iq', iq_type='result') + + # Read the file from Salut's socket + data = '' + read = 0 + while read < FILE_SIZE: + data += s.recv(FILE_SIZE - read) + read = len(data) + assert data == FILE_DATA + + e = q.expect('dbus-signal', signal='TransferredBytesChanged') + count = e.args[0] + while count < FILE_SIZE: + # Catch TransferredBytesChanged until we transfered all the data + e = q.expect('dbus-signal', signal='TransferredBytesChanged') + count = e.args[0] + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_COMPLETED + assert reason == FT_STATE_CHANGE_REASON_NONE + + channel.Close() + q.expect('dbus-signal', signal='Closed') + +class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + # is that the right file ? + filename = self.path.rsplit('/', 2)[-1] + assert filename == urllib.quote(FILE_NAME) + + self.send_response(200) + self.send_header('Content-type', FILE_CONTENT_TYPE) + self.end_headers() + self.wfile.write(FILE_DATA) + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-send-file-and-cancel-immediately.py b/tests/twisted/avahi/test-send-file-and-cancel-immediately.py new file mode 100644 index 00000000..3954e236 --- /dev/null +++ b/tests/twisted/avahi/test-send-file-and-cancel-immediately.py @@ -0,0 +1,202 @@ +import httplib +import urlparse +import dbus +import socket +import md5 +import urllib +import errno + +from saluttest import exec_test +from avahitest import AvahiAnnouncer +from avahitest import get_host_name + +from xmppstream import setup_stream_listener +from servicetest import make_channel_proxy, EventPattern + +from twisted.words.xish import domish, xpath + +from dbus import PROPERTIES_IFACE + +CONNECTION_INTERFACE_REQUESTS = 'org.freedesktop.Telepathy.Connection.Interface.Requests' +CHANNEL_INTERFACE ='org.freedesktop.Telepathy.Channel' +CHANNEL_TYPE_FILE_TRANSFER = 'org.freedesktop.Telepathy.Channel.Type.FileTransfer.DRAFT' + +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) + +FT_STATE_NONE = 0 +FT_STATE_PENDING = 1 +FT_STATE_ACCEPTED = 2 +FT_STATE_OPEN = 3 +FT_STATE_COMPLETED = 4 +FT_STATE_CANCELLED = 5 + +FT_STATE_CHANGE_REASON_NONE = 0 +FT_STATE_CHANGE_REASON_REQUESTED = 1 +FT_STATE_CHANGE_REASON_LOCAL_STOPPED = 2 + +FILE_HASH_TYPE_MD5 = 1 + +SOCKET_ADDRESS_TYPE_UNIX = 0 +SOCKET_ADDRESS_TYPE_IPV4 = 2 + +SOCKET_ACCESS_CONTROL_LOCALHOST = 0 + +# File to Offer +FILE_DATA = "That works!" +FILE_SIZE = len(FILE_DATA) +FILE_NAME = 'My test.txt' +FILE_CONTENT_TYPE = 'text/plain' +FILE_DESCRIPTION = 'A nice file to test' +FILE_HASH_TYPE = FILE_HASH_TYPE_MD5 +m = md5.new() +m.update(FILE_DATA) +FILE_HASH = m.hexdigest() + +# TODO: There is lot of duplicated code between FT tests. Would be good to +# refactor them. + +def test(q, bus, conn): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + basic_txt = { "txtvers": "1", "status": "avail" } + + self_handle = conn.GetSelfHandle() + self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0] + + contact_name = "test-file-receiver@" + get_host_name() + listener, port = setup_stream_listener(q, contact_name) + + AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt) + + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + + handle = 0 + # Wait until the record shows up in publish + while handle == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact_name: + handle = h + + requests_iface = dbus.Interface(conn, CONNECTION_INTERFACE_REQUESTS) + + # check if we can request FileTransfer channels + properties = conn.GetAll( + 'org.freedesktop.Telepathy.Connection.Interface.Requests', + dbus_interface='org.freedesktop.DBus.Properties') + + assert ({CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT}, + [CHANNEL_INTERFACE + '.TargetHandle', + CHANNEL_INTERFACE + '.TargetID', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType', + CHANNEL_TYPE_FILE_TRANSFER + '.Filename', + CHANNEL_TYPE_FILE_TRANSFER + '.Size', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash', + CHANNEL_TYPE_FILE_TRANSFER + '.Description', + CHANNEL_TYPE_FILE_TRANSFER + '.Date', + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'], + ) in properties.get('RequestableChannelClasses'),\ + properties['RequestableChannelClasses'] + + path, props = requests_iface.CreateChannel({ + CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT, + CHANNEL_INTERFACE + '.TargetHandle': handle, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType': FILE_CONTENT_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.Filename': FILE_NAME, + CHANNEL_TYPE_FILE_TRANSFER + '.Size': FILE_SIZE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType': FILE_HASH_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash': FILE_HASH, + CHANNEL_TYPE_FILE_TRANSFER + '.Description': FILE_DESCRIPTION, + CHANNEL_TYPE_FILE_TRANSFER + '.Date': 1225278834, + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset': 0, + }) + + # org.freedesktop.Telepathy.Channel D-Bus properties + assert props[CHANNEL_INTERFACE + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER + assert props[CHANNEL_INTERFACE + '.Interfaces'] == [] + assert props[CHANNEL_INTERFACE + '.TargetHandle'] == handle + assert props[CHANNEL_INTERFACE + '.TargetID'] == contact_name + assert props[CHANNEL_INTERFACE + '.TargetHandleType'] == HT_CONTACT + assert props[CHANNEL_INTERFACE + '.Requested'] == True + assert props[CHANNEL_INTERFACE + '.InitiatorHandle'] == self_handle + assert props[CHANNEL_INTERFACE + '.InitiatorID'] == self_handle_name + + # org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.State'] == FT_STATE_PENDING + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentType'] == FILE_CONTENT_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Filename'] == FILE_NAME + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Size'] == FILE_SIZE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType'] == FILE_HASH_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash'] == FILE_HASH + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Description'] == FILE_DESCRIPTION + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Date'] == 1225278834 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes'] == \ + {SOCKET_ADDRESS_TYPE_UNIX: [SOCKET_ACCESS_CONTROL_LOCALHOST]} + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'] == 0 + + channel = make_channel_proxy(conn, path, 'Channel') + ft_channel = make_channel_proxy(conn, path, 'Channel.Type.FileTransfer.DRAFT') + ft_props = dbus.Interface(bus.get_object(conn.object.bus_name, path), PROPERTIES_IFACE) + + conn_event, iq_event = q.expect_many( + EventPattern('incoming-connection', listener = listener), + EventPattern('stream-iq')) + + incoming = conn_event.connection + + assert iq_event.iq_type == 'set' + assert iq_event.connection == incoming + iq = iq_event.stanza + assert iq['to'] == contact_name + query = iq.firstChildElement() + assert query.uri == 'jabber:iq:oob' + url_node = xpath.queryForNodes("/iq/query/url", iq)[0] + assert url_node['type'] == 'file' + assert url_node['size'] == str(FILE_SIZE) + assert url_node['mimeType'] == FILE_CONTENT_TYPE + url = url_node.children[0] + _, host, file, _, _, _ = urlparse.urlparse(url) + urllib.unquote(file) == FILE_NAME + desc_node = xpath.queryForNodes("/iq/query/desc", iq)[0] + desc = desc_node.children[0] + assert desc == FILE_DESCRIPTION + + address = ft_channel.ProvideFile(SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, "") + + # state is still Pending as remote didn't accept the transfer yet + state = ft_props.Get(CHANNEL_TYPE_FILE_TRANSFER, 'State') + assert state == FT_STATE_PENDING + + # cancel the transfer before the receiver accepts it + channel.Close() + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_CANCELLED + assert reason == FT_STATE_CHANGE_REASON_LOCAL_STOPPED + + q.expect('dbus-signal', signal='Closed') + + # Connect HTTP client to the CM and request the file + http = httplib.HTTPConnection(host) + # can't retry the file as the transfer was cancelled + try: + http.request('GET', file) + except socket.error, e: + code, msg = e.args + assert errno.errorcode[code] == 'ECONNREFUSED' + else: + assert False + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-send-file-and-disconnect.py b/tests/twisted/avahi/test-send-file-and-disconnect.py new file mode 100644 index 00000000..aa2def19 --- /dev/null +++ b/tests/twisted/avahi/test-send-file-and-disconnect.py @@ -0,0 +1,183 @@ +import httplib +import urlparse +import dbus +import socket +import md5 +import urllib + +from saluttest import exec_test +from avahitest import AvahiAnnouncer +from avahitest import get_host_name + +from xmppstream import setup_stream_listener +from servicetest import make_channel_proxy, EventPattern + +from twisted.words.xish import domish, xpath + +from dbus import PROPERTIES_IFACE + +CONNECTION_INTERFACE_REQUESTS = 'org.freedesktop.Telepathy.Connection.Interface.Requests' +CHANNEL_INTERFACE ='org.freedesktop.Telepathy.Channel' +CHANNEL_TYPE_FILE_TRANSFER = 'org.freedesktop.Telepathy.Channel.Type.FileTransfer.DRAFT' + +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) + +FT_STATE_NONE = 0 +FT_STATE_PENDING = 1 +FT_STATE_ACCEPTED = 2 +FT_STATE_OPEN = 3 +FT_STATE_COMPLETED = 4 +FT_STATE_CANCELLED = 5 + +FT_STATE_CHANGE_REASON_NONE = 0 +FT_STATE_CHANGE_REASON_REQUESTED = 1 + +FILE_HASH_TYPE_MD5 = 1 + +SOCKET_ADDRESS_TYPE_UNIX = 0 +SOCKET_ADDRESS_TYPE_IPV4 = 2 + +SOCKET_ACCESS_CONTROL_LOCALHOST = 0 + +# File to Offer +FILE_DATA = "That works!" +FILE_SIZE = len(FILE_DATA) +FILE_NAME = 'My test.txt' +FILE_CONTENT_TYPE = 'text/plain' +FILE_DESCRIPTION = 'A nice file to test' +FILE_HASH_TYPE = FILE_HASH_TYPE_MD5 +m = md5.new() +m.update(FILE_DATA) +FILE_HASH = m.hexdigest() + +# TODO: There is lot of duplicated code between FT tests. Would be good to +# refactor them. + +def test(q, bus, conn): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + basic_txt = { "txtvers": "1", "status": "avail" } + + self_handle = conn.GetSelfHandle() + self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0] + + contact_name = "test-file-receiver@" + get_host_name() + listener, port = setup_stream_listener(q, contact_name) + + AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt) + + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + + handle = 0 + # Wait until the record shows up in publish + while handle == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact_name: + handle = h + + requests_iface = dbus.Interface(conn, CONNECTION_INTERFACE_REQUESTS) + + # check if we can request FileTransfer channels + properties = conn.GetAll( + 'org.freedesktop.Telepathy.Connection.Interface.Requests', + dbus_interface='org.freedesktop.DBus.Properties') + + assert ({CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT}, + [CHANNEL_INTERFACE + '.TargetHandle', + CHANNEL_INTERFACE + '.TargetID', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType', + CHANNEL_TYPE_FILE_TRANSFER + '.Filename', + CHANNEL_TYPE_FILE_TRANSFER + '.Size', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash', + CHANNEL_TYPE_FILE_TRANSFER + '.Description', + CHANNEL_TYPE_FILE_TRANSFER + '.Date', + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'], + ) in properties.get('RequestableChannelClasses'),\ + properties['RequestableChannelClasses'] + + path, props = requests_iface.CreateChannel({ + CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT, + CHANNEL_INTERFACE + '.TargetHandle': handle, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType': FILE_CONTENT_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.Filename': FILE_NAME, + CHANNEL_TYPE_FILE_TRANSFER + '.Size': FILE_SIZE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType': FILE_HASH_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash': FILE_HASH, + CHANNEL_TYPE_FILE_TRANSFER + '.Description': FILE_DESCRIPTION, + CHANNEL_TYPE_FILE_TRANSFER + '.Date': 1225278834, + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset': 0, + }) + + # org.freedesktop.Telepathy.Channel D-Bus properties + assert props[CHANNEL_INTERFACE + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER + assert props[CHANNEL_INTERFACE + '.Interfaces'] == [] + assert props[CHANNEL_INTERFACE + '.TargetHandle'] == handle + assert props[CHANNEL_INTERFACE + '.TargetID'] == contact_name + assert props[CHANNEL_INTERFACE + '.TargetHandleType'] == HT_CONTACT + assert props[CHANNEL_INTERFACE + '.Requested'] == True + assert props[CHANNEL_INTERFACE + '.InitiatorHandle'] == self_handle + assert props[CHANNEL_INTERFACE + '.InitiatorID'] == self_handle_name + + # org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.State'] == FT_STATE_PENDING + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentType'] == FILE_CONTENT_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Filename'] == FILE_NAME + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Size'] == FILE_SIZE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType'] == FILE_HASH_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash'] == FILE_HASH + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Description'] == FILE_DESCRIPTION + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Date'] == 1225278834 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes'] == \ + {SOCKET_ADDRESS_TYPE_UNIX: [SOCKET_ACCESS_CONTROL_LOCALHOST]} + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'] == 0 + + channel = make_channel_proxy(conn, path, 'Channel') + ft_channel = make_channel_proxy(conn, path, 'Channel.Type.FileTransfer.DRAFT') + ft_props = dbus.Interface(bus.get_object(conn.object.bus_name, path), PROPERTIES_IFACE) + + conn_event, iq_event = q.expect_many( + EventPattern('incoming-connection', listener = listener), + EventPattern('stream-iq')) + + incoming = conn_event.connection + + assert iq_event.iq_type == 'set' + assert iq_event.connection == incoming + iq = iq_event.stanza + assert iq['to'] == contact_name + query = iq.firstChildElement() + assert query.uri == 'jabber:iq:oob' + url_node = xpath.queryForNodes("/iq/query/url", iq)[0] + assert url_node['type'] == 'file' + assert url_node['size'] == str(FILE_SIZE) + assert url_node['mimeType'] == FILE_CONTENT_TYPE + url = url_node.children[0] + _, host, file, _, _, _ = urlparse.urlparse(url) + urllib.unquote(file) == FILE_NAME + desc_node = xpath.queryForNodes("/iq/query/desc", iq)[0] + desc = desc_node.children[0] + assert desc == FILE_DESCRIPTION + + address = ft_channel.ProvideFile(SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, "") + + # state is still Pending as remote didn't accept the transfer yet + state = ft_props.Get(CHANNEL_TYPE_FILE_TRANSFER, 'State') + assert state == FT_STATE_PENDING + + # regression test: Salut used to crash at this point + conn.Disconnect() + q.expect('dbus-signal', signal='StatusChanged', args=[2L, 1L]) + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-send-file-declined.py b/tests/twisted/avahi/test-send-file-declined.py new file mode 100644 index 00000000..6b7ca9ca --- /dev/null +++ b/tests/twisted/avahi/test-send-file-declined.py @@ -0,0 +1,202 @@ +import httplib +import urlparse +import dbus +import socket +import md5 + +from saluttest import exec_test +from avahitest import AvahiAnnouncer +from avahitest import get_host_name + +from xmppstream import setup_stream_listener +from servicetest import make_channel_proxy, EventPattern + +from twisted.words.xish import domish, xpath + +from dbus import PROPERTIES_IFACE + +CONNECTION_INTERFACE_REQUESTS = 'org.freedesktop.Telepathy.Connection.Interface.Requests' +CHANNEL_INTERFACE ='org.freedesktop.Telepathy.Channel' +CHANNEL_TYPE_FILE_TRANSFER = 'org.freedesktop.Telepathy.Channel.Type.FileTransfer.DRAFT' + +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) + +FT_STATE_NONE = 0 +FT_STATE_PENDING = 1 +FT_STATE_ACCEPTED = 2 +FT_STATE_OPEN = 3 +FT_STATE_COMPLETED = 4 +FT_STATE_CANCELLED = 5 + +FT_STATE_CHANGE_REASON_NONE = 0 +FT_STATE_CHANGE_REASON_REQUESTED = 1 +FT_STATE_CHANGE_REASON_LOCAL_STOPPED = 2 +FT_STATE_CHANGE_REASON_REMOTE_STOPPED = 3 +FT_STATE_CHANGE_REASON_LOCAL_ERROR = 4 +FT_STATE_CHANGE_REASON_REMOTE_ERROR = 5 + +FILE_HASH_TYPE_MD5 = 1 + +SOCKET_ADDRESS_TYPE_UNIX = 0 +SOCKET_ADDRESS_TYPE_IPV4 = 2 + +SOCKET_ACCESS_CONTROL_LOCALHOST = 0 + +# File to Offer +FILE_DATA = "That works!" +FILE_SIZE = len(FILE_DATA) +FILE_NAME = 'test.txt' +FILE_CONTENT_TYPE = 'text/plain' +FILE_DESCRIPTION = 'A nice file to test' +FILE_HASH_TYPE = FILE_HASH_TYPE_MD5 +m = md5.new() +m.update(FILE_DATA) +FILE_HASH = m.hexdigest() + +def test(q, bus, conn): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + basic_txt = { "txtvers": "1", "status": "avail" } + + self_handle = conn.GetSelfHandle() + self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0] + + contact_name = "test-file-receiver@" + get_host_name() + listener, port = setup_stream_listener(q, contact_name) + + AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt) + + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + + handle = 0 + # Wait until the record shows up in publish + while handle == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact_name: + handle = h + + requests_iface = dbus.Interface(conn, CONNECTION_INTERFACE_REQUESTS) + + # check if we can request FileTransfer channels + properties = conn.GetAll( + 'org.freedesktop.Telepathy.Connection.Interface.Requests', + dbus_interface='org.freedesktop.DBus.Properties') + + assert ({CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT}, + [CHANNEL_INTERFACE + '.TargetHandle', + CHANNEL_INTERFACE + '.TargetID', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType', + CHANNEL_TYPE_FILE_TRANSFER + '.Filename', + CHANNEL_TYPE_FILE_TRANSFER + '.Size', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash', + CHANNEL_TYPE_FILE_TRANSFER + '.Description', + CHANNEL_TYPE_FILE_TRANSFER + '.Date', + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'], + ) in properties.get('RequestableChannelClasses'),\ + properties['RequestableChannelClasses'] + + path, props = requests_iface.CreateChannel({ + CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT, + CHANNEL_INTERFACE + '.TargetHandle': handle, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType': FILE_CONTENT_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.Filename': FILE_NAME, + CHANNEL_TYPE_FILE_TRANSFER + '.Size': FILE_SIZE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType': FILE_HASH_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash': FILE_HASH, + CHANNEL_TYPE_FILE_TRANSFER + '.Description': FILE_DESCRIPTION, + CHANNEL_TYPE_FILE_TRANSFER + '.Date': 1225278834, + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset': 0, + }) + + # org.freedesktop.Telepathy.Channel D-Bus properties + assert props[CHANNEL_INTERFACE + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER + assert props[CHANNEL_INTERFACE + '.Interfaces'] == [] + assert props[CHANNEL_INTERFACE + '.TargetHandle'] == handle + assert props[CHANNEL_INTERFACE + '.TargetID'] == contact_name + assert props[CHANNEL_INTERFACE + '.TargetHandleType'] == HT_CONTACT + assert props[CHANNEL_INTERFACE + '.Requested'] == True + assert props[CHANNEL_INTERFACE + '.InitiatorHandle'] == self_handle + assert props[CHANNEL_INTERFACE + '.InitiatorID'] == self_handle_name + + # org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.State'] == FT_STATE_PENDING + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentType'] == FILE_CONTENT_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Filename'] == FILE_NAME + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Size'] == FILE_SIZE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType'] == FILE_HASH_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash'] == FILE_HASH + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Description'] == FILE_DESCRIPTION + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Date'] == 1225278834 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes'] == \ + {SOCKET_ADDRESS_TYPE_UNIX: [SOCKET_ACCESS_CONTROL_LOCALHOST]} + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'] == 0 + + channel = make_channel_proxy(conn, path, 'Channel') + ft_channel = make_channel_proxy(conn, path, 'Channel.Type.FileTransfer.DRAFT') + ft_props = dbus.Interface(bus.get_object(conn.object.bus_name, path), PROPERTIES_IFACE) + + address = ft_channel.ProvideFile(SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, "") + + conn_event, iq_event = q.expect_many( + EventPattern('incoming-connection', listener = listener), + EventPattern('stream-iq')) + + incoming = conn_event.connection + + assert iq_event.iq_type == 'set' + assert iq_event.connection == incoming + iq = iq_event.stanza + assert iq['to'] == contact_name + query = iq.firstChildElement() + assert query.uri == 'jabber:iq:oob' + url_node = xpath.queryForNodes("/iq/query/url", iq)[0] + assert url_node['type'] == 'file' + assert url_node['size'] == str(FILE_SIZE) + assert url_node['mimeType'] == FILE_CONTENT_TYPE + url = url_node.children[0] + assert url.endswith(FILE_NAME) + desc_node = xpath.queryForNodes("/iq/query/desc", iq)[0] + desc = desc_node.children[0] + assert desc == FILE_DESCRIPTION + + # Receiver declines the file offer + reply = domish.Element(('', 'iq')) + reply['to'] = iq['from'] + reply['from'] = iq['to'] + reply['type'] = 'error' + reply['id'] = iq['id'] + query = reply.addElement(('jabber:iq:oob', 'query')) + url_node = query.addElement('url', content=url) + query.addElement('desc', content=desc) + error_node = reply.addElement((None, 'error')) + error_node['code'] = '406' + error_node['type'] = 'modify' + not_acceptable_node = error_node.addElement(('urn:ietf:params:xml:ns:xmpp-stanzas', + 'not-acceptable')) + incoming.send(reply) + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_CANCELLED, state + assert reason == FT_STATE_CHANGE_REASON_REMOTE_STOPPED + + transferred = ft_props.Get(CHANNEL_TYPE_FILE_TRANSFER, 'TransferredBytes') + # no byte has been transferred as the file was declined + assert transferred == 0 + + channel.Close() + q.expect('dbus-signal', signal='Closed') + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-send-file-item-not-found.py b/tests/twisted/avahi/test-send-file-item-not-found.py new file mode 100644 index 00000000..095dd5bd --- /dev/null +++ b/tests/twisted/avahi/test-send-file-item-not-found.py @@ -0,0 +1,202 @@ +import httplib +import urlparse +import dbus +import socket +import md5 + +from saluttest import exec_test +from avahitest import AvahiAnnouncer +from avahitest import get_host_name + +from xmppstream import setup_stream_listener +from servicetest import make_channel_proxy, EventPattern + +from twisted.words.xish import domish, xpath + +from dbus import PROPERTIES_IFACE + +CONNECTION_INTERFACE_REQUESTS = 'org.freedesktop.Telepathy.Connection.Interface.Requests' +CHANNEL_INTERFACE ='org.freedesktop.Telepathy.Channel' +CHANNEL_TYPE_FILE_TRANSFER = 'org.freedesktop.Telepathy.Channel.Type.FileTransfer.DRAFT' + +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) + +FT_STATE_NONE = 0 +FT_STATE_PENDING = 1 +FT_STATE_ACCEPTED = 2 +FT_STATE_OPEN = 3 +FT_STATE_COMPLETED = 4 +FT_STATE_CANCELLED = 5 + +FT_STATE_CHANGE_REASON_NONE = 0 +FT_STATE_CHANGE_REASON_REQUESTED = 1 +FT_STATE_CHANGE_REASON_LOCAL_STOPPED = 2 +FT_STATE_CHANGE_REASON_REMOTE_STOPPED = 3 +FT_STATE_CHANGE_REASON_LOCAL_ERROR = 4 +FT_STATE_CHANGE_REASON_REMOTE_ERROR = 5 + +FILE_HASH_TYPE_MD5 = 1 + +SOCKET_ADDRESS_TYPE_UNIX = 0 +SOCKET_ADDRESS_TYPE_IPV4 = 2 + +SOCKET_ACCESS_CONTROL_LOCALHOST = 0 + +# File to Offer +FILE_DATA = "That works!" +FILE_SIZE = len(FILE_DATA) +FILE_NAME = 'test.txt' +FILE_CONTENT_TYPE = 'text/plain' +FILE_DESCRIPTION = 'A nice file to test' +FILE_HASH_TYPE = FILE_HASH_TYPE_MD5 +m = md5.new() +m.update(FILE_DATA) +FILE_HASH = m.hexdigest() + +def test(q, bus, conn): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + basic_txt = { "txtvers": "1", "status": "avail" } + + self_handle = conn.GetSelfHandle() + self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0] + + contact_name = "test-file-receiver@" + get_host_name() + listener, port = setup_stream_listener(q, contact_name) + + AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt) + + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + + handle = 0 + # Wait until the record shows up in publish + while handle == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact_name: + handle = h + + requests_iface = dbus.Interface(conn, CONNECTION_INTERFACE_REQUESTS) + + # check if we can request FileTransfer channels + properties = conn.GetAll( + 'org.freedesktop.Telepathy.Connection.Interface.Requests', + dbus_interface='org.freedesktop.DBus.Properties') + + assert ({CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT}, + [CHANNEL_INTERFACE + '.TargetHandle', + CHANNEL_INTERFACE + '.TargetID', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType', + CHANNEL_TYPE_FILE_TRANSFER + '.Filename', + CHANNEL_TYPE_FILE_TRANSFER + '.Size', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash', + CHANNEL_TYPE_FILE_TRANSFER + '.Description', + CHANNEL_TYPE_FILE_TRANSFER + '.Date', + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'], + ) in properties.get('RequestableChannelClasses'),\ + properties['RequestableChannelClasses'] + + path, props = requests_iface.CreateChannel({ + CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT, + CHANNEL_INTERFACE + '.TargetHandle': handle, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType': FILE_CONTENT_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.Filename': FILE_NAME, + CHANNEL_TYPE_FILE_TRANSFER + '.Size': FILE_SIZE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType': FILE_HASH_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash': FILE_HASH, + CHANNEL_TYPE_FILE_TRANSFER + '.Description': FILE_DESCRIPTION, + CHANNEL_TYPE_FILE_TRANSFER + '.Date': 1225278834, + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset': 0, + }) + + # org.freedesktop.Telepathy.Channel D-Bus properties + assert props[CHANNEL_INTERFACE + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER + assert props[CHANNEL_INTERFACE + '.Interfaces'] == [] + assert props[CHANNEL_INTERFACE + '.TargetHandle'] == handle + assert props[CHANNEL_INTERFACE + '.TargetID'] == contact_name + assert props[CHANNEL_INTERFACE + '.TargetHandleType'] == HT_CONTACT + assert props[CHANNEL_INTERFACE + '.Requested'] == True + assert props[CHANNEL_INTERFACE + '.InitiatorHandle'] == self_handle + assert props[CHANNEL_INTERFACE + '.InitiatorID'] == self_handle_name + + # org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.State'] == FT_STATE_PENDING + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentType'] == FILE_CONTENT_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Filename'] == FILE_NAME + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Size'] == FILE_SIZE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType'] == FILE_HASH_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash'] == FILE_HASH + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Description'] == FILE_DESCRIPTION + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Date'] == 1225278834 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes'] == \ + {SOCKET_ADDRESS_TYPE_UNIX: [SOCKET_ACCESS_CONTROL_LOCALHOST]} + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'] == 0 + + channel = make_channel_proxy(conn, path, 'Channel') + ft_channel = make_channel_proxy(conn, path, 'Channel.Type.FileTransfer.DRAFT') + ft_props = dbus.Interface(bus.get_object(conn.object.bus_name, path), PROPERTIES_IFACE) + + address = ft_channel.ProvideFile(SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, "") + + conn_event, iq_event = q.expect_many( + EventPattern('incoming-connection', listener = listener), + EventPattern('stream-iq')) + + incoming = conn_event.connection + + assert iq_event.iq_type == 'set' + assert iq_event.connection == incoming + iq = iq_event.stanza + assert iq['to'] == contact_name + query = iq.firstChildElement() + assert query.uri == 'jabber:iq:oob' + url_node = xpath.queryForNodes("/iq/query/url", iq)[0] + assert url_node['type'] == 'file' + assert url_node['size'] == str(FILE_SIZE) + assert url_node['mimeType'] == FILE_CONTENT_TYPE + url = url_node.children[0] + assert url.endswith(FILE_NAME) + desc_node = xpath.queryForNodes("/iq/query/desc", iq)[0] + desc = desc_node.children[0] + assert desc == FILE_DESCRIPTION + + # Receiver can't retrieve the file + reply = domish.Element(('', 'iq')) + reply['to'] = iq['from'] + reply['from'] = iq['to'] + reply['type'] = 'error' + reply['id'] = iq['id'] + query = reply.addElement(('jabber:iq:oob', 'query')) + url_node = query.addElement('url', content=url) + query.addElement('desc', content=desc) + error_node = reply.addElement((None, 'error')) + error_node['code'] = '404' + error_node['type'] = 'modify' + error_node.addElement(('urn:ietf:params:xml:ns:xmpp-stanzas', + 'item-not-found')) + incoming.send(reply) + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_CANCELLED, state + assert reason == FT_STATE_CHANGE_REASON_REMOTE_ERROR + + transferred = ft_props.Get(CHANNEL_TYPE_FILE_TRANSFER, 'TransferredBytes') + # no byte has been transferred as the transfer failed + assert transferred == 0 + + channel.Close() + q.expect('dbus-signal', signal='Closed') + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-send-file-provide-immediately.py b/tests/twisted/avahi/test-send-file-provide-immediately.py new file mode 100644 index 00000000..73f1fda5 --- /dev/null +++ b/tests/twisted/avahi/test-send-file-provide-immediately.py @@ -0,0 +1,228 @@ +import httplib +import urlparse +import dbus +import socket +import md5 +import urllib + +from saluttest import exec_test +from avahitest import AvahiAnnouncer +from avahitest import get_host_name + +from xmppstream import setup_stream_listener +from servicetest import make_channel_proxy, EventPattern + +from twisted.words.xish import domish, xpath + +from dbus import PROPERTIES_IFACE + +CONNECTION_INTERFACE_REQUESTS = 'org.freedesktop.Telepathy.Connection.Interface.Requests' +CHANNEL_INTERFACE ='org.freedesktop.Telepathy.Channel' +CHANNEL_TYPE_FILE_TRANSFER = 'org.freedesktop.Telepathy.Channel.Type.FileTransfer.DRAFT' + +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) + +FT_STATE_NONE = 0 +FT_STATE_PENDING = 1 +FT_STATE_ACCEPTED = 2 +FT_STATE_OPEN = 3 +FT_STATE_COMPLETED = 4 +FT_STATE_CANCELLED = 5 + +FT_STATE_CHANGE_REASON_NONE = 0 +FT_STATE_CHANGE_REASON_REQUESTED = 1 + +FILE_HASH_TYPE_MD5 = 1 + +SOCKET_ADDRESS_TYPE_UNIX = 0 +SOCKET_ADDRESS_TYPE_IPV4 = 2 + +SOCKET_ACCESS_CONTROL_LOCALHOST = 0 + +# File to Offer +FILE_DATA = "That works!" +FILE_SIZE = len(FILE_DATA) +FILE_NAME = 'My test.txt' +FILE_CONTENT_TYPE = 'text/plain' +FILE_DESCRIPTION = 'A nice file to test' +FILE_HASH_TYPE = FILE_HASH_TYPE_MD5 +m = md5.new() +m.update(FILE_DATA) +FILE_HASH = m.hexdigest() + +# TODO: There is lot of duplicated code between FT tests. Would be good to +# refactor them. + +def test(q, bus, conn): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + basic_txt = { "txtvers": "1", "status": "avail" } + + self_handle = conn.GetSelfHandle() + self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0] + + contact_name = "test-file-receiver@" + get_host_name() + listener, port = setup_stream_listener(q, contact_name) + + AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt) + + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + + handle = 0 + # Wait until the record shows up in publish + while handle == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact_name: + handle = h + + requests_iface = dbus.Interface(conn, CONNECTION_INTERFACE_REQUESTS) + + # check if we can request FileTransfer channels + properties = conn.GetAll( + 'org.freedesktop.Telepathy.Connection.Interface.Requests', + dbus_interface='org.freedesktop.DBus.Properties') + + assert ({CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT}, + [CHANNEL_INTERFACE + '.TargetHandle', + CHANNEL_INTERFACE + '.TargetID', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType', + CHANNEL_TYPE_FILE_TRANSFER + '.Filename', + CHANNEL_TYPE_FILE_TRANSFER + '.Size', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash', + CHANNEL_TYPE_FILE_TRANSFER + '.Description', + CHANNEL_TYPE_FILE_TRANSFER + '.Date', + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'], + ) in properties.get('RequestableChannelClasses'),\ + properties['RequestableChannelClasses'] + + path, props = requests_iface.CreateChannel({ + CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT, + CHANNEL_INTERFACE + '.TargetHandle': handle, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType': FILE_CONTENT_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.Filename': FILE_NAME, + CHANNEL_TYPE_FILE_TRANSFER + '.Size': FILE_SIZE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType': FILE_HASH_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash': FILE_HASH, + CHANNEL_TYPE_FILE_TRANSFER + '.Description': FILE_DESCRIPTION, + CHANNEL_TYPE_FILE_TRANSFER + '.Date': 1225278834, + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset': 0, + }) + + # org.freedesktop.Telepathy.Channel D-Bus properties + assert props[CHANNEL_INTERFACE + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER + assert props[CHANNEL_INTERFACE + '.Interfaces'] == [] + assert props[CHANNEL_INTERFACE + '.TargetHandle'] == handle + assert props[CHANNEL_INTERFACE + '.TargetID'] == contact_name + assert props[CHANNEL_INTERFACE + '.TargetHandleType'] == HT_CONTACT + assert props[CHANNEL_INTERFACE + '.Requested'] == True + assert props[CHANNEL_INTERFACE + '.InitiatorHandle'] == self_handle + assert props[CHANNEL_INTERFACE + '.InitiatorID'] == self_handle_name + + # org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.State'] == FT_STATE_PENDING + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentType'] == FILE_CONTENT_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Filename'] == FILE_NAME + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Size'] == FILE_SIZE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType'] == FILE_HASH_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash'] == FILE_HASH + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Description'] == FILE_DESCRIPTION + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Date'] == 1225278834 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes'] == \ + {SOCKET_ADDRESS_TYPE_UNIX: [SOCKET_ACCESS_CONTROL_LOCALHOST]} + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'] == 0 + + channel = make_channel_proxy(conn, path, 'Channel') + ft_channel = make_channel_proxy(conn, path, 'Channel.Type.FileTransfer.DRAFT') + ft_props = dbus.Interface(bus.get_object(conn.object.bus_name, path), PROPERTIES_IFACE) + + conn_event, iq_event = q.expect_many( + EventPattern('incoming-connection', listener = listener), + EventPattern('stream-iq')) + + incoming = conn_event.connection + + assert iq_event.iq_type == 'set' + assert iq_event.connection == incoming + iq = iq_event.stanza + assert iq['to'] == contact_name + query = iq.firstChildElement() + assert query.uri == 'jabber:iq:oob' + url_node = xpath.queryForNodes("/iq/query/url", iq)[0] + assert url_node['type'] == 'file' + assert url_node['size'] == str(FILE_SIZE) + assert url_node['mimeType'] == FILE_CONTENT_TYPE + url = url_node.children[0] + _, host, file, _, _, _ = urlparse.urlparse(url) + urllib.unquote(file) == FILE_NAME + desc_node = xpath.queryForNodes("/iq/query/desc", iq)[0] + desc = desc_node.children[0] + assert desc == FILE_DESCRIPTION + + address = ft_channel.ProvideFile(SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, "") + + # state is still Pending as remote didn't accept the transfer yet + state = ft_props.Get(CHANNEL_TYPE_FILE_TRANSFER, 'State') + assert state == FT_STATE_PENDING + + # Connect HTTP client to the CM and request the file + http = httplib.HTTPConnection(host) + http.request('GET', file) + + e = q.expect('dbus-signal', signal='InitialOffsetDefined') + offset = e.args[0] + # We don't support resume + assert offset == 0 + + # Channel is open. We can start to send the file + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_OPEN + assert reason == FT_STATE_CHANGE_REASON_NONE + + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(address) + s.send(FILE_DATA) + + e = q.expect('dbus-signal', signal='TransferredBytesChanged') + + count = e.args[0] + while count < FILE_SIZE: + # Catch TransferredBytesChanged until we transfered all the data + e = q.expect('dbus-signal', signal='TransferredBytesChanged') + count = e.args[0] + + response = http.getresponse() + assert (response.status, response.reason) == (200, 'OK') + data = response.read(FILE_SIZE) + # Did we received the right file? + assert data == FILE_DATA + + # Inform sender that we received all the file from the OOB transfer + reply = domish.Element(('', 'iq')) + reply['to'] = iq['from'] + reply['from'] = iq['to'] + reply['type'] = 'result' + reply['id'] = iq['id'] + incoming.send(reply) + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_COMPLETED + assert reason == FT_STATE_CHANGE_REASON_NONE + + channel.Close() + q.expect('dbus-signal', signal='Closed') + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-send-file-to-unknown-contact.py b/tests/twisted/avahi/test-send-file-to-unknown-contact.py new file mode 100644 index 00000000..0f1c8f77 --- /dev/null +++ b/tests/twisted/avahi/test-send-file-to-unknown-contact.py @@ -0,0 +1,101 @@ +import httplib +import urlparse +import dbus +import socket +import md5 + +from saluttest import exec_test +from avahitest import AvahiAnnouncer +from avahitest import get_host_name + +from xmppstream import setup_stream_listener +from servicetest import make_channel_proxy, EventPattern, call_async + +from twisted.words.xish import domish, xpath + +from dbus import PROPERTIES_IFACE + +CONNECTION_INTERFACE_REQUESTS = 'org.freedesktop.Telepathy.Connection.Interface.Requests' +CHANNEL_INTERFACE ='org.freedesktop.Telepathy.Channel' +CHANNEL_TYPE_FILE_TRANSFER = 'org.freedesktop.Telepathy.Channel.Type.FileTransfer.DRAFT' + +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) + +FT_STATE_NONE = 0 +FT_STATE_PENDING = 1 +FT_STATE_ACCEPTED = 2 +FT_STATE_OPEN = 3 +FT_STATE_COMPLETED = 4 +FT_STATE_CANCELLED = 5 + +FT_STATE_CHANGE_REASON_NONE = 0 +FT_STATE_CHANGE_REASON_REQUESTED = 1 + +FILE_HASH_TYPE_MD5 = 1 + +SOCKET_ADDRESS_TYPE_UNIX = 0 +SOCKET_ADDRESS_TYPE_IPV4 = 2 + +SOCKET_ACCESS_CONTROL_LOCALHOST = 0 + +# File to Offer +FILE_DATA = "That works!" +FILE_SIZE = len(FILE_DATA) +FILE_NAME = 'test.txt' +FILE_CONTENT_TYPE = 'text/plain' +FILE_DESCRIPTION = 'A nice file to test' +FILE_HASH_TYPE = FILE_HASH_TYPE_MD5 +m = md5.new() +m.update(FILE_DATA) +FILE_HASH = m.hexdigest() + +def test(q, bus, conn): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + + contact_name = "test-file-receiver@" + get_host_name() + handle = conn.RequestHandles(HT_CONTACT, [contact_name])[0] + + requests_iface = dbus.Interface(conn, CONNECTION_INTERFACE_REQUESTS) + + # check if we can request FileTransfer channels + properties = conn.GetAll( + 'org.freedesktop.Telepathy.Connection.Interface.Requests', + dbus_interface='org.freedesktop.DBus.Properties') + + assert ({CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT}, + [CHANNEL_INTERFACE + '.TargetHandle', + CHANNEL_INTERFACE + '.TargetID', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType', + CHANNEL_TYPE_FILE_TRANSFER + '.Filename', + CHANNEL_TYPE_FILE_TRANSFER + '.Size', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash', + CHANNEL_TYPE_FILE_TRANSFER + '.Description', + CHANNEL_TYPE_FILE_TRANSFER + '.Date', + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'], + ) in properties.get('RequestableChannelClasses'),\ + properties['RequestableChannelClasses'] + + call_async(q, requests_iface, 'CreateChannel', + {CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT, + CHANNEL_INTERFACE + '.TargetHandle': handle, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType': FILE_CONTENT_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.Filename': FILE_NAME, + CHANNEL_TYPE_FILE_TRANSFER + '.Size': FILE_SIZE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType': FILE_HASH_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash': FILE_HASH, + CHANNEL_TYPE_FILE_TRANSFER + '.Description': FILE_DESCRIPTION, + CHANNEL_TYPE_FILE_TRANSFER + '.Date': 1225278834, + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset': 0, + }) + + e = q.expect('dbus-error') + assert e.error.get_dbus_name() == 'org.freedesktop.Telepathy.Errors.NotAvailable' + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-send-file-wait-to-provide.py b/tests/twisted/avahi/test-send-file-wait-to-provide.py new file mode 100644 index 00000000..2c7b521b --- /dev/null +++ b/tests/twisted/avahi/test-send-file-wait-to-provide.py @@ -0,0 +1,235 @@ +import httplib +import urlparse +import dbus +import socket +import md5 +import urllib + +from saluttest import exec_test +from avahitest import AvahiAnnouncer +from avahitest import get_host_name + +from xmppstream import setup_stream_listener +from servicetest import make_channel_proxy, EventPattern + +from twisted.words.xish import domish, xpath + +from dbus import PROPERTIES_IFACE + +CONNECTION_INTERFACE_REQUESTS = 'org.freedesktop.Telepathy.Connection.Interface.Requests' +CHANNEL_INTERFACE ='org.freedesktop.Telepathy.Channel' +CHANNEL_TYPE_FILE_TRANSFER = 'org.freedesktop.Telepathy.Channel.Type.FileTransfer.DRAFT' + +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) + +FT_STATE_NONE = 0 +FT_STATE_PENDING = 1 +FT_STATE_ACCEPTED = 2 +FT_STATE_OPEN = 3 +FT_STATE_COMPLETED = 4 +FT_STATE_CANCELLED = 5 + +FT_STATE_CHANGE_REASON_NONE = 0 +FT_STATE_CHANGE_REASON_REQUESTED = 1 + +FILE_HASH_TYPE_MD5 = 1 + +SOCKET_ADDRESS_TYPE_UNIX = 0 +SOCKET_ADDRESS_TYPE_IPV4 = 2 + +SOCKET_ACCESS_CONTROL_LOCALHOST = 0 + +# File to Offer +FILE_DATA = "That works!" +FILE_SIZE = len(FILE_DATA) +FILE_NAME = 'test.txt' +FILE_CONTENT_TYPE = 'text/plain' +FILE_DESCRIPTION = 'A nice file to test' +FILE_HASH_TYPE = FILE_HASH_TYPE_MD5 +m = md5.new() +m.update(FILE_DATA) +FILE_HASH = m.hexdigest() + +def test(q, bus, conn): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + basic_txt = { "txtvers": "1", "status": "avail" } + + self_handle = conn.GetSelfHandle() + self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0] + + contact_name = "test-file-receiver@" + get_host_name() + listener, port = setup_stream_listener(q, contact_name) + + AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt) + + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + + handle = 0 + # Wait until the record shows up in publish + while handle == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact_name: + handle = h + + requests_iface = dbus.Interface(conn, CONNECTION_INTERFACE_REQUESTS) + + # check if we can request FileTransfer channels + properties = conn.GetAll( + 'org.freedesktop.Telepathy.Connection.Interface.Requests', + dbus_interface='org.freedesktop.DBus.Properties') + + assert ({CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT}, + [CHANNEL_INTERFACE + '.TargetHandle', + CHANNEL_INTERFACE + '.TargetID', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType', + CHANNEL_TYPE_FILE_TRANSFER + '.Filename', + CHANNEL_TYPE_FILE_TRANSFER + '.Size', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType', + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash', + CHANNEL_TYPE_FILE_TRANSFER + '.Description', + CHANNEL_TYPE_FILE_TRANSFER + '.Date', + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'], + ) in properties.get('RequestableChannelClasses'),\ + properties['RequestableChannelClasses'] + + path, props = requests_iface.CreateChannel({ + CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL_INTERFACE + '.TargetHandleType': HT_CONTACT, + CHANNEL_INTERFACE + '.TargetHandle': handle, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentType': FILE_CONTENT_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.Filename': FILE_NAME, + CHANNEL_TYPE_FILE_TRANSFER + '.Size': FILE_SIZE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType': FILE_HASH_TYPE, + CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash': FILE_HASH, + CHANNEL_TYPE_FILE_TRANSFER + '.Description': FILE_DESCRIPTION, + CHANNEL_TYPE_FILE_TRANSFER + '.Date': 1225278834, + CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset': 0, + }) + + # org.freedesktop.Telepathy.Channel D-Bus properties + assert props[CHANNEL_INTERFACE + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER + assert props[CHANNEL_INTERFACE + '.Interfaces'] == [] + assert props[CHANNEL_INTERFACE + '.TargetHandle'] == handle + assert props[CHANNEL_INTERFACE + '.TargetID'] == contact_name + assert props[CHANNEL_INTERFACE + '.TargetHandleType'] == HT_CONTACT + assert props[CHANNEL_INTERFACE + '.Requested'] == True + assert props[CHANNEL_INTERFACE + '.InitiatorHandle'] == self_handle + assert props[CHANNEL_INTERFACE + '.InitiatorID'] == self_handle_name + + # org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.State'] == FT_STATE_PENDING + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentType'] == FILE_CONTENT_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Filename'] == FILE_NAME + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Size'] == FILE_SIZE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType'] == FILE_HASH_TYPE + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash'] == FILE_HASH + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Description'] == FILE_DESCRIPTION + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.Date'] == 1225278834 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes'] == \ + {SOCKET_ADDRESS_TYPE_UNIX: [SOCKET_ACCESS_CONTROL_LOCALHOST]} + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes'] == 0 + assert props[CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'] == 0 + + channel = make_channel_proxy(conn, path, 'Channel') + ft_channel = make_channel_proxy(conn, path, 'Channel.Type.FileTransfer.DRAFT') + ft_props = dbus.Interface(bus.get_object(conn.object.bus_name, path), PROPERTIES_IFACE) + + conn_event, iq_event = q.expect_many( + EventPattern('incoming-connection', listener = listener), + EventPattern('stream-iq')) + + incoming = conn_event.connection + + assert iq_event.iq_type == 'set' + assert iq_event.connection == incoming + iq = iq_event.stanza + assert iq['to'] == contact_name + query = iq.firstChildElement() + assert query.uri == 'jabber:iq:oob' + url_node = xpath.queryForNodes("/iq/query/url", iq)[0] + assert url_node['type'] == 'file' + assert url_node['size'] == str(FILE_SIZE) + assert url_node['mimeType'] == FILE_CONTENT_TYPE + url = url_node.children[0] + _, host, file, _, _, _ = urlparse.urlparse(url) + urllib.unquote(file) == FILE_NAME + desc_node = xpath.queryForNodes("/iq/query/desc", iq)[0] + desc = desc_node.children[0] + assert desc == FILE_DESCRIPTION + + # state is still Pending as remote didn't accept the transfer yet + state = ft_props.Get(CHANNEL_TYPE_FILE_TRANSFER, 'State') + assert state == FT_STATE_PENDING + + # Connect HTTP client to the CM and request the file + http = httplib.HTTPConnection(host) + http.request('GET', file) + + # Remote accepted the transfer + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_ACCEPTED, state + assert reason == FT_STATE_CHANGE_REASON_NONE + + address = ft_channel.ProvideFile(SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, "") + + e = q.expect('dbus-signal', signal='InitialOffsetDefined') + offset = e.args[0] + # We don't support resume + assert offset == 0 + + # Channel is open. We can start to send the file + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_OPEN + assert reason == FT_STATE_CHANGE_REASON_REQUESTED + + offset = ft_props.Get(CHANNEL_TYPE_FILE_TRANSFER, 'InitialOffset') + # We don't support resume + assert offset == 0 + + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(address) + s.send(FILE_DATA) + + e = q.expect('dbus-signal', signal='TransferredBytesChanged') + + count = e.args[0] + while count < FILE_SIZE: + # Catch TransferredBytesChanged until we transfered all the data + e = q.expect('dbus-signal', signal='TransferredBytesChanged') + count = e.args[0] + + response = http.getresponse() + assert (response.status, response.reason) == (200, 'OK') + data = response.read(FILE_SIZE) + # Did we received the right file? + assert data == FILE_DATA + + # Inform sender that we received all the file from the OOB transfer + reply = domish.Element(('', 'iq')) + reply['to'] = iq['from'] + reply['from'] = iq['to'] + reply['type'] = 'result' + reply['id'] = iq['id'] + incoming.send(reply) + + e = q.expect('dbus-signal', signal='FileTransferStateChanged') + state, reason = e.args + assert state == FT_STATE_COMPLETED + assert reason == FT_STATE_CHANGE_REASON_NONE + + channel.Close() + q.expect('dbus-signal', signal='Closed') + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-tube-close.py b/tests/twisted/avahi/test-tube-close.py new file mode 100644 index 00000000..42d61ed3 --- /dev/null +++ b/tests/twisted/avahi/test-tube-close.py @@ -0,0 +1,82 @@ +""" +Offer a 1-1 stream tube and close the connection. Salut must just send a +stanza to close the tube and disconnect. +""" + +from saluttest import exec_test +from avahitest import AvahiAnnouncer, AvahiListener +from avahitest import get_host_name +import avahi + +from xmppstream import setup_stream_listener, connect_to_stream +from servicetest import make_channel_proxy + +from twisted.words.xish import xpath, domish + +import dbus + +PUBLISHED_NAME="test-tube" + +CHANNEL_TYPE_TUBES = "org.freedesktop.Telepathy.Channel.Type.Tubes" +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +SOCKET_ADDRESS_TYPE_IPV4 = dbus.UInt32(2) +SOCKET_ACCESS_CONTROL_LOCALHOST = dbus.UInt32(0) + +print "FIXME: test-tube-close.py disabled because sending a close stanza on " +print "disconnection is not yet implemented in telepathy-salut. It requires " +print "to ensure the XmppConnection and reestablish it" +# exiting 77 causes automake to consider the test to have been skipped +raise SystemExit(77) + +def test(q, bus, conn): + # Salut will not connect to this socket, the test finishs before + socket_address = ('0.0.0.0', dbus.UInt16(0)) + + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + basic_txt = { "txtvers": "1", "status": "avail" } + + contact_name = PUBLISHED_NAME + get_host_name() + listener, port = setup_stream_listener(q, contact_name) + + announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt) + + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + + handle = 0 + # Wait until the record shows up in publish + while handle == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact_name: + handle = h + + t = conn.RequestChannel(CHANNEL_TYPE_TUBES, HT_CONTACT, handle, + True) + tubes_channel = make_channel_proxy(conn, t, "Channel.Type.Tubes") + + tubes_channel.OfferStreamTube("http", dbus.Dictionary({}), + SOCKET_ADDRESS_TYPE_IPV4, socket_address, + SOCKET_ACCESS_CONTROL_LOCALHOST, "") + + e = q.expect('stream-iq') + + # Close the connection just after the tube has been offered. + conn.Disconnect() + + # receive the close stanza for the tube + event = q.expect('stream-message') + message = event.stanza + close_node = xpath.queryForNodes('/message/close[@xmlns="%s"]' % NS_TUBES, + message) + assert close_node is not None + + q.expect('dbus-signal', signal='StatusChanged', args=[2, 1]) + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-tube.py b/tests/twisted/avahi/test-tube.py new file mode 100644 index 00000000..9720b2ba --- /dev/null +++ b/tests/twisted/avahi/test-tube.py @@ -0,0 +1,150 @@ +from saluttest import exec_test +from avahitest import AvahiAnnouncer, AvahiListener +from avahitest import get_host_name +import avahi +import dbus +import os +import errno +import string + +from xmppstream import setup_stream_listener, connect_to_stream +from servicetest import make_channel_proxy, Event + +from twisted.words.xish import xpath, domish +from twisted.internet.protocol import Factory, Protocol, ClientCreator +from twisted.internet import reactor + +PUBLISHED_NAME="test-tube" + +CHANNEL_TYPE_TUBES = "org.freedesktop.Telepathy.Channel.Type.Tubes" +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) +SOCKET_ADDRESS_TYPE_UNIX = dbus.UInt32(0) +SOCKET_ADDRESS_TYPE_IPV4 = dbus.UInt32(2) +SOCKET_ACCESS_CONTROL_LOCALHOST = dbus.UInt32(0) + +sample_parameters = dbus.Dictionary({ + 's': 'hello', + 'ay': dbus.ByteArray('hello'), + 'u': dbus.UInt32(123), + 'i': dbus.Int32(-123), + }, signature='sv') + +test_string = "This string travels on a tube !" + +def test(q, bus, conn): + + # define a basic tcp server that echoes what the client says, but with + # swapcase + class TrivialServer(Protocol): + def dataReceived(self, data): + self.transport.write(string.swapcase(data)) + e = Event('server-data-received', service = self, data = data) + q.append(e) + + # define a basic tcp client + class ClientGreeter(Protocol): + def dataReceived(self, data): + e = Event('client-data-received', service = self, data = data) + q.append(e) + def client_connected_cb(p): + e = Event('client-connected', transport = p.transport) + q.append(e) + + # create the server + factory = Factory() + factory.protocol = TrivialServer + server_socket_address = os.getcwd() + '/stream' + try: + os.remove(server_socket_address) + except OSError, e: + if e.errno != errno.ENOENT: + raise + l = reactor.listenUNIX(server_socket_address, factory) + + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + basic_txt = { "txtvers": "1", "status": "avail" } + + contact_name = PUBLISHED_NAME + get_host_name() + listener, port = setup_stream_listener(q, contact_name) + + announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt) + + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + + handle = 0 + # Wait until the record shows up in publish + while handle == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact_name: + handle = h + + t = conn.RequestChannel(CHANNEL_TYPE_TUBES, HT_CONTACT, handle, + True) + tubes_channel = make_channel_proxy(conn, t, "Channel.Type.Tubes") + + tube_id = tubes_channel.OfferStreamTube("http", sample_parameters, + SOCKET_ADDRESS_TYPE_UNIX, dbus.ByteArray(server_socket_address), + SOCKET_ACCESS_CONTROL_LOCALHOST, "") + + e = q.expect('stream-iq') + iq_tube = xpath.queryForNodes('/iq/tube', e.stanza)[0] + transport = xpath.queryForNodes('/iq/tube/transport', e.stanza)[0] + assert iq_tube.attributes['type'] == 'stream' + assert iq_tube.attributes['service'] == 'http' + assert iq_tube.attributes['id'] is not None + port = transport.attributes['port'] + assert port is not None + port = int(port) + assert port > 1024 + assert port < 65536 + + params = {} + parameter_nodes = xpath.queryForNodes('/iq/tube/parameters/parameter', + e.stanza) + for node in parameter_nodes: + assert node['name'] not in params + params[node['name']] = (node['type'], str(node)) + assert params == {'ay': ('bytes', 'aGVsbG8='), + 's': ('str', 'hello'), + 'i': ('int', '-123'), + 'u': ('uint', '123'), + }, params + + # find the right host/IP address because Salut checks it + self_handle = conn.GetSelfHandle() + self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0] + AvahiListener(q).listen_for_service("_presence._tcp") + e = q.expect('service-added', name = self_handle_name, + protocol = avahi.PROTO_INET) + service = e.service + service.resolve() + e = q.expect('service-resolved', service = service) + host_name = e.host_name + + client = ClientCreator(reactor, ClientGreeter) + client.connectTCP(host_name, port).addCallback(client_connected_cb) + + e = q.expect('client-connected') + client_transport = e.transport + client_transport.write(test_string) + + e = q.expect('server-data-received') + assert e.data == test_string + + e = q.expect('client-data-received') + assert e.data == string.swapcase(test_string) + + # Close the tube propertly + tubes_channel.CloseTube(tube_id) + conn.Disconnect() + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/avahi/test-two-tubes.py b/tests/twisted/avahi/test-two-tubes.py new file mode 100644 index 00000000..0f3d2fb5 --- /dev/null +++ b/tests/twisted/avahi/test-two-tubes.py @@ -0,0 +1,180 @@ +from saluttest import exec_test, make_connection +from avahitest import AvahiAnnouncer, AvahiListener +from avahitest import get_host_name +import avahi +import dbus +import os +import errno +import string + +from xmppstream import setup_stream_listener, connect_to_stream +from servicetest import make_channel_proxy, Event + +from twisted.words.xish import xpath, domish +from twisted.internet.protocol import Factory, Protocol, ClientCreator +from twisted.internet import reactor + +CHANNEL_TYPE_TUBES = "org.freedesktop.Telepathy.Channel.Type.Tubes" +HT_CONTACT = 1 +HT_CONTACT_LIST = 3 +TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0) +SOCKET_ADDRESS_TYPE_UNIX = dbus.UInt32(0) +SOCKET_ADDRESS_TYPE_IPV4 = dbus.UInt32(2) +SOCKET_ACCESS_CONTROL_LOCALHOST = dbus.UInt32(0) + +sample_parameters = dbus.Dictionary({ + 's': 'hello', + 'ay': dbus.ByteArray('hello'), + 'u': dbus.UInt32(123), + 'i': dbus.Int32(-123), + }, signature='sv') + +test_string = "This string travels on a tube !" + +def test(q, bus, conn): + + # define a basic tcp server that echoes what the client says, but with + # swapcase + class TrivialServer(Protocol): + def dataReceived(self, data): + self.transport.write(string.swapcase(data)) + e = Event('server-data-received', service = self, data = data) + q.append(e) + + # define a basic tcp client + class ClientGreeter(Protocol): + def dataReceived(self, data): + e = Event('client-data-received', service = self, data = data) + q.append(e) + def client_connected_cb(p): + e = Event('client-connected', transport = p.transport) + q.append(e) + + # create the server + factory = Factory() + factory.protocol = TrivialServer + server_socket_address = os.getcwd() + '/stream' + try: + os.remove(server_socket_address) + except OSError, e: + if e.errno != errno.ENOENT: + raise + l = reactor.listenUNIX(server_socket_address, factory) + + # first connection: connect + contact1_name = "testsuite" + "@" + get_host_name() + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + + # second connection: connect + conn2_params = { + 'published-name': 'testsuite2', + 'first-name': 'test2', + 'last-name': 'suite2', + } + contact2_name = "testsuite2" + "@" + get_host_name() + conn2 = make_connection(bus, q.append, conn2_params) + conn2.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + + # first connection: get the contact list + publish_handle = conn.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + conn1_publish = conn.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + conn1_publish_proxy = bus.get_object(conn.bus_name, conn1_publish) + + # second connection: get the contact list + publish_handle = conn2.RequestHandles(HT_CONTACT_LIST, ["publish"])[0] + conn2_publish = conn2.RequestChannel( + "org.freedesktop.Telepathy.Channel.Type.ContactList", + HT_CONTACT_LIST, publish_handle, False) + conn2_publish_proxy = bus.get_object(conn2.bus_name, conn2_publish) + + # first connection: wait to see contact2 + # The signal MembersChanged may be already emitted... check the Members + # property first + contact2_handle_on_conn1 = 0 + conn1_members = conn1_publish_proxy.Get( + 'org.freedesktop.Telepathy.Channel.Interface.Group', 'Members', + dbus_interface='org.freedesktop.DBus.Properties') + for h in conn1_members: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact2_name: + contact2_handle_on_conn1 = h + while contact2_handle_on_conn1 == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=conn1_publish) + for h in e.args[1]: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact2_name: + contact2_handle_on_conn1 = h + + # second connection: wait to see contact1 + # The signal MembersChanged may be already emitted... check the Members + # property first + contact1_handle_on_conn2 = 0 + conn2_members = conn2_publish_proxy.Get( + 'org.freedesktop.Telepathy.Channel.Interface.Group', 'Members', + dbus_interface='org.freedesktop.DBus.Properties') + for h in conn2_members: + name = conn.InspectHandles(HT_CONTACT, [h])[0] + if name == contact1_name: + contact1_handle_on_conn2 = h + while contact1_handle_on_conn2 == 0: + e = q.expect('dbus-signal', signal='MembersChanged', path=conn2_publish) + for h in e.args[1]: + name = conn2.InspectHandles(HT_CONTACT, [h])[0] + if name == contact1_name: + contact1_handle_on_conn2 = h + + # do tubes + t = conn.RequestChannel(CHANNEL_TYPE_TUBES, HT_CONTACT, + contact2_handle_on_conn1, True) + contact1_tubes_channel = make_channel_proxy(conn, t, "Channel.Type.Tubes") + + tube_id = contact1_tubes_channel.OfferStreamTube("http", sample_parameters, + SOCKET_ADDRESS_TYPE_UNIX, dbus.ByteArray(server_socket_address), + SOCKET_ACCESS_CONTROL_LOCALHOST, "") + + contact2_channeltype = None + while contact2_channeltype == None: + e = q.expect('dbus-signal', signal='NewChannel') + if (e.args[1] == CHANNEL_TYPE_TUBES) and (e.path.endswith("testsuite2") == True): + contact2_objpath = e.args[0] + contact2_channeltype = e.args[1] + + contact2_tubes_channel = make_channel_proxy(conn2, contact2_objpath, "Channel.Type.Tubes") + + contact2_tubes = contact2_tubes_channel.ListTubes() + assert len(contact2_tubes) == 1 + contact2_tube = contact2_tubes[0] + assert contact2_tube[0] is not None # tube id + assert contact2_tube[1] is not None # initiator + assert contact2_tube[2] == 1 # type = stream tube + assert contact2_tube[3] == 'http' # service = http + assert contact2_tube[4] is not None # parameters + assert contact2_tube[5] == 0, contact2_tube[5] # status = local pending + + unix_socket_adr = contact2_tubes_channel.AcceptStreamTube( + contact2_tube[0], 0, 0, '', byte_arrays=True) + + client = ClientCreator(reactor, ClientGreeter) + client.connectUNIX(unix_socket_adr).addCallback(client_connected_cb) + + e = q.expect('client-connected') + client_transport = e.transport + client_transport.write(test_string) + + e = q.expect('server-data-received') + assert e.data == test_string + + e = q.expect('client-data-received') + assert e.data == string.swapcase(test_string) + + # Close the tube propertly + contact1_tubes_channel.CloseTube(tube_id) + conn.Disconnect() + conn2.Disconnect() + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/trivialstream.py b/tests/twisted/trivialstream.py new file mode 100644 index 00000000..ca80b755 --- /dev/null +++ b/tests/twisted/trivialstream.py @@ -0,0 +1,70 @@ +import dbus.glib +import gobject +import sys +import time +import os +import socket +import tempfile +import random +import string + +class TrivialStream: + def __init__(self, socket_address=None): + self.socket_address = socket_address + + def read_socket(self, s): + try: + data = s.recv(1024) + if len(data) > 0: + print "received:", data + except socket.error, e: + pass + return True + + def write_socket(self, s, msg): + print "send:", msg + try: + s = s.send(msg) + except socket.error, e: + pass + return True + +class TrivialStreamServer(TrivialStream): + def __init__(self): + TrivialStream.__init__(self) + + def run(self): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setblocking(1) + s.settimeout(0.1) + s.bind(("127.0.0.1", 0)) + + self.socket_address = s.getsockname() + print "Trivial Server lauched on socket", self.socket_address + s.listen(1) + + gobject.timeout_add(1000, self.accept_client, s) + + def accept_client(self, s): + try: + s2, addr = s.accept() + s2.setblocking(1) + s2.setblocking(0.1) + self.handle_client(s2) + return True + except socket.timeout: + return True + + def handle_client(self, s): + gobject.timeout_add(5000, self.write_socket, s, "hi !") + +class TrivialStreamClient(TrivialStream): + def __init__(self, socket_address): + TrivialStream.__init__(self, socket_address) + + def connect(self): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(self.socket_address) + print "Trivial client connected to", self.socket_address + gobject.timeout_add(1000, self.read_socket, s) + |