def _await_descriptor_upload(tor_protocol, onion, progress, await_all_uploads):
    """
    Internal helper.

    :param tor_protocol: ITorControlProtocol instance

    :param onion: IOnionService instance

    :param progress: a progess callback, or None

    :returns: a Deferred that fires once we've detected at least one
        descriptor upload for the service (as detected by listening for
        HS_DESC events)
    """
    # For v3 services, Tor attempts to upload to 16 services; we'll
    # assume that for now but also cap it (we want to show some
    # progress for "attempting uploads" but we need to decide how
    # much) .. so we leave 50% of the "progress" for attempts, and the
    # other 50% for "are we done" (which is either "one thing
    # uploaded" or "all the things uploaded")
    attempted_uploads = set()
    confirmed_uploads = set()
    failed_uploads = set()
    uploaded = defer.Deferred()
    await_all = False if await_all_uploads is None else await_all_uploads

    def translate_progress(tag, description):
        if progress:
            done = len(confirmed_uploads) + len(failed_uploads)
            done_endpoint = float(len(attempted_uploads)) if await_all else 1.0
            done_pct = 0 if not attempted_uploads else float(done) / done_endpoint
            started_pct = float(min(16, len(attempted_uploads))) / 16.0
            try:
                progress(
                    (done_pct * 50.0) + (started_pct * 50.0),
                    tag,
                    description,
                )
            except Exception:
                log.err()

    def hostname_matches(hostname):
        if IAuthenticatedOnionClients.providedBy(onion):
            return hostname[:-6] == onion.get_permanent_id()
        else:
            # provides IOnionService
            return onion.hostname == hostname

    def hs_desc(evt):
        """
        From control-spec:
        "650" SP "HS_DESC" SP Action SP HSAddress SP AuthType SP HsDir
        [SP DescriptorID] [SP "REASON=" Reason] [SP "REPLICA=" Replica]
        """
        args = evt.split()
        subtype = args[0]
        if subtype == 'UPLOAD':
            if hostname_matches('{}.onion'.format(args[1])):
                attempted_uploads.add(args[3])
                translate_progress(
                    "wait_descriptor",
                    "Upload to {} started".format(args[3])
                )

        elif subtype == 'UPLOADED':
            # we only need ONE successful upload to happen for the
            # HS to be reachable.
            # unused? addr = args[1]

            # XXX FIXME I think tor is sending the onion-address
            # properly with these now, so we can use those
            # (i.e. instead of matching to "attempted_uploads")
            if args[3] in attempted_uploads:
                confirmed_uploads.add(args[3])
                log.msg("Uploaded '{}' to '{}'".format(args[1], args[3]))
                translate_progress(
                    "wait_descriptor",
                    "Successful upload to {}".format(args[3])
                )
                if not uploaded.called:
                    if await_all:
                        if (len(failed_uploads) + len(confirmed_uploads)) == len(attempted_uploads):
                            uploaded.callback(onion)
                    else:
                        uploaded.callback(onion)

        elif subtype == 'FAILED':
            if hostname_matches('{}.onion'.format(args[1])):
                failed_uploads.add(args[3])
                translate_progress(
                    "wait_descriptor",
                    "Failed upload to {}".format(args[3])
                )
                if failed_uploads == attempted_uploads:
                    msg = "Failed to upload '{}' to: {}".format(
                        args[1],
                        ', '.join(failed_uploads),
                    )
                    uploaded.errback(RuntimeError(msg))

    # the first 'yield' should be the add_event_listener so that a
    # caller can do "d = _await_descriptor_upload()", then add the
    # service.
    yield tor_protocol.add_event_listener('HS_DESC', hs_desc)
    yield uploaded
    yield tor_protocol.remove_event_listener('HS_DESC', hs_desc)
    # ensure we show "100%" at the end
    if progress:
        if await_all_uploads:
            msg = "Completed descriptor uploads"
        else:
            msg = "At least one descriptor uploaded"
        try:
            progress(100.0, "wait_descriptor", msg)
        except Exception:
            log.err()
Internal helper.

    :param tor_protocol: ITorControlProtocol instance

    :param onion: IOnionService instance

    :param progress: a progess callback, or None

    :returns: a Deferred that fires once we've detected at least one
        descriptor upload for the service (as detected by listening for
        HS_DESC events)