Completed
Branch 0.4-dev (54e67b)
by Evgenij
02:52
created

AbstractClientIo::read()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

Changes 8
Bugs 1 Features 0
Metric Value
c 8
b 1
f 0
dl 0
loc 25
ccs 17
cts 17
cp 1
rs 8.439
cc 5
eloc 15
nc 5
nop 3
crap 5
1
<?php
2
/**
3
 * Async sockets
4
 *
5
 * @copyright Copyright (c) 2015-2016, Efimov Evgenij <[email protected]>
6
 *
7
 * This source file is subject to the MIT license that is bundled
8
 * with this source code in the file LICENSE.
9
 */
10
namespace AsyncSockets\Socket\Io;
11
12
use AsyncSockets\Exception\ConnectionException;
13
use AsyncSockets\Exception\FrameException;
14
use AsyncSockets\Exception\NetworkSocketException;
15
use AsyncSockets\Exception\UnsupportedOperationException;
16
use AsyncSockets\Frame\FramePickerInterface;
17
use AsyncSockets\Frame\PartialFrame;
18
use AsyncSockets\Socket\SocketInterface;
19
20
/**
21
 * Class AbstractClientIo
22
 */
23
abstract class AbstractClientIo extends AbstractIo
24
{
25
    /**
26
     * Disconnected state
27
     */
28
    const STATE_DISCONNECTED = 0;
29
30
    /**
31
     * Connected state
32
     */
33
    const STATE_CONNECTED = 1;
34
35
    /**
36
     * Socket state
37
     *
38
     * @var int
39
     */
40
    private $state = self::STATE_DISCONNECTED;
41
42
    /**
43
     * Amount of attempts to write data before treat request as failed
44
     *
45
     * @var int
46
     */
47
    private $writeAttempts = self::IO_ATTEMPTS;
48
49
    /**
50
     * Maximum amount of OOB data length (0 - not supported)
51
     *
52
     * @var int
53
     */
54
    private $maxOobPacketLength;
55
56
    /**
57
     * AbstractClientIo constructor.
58
     *
59
     * @param SocketInterface $socket Socket object
60
     * @param int             $maxOobPacketLength Maximum amount of OOB data length (0 - not supported)
61
     */
62 110
    public function __construct(SocketInterface $socket, $maxOobPacketLength)
63
    {
64 110
        parent::__construct($socket);
65 110
        $this->maxOobPacketLength = $maxOobPacketLength;
66 110
    }
67
68
    /**
69
     * Read raw data from network into given picker
70
     *
71
     * @param FramePickerInterface $picker Frame picker
72
     * @param bool                 $isOutOfBand Flag if these are out of band data
73
     *
74
     * @return string Data after end of frame
75
     */
76
    abstract protected function readRawDataIntoPicker(FramePickerInterface $picker, $isOutOfBand);
77
78
    /**
79
     * Write data to socket
80
     *
81
     * @param string $data Data to write
82
     * @param bool $isOutOfBand Flag if data are out of band
83
     *
84
     * @return int Number of written bytes
85
     */
86
    abstract protected function writeRawData($data, $isOutOfBand);
87
88
    /**
89
     * Check whether given socket resource is connected
90
     *
91
     * @return bool
92
     */
93
    abstract protected function isConnected();
94
95
    /**
96
     * Return true if frame can be collected in nearest future, false otherwise
97
     *
98
     * @return bool
99
     */
100
    abstract protected function canReachFrame();
101
102
    /**
103
     * Return remote socket ip address
104
     *
105
     * @return string
106
     */
107
    abstract protected function getRemoteAddress();
108
109
    /** {@inheritdoc} */
110 51
    final public function read(FramePickerInterface $picker, Context $context, $isOutOfBand)
111
    {
112 51
        $this->setConnectedState();
113 41
        $this->verifyOobData($isOutOfBand, null);
114
115 41
        $isEndOfFrameReached = $this->handleUnreadData($picker, $context, $isOutOfBand);
116 41
        if (!$isEndOfFrameReached) {
117 41
            $context->setUnreadData(
118 41
                $this->readRawDataIntoPicker($picker, $isOutOfBand),
119
                $isOutOfBand
120 40
            );
121
122 40
            $isEndOfFrameReached = $picker->isEof();
123 40
            if (!$isEndOfFrameReached && !$this->canReachFrame()) {
124 5
                throw new FrameException($picker, $this->socket, 'Failed to receive desired frame.');
125
            }
126 36
        }
127
128 36
        $frame = $picker->createFrame();
129 36
        if (!$isEndOfFrameReached) {
130 2
            $frame = new PartialFrame($frame);
131 2
        }
132
133 36
        return $frame;
134
    }
135
136
    /** {@inheritdoc} */
137 44
    final public function write($data, Context $context, $isOutOfBand)
138
    {
139 44
        $this->setConnectedState();
140 26
        $this->verifyOobData($isOutOfBand, $data);
141
142 14
        $result              = $this->writeRawData($data, $isOutOfBand);
143 7
        $this->writeAttempts = $result > 0 ? self::IO_ATTEMPTS : $this->writeAttempts - 1;
144
145 7
        $this->throwNetworkSocketExceptionIf(
146 7
            !$this->writeAttempts && $result !== strlen($data),
147
            'Failed to send data.'
148 7
        );
149
150 7
        return $result;
151
    }
152
153
    /**
154
     * Verifies given data according to OOB rules
155
     *
156
     * @param bool   $isOutOfBand Flag if data are out of band
157
     * @param string $data Accepted data or null to skip check
158
     *
159
     * @return void
160
     */
161 67
    private function verifyOobData($isOutOfBand, $data)
162
    {
163 67
        if (!$isOutOfBand) {
164 53
            return;
165
        }
166
167 14
        if ($this->maxOobPacketLength === 0) {
168 8
            throw UnsupportedOperationException::oobDataUnsupported($this->socket);
169
        }
170
171 6
        if ($data !== null && strlen($data) > $this->maxOobPacketLength) {
172 4
            throw UnsupportedOperationException::oobDataPackageSizeExceeded(
173 4
                $this->socket,
174 4
                $this->maxOobPacketLength,
175 4
                strlen($data)
176 4
            );
177
        }
178 2
    }
179
180
    /**
181
     * Verify, that we are in connected state
182
     *
183
     * @return void
184
     * @throws ConnectionException
185
     */
186 95
    private function setConnectedState()
187
    {
188 95
        $resource = $this->socket->getStreamResource();
189 95
        if (!is_resource($resource)) {
190 24
            $message = $this->state === self::STATE_CONNECTED ?
191 24
                'Connection was unexpectedly closed.' :
192 24
                'Can not start io operation on uninitialized socket.';
193 24
            throw new ConnectionException($this->socket, $message);
194
        }
195
196 71
        if ($this->state !== self::STATE_CONNECTED) {
197 71
            if (!$this->isConnected()) {
198 4
                throw new ConnectionException($this->socket, 'Connection refused.');
199
            }
200
201 67
            $this->state = self::STATE_CONNECTED;
202 67
        }
203 67
    }
204
205
    /**
206
     * Checks that we are in connected state
207
     *
208
     * @param string $message Message to pass in exception
209
     *
210
     * @return void
211
     * @throws NetworkSocketException
212
     */
213 2
    final protected function throwExceptionIfNotConnected($message)
214
    {
215 2
        $this->throwNetworkSocketExceptionIf(
216 2
            !$this->isConnected(),
217
            $message
218 2
        );
219 1
    }
220
221
    /**
222
     * Read unhandled data if there is something from the previous operation
223
     *
224
     * @param FramePickerInterface $picker Frame picker to use
225
     * @param Context              $context Socket context
226
     * @param bool                 $isOutOfBand Flag if it is out-of-band data
227
     *
228
     * @return bool Flag whether it is the end of the frame
229
     */
230 41
    private function handleUnreadData(FramePickerInterface $picker, Context $context, $isOutOfBand)
231
    {
232 41
        $unhandledData = $context->getUnreadData($isOutOfBand);
233 41
        if ($unhandledData) {
234 9
            $context->setUnreadData(
235 9
                $picker->pickUpData($unhandledData, $this->getRemoteAddress()),
236
                $isOutOfBand
237 9
            );
238 9
        }
239
240 41
        return $picker->isEof();
241
    }
242
}
243