Completed
Branch 0.4-dev (d27253)
by Evgenij
03:26
created

AbstractClientIo::readRawDataIntoPicker()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 1
ccs 0
cts 0
cp 0
c 0
b 0
f 0
nc 1
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 121
    public function __construct(SocketInterface $socket, $maxOobPacketLength)
64
    {
65 121
        parent::__construct($socket);
66 121
        $this->maxOobPacketLength = $maxOobPacketLength;
67 121
    }
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
     * Check whether given socket resource is connected
146
     *
147
     * @return bool
148
     */
149
    abstract protected function isConnected();
150
151
    /**
152
     * Verifies given data according to OOB rules
153
     *
154
     * @param bool   $isOutOfBand Flag if data are out of band
155
     * @param string $data Accepted data or null to skip check
156
     *
157
     * @return void
158
     */
159 75
    private function verifyOobData($isOutOfBand, $data)
160
    {
161 75
        if (!$isOutOfBand) {
162 60
            return;
163
        }
164
165 15
        if ($this->maxOobPacketLength === 0) {
166 8
            throw UnsupportedOperationException::oobDataUnsupported($this->socket);
167
        }
168
169 7
        if ($data !== null && strlen($data) > $this->maxOobPacketLength) {
170 4
            throw UnsupportedOperationException::oobDataPackageSizeExceeded(
171 4
                $this->socket,
172 4
                $this->maxOobPacketLength,
173 4
                strlen($data)
174 4
            );
175
        }
176 3
    }
177
178
    /**
179
     * Read unhandled data if there is something from the previous operation
180
     *
181
     * @param FramePickerInterface $picker Frame picker to use
182
     * @param Context              $context Socket context
183
     * @param bool                 $isOutOfBand Flag if it is out-of-band data
184
     *
185
     * @return bool Flag whether it is the end of the frame
186
     */
187 44
    private function handleUnreadData(FramePickerInterface $picker, Context $context, $isOutOfBand)
188
    {
189 44
        $unhandledData = $context->getUnreadData($isOutOfBand);
190 44
        if ($unhandledData) {
191 11
            $context->setUnreadData(
192 11
                $picker->pickUpData($unhandledData, $this->getRemoteAddress()),
193
                $isOutOfBand
194 11
            );
195 11
        }
196
197 44
        return $picker->isEof();
198
    }
199
200
    /**
201
     * Return remote socket ip address
202
     *
203
     * @return string
204
     */
205
    abstract protected function getRemoteAddress();
206
207
    /**
208
     * Read raw data from network into given picker
209
     *
210
     * @param FramePickerInterface $picker Frame picker
211
     * @param bool                 $isOutOfBand Flag if these are out of band data
212
     *
213
     * @return string Data after end of frame
214
     */
215
    abstract protected function readRawDataIntoPicker(FramePickerInterface $picker, $isOutOfBand);
216
217
    /**
218
     * Return true if frame can be collected in nearest future, false otherwise
219
     *
220
     * @return bool
221
     */
222
    abstract protected function canReachFrame();
223
224
    /**
225
     * Write data to socket
226
     *
227
     * @param string $data Data to write
228
     * @param bool $isOutOfBand Flag if data are out of band
229
     *
230
     * @return int Number of written bytes
231
     */
232
    abstract protected function writeRawData($data, $isOutOfBand);
233
}
234