def _parse_single_header(b3_header):
    """
    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 = dict.fromkeys(("trace_id", "span_id", "parent_span_id", "sampled_str"))

    # b3={TraceId}-{SpanId}-{SamplingState}-{ParentSpanId}
    #      (last 2 fields optional)
    # OR
    # b3={SamplingState}
    bits = b3_header.split("-")

    # Handle the lone-sampling-decision case:
    if len(bits) == 1:
        if bits[0] in ("0", "1", "d"):
            parsed["sampled_str"] = bits[0]
            return parsed
        raise ValueError("Invalid sample-only value: %r" % bits[0])
    if len(bits) > 4:
        # Too many segments
        raise ValueError("Too many segments in b3 header: %r" % b3_header)
    parsed["trace_id"] = bits[0]
    if not parsed["trace_id"]:
        raise ValueError("Bad or missing TraceId")
    parsed["span_id"] = bits[1]
    if not parsed["span_id"]:
        raise ValueError("Bad or missing SpanId")
    if len(bits) > 3:
        parsed["parent_span_id"] = bits[3]
        if not parsed["parent_span_id"]:
            raise ValueError("Got empty ParentSpanId")
    if len(bits) > 2:
        # Empty-string means "missing" which means "Defer"
        if bits[2]:
            parsed["sampled_str"] = bits[2]
            if parsed["sampled_str"] not in ("0", "1", "d"):
                raise ValueError("Bad SampledState: %r" % parsed["sampled_str"])
    return parsed