Passed
Push — master ( bf0b22...b8091d )
by Brendan
02:30 queued 42s
created

TelnetClient::setReadTimeout()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 9.9
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
/**
4
 * This file is part of graze/telnet-client.
5
 *
6
 * Copyright (c) 2016 Nature Delivered Ltd. <https://www.graze.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * @license https://github.com/graze/telnet-client/blob/master/LICENSE
12
 * @link https://github.com/graze/telnet-client
13
 */
14
15
namespace Graze\TelnetClient;
16
17
use Exception;
18
use Graze\TelnetClient\Exception\TelnetException;
19
use Graze\TelnetClient\Exception\TelnetExceptionInterface;
20
use Socket\Raw\Factory as SocketFactory;
21
use Socket\Raw\Socket;
22
23
class TelnetClient implements TelnetClientInterface
24
{
25
    /**
26
     * @var SocketFactory
27
     */
28
    protected $socketFactory;
29
30
    /**
31
     * @var PromptMatcherInterface
32
     */
33
    protected $promptMatcher;
34
35
    /**
36
     * @var InterpretAsCommand
37
     */
38
    protected $interpretAsCommand;
39
40
    /**
41
     * @var string
42
     */
43
    protected $prompt = '\$';
44
45
    /**
46
     * @var string
47
     */
48
    protected $promptError = 'ERROR';
49
50
    /**
51
     * @var string
52
     */
53
    protected $lineEnding = "\n";
54
55
    /**
56
     * @var Socket
57
     */
58
    protected $socket;
59
60
    /**
61
     * @var string
62
     */
63
    protected $buffer;
64
65
    /**
66
     * @var string
67
     */
68
    protected $NULL;
69
70
    /**
71
     * @var string
72
     */
73
    protected $DC1;
74
75
    /**
76
     * @var string
77
     */
78
    protected $IAC;
79
80
    /**
81
     * @param SocketFactory $socketFactory
82
     * @param PromptMatcherInterface $promptMatcher
83
     * @param InterpretAsCommand $interpretAsCommand
84
     */
85 18
    public function __construct(
86
        SocketFactory $socketFactory,
87
        PromptMatcherInterface $promptMatcher,
88
        InterpretAsCommand $interpretAsCommand
89
    ) {
90 18
        $this->socketFactory = $socketFactory;
91 18
        $this->promptMatcher = $promptMatcher;
92 18
        $this->interpretAsCommand = $interpretAsCommand;
93
94 18
        $this->NULL = chr(0);
95 18
        $this->DC1 = chr(17);
96 18
    }
97
98
    /**
99
     * @param string $dsn
100
     * @param string $prompt
101
     * @param string $promptError
102
     * @param string $lineEnding
103
     * @param float|null $timeout
104
     *
105
     * @throws TelnetExceptionInterface
106
     */
107 17
    public function connect($dsn, $prompt = null, $promptError = null, $lineEnding = null, $timeout = null)
108
    {
109 17
        if ($prompt !== null) {
110 4
            $this->setPrompt($prompt);
111 4
        }
112
113 17
        if ($promptError !== null) {
114 3
            $this->setPromptError($promptError);
115 3
        }
116
117 17
        if ($lineEnding !== null) {
118 16
            $this->setLineEnding($lineEnding);
119 16
        }
120
121
        try {
122 17
            $socket = $this->socketFactory->createClient($dsn, $timeout);
123 17
        } catch (Exception $e) {
124
            throw new TelnetException(sprintf('unable to create socket connection to [%s]', $dsn), 0, $e);
125
        }
126
127 17
        $this->setSocket($socket);
128 17
    }
129
130
    /**
131
     * @param string $prompt
132
     */
133 2
    public function setPrompt($prompt)
134
    {
135 2
        $this->prompt = $prompt;
136 2
    }
137
138
    /**
139
     * @param string $promptError
140
     */
141 1
    public function setPromptError($promptError)
142
    {
143 1
        $this->promptError = $promptError;
144 1
    }
145
146
    /**
147
     * @param string $lineEnding
148
     */
149 14
    public function setLineEnding($lineEnding)
150
    {
151 14
        $this->lineEnding = $lineEnding;
152 14
    }
153
154
    /**
155
     * @param Socket $socket
156
     */
157 19
    public function setSocket(Socket $socket)
158
    {
159 19
        $this->socket = $socket;
160 19
    }
161
162
    /**
163
     * @return Socket
164
     */
165
    public function getSocket()
166
    {
167
        return $this->socket;
168
    }
169
170
    /**
171
     * @param float $timeout
172
     */
173 4
    public function setReadTimeout($timeout)
174
    {
175 4
        if (!$this->socket) {
176 1
            throw new TelnetException('cannot set read timeout, socket does not exist (call connect() first)');
177
        }
178
179 3
        $sec = floor($timeout);
180 3
        $usec = round(fmod($timeout, 1) * 1000000);
181
182 3
        $this->socket->setOption(SOL_SOCKET, SO_RCVTIMEO, ['sec' => $sec, 'usec' => $usec]);
183 3
    }
184
185
    /**
186
     * @param string $command
187
     * @param string $prompt
188
     * @param string $promptError
189
     *
190
     * @return TelnetResponseInterface
191
     */
192 17
    public function execute($command, $prompt = null, $promptError = null)
193
    {
194 17
        if (!$this->socket) {
195 1
            throw new TelnetException('attempt to execute without a connection - call connect first');
196
        }
197
198 16
        $this->write($command);
199 15
        return $this->getResponse($prompt, $promptError);
200
    }
201
202
    /**
203
     * @param string $command
204
     *
205
     * @return void
206
     * @throws TelnetExceptionInterface
207
     */
208 16
    protected function write($command)
209
    {
210
        try {
211 16
            $this->socket->write($command . $this->lineEnding);
212 16
        } catch (Exception $e) {
213 1
            throw new TelnetException(sprintf('failed writing to socket [%s]', $command), 0, $e);
214
        }
215 15
    }
216
217
    /**
218
     * @param string $prompt
219
     * @param string $promptError
220
     *
221
     * @return TelnetResponseInterface
222
     * @throws TelnetExceptionInterface
223
     */
224 15
    protected function getResponse($prompt = null, $promptError = null)
225
    {
226 15
        $isError = false;
227 15
        $buffer = '';
228
        do {
229
            // process one character at a time
230
            try {
231 15
                $character = $this->socket->read(1);
232 15
            } catch (Exception $e) {
233 1
                throw new TelnetException('failed reading from socket', 0, $e);
234
            }
235
236 14
            if (in_array($character, [$this->NULL, $this->DC1])) {
237
                break;
238
            }
239
240 14
            if ($this->interpretAsCommand->interpret($character, $this->socket)) {
241
                continue;
242
            }
243
244 14
            $buffer .= $character;
245
246
            // check for prompt
247 14
            if ($this->promptMatcher->isMatch($prompt ?: $this->prompt, $buffer, $this->lineEnding)) {
248 8
                break;
249
            }
250
251
            // check for error prompt
252 14
            if ($this->promptMatcher->isMatch($promptError ?: $this->promptError, $buffer, $this->lineEnding)) {
253 6
                $isError = true;
254 6
                break;
255
            }
256 14
        } while (true);
257
258 14
        return new TelnetResponse(
259 14
            $isError,
260 14
            $this->promptMatcher->getResponseText(),
261 14
            $this->promptMatcher->getMatches()
262 14
        );
263
    }
264
265
    /**
266
     * @return TelnetClientInterface
267
     */
268 1
    public static function factory()
269
    {
270 1
        return new static(
271 1
            new SocketFactory(),
272 1
            new PromptMatcher(),
273 1
            new InterpretAsCommand()
274 1
        );
275
    }
276
277 15
    public function __destruct()
278
    {
279 15
        if (!$this->socket) {
280 1
            return;
281
        }
282
283 14
        $this->socket->close();
284 14
    }
285
}
286