Completed
Branch 0.4-dev (79cc15)
by Evgenij
03:32
created

AbstractClientIo::canReachFrame()

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 115
    public function __construct(SocketInterface $socket, $maxOobPacketLength)
64
    {
65 115
        parent::__construct($socket);
66 115
        $this->maxOobPacketLength = $maxOobPacketLength;
67 115
    }
68
69
    /** {@inheritdoc} */
70 53
    final public function read(FramePickerInterface $picker, Context $context, $isOutOfBand)
71
    {
72 53
        $this->setConnectedState();
73 43
        $this->verifyOobData($isOutOfBand, null);
74
75 43
        $isEndOfFrameReached = $this->handleUnreadData($picker, $context, $isOutOfBand);
76 43
        if (!$isEndOfFrameReached) {
77 43
            $context->setUnreadData(
78 43
                $this->readRawDataIntoPicker($picker, $isOutOfBand),
79
                $isOutOfBand
80
            );
81
82 42
            $isEndOfFrameReached = $picker->isEof();
83 42
            if (!$isEndOfFrameReached && !$this->canReachFrame()) {
84 5
                if (!$this->isConnected()) {
85
                    throw new DisconnectException($this->socket, 'Remote connection has been lost.');
86
                }
87
88 5
                throw new FrameException($picker, $this->socket, 'Failed to receive desired frame.');
89
            }
90
        }
91
92 38
        $frame = $picker->createFrame();
93 38
        if (!$isEndOfFrameReached) {
94 2
            $frame = new PartialFrame($frame);
95
        }
96
97 38
        return $frame;
98
    }
99
100
    /** {@inheritdoc} */
101 44
    final public function write($data, Context $context, $isOutOfBand)
102
    {
103 44
        $this->setConnectedState();
104 26
        $this->verifyOobData($isOutOfBand, $data);
105
106 14
        $result              = $this->writeRawData($data, $isOutOfBand);
107 7
        $this->writeAttempts = $result > 0 ? self::IO_ATTEMPTS : $this->writeAttempts - 1;
108
109 7
        if (!$this->writeAttempts) {
110 2
            throw new SendDataException($this->socket, 'Failed to send data.');
111
        }
112
113 7
        return $result;
114
    }
115
116
    /**
117
     * Verify, that we are in connected state
118
     *
119
     * @return void
120
     * @throws ConnectionException
121
     */
122 97
    private function setConnectedState()
123
    {
124 97
        $resource = $this->socket->getStreamResource();
125 97
        if (!is_resource($resource)) {
126 24
            $message = $this->state === self::STATE_CONNECTED ?
127
                'Connection was unexpectedly closed.' :
128 24
                'Can not start io operation on uninitialized socket.';
129 24
            throw new ConnectionException($this->socket, $message);
130
        }
131
132 73
        if ($this->state !== self::STATE_CONNECTED) {
133 73
            if (!$this->isConnected()) {
134 4
                throw new ConnectionException($this->socket, 'Connection refused.');
135
            }
136
137 69
            $this->state = self::STATE_CONNECTED;
138
        }
139 69
    }
140
141
    /**
142
     * Check whether given socket resource is connected
143
     *
144
     * @return bool
145
     */
146
    abstract protected function isConnected();
147
148
    /**
149
     * Verifies given data according to OOB rules
150
     *
151
     * @param bool   $isOutOfBand Flag if data are out of band
152
     * @param string $data Accepted data or null to skip check
153
     *
154
     * @return void
155
     */
156 69
    private function verifyOobData($isOutOfBand, $data)
157
    {
158 69
        if (!$isOutOfBand) {
159 55
            return;
160
        }
161
162 14
        if ($this->maxOobPacketLength === 0) {
163 8
            throw UnsupportedOperationException::oobDataUnsupported($this->socket);
164
        }
165
166 6
        if ($data !== null && strlen($data) > $this->maxOobPacketLength) {
167 4
            throw UnsupportedOperationException::oobDataPackageSizeExceeded(
168 4
                $this->socket,
169 4
                $this->maxOobPacketLength,
170
                strlen($data)
171
            );
172
        }
173 2
    }
174
175
    /**
176
     * Read unhandled data if there is something from the previous operation
177
     *
178
     * @param FramePickerInterface $picker Frame picker to use
179
     * @param Context              $context Socket context
180
     * @param bool                 $isOutOfBand Flag if it is out-of-band data
181
     *
182
     * @return bool Flag whether it is the end of the frame
183
     */
184 43
    private function handleUnreadData(FramePickerInterface $picker, Context $context, $isOutOfBand)
185
    {
186 43
        $unhandledData = $context->getUnreadData($isOutOfBand);
187 43
        if ($unhandledData) {
188 11
            $context->setUnreadData(
189 11
                $picker->pickUpData($unhandledData, $this->getRemoteAddress()),
190
                $isOutOfBand
191
            );
192
        }
193
194 43
        return $picker->isEof();
195
    }
196
197
    /**
198
     * Return remote socket ip address
199
     *
200
     * @return string
201
     */
202
    abstract protected function getRemoteAddress();
203
204
    /**
205
     * Read raw data from network into given picker
206
     *
207
     * @param FramePickerInterface $picker Frame picker
208
     * @param bool                 $isOutOfBand Flag if these are out of band data
209
     *
210
     * @return string Data after end of frame
211
     */
212
    abstract protected function readRawDataIntoPicker(FramePickerInterface $picker, $isOutOfBand);
213
214
    /**
215
     * Return true if frame can be collected in nearest future, false otherwise
216
     *
217
     * @return bool
218
     */
219
    abstract protected function canReachFrame();
220
221
    /**
222
     * Write data to socket
223
     *
224
     * @param string $data Data to write
225
     * @param bool $isOutOfBand Flag if data are out of band
226
     *
227
     * @return int Number of written bytes
228
     */
229
    abstract protected function writeRawData($data, $isOutOfBand);
230
}
231