AbstractClientIo::read()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 6

Importance

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