%%%------------------------------------------------------------------------ %% Copyright 2019, OpenTelemetry Authors %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% @doc %% @end %%%----------------------------------------------------------------------- -module(ot_propagation_http_w3c). -export([inject/2, encode/1, extract/2, decode/1]). -include_lib("opentelemetry_api/include/opentelemetry.hrl"). -include("ot_tracer.hrl"). -define(VERSION, <<"00">>). -define(ZERO_TRACEID, <<"00000000000000000000000000000000">>). -define(ZERO_SPANID, <<"0000000000000000">>). -define(HEADER_KEY, <<"traceparent">>). -define(STATE_HEADER_KEY, <<"tracestate">>). -define(KEY_MP, element(2, re:compile("^[a-z0-9][a-z0-9_*/-]{0,255}$|^([a-z0-9_*/-]{1,241})(@[a-z0-9_*/-]{1,14})$"))). -define(VALUE_MP, element(2, re:compile("^[ -~]{0,256}$"))). -define(MAX_TRACESTATE_PAIRS, 32). -spec inject(ot_propagation:http_headers(), tracer_ctx() | undefined) -> ot_propagation:http_headers(). inject(_, #tracer_ctx{active=#span_ctx{trace_id=TraceId, span_id=SpanId}, previous=_}) when TraceId =:= 0 orelse SpanId =:= 0 -> []; inject(_, #tracer_ctx{active=SpanCtx=#span_ctx{}, previous=_}) -> EncodedValue = encode(SpanCtx), [{?HEADER_KEY, EncodedValue} | encode_tracestate(SpanCtx)]; inject(_, undefined) -> []. -spec encode(opencensus:span_ctx()) -> iolist(). encode(#span_ctx{trace_id=TraceId, span_id=SpanId, trace_flags=TraceOptions}) -> Options = case TraceOptions band 1 of 1 -> <<"01">>; _ -> <<"00">> end, EncodedTraceId = io_lib:format("~32.16.0b", [TraceId]), EncodedSpanId = io_lib:format("~16.16.0b", [SpanId]), [?VERSION, "-", EncodedTraceId, "-", EncodedSpanId, "-", Options]. encode_tracestate(#span_ctx{tracestate=undefined}) -> []; encode_tracestate(#span_ctx{tracestate=Entries}) -> StateHeaderValue = lists:join($,, [[Key, $=, Value] || {Key, Value} <- Entries]), [{?STATE_HEADER_KEY, StateHeaderValue}]. -spec extract(ot_propagation:http_headers(), term()) -> opentelemetry:span_ctx()| undefined. extract(Headers, _) when is_list(Headers) -> case header_take(?HEADER_KEY, Headers) of [{_, Value} | RestHeaders] -> case header_member(?HEADER_KEY, RestHeaders) of true -> %% duplicate traceparent header found undefined; false -> case decode(string:trim(Value)) of undefined -> undefined; SpanCtx -> Tracestate = tracestate_from_headers(Headers), SpanCtx#span_ctx{tracestate=Tracestate} end end; _ -> undefined end; extract(_, _) -> undefined. tracestate_from_headers(Headers) -> %% could be multiple tracestate headers. Combine them all with comma separators case combine_headers(?STATE_HEADER_KEY, Headers) of [] -> undefined; FieldValue -> tracestate_decode(FieldValue) end. combine_headers(Key, Headers) -> lists:foldl(fun({K, V}, Acc) -> case string:equal(Key, string:casefold(K)) of true -> [Acc, $, | V]; false -> Acc end end, [], Headers). split(Pair) -> case string:split(Pair, "=", all) of [Key, Value] when Value =/= [] andalso Value =/= <<>> -> {iolist_to_binary(Key), iolist_to_binary(Value)}; _ -> undefined end. %% note: version ff (255) not allowed by spec decode(TraceContext) when is_list(TraceContext) -> decode(list_to_binary(TraceContext)); decode(<<_:2/binary, "-", TraceId:32/binary, "-", SpanId:16/binary, _/binary>>) when TraceId =:= ?ZERO_TRACEID orelse SpanId =:= ?ZERO_SPANID -> undefined; decode(<>) when Version >= ?VERSION andalso Version =/= <<"ff">> -> to_span_ctx(Version, TraceId, SpanId, Opts); %% future versions could have more after Opts, so allow for a trailing - decode(<>) when Version > ?VERSION andalso Version =/= <<"ff">> -> to_span_ctx(Version, TraceId, SpanId, Opts); decode(_) -> undefined. to_span_ctx(Version, TraceId, SpanId, Opts) -> try %% verify version is hexadecimal _ = binary_to_integer(Version, 16), #span_ctx{trace_id=binary_to_integer(TraceId, 16), span_id=binary_to_integer(SpanId, 16), trace_flags=case Opts of <<"01">> -> 1; <<"00">> -> 0; _ -> error(badarg) end} catch %% to integer from base 16 string failed error:badarg -> undefined end. tracestate_decode(Value) -> parse_pairs(string:lexemes(Value, [$,])). parse_pairs(Pairs) when length(Pairs) =< ?MAX_TRACESTATE_PAIRS -> parse_pairs(Pairs, []); parse_pairs(_) -> undefined. parse_pairs([], Acc) -> Acc; parse_pairs([Pair | Rest], Acc) -> case split(string:trim(Pair)) of {K, V} -> case re:run(K, ?KEY_MP) =/= nomatch andalso not lists:keymember(K, 1, Acc) andalso re:run(V, ?VALUE_MP) =/= nomatch of false -> undefined; true -> parse_pairs(Rest, Acc ++ [{K, V}]) end; undefined -> undefined end. %% header_take(Key, Headers) -> lists:dropwhile(fun({K, _}) -> not string:equal(Key, string:casefold(K)) end, Headers). header_member(_, []) -> false; header_member(Key, [{K, _} | T]) -> string:equal(Key, string:casefold(K)) orelse header_member(Key, T).