private function parseTraceParentHeader()

in agent/php/ElasticApm/Impl/HttpDistributedTracing.php [176:295]


    private function parseTraceParentHeader(string $headerRawValue): ?DistributedTracingDataInternal
    {
        // 00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01
        // ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^
        // || |||||||||||||||||||||||||||||||| |||||||||||||||| -- - flagsAsString
        // || |||||||||||||||||||||||||||||||| ---------------- - parentId
        // || -------------------------------- - trace-id
        // -- - version

        $headerValue = trim($headerRawValue);

        $parentFunc = __FUNCTION__;
        $implicitContextToLog = ['headerValue' => $headerValue];
        $logParsingFailedMessage = function (
            string $reason,
            int $srcCodeLineNumber,
            array $context = []
        ) use (
            $parentFunc,
            &$implicitContextToLog
        ): void {
            ($loggerProxy = $this->logger->ifDebugLevelEnabled($srcCodeLineNumber, $parentFunc))
            && $loggerProxy->log(
                'Failed to parse ' . self::TRACE_PARENT_HEADER_NAME . ' HTTP header: '
                . $reason,
                array_merge($context, $implicitContextToLog)
            );
        };

        $result = new DistributedTracingDataInternal();

        $expectedNumberOfParts = 4;
        $implicitContextToLog['expectedNumberOfParts'] = $expectedNumberOfParts;
        /**
         * limit is $expectedNumberOfParts + 1 because according to W3C spec requires parser to allow
         * more than $expectedNumberOfParts in the future versions
         * as long as the new parts are appended at the end after a dash
         *
         * @link https://www.w3.org/TR/trace-context/#versioning-of-traceparent
         */
        $parts = explode(/* separator: */ '-', $headerValue, /* limit: */ $expectedNumberOfParts + 1);
        $implicitContextToLog['parts'] = $parts;
        if (count($parts) < $expectedNumberOfParts) {
            $logParsingFailedMessage("the number of separated parts is less than expected", __LINE__);
            return null;
        }

        $version = $parts[0];
        $implicitContextToLog['version'] = $version;
        /**
         * @link https://www.w3.org/TR/trace-context/#version
         *
         * Version ff is forbidden
         */
        /** @noinspection PhpStrictComparisonWithOperandsOfDifferentTypesInspection */
        if (strcasecmp($version, 'ff') === 0) {
            $logParsingFailedMessage('version ff is forbidden', __LINE__);
            return null;
        }
        if (!IdValidationUtil::isValidHexNumberString($version, /* expectedSizeInBytes - 1 byte = 2 hex chars */ 1)) {
            $logParsingFailedMessage('version is not a valid 2 hex characters string', __LINE__);
            return null;
        }
        if ($version === self::TRACE_PARENT_SUPPORTED_FORMAT_VERSION && count($parts) !== $expectedNumberOfParts) {
            $logParsingFailedMessage(
                'there are more than expected number of separated parts for the current format version',
                __LINE__
            );
            return null;
        }

        $traceId = $parts[1];
        $implicitContextToLog['traceId'] = $traceId;
        if (!IdValidationUtil::isValidHexNumberString($traceId, Constants::TRACE_ID_SIZE_IN_BYTES)) {
            $logParsingFailedMessage(
                'traceId is not a valid ' . Constants::TRACE_ID_SIZE_IN_BYTES . ' bytes hex ID',
                __LINE__
            );
            return null;
        }
        if ($traceId === self::TRACE_PARENT_INVALID_TRACE_ID) {
            $logParsingFailedMessage(
                'traceId that is all bytes as zero (' . self::TRACE_PARENT_INVALID_TRACE_ID . ') is an invalid value',
                __LINE__
            );
            return null;
        }
        $result->traceId = strtolower($traceId);

        $parentId = $parts[2];
        $implicitContextToLog['parentId'] = $parentId;
        if (!IdValidationUtil::isValidHexNumberString($parentId, Constants::EXECUTION_SEGMENT_ID_SIZE_IN_BYTES)) {
            $logParsingFailedMessage(
                'parentId is not a valid ' . Constants::EXECUTION_SEGMENT_ID_SIZE_IN_BYTES . ' bytes hex ID',
                __LINE__
            );
            return null;
        }
        if ($parentId === self::TRACE_PARENT_INVALID_PARENT_ID) {
            $logParsingFailedMessage(
                'parentId that is all bytes as zero ('
                . self::TRACE_PARENT_INVALID_PARENT_ID
                . ') is considered an invalid value',
                __LINE__
            );
            return null;
        }
        $result->parentId = strtolower($parentId);

        $flagsAsString = $parts[3];
        $implicitContextToLog['flagsAsString'] = $flagsAsString;
        if (!IdValidationUtil::isValidHexNumberString($flagsAsString, /* $expectedSizeInBytes */ 1)) {
            $logParsingFailedMessage('flagsAsString is not a valid 2 hex characters string', __LINE__);
            return null;
        }
        $flagsAsInt = hexdec($flagsAsString);
        $result->isSampled = ($flagsAsInt & self::TRACE_PARENT_SAMPLED_FLAG) === 1;

        return $result;
    }