*/ public static function everyOverlapState(): array { return array(self::OVERLAP_COMPUTED, self::OVERLAP_UNAVAILABLE, self::OVERLAP_PENDING); } /** * File the receipt beside that control's own server trace while retaining * which later request delivered it. * * The browser session is stamped on the record for the same reason the * canary step receipts carry it (see * Ajax_CanaryLadder::journalPriorStepReceipts): the reconstruction that * consumes this record must not have to resolve the session through a * second, separately-armed journal that can be empty. Hashed into the * `session_key` this journal already uses for `detach_ab_mode`, because * equality is the whole requirement. * * @param array{carrierRequestId: string, sessionId: string, report: array} $receipt * One keyed bag rather than two adjacent strings. Both are opaque * identifiers of the same type, and swapping them type-checks: the record * would then be filed under the session, `carried_by` would name the * session, and `session_key` would be a perfectly valid hash of a request * ID. Every join in the record points somewhere plausible and wrong, and * because the joins are what the reconstruction reads, nothing downstream * can tell. `sessionId` is already bounded by * AjaxRequestLedger::readFields(). */ public static function journal(array $receipt): void { $carrierRequestId = $receipt['carrierRequestId']; $report = $receipt['report']; $controlRequestId = self::ledgerIdOrEmpty($report['controlRequestId'] ?? ''); $controlForRequestId = self::ledgerIdOrEmpty($report['controlForRequestId'] ?? ''); ABJ_404_Solution_AjaxCheckpointLogger::record( $controlRequestId !== '' ? $controlRequestId : $carrierRequestId, self::JOURNAL_EVENT, array( 'carried_by' => $carrierRequestId, 'control_for_request_id' => $controlForRequestId, 'control_request_id' => $controlRequestId, 'session_key' => ABJ_404_Solution_DetachAbExperiment::sessionKey($receipt['sessionId']), 'report' => $report, ) ); } /** * Is this raw browser report a concurrent-control receipt at all? * * @param array $report */ public static function isBrowserReceipt(array $report): bool { return ($report['kind'] ?? '') === self::KIND; } /** * Whether a journal record is complete enough to serve as the required * concurrent-control evidence in a support payload. * * Both halves must hold: the JOURNAL joins (this record can be tied to the * control request, its carrier, and the request it was a control FOR) and * the BROWSER evidence (the browser actually observed an outcome and an * overlap). A record with joins and no observation describes a probe that * ran and told us nothing, and counting it as evidence would let the * support payload claim a control it does not have. * * @param array $record */ public static function isCompleteJournalRecord(array $record): bool { $report = is_array($record['report'] ?? null) ? $record['report'] : array(); return self::hasJournalJoins($record) && self::hasBrowserEvidence($report); } /** @param array $record */ private static function hasJournalJoins(array $record): bool { return ($record['envelope'] ?? '') === ABJ_404_Solution_CheckpointRecordFactory::ENVELOPE_FULL && ($record['event'] ?? '') === self::JOURNAL_EVENT && is_string($record['carried_by'] ?? null) && $record['carried_by'] !== '' && is_string($record['control_for_request_id'] ?? null) && $record['control_for_request_id'] !== '' && is_string($record['control_request_id'] ?? null) && $record['control_request_id'] !== '' && ($record['request_id'] ?? '') === $record['control_request_id']; } /** @param array $report */ private static function hasBrowserEvidence(array $report): bool { $receipt = is_array($report['receipt'] ?? null) ? $report['receipt'] : array(); $overlap = is_array($report['overlap'] ?? null) ? $report['overlap'] : array(); $overlapState = $overlap['state'] ?? ''; $validOverlap = $overlapState === self::OVERLAP_UNAVAILABLE || ($overlapState === self::OVERLAP_COMPUTED && is_numeric($overlap['durationMs'] ?? null) && (int)$overlap['durationMs'] >= 0); return self::isBrowserReceipt($report) && $validOverlap && is_string($receipt['resourceTimingState'] ?? null) && $receipt['resourceTimingState'] !== ''; } /** * A client-supplied request id, or '' when it is not a ledger id. The * journal is joined on these, so a value that cannot be one is dropped * rather than reflected into a record other readers will try to match. * * @param mixed $value */ private static function ledgerIdOrEmpty($value): string { $id = is_scalar($value) ? (string)$value : ''; return preg_match('/^[a-zA-Z0-9]{8,64}$/', $id) === 1 ? $id : ''; } }