github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/w3ctrace/version.go (about)

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2020
     3  
     4  package w3ctrace
     5  
     6  import (
     7  	"encoding/hex"
     8  	"fmt"
     9  	"strconv"
    10  )
    11  
    12  // Version represents the W3C trace context version. It defines the format of `traceparent` header
    13  type Version uint8
    14  
    15  const (
    16  	// Version_Invalid represend an invalid  W3C Trace Context version
    17  	Version_Invalid Version = iota
    18  	// Version_0 represent the W3C Trace Context version 00
    19  	Version_0
    20  	// Version_Max is the latest version of W3C Trace Context supported by this package
    21  	Version_Max = Version_0
    22  )
    23  
    24  // ParseVersion parses the version part of a `traceparent` header value. It returns ErrContextCorrupted
    25  // if the version is malformed
    26  func ParseVersion(s string) (Version, error) {
    27  	if len(s) < 2 || (len(s) > 2 && s[2] != '-') {
    28  		return Version_Invalid, ErrContextCorrupted
    29  	}
    30  	s = s[:2]
    31  
    32  	if s == "ff" {
    33  		return Version_Invalid, nil
    34  	}
    35  
    36  	ver, err := strconv.ParseUint(s, 16, 8)
    37  	if err != nil {
    38  		return Version_Invalid, ErrContextCorrupted
    39  	}
    40  
    41  	return Version(ver + 1), nil
    42  }
    43  
    44  // String returns string representation of a trace parent version. The returned value is compatible with the
    45  // `traceparent` header format. The caller should take care of handling the Version_Unknown, otherwise this
    46  // method will return "ff" which is considered invalid
    47  func (ver Version) String() string {
    48  	if ver == Version_Invalid {
    49  		return "ff"
    50  	}
    51  
    52  	return fmt.Sprintf("%02x", uint8(ver)-1)
    53  }
    54  
    55  // parseParent parses the version-format string as described in https://www.w3.org/TR/trace-context/#version-format
    56  func (ver Version) parseParent(s string) (Parent, error) {
    57  	if ver == Version_Invalid {
    58  		return Parent{Version: Version_Invalid}, ErrContextCorrupted
    59  	}
    60  
    61  	// If a higher version is detected, we try to parse it as the highest version
    62  	// that is currently supported
    63  	if ver > Version_Max {
    64  		ver = Version_Max
    65  	}
    66  
    67  	switch ver {
    68  	case Version_0:
    69  		return parseV0Parent(s)
    70  	default:
    71  		return Parent{Version: ver}, ErrUnsupportedVersion
    72  	}
    73  }
    74  
    75  // formatParent returns the version-format string for this version as described in
    76  // https://www.w3.org/TR/trace-context/#version-format. The returned value is
    77  // empty if the version is not supported or invalid
    78  func (ver Version) formatParent(p Parent) string {
    79  	// Construct the new traceparent field according to the highest version of
    80  	// the specification known to the implementation
    81  	if ver > Version_Max {
    82  		ver = Version_Max
    83  	}
    84  
    85  	switch ver {
    86  	case Version_0:
    87  		return formatV0Parent(p)
    88  	default:
    89  		return ""
    90  	}
    91  }
    92  
    93  // W3C Trace Context v0 version-format parsing/formatting
    94  const (
    95  	v0SampledFlag uint8 = 1 << iota
    96  )
    97  
    98  func parseV0Parent(s string) (Parent, error) {
    99  	const (
   100  		versionFormatLen = 55
   101  		versionLen       = 2
   102  		traceIDLen       = 32
   103  		parentIDLen      = 16
   104  		flagsLen         = 2
   105  		separator        = '-'
   106  		invalidTraceID   = "00000000000000000000000000000000"
   107  		invalidParentID  = "0000000000000000"
   108  	)
   109  
   110  	// trim version part
   111  	if len(s) < versionFormatLen || s[versionLen] != separator {
   112  		return Parent{}, ErrContextCorrupted
   113  	}
   114  	_, s = s[:versionLen], s[versionLen+1:]
   115  
   116  	// extract trace id
   117  	if s[traceIDLen] != separator {
   118  		return Parent{}, ErrContextCorrupted
   119  	}
   120  	traceID, s := s[:traceIDLen], s[traceIDLen+1:]
   121  
   122  	if traceID == invalidTraceID || !isHex(traceID) {
   123  		return Parent{}, ErrContextCorrupted
   124  	}
   125  
   126  	// extract parent id
   127  	if s[parentIDLen] != separator {
   128  		return Parent{}, ErrContextCorrupted
   129  	}
   130  	parentID, s := s[:parentIDLen], s[parentIDLen+1:]
   131  
   132  	if parentID == invalidParentID || !isHex(parentID) {
   133  		return Parent{}, ErrContextCorrupted
   134  	}
   135  
   136  	// extract and parse flags
   137  	if len(s) > flagsLen && s[flagsLen] != separator {
   138  		return Parent{}, ErrContextCorrupted
   139  	}
   140  
   141  	flags, err := strconv.ParseUint(s[:flagsLen], 16, 8)
   142  	if err != nil {
   143  		return Parent{}, ErrContextCorrupted
   144  	}
   145  
   146  	return Parent{
   147  		Version:  Version_0,
   148  		TraceID:  traceID,
   149  		ParentID: parentID,
   150  		Flags: Flags{
   151  			Sampled: uint8(flags)&v0SampledFlag != 0,
   152  		},
   153  	}, nil
   154  }
   155  
   156  func formatV0Parent(p Parent) string {
   157  	var flags uint8
   158  	if p.Flags.Sampled {
   159  		flags |= v0SampledFlag
   160  	}
   161  
   162  	return fmt.Sprintf("00-%032s-%016s-%02x", p.TraceID, p.ParentID, flags)
   163  }
   164  
   165  func isHex(s string) bool {
   166  	_, err := hex.DecodeString(s)
   167  	return err == nil
   168  }