Skip to content

Commit 4ed8c26

Browse files
committed
[UPD] Client to 1.1.0
1 parent 94d15fa commit 4ed8c26

9 files changed

Lines changed: 389 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Changelog
2+
3+
## [1.1.0] - 2026-03-18
4+
5+
### Changed
6+
7+
- Updated dependency `gianfriaur/opcua-php-client` from `^1.0` to `^1.1`, requiring the new auto-generated certificate feature introduced in that release.
8+
9+
### Added
10+
11+
- **Auto-generated client certificate support.** When a secure connection is opened through the daemon with `SecurityPolicy` and `SecurityMode` configured but no `clientCertPath`/`clientKeyPath` provided, the underlying `Client` automatically generates an in-memory self-signed certificate. The behaviour is transparent and inherited from `opcua-php-client` v1.1 — no changes required in `ManagedClient` or `CommandHandler`.
12+
- Unit and integration tests for the auto-generated certificate flow.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Gianfrancesco
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"license": "MIT",
66
"require": {
77
"php": "^8.2",
8-
"gianfriaur/opcua-php-client": "^1.0",
8+
"gianfriaur/opcua-php-client": "^1.1",
99
"react/event-loop": "^1.5",
1010
"react/socket": "^1.16"
1111
},

src/Client/ManagedClient.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
namespace Gianfriaur\OpcuaSessionManager\Client;
66

77
use Gianfriaur\OpcuaPhpClient\Exception\ConnectionException;
8-
use Gianfriaur\OpcuaPhpClient\Exception\OpcUaException;
98
use Gianfriaur\OpcuaPhpClient\Exception\ServiceException;
109
use Gianfriaur\OpcuaPhpClient\OpcUaClientInterface;
1110
use Gianfriaur\OpcuaPhpClient\Security\SecurityMode;

src/Daemon/CommandHandler.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
use Gianfriaur\OpcuaPhpClient\Client;
88
use Gianfriaur\OpcuaPhpClient\Security\SecurityMode;
99
use Gianfriaur\OpcuaPhpClient\Security\SecurityPolicy;
10-
use Gianfriaur\OpcuaPhpClient\Types\BuiltinType;
11-
use Gianfriaur\OpcuaPhpClient\Types\NodeId;
12-
use Gianfriaur\OpcuaPhpClient\Types\Variant;
1310
use Gianfriaur\OpcuaSessionManager\Exception\SessionNotFoundException;
1411
use Gianfriaur\OpcuaSessionManager\Serialization\TypeSerializer;
1512

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Gianfriaur\OpcuaPhpClient\Security\SecurityMode;
6+
use Gianfriaur\OpcuaPhpClient\Security\SecurityPolicy;
7+
use Gianfriaur\OpcuaPhpClient\Types\NodeId;
8+
use Gianfriaur\OpcuaPhpClient\Types\StatusCode;
9+
use Gianfriaur\OpcuaSessionManager\Client\SocketConnection;
10+
use Gianfriaur\OpcuaSessionManager\Tests\Integration\Helpers\TestHelper;
11+
12+
// Tests for auto-generated client certificate connections (opcua-php-client v1.1+).
13+
// The daemon's Client auto-generates a self-signed certificate when security
14+
// policy/mode are configured but no explicit clientCertPath/clientKeyPath are provided.
15+
16+
beforeAll(fn() => TestHelper::startDaemon());
17+
afterAll(fn() => TestHelper::stopDaemon());
18+
19+
describe('Auto-generated certificate: connection', function () {
20+
21+
it('connects to auto-accept server with security but no explicit cert paths', function () {
22+
$client = null;
23+
try {
24+
$client = TestHelper::createManagedClient();
25+
$client->setSecurityPolicy(SecurityPolicy::Basic256Sha256);
26+
$client->setSecurityMode(SecurityMode::SignAndEncrypt);
27+
// No setClientCertificate() — daemon auto-generates the cert
28+
$client->connect(TestHelper::ENDPOINT_AUTO_ACCEPT);
29+
30+
expect($client->getSessionId())->toBeString()->not->toBeEmpty();
31+
} finally {
32+
TestHelper::safeDisconnect($client);
33+
}
34+
})->group('integration');
35+
36+
it('reads ServerState after auto-cert connection', function () {
37+
$client = null;
38+
try {
39+
$client = TestHelper::createManagedClient();
40+
$client->setSecurityPolicy(SecurityPolicy::Basic256Sha256);
41+
$client->setSecurityMode(SecurityMode::SignAndEncrypt);
42+
$client->connect(TestHelper::ENDPOINT_AUTO_ACCEPT);
43+
44+
$dataValue = $client->read(NodeId::numeric(0, 2259));
45+
expect($dataValue->getStatusCode())->toBe(StatusCode::Good);
46+
expect($dataValue->getValue())->toBeInt()->toBe(0);
47+
} finally {
48+
TestHelper::safeDisconnect($client);
49+
}
50+
})->group('integration');
51+
52+
it('browses Objects folder after auto-cert connection', function () {
53+
$client = null;
54+
try {
55+
$client = TestHelper::createManagedClient();
56+
$client->setSecurityPolicy(SecurityPolicy::Basic256Sha256);
57+
$client->setSecurityMode(SecurityMode::SignAndEncrypt);
58+
$client->connect(TestHelper::ENDPOINT_AUTO_ACCEPT);
59+
60+
$refs = $client->browse(NodeId::numeric(0, 85));
61+
expect($refs)->toBeArray()->not->toBeEmpty();
62+
63+
$names = array_map(fn($r) => $r->getBrowseName()->getName(), $refs);
64+
expect($names)->toContain('Server');
65+
} finally {
66+
TestHelper::safeDisconnect($client);
67+
}
68+
})->group('integration');
69+
70+
it('disconnects auto-cert session cleanly', function () {
71+
$client = TestHelper::createManagedClient();
72+
$client->setSecurityPolicy(SecurityPolicy::Basic256Sha256);
73+
$client->setSecurityMode(SecurityMode::SignAndEncrypt);
74+
$client->connect(TestHelper::ENDPOINT_AUTO_ACCEPT);
75+
76+
$sessionId = $client->getSessionId();
77+
expect($sessionId)->toBeString()->not->toBeEmpty();
78+
79+
$client->disconnect();
80+
expect($client->getSessionId())->toBeNull();
81+
})->group('integration');
82+
83+
it('can reconnect with auto-cert after disconnect', function () {
84+
$client = TestHelper::createManagedClient();
85+
$client->setSecurityPolicy(SecurityPolicy::Basic256Sha256);
86+
$client->setSecurityMode(SecurityMode::SignAndEncrypt);
87+
88+
$client->connect(TestHelper::ENDPOINT_AUTO_ACCEPT);
89+
$firstSession = $client->getSessionId();
90+
$client->disconnect();
91+
92+
$client->connect(TestHelper::ENDPOINT_AUTO_ACCEPT);
93+
$secondSession = $client->getSessionId();
94+
95+
expect($secondSession)->not->toBe($firstSession);
96+
97+
$dataValue = $client->read(NodeId::numeric(0, 2259));
98+
expect($dataValue->getValue())->toBeInt();
99+
100+
$client->disconnect();
101+
})->group('integration');
102+
103+
})->group('integration');
104+
105+
describe('Auto-generated certificate: daemon list output', function () {
106+
107+
it('does not expose cert paths in list for auto-cert session', function () {
108+
$client = null;
109+
try {
110+
$client = TestHelper::createManagedClient();
111+
$client->setSecurityPolicy(SecurityPolicy::Basic256Sha256);
112+
$client->setSecurityMode(SecurityMode::SignAndEncrypt);
113+
$client->connect(TestHelper::ENDPOINT_AUTO_ACCEPT);
114+
115+
$response = SocketConnection::send(TestHelper::SOCKET_PATH, ['command' => 'list']);
116+
expect($response['success'])->toBeTrue();
117+
118+
$found = false;
119+
foreach ($response['data']['sessions'] as $session) {
120+
if ($session['id'] === $client->getSessionId()) {
121+
$found = true;
122+
expect($session['config'])->not->toHaveKey('clientCertPath');
123+
expect($session['config'])->not->toHaveKey('clientKeyPath');
124+
expect($session['config'])->not->toHaveKey('caCertPath');
125+
expect($session['config'])->toHaveKey('securityPolicy');
126+
expect($session['config'])->toHaveKey('securityMode');
127+
break;
128+
}
129+
}
130+
expect($found)->toBeTrue('Auto-cert session not found in daemon list');
131+
} finally {
132+
TestHelper::safeDisconnect($client);
133+
}
134+
})->group('integration');
135+
136+
it('explicit-cert and auto-cert sessions coexist in daemon', function () {
137+
$autoClient = null;
138+
$explicitClient = null;
139+
try {
140+
$autoClient = TestHelper::createManagedClient();
141+
$autoClient->setSecurityPolicy(SecurityPolicy::Basic256Sha256);
142+
$autoClient->setSecurityMode(SecurityMode::SignAndEncrypt);
143+
$autoClient->connect(TestHelper::ENDPOINT_AUTO_ACCEPT);
144+
145+
$explicitClient = TestHelper::createManagedClient();
146+
$explicitClient->setSecurityPolicy(SecurityPolicy::Basic256Sha256);
147+
$explicitClient->setSecurityMode(SecurityMode::SignAndEncrypt);
148+
$explicitClient->setClientCertificate(
149+
TestHelper::getClientCertPath(),
150+
TestHelper::getClientKeyPath(),
151+
TestHelper::getCaCertPath(),
152+
);
153+
$explicitClient->connect(TestHelper::ENDPOINT_AUTO_ACCEPT);
154+
155+
expect($autoClient->getSessionId())->not->toBe($explicitClient->getSessionId());
156+
157+
// Both sessions should return valid data
158+
$v1 = $autoClient->read(NodeId::numeric(0, 2259))->getValue();
159+
$v2 = $explicitClient->read(NodeId::numeric(0, 2259))->getValue();
160+
expect($v1)->toBeInt();
161+
expect($v2)->toBeInt();
162+
163+
// Check list: key presence differs per session type
164+
$response = SocketConnection::send(TestHelper::SOCKET_PATH, ['command' => 'list']);
165+
$configs = array_column($response['data']['sessions'], 'config', 'id');
166+
167+
expect($configs[$autoClient->getSessionId()])->not->toHaveKey('clientCertPath');
168+
expect($configs[$explicitClient->getSessionId()])->toHaveKey('clientCertPath');
169+
expect($configs[$explicitClient->getSessionId()])->not->toHaveKey('clientKeyPath');
170+
} finally {
171+
TestHelper::safeDisconnect($autoClient);
172+
TestHelper::safeDisconnect($explicitClient);
173+
}
174+
})->group('integration');
175+
176+
})->group('integration');

tests/Integration/Helpers/TestHelper.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
namespace Gianfriaur\OpcuaSessionManager\Tests\Integration\Helpers;
66

7-
use Gianfriaur\OpcuaSessionManager\Client\ManagedClient;
8-
use Gianfriaur\OpcuaSessionManager\Client\SocketConnection;
97
use Gianfriaur\OpcuaPhpClient\Types\NodeId;
108
use Gianfriaur\OpcuaPhpClient\Types\ReferenceDescription;
9+
use Gianfriaur\OpcuaSessionManager\Client\ManagedClient;
10+
use Gianfriaur\OpcuaSessionManager\Client\SocketConnection;
1111

1212
final class TestHelper
1313
{
@@ -27,7 +27,7 @@ final class TestHelper
2727
// ── Certificate paths ───────────────────────────────────────────────
2828
public static function getCertsDir(): string
2929
{
30-
$dir = getenv('OPCUA_CERTS_DIR') ?: __DIR__ . '/../../../../server/certs';
30+
$dir = getenv('OPCUA_CERTS_DIR') ?: __DIR__ . '/../../../../opcua-test-server-suite/certs';
3131

3232
return realpath($dir) ?: $dir;
3333
}

tests/Integration/SessionPersistenceTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Gianfriaur\OpcuaPhpClient\Types\BuiltinType;
66
use Gianfriaur\OpcuaPhpClient\Types\NodeId;
77
use Gianfriaur\OpcuaPhpClient\Types\StatusCode;
8-
use Gianfriaur\OpcuaSessionManager\Client\ManagedClient;
98
use Gianfriaur\OpcuaSessionManager\Client\SocketConnection;
109
use Gianfriaur\OpcuaSessionManager\Tests\Integration\Helpers\TestHelper;
1110

0 commit comments

Comments
 (0)