1 | <?php |
||
12 | namespace Lisachenko\Protocol\FCGI; |
||
13 | |||
14 | use Lisachenko\Protocol\FCGI; |
||
15 | use ReflectionClass; |
||
16 | |||
17 | /** |
||
18 | * FCGI record. |
||
19 | * |
||
20 | * @author Alexander.Lisachenko |
||
21 | */ |
||
22 | class Record |
||
23 | { |
||
24 | /** |
||
25 | * Identifies the FastCGI protocol version. |
||
26 | */ |
||
27 | protected int $version = FCGI::VERSION_1; |
||
|
|||
28 | |||
29 | /** |
||
30 | * Identifies the FastCGI record type, i.e. the general function that the record performs. |
||
31 | */ |
||
32 | protected int $type = FCGI::UNKNOWN_TYPE; |
||
33 | |||
34 | /** |
||
35 | * Identifies the FastCGI request to which the record belongs. |
||
36 | */ |
||
37 | protected int $requestId = FCGI::NULL_REQUEST_ID; |
||
38 | |||
39 | /** |
||
40 | * Reserved byte for future proposes |
||
41 | */ |
||
42 | protected int $reserved = 0; |
||
43 | |||
44 | /** |
||
45 | * The number of bytes in the contentData component of the record. |
||
46 | */ |
||
47 | private int $contentLength = 0; |
||
48 | |||
49 | /** |
||
50 | * The number of bytes in the paddingData component of the record. |
||
51 | */ |
||
52 | private int $paddingLength = 0; |
||
53 | |||
54 | /** |
||
55 | * Binary data, between 0 and 65535 bytes of data, interpreted according to the record type. |
||
56 | */ |
||
57 | private string $contentData = ''; |
||
58 | |||
59 | /** |
||
60 | * Padding data, between 0 and 255 bytes of data, which are ignored. |
||
61 | */ |
||
62 | private string $paddingData = ''; |
||
63 | |||
64 | /** |
||
65 | * Unpacks the message from the binary data buffer |
||
66 | * |
||
67 | * @return static |
||
68 | */ |
||
69 | final public static function unpack(string $binaryData): self |
||
70 | { |
||
71 | /** @var static $self */ |
||
72 | $self = (new ReflectionClass(static::class))->newInstanceWithoutConstructor(); |
||
73 | |||
74 | /** @phpstan-var false|array{version: int, type: int, requestId: int, contentLength: int, paddingLength: int} */ |
||
75 | $packet = unpack(FCGI::HEADER_FORMAT, $binaryData); |
||
76 | if ($packet === false) { |
||
77 | throw new \RuntimeException('Can not unpack data from the binary buffer'); |
||
78 | } |
||
79 | 14 | [ |
|
80 | $self->version, |
||
81 | 14 | $self->type, |
|
82 | $self->requestId, |
||
83 | 14 | $self->contentLength, |
|
84 | 14 | $self->paddingLength, |
|
85 | 14 | $self->reserved |
|
86 | 14 | ] = array_values($packet); |
|
87 | 14 | ||
88 | 14 | $payload = substr($binaryData, FCGI::HEADER_LEN); |
|
89 | 14 | self::unpackPayload($self, $payload); |
|
90 | if (static::class !== self::class && $self->contentLength > 0) { |
||
91 | 14 | static::unpackPayload($self, $payload); |
|
92 | 14 | } |
|
93 | 14 | ||
94 | 11 | return $self; |
|
95 | 11 | } |
|
96 | |||
97 | 14 | /** |
|
98 | * Returns the binary message representation of record |
||
99 | */ |
||
100 | final public function __toString(): string |
||
101 | { |
||
102 | $headerPacket = pack( |
||
103 | "CCnnCC", |
||
104 | $this->version, |
||
105 | 13 | $this->type, |
|
106 | $this->requestId, |
||
107 | 13 | $this->contentLength, |
|
108 | 13 | $this->paddingLength, |
|
109 | 13 | $this->reserved |
|
110 | 13 | ); |
|
111 | 13 | ||
112 | 13 | $payloadPacket = $this->packPayload(); |
|
113 | 13 | $paddingPacket = pack("a{$this->paddingLength}", $this->paddingData); |
|
114 | 13 | ||
115 | 13 | return $headerPacket . $payloadPacket . $paddingPacket; |
|
116 | } |
||
117 | 13 | ||
118 | 13 | /** |
|
119 | * Sets the content data and adjusts the length fields |
||
120 | 13 | */ |
|
121 | public function setContentData(string $data): self |
||
122 | { |
||
123 | $this->contentData = $data; |
||
124 | $this->contentLength = strlen($this->contentData); |
||
125 | $extraLength = $this->contentLength % 8; |
||
126 | $this->paddingLength = $extraLength ? (8 - $extraLength) : 0; |
||
127 | |||
128 | 23 | return $this; |
|
129 | } |
||
130 | 23 | ||
131 | 23 | /** |
|
132 | 23 | * Returns the context data from the record |
|
133 | 23 | */ |
|
134 | 23 | public function getContentData(): string |
|
135 | { |
||
136 | return $this->contentData; |
||
137 | } |
||
138 | |||
139 | /** |
||
140 | * Returns the version of record |
||
141 | 10 | */ |
|
142 | public function getVersion(): int |
||
143 | 10 | { |
|
144 | return $this->version; |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * Returns record type |
||
149 | */ |
||
150 | public function getType(): int |
||
151 | 1 | { |
|
152 | return $this->type; |
||
153 | 1 | } |
|
154 | |||
155 | /** |
||
156 | * Returns request ID |
||
157 | */ |
||
158 | public function getRequestId(): int |
||
159 | 24 | { |
|
160 | return $this->requestId; |
||
161 | 24 | } |
|
162 | |||
163 | /** |
||
164 | * Sets request ID |
||
165 | * |
||
166 | * There should be only one unique ID for all active requests, |
||
167 | 4 | * use random number or preferably resetting auto-increment. |
|
168 | */ |
||
169 | 4 | public function setRequestId(int $requestId): self |
|
170 | { |
||
171 | $this->requestId = $requestId; |
||
172 | |||
173 | return $this; |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | 3 | * Returns the size of content length |
|
178 | */ |
||
179 | 3 | final public function getContentLength(): int |
|
180 | { |
||
181 | 3 | return $this->contentLength; |
|
182 | } |
||
183 | |||
184 | /** |
||
185 | * Returns the size of padding length |
||
186 | */ |
||
187 | final public function getPaddingLength(): int |
||
188 | { |
||
189 | 5 | return $this->paddingLength; |
|
190 | } |
||
191 | 5 | ||
192 | /** |
||
193 | * Method to unpack the payload for the record. |
||
194 | * |
||
195 | * NB: Default implementation will be always called |
||
196 | * @param static $self |
||
197 | */ |
||
198 | protected static function unpackPayload(Record $self, string $binaryData): void |
||
199 | 3 | { |
|
200 | /** @phpstan-var false|array{contentData: string, paddingData: string} */ |
||
201 | 3 | $payload = unpack("a{$self->contentLength}contentData/a{$self->paddingLength}paddingData", $binaryData); |
|
202 | if ($payload === false) { |
||
203 | throw new \RuntimeException('Can not unpack data from the binary buffer'); |
||
204 | } |
||
205 | [ |
||
206 | $self->contentData, |
||
207 | $self->paddingData |
||
208 | ] = array_values($payload); |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | 14 | * Implementation of packing the payload |
|
213 | */ |
||
214 | protected function packPayload(): string |
||
215 | 14 | { |
|
216 | 14 | return pack("a{$this->contentLength}", $this->contentData); |
|
217 | 14 | } |
|
218 | } |
||
219 |