File Coverage

blib/lib/OpenTelemetry/TraceContext/W3C.pm
Criterion Covered Total %
statement 70 71 98.5
branch 29 32 90.6
condition 23 29 79.3
subroutine 9 9 100.0
pod 3 5 60.0
total 134 146 91.7


line stmt bran cond sub pod time code
1             package OpenTelemetry::TraceContext::W3C;
2             # ABSTRACT: W3C Trace Context implementation
3              
4 7     7   566202 use strict;
  7         80  
  7         234  
5 7     7   50 use warnings;
  7         20  
  7         187  
6 7     7   34 use Exporter qw();
  7         16  
  7         8268  
7              
8             our $VERSION = '0.02'; # VERSION
9              
10             *import = \&Exporter::import;
11              
12             our @EXPORT_OK = qw(
13             parse_traceparent
14             format_traceparent
15             format_traceparent_v00
16              
17             parse_tracestate
18             format_tracestate
19             update_tracestate
20             format_tracestate_v00
21             );
22             our %EXPORT_TAGS = (
23             all => \@EXPORT_OK,
24             );
25              
26             my $match_traceparent_v0 = do {
27             my $h = '[0-9a-f]';
28              
29             qr{^(${h}{2})-(${h}{32})-(${h}{16})-(${h}{2})(-|\z)};
30             };
31             my $invalid_trace_id = '0' x 32;
32             my $invalid_parent_id = '0' x 16;
33              
34             my $match_tracestate_listmember_v0 = do {
35             my $a = '[a-z]';
36             my $ad = '[a-z0-9]';
37             my $id_char = '[a-z0-9_*/-]';
38              
39             my $vc = '[\x20-\x2b\x2d-\x3c\x3e-\x7e]';
40             my $vnb = '[\x21-\x2b\x2d-\x3c\x3e-\x7e]';
41              
42             qr{(?:(${a}${id_char}{0,255})|(${ad}${id_char}{0,240})@(${a}${id_char}{0,13}))=(${vc}{0,255}${vnb})};
43             };
44              
45             sub parse_traceparent {
46 9     9 1 189 my ($value) = @_;
47              
48 9 100       85 if ($value !~ $match_traceparent_v0) {
49 1         31 return undef;
50             }
51              
52 8         50 my ($version, $trace_id, $parent_id, $trace_flags, $next_char) =
53             (hex $1, $2, $3, hex $4, $5);
54 8 100 100     55 if ($version == 0xff || ($version == 0x00 && $next_char ne '')) {
      100        
55 2         10 return undef;
56             }
57 6 100 100     32 if ($trace_id eq $invalid_trace_id || $parent_id eq $invalid_parent_id) {
58 2         11 return undef;
59             }
60              
61             return {
62 4         41 version => $version,
63             trace_id => $trace_id,
64             parent_id => $parent_id,
65             trace_flags => $trace_flags,
66             };
67             }
68              
69             sub format_traceparent_v00 {
70 6     6 0 100 my ($parsed) = @_;
71 6         16 my ($trace_id, $parent_id) = ($parsed->{trace_id}, $parsed->{parent_id});
72              
73 6 100 66     67 if (!$trace_id || length($trace_id) != 32 || $trace_id eq $invalid_trace_id) {
      66        
74 1         5 return undef;
75             }
76 5 100 66     30 if (!$parent_id || length($parent_id) != 16 || $parent_id eq $invalid_parent_id) {
      66        
77 1         6 return undef;
78             }
79              
80 4         36 return sprintf "00-%s-%s-%02d", $trace_id, $parent_id, $parsed->{trace_flags} & 0x01;
81             }
82              
83             sub parse_tracestate {
84 7     7 1 893 my ($value) = @_;
85 7         56 my @parts = split /[\x20\x09]*,[\x20\x09]*/, $value;
86              
87             # TODO check
88             # if there are more than 32 parts it is not clear if the whole header
89             # should be considered invalid, or whether it should be truncated to 32,
90             # I'm picking the latter here
91 7 50       27 $#parts = 31 if $#parts > 31;
92              
93 7         16 my @list_members;
94 7         24 for my $part (@parts) {
95 15         33 my $list_member = _make_tracestate_list_member($part);
96 15 100       36 next unless $list_member;
97              
98 12         28 push @list_members, $list_member;
99             }
100              
101 7         87 return { list_members => \@list_members };
102             }
103              
104             sub format_tracestate_v00 {
105 7     7 0 107 my ($parsed, $options) = @_;
106 7 100 50     26 my $max_length = $options ? $options->{max_length} // 512 : 512;
107 7         15 my $formatted = join ',', map "$_->{key}=$_->{value}", @{$parsed->{list_members}};
  7         57  
108              
109 7 100       28 if (length($formatted) > $max_length) {
110 3         27 my @chopping_list = map [length($_->{key}) + length($_->{value}) + 2, $_], @{$parsed->{list_members}};
  3         19  
111 3         6 my $length = length($formatted);
112              
113 3   66     18 for (my $i = $#chopping_list; $i >= 0 && $length > $max_length; --$i) {
114 6 100       22 next if $chopping_list[$i][0] < 129;
115 1         2 $length -= $chopping_list[$i][0];
116 1         4 splice @chopping_list, $i, 1;
117             }
118              
119 3   100     15 while (@chopping_list && $length > $max_length) {
120 3         7 $length -= $chopping_list[-1][0];
121 3         9 pop @chopping_list;
122             }
123              
124 3         16 $formatted = join ',', map "$_->[1]{key}=$_->[1]{value}", @chopping_list;
125             }
126              
127 7         34 return $formatted;
128             }
129              
130             sub update_tracestate {
131 3     3 1 4049 my ($parsed, $key, $value) = @_;
132              
133 3         11 my $list_member = _make_tracestate_list_member("${key}=${value}");
134 3 50       10 return 0 unless $list_member;
135              
136 3         5 for my $i (0 .. $#{$parsed->{list_members}}) {
  3         12  
137 3 100       20 if ($parsed->{list_members}[$i]{key} eq $list_member->{key}) {
138 1         2 splice @{$parsed->{list_members}}, $i , 1;
  1         4  
139 1         3 last;
140             }
141             }
142              
143 3         7 unshift @{$parsed->{list_members}}, $list_member;
  3         8  
144 3 50       5 $#{$parsed->{list_members}} = 31 if $#{$parsed->{list_members}} > 31;
  0         0  
  3         9  
145              
146 3         8 return 1;
147             }
148              
149             sub _make_tracestate_list_member {
150 18     18   35 my ($formatted_string) = @_;
151              
152 18 100       128 if ($formatted_string !~ $match_tracestate_listmember_v0) {
153 3         8 return undef;
154             }
155              
156 15         67 my ($simple_key, $tenant_id, $system_id, $value) = ($1, $2, $3, $4);
157             return {
158 15 100       91 !$simple_key ? () : ( key => $simple_key ),
    100          
159             !$tenant_id ? () : (
160             key => "${tenant_id}\@${system_id}",
161             tenant_id => $tenant_id,
162             system_id => $system_id,
163             ),
164             value => $value,
165             };
166             }
167              
168             *format_traceparent = \&format_traceparent_v00;
169             *format_tracestate = \&format_tracestate_v00;
170              
171             1;
172              
173             __END__