Skip to content

Commit 81700c1

Browse files
Merge pull request #37 from stefanak-michal/issue/35_tls
Issue/35 tls
2 parents d5f1744 + b366638 commit 81700c1

File tree

7 files changed

+189
-13
lines changed

7 files changed

+189
-13
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Bolt
22
Bolt protocol library over TCP socket. Bolt protocol is primary used for communication with [Neo4j](https://neo4j.com/) Graph database. The documentation is available at [https://7687.org/](https://7687.org/).
33

4-
![](https://img.shields.io/badge/phpunit-passed-success) ![](https://img.shields.io/badge/coverage-74%25-yellowgreen) ![](https://img.shields.io/github/stars/stefanak-michal/Bolt) ![](https://img.shields.io/packagist/dt/stefanak-michal/bolt) ![](https://img.shields.io/github/v/release/stefanak-michal/bolt) ![](https://img.shields.io/github/commits-since/stefanak-michal/bolt/latest)
4+
![](https://img.shields.io/badge/phpunit-passed-success) ![](https://img.shields.io/badge/coverage-77%25-green) ![](https://img.shields.io/github/stars/stefanak-michal/Bolt) ![](https://img.shields.io/packagist/dt/stefanak-michal/bolt) ![](https://img.shields.io/github/v/release/stefanak-michal/bolt) ![](https://img.shields.io/github/commits-since/stefanak-michal/bolt/latest)
55

66
## Version support
77
Bolt <= 4.1

src/Bolt.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,12 @@ final class Bolt
7171

7272
/**
7373
* Bolt constructor
74-
* @param string $ip
75-
* @param int $port
76-
* @param int $timeout
74+
* @param IConnection $connection
7775
* @throws Exception
7876
*/
79-
public function __construct(string $ip = '127.0.0.1', int $port = 7687, int $timeout = 15)
77+
public function __construct(IConnection $connection)
8078
{
81-
$this->connection = new \Bolt\connection\Socket($ip, $port, $timeout);
79+
$this->connection = $connection;
8280

8381
$packerClass = "\\Bolt\\PackStream\\v" . $this->packStreamVersion . "\\Packer";
8482
if (!class_exists($packerClass)) {

src/connection/IConnection.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
*/
1212
interface IConnection
1313
{
14+
public function __construct(string $ip = '127.0.0.1', int $port = 7687, int $timeout = 15);
15+
1416
public function connect(): bool;
1517

1618
public function write(string $buffer);

src/connection/Socket.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Bolt\connection;
44

55
use Bolt\Bolt;
6+
use Exception;
67

78
/**
89
* Socket class
@@ -38,9 +39,9 @@ class Socket implements IConnection
3839
* @param string $ip
3940
* @param int $port
4041
* @param int $timeout
41-
* @throws \Exception
42+
* @throws Exception
4243
*/
43-
public function __construct(string $ip, int $port, int $timeout)
44+
public function __construct(string $ip = '127.0.0.1', int $port = 7687, int $timeout = 15)
4445
{
4546
if (!extension_loaded('sockets')) {
4647
Bolt::error('PHP Extension sockets not enabled');
@@ -54,7 +55,7 @@ public function __construct(string $ip, int $port, int $timeout)
5455
/**
5556
* Create socket connection
5657
* @return bool
57-
* @throws \Exception
58+
* @throws Exception
5859
*/
5960
public function connect(): bool
6061
{
@@ -87,7 +88,7 @@ public function connect(): bool
8788
/**
8889
* Write buffer to socket
8990
* @param string $buffer
90-
* @throws \Exception
91+
* @throws Exception
9192
*/
9293
public function write(string $buffer)
9394
{
@@ -119,7 +120,7 @@ public function write(string $buffer)
119120
* Read buffer from socket
120121
* @param int $length
121122
* @return string
122-
* @throws \Exception
123+
* @throws Exception
123124
*/
124125
public function read(int $length = 2048): string
125126
{

src/connection/StreamSocket.php

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<?php
2+
3+
4+
namespace Bolt\connection;
5+
6+
use Bolt\Bolt;
7+
use Exception;
8+
9+
/**
10+
* Stream socket class
11+
*
12+
* @author Michal Stefanak
13+
* @link https://github.com/stefanak-michal/Bolt
14+
* @package Bolt\connection
15+
*/
16+
class StreamSocket implements IConnection
17+
{
18+
19+
/**
20+
* @var string
21+
*/
22+
private $ip;
23+
24+
/**
25+
* @var int
26+
*/
27+
private $port;
28+
29+
/**
30+
* @var int
31+
*/
32+
private $timeout;
33+
34+
/**
35+
* @var array
36+
*/
37+
private $sslContextOptions = [];
38+
39+
/**
40+
* @var resource
41+
*/
42+
private $stream;
43+
44+
/**
45+
* StreamSocket constructor.
46+
* @param string $ip
47+
* @param int $port
48+
* @param int $timeout
49+
*/
50+
public function __construct(string $ip = '127.0.0.1', int $port = 7687, int $timeout = 15)
51+
{
52+
$this->ip = $ip;
53+
$this->port = $port;
54+
$this->timeout = $timeout;
55+
}
56+
57+
/**
58+
* Set SSL Context options
59+
* @link https://www.php.net/manual/en/context.ssl.php
60+
* @param array $options
61+
*/
62+
public function setSslContextOptions(array $options)
63+
{
64+
$this->sslContextOptions = $options;
65+
}
66+
67+
/**
68+
* Connect
69+
* @return bool
70+
* @throws Exception
71+
*/
72+
public function connect(): bool
73+
{
74+
$context = stream_context_create([
75+
'socket' => [
76+
'tcp_nodelay' => true,
77+
],
78+
'ssl' => $this->sslContextOptions
79+
]);
80+
81+
$this->stream = stream_socket_client( 'tcp://' . $this->ip . ':' . $this->port, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $context);
82+
83+
if ($this->stream === false) {
84+
Bolt::error($errstr . ' (' . $errno . ')');
85+
return false;
86+
}
87+
88+
if (!stream_set_blocking($this->stream, true)) {
89+
Bolt::error('Cannot set socket into blocking mode');
90+
return false;
91+
}
92+
93+
if (!empty($this->sslContextOptions)) {
94+
if (stream_socket_enable_crypto($this->stream, true, STREAM_CRYPTO_METHOD_ANY_CLIENT) !== true) {
95+
Bolt::error('Enable encryption error');
96+
return false;
97+
}
98+
}
99+
100+
return true;
101+
}
102+
103+
/**
104+
* Write to connection
105+
* @param string $buffer
106+
* @throws Exception
107+
*/
108+
public function write(string $buffer)
109+
{
110+
if (Bolt::$debug)
111+
$this->printHex($buffer);
112+
113+
114+
if (fwrite($this->stream, $buffer) === false)
115+
Bolt::error('Write error');
116+
}
117+
118+
/**
119+
* Read from connection
120+
* @param int $length
121+
* @return string
122+
* @throws Exception
123+
*/
124+
public function read(int $length = 2048): string
125+
{
126+
$res = fread($this->stream, $length);
127+
if (empty($res))
128+
Bolt::error('Read error');
129+
130+
if (Bolt::$debug)
131+
$this->printHex($res, false);
132+
133+
return (string)$res;
134+
}
135+
136+
/**
137+
* Close connection
138+
*/
139+
public function disconnect()
140+
{
141+
if (is_resource($this->stream))
142+
stream_socket_shutdown($this->stream, STREAM_SHUT_RDWR);
143+
}
144+
145+
/**
146+
* Print buffer as HEX
147+
* @param string $str
148+
* @param bool $write
149+
*/
150+
private function printHex(string $str, bool $write = true)
151+
{
152+
$str = implode(unpack('H*', $str));
153+
echo '<pre>';
154+
echo $write ? '> ' : '< ';
155+
foreach (str_split($str, 8) as $chunk) {
156+
echo implode(' ', str_split($chunk, 2));
157+
echo ' ';
158+
}
159+
echo '</pre>';
160+
}
161+
162+
}

tests/ATest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ protected function mockConnection()
4040
$mockBuilder = $this
4141
->getMockBuilder(IConnection::class)
4242
->disableOriginalConstructor();
43-
call_user_func([$mockBuilder, method_exists($mockBuilder, 'onlyMethods') ? 'onlyMethods' : 'setMethods'], ['write', 'read', 'connect', 'disconnect']);
43+
call_user_func([$mockBuilder, method_exists($mockBuilder, 'onlyMethods') ? 'onlyMethods' : 'setMethods'], ['__construct', 'write', 'read', 'connect', 'disconnect']);
4444
/** @var IConnection $connection */
4545
$connection = $mockBuilder->getMock();
4646

tests/BoltTest.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*
1313
* @covers \Bolt\Bolt
1414
* @covers \Bolt\connection\Socket
15+
* @covers \Bolt\connection\StreamSocket
1516
* @covers \Bolt\PackStream\v1\Packer
1617
* @covers \Bolt\PackStream\v1\Unpacker
1718
*
@@ -28,10 +29,22 @@ class BoltTest extends \Bolt\tests\ATest
2829
*/
2930
public function testHello(): ?Bolt
3031
{
32+
Bolt::$debug = true;
33+
3134
try {
32-
$bolt = new Bolt($GLOBALS['NEO_HOST'] ?? '127.0.0.1', $GLOBALS['NEO_PORT'] ?? 7687);
35+
$conn = new \Bolt\connection\StreamSocket($GLOBALS['NEO_HOST'] ?? '127.0.0.1', $GLOBALS['NEO_PORT'] ?? 7687);
36+
$this->assertInstanceOf(\Bolt\connection\StreamSocket::class, $conn);
37+
$bolt = new Bolt($conn);
3338
$this->assertInstanceOf(Bolt::class, $bolt);
3439
$this->assertTrue($bolt->hello('Test/1.0', $GLOBALS['NEO_USER'], $GLOBALS['NEO_PASS']));
40+
unset($bolt);
41+
42+
$conn = new \Bolt\connection\Socket($GLOBALS['NEO_HOST'] ?? '127.0.0.1', $GLOBALS['NEO_PORT'] ?? 7687);
43+
$this->assertInstanceOf(\Bolt\connection\Socket::class, $conn);
44+
$bolt = new Bolt($conn);
45+
$this->assertInstanceOf(Bolt::class, $bolt);
46+
$this->assertTrue($bolt->hello('Test/1.0', $GLOBALS['NEO_USER'], $GLOBALS['NEO_PASS']));
47+
3548
return $bolt;
3649
} catch (\Exception $e) {
3750
$this->markTestSkipped($e->getMessage());

0 commit comments

Comments
 (0)