Passed
Push — master ( 47029f...4c6b4e )
by Brendan
43s
created

TelnetClient::connect()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5.0073

Importance

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