internal/tracectx/tracectx.go
Rama main · réplica de lectura del repositorio original.
// Package tracectx parses the W3C trace context a create may carry, so every
// surface reads the same value the same way.
//
// A create's `traceparent` is the only place Cairn learns which run produced an
// artifact. It is optional and it is never trusted for anything but provenance:
// a missing or malformed value is ignored rather than rejected, because failing
// an upload over an observability header would turn a telemetry bug into a
// data-loss bug.
//
// Parsing is strict, per SPEC-0024 REQ "Input Handling": exactly 55 bytes in
// the form 00-<32 hex>-<16 hex>-<2 hex>, lowercase hex only, version 00, and
// neither identifier all zeros (an all-zero id is invalid on the wire, and a
// zero trace would group every sloppy client's spans into one fake run).
// `tracestate` is ignored: nothing in Cairn consumes it.
//
// Governing: ADR-0030 (Artifact Relations and Trace Context),
// SPEC-0024 REQ "Trace Context on Create"
package tracectx
const (
// Length is the exact wire length of a version-00 traceparent:
// "00-" + 32 + "-" + 16 + "-" + 2.
Length = 55
// TraceLen and SpanLen are the raw identifier widths stored on the artifact.
TraceLen = 16
SpanLen = 8
)
// Context is a parsed trace context. TraceID is the 16-byte trace and SpanID
// the 8-byte parent span — the span of the caller that made this request, which
// is what lets an artifact hang off the span that produced it.
type Context struct {
TraceID [TraceLen]byte
SpanID [SpanLen]byte
}
// Parse reads a traceparent. ok is false for anything that is not a valid
// version-00 value, and the caller then stores nothing: an absent header and a
// malformed one mean the same thing.
func Parse(s string) (ctx Context, ok bool) {
if len(s) != Length || s[2] != '-' || s[35] != '-' || s[52] != '-' {
return ctx, false
}
if s[0] != '0' || s[1] != '0' {
return ctx, false // only version 00 is defined here
}
// flags (s[53:55]) are validated as hex and otherwise ignored: the sampled
// bit changes nothing about what Cairn stores.
trace, ok := decodeHex(s[3:35])
if !ok {
return ctx, false
}
span, ok := decodeHex(s[36:52])
if !ok {
return ctx, false
}
if _, ok := decodeHex(s[53:55]); !ok {
return ctx, false
}
if isZero(trace) || isZero(span) {
return ctx, false
}
copy(ctx.TraceID[:], trace)
copy(ctx.SpanID[:], span)
return ctx, true
}
// TraceHex is the trace id as 32 lowercase hex characters, the form every read
// surface and artifact.created put on the wire (SPEC-0024 REQ "Trace Id on Read
// and in Events").
func (c Context) TraceHex() string { return encodeHex(c.TraceID[:]) }
// From rebuilds a Context from the stored bytes, so a read returns the trace the
// create stored rather than one re-parsed from hex. ok is false unless both
// identifiers have their exact width — a short read is a bug elsewhere, and
// reporting it is better than padding a trace that never existed.
func From(traceID, parentSpanID []byte) (ctx Context, ok bool) {
if len(traceID) != TraceLen || len(parentSpanID) != SpanLen {
return ctx, false
}
copy(ctx.TraceID[:], traceID)
copy(ctx.SpanID[:], parentSpanID)
return ctx, true
}
// SpanHex is the parent span id as 16 lowercase hex characters. It is stored
// for a later join and is not put on the wire in this slice.
func (c Context) SpanHex() string { return encodeHex(c.SpanID[:]) }
const hexDigits = "0123456789abcdef"
// decodeHex decodes a run of lowercase hex. Uppercase is rejected rather than
// folded, so the stored bytes are byte-for-byte what the client sent.
func decodeHex(s string) ([]byte, bool) {
if len(s)%2 != 0 {
return nil, false
}
out := make([]byte, len(s)/2)
for i := 0; i < len(out); i++ {
hi, ok := hexValue(s[2*i])
if !ok {
return nil, false
}
lo, ok := hexValue(s[2*i+1])
if !ok {
return nil, false
}
out[i] = hi<<4 | lo
}
return out, true
}
func hexValue(c byte) (byte, bool) {
switch {
case c >= '0' && c <= '9':
return c - '0', true
case c >= 'a' && c <= 'f':
return c - 'a' + 10, true
default:
return 0, false
}
}
func encodeHex(b []byte) string {
out := make([]byte, len(b)*2)
for i, c := range b {
out[2*i] = hexDigits[c>>4]
out[2*i+1] = hexDigits[c&0x0f]
}
return string(out)
}
func isZero(b []byte) bool {
for _, c := range b {
if c != 0 {
return false
}
}
return true
}