SteamQuery   C
last analyzed

Complexity

Total Complexity 56

Size/Duplication

Total Lines 360
Duplicated Lines 6.67 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 56
c 1
b 1
f 0
lcom 1
cbo 6
dl 24
loc 360
rs 6.5957

10 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 0 76 13
B verifyStatus() 0 12 6
C getServerInfos() 0 52 9
B getChallenge() 24 24 4
B getPlayers() 0 30 5
B getRules() 0 32 6
A getLatency() 0 21 4
A isOnline() 0 4 1
A getIsOnline() 0 4 1
C isBanned() 0 32 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SteamQuery often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SteamQuery, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of Dedipanel project
5
 *
6
 * (c) 2010-2015 Dedipanel <http://www.dedicated-panel.net>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace DP\GameServer\SteamServerBundle\SteamQuery;
13
14
use DP\GameServer\GameServerBundle\Socket\Socket;
15
use DP\GameServer\GameServerBundle\Socket\Packet;
16
use DP\GameServer\GameServerBundle\Socket\PacketCollection;
17
use DP\GameServer\GameServerBundle\Query\QueryInterface;
18
use DP\GameServer\GameServerBundle\Socket\Exception\ConnectionFailedException;
19
use DP\GameServer\GameServerBundle\Socket\Exception\NotConnectedException;
20
use DP\GameServer\GameServerBundle\Socket\Exception\RecvTimeoutException;
21
use DP\GameServer\SteamServerBundle\SteamQuery\Exception\ServerTimeoutException;
22
use DP\Core\CoreBundle\Exception\IPBannedException;
23
use DP\GameServer\SteamServerBundle\SteamQuery\Exception\UnexpectedServerTypeException;
24
25
/**
26
 * @author Albin Kerouanton 
27
 */
28
class SteamQuery implements QueryInterface
29
{
30
    private $container;
31
    private $socket;
32
    private $packetFactory;
33
34
    protected $challenge;
35
    protected $latency;
36
    protected $serverInfos;
37
    protected $players;
38
    protected $rules;
39
    protected $banned = null;
40
    protected $type;
41
    
42
    const TYPE_GOLDSRC = 1;
43
    const TYPE_SOURCE  = 2;
44
    const TYPE_HLTV    = 3;
45
    
46
    /**
47
     * Constructor
48
     * 
49
     * @param Service Container $container
50
     * @param string $host
51
     * @param int $port
52
     */
53
    public function __construct($container, $host, $port, $type)
54
    {
55
        $this->type = $type;
56
        
57
        // On ne déclare pas les 2 callbacks simultanément
58
        // Puisque le 2nd fait appel au 1er
59
        $callbacks = array(
60
            Socket::MULTI_DETECTOR => function ($packet) {
61
                if (is_null($packet)) return false;
62
63
                $val = $packet->getLong();
64
                return $val == -2;
65
            }
66
        ); 
67
        $callbacks[Socket::MULTI_RECEIVER] = function(Packet $packet, Socket $socket) use ($callbacks, $type) {
68
            $splittedPackets = new PacketCollection();
69
            $respId = null;
70
71
            do {                
72
                // On récupère l'id de la transmission
73
                // Et on vérifie que les packets récupérés aient le même ID
74
                $id = $packet->getLong();
75
                if (!$respId) {
76
                    $respId = $id;
77
                }
78
                elseif ($respId != $id) {
79
                    $packet = null;
0 ignored issues
show
Unused Code introduced by
$packet is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
80
                    $packet = $socket->recv(false);
81
                    continue;
82
                }
83
84
                if ($type == SteamQuery::TYPE_GOLDSRC || $type == SteamQuery::TYPE_HLTV) {
85
                    $infosPacket = $packet->getByte();
86
                    $nbrePacket = $infosPacket & 0xF;
87
                    $packetId = $infosPacket >> 4;
88
                }
89
                else {
90
                    $nbrePacket = $packet->getByte();
91
                    $packetId = $packet->getByte();
92
                }
93
94
                $splittedPackets[$packetId] = $packet;
95
96
                // On remet à zéro le packet pour ne pas avoir de boucle infinie
97
                $packet = null;
98
                $isMultiResp = false;
99
                if (count($splittedPackets) < $nbrePacket) {
100
                    $packet = $socket->recv(false);
101
                    $isMultiResp = call_user_func($callbacks[Socket::MULTI_DETECTOR], $packet);
102
                }
103
            } while (!empty($packet) && $isMultiResp == true);
0 ignored issues
show
Bug introduced by
The variable $isMultiResp does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
104
105
            // Les réponses multi packet une fois réassemblé
106
            // Comence par l'entier -1
107
            $ret = $splittedPackets->reassemble();
108
            $ret->getLong();
109
            
110
            return $ret;
111
        };
112
        
113
        $this->container = $container;
114
        $this->packetFactory = $container->get('packet.factory.steam.query');
115
        
116
        $this->socket = $container->get('socket')->getUDPSocket($host, $port, $callbacks);
117
        
118
        try {
119
            $this->socket->connect();
120
            $this->getLatency();
121
            $this->isBanned();
122
            $this->getChallenge();
123
        }
124
        catch (ConnectionFailedException $e) {}
125
        catch (IPBannedException $e) {}
126
        catch (ServerTimeoutException $e) {}
127
        catch (NotConnectedException $e) {}
128
    }
129
    
130
    public function verifyStatus()
131
    {
132
        $infos = $this->getServerInfos();
133
        
134
        if (empty($infos) || ($this->type == SteamQuery::TYPE_HLTV && $infos['protocol'] != 0) 
135
        ||  ($this->type != SteamQuery::TYPE_HLTV && $infos['protocol'] == 0)) {
136
            $this->latency = false;
137
            throw new UnexpectedServerTypeException($this->type == SteamQuery::TYPE_HLTV);
138
        }
139
        
140
        return true;
141
    }
142
    
143
    /**
144
     * Get the server info
145
     * 
146
     * @return array|bool
147
     * @throws Exception\ServerTimeoutException 
148
     */
149
    public function getServerInfos()
150
    {
151
        if ($this->banned || $this->latency === false) {
152
            return false;
153
        }
154
        
155
        if (!isset($this->serverInfos)) {
156
            try {
157
                $this->socket->send($this->packetFactory->A2S_INFO());
158
                $resp = $this->socket->recv();
159
                
160
                $infos = $resp->rewind()->extract(array(
161
                    'header' => 'byte',
162
                    'protocol' => 'byte', 
163
                    'serverName' => 'string',
164
                    'map' => 'string',
165
                    'gameDir' => 'string',
166
                    'gameName' => 'string',
167
                    'appId' => 'short',
168
                    'players' => 'byte',
169
                    'maxPlayers' => 'byte',
170
                    'bot' => 'byte',
171
                    'serverType' => 'byte',
172
                    'os' => 'byte',
173
                    'password' => 'byte',
174
                    'vac' => 'byte',
175
                    'gameVer' => 'string',
176
                    'edf' => 'byte'));
177
178
                if ($infos['edf'] & 0x080) {
179
                    $infos['port'] = $resp->getShort();
180
                }
181
                elseif ($infos['edf'] & 0x040) {
182
                    $infos['hltv_port'] = $resp->getShort();
183
                    $infos['hltv_name'] = $resp->getString();
184
                }
185
                elseif ($infos['edf'] & 0x020) {
186
                    $infos['keywords'] = $resp->getString();
187
                }
188
                
189
                $this->serverInfos = $infos;
190
            }
191
            catch (RecvTimeoutException $e) {
192
                throw new Exception\ServerTimeoutException();
193
            }
194
            catch (NotConnectedException $e) {
195
                $this->serverInfos = array();
196
            }
197
        }
198
        
199
        return $this->serverInfos;
200
    }
201
    
202
    /**
203
     * Get server challenge
204
     * 
205
     * @return long
206
     */
207 View Code Duplication
    protected function getChallenge()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
208
    {
209
        if (!isset($this->challenge)) {
210
            try {
211
                $packet = $this->packetFactory->A2S_SERVERQUERY_GETCHALLENGE();
212
                $this->socket->send($packet);
213
                $resp = $this->socket->recv();
214
215
                $data = $resp->extract(array(
216
                    'header' => 'byte', 
217
                    'challenge' => 'long', 
218
                ));
219
220
                if ($data['header'] == 65) {
221
                    $this->challenge = $data['challenge'];
222
                }
223
            }
224
            catch (RecvTimeoutException $e) {
225
                throw new ServerTimeoutException();
226
            }
227
        }
228
        
229
        return $this->challenge;
230
    }
231
    
232
    /**
233
     * Get players list
234
     * 
235
     * @return array
236
     * @throws ServerTimeoutException 
237
     */
238
    public function getPlayers()
239
    {
240
        if (!isset($this->players)) {
241
            try {
242
                $challenge = $this->getChallenge();
243
                
244
                $this->socket->send($this->packetFactory->A2S_PLAYER($challenge));
245
                $resp = $this->socket->recv();
246
247
                $players = array();
248
                $header = $resp->extract(
249
                    array('header' => 'byte', 'nb_players' => 'byte'));
250
251
                for ($i = 0, $max = $header['nb_players']; $i < $max; ++$i) {
252
                    $players[$i] = $resp->extract(array('id' => 'byte', 
253
                        'nom' => 'string', 'score' => 'long', 'timeConnected' => 'float'));
254
                }
255
256
                $this->players = $players;
257
            }
258
            catch (RecvTimeoutException $e) {
259
                throw new ServerTimeoutException();
260
            }
261
            catch (NotConnectedException $e) {
262
                $this->players = array();
263
            }
264
        }
265
        
266
        return $this->players;
267
    }
268
    
269
    /**
270
     * Get rules list
271
     * @return type
272
     * @throws ServerTimeoutException 
273
     */
274
    public function getRules()
275
    {
276
        if (!isset($this->rules)) {
277
            try {
278
                $this->socket->send($this->packetFactory->A2S_RULES($this->getChallenge()));
279
                $resp = $this->socket->recv();
280
                
281
                $rules = array();
282
                $header = $resp->extract(
283
                    array('header' => 'byte', 'nb_rules' => 'short')
284
                );
285
286
                if ($header['header'] == 69) {
287
                    for ($i = 0, $max = $header['nb_rules']; $i < $max; ++$i) {
288
                        $rules[$i] = $resp->extract(
289
                            array('name' => 'string', 'value' => 'string')
290
                        );
291
                    }
292
293
                    $this->rules = $rules;
294
                }
295
            }
296
            catch (RecvTimeoutException $e) {
297
                throw new ServerTimeoutException();
298
            }
299
            catch (NotConnectedException $e) {
300
                $this->rules = array();
301
            }
302
        }
303
        
304
        return $this->rules;
305
    }
306
    
307
    /**
308
     * Get the server latency
309
     * @return float
310
     */
311
    public function getLatency()
312
    {
313
        if (!isset($this->latency)) {
314
            $packet = $this->packetFactory->A2A_PING();
315
            
316
            try {
317
                $ping = microtime(true);
318
                $this->socket->send($packet);
319
                $this->socket->recv();
320
                $this->latency = round((microtime(true) - $ping) * 1000);
321
            }
322
            catch (RecvTimeoutException $e) {
323
                $this->latency = false;
324
            }
325
            catch (NotConnectedException $e) {
326
                $this->latency = false;
327
            }
328
        }
329
        
330
        return $this->latency;
331
    }
332
    
333
    /**
334
     * Check if the server is online
335
     * @return bool 
336
     */
337
    public function isOnline()
338
    {
339
        return ($this->getLatency() != false);
340
    }
341
    
342
    /**
343
     * Alias of isOnline method
344
     */
345
    public function getIsOnline()
346
    {
347
        return $this->isOnline();
348
    }
349
    
350
    /**
351
     * Check if the IP is banned from the server
352
     * This method is executed before the serverInfos
353
     * @return bool
354
     */
355
    public function isBanned($fromTpl = false)
356
    {
357
        if ($this->banned === null && $this->latency === null) {
358
            try {
359
                $this->socket->send($this->packetFactory->A2A_PING());
360
                $resp = $this->socket->recv();
361
                
362
                if (strpos($resp->setPos(5)->getString(false), 
363
                    'Banned by server') !== false) {
364
                    $this->latency = false;
365
                    $this->banned = true;
366
                    
367
                    if ($fromTpl !== true) {
368
                     throw new IPBannedException();   
369
                    }
370
                }
371
                $this->banned = false;
372
            }
373
            catch (RecvTimeoutException $e) {
374
                $this->latency = false;
375
                $this->serverInfos = array();
376
                throw new ServerTimeoutException();
377
            }
378
            catch (NotConnectedException $e) {
379
                $this->latency = false;
380
                $this->serverInfos = array();
381
                throw new ServerTimeoutException();
382
            }
383
        }
384
        
385
        return $this->banned;
386
    }
387
}
388