turn === 0) { $seasonId = $gameState->config->seasonId ?? ''; $rulesVersion = $gameState->config->rulesVersion ?? ''; fwrite(STDOUT, "match={$gameState->matchId} season_id={$seasonId} rules_version={$rulesVersion} rows={$gameState->config->rows} cols={$gameState->config->cols}\n"); } // Compute moves $moves = compute_moves($gameState); // Build response $response = ['moves' => array_map(fn($m) => $m->toArray(), $moves)]; $responseBody = json_encode($response); // Sign response $turn = (int)$turnStr; $responseSig = sign_response($secret, $matchId, $turn, $responseBody); $headers = [ 'Content-Type: application/json', "X-ACB-Signature: $responseSig" ]; send_response($conn, 200, 'application/json', $responseBody, $headers); } /** * Verify HMAC signature */ function verify_signature(string $secret, string $matchId, string $turn, string $timestamp, string $body, string $signature): bool { $bodyHash = hash('sha256', $body); $signingString = "$matchId.$turn.$timestamp.$bodyHash"; $expected = hash_hmac('sha256', $signingString, $secret); return hash_equals($expected, $signature); } /** * Sign response body */ function sign_response(string $secret, string $matchId, int $turn, string $body): string { $bodyHash = hash('sha256', $body); $signingString = "$matchId.$turn.$bodyHash"; return hash_hmac('sha256', $signingString, $secret); } /** * Send HTTP response */ function send_response($conn, int $status, string $contentType, string $body, array $extraHeaders = []): void { $statusText = [ 200 => 'OK', 400 => 'Bad Request', 401 => 'Unauthorized', 404 => 'Not Found', ][$status] ?? 'Unknown'; $response = "HTTP/1.1 $status $statusText\r\n"; $response .= "Content-Type: $contentType\r\n"; $response .= "Content-Length: " . strlen($body) . "\r\n"; foreach ($extraHeaders as $header) { $response .= "$header\r\n"; } $response .= "\r\n"; $response .= $body; fwrite($conn, $response); }