Passed
Branch master (e090ea)
by kacper
04:22
created

BinLogSocketConnect::auth()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 8
nc 1
nop 0
1
<?php
2
3
namespace MySQLReplication\BinLog;
4
5
use MySQLReplication\BinaryDataReader\BinaryDataReader;
6
use MySQLReplication\Config\Config;
7
use MySQLReplication\Definitions\ConstCapabilityFlags;
8
use MySQLReplication\Definitions\ConstCommand;
9
use MySQLReplication\Gtid\GtidException;
10
use MySQLReplication\Gtid\GtidFactory;
11
use MySQLReplication\Repository\RepositoryInterface;
12
use MySQLReplication\Socket\SocketInterface;
13
14
/**
15
 * Class BinLogSocketConnect
16
 * @package MySQLReplication\BinLog
17
 */
18
class BinLogSocketConnect
19
{
20
    /**
21
     * @var bool
22
     */
23
    private $checkSum = false;
24
    /**
25
     * @var RepositoryInterface
26
     */
27
    private $repository;
28
    /**
29
     * http://dev.mysql.com/doc/internals/en/auth-phase-fast-path.html 00 FE
30
     * @var array
31
     */
32
    private $packageOkHeader = [0, 254];
33
    /**
34
     * @var SocketInterface
35
     */
36
    private $socket;
37
    /**
38
     * 2^24 - 1 16m
39
     * @var int
40
     */
41
    private $binaryDataMaxLength = 16777215;
42
43
    /**
44
     * @param RepositoryInterface $repository
45
     * @param SocketInterface $socket
46
     * @throws BinLogException
47
     * @throws \MySQLReplication\Gtid\GtidException
48
     * @throws \MySQLReplication\Socket\SocketException
49
     */
50 54
    public function __construct(
51
        RepositoryInterface $repository,
52
        SocketInterface $socket
53
    ) {
54 54
        $this->repository = $repository;
55 54
        $this->socket = $socket;
56
57 54
        $this->socket->connectToStream(Config::getHost(), Config::getPort());
58 54
        BinLogServerInfo::parsePackage($this->getResponse(false), $this->repository->getVersion());
59 54
        $this->authenticate();
60 54
        $this->getBinlogStream();
61 54
    }
62
63
    /**
64
     * @return bool
65
     */
66 54
    public function getCheckSum()
67
    {
68 54
        return $this->checkSum;
69
    }
70
71
    /**
72
     * @param bool $checkResponse
73
     * @return string
74
     * @throws \MySQLReplication\BinLog\BinLogException
75
     * @throws \MySQLReplication\Socket\SocketException
76
     */
77 54
    public function getResponse($checkResponse = true)
78
    {
79 54
        $header = $this->socket->readFromSocket(4);
80 54
        if ('' === $header) {
81
            return '';
82
        }
83 54
        $dataLength = unpack('L', $header[0] . $header[1] . $header[2] . chr(0))[1];
84
85 54
        $result = $this->socket->readFromSocket($dataLength);
86 54
        if (true === $checkResponse) {
87 54
            $this->isWriteSuccessful($result);
88 54
        }
89
90 54
        return $result;
91
    }
92
93
    /**
94
     * @param string $data
95
     * @throws BinLogException
96
     */
97 54
    private function isWriteSuccessful($data)
98
    {
99 54
        $head = ord($data[0]);
100 54
        if (!in_array($head, $this->packageOkHeader, true)) {
101
            $errorCode = unpack('v', $data[1] . $data[2])[1];
102
            $errorMessage = '';
103
            $packetLength = strlen($data);
104
            for ($i = 9; $i < $packetLength; ++$i) {
105
                $errorMessage .= $data[$i];
106
            }
107
108
            throw new BinLogException($errorMessage, $errorCode);
109
        }
110 54
    }
111
112
    /**
113
     * @throws BinLogException
114
     * @throws \MySQLReplication\Socket\SocketException
115
     * @link http://dev.mysql.com/doc/internals/en/secure-password-authentication.html#packet-Authentication::Native41
116
     */
117 54
    private function authenticate()
118
    {
119 54
        $data = pack('L', ConstCapabilityFlags::getCapabilities());
120 54
        $data .= pack('L', $this->binaryDataMaxLength);
121 54
        $data .= chr(33);
122 54
        for ($i = 0; $i < 23; $i++) {
123 54
            $data .= chr(0);
124 54
        }
125 54
        $result = sha1(Config::getPassword(), true) ^ sha1(
126 54
                BinLogServerInfo::getSalt() . sha1(sha1(Config::getPassword(), true), true), true
127 54
            );
128
129 54
        $data = $data . Config::getUser() . chr(0) . chr(strlen($result)) . $result;
130 54
        $str = pack('L', strlen($data));
131 54
        $s = $str[0] . $str[1] . $str[2];
132 54
        $data = $s . chr(1) . $data;
133
134 54
        $this->socket->writeToSocket($data);
135 54
        $this->getResponse();
136 54
    }
137
138
    /**
139
     * @throws BinLogException
140
     * @throws GtidException
141
     * @throws \MySQLReplication\Socket\SocketException
142
     */
143 54
    private function getBinlogStream()
144
    {
145 54
        $this->checkSum = $this->repository->isCheckSum();
146 54
        if ($this->checkSum) {
147 54
            $this->execute('SET @master_binlog_checksum = @@global.binlog_checksum');
148 54
        }
149
150 54
        if (0 !== Config::getHeartbeatPeriod()) {
151
            // master_heartbeat_period is nanoseconds
152
            $this->execute('SET @master_heartbeat_period = ' . Config::getHeartbeatPeriod() * 1000000000);
153
        }
154
155 54
        $this->registerSlave();
156
157 54
        if ('' !== Config::getMariaDbGtid()) {
158
            $this->execute('SET @mariadb_slave_capability = 4');
159
            $this->execute('SET @slave_connect_state = \'' . Config::getMariaDbGtid() . '\'');
160
            $this->execute('SET @slave_gtid_strict_mode = 0');
161
            $this->execute('SET @slave_gtid_ignore_duplicates = 0');
162 54
        } else if ('' !== Config::getGtid()) {
163
            $this->setBinLogDumpGtid();
164
        } else {
165 54
            $this->setBinLogDump();
166
        }
167 54
    }
168
169
    /**
170
     * @param string $sql
171
     * @throws BinLogException
172
     * @throws \MySQLReplication\Socket\SocketException
173
     */
174 54
    private function execute($sql)
175
    {
176 54
        $this->socket->writeToSocket(pack('LC', strlen($sql) + 1, 0x03) . $sql);
177 54
        $this->getResponse();
178 54
    }
179
180
    /**
181
     * @see https://dev.mysql.com/doc/internals/en/com-register-slave.html
182
     * @throws BinLogException
183
     * @throws \MySQLReplication\Socket\SocketException
184
     */
185 54
    private function registerSlave()
186
    {
187 54
        $host = gethostname();
188 54
        $hostLength = strlen($host);
189 54
        $userLength = strlen(Config::getUser());
190 54
        $passLength = strlen(Config::getPassword());
191
192 54
        $data = pack('l', 18 + $hostLength + $userLength + $passLength);
193 54
        $data .= chr(ConstCommand::COM_REGISTER_SLAVE);
194 54
        $data .= pack('V', Config::getSlaveId());
195 54
        $data .= pack('C', $hostLength);
196 54
        $data .= $host;
197 54
        $data .= pack('C', $userLength);
198 54
        $data .= Config::getUser();
199 54
        $data .= pack('C', $passLength);
200 54
        $data .= Config::getPassword();
201 54
        $data .= pack('v', Config::getPort());
202 54
        $data .= pack('V', 0);
203 54
        $data .= pack('V', 0);
204
205 54
        $this->socket->writeToSocket($data);
206 54
        $this->getResponse();
207 54
    }
208
209
    /**
210
     * @see https://dev.mysql.com/doc/internals/en/com-binlog-dump-gtid.html
211
     * @throws BinLogException
212
     * @throws GtidException
213
     * @throws \MySQLReplication\Socket\SocketException
214
     */
215
    private function setBinLogDumpGtid()
216
    {
217
        $collection = GtidFactory::makeCollectionFromString(Config::getGtid());
218
219
        $data = pack('l', 26 + $collection->getEncodedLength()) . chr(ConstCommand::COM_BINLOG_DUMP_GTID);
220
        $data .= pack('S', 0);
221
        $data .= pack('I', Config::getSlaveId());
222
        $data .= pack('I', 3);
223
        $data .= chr(0);
224
        $data .= chr(0);
225
        $data .= chr(0);
226
        $data .= BinaryDataReader::pack64bit(4);
227
        $data .= pack('I', $collection->getEncodedLength());
228
        $data .= $collection->getEncoded();
229
230
        $this->socket->writeToSocket($data);
231
        $this->getResponse();
232
    }
233
234
    /**
235
     * @see https://dev.mysql.com/doc/internals/en/com-binlog-dump.html
236
     * @throws BinLogException
237
     * @throws \MySQLReplication\Socket\SocketException
238
     */
239 54
    private function setBinLogDump()
240
    {
241 54
        $binFilePos = Config::getBinLogPosition();
242 54
        $binFileName = Config::getBinLogFileName();
243 54
        if (0 === $binFilePos || '' === $binFileName) {
244 54
            $master = $this->repository->getMasterStatus();
245 54
            $binFilePos = $master['Position'];
246 54
            $binFileName = $master['File'];
247 54
        }
248
249 54
        $data = pack('i', strlen($binFileName) + 11) . chr(ConstCommand::COM_BINLOG_DUMP);
250 54
        $data .= pack('I', $binFilePos);
251 54
        $data .= pack('v', 0);
252 54
        $data .= pack('I', Config::getSlaveId());
253 54
        $data .= $binFileName;
254
255 54
        $this->socket->writeToSocket($data);
256 54
        $this->getResponse();
257 54
    }
258
}
259