def _parse_multi_header(headers):
    """
    Parse out and return the data necessary for generating ZipkinAttrs.

    Returns a dict with the following keys:
        'trace_id':             str or None
        'span_id':              str or None
        'parent_span_id':       str or None
        'sampled_str':          '0', '1', 'd', or None (defer)
    """
    parsed = {
        "trace_id": headers.get("X-B3-TraceId", None),
        "span_id": headers.get("X-B3-SpanId", None),
        "parent_span_id": headers.get("X-B3-ParentSpanId", None),
        "sampled_str": headers.get("X-B3-Sampled", None),
    }
    # Normalize X-B3-Flags and X-B3-Sampled to None, '0', '1', or 'd'
    if headers.get("X-B3-Flags") == "1":
        parsed["sampled_str"] = "d"
    if parsed["sampled_str"] == "true":
        parsed["sampled_str"] = "1"
    elif parsed["sampled_str"] == "false":
        parsed["sampled_str"] = "0"
    if parsed["sampled_str"] not in (None, "1", "0", "d"):
        raise ValueError("Got invalid X-B3-Sampled: %s" % parsed["sampled_str"])
    for k in ("trace_id", "span_id", "parent_span_id"):
        if parsed[k] == "":
            raise ValueError("Got empty-string %r" % k)
    if parsed["trace_id"] and not parsed["span_id"]:
        raise ValueError("Got X-B3-TraceId but not X-B3-SpanId")
    elif parsed["span_id"] and not parsed["trace_id"]:
        raise ValueError("Got X-B3-SpanId but not X-B3-TraceId")

    # Handle the common case of no headers at all
    if not parsed["trace_id"] and not parsed["sampled_str"]:
        raise ValueError()  # won't trigger a log message

    return parsed