def test(q, bus, conn, stream, bytestream_cls, access_control):
    disco_event = q.expect('stream-iq', to='localhost', query_ns=ns.DISCO_ITEMS)

    announce_socks5_proxy(q, stream, disco_event.stanza)

    t.check_conn_properties(q, conn)

    self_handle = conn.Properties.Get(cs.CONN, "SelfHandle")
    alice_handle = conn.get_contact_handle_sync('alice@localhost')

    # send Alice's presence
    caps =  { 'ext': '', 'ver': '0.0.0',
        'node': 'http://example.com/fake-client0' }
    presence = make_presence('alice@localhost/Test', caps=caps)
    stream.send(presence)

    _, disco_event = q.expect_many(
        EventPattern('dbus-signal', signal='PresencesChanged',
            args = [{alice_handle: (2L, u'available', u'')}]),
        EventPattern('stream-iq', to='alice@localhost/Test',
            query_ns=ns.DISCO_INFO),
        )

    # reply to disco query
    send_disco_reply(stream, disco_event.stanza, [], [ns.TUBES])

    sync_stream(q, stream)

    offer_new_dbus_tube(q, bus, conn, stream, self_handle, alice_handle, bytestream_cls, access_control)
def handle_disco(q, stream, contact_jid, identity):
    # Gabble tries to resolve a caps hash.
    ver = compute_caps_hash([identity], features, {})
    event = q.expect('stream-iq', to=contact_jid, query_ns=ns.DISCO_INFO)
    assertEquals(client + '#' + ver, event.query.attributes['node'])

    # The bare jid replies.
    send_disco_reply(stream, event.stanza, [identity], features)
def handle_disco(q, stream, contact_jid, identity):
    # Gabble tries to resolve a caps hash.
    ver = compute_caps_hash([identity], features, {})
    event = q.expect('stream-iq', to=contact_jid, query_ns=ns.DISCO_INFO)
    assertEquals(client + '#' + ver, event.query.attributes['node'])

    # The bare jid replies.
    send_disco_reply(stream, event.stanza, [identity], features)
    def announce_contact(self, name=CONTACT_NAME, metadata=True):
        client = 'http://telepathy.freedesktop.org/fake-client'
        features = [ns.IQ_OOB]

        if metadata:
            features += [ns.TP_FT_METADATA]

        ver = compute_caps_hash([], features, {})
        txt_record = {
            "txtvers": "1",
            "status": "avail",
            "node": client,
            "ver": ver,
            "hash": "sha-1"
        }

        suffix = '@%s' % get_host_name()
        name += ('-' + os.path.splitext(os.path.basename(sys.argv[0]))[0])

        self.contact_name = name + suffix
        if len(self.contact_name) > 63:
            allowed = 63 - len(suffix)
            self.contact_name = name[:allowed] + suffix

        self.listener, port = setup_stream_listener(self.q, self.contact_name)

        self.contact_service = AvahiAnnouncer(self.contact_name,
                                              "_presence._tcp", port,
                                              txt_record)

        self.handle = wait_for_contact_in_publish(self.q, self.bus, self.conn,
                                                  self.contact_name)

        # expect salut to disco our caps
        e = self.q.expect('incoming-connection', listener=self.listener)
        stream = e.connection

        e = self.q.expect('stream-iq',
                          to=self.contact_name,
                          query_ns=ns.DISCO_INFO,
                          connection=stream)
        assertEquals(client + '#' + ver, e.query['node'])
        send_disco_reply(stream, e.stanza, [], features)

        # lose the connection here to ensure connections are created
        # where necessary; I just wanted salut to know my caps.
        stream.send('</stream:stream>')
        # spend a bit of time in the main loop to ensure the last two
        # stanzas are actually received by salut before closing the
        # connection.
        sync_dbus(self.bus, self.q, self.conn)
        stream.transport.loseConnection()
def connect_and_announce_alice(q, bus, conn, stream):
    q.forbid_events(proxy_query_events)

    # Send Alice's presence
    caps =  { 'ext': '', 'ver': '0.0.0',
        'node': 'http://example.com/fake-client0' }
    presence = make_presence('alice@localhost/Test', caps=caps)
    stream.send(presence)

    disco_event = q.expect('stream-iq', to='alice@localhost/Test',
        query_ns=ns.DISCO_INFO)

    send_disco_reply(
        stream, disco_event.stanza, [], [ns.TUBES, ns.FILE_TRANSFER])
    sync_stream(q, stream)

    q.unforbid_events(proxy_query_events)
    def announce_contact(self, name=CONTACT_NAME, metadata=True):
        client = 'http://telepathy.freedesktop.org/fake-client'
        features = [ns.IQ_OOB]

        if metadata:
            features += [ns.TP_FT_METADATA]

        ver = compute_caps_hash([], features, {})
        txt_record = { "txtvers": "1", "status": "avail",
                       "node": client, "ver": ver, "hash": "sha-1"}

        suffix = '@%s' % get_host_name()
        name += ('-' + os.path.splitext(os.path.basename(sys.argv[0]))[0])

        self.contact_name = name + suffix
        if len(self.contact_name) > 63:
            allowed = 63 - len(suffix)
            self.contact_name = name[:allowed] + suffix

        self.listener, port = setup_stream_listener(self.q, self.contact_name)

        self.contact_service = AvahiAnnouncer(self.contact_name, "_presence._tcp",
                port, txt_record)

        self.handle = wait_for_contact_in_publish(self.q, self.bus, self.conn,
                self.contact_name)

        # expect salut to disco our caps
        e = self.q.expect('incoming-connection', listener=self.listener)
        stream = e.connection

        e = self.q.expect('stream-iq', to=self.contact_name, query_ns=ns.DISCO_INFO,
                     connection=stream)
        assertEquals(client + '#' + ver, e.query['node'])
        send_disco_reply(stream, e.stanza, [], features)

        # lose the connection here to ensure connections are created
        # where necessary; I just wanted salut to know my caps.
        stream.send('</stream:stream>')
        # spend a bit of time in the main loop to ensure the last two
        # stanzas are actually received by salut before closing the
        # connection.
        sync_dbus(self.bus, self.q, self.conn)
        stream.transport.loseConnection()
Esempio n. 7
0
def connect_and_announce_alice(q, bus, conn, stream):
    q.forbid_events(proxy_query_events)

    # Send Alice's presence
    caps = {
        'ext': '',
        'ver': '0.0.0',
        'node': 'http://example.com/fake-client0'
    }
    presence = make_presence('alice@localhost/Test', caps=caps)
    stream.send(presence)

    disco_event = q.expect('stream-iq',
                           to='alice@localhost/Test',
                           query_ns=ns.DISCO_INFO)

    send_disco_reply(stream, disco_event.stanza, [],
                     [ns.TUBES, ns.FILE_TRANSFER])
    sync_stream(q, stream)

    q.unforbid_events(proxy_query_events)
def test(q, bus, conn, stream, bytestream_cls, access_control):
    disco_event = q.expect('stream-iq',
                           to='localhost',
                           query_ns=ns.DISCO_ITEMS)

    announce_socks5_proxy(q, stream, disco_event.stanza)

    t.check_conn_properties(q, conn)

    self_handle = conn.Properties.Get(cs.CONN, "SelfHandle")
    alice_handle = conn.get_contact_handle_sync('alice@localhost')

    # send Alice's presence
    caps = {
        'ext': '',
        'ver': '0.0.0',
        'node': 'http://example.com/fake-client0'
    }
    presence = make_presence('alice@localhost/Test', caps=caps)
    stream.send(presence)

    _, disco_event = q.expect_many(
        EventPattern('dbus-signal',
                     signal='PresencesChanged',
                     args=[{
                         alice_handle: (2L, u'available', u'')
                     }]),
        EventPattern('stream-iq',
                     to='alice@localhost/Test',
                     query_ns=ns.DISCO_INFO),
    )

    # reply to disco query
    send_disco_reply(stream, disco_event.stanza, [], [ns.TUBES])

    sync_stream(q, stream)

    offer_new_dbus_tube(q, bus, conn, stream, self_handle, alice_handle,
                        bytestream_cls, access_control)
def two_contacts_with_the_same_hash(q, bus, conn, stream, bare_jids):
    contact1 = '*****@*****.**'
    contact2 = '*****@*****.**'

    if not bare_jids:
        contact1 += '/lol'
        contact2 += '/whut'

    h1, h2 = conn.get_contact_handles_sync([contact1, contact2])
    ver = compute_caps_hash(BANANAPHONE, features, {})
    caps = {
        # Uniquify slightly with a stringified boolean ;-)
        'node': '%s%s' % (client_base, bare_jids),
        'ver': ver,
        'hash': 'sha-1',
    }

    send_presence(q, conn, stream, contact1, caps)
    stanza = expect_disco(q, contact1, caps['node'], caps)

    send_presence(q, conn, stream, contact2, caps)
    q.forbid_events([
        EventPattern('stream-iq', to=contact2, query_ns=ns.DISCO_INFO),
    ])
    sync_stream(q, stream)

    send_disco_reply(stream, stanza, BANANAPHONE, features, {})
    q.expect_many(
        EventPattern('dbus-signal',
                     signal='ClientTypesUpdated',
                     args=[h1, ['phone']]),
        # Gabble previously did not emit ClientTypesUpdated for anyone beside
        # the contact we sent the disco request to; so this second event would
        # never arrive.
        EventPattern('dbus-signal',
                     signal='ClientTypesUpdated',
                     args=[h2, ['phone']]),
    )
Esempio n. 10
0
def two_contacts_with_the_same_hash(q, bus, conn, stream, bare_jids):
    contact1 = '*****@*****.**'
    contact2 = '*****@*****.**'

    if not bare_jids:
        contact1 += '/lol'
        contact2 += '/whut'

    h1, h2 = conn.get_contact_handles_sync([contact1, contact2])
    ver = compute_caps_hash(BANANAPHONE, features, {})
    caps = {
        # Uniquify slightly with a stringified boolean ;-)
        'node': '%s%s' % (client_base, bare_jids),
        'ver':  ver,
        'hash': 'sha-1',
        }

    send_presence(q, conn, stream, contact1, caps)
    stanza = expect_disco(q, contact1, caps['node'], caps)

    send_presence(q, conn, stream, contact2, caps)
    q.forbid_events([
        EventPattern('stream-iq', to=contact2, query_ns=ns.DISCO_INFO),
        ])
    sync_stream(q, stream)

    send_disco_reply(stream, stanza, BANANAPHONE, features, {})
    q.expect_many(
        EventPattern('dbus-signal', signal='ClientTypesUpdated',
            args=[h1, ['phone']]),
        # Gabble previously did not emit ClientTypesUpdated for anyone beside
        # the contact we sent the disco request to; so this second event would
        # never arrive.
        EventPattern('dbus-signal', signal='ClientTypesUpdated',
            args=[h2, ['phone']]),
        )
Esempio n. 11
0
def test(q, bus, conn, stream):
    client = 'http://example.com/perverse-client'
    contact_bare_jid = '*****@*****.**'
    contact_with_resource = '[email protected]/hi'
    contact_handle = conn.get_contact_handle_sync(contact_bare_jid)

    # Gabble gets a presence stanza from a bare JID, which is a tad surprising.
    features = [
        ns.JINGLE_015,
        ns.JINGLE_015_AUDIO,
        ns.JINGLE_015_VIDEO,
        ns.GOOGLE_P2P,
    ]
    caps = {
        'node': client,
        'hash': 'sha-1',
        'ver': compute_caps_hash([], features, {}),
    }
    p = make_presence(contact_bare_jid, status='Hello', caps=caps)
    stream.send(p)

    # Gabble looks up the hash
    event = q.expect('stream-iq',
                     to=contact_bare_jid,
                     query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
    assertEquals(client + '#' + caps['ver'], query_node.attributes['node'])

    # The bare jid replies
    send_disco_reply(stream, event.stanza, [], features)

    # Gabble lets us know their caps have changed. (Gabble used to ignore the
    # reply.)
    cc, = q.expect_many(
        EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), )
    assert_rccs_callable(cc.args[0][contact_handle])

    # Gabble gets another presence stanza from the bare JID, with different
    # caps.
    features.append(ns.TUBES)
    caps = {
        'node': client,
        'hash': 'sha-1',
        'ver': compute_caps_hash([], features, {}),
    }
    p = make_presence(contact_bare_jid, status='Get out the abacus', caps=caps)
    stream.send(p)

    # Gabble looks up the new hash
    disco2 = q.expect('stream-iq',
                      to=contact_bare_jid,
                      query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', disco2.stanza)[0]
    assertEquals(client + '#' + caps['ver'], query_node.attributes['node'])

    # This time, before the bare JID replies, Gabble gets a presence from the
    # resourceful jid.
    features_ = features + [ns.CHAT_STATES]
    caps = {
        'node': client,
        'hash': 'sha-1',
        'ver': compute_caps_hash([], features_, {}),
    }
    p = make_presence(contact_with_resource, status='Count this', caps=caps)
    stream.send(p)

    # Gabble throws away presence from the bare JID when it gets presence from
    # a resource (and vice versa), so it should now say the contact is
    # incapable.  Gabble also looks up the resourceful JID's hash.
    cc, disco3 = q.expect_many(
        EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'),
        EventPattern('stream-iq',
                     to=contact_with_resource,
                     query_ns='http://jabber.org/protocol/disco#info'),
    )

    assert_rccs_not_callable(cc.args[0][contact_handle])

    query_node = xpath.queryForNodes('/iq/query', disco3.stanza)[0]
    assertEquals(client + '#' + caps['ver'], query_node.attributes['node'])

    # The bare jid replies! Getting a disco reply from a bare JID when we've
    # got presence from resources used to crash Gabble, but now it just ignores
    # it.
    send_disco_reply(stream, disco2.stanza, [], features)

    # Now the resourceful JID replies:
    send_disco_reply(stream, disco3.stanza, [], features_)

    # Gabble should announce that the contact has acquired some caps.
    cc, = q.expect_many(
        EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), )
    assert_rccs_callable(cc.args[0][contact_handle])
Esempio n. 12
0
def test(q, bus, conn, stream):
    caps = {
        'node': client,
        'ver':  '0.1',
        }

    update_contact_caps(q, conn, stream, '[email protected]/Foo', caps)
    update_contact_caps(q, conn, stream, '[email protected]/Foo', caps)

    # Meredith signs in from one resource.
    update_contact_caps(q, conn, stream, '[email protected]/One', caps)
    # Meredith signs in from another resource with the same client. We don't
    # need to disco her, even though we don't trust this caps node in general
    # yet, because she's already told us what it means.
    meredith_two = '[email protected]/Two'
    q.forbid_events([
        EventPattern('stream-iq', to=meredith_two, query_ns=ns.DISCO_INFO)
        ])
    stream.send(make_presence(meredith_two, 'hello', caps=caps))
    sync_stream(q, stream)

    # Jens signs in from one resource, which is slow to answer the disco query.
    jens_one = '[email protected]/One'
    j = send_presence(q, conn, stream, jens_one, caps)
    j_stanza = expect_disco(q, jens_one, client, caps)

    # Jens now signs in elsewhere with the same client; we disco it (maybe
    # it'll reply sooner? Maybe his first client's network connection went away
    # and the server hasn't noticed yet?) and it replies immediately.
    update_contact_caps (q, conn, stream, '[email protected]/Two', caps,
        initial=False)

    # Jens' first client replies. We don't expect any caps changes here, and
    # this shouldn't count as a second point towards the five we need to trust
    # this caps node.
    send_disco_reply(stream, j_stanza, [], features)
    check_caps (conn, j)

    update_contact_caps (q, conn, stream, '[email protected]/Foo', caps)

    # Now five distinct contacts have told us what this caps node means, we
    # trust it.
    update_contact_caps (q, conn, stream, '[email protected]/Foo', caps,
        disco = False)
    update_contact_caps (q, conn, stream, '[email protected]/Foo', caps,
        disco = False)

    caps = {
        'node': client,
        'ver':  compute_caps_hash([], features, fake_client_dataforms),
        'hash': 'sha-1',
        }

    update_contact_caps(q, conn, stream, '[email protected]/Foo',
       caps,  dataforms = fake_client_dataforms)
    # We can verify the reply for these caps against the hash, and thus never
    # need to disco it again.
    update_contact_caps(q, conn, stream, '[email protected]/Foo', caps,
        disco = False, dataforms = fake_client_dataforms)
    update_contact_caps(q, conn, stream, '[email protected]/Foo', caps,
        disco = False, dataforms = fake_client_dataforms)
def test(q, bus, conn, stream):
    # check all these types appear as they should
    contact_online(q, conn, stream, '[email protected]/lol', BOT)
    contact_online(q, conn, stream, '[email protected]/lol', CONSOLE)
    contact_online(q, conn, stream, '[email protected]/lol', GAME)
    contact_online(q, conn, stream, '[email protected]/lol', HANDHELD)
    contact_online(q, conn, stream, '[email protected]/lol', PC)
    contact_online(q, conn, stream, '[email protected]/lol', PHONE)
    contact_online(q, conn, stream, '[email protected]/lol', WEB)
    contact_online(q, conn, stream, '[email protected]/lol', SMS)

    meredith_one = '[email protected]/One'
    meredith_two = '[email protected]/Two'
    meredith_three = '[email protected]/Three'
    meredith_handle = conn.get_contact_handle_sync(meredith_one)

    # Meredith signs in from one resource
    contact_online(q, conn, stream, meredith_one, PC, show='chat')

    # * One: chat: pc
    # ClientTypes should be: ['pc']

    # Meredith signs in from another resource
    contact_online(q,
                   conn,
                   stream,
                   meredith_two,
                   PHONE,
                   show='dnd',
                   initial=False)

    # * One: chat: pc
    # * Two: dnd: phone
    # ClientTypes should be: ['pc']

    # check we're still a PC
    types = get_client_types(conn, meredith_handle)

    assertLength(1, types)
    assertEquals('pc', types[0])

    types = conn.RequestClientTypes(meredith_handle,
                                    dbus_interface=cs.CONN_IFACE_CLIENT_TYPES)

    assertLength(1, types)
    assertEquals('pc', types[0])

    # Two now becomes more available
    stream.send(make_presence(meredith_two, show='chat'))

    # * One: chat: pc
    # * Two: chat: phone
    # ClientTypes should be: ['pc']

    types = get_client_types(conn, meredith_handle)
    assertEquals('pc', types[0])

    # One now becomes less available
    stream.send(make_presence(meredith_one, show='away'))

    # * One: away: pc
    # * Two: chat: phone
    # ClientTypes should be: ['phone']

    # wait for the presence change
    q.expect('dbus-signal',
             signal='PresencesChanged',
             args=[{
                 meredith_handle: (cs.PRESENCE_AVAILABLE, 'chat', '')
             }])

    # now wait for the change in client type
    event = q.expect('dbus-signal', signal='ClientTypesUpdated')
    assertEquals([meredith_handle, ['phone']], event.args)

    # make One more available again
    stream.send(make_presence(meredith_one, show='chat', status='lawl'))

    # * One: chat: pc
    # * Two: chat: phone
    # ClientTypes should be: ['pc']

    # wait for the presence change
    q.expect('dbus-signal',
             signal='PresencesChanged',
             args=[{
                 meredith_handle: (cs.PRESENCE_AVAILABLE, 'chat', 'lawl')
             }])

    # now wait for the change in client type
    event = q.expect('dbus-signal', signal='ClientTypesUpdated')
    assertEquals([meredith_handle, ['pc']], event.args)

    # both One and Two go away
    stream.send(make_presence(meredith_one, show='away'))

    # * One: away: pc
    # * Two: chat: phone
    # ClientTypes should be: ['phone']

    stream.send(make_presence(meredith_two, show='away'))

    # * One: away: pc
    # * Two: away: phone
    # ClientTypes should be: ['pc']

    # wait for the presence change
    q.expect('dbus-signal',
             signal='PresencesChanged',
             args=[{
                 meredith_handle: (cs.PRESENCE_AWAY, 'away', '')
             }])

    # check it still thinks we're a PC
    types = get_client_types(conn, meredith_handle)
    assertEquals('pc', types[0])

    # Three, with multiple identities, signs in
    identities = [PHONE[0], CONSOLE[0], HANDHELD[0], BOT[0]]
    contact_online(q,
                   conn,
                   stream,
                   meredith_three,
                   identities,
                   show='chat',
                   initial=False)

    # * One: away: pc
    # * Two: away: phone
    # * Three: chat: phone, console, handheld, bot
    # ClientTypes should be: ['phone', 'console', 'handheld', 'bot'] in some order

    # wait for the presence change
    q.expect('dbus-signal',
             signal='PresencesChanged',
             args=[{
                 meredith_handle: (cs.PRESENCE_AVAILABLE, 'chat', 'hello')
             }])

    # now wait for the change in client type
    event = q.expect('dbus-signal', signal='ClientTypesUpdated')
    assertEquals(meredith_handle, event.args[0])
    assertEquals(['bot', 'console', 'handheld', 'phone'],
                 sorted(event.args[1]))

    # that'll do
    #
    # ...
    #
    # wait wait! no it won't! Here's a regression test for
    # <https://bugs.freedesktop.org/show_bug.cgi?id=31772>.
    (caps, client, types) = build_stuff(TRANSIENT_PHONE)
    contact = '[email protected]/hai'
    send_presence(q, conn, stream, contact, caps)
    stanza = expect_disco(q, contact, client, caps)
    stream.send(make_presence(contact, type='unavailable'))
    send_disco_reply(stream, stanza, TRANSIENT_PHONE, [])

    # Gabble used to crash upon receiving a disco reply from a contact who's no
    # longer in the presence cache. So we sync here to check if it's died.
    sync_stream(q, stream)
Esempio n. 14
0
def test_hash(q, bus, conn, stream, contact, contact_handle, client):
    presence = make_presence(contact, status='hello')
    stream.send(presence)

    q.expect('dbus-signal', signal='PresencesChanged',
            args=[{contact_handle:
                (2, u'available', 'hello')}])

    # no special capabilities
    for rcc in get_contacts_capabilities_sync(conn, [contact_handle])[contact_handle]:
        assertEquals(cs.CHANNEL_TYPE_TEXT, rcc[0].get(cs.CHANNEL_TYPE))

    # send updated presence with Jingle caps info
    presence = make_presence(contact, status='hello',
        caps={'node': client,
              'ver':  '0.1',
             })
    stream.send(presence)

    # Gabble looks up our capabilities
    event = q.expect('stream-iq', to=contact,
        query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
    assert query_node.attributes['node'] == \
        client + '#' + '0.1'

    # send good reply
    send_disco_reply(stream, event.stanza, [], jingle_av_features)

    # we can now do audio calls
    cc, = q.expect_many(
        EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'),
        )

    assert_rccs_callable(cc.args[0][contact_handle])
    assertEquals(cc.args[0],
            get_contacts_capabilities_sync(conn, [contact_handle]))

    # Send presence without any capabilities. XEP-0115 §8.4 Caps Optimization
    # says “receivers of presence notifications MUST NOT expect an annotation
    # on every presence notification they receive”, so the contact should still
    # be media-capable afterwards.
    stream.send(make_presence(contact, status='very capable'))
    q.expect('dbus-signal', signal='PresencesChanged',
        args=[{contact_handle: (2, u'available', 'very capable')}])
    # still exactly the same capabilities
    assertEquals(cc.args[0],
            get_contacts_capabilities_sync(conn, [contact_handle]))

    # send bogus presence
    caps = {
        'node': client,
        'ver':  'ceci=nest=pas=un=hash',
        'hash': 'sha-1',
        }
    presence = make_presence(contact, status='hello', caps=caps)
    stream.send(presence)

    # Gabble looks up our capabilities
    event = q.expect('stream-iq', to=contact,
        query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
    assert query_node.attributes['node'] == \
        client + '#' + caps['ver']

    # send bogus reply
    send_disco_reply(stream, event.stanza, [],
        ['http://jabber.org/protocol/bogus-feature'])

    # don't receive any D-Bus signal
    forbidden = [
        EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'),
        ]
    q.forbid_events(forbidden)
    sync_dbus(bus, q, conn)
    sync_stream(q, stream)

    # send presence with empty caps
    presence = make_presence(contact, status='hello',
        caps={'node': client,
              'ver':  '0.0',
             })
    stream.send(presence)

    # Gabble looks up our capabilities
    event = q.expect('stream-iq', to=contact,
        query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
    assert query_node.attributes['node'] == \
        client + '#' + '0.0'

    # still don't receive any D-Bus signal
    sync_dbus(bus, q, conn)

    # send good reply
    q.unforbid_events(forbidden)
    result = make_result_iq(stream, event.stanza)
    query = result.firstChildElement()
    stream.send(result)

    # we can now do nothing
    cc, = q.expect_many(
        EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'),
        )
    for rcc in cc.args[0][contact_handle]:
        assertEquals(cs.CHANNEL_TYPE_TEXT, rcc[0].get(cs.CHANNEL_TYPE))
    assert_rccs_not_callable(cc.args[0][contact_handle])
    assertEquals(cc.args[0],
            get_contacts_capabilities_sync(conn, [contact_handle]))

    # send correct presence
    ver = compute_caps_hash(some_identities, jingle_av_features, fake_client_dataforms)
    caps = {
        'node': client,
        'ver':  ver,
        'hash': 'sha-1',
        }
    presence = make_presence(contact, status='hello', caps=caps)
    stream.send(presence)

    # Gabble looks up our capabilities
    event = q.expect('stream-iq', to=contact,
        query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
    assert query_node.attributes['node'] == \
        client + '#' + caps['ver']

    # don't receive any D-Bus signal
    q.forbid_events(forbidden)
    sync_dbus(bus, q, conn)
    q.unforbid_events(forbidden)

    # send good reply
    send_disco_reply(
        stream, event.stanza, some_identities, jingle_av_features, fake_client_dataforms)

    # we can now do audio calls
    cc, = q.expect_many(
        EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'),
        )
    assert_rccs_callable(cc.args[0][contact_handle])
    assertEquals(cc.args[0],
            get_contacts_capabilities_sync(conn, [contact_handle]))
Esempio n. 15
0
def test(q, bus, conn, stream):
    # check all these types appear as they should
    contact_online(q, conn, stream, '[email protected]/lol', BOT)
    contact_online(q, conn, stream, '[email protected]/lol', CONSOLE)
    contact_online(q, conn, stream, '[email protected]/lol', GAME)
    contact_online(q, conn, stream, '[email protected]/lol', HANDHELD)
    contact_online(q, conn, stream, '[email protected]/lol', PC)
    contact_online(q, conn, stream, '[email protected]/lol', PHONE)
    contact_online(q, conn, stream, '[email protected]/lol', WEB)
    contact_online(q, conn, stream, '[email protected]/lol', SMS)

    meredith_one = '[email protected]/One'
    meredith_two = '[email protected]/Two'
    meredith_three = '[email protected]/Three'
    meredith_handle = conn.get_contact_handle_sync(meredith_one)

    # Meredith signs in from one resource
    contact_online(q, conn, stream, meredith_one, PC, show='chat')

    # * One: chat: pc
    # ClientTypes should be: ['pc']

    # Meredith signs in from another resource
    contact_online(q, conn, stream, meredith_two, PHONE, show='dnd', initial=False)

    # * One: chat: pc
    # * Two: dnd: phone
    # ClientTypes should be: ['pc']

    # check we're still a PC
    types = get_client_types(conn, meredith_handle)

    assertLength(1, types)
    assertEquals('pc', types[0])

    types = conn.RequestClientTypes(meredith_handle,
            dbus_interface=cs.CONN_IFACE_CLIENT_TYPES)

    assertLength(1, types)
    assertEquals('pc', types[0])

    # Two now becomes more available
    stream.send(make_presence(meredith_two, show='chat'))

    # * One: chat: pc
    # * Two: chat: phone
    # ClientTypes should be: ['pc']

    types = get_client_types(conn, meredith_handle)
    assertEquals('pc', types[0])

    # One now becomes less available
    stream.send(make_presence(meredith_one, show='away'))

    # * One: away: pc
    # * Two: chat: phone
    # ClientTypes should be: ['phone']

    # wait for the presence change
    q.expect('dbus-signal', signal='PresencesChanged',
             args=[{meredith_handle: (cs.PRESENCE_AVAILABLE, 'chat', '')}])

    # now wait for the change in client type
    event = q.expect('dbus-signal', signal='ClientTypesUpdated')
    assertEquals([meredith_handle, ['phone']], event.args)

    # make One more available again
    stream.send(make_presence(meredith_one, show='chat', status='lawl'))

    # * One: chat: pc
    # * Two: chat: phone
    # ClientTypes should be: ['pc']

    # wait for the presence change
    q.expect('dbus-signal', signal='PresencesChanged',
             args=[{meredith_handle: (cs.PRESENCE_AVAILABLE, 'chat', 'lawl')}])

    # now wait for the change in client type
    event = q.expect('dbus-signal', signal='ClientTypesUpdated')
    assertEquals([meredith_handle, ['pc']], event.args)

    # both One and Two go away
    stream.send(make_presence(meredith_one, show='away'))

    # * One: away: pc
    # * Two: chat: phone
    # ClientTypes should be: ['phone']

    stream.send(make_presence(meredith_two, show='away'))

    # * One: away: pc
    # * Two: away: phone
    # ClientTypes should be: ['pc']

    # wait for the presence change
    q.expect('dbus-signal', signal='PresencesChanged',
             args=[{meredith_handle: (cs.PRESENCE_AWAY, 'away', '')}])

    # check it still thinks we're a PC
    types = get_client_types(conn, meredith_handle)
    assertEquals('pc', types[0])

    # Three, with multiple identities, signs in
    identities = [PHONE[0], CONSOLE[0], HANDHELD[0], BOT[0]]
    contact_online(q, conn, stream, meredith_three, identities,
                   show='chat', initial=False)

    # * One: away: pc
    # * Two: away: phone
    # * Three: chat: phone, console, handheld, bot
    # ClientTypes should be: ['phone', 'console', 'handheld', 'bot'] in some order

    # wait for the presence change
    q.expect('dbus-signal', signal='PresencesChanged',
             args=[{meredith_handle: (cs.PRESENCE_AVAILABLE, 'chat', 'hello')}])

    # now wait for the change in client type
    event = q.expect('dbus-signal', signal='ClientTypesUpdated')
    assertEquals(meredith_handle, event.args[0])
    assertEquals(['bot', 'console', 'handheld', 'phone'], sorted(event.args[1]))

    # that'll do
    #
    # ...
    #
    # wait wait! no it won't! Here's a regression test for
    # <https://bugs.freedesktop.org/show_bug.cgi?id=31772>.
    (caps, client, types) = build_stuff(TRANSIENT_PHONE)
    contact = '[email protected]/hai'
    send_presence(q, conn, stream, contact, caps)
    stanza = expect_disco(q, contact, client, caps)
    stream.send(make_presence(contact, type='unavailable'))
    send_disco_reply(stream, stanza, TRANSIENT_PHONE, [])

    # Gabble used to crash upon receiving a disco reply from a contact who's no
    # longer in the presence cache. So we sync here to check if it's died.
    sync_stream(q, stream)
Esempio n. 16
0
def test(q, bus, conn, stream):
    caps = {
        'node': client,
        'ver':  '0.1',
        }

    update_contact_caps(q, conn, stream, '[email protected]/Foo', caps)
    update_contact_caps(q, conn, stream, '[email protected]/Foo', caps)

    # Meredith signs in from one resource.
    update_contact_caps(q, conn, stream, '[email protected]/One', caps)
    # Meredith signs in from another resource with the same client. We don't
    # need to disco her, even though we don't trust this caps node in general
    # yet, because she's already told us what it means.
    meredith_two = '[email protected]/Two'
    q.forbid_events([
        EventPattern('stream-iq', to=meredith_two, query_ns=ns.DISCO_INFO)
        ])
    stream.send(make_presence(meredith_two, 'hello', caps=caps))
    sync_stream(q, stream)

    # Jens signs in from one resource, which is slow to answer the disco query.
    jens_one = '[email protected]/One'
    j = send_presence(q, conn, stream, jens_one, caps)
    j_stanza = expect_disco(q, jens_one, client, caps)

    # Jens now signs in elsewhere with the same client; we disco it (maybe
    # it'll reply sooner? Maybe his first client's network connection went away
    # and the server hasn't noticed yet?) and it replies immediately.
    update_contact_caps (q, conn, stream, '[email protected]/Two', caps,
        initial=False)

    # Jens' first client replies. We don't expect any caps changes here, and
    # this shouldn't count as a second point towards the five we need to trust
    # this caps node.
    send_disco_reply(stream, j_stanza, [], features)
    check_caps (conn, j)

    update_contact_caps (q, conn, stream, '[email protected]/Foo', caps)

    # Now five distinct contacts have told us what this caps node means, we
    # trust it.
    update_contact_caps (q, conn, stream, '[email protected]/Foo', caps,
        disco = False)
    update_contact_caps (q, conn, stream, '[email protected]/Foo', caps,
        disco = False)

    caps = {
        'node': client,
        'ver':  compute_caps_hash([], features, fake_client_dataforms),
        'hash': 'sha-1',
        }

    update_contact_caps(q, conn, stream, '[email protected]/Foo',
       caps,  dataforms = fake_client_dataforms)
    # We can verify the reply for these caps against the hash, and thus never
    # need to disco it again.
    update_contact_caps(q, conn, stream, '[email protected]/Foo', caps,
        disco = False, dataforms = fake_client_dataforms)
    update_contact_caps(q, conn, stream, '[email protected]/Foo', caps,
        disco = False, dataforms = fake_client_dataforms)
Esempio n. 17
0
def test_hash(q, bus, conn, stream, contact, contact_handle, client):
    global caps_changed_flag

    presence = make_presence(contact, status='hello')
    stream.send(presence)

    q.expect('dbus-signal', signal='PresencesChanged',
            args=[{contact_handle:
                (2, u'available', 'hello')}])

    # no special capabilities
    basic_caps = [(contact_handle, cs.CHANNEL_TYPE_TEXT, 3, 0)]
    assert conn.Capabilities.GetCapabilities([contact_handle]) == basic_caps

    # send updated presence with Jingle caps info
    presence = make_presence(contact, status='hello',
        caps={'node': client,
              'ver':  '0.1',
             })
    stream.send(presence)

    # Gabble looks up our capabilities
    event = q.expect('stream-iq', to=contact,
        query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
    assert query_node.attributes['node'] == \
        client + '#' + '0.1'

    # send good reply
    send_disco_reply(stream, event.stanza, [], jingle_av_features)

    # we can now do audio calls
    event = q.expect('dbus-signal', signal='CapabilitiesChanged')
    caps_diff = event.args[0]
    media_diff = [c for c in caps_diff
                    if c[1] == cs.CHANNEL_TYPE_STREAMED_MEDIA][0]
    assert media_diff[5] & cs.MEDIA_CAP_AUDIO, media_diff[5]
    caps_changed_flag = False

    # Send presence without any capabilities. XEP-0115 §8.4 Caps Optimization
    # says “receivers of presence notifications MUST NOT expect an annotation
    # on every presence notification they receive”, so the contact should still
    # be media-capable afterwards.
    stream.send(make_presence(contact, status='very capable'))
    q.expect('dbus-signal', signal='PresencesChanged',
        args=[{contact_handle: (2, u'available', 'very capable')}])
    ye_olde_caps = conn.Capabilities.GetCapabilities([contact_handle])
    assertLength(1, [c for c in ye_olde_caps
                       if c[1] == cs.CHANNEL_TYPE_STREAMED_MEDIA and
                          c[3] & cs.MEDIA_CAP_AUDIO])

    # send bogus presence
    caps = {
        'node': client,
        'ver':  'ceci=nest=pas=un=hash',
        'hash': 'sha-1',
        }
    presence = make_presence(contact, status='hello', caps=caps)
    stream.send(presence)

    # Gabble looks up our capabilities
    event = q.expect('stream-iq', to=contact,
        query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
    assert query_node.attributes['node'] == \
        client + '#' + caps['ver']

    # send bogus reply
    send_disco_reply(stream, event.stanza, [],
        ['http://jabber.org/protocol/bogus-feature'])

    # don't receive any D-Bus signal
    sync_dbus(bus, q, conn)
    sync_stream(q, stream)
    assert caps_changed_flag == False


    # send presence with empty caps
    presence = make_presence(contact, status='hello',
        caps={'node': client,
              'ver':  '0.0',
             })
    stream.send(presence)

    # Gabble looks up our capabilities
    event = q.expect('stream-iq', to=contact,
        query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
    assert query_node.attributes['node'] == \
        client + '#' + '0.0'

    # still don't receive any D-Bus signal
    sync_dbus(bus, q, conn)
    assert caps_changed_flag == False

    # send good reply
    result = make_result_iq(stream, event.stanza)
    query = result.firstChildElement()
    stream.send(result)

    # we can now do nothing
    event = q.expect('dbus-signal', signal='CapabilitiesChanged')
    assert caps_changed_flag == True
    caps_changed_flag = False


    # send correct presence
    ver = compute_caps_hash(some_identities, jingle_av_features, fake_client_dataforms)
    caps = {
        'node': client,
        'ver':  ver,
        'hash': 'sha-1',
        }
    presence = make_presence(contact, status='hello', caps=caps)
    stream.send(presence)

    # Gabble looks up our capabilities
    event = q.expect('stream-iq', to=contact,
        query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
    assert query_node.attributes['node'] == \
        client + '#' + caps['ver']

    # don't receive any D-Bus signal
    sync_dbus(bus, q, conn)
    assert caps_changed_flag == False

    # send good reply
    send_disco_reply(
        stream, event.stanza, some_identities, jingle_av_features, fake_client_dataforms)

    # we can now do audio calls
    event = q.expect('dbus-signal', signal='CapabilitiesChanged',
    )
    assert caps_changed_flag == True
    caps_changed_flag = False
Esempio n. 18
0
def test_two_clients(q, bus, conn, stream, contact1, contact2,
        contact_handle1, contact_handle2, client, broken_hash):
    global caps_changed_flag

    presence = make_presence(contact1, status='hello')
    stream.send(presence)

    q.expect('dbus-signal', signal='PresencesChanged',
            args=[{contact_handle1:
                (2, u'available', 'hello')}])

    presence = make_presence(contact2, status='hello')
    stream.send(presence)

    q.expect('dbus-signal', signal='PresencesChanged',
            args=[{contact_handle2:
                (2, u'available', 'hello')}])

    # no special capabilities
    basic_caps = [(contact_handle1, cs.CHANNEL_TYPE_TEXT, 3, 0)]
    assert conn.Capabilities.GetCapabilities([contact_handle1]) == basic_caps
    basic_caps = [(contact_handle2, cs.CHANNEL_TYPE_TEXT, 3, 0)]
    assert conn.Capabilities.GetCapabilities([contact_handle2]) == basic_caps

    # send updated presence with Jingle caps info
    ver = compute_caps_hash(some_identities, jingle_av_features, {})
    caps = {
        'node': client,
        'ver': ver,
        'hash': 'sha-1',
        }
    presence = make_presence(contact1, status='hello', caps=caps)
    stream.send(presence)
    presence = make_presence(contact2, status='hello', caps=caps)
    stream.send(presence)

    # Gabble looks up our capabilities
    event = q.expect('stream-iq', to=contact1,
        query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
    assert query_node.attributes['node'] == \
        client + '#' + ver

    # don't receive any D-Bus signal
    sync_dbus(bus, q, conn)
    assert caps_changed_flag == False

    result = make_caps_disco_reply(
        stream, event.stanza, some_identities, jingle_av_features)

    if broken_hash:
        # make the hash break!
        query = result.firstChildElement()
        query.addElement('feature')['var'] = 'http://example.com/another-feature'

    stream.send(result)

    if broken_hash:
        # Gabble looks up our capabilities again because the first contact
        # failed to provide a valid hash
        event = q.expect('stream-iq', to=contact2,
            query_ns='http://jabber.org/protocol/disco#info')
        query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
        assert query_node.attributes['node'] == \
            client + '#' + ver

        # don't receive any D-Bus signal
        sync_dbus(bus, q, conn)
        assert caps_changed_flag == False

        # send good reply
        send_disco_reply(stream, event.stanza, some_identities, jingle_av_features)

    # we can now do audio calls with both contacts
    event = q.expect('dbus-signal', signal='CapabilitiesChanged',
        args=[[(contact_handle2, cs.CHANNEL_TYPE_STREAMED_MEDIA, 0, 3, 0,
            cs.MEDIA_CAP_AUDIO | cs.MEDIA_CAP_VIDEO)]])
    if not broken_hash:
        # if the first contact failed to provide a good hash, it does not
        # deserve its capabilities to be understood by Gabble!
        event = q.expect('dbus-signal', signal='CapabilitiesChanged',
            args=[[(contact_handle1, cs.CHANNEL_TYPE_STREAMED_MEDIA, 0, 3, 0,
                cs.MEDIA_CAP_AUDIO | cs.MEDIA_CAP_VIDEO)]])

    caps_changed_flag = False

    # don't receive any D-Bus signal
    sync_dbus(bus, q, conn)
    assert caps_changed_flag == False
Esempio n. 19
0
 def send_remote_disco_reply(self, stanza):
     send_disco_reply(self.stream, stanza, [], self.remote_feats)
Esempio n. 20
0
def test(q, bus, conn, stream):
    client = 'http://example.com/perverse-client'
    contact_bare_jid = '*****@*****.**'
    contact_with_resource = '[email protected]/hi'
    contact_handle = conn.RequestHandles(cs.HT_CONTACT, [contact_bare_jid])[0]

    # Gabble gets a presence stanza from a bare JID, which is a tad surprising.
    features = [
        ns.JINGLE_015,
        ns.JINGLE_015_AUDIO,
        ns.JINGLE_015_VIDEO,
        ns.GOOGLE_P2P,
        ]
    caps = {'node': client,
            'hash': 'sha-1',
            'ver': compute_caps_hash([], features, {}),
           }
    p = make_presence(contact_bare_jid, status='Hello', caps=caps)
    stream.send(p)

    # Gabble looks up the hash
    event = q.expect('stream-iq', to=contact_bare_jid,
        query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
    assertEquals(client + '#' + caps['ver'], query_node.attributes['node'])

    # The bare jid replies
    send_disco_reply(stream, event.stanza, [], features)

    # Gabble lets us know their caps have changed. (Gabble used to ignore the
    # reply.)
    streamed_media_caps = (contact_handle, cs.CHANNEL_TYPE_STREAMED_MEDIA,
        0, 3, 0, cs.MEDIA_CAP_AUDIO | cs.MEDIA_CAP_VIDEO)
    e = q.expect('dbus-signal', signal='CapabilitiesChanged')
    assertContains(streamed_media_caps, e.args[0])

    # Gabble gets another presence stanza from the bare JID, with different
    # caps.
    features.append(ns.TUBES)
    caps = {'node': client,
            'hash': 'sha-1',
            'ver': compute_caps_hash([], features, {}),
           }
    p = make_presence(contact_bare_jid, status='Get out the abacus', caps=caps)
    stream.send(p)

    # Gabble looks up the new hash
    disco2 = q.expect('stream-iq', to=contact_bare_jid,
        query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', disco2.stanza)[0]
    assertEquals(client + '#' + caps['ver'], query_node.attributes['node'])

    # This time, before the bare JID replies, Gabble gets a presence from the
    # resourceful jid.
    features_ = features + [ns.CHAT_STATES]
    caps = {'node': client,
            'hash': 'sha-1',
            'ver': compute_caps_hash([], features_, {}),
           }
    p = make_presence(contact_with_resource, status='Count this', caps=caps)
    stream.send(p)

    # Gabble throws away presence from the bare JID when it gets presence from
    # a resource (and vice versa), so it should now say the contact is
    # incapable.  Gabble also looks up the resourceful JID's hash.
    cc, disco3 = q.expect_many(
        EventPattern('dbus-signal', signal='CapabilitiesChanged'),
        EventPattern('stream-iq', to=contact_with_resource,
            query_ns='http://jabber.org/protocol/disco#info'),
        )

    assertDoesNotContain(streamed_media_caps, cc.args[0])

    query_node = xpath.queryForNodes('/iq/query', disco3.stanza)[0]
    assertEquals(client + '#' + caps['ver'], query_node.attributes['node'])

    # The bare jid replies! Getting a disco reply from a bare JID when we've
    # got presence from resources used to crash Gabble, but now it just ignores
    # it.
    send_disco_reply(stream, disco2.stanza, [], features)

    # Now the resourceful JID replies:
    send_disco_reply(stream, disco3.stanza, [], features_)

    # Gabble should announce that the contact has acquired some caps.
    e = q.expect('dbus-signal', signal='CapabilitiesChanged')
    assertContains(streamed_media_caps, e.args[0])
Esempio n. 21
0
def test_two_clients(q, bus, conn, stream, contact1, contact2,
        contact_handle1, contact_handle2, client, broken_hash):

    presence = make_presence(contact1, status='hello')
    stream.send(presence)

    q.expect('dbus-signal', signal='PresencesChanged',
            args=[{contact_handle1:
                (2, u'available', 'hello')}])

    presence = make_presence(contact2, status='hello')
    stream.send(presence)

    q.expect('dbus-signal', signal='PresencesChanged',
            args=[{contact_handle2:
                (2, u'available', 'hello')}])

    # no special capabilities
    for h in (contact_handle1, contact_handle2):
        for rcc in get_contacts_capabilities_sync(conn, [h])[h]:
            assertEquals(cs.CHANNEL_TYPE_TEXT, rcc[0].get(cs.CHANNEL_TYPE))

    # send updated presence with Jingle caps info
    ver = compute_caps_hash(some_identities, jingle_av_features, {})
    caps = {
        'node': client,
        'ver': ver,
        'hash': 'sha-1',
        }
    presence = make_presence(contact1, status='hello', caps=caps)
    stream.send(presence)
    presence = make_presence(contact2, status='hello', caps=caps)
    stream.send(presence)

    # Gabble looks up our capabilities
    event = q.expect('stream-iq', to=contact1,
        query_ns='http://jabber.org/protocol/disco#info')
    query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
    assert query_node.attributes['node'] == \
        client + '#' + ver

    # don't receive any D-Bus signal
    forbidden = [
        EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'),
        ]
    q.forbid_events(forbidden)
    sync_dbus(bus, q, conn)
    q.unforbid_events(forbidden)

    result = make_caps_disco_reply(
        stream, event.stanza, some_identities, jingle_av_features)

    if broken_hash:
        # make the hash break!
        query = result.firstChildElement()
        query.addElement('feature')['var'] = 'http://example.com/another-feature'

    stream.send(result)

    if broken_hash:
        # Gabble looks up our capabilities again because the first contact
        # failed to provide a valid hash
        event = q.expect('stream-iq', to=contact2,
            query_ns='http://jabber.org/protocol/disco#info')
        query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
        assert query_node.attributes['node'] == \
            client + '#' + ver

        # don't receive any D-Bus signal
        q.forbid_events(forbidden)
        sync_dbus(bus, q, conn)
        q.unforbid_events(forbidden)

        # send good reply
        send_disco_reply(stream, event.stanza, some_identities, jingle_av_features)

    # we can now do audio calls
    cc, = q.expect_many(
        EventPattern('dbus-signal', signal='ContactCapabilitiesChanged',
            predicate=lambda e: contact_handle2 in e.args[0]),
        )
    assert_rccs_callable(cc.args[0][contact_handle2])

    if not broken_hash:
        # if the first contact failed to provide a good hash, it does not
        # deserve its capabilities to be understood by Gabble!
        cc, = q.expect_many(
            EventPattern('dbus-signal', signal='ContactCapabilitiesChanged',
                predicate=lambda e: contact_handle1 in e.args[0]),
            )
        assert_rccs_callable(cc.args[0][contact_handle1])

    # don't receive any further signals
    q.forbid_events(forbidden)
    sync_dbus(bus, q, conn)
    q.unforbid_events(forbidden)