PHP Classes

File: tests/Unit/Protocol/PublishServiceTest.php

Recommend this page to a friend!
  Packages of Gianfrancesco Aurecchia   OPC UA Client   tests/Unit/Protocol/PublishServiceTest.php   Download  
File: tests/Unit/Protocol/PublishServiceTest.php
Role: Example script
Content type: text/plain
Description: Example script
Class: OPC UA Client
Control devices that support the OPC UA protocol
Author: By
Last change:
Date: 4 days ago
Size: 9,772 bytes
 

Contents

Class file image Download
<?php

declare(strict_types=1);

use
PhpOpcua\Client\Encoding\BinaryDecoder;
use
PhpOpcua\Client\Encoding\BinaryEncoder;
use
PhpOpcua\Client\Module\Subscription\PublishService;
use
PhpOpcua\Client\Protocol\MessageHeader;
use
PhpOpcua\Client\Protocol\SessionService;
use
PhpOpcua\Client\Types\BuiltinType;
use
PhpOpcua\Client\Types\NodeId;

function
writePublishResponseHeader(BinaryEncoder $encoder, int $statusCode = 0): void
{
   
$encoder->writeInt64(0);
   
$encoder->writeUInt32(1);
   
$encoder->writeUInt32($statusCode);
   
$encoder->writeByte(0);
   
$encoder->writeInt32(0);
   
$encoder->writeNodeId(NodeId::numeric(0, 0));
   
$encoder->writeByte(0);
}

function
writePublishMessagePrefix(BinaryEncoder $encoder): void
{
   
$encoder->writeUInt32(1);
   
$encoder->writeUInt32(1);
   
$encoder->writeUInt32(1);
}

describe('PublishService encoding', function () {

   
it('encodes a Publish request without acknowledgements', function () {
       
$session = new SessionService(1, 1);
       
$service = new PublishService($session);

       
$bytes = $service->encodePublishRequest(1, NodeId::numeric(0, 0));

       
$decoder = new BinaryDecoder($bytes);
       
$header = MessageHeader::decode($decoder);
       
expect($header->getMessageType())->toBe('MSG');
    });

   
it('encodes a Publish request with acknowledgements', function () {
       
$session = new SessionService(1, 1);
       
$service = new PublishService($session);

       
$bytes = $service->encodePublishRequest(1, NodeId::numeric(0, 0), [
            [
'subscriptionId' => 42, 'sequenceNumber' => 1],
            [
'subscriptionId' => 42, 'sequenceNumber' => 2],
        ]);

       
expect(strlen($bytes))->toBeGreaterThan(40);
    });
});

describe('PublishService decoding', function () {

   
it('decodes a PublishResponse with DataChangeNotification', function () {
       
$session = new SessionService(1, 1);
       
$service = new PublishService($session);

       
$encoder = new BinaryEncoder();
       
writePublishMessagePrefix($encoder);
       
$encoder->writeNodeId(NodeId::numeric(0, 829)); // PublishResponse
       
writePublishResponseHeader($encoder);

       
// SubscriptionId
       
$encoder->writeUInt32(42);
       
// AvailableSequenceNumbers: 1
       
$encoder->writeInt32(1);
       
$encoder->writeUInt32(5);
       
// MoreNotifications
       
$encoder->writeBoolean(false);

       
// NotificationMessage
       
$encoder->writeUInt32(5); // SequenceNumber
       
$encoder->writeDateTime(null); // PublishTime

        // NotificationData: 1 DataChangeNotification (TypeId 811)
       
$encoder->writeInt32(1);
       
$encoder->writeNodeId(NodeId::numeric(0, 811));
       
$encoder->writeByte(0x01); // has body

        // Build DataChangeNotification body
       
$bodyEncoder = new BinaryEncoder();
       
// MonitoredItems: 2
       
$bodyEncoder->writeInt32(2);
       
// Item 1
       
$bodyEncoder->writeUInt32(1); // clientHandle
       
$bodyEncoder->writeByte(0x01); // DataValue mask (value)
       
$bodyEncoder->writeByte(BuiltinType::Int32->value);
       
$bodyEncoder->writeInt32(100);
       
// Item 2
       
$bodyEncoder->writeUInt32(2); // clientHandle
       
$bodyEncoder->writeByte(0x01);
       
$bodyEncoder->writeByte(BuiltinType::Double->value);
       
$bodyEncoder->writeDouble(3.14);
       
// DiagnosticInfos: empty
       
$bodyEncoder->writeInt32(0);

       
$body = $bodyEncoder->getBuffer();
       
$encoder->writeInt32(strlen($body));
       
$encoder->writeRawBytes($body);

       
// Results: empty
       
$encoder->writeInt32(0);
       
// DiagnosticInfos: empty
       
$encoder->writeInt32(0);

       
$decoder = new BinaryDecoder($encoder->getBuffer());
       
$result = $service->decodePublishResponse($decoder);
       
expect($result->subscriptionId)->toBe(42);
       
expect($result->sequenceNumber)->toBe(5);
       
expect($result->moreNotifications)->toBeFalse();
       
expect($result->availableSequenceNumbers)->toBe([5]);
       
expect($result->notifications)->toHaveCount(2);
       
expect($result->notifications[0]['type'])->toBe('DataChange');
       
expect($result->notifications[0]['clientHandle'])->toBe(1);
       
expect($result->notifications[0]['dataValue']->getValue())->toBe(100);
       
expect($result->notifications[1]['clientHandle'])->toBe(2);
       
expect($result->notifications[1]['dataValue']->getValue())->toBe(3.14);
    });

   
it('decodes a PublishResponse with EventNotificationList', function () {
       
$session = new SessionService(1, 1);
       
$service = new PublishService($session);

       
$encoder = new BinaryEncoder();
       
writePublishMessagePrefix($encoder);
       
$encoder->writeNodeId(NodeId::numeric(0, 829));
       
writePublishResponseHeader($encoder);

       
$encoder->writeUInt32(10); // SubscriptionId
       
$encoder->writeInt32(0); // AvailableSequenceNumbers: 0
       
$encoder->writeBoolean(false);

       
// NotificationMessage
       
$encoder->writeUInt32(1);
       
$encoder->writeDateTime(null);

       
// NotificationData: 1 EventNotificationList (TypeId 916)
       
$encoder->writeInt32(1);
       
$encoder->writeNodeId(NodeId::numeric(0, 916));
       
$encoder->writeByte(0x01);

       
// Build EventNotificationList body
       
$bodyEncoder = new BinaryEncoder();
       
// Events: 1 EventFieldList
       
$bodyEncoder->writeInt32(1);
       
$bodyEncoder->writeUInt32(5); // clientHandle
        // EventFields: 2 variants
       
$bodyEncoder->writeInt32(2);
       
// Field 1: String
       
$bodyEncoder->writeByte(BuiltinType::String->value);
       
$bodyEncoder->writeString('TestEvent');
       
// Field 2: UInt16 (Severity)
       
$bodyEncoder->writeByte(BuiltinType::UInt16->value);
       
$bodyEncoder->writeUInt16(500);

       
$body = $bodyEncoder->getBuffer();
       
$encoder->writeInt32(strlen($body));
       
$encoder->writeRawBytes($body);

       
// Results: empty
       
$encoder->writeInt32(0);
       
// DiagnosticInfos: empty
       
$encoder->writeInt32(0);

       
$decoder = new BinaryDecoder($encoder->getBuffer());
       
$result = $service->decodePublishResponse($decoder);
       
expect($result->subscriptionId)->toBe(10);
       
expect($result->notifications)->toHaveCount(1);
       
expect($result->notifications[0]['type'])->toBe('Event');
       
expect($result->notifications[0]['clientHandle'])->toBe(5);
       
expect($result->notifications[0]['eventFields'])->toHaveCount(2);
       
expect($result->notifications[0]['eventFields'][0]->getValue())->toBe('TestEvent');
       
expect($result->notifications[0]['eventFields'][1]->getValue())->toBe(500);
    });

   
it('decodes a PublishResponse with unknown notification type (skips)', function () {
       
$session = new SessionService(1, 1);
       
$service = new PublishService($session);

       
$encoder = new BinaryEncoder();
       
writePublishMessagePrefix($encoder);
       
$encoder->writeNodeId(NodeId::numeric(0, 829));
       
writePublishResponseHeader($encoder);

       
$encoder->writeUInt32(1);
       
$encoder->writeInt32(0);
       
$encoder->writeBoolean(false);
       
$encoder->writeUInt32(1);
       
$encoder->writeDateTime(null);

       
// NotificationData: 1 unknown type
       
$encoder->writeInt32(1);
       
$encoder->writeNodeId(NodeId::numeric(0, 9999)); // unknown
       
$encoder->writeByte(0x01);
       
$fakeBody = str_repeat("\x00", 16);
       
$encoder->writeInt32(strlen($fakeBody));
       
$encoder->writeRawBytes($fakeBody);

       
$encoder->writeInt32(0);
       
$encoder->writeInt32(0);

       
$decoder = new BinaryDecoder($encoder->getBuffer());
       
$result = $service->decodePublishResponse($decoder);
       
expect($result->notifications)->toBe([]);
    });

   
it('decodes a PublishResponse with no-body notification', function () {
       
$session = new SessionService(1, 1);
       
$service = new PublishService($session);

       
$encoder = new BinaryEncoder();
       
writePublishMessagePrefix($encoder);
       
$encoder->writeNodeId(NodeId::numeric(0, 829));
       
writePublishResponseHeader($encoder);

       
$encoder->writeUInt32(1);
       
$encoder->writeInt32(0);
       
$encoder->writeBoolean(false);
       
$encoder->writeUInt32(1);
       
$encoder->writeDateTime(null);

       
// NotificationData: 1 with no body
       
$encoder->writeInt32(1);
       
$encoder->writeNodeId(NodeId::numeric(0, 0));
       
$encoder->writeByte(0x00); // no body

       
$encoder->writeInt32(0);
       
$encoder->writeInt32(0);

       
$decoder = new BinaryDecoder($encoder->getBuffer());
       
$result = $service->decodePublishResponse($decoder);
       
expect($result->notifications)->toBe([]);
    });

   
it('decodes an empty PublishResponse (keep-alive)', function () {
       
$session = new SessionService(1, 1);
       
$service = new PublishService($session);

       
$encoder = new BinaryEncoder();
       
writePublishMessagePrefix($encoder);
       
$encoder->writeNodeId(NodeId::numeric(0, 829));
       
writePublishResponseHeader($encoder);

       
$encoder->writeUInt32(42);
       
$encoder->writeInt32(0); // no avail seq numbers
       
$encoder->writeBoolean(false);
       
$encoder->writeUInt32(0); // seq number 0
       
$encoder->writeDateTime(null);
       
$encoder->writeInt32(0); // no notification data

       
$encoder->writeInt32(0);
       
$encoder->writeInt32(0);

       
$decoder = new BinaryDecoder($encoder->getBuffer());
       
$result = $service->decodePublishResponse($decoder);
       
expect($result->subscriptionId)->toBe(42);
       
expect($result->notifications)->toBe([]);
       
expect($result->moreNotifications)->toBeFalse();
    });
});