Connection::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
3
namespace gries\Rcon;
4
5
use gries\Rcon\Exception\AuthenticationFailedException;
6
use Socket\Raw\Socket;
7
8
/**
9
 * Class Connection
10
 *
11
 * The Connection class wraps around a socket and is able to pass RconMessages
12
 * to a server.
13
 * Responses will be passed back as RconResponse.
14
 *
15
 *
16
 * @package gries\Rcon
17
 */
18
class Connection
19
{
20
    /**
21
     * The socket used to send messages to the server.
22
     *
23
     * @var \Socket\Raw\Socket
24
     */
25
    protected $client;
26
27
    /**
28
     * Current message id that is processed.
29
     *
30
     * @var int
31
     */
32
    protected $currentId = 0;
33
34
    /**
35
     * Initialize this connection.
36
     * The socket client is used to send and receive actual data to the rcon server.
37
     *
38
     * @param Socket $client
39
     * @param        $password
40
     */
41
    public function __construct(Socket $client, $password)
42
    {
43
        $this->client = $client;
44
        $this->authenticate($password);
45
    }
46
47
    /**
48
     * Send a RCON message.
49
     *
50
     *
51
     * @param Message $message
52
     *
53
     * @return Message
54
     */
55
    public function sendMessage(Message $message)
56
    {
57
        $this->currentId++;
58
        $messageData = $message->convertToRconData($this->currentId);
59
        $this->client->write($messageData);
60
61
        return $this->getResponseMessage();
62
    }
63
64
    /**
65
     * Get the response value of the server.
66
     *
67
     * @return string
68
     */
69
    protected function getResponseMessage()
70
    {
71
72
        // read the first 4 bytes which include the length of the response
73
        $lengthEncoded = $this->client->read(4);
74
75
        if (strlen($lengthEncoded) < 4) {
76
            return new Message('', Message::TYPE_RESPONSE_VALUE);
77
        }
78
79
        $lengthInBytes = unpack('V1size', $lengthEncoded)['size'];
80
81
        if ($lengthInBytes <= 0) {
82
            return new Message('', Message::TYPE_RESPONSE_VALUE);
83
        }
84
85
        $responseData = $this->client->read($lengthInBytes);
86
87
        // return an empty message if the server did not send any response
88
        if (null === $responseData) {
89
            return new Message('', Message::TYPE_RESPONSE_VALUE);
90
        }
91
92
        $responseMessage = new Message();
93
        $responseMessage->initializeFromRconData($responseData);
94
95
        if ($lengthInBytes >= 4000) {
96
            $this->handleFragmentedResponse($responseMessage);
97
        }
98
99
        return $responseMessage;
100
    }
101
102
    /**
103
     * Authenticate with the server.
104
     *
105
     * @param $password
106
     *
107
     * @throws Exception\AuthenticationFailedException
108
     */
109
    protected function authenticate($password)
110
    {
111
        $message = new Message($password, Message::TYPE_AUTH);
112
        $response = $this->sendMessage($message);
113
114
        if ($response->getType() === Message::TYPE_AUTH_FAILURE) {
115
            throw new AuthenticationFailedException('Could not authenticate to the server.');
116
        }
117
    }
118
119
    /**
120
     * This handles a fragmented response.
121
     * (https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Multiple-packet_Responses)
122
     *
123
     * We basically send a RESPONSE_VALUE Message to the server to force the response of the rest of the package,
124
     * until we receive another package or an empty response.
125
     *
126
     * All the received data is then appended to the current ResponseMessage.
127
     *
128
     * @param $responseMessage
129
     * @throws Exception\InvalidPacketException
130
     */
131
    protected function handleFragmentedResponse(Message $responseMessage)
132
    {
133
        do {
134
            usleep(20000); // some servers stop responding if we send to many packages so we wait 20ms
135
            $this->client->write(Message::TYPE_RESPONSE_VALUE);
136
137
            $responseData = $this->client->read(4096);
138
            if (empty($responseData)) {
139
                break;
140
            }
141
            $fragmentedMessage = new Message();
142
            $fragmentedMessage->initializeFromRconData($responseData, true);
143
144
            $responseMessage->append($fragmentedMessage);
145
146
            if ($fragmentedMessage->getType() !== Message::TYPE_RESPONSE_VALUE) {
147
                break;
148
            }
149
150
        } while (true);
151
    }
152
}
153