Skip to content

Commit 0c3fd01

Browse files
Added test for message chunking. Optimized Packer for better performance.
1 parent fca269e commit 0c3fd01

File tree

3 files changed

+86
-53
lines changed

3 files changed

+86
-53
lines changed

src/PackStream/v1/Packer.php

Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,13 @@ public function pack($signature, ...$params): Generator
8383
}
8484

8585
//structure buffer
86-
$len = mb_strlen($output, '8bit');
87-
for ($i = 0; $i < $len; $i += 65535) {
88-
$chunk = mb_substr($output, $i, 65535, '8bit');
89-
yield pack('n', mb_strlen($chunk, '8bit')) . $chunk;
86+
$totalLength = mb_strlen($output, '8bit');
87+
$offset = 0;
88+
while ($offset < $totalLength) {
89+
$chunk = mb_strcut($output, $offset, 65535, '8bit');
90+
$chunkLength = mb_strlen($chunk, '8bit');
91+
$offset += $chunkLength;
92+
yield pack('n', $chunkLength) . $chunk;
9093
}
9194

9295
yield chr(0x00) . chr(0x00);
@@ -99,33 +102,33 @@ public function pack($signature, ...$params): Generator
99102
*/
100103
private function p($param): string
101104
{
102-
$output = '';
103-
if (is_int($param)) {
104-
$output .= $this->packInteger($param);
105-
} elseif (is_float($param)) {
106-
$output .= $this->packFloat($param);
107-
} elseif (is_null($param)) {
108-
$output .= chr(0xC0);
109-
} elseif (is_bool($param)) {
110-
$output .= chr($param ? 0xC3 : 0xC2);
111-
} elseif (is_string($param)) {
112-
$output .= $this->packString($param);
113-
} elseif ($param instanceof IStructure) {
114-
$output .= $this->packStructure($param);
115-
} elseif (is_array($param)) {
116-
$keys = array_keys($param);
117-
if (count($param) == 0 || count(array_filter($keys, 'is_int')) == count($keys)) {
118-
$output .= $this->packList($param);
119-
} else {
120-
$output .= $this->packMap($param);
121-
}
122-
} elseif (is_object($param)) {
123-
$output .= $this->packMap((array)$param);
124-
} else {
125-
throw new PackException('Not recognized type of parameter');
105+
switch (gettype($param)) {
106+
case 'integer':
107+
return $this->packInteger($param);
108+
case 'double':
109+
return $this->packFloat($param);
110+
case 'boolean':
111+
return chr($param ? 0xC3 : 0xC2);
112+
case 'NULL':
113+
return chr(0xC0);
114+
case 'string':
115+
return $this->packString($param);
116+
case 'array':
117+
if ($param === array_values($param)) {
118+
return $this->packList($param);
119+
} else {
120+
return $this->packMap($param);
121+
}
122+
case 'object':
123+
if ($param instanceof IStructure) {
124+
return $this->packStructure($param);
125+
} else {
126+
return $this->packMap((array)$param);
127+
}
128+
129+
default:
130+
throw new PackException('Not recognized type of parameter');
126131
}
127-
128-
return $output;
129132
}
130133

131134
/**
@@ -135,22 +138,19 @@ private function p($param): string
135138
*/
136139
private function packString(string $str): string
137140
{
138-
$output = '';
139141
$length = mb_strlen($str, '8bit');
140142

141143
if ($length < self::SMALL) { //TINY_STRING
142-
$output .= pack('C', 0b10000000 | $length) . $str;
144+
return pack('C', 0b10000000 | $length) . $str;
143145
} elseif ($length < self::MEDIUM) { //STRING_8
144-
$output .= chr(0xD0) . pack('C', $length) . $str;
146+
return chr(0xD0) . pack('C', $length) . $str;
145147
} elseif ($length < self::LARGE) { //STRING_16
146-
$output .= chr(0xD1) . pack('n', $length) . $str;
148+
return chr(0xD1) . pack('n', $length) . $str;
147149
} elseif ($length < self::HUGE) { //STRING_32
148-
$output .= chr(0xD2) . pack('N', $length) . $str;
150+
return chr(0xD2) . pack('N', $length) . $str;
149151
} else {
150152
throw new PackException('String too long');
151153
}
152-
153-
return $output;
154154
}
155155

156156
/**
@@ -169,31 +169,27 @@ private function packFloat(float $value): string
169169
*/
170170
private function packInteger(int $value): string
171171
{
172-
$output = '';
173-
174172
if ($value >= 0 && $value <= 127) { //+TINY_INT
175173
$packed = pack('C', 0b00000000 | $value);
176-
$output .= $this->littleEndian ? strrev($packed) : $packed;
174+
return $this->littleEndian ? strrev($packed) : $packed;
177175
} elseif ($value >= -16 && $value < 0) { //-TINY_INT
178176
$packed = pack('c', 0b11110000 | $value);
179-
$output .= $this->littleEndian ? strrev($packed) : $packed;
177+
return $this->littleEndian ? strrev($packed) : $packed;
180178
} elseif ($value >= -128 && $value <= -17) { //INT_8
181179
$packed = pack('c', $value);
182-
$output .= chr(0xC8) . ($this->littleEndian ? strrev($packed) : $packed);
180+
return chr(0xC8) . ($this->littleEndian ? strrev($packed) : $packed);
183181
} elseif (($value >= 128 && $value <= 32767) || ($value >= -32768 && $value <= -129)) { //INT_16
184182
$packed = pack('s', $value);
185-
$output .= chr(0xC9) . ($this->littleEndian ? strrev($packed) : $packed);
183+
return chr(0xC9) . ($this->littleEndian ? strrev($packed) : $packed);
186184
} elseif (($value >= 32768 && $value <= 2147483647) || ($value >= -2147483648 && $value <= -32769)) { //INT_32
187185
$packed = pack('l', $value);
188-
$output .= chr(0xCA) . ($this->littleEndian ? strrev($packed) : $packed);
186+
return chr(0xCA) . ($this->littleEndian ? strrev($packed) : $packed);
189187
} elseif (($value >= 2147483648 && $value <= 9223372036854775807) || ($value >= -9223372036854775808 && $value <= -2147483649)) { //INT_64
190188
$packed = pack('q', $value);
191-
$output .= chr(0xCB) . ($this->littleEndian ? strrev($packed) : $packed);
189+
return chr(0xCB) . ($this->littleEndian ? strrev($packed) : $packed);
192190
} else {
193191
throw new PackException('Integer out of range');
194192
}
195-
196-
return $output;
197193
}
198194

199195
/**
@@ -262,11 +258,11 @@ private function packList(array $arr): string
262258
*/
263259
private function packStructure(IStructure $structure): string
264260
{
265-
if (!array_key_exists(get_class($structure), $this->structuresLt)) {
261+
$arr = $this->structuresLt[get_class($structure)] ?? null;
262+
if ($arr === null) {
266263
throw new PackException('Provided structure as parameter is not supported');
267264
}
268265

269-
$arr = $this->structuresLt[get_class($structure)];
270266
$signature = chr(array_shift($arr));
271267
$output = pack('C', 0b10110000 | count($arr)) . $signature;
272268
foreach ($arr as $structureMethod => $packerMethod) {

src/PackStream/v1/Unpacker.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ private function unpackStruct(int $marker)
170170

171171
if (array_key_exists($signature, $this->structuresLt)) {
172172
if ($size + 1 !== count($this->structuresLt[$signature]))
173-
trigger_error('Incorrect amount of structure fields', E_USER_ERROR);
173+
throw new UnpackException('Incorrect amount of structure fields for ' . reset($this->structuresLt[$signature]));
174174
return $this->unpackSpecificStructure(...$this->structuresLt[$signature]);
175175
} else {
176176
$this->signature = $signature;
@@ -181,11 +181,11 @@ private function unpackStruct(int $marker)
181181
/**
182182
* Dynamic predefined specific structure unpacking
183183
* @param string $class
184-
* @param mixed ...$methods
185-
* @return mixed
184+
* @param string ...$methods
185+
* @return IStructure
186186
* @throws UnpackException
187187
*/
188-
private function unpackSpecificStructure(string $class, ...$methods)
188+
private function unpackSpecificStructure(string $class, string ...$methods): IStructure
189189
{
190190
$values = [];
191191
foreach ($methods as $method) {

tests/BoltTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,41 @@ public function testReset(AProtocol $protocol): void
181181
$this->markTestIncomplete($e->getMessage());
182182
}
183183
}
184+
185+
/**
186+
* @large
187+
* @depends testHello
188+
* @param AProtocol $protocol
189+
* @throws Exception
190+
*/
191+
public function testChunking(AProtocol $protocol)
192+
{
193+
Bolt::$debug = false;
194+
195+
$protocol->begin();
196+
$protocol->run('CREATE (a:Test) RETURN ID(a)');
197+
$result = $protocol->pull();
198+
199+
$data = [];
200+
while (strlen(serialize($data)) < 65535 * 2) {
201+
$data[base64_encode(random_bytes(32))] = base64_encode(random_bytes(128));
202+
try {
203+
$run = $protocol->run('MATCH (a:Test) WHERE ID(a) = $id SET a += $data RETURN a', [
204+
'id' => $result[0][0],
205+
'data' => (object)$data
206+
]);
207+
$this->assertIsArray($run);
208+
209+
$pull = $protocol->pull();
210+
$this->assertIsArray($pull);
211+
$this->assertInstanceOf(\Bolt\structures\Node::class, $pull[0][0]);
212+
$this->assertCount(count($data), $pull[0][0]->properties());
213+
} catch (Exception $e) {
214+
$this->markTestIncomplete();
215+
break;
216+
}
217+
}
218+
219+
$protocol->rollback();
220+
}
184221
}

0 commit comments

Comments
 (0)