All files / tao-telemetry/src ids.js

100% Statements 38/38
100% Branches 22/22
100% Functions 5/5
100% Lines 37/37

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 1062x 2x 2x 2x 2x                       64x 64x 60x 60x 60x 60x 672x   60x   4x 4x 80x   4x                     24x 25x   24x                     38x 39x   38x                       1x                               15x 1x   14x 14x 4x   10x 10x 2x   8x 2x   6x 2x   4x    
const HEX = '0123456789abcdef';
const ALL_ZEROS = /^0+$/;
const TRACE_ID_RX = /^[0-9a-f]{32}$/;
const SPAN_ID_RX = /^[0-9a-f]{16}$/;
const VERSION_RX = /^[0-9a-f]{2}$/;
 
/**
 * Produce `chars` random lowercase hex characters, cryptographically strong
 * when the platform provides `crypto.getRandomValues` (Math.random fallback
 * for exotic embedders).
 *
 * @param {number} chars - even count of hex characters to produce
 * @returns {string}
 */
function randomHex(chars) {
  // Stryker disable next-line ConditionalExpression,StringLiteral: equivalent - globalThis always exists in every runnable JS environment we can test; the guard is for exotic embedders
  const cryptoObj = typeof globalThis !== 'undefined' && globalThis.crypto;
  if (cryptoObj && typeof cryptoObj.getRandomValues === 'function') {
    const bytes = new Uint8Array(chars / 2);
    cryptoObj.getRandomValues(bytes);
    let out = '';
    for (const byte of bytes) {
      out += HEX[byte >> 4] + HEX[byte & 15];
    }
    return out;
  }
  let out = '';
  for (let i = 0; i < chars; i++) {
    out += HEX[(Math.random() * 16) | 0];
  }
  return out;
}
 
/**
 * Generate a new W3C-compatible trace id (32 lowercase hex chars; the
 * all-zero id is invalid per Trace Context and is never produced).
 *
 * @returns {string}
 */
export function newTraceId() {
  let id;
  do {
    id = randomHex(32);
  } while (ALL_ZEROS.test(id));
  return id;
}
 
/**
 * Generate a new W3C-compatible span/signal id (16 lowercase hex chars; the
 * all-zero id is invalid per Trace Context and is never produced).
 *
 * @returns {string}
 */
export function newSignalId() {
  let id;
  do {
    id = randomHex(16);
  } while (ALL_ZEROS.test(id));
  return id;
}
 
/**
 * Format a signal stamp or record as a W3C `traceparent` header value:
 * `00-<traceId>-<signalId>-01` (version `00`, flags `01` = sampled).
 *
 * @param {{ traceId: string, signalId: string }} stamp - a chain stamp or a
 *        `TraceRecord` (structurally compatible)
 * @returns {string} e.g. `00-<32 hex>-<16 hex>-01`
 */
export function toTraceparent({ traceId, signalId }) {
  return `00-${traceId}-${signalId}-01`;
}
 
/**
 * Parse a W3C `traceparent` header value into trace continuation context.
 *
 * Tolerant per the Trace Context spec: case and surrounding whitespace are
 * normalized, and future versions with extra dash-separated fields are
 * accepted (at least 4 parts; extras ignored). Rejected as malformed: a
 * non-hex or forbidden (`ff`) version, and non-hex, wrong-length, or
 * all-zero trace/parent ids.
 *
 * @param {string} header
 * @returns {{ traceId: string, parentId: string } | null} null when malformed
 */
export function parseTraceparent(header) {
  if (typeof header !== 'string') {
    return null;
  }
  const parts = header.trim().toLowerCase().split('-');
  if (parts.length < 4) {
    return null;
  }
  const [version, traceId, parentId] = parts;
  if (!VERSION_RX.test(version) || version === 'ff') {
    return null;
  }
  if (!TRACE_ID_RX.test(traceId) || ALL_ZEROS.test(traceId)) {
    return null;
  }
  if (!SPAN_ID_RX.test(parentId) || ALL_ZEROS.test(parentId)) {
    return null;
  }
  return { traceId, parentId };
}