Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.84% covered (warning)
86.84%
33 / 38
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SteamProxyHelper
86.84% covered (warning)
86.84%
33 / 38
40.00% covered (danger)
40.00%
2 / 5
17.66
0.00% covered (danger)
0.00%
0 / 1
 jenkinsHash
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 challengeNew
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
4.13
 challengeGet
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 challengeValidate
85.71% covered (warning)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
7.14
1<?php declare(strict_types=1);
2
3/**
4 * Clansuite Server Query
5 *
6 * SPDX-FileCopyrightText: 2003-2025 Jens A. Koch
7 * SPDX-License-Identifier: MIT
8 *
9 * For the full copyright and license information, please view
10 * the LICENSE file that was distributed with this source code.
11 */
12
13namespace Clansuite\ServerQuery\Util;
14
15use const PHP_INT_MAX;
16use function random_int;
17
18/**
19 * Helper utilities for implementing a lightweight Steam Query proxy in PHP.
20 */
21final class SteamProxyHelper
22{
23    /**
24     * @var array<int>
25     */
26    private array $challenges = [];
27    private int $index        = 0;
28    private readonly int $size;
29
30    public static function jenkinsHash(int $value): int
31    {
32        // emulate the 32-bit Jenkins mix used in the C implementation
33        $value = ($value + 0x7ED55D16) + (($value << 12) & 0xFFFFFFFF);
34        $value = ($value ^ 0xC761C23C) ^ (($value >> 19) & 0xFFFFFFFF);
35        $value = ($value + 0x165667B1) + (($value << 5) & 0xFFFFFFFF);
36        $value = ($value + 0xD3A2646C) ^ (($value << 9) & 0xFFFFFFFF);
37        $value = ($value + 0xFD7046C5) + (($value << 3) & 0xFFFFFFFF);
38        $value = ($value ^ 0xB55A4F09) ^ (($value >> 16) & 0xFFFFFFFF);
39
40        return $value & 0xFFFFFFFF;
41    }
42
43    /**
44     * Constructor.
45     */
46    public function __construct(int $size = 6)
47    {
48        $this->size = $size > 0 ? $size : 6;
49
50        // seed challenges
51        for ($i = 0; $i < $this->size; $i++) {
52            $this->challengeNew();
53        }
54    }
55
56    /**
57     * challengeNew method.
58     */
59    public function challengeNew(): void
60    {
61        for ($i = 0; $i < 100; $i++) {
62            $next = ($this->index + $i) % $this->size;
63            $new  = self::jenkinsHash(random_int(1, PHP_INT_MAX));
64
65            if ($new === 0) {
66                continue;
67            }
68
69            if ($new === 0xFFFFFFFF) {
70                continue;
71            }
72            $this->challenges[$next] = $new & 0xFFFFFFFF;
73            $this->index             = $next;
74
75            return;
76        }
77    }
78
79    /**
80     * challengeGet method.
81     */
82    public function challengeGet(int $mutate): int
83    {
84        if (!isset($this->challenges[$this->index])) {
85            // If no challenge is set at the current index, generate a new one and use it
86            $this->challengeNew();
87        }
88
89        $challenge = $this->challenges[$this->index] ?? 0;
90
91        return ($challenge + self::jenkinsHash($mutate)) & 0xFFFFFFFF;
92    }
93
94    /**
95     * challengeValidate method.
96     */
97    public function challengeValidate(int $challenge, int $mutate): bool
98    {
99        if ($challenge === 0 || $challenge === 0xFFFFFFFF) {
100            return false;
101        }
102
103        $idx = $this->index;
104
105        for ($i = 0; $i < $this->size; $i++) {
106            if (!isset($this->challenges[$idx])) {
107                return false;
108            }
109            $chVal = $this->challenges[$idx];
110            $check = ($chVal + self::jenkinsHash($mutate)) & 0xFFFFFFFF;
111
112            if ($check === ($challenge & 0xFFFFFFFF)) {
113                return true;
114            }
115            $idx++;
116
117            if ($idx === $this->size) {
118                $idx = 0;
119            }
120        }
121
122        return false;
123    }
124}