'Handle a device key query from a client
"device_keys": {
"<user_id>": ["<device_id>"]
"device_keys": {
"<user_id>": {
"<device_id>": {'

@defer.inlineCallbacks
def query_devices(self, query_body, timeout):

    device_keys_query = query_body.get('device_keys', {})
    local_query = {}
    remote_queries = {}
    for (user_id, device_ids) in device_keys_query.items():
        if self.is_mine_id(user_id):
            local_query[user_id] = device_ids
        else:
            remote_queries[user_id] = device_ids
    failures = {}
    results = {}
    if local_query:
        local_result = (yield self.query_local_devices(local_query))
        for (user_id, keys) in local_result.items():
            if (user_id in local_query):
                results[user_id] = keys
    remote_queries_not_in_cache = {}
    if remote_queries:
        query_list = []
        for (user_id, device_ids) in remote_queries.iteritems():
            if device_ids:
                query_list.extend(((user_id, device_id) for device_id in device_ids))
            else:
                query_list.append((user_id, None))
        (user_ids_not_in_cache, remote_results) = (yield self.store.get_user_devices_from_cache(query_list))
        for (user_id, devices) in remote_results.iteritems():
            user_devices = results.setdefault(user_id, {})
            for (device_id, device) in devices.iteritems():
                keys = device.get('keys', None)
                device_display_name = device.get('device_display_name', None)
                if keys:
                    result = dict(keys)
                    unsigned = result.setdefault('unsigned', {})
                    if device_display_name:
                        unsigned['device_display_name'] = device_display_name
                    user_devices[device_id] = result
        for user_id in user_ids_not_in_cache:
            domain = get_domain_from_id(user_id)
            r = remote_queries_not_in_cache.setdefault(domain, {})
            r[user_id] = remote_queries[user_id]
    @defer.inlineCallbacks
    def do_remote_query(destination):
        destination_query = remote_queries_not_in_cache[destination]
        try:
            remote_result = (yield self.federation.query_client_keys(destination, {'device_keys': destination_query}, timeout=timeout))
            for (user_id, keys) in remote_result['device_keys'].items():
                if (user_id in destination_query):
                    results[user_id] = keys
        except CodeMessageException as e:
            failures[destination] = {'status': e.code, 'message': e.message}
        except NotRetryingDestination as e:
            failures[destination] = {'status': 503, 'message': 'Not    ready    for    retry'}
        except Exception as e:
            failures[destination] = {'status': 503, 'message': e.message}
    (yield make_deferred_yieldable(defer.gatherResults([preserve_fn(do_remote_query)(destination) for destination in remote_queries_not_in_cache])))
    defer.returnValue({'device_keys': results, 'failures': failures})
