Merge pull request #8521 from o6-automation/merge_14_15

Merge 1.4 to 1.5
This commit is contained in:
Julius Pfrommer
2026-10-04 16:32:38 +02:00
committed by GitHub
9 changed files with 480 additions and 3 deletions
+5
View File
@@ -229,6 +229,11 @@ processHEL(UA_Server *server, UA_SecureChannel *channel, const UA_ByteString *ms
if(retval != UA_STATUSCODE_GOOD)
return retval;
/* HEL has no fields after the EndpointUrl. */
if(offset != msg->length) {
UA_String_clear(&helloMessage.endpointUrl);
return UA_STATUSCODE_BADDECODINGERROR;
}
/* Part 6, 7.1.2.3: the EndpointUrl shall be less than 4096 bytes. The
* resource it identifies is currently not checked. */
if(helloMessage.endpointUrl.length >= 4096) {
+2
View File
@@ -28,7 +28,9 @@
#include <open62541/plugin/historydatabase.h>
#endif
/* Indexed by the AttributeId, which starts at 1 */
static const UA_NodeAttributesMask attr2mask[28] = {
UA_NODEATTRIBUTESMASK_NONE,
UA_NODEATTRIBUTESMASK_NODEID,
UA_NODEATTRIBUTESMASK_NODECLASS,
UA_NODEATTRIBUTESMASK_BROWSENAME,
+6 -3
View File
@@ -1427,9 +1427,12 @@ Service_Cancel(UA_Server *server, UA_Session *session,
SIMPLEQ_REMOVE_HEAD(&session->responseQueue, listEntry);
session->responseQueueSize--;
/* Send response and clean up */
response->responseHeader.serviceResult = UA_STATUSCODE_BADREQUESTCANCELLEDBYCLIENT;
sendResponse(server, session->channel, pre->requestId, (UA_Response *)response,
/* Cancel the queued Publish response. Keep the CancelResponse good
* so the client receives the number of cancelled requests. */
pre->response.responseHeader.serviceResult =
UA_STATUSCODE_BADREQUESTCANCELLEDBYCLIENT;
sendResponse(server, session->channel, pre->requestId,
(UA_Response *)&pre->response,
&UA_TYPES[UA_TYPES_PUBLISHRESPONSE]);
UA_PublishResponse_clear(&pre->response);
UA_free(pre);
+10
View File
@@ -869,6 +869,10 @@ FUNC_DECODE_BINARY(LocalizedText) {
* possible to reuse UA_findDataType */
static const UA_DataType *
UA_findDataTypeByBinaryInternal(Ctx *ctx, const UA_NodeId *typeId) {
/* A null identifier means that the type has no binary encoding. */
if(UA_NodeId_isNull(typeId))
return NULL;
/* Always look in the built-in types first. Assume that only numeric
* identifiers are used for the builtin types. (They may contain data types
* from all namespaces though.) */
@@ -1322,12 +1326,18 @@ FUNC_DECODE_BINARY(Variant) {
/* Does the variant contain an array? */
const UA_Boolean isArray = (encodingByte & (u8)UA_VARIANT_ENCODINGMASKTYPE_ARRAY) > 0;
UA_CHECK(isArray || !(encodingByte & UA_VARIANT_ENCODINGMASKTYPE_DIMENSIONS),
return UA_STATUSCODE_BADDECODINGERROR);
/* Get the datatype of the content. The type must be a builtin data type.
* All not-builtin types are wrapped in an ExtensionObject. The "type kind"
* for types up to DiagnsticInfo equals to the index in the encoding
* byte. */
size_t typeKind = (size_t)((encodingByte & (u8)UA_VARIANT_ENCODINGMASKTYPE_TYPEID_MASK) - 1);
/* Reserved wire type ids 26..31 contain ByteStrings (Part 6, 5.2.2.16).
* Normalize them to ByteString; the original reserved id is not retained. */
if(typeKind >= 25 && typeKind <= 30)
typeKind = UA_DATATYPEKIND_BYTESTRING;
UA_CHECK(typeKind <= UA_DATATYPEKIND_DIAGNOSTICINFO, return UA_STATUSCODE_BADDECODINGERROR);
/* A variant cannot contain a variant. But it can contain an array of
+182
View File
@@ -1945,6 +1945,183 @@ START_TEST(UA_DataType_getStructMember_test) {
} END_TEST
#endif
START_TEST(UA_ExtensionObject_decodeNullEncodingId) {
UA_NodeId nullId = UA_NODEID_NULL;
ck_assert_ptr_eq(UA_findDataTypeByBinary(&nullId), NULL);
UA_Byte data[] = {0, 0, 1, 1, 0, 0, 0, 5};
UA_ByteString input = {sizeof(data), data};
UA_ExtensionObject eo;
size_t offset = 0;
ck_assert_uint_eq(UA_decodeBinaryInternal(&input, &offset, &eo,
&UA_TYPES[UA_TYPES_EXTENSIONOBJECT], NULL), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(offset, input.length);
ck_assert_uint_eq(eo.encoding, UA_EXTENSIONOBJECT_ENCODED_BYTESTRING);
ck_assert(UA_NodeId_isNull(&eo.content.encoded.typeId));
ck_assert_uint_eq(eo.content.encoded.body.length, 1);
ck_assert_uint_eq(eo.content.encoded.body.data[0], 5);
UA_ExtensionObject_clear(&eo);
/* Numeric zero in another namespace can be a registered encoding id. */
UA_DataType custom = UA_TYPES[UA_TYPES_BOOLEAN];
custom.typeId = UA_NODEID_NUMERIC(1, 123);
custom.binaryEncodingId = UA_NODEID_NUMERIC(1, 0);
UA_DataTypeArray customTypes = {NULL, 1, &custom, false};
UA_DecodeBinaryOptions options = {0};
options.customTypes = &customTypes;
UA_Byte namespaced[] = {1, 1, 0, 0, 1, 1, 0, 0, 0, 5};
input.data = namespaced;
input.length = sizeof(namespaced);
offset = 0;
ck_assert_uint_eq(UA_decodeBinaryInternal(&input, &offset, &eo,
&UA_TYPES[UA_TYPES_EXTENSIONOBJECT], &options), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(eo.encoding, UA_EXTENSIONOBJECT_DECODED);
ck_assert_ptr_eq(eo.content.decoded.type, &custom);
UA_ExtensionObject_clear(&eo);
/* Legitimate null ExtensionObjects still have no body. */
data[2] = 0;
input.data = data;
input.length = 3;
ck_assert_uint_eq(UA_decodeBinary(&input, &eo, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT],
NULL), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(eo.encoding, UA_EXTENSIONOBJECT_ENCODED_NOBODY);
UA_ExtensionObject_clear(&eo);
} END_TEST
START_TEST(UA_Variant_decodeNullEncodingId) {
UA_Byte data[] = {0x96, 2, 0, 0, 0,
0, 0, 1, 1, 0, 0, 0, 5,
0, 0, 1, 1, 0, 0, 0, 0};
UA_ByteString input = {sizeof(data), data};
UA_Variant v;
ck_assert_uint_eq(UA_decodeBinary(&input, &v, &UA_TYPES[UA_TYPES_VARIANT], NULL),
UA_STATUSCODE_GOOD);
ck_assert_ptr_eq(v.type, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]);
ck_assert_uint_eq(v.arrayLength, 2);
UA_ExtensionObject *eo = (UA_ExtensionObject*)v.data;
ck_assert_uint_eq(eo[0].encoding, UA_EXTENSIONOBJECT_ENCODED_BYTESTRING);
ck_assert_uint_eq(eo[1].encoding, UA_EXTENSIONOBJECT_ENCODED_BYTESTRING);
ck_assert_uint_eq(eo[0].content.encoded.body.data[0], 5);
ck_assert_uint_eq(eo[1].content.encoded.body.data[0], 0);
UA_Variant_clear(&v);
data[4] = 0x16;
input.data = data + 4;
input.length = 9;
ck_assert_uint_eq(UA_decodeBinary(&input, &v, &UA_TYPES[UA_TYPES_VARIANT], NULL),
UA_STATUSCODE_GOOD);
ck_assert(UA_Variant_hasScalarType(&v, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]));
eo = (UA_ExtensionObject*)v.data;
ck_assert_uint_eq(eo->encoding, UA_EXTENSIONOBJECT_ENCODED_BYTESTRING);
ck_assert_uint_eq(eo->content.encoded.body.data[0], 5);
UA_Variant_clear(&v);
} END_TEST
START_TEST(UA_Variant_decodeReservedTypeIds) {
UA_Byte scalar[] = {0, 3, 0, 0, 0, 0x00, 0x80, 0xff};
UA_Byte nullScalar[] = {0, 0xff, 0xff, 0xff, 0xff};
UA_Byte emptyScalar[] = {0, 0, 0, 0, 0};
UA_Byte nullArray[] = {0x80, 0xff, 0xff, 0xff, 0xff};
UA_Byte emptyArray[] = {0x80, 0, 0, 0, 0};
UA_Byte array[] = {0x80, 2, 0, 0, 0,
3, 0, 0, 0, 0x00, 0x80, 0xff,
1, 0, 0, 0, 0x05};
UA_Byte matrix[] = {0xc0, 2, 0, 0, 0,
3, 0, 0, 0, 0x00, 0x80, 0xff,
1, 0, 0, 0, 0x05,
2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0};
UA_ByteString inputs[] = {
{sizeof(scalar), scalar}, {sizeof(nullScalar), nullScalar},
{sizeof(emptyScalar), emptyScalar}, {sizeof(nullArray), nullArray},
{sizeof(emptyArray), emptyArray}, {sizeof(array), array},
{sizeof(matrix), matrix}
};
for(UA_Byte id = 26; id <= 31; id++) {
for(size_t i = 0; i < sizeof(inputs) / sizeof(inputs[0]); i++) {
UA_ByteString *input = &inputs[i];
UA_Byte flags = input->data[0] & 0xc0;
input->data[0] = flags | id;
UA_Variant v;
size_t offset = 0;
ck_assert_uint_eq(UA_decodeBinaryInternal(input, &offset, &v,
&UA_TYPES[UA_TYPES_VARIANT], NULL), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(offset, input->length);
ck_assert_ptr_eq(v.type, &UA_TYPES[UA_TYPES_BYTESTRING]);
ck_assert_int_eq(UA_Variant_isScalar(&v), flags == 0);
if(flags & 0x80)
ck_assert_uint_eq(v.arrayLength, i >= 5 ? 2 : 0);
if(flags & 0x40) {
ck_assert_uint_eq(v.arrayDimensionsSize, 2);
ck_assert_uint_eq(v.arrayDimensions[0], 1);
ck_assert_uint_eq(v.arrayDimensions[1], 2);
}
/* Preserve payload, null/empty distinctions and dimensions, but
* re-encode with the ordinary ByteString wire type id (15). */
UA_ByteString encoded = UA_BYTESTRING_NULL;
ck_assert_uint_eq(UA_encodeBinary(&v, &UA_TYPES[UA_TYPES_VARIANT],
&encoded, NULL), UA_STATUSCODE_GOOD);
input->data[0] = flags | 15;
ck_assert(UA_ByteString_equal(input, &encoded));
UA_ByteString_clear(&encoded);
UA_Variant_clear(&v);
}
}
} END_TEST
START_TEST(UA_Variant_decodeReservedTypeIdsMalformed) {
UA_Byte scalar[] = {0, 3, 0, 0, 0, 0x00, 0x80, 0xff};
UA_Byte matrix[] = {0xc0, 2, 0, 0, 0,
1, 0, 0, 0, 0x05, 1, 0, 0, 0, 0x06,
2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0};
UA_ByteString inputs[] = {{sizeof(scalar), scalar}, {sizeof(matrix), matrix}};
for(UA_Byte id = 26; id <= 31; id++) {
scalar[0] = id;
matrix[0] = 0xc0 | id;
for(size_t i = 0; i < sizeof(inputs) / sizeof(inputs[0]); i++) {
for(size_t length = 1; length < inputs[i].length; length++) {
UA_ByteString truncated = {length, inputs[i].data};
UA_Variant v;
ck_assert_uint_eq(UA_decodeBinary(&truncated, &v,
&UA_TYPES[UA_TYPES_VARIANT], NULL),
UA_STATUSCODE_BADDECODINGERROR);
UA_Variant_clear(&v);
}
}
}
/* Only ids 26..31 are normalized; higher ids still fail. */
for(UA_Byte id = 32; id <= 63; id++) {
scalar[0] = id;
UA_Variant v;
ck_assert_uint_eq(UA_decodeBinary(&inputs[0], &v,
&UA_TYPES[UA_TYPES_VARIANT], NULL),
UA_STATUSCODE_BADDECODINGERROR);
UA_Variant_clear(&v);
}
} END_TEST
START_TEST(UA_Variant_decodeScalarDimensions) {
/* Reject the illegal flag even without trailing bytes. */
UA_Byte data[] = {0x46, 0xff, 0xff, 0xff, 0xff, 0xde, 0xad, 0xbe, 0xef};
UA_ByteString input = {sizeof(data), data};
UA_Variant v;
for(size_t len = 5; len <= sizeof(data); len += 4) {
input.length = len;
ck_assert_uint_eq(UA_decodeBinary(&input, &v, &UA_TYPES[UA_TYPES_VARIANT], NULL),
UA_STATUSCODE_BADDECODINGERROR);
UA_Variant_clear(&v);
}
data[0] = 0x06;
input.length = 5;
ck_assert_uint_eq(UA_decodeBinary(&input, &v, &UA_TYPES[UA_TYPES_VARIANT], NULL),
UA_STATUSCODE_GOOD);
ck_assert_int_eq(*(UA_Int32*)v.data, -1);
UA_Variant_clear(&v);
} END_TEST
static Suite *testSuite_builtin(void) {
Suite *s = suite_create("Built-in Data Types 62541-6 Table 1");
@@ -1976,6 +2153,11 @@ static Suite *testSuite_builtin(void) {
tcase_add_test(tc_decode, UA_Variant_decodeWithArrayFlagSetShallSetVTAndAllocateMemoryForArray);
tcase_add_test(tc_decode, UA_Variant_decodeWithOutDeleteMembersShallFailInCheckMem);
tcase_add_test(tc_decode, UA_Variant_decodeWithTooSmallSourceShallReturnWithError);
tcase_add_test(tc_decode, UA_ExtensionObject_decodeNullEncodingId);
tcase_add_test(tc_decode, UA_Variant_decodeNullEncodingId);
tcase_add_test(tc_decode, UA_Variant_decodeReservedTypeIds);
tcase_add_test(tc_decode, UA_Variant_decodeReservedTypeIdsMalformed);
tcase_add_test(tc_decode, UA_Variant_decodeScalarDimensions);
suite_add_tcase(s, tc_decode);
TCase *tc_encode = tcase_create("encode");
+66
View File
@@ -1384,6 +1384,71 @@ START_TEST(Client_subscription_createDataChanges_async) {
}
END_TEST
typedef struct {
UA_UInt32 requestId;
UA_UInt32 requestHandle;
UA_Boolean received;
} CancelledPublish;
static void
cancelledPublishCallback(UA_Client *client, void *userdata,
UA_UInt32 requestId, void *response) {
CancelledPublish *expected = (CancelledPublish*)userdata;
UA_PublishResponse *pr = (UA_PublishResponse*)response;
ck_assert(!expected->received);
ck_assert_uint_eq(requestId, expected->requestId);
ck_assert_uint_eq(pr->responseHeader.requestHandle, expected->requestHandle);
ck_assert_uint_eq(pr->responseHeader.serviceResult,
UA_STATUSCODE_BADREQUESTCANCELLEDBYCLIENT);
expected->received = true;
}
START_TEST(Client_cancelQueuedPublish) {
UA_Client *client = UA_Client_newForUnitTest();
UA_Client_getConfig(client)->outStandingPublishRequests = 0;
ck_assert_uint_eq(UA_Client_connect(client, "opc.tcp://localhost:4840"),
UA_STATUSCODE_GOOD);
UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
UA_CreateSubscriptionResponse response =
UA_Client_Subscriptions_create(client, request, NULL, NULL, NULL);
ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
UA_CreateSubscriptionResponse_clear(&response);
/* Interleave matching and non-matching handles to exercise removal from
* both the head and the tail, while preserving the middle request. */
CancelledPublish pending[3] = {{0, 1234, false}, {0, 5678, false}, {0, 1234, false}};
for(size_t i = 0; i < 3; i++) {
UA_PublishRequest pr;
UA_PublishRequest_init(&pr);
pr.requestHeader.requestHandle = pending[i].requestHandle;
pr.requestHeader.timeoutHint = 60000;
ck_assert_uint_eq(__UA_Client_AsyncService(
client, &pr, &UA_TYPES[UA_TYPES_PUBLISHREQUEST], cancelledPublishCallback,
&UA_TYPES[UA_TYPES_PUBLISHRESPONSE], &pending[i], &pending[i].requestId),
UA_STATUSCODE_GOOD);
}
UA_UInt32 count = 0;
ck_assert_uint_eq(UA_Client_cancelByRequestHandle(client, 1234, &count),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(count, 2);
ck_assert(pending[0].received);
ck_assert(!pending[1].received);
ck_assert(pending[2].received);
ck_assert_uint_eq(UA_Client_cancelByRequestHandle(client, 5678, &count),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(count, 1);
ck_assert(pending[1].received);
ck_assert_uint_eq(UA_Client_cancelByRequestHandle(client, 1234, &count),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(count, 0);
UA_Client_disconnect(client);
UA_Client_delete(client);
} END_TEST
START_TEST(Client_subscription_keepAlive) {
UA_Client *client = UA_Client_newForUnitTest();
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
@@ -2680,6 +2745,7 @@ static Suite* testSuite_Client(void) {
tcase_add_test(tc_client, Client_subscription_createDataChanges_negativeInterval);
tcase_add_test(tc_client, Client_subscription_modifyMonitoredItem);
tcase_add_test(tc_client, Client_subscription_createDataChanges_async);
tcase_add_test(tc_client, Client_cancelQueuedPublish);
tcase_add_test(tc_client, Client_subscription_keepAlive);
tcase_add_test(tc_client, Client_subscription_priority);
tcase_add_test(tc_client, Client_subscription_without_notification);
+25
View File
@@ -397,6 +397,30 @@ START_TEST(InvalidSignature) {
}
END_TEST
START_TEST(ShortSignedNetworkMessage) {
UA_FieldMetaData *fields = newReaderGroupWithSecurity(
UA_MESSAGESECURITYMODE_SIGNANDENCRYPT);
UA_PubSubManager *psm = getPSM(server);
UA_ReaderGroup *rg = UA_ReaderGroup_find(psm, readerGroupId);
ck_assert_ptr_ne(rg, NULL);
UA_ByteString buffer;
buffer.length = (sizeof(MSG_HEADER) - 1) / 2;
buffer.data = hexstr_to_char(MSG_HEADER);
UA_NetworkMessage msg;
memset(&msg, 0, sizeof(UA_NetworkMessage));
UA_StatusCode rv = UA_ReaderGroup_decodeNetworkMessage(psm, rg, buffer, &msg);
ck_assert(rv == UA_STATUSCODE_BADSECURITYCHECKSFAILED);
UA_NetworkMessage_clear(&msg);
UA_free(fields);
free(buffer.data);
}
END_TEST
START_TEST(InvalidSecurityModeInsufficientSig) {
UA_FieldMetaData *fields = newReaderGroupWithSecurity(UA_MESSAGESECURITYMODE_NONE);
UA_PubSubManager *psm = getPSM(server);
@@ -486,6 +510,7 @@ main(void) {
TCase *tc_pubsub_subscribe_invalid_sig = tcase_create("PubSub Subscribe Invalid Signature");
tcase_add_checked_fixture(tc_pubsub_subscribe_invalid_sig, setup, teardown);
tcase_add_test(tc_pubsub_subscribe_invalid_sig, InvalidSignature);
tcase_add_test(tc_pubsub_subscribe_invalid_sig, ShortSignedNetworkMessage);
TCase *tc_pubsub_subscribe_invalid_securitymode =
tcase_create("PubSub Subscribe Invalid SecurityMode");
+96
View File
@@ -9,10 +9,13 @@
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/types.h>
#include <open62541/transport_generated.h>
#include "server/ua_server_internal.h"
#include "server/ua_services.h"
#include "test_helpers.h"
#include "ua_types_encoding_binary.h"
#include "testing_networklayers.h"
#include <stdio.h>
#include <stdlib.h>
@@ -662,6 +665,97 @@ START_TEST(checkCallbackManagement) {
ck_assert_int_eq(ret, UA_STATUSCODE_GOOD);
} END_TEST
static unsigned closeCount;
static UA_StatusCode
closeConnection(UA_ConnectionManager *cm, uintptr_t connectionId) {
(void)cm;
ck_assert_uint_eq(connectionId, 2);
closeCount++;
return UA_STATUSCODE_GOOD;
}
static void
checkHello(size_t urlLength, size_t trailing, UA_UInt32 version, UA_StatusCode expected) {
UA_ConnectionManager *cm = TestConnectionManager_new("tcp", NULL);
ck_assert_ptr_ne(cm, NULL);
cm->eventSource.eventLoop = UA_Server_getConfig(server)->eventLoop;
cm->closeConnection = closeConnection;
UA_ServerComponent *bpm = getServerComponentByName(server, UA_STRING("binary"));
ck_assert_ptr_ne(bpm, NULL);
closeCount = 0;
void *listener = NULL;
serverNetworkCallback(cm, 1, bpm, &listener, UA_CONNECTIONSTATE_ESTABLISHED,
&UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
ck_assert_ptr_ne(listener, NULL);
UA_TcpHelloMessage hello;
UA_TcpHelloMessage_init(&hello);
hello.protocolVersion = version;
hello.receiveBufferSize = hello.sendBufferSize = 65536;
ck_assert_uint_eq(UA_ByteString_allocBuffer(&hello.endpointUrl, urlLength),
UA_STATUSCODE_GOOD);
memset(hello.endpointUrl.data, 'x', urlLength);
UA_ByteString body = UA_BYTESTRING_NULL;
ck_assert_uint_eq(UA_encodeBinary(&hello, &UA_TRANSPORT[UA_TRANSPORT_TCPHELLOMESSAGE],
&body, NULL), UA_STATUSCODE_GOOD);
UA_TcpHelloMessage_clear(&hello);
UA_ByteString frame;
ck_assert_uint_eq(UA_ByteString_allocBuffer(&frame, 8 + body.length + trailing),
UA_STATUSCODE_GOOD);
UA_TcpMessageHeader header = {UA_MESSAGETYPE_HEL + UA_CHUNKTYPE_FINAL,
(UA_UInt32)frame.length};
UA_ByteString headerBuf = {8, frame.data};
ck_assert_uint_eq(UA_encodeBinary(&header, &UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER],
&headerBuf, NULL), UA_STATUSCODE_GOOD);
memcpy(frame.data + 8, body.data, body.length);
memset(frame.data + 8 + body.length, 0xab, trailing);
UA_ByteString_clear(&body);
void *connection = listener;
serverNetworkCallback(cm, 2, bpm, &connection, UA_CONNECTIONSTATE_ESTABLISHED,
&UA_KEYVALUEMAP_NULL, frame);
const UA_ByteString *response = TestConnectionManager_getLastSent(cm);
size_t offset = 0;
ck_assert_uint_eq(UA_decodeBinaryInternal(response, &offset, &header,
&UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], NULL), UA_STATUSCODE_GOOD);
if(expected == UA_STATUSCODE_GOOD) {
ck_assert_uint_eq(header.messageTypeAndChunkType, UA_MESSAGETYPE_ACK + UA_CHUNKTYPE_FINAL);
UA_TcpAcknowledgeMessage ack;
ck_assert_uint_eq(UA_decodeBinaryInternal(response, &offset, &ack,
&UA_TRANSPORT[UA_TRANSPORT_TCPACKNOWLEDGEMESSAGE], NULL), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(ack.protocolVersion, 0);
ck_assert_uint_eq(closeCount, 0);
} else {
ck_assert_uint_eq(header.messageTypeAndChunkType, UA_MESSAGETYPE_ERR + UA_CHUNKTYPE_FINAL);
UA_TcpErrorMessage error;
ck_assert_uint_eq(UA_decodeBinaryInternal(response, &offset, &error,
&UA_TRANSPORT[UA_TRANSPORT_TCPERRORMESSAGE], NULL), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(error.error, expected);
ck_assert_uint_eq(closeCount, 1);
UA_TcpErrorMessage_clear(&error);
}
serverNetworkCallback(cm, 2, bpm, &connection, UA_CONNECTIONSTATE_CLOSING,
&UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
serverNetworkCallback(cm, 1, bpm, &listener, UA_CONNECTIONSTATE_CLOSING,
&UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
cm->eventSource.free(&cm->eventSource);
UA_ByteString_clear(&frame);
}
START_TEST(helloEndpointUrlLimit) {
checkHello(32, 0, 0, UA_STATUSCODE_GOOD);
checkHello(4095, 0, 0, UA_STATUSCODE_GOOD);
checkHello(4096, 0, 0, UA_STATUSCODE_BADTCPENDPOINTURLINVALID);
checkHello(4097, 0, 0, UA_STATUSCODE_BADTCPENDPOINTURLINVALID);
checkHello(5000, 0, 0, UA_STATUSCODE_BADTCPENDPOINTURLINVALID);
checkHello(32, 0, 0xdeadbeef, UA_STATUSCODE_GOOD);
} END_TEST
START_TEST(helloTrailingData) {
checkHello(32, 1, 0, UA_STATUSCODE_BADDECODINGERROR);
checkHello(32, 8, 0, UA_STATUSCODE_BADDECODINGERROR);
} END_TEST
int main(void) {
Suite *s = suite_create("server");
@@ -671,6 +765,8 @@ int main(void) {
tcase_add_test(tc_call, checkGetNamespaceByName);
tcase_add_test(tc_call, checkGetNamespaceById);
tcase_add_test(tc_call, checkServer_run);
tcase_add_test(tc_call, helloEndpointUrlLimit);
tcase_add_test(tc_call, helloTrailingData);
suite_add_tcase(s, tc_call);
TCase *tc_ext = tcase_create("server - extended");
+88
View File
@@ -1769,6 +1769,89 @@ START_TEST(ReadSingleAttributeIsAbstractOnNonAbstract) {
ck_assert_uint_ne(retval, UA_STATUSCODE_GOOD);
} END_TEST
/* Records the attribute mask of the first getNode call, which is the call
* that looks up the node of the Read operation */
static const UA_Node *
(*originalGetNode)(UA_Nodestore *ns, const UA_NodeId *nodeId,
UA_UInt32 attributeMask, UA_ReferenceTypeSet references,
UA_BrowseDirection referenceDirections);
static UA_Boolean attributeMaskRecorded;
static UA_UInt32 recordedAttributeMask;
static const UA_Node *
recordAttributeMaskGetNode(UA_Nodestore *ns, const UA_NodeId *nodeId,
UA_UInt32 attributeMask,
UA_ReferenceTypeSet references,
UA_BrowseDirection referenceDirections) {
if(!attributeMaskRecorded) {
recordedAttributeMask = attributeMask;
attributeMaskRecorded = true;
}
return originalGetNode(ns, nodeId, attributeMask, references,
referenceDirections);
}
/* The Read service tells the Nodestore which attribute is read, so that a
* Nodestore can return a node with only that attribute */
START_TEST(ReadRequestsAttributeFromNodestore) {
static const struct {
UA_UInt32 attributeId;
UA_UInt32 attributeMask;
} expected[] = {
{UA_ATTRIBUTEID_NODEID, UA_NODEATTRIBUTESMASK_NODEID},
{UA_ATTRIBUTEID_NODECLASS, UA_NODEATTRIBUTESMASK_NODECLASS},
{UA_ATTRIBUTEID_BROWSENAME, UA_NODEATTRIBUTESMASK_BROWSENAME},
{UA_ATTRIBUTEID_DISPLAYNAME, UA_NODEATTRIBUTESMASK_DISPLAYNAME},
{UA_ATTRIBUTEID_DESCRIPTION, UA_NODEATTRIBUTESMASK_DESCRIPTION},
{UA_ATTRIBUTEID_WRITEMASK, UA_NODEATTRIBUTESMASK_WRITEMASK},
{UA_ATTRIBUTEID_USERWRITEMASK, UA_NODEATTRIBUTESMASK_USERWRITEMASK},
{UA_ATTRIBUTEID_ISABSTRACT, UA_NODEATTRIBUTESMASK_ISABSTRACT},
{UA_ATTRIBUTEID_SYMMETRIC, UA_NODEATTRIBUTESMASK_SYMMETRIC},
{UA_ATTRIBUTEID_INVERSENAME, UA_NODEATTRIBUTESMASK_INVERSENAME},
{UA_ATTRIBUTEID_CONTAINSNOLOOPS, UA_NODEATTRIBUTESMASK_CONTAINSNOLOOPS},
{UA_ATTRIBUTEID_EVENTNOTIFIER, UA_NODEATTRIBUTESMASK_EVENTNOTIFIER},
{UA_ATTRIBUTEID_VALUE, UA_NODEATTRIBUTESMASK_VALUE},
{UA_ATTRIBUTEID_DATATYPE, UA_NODEATTRIBUTESMASK_DATATYPE},
{UA_ATTRIBUTEID_VALUERANK, UA_NODEATTRIBUTESMASK_VALUERANK},
{UA_ATTRIBUTEID_ARRAYDIMENSIONS, UA_NODEATTRIBUTESMASK_ARRAYDIMENSIONS},
{UA_ATTRIBUTEID_ACCESSLEVEL, UA_NODEATTRIBUTESMASK_ACCESSLEVEL},
{UA_ATTRIBUTEID_USERACCESSLEVEL, UA_NODEATTRIBUTESMASK_USERACCESSLEVEL},
{UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL,
UA_NODEATTRIBUTESMASK_MINIMUMSAMPLINGINTERVAL},
{UA_ATTRIBUTEID_HISTORIZING, UA_NODEATTRIBUTESMASK_HISTORIZING},
{UA_ATTRIBUTEID_EXECUTABLE, UA_NODEATTRIBUTESMASK_EXECUTABLE},
{UA_ATTRIBUTEID_USEREXECUTABLE, UA_NODEATTRIBUTESMASK_USEREXECUTABLE},
{UA_ATTRIBUTEID_DATATYPEDEFINITION, UA_NODEATTRIBUTESMASK_DATATYPEDEFINITION},
{UA_ATTRIBUTEID_ROLEPERMISSIONS, UA_NODEATTRIBUTESMASK_ROLEPERMISSIONS},
/* In 1.5 these derived attributes use the underlying stored fields. */
{UA_ATTRIBUTEID_USERROLEPERMISSIONS, UA_NODEATTRIBUTESMASK_ROLEPERMISSIONS},
{UA_ATTRIBUTEID_ACCESSRESTRICTIONS, UA_NODEATTRIBUTESMASK_ACCESSRESTRICTIONS},
{UA_ATTRIBUTEID_ACCESSLEVELEX, UA_NODEATTRIBUTESMASK_ACCESSLEVEL},
{UA_ATTRIBUTEID_INVALID, UA_NODEATTRIBUTESMASK_NONE},
{28, UA_NODEATTRIBUTESMASK_NONE},
{0x80000000u, UA_NODEATTRIBUTESMASK_NONE},
{0xffffffffu, UA_NODEATTRIBUTESMASK_NONE}
};
UA_Nodestore *ns = UA_Server_getConfig(server)->nodestore;
originalGetNode = ns->getNode;
ns->getNode = recordAttributeMaskGetNode;
UA_ReadValueId rvi;
UA_ReadValueId_init(&rvi);
rvi.nodeId = UA_NODEID_STRING(1, "the.answer");
for(size_t i = 0; i < sizeof(expected) / sizeof(expected[0]); i++) {
rvi.attributeId = expected[i].attributeId;
attributeMaskRecorded = false;
UA_DataValue dv = UA_Server_read(server, &rvi, UA_TIMESTAMPSTORETURN_NEITHER);
UA_DataValue_clear(&dv);
ck_assert(attributeMaskRecorded);
ck_assert_uint_eq(recordedAttributeMask, expected[i].attributeMask);
}
ns->getNode = originalGetNode;
} END_TEST
static Suite * testSuite_services_attributes(void) {
Suite *s = suite_create("services_attributes_read");
@@ -1894,6 +1977,11 @@ static Suite * testSuite_services_attributes(void) {
tcase_add_test(tc_attr_edge, ReadSingleAttributeIsAbstractOnNonAbstract);
suite_add_tcase(s, tc_attr_edge);
TCase *tc_attributeMask = tcase_create("attributeMask");
tcase_add_checked_fixture(tc_attributeMask, setup, teardown);
tcase_add_test(tc_attributeMask, ReadRequestsAttributeFromNodestore);
suite_add_tcase(s, tc_attributeMask);
return s;
}