private static void SetLinks(Span.Types.Links spanLinks, IDictionary <string, string> telemetryProperties)
        {
            if (spanLinks?.Link == null)
            {
                return;
            }

            // for now, we just put links to telemetry properties
            // link0_spanId = ...
            // link0_traceId = ...
            // link0_type = child | parent | other
            // link0_<attributeKey> = <attributeValue>
            // this is not convenient for querying data
            // We'll consider adding Links to operation telemetry schema

            int num = 0;

            foreach (var link in spanLinks.Link)
            {
                string prefix = $"{LinkPropertyName}{num++}_";
                telemetryProperties[prefix + LinkSpanIdPropertyName]  = BytesStringToHexString(link.SpanId);
                telemetryProperties[prefix + LinkTraceIdPropertyName] = BytesStringToHexString(link.TraceId);
                telemetryProperties[prefix + LinkTypePropertyName]    = link.Type.ToString();
                if (link.Attributes?.AttributeMap != null)
                {
                    foreach (var attribute in link.Attributes.AttributeMap)
                    {
                        SetCustomProperty(telemetryProperties, attribute, prefix);
                    }
                }
            }
        }
        internal static Span ToProtoSpan(this SpanData spanData)
        {
            try
            {
                // protobuf doesn't understand Span<T> yet: https://github.com/protocolbuffers/protobuf/issues/3431
                Span <byte> traceIdBytes = stackalloc byte[16];
                Span <byte> spanIdBytes  = stackalloc byte[8];

                spanData.Context.TraceId.CopyTo(traceIdBytes);
                spanData.Context.SpanId.CopyTo(spanIdBytes);

                var parentSpanIdString = ByteString.Empty;
                if (spanData.ParentSpanId != default)
                {
                    Span <byte> parentSpanIdBytes = stackalloc byte[8];
                    spanData.ParentSpanId.CopyTo(parentSpanIdBytes);
                    parentSpanIdString = ByteString.CopyFrom(parentSpanIdBytes.ToArray());
                }

                return(new Span
                {
                    Name = new TruncatableString {
                        Value = spanData.Name
                    },

                    // TODO: Utilize new Span.Types.SpanKind below when updated protos are incorporated, also confirm default for SpanKind.Internal
                    Kind = spanData.Kind == SpanKind.Client || spanData.Kind == SpanKind.Producer ? Span.Types.SpanKind.Client : Span.Types.SpanKind.Server,

                    TraceId = ByteString.CopyFrom(traceIdBytes.ToArray()),
                    SpanId = ByteString.CopyFrom(spanIdBytes.ToArray()),
                    ParentSpanId = parentSpanIdString,

                    StartTime = spanData.StartTimestamp.ToTimestamp(),
                    EndTime = spanData.EndTimestamp.ToTimestamp(),
                    Status = spanData.Status == null
                        ? null
                        : new OpenTelemetry.Proto.Trace.V1.Status
                    {
                        Code = (int)spanData.Status.CanonicalCode,
                        Message = spanData.Status.Description ?? string.Empty,
                    },
                    SameProcessAsParentSpan = spanData.ParentSpanId != default,
                    ChildSpanCount = spanData.ChildSpanCount.HasValue ? (uint)spanData.ChildSpanCount.Value : 0,
                    Attributes = FromAttributes(spanData.Attributes),
                    TimeEvents = FromITimeEvents(spanData.Events),
                    Links = new Span.Types.Links
                    {
                        DroppedLinksCount = spanData.Links.DroppedLinksCount,
                        Link = { spanData.Links.Links.Select(FromILink), },
                    },
                });
            }