open62541/tests/pubsub/check_pubsub_sks_push.c
Muddasir shakil b457206d24
feat(pubsub): KeyStorage and SetSecurityKeys (#5243)
* Add UA_ENABLE_PUBSUB_SKS Cmake option

The SKS related features are enabled in open62541. The UA_ENABLE_PUBSUB_SKS
cmake option adds the support to enable/disable SKS related features.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add securityGroupId parameter to PubSub Group Config

The PubSub groups (Reader/Writer) are associated to a security group by
securityGroupId. The securityGroupId parameter is added to UA_WriterGroupConfig
and UA_ReaderGroupConfig structs.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add UA_PubSubKeyListItem struct

The Keystorage contains a linked list of KeyItems. The KeyItems holds the
information related to a key. The UA_PubSubKeyListItem struct forms the keyItems
of the keystorage KeyList.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add UA_PubSubKeyStorage struct

A structured storage is required to store all the keys and related information,
such as which security group, what security policy, how many PubSub Groups, and
when to move to next keys. The UA_PubSubKeyStorage struct holds the list of keys
used to secure the pubsub message for a security group and related information.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add PubSubKeyStorageList to the pubSubManager

The KeyStorage is managed by the pubSubManager object. There can be multiple key
storages managed by a pubSubManager for different Security Groups. An
UA_PubSubKeyStorage List is added to the pubSubManager, which contains the
pointers to all the key Storages managed by the pubSubManager for different
Security Groups.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add KeyStorage pointer to ReaderGroup and WriterGroup struct

A reference to a Keystorage is created when a ReaderGroup or WriterGroup group
is created. This reference is used for the life cycle management of the
KeyStorage. A non-owning pointer is added to WriterGroup and ReaderGroup structs
to point to their associated keystorage in the server.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add function to initialize a keyStorage

An empty keystorage list is created when a ReaderGroup or a WriterGroup is
created in server. The new KeyStorage is added to the server KeyList and
initialized for a securityGroup. If a KeyStorage already exists in the server
for a securityGroupId, then the referenceCount is incremented and the keyStorage
is added to the initializing ReaderGroup or WriterGroup. In case of failure,
the keystorage is cleaned and deleted.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add storeSecurityKey function

After the keystorage is initialized and added to the server keyList, the
keys should be stored in the keystorage keylist. the storeSecurityKeys function
takes current key and future key list and add it a KeyStorage in the server.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add Mechanism for key Rollover after KeyLifeTime expires

After the KeyLifeTime expires the Publisher and Subscriber are required to
move to the next key in the existing list. The addMoveToNextKeyCallback function
calculates the time to trigger the callback function and adds a timed server
callback. The importKeyToChannelContext function takes the key material,
divides the key into singing, encrypting and keyNonce part according to security
Policy assigned to KeyStorage and adds them to the channelcontext of the PubSub
Group. The moveToKeyCallback is the callback function to set the keys and
add next callback for key Rollover.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add API function update the existing keyStorage

The existing KeyList is updated/replaced with the fetched list. If the
currentTokenId is unknown in the existing list, then the existing keylist is
replaced by the fetched KeyList. In othercases keystorage is updated and keylist
is extended with new future keys.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add removeKeyStorage function

A keyStorage can be referenced by multiple PubSub Groups in a server. When a
keyStorage is removed with removeKeyStorage function, it checks the number of
of referenceCount parameter and decrements it by 1. If referenceCount was 1, then
the keyStorage pointer is freed and removed from the server.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add keyStorage in Server with ReaderGroup

Each ReaderGroup is assocaited with a keyStorage. The keys are used in its
channel context to secure the messages. A keyStorage is added/updated when
a ReaderGroup is created in the server.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add KeyStorage in server with WriterGroup

Each WriterGroup is assocaited with a keyStorage. The keys are used in its
channel context to secure the messages. A keyStorage is added/updated when
a WriterGroup is created in the server.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Remove KeyStorage from server with ReaderGroup

Each ReaderGroup is assocaited with a keyStorage. The keys are used in its
channel context to secure the messages. A keyStorage is removed/updated when
a ReaderGroup is removed from the server.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Remove KeyStorage from server with a WriterGroup

Each WriterGroup is assocaited with a keyStorage. The keys are used in its
channel context to secure the messages. A keyStorage is removed/updated when
a WriterGroup is removed in the server.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Delete All KeyStorages from Server

When PubSub Configuration is deleted, all the nested configuration and members
are deleted from the server. The KeyStorages are also deleted when a
PubSubManager is deleted, because KeyStorages does not serve the purpose anymore.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add KeyStorage Test setup and unit tests

A test suite is added for the keystorage and the following using tests are added
to it
	- Initialization of empty keystorage
	- Initialization of keystorage with invalid PubSub security policy
	- storing keys into empty keystorage
	- move to next key after keylifetime expire
	- Setting keys to the channel context
	- Adding keyStorage with Writer and ReaderGroup
	- Adding a Reader/WriterGroup to existing Keystorage
	- Removing keyStorage with a Writer/ReaderGroup

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add setSecurityKeys Method Node Callback

The keys are pushed to the Publishers and Subscribers who do not have client
functionality using setSecurityKeys Method Node. The setSecurityKeys callback
implements the backend of the setSecurityKeys Method node exposed by the server.
Checking for user credentials against PubSub Security Group Object nodes
is unresolved becuase creation of Security Group Object is not implemented.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add sks push test setup and unit tests

Setups the PubSub SKS Push test suite and helper methods. The test suite
includes a server with encryption and a client to connect with the server
with encrypted channel. The following unit tests are added
	- insufficient Security Mode
	- MissingSecurityGroup
	- Setting Security Keys
	- Update CurrentKey from exisiting list
	- Update CurrentKey and add new future keys
	- Replace existing keys with new keys

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Add PubSubKeyStorage description

The PubSubKeyStorage description is added to the header file. It also includes
the implemented workflow of the KeyStorage API to interact with KeyStorage.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

* Integrate PubSub SKS build and unit test in CI

The unit tests should be intergerated in the CI of open62541 pipeline to provide
proper testing and integration support of the new feature. The  build and unit
tests of PubSub SKS are integrated in the CI pipeline.

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>

Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>
2022-10-17 20:55:11 +02:00

397 lines
16 KiB
C

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright (c) 2022 Linutronix GmbH (Author: Muddasir Shakil)
*/
#include <open62541/plugin/pubsub_udp.h>
#include <open62541/plugin/securitypolicy_default.h>
#include <open62541/server_pubsub.h>
#include <open62541/server_config_default.h>
#include <open62541/client.h>
#include <open62541/client_config_default.h>
#include <open62541/client_highlevel.h>
#include "../encryption/certificates.h"
#include "ua_pubsub.h"
#include "ua_pubsub_keystorage.h"
#include "ua_server_internal.h"
#include <check.h>
#include "thread_wrapper.h"
#define UA_PUBSUB_KEYMATERIAL_NONCELENGTH 32
UA_Server *server = NULL;
UA_ByteString currentKey;
UA_ByteString *futureKey = NULL;
UA_String securityGroupId;
UA_NodeId connection, writerGroup, readerGroup, publishedDataSet, dataSetWriter;
UA_Boolean running;
THREAD_HANDLE server_thread;
THREAD_CALLBACK(serverloop) {
while(running)
UA_Server_run_iterate(server, true);
return 0;
}
static UA_StatusCode
generateKeyData(const UA_PubSubSecurityPolicy *policy, UA_ByteString *key) {
if(!key || !policy)
return UA_STATUSCODE_BADINVALIDARGUMENT;
UA_StatusCode retVal;
UA_Byte secretBytes[UA_PUBSUB_KEYMATERIAL_NONCELENGTH];
UA_ByteString secret;
secret.length = UA_PUBSUB_KEYMATERIAL_NONCELENGTH;
secret.data = secretBytes;
UA_Byte seedBytes[UA_PUBSUB_KEYMATERIAL_NONCELENGTH];
UA_ByteString seed;
seed.data = seedBytes;
seed.length = UA_PUBSUB_KEYMATERIAL_NONCELENGTH;
retVal = policy->symmetricModule.generateNonce(policy->policyContext, &secret);
retVal |= policy->symmetricModule.generateNonce(policy->policyContext, &seed);
if(retVal != UA_STATUSCODE_GOOD)
return retVal;
retVal =
policy->symmetricModule.generateKey(policy->policyContext, &secret, &seed, key);
return retVal;
}
static UA_StatusCode
encyrptedclientconnect(UA_Client *client) {
UA_ByteString *trustList = NULL;
size_t trustListSize = 0;
UA_ByteString *revocationList = NULL;
size_t revocationListSize = 0;
/* Load certificate and private key */
UA_ByteString certificate;
certificate.length = CERT_DER_LENGTH;
certificate.data = CERT_DER_DATA;
ck_assert_uint_ne(certificate.length, 0);
UA_ByteString privateKey;
privateKey.length = KEY_DER_LENGTH;
privateKey.data = KEY_DER_DATA;
ck_assert_uint_ne(privateKey.length, 0);
/* Secure client initialization */
UA_ClientConfig *cc = UA_Client_getConfig(client);
cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey, trustList,
trustListSize, revocationList,
revocationListSize);
cc->securityPolicyUri =
UA_STRING_ALLOC("http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256");
ck_assert(client != NULL);
/* Secure client connect */
return UA_Client_connect(client, "opc.tcp://localhost:4840");
}
static UA_StatusCode
callSetSecurityKey(UA_Client *client, UA_String pSecurityGroupId, UA_UInt32 currentTokenId, UA_UInt32 futureKeySize){
UA_NodeId parentId = UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE);
UA_NodeId methodId = UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE_SETSECURITYKEYS);
size_t inputSize = 7;
UA_Variant inputs[7];
size_t outputSize;
UA_Variant *output;
UA_Variant_setScalar(&inputs[0], &pSecurityGroupId, &UA_TYPES[UA_TYPES_STRING]);
UA_String policUri =
UA_STRING("http://opcfoundation.org/UA/SecurityPolicy#PubSub-Aes256-CTR");
UA_Variant_setScalar(&inputs[1], &policUri, &UA_TYPES[UA_TYPES_STRING]);
UA_Variant_setScalar(&inputs[2], &currentTokenId, &UA_TYPES[UA_TYPES_UINT32]);
size_t keyLength = server->config.pubSubConfig.securityPolicies->symmetricModule.secureChannelNonceLength;
UA_ByteString_allocBuffer(&currentKey, keyLength);
generateKeyData(server->config.pubSubConfig.securityPolicies, &currentKey);
UA_Variant_setScalar(&inputs[3], &currentKey, &UA_TYPES[UA_TYPES_BYTESTRING]);
futureKey = (UA_ByteString *)UA_calloc(futureKeySize, sizeof(UA_ByteString));
for (size_t i = 0; i < futureKeySize; i++) {
UA_ByteString_allocBuffer(&futureKey[i], keyLength);
generateKeyData(server->config.pubSubConfig.securityPolicies, &futureKey[i]);
}
UA_Variant_setArrayCopy(&inputs[4], futureKey, futureKeySize, &UA_TYPES[UA_TYPES_BYTESTRING]);
UA_Duration msTimeNextKey = 0;
UA_Variant_setScalar(&inputs[5], &msTimeNextKey, &UA_TYPES[UA_TYPES_DURATION]);
UA_Duration mskeyLifeTime = 2000;
UA_Variant_setScalar(&inputs[6], &mskeyLifeTime, &UA_TYPES[UA_TYPES_DURATION]);
return UA_Client_call(client,parentId, methodId, inputSize, inputs, &outputSize, &output);
}
static void
addTestWriterGroup(UA_String securitygroupId) {
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(writerGroupConfig));
writerGroupConfig.name = UA_STRING("WriterGroup");
writerGroupConfig.publishingInterval = 100;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
writerGroupConfig.securityGroupId = securitygroupId;
writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
retval |=
UA_Server_addWriterGroup(server, connection, &writerGroupConfig, &writerGroup);
UA_Server_setWriterGroupOperational(server, writerGroup);
}
static void
addTestReaderGroup(UA_String securitygroupId) {
UA_ServerConfig *config = UA_Server_getConfig(server);
/* To check status after running both publisher and subscriber */
UA_StatusCode retVal = UA_STATUSCODE_GOOD;
/* Reader Group */
UA_ReaderGroupConfig readerGroupConfig;
memset(&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig));
readerGroupConfig.name = UA_STRING("ReaderGroup");
/* Reader Group Encryption settings */
readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
readerGroupConfig.securityGroupId = securitygroupId;
readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
retVal |=
UA_Server_addReaderGroup(server, connection, &readerGroupConfig, &readerGroup);
}
static void
setup(void) {
running = true;
/* Load certificate and private key */
UA_ByteString certificate;
certificate.length = CERT_DER_LENGTH;
certificate.data = CERT_DER_DATA;
UA_ByteString privateKey;
privateKey.length = KEY_DER_LENGTH;
privateKey.data = KEY_DER_DATA;
size_t trustListSize = 0;
UA_ByteString *trustList = NULL;
size_t issuerListSize = 0;
UA_ByteString *issuerList = NULL;
UA_ByteString *revocationList = NULL;
size_t revocationListSize = 0;
server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefaultWithSecurityPolicies(config, 4840, &certificate, &privateKey,
trustList, trustListSize,
issuerList, issuerListSize,
revocationList, revocationListSize);
/* Set the ApplicationUri used in the certificate */
UA_String_clear(&config->applicationDescription.applicationUri);
config->applicationDescription.applicationUri =
UA_STRING_ALLOC("urn:unconfigured:application");
UA_ServerConfig_addPubSubTransportLayer(config, UA_PubSubTransportLayerUDPMP());
config->pubSubConfig.securityPolicies =
(UA_PubSubSecurityPolicy *)UA_malloc(sizeof(UA_PubSubSecurityPolicy));
config->pubSubConfig.securityPoliciesSize = 1;
UA_PubSubSecurityPolicy_Aes256Ctr(config->pubSubConfig.securityPolicies,
&config->logger);
UA_Server_run_startup(server);
// add 2 connections
UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig));
connectionConfig.name = UA_STRING("UADP Connection");
UA_NetworkAddressUrlDataType networkAddressUrl = {
UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")};
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.transportProfileUri =
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
UA_Server_addPubSubConnection(server, &connectionConfig, &connection);
securityGroupId = UA_STRING("TestSecurityGroup");
addTestReaderGroup(securityGroupId);
addTestWriterGroup(securityGroupId);
UA_Server_run_startup(server);
THREAD_CREATE(server_thread, serverloop);
}
static void
teardown(void) {
running = false;
THREAD_JOIN(server_thread);
UA_Server_run_shutdown(server);
UA_Server_delete(server);
if(futureKey) {
UA_free(futureKey);
futureKey = NULL;
}
}
START_TEST(TestSetSecurityKeys_InsufficientSecurityMode) {
UA_Client *client = UA_Client_new();
UA_ClientConfig_setDefault(UA_Client_getConfig(client));
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
if(retval != UA_STATUSCODE_GOOD) {
UA_Client_delete(client);
}
retval = callSetSecurityKey(client, securityGroupId, 1, 2);
ck_assert_msg(retval == UA_STATUSCODE_BADSECURITYMODEINSUFFICIENT, "Expected BAD_SECURITYMODEINSUFFICIENT but erorr code : %s \n", UA_StatusCode_name(retval));
ck_assert_uint_eq(retval, UA_STATUSCODE_BADSECURITYMODEINSUFFICIENT);
} END_TEST
START_TEST(TestSetSecurityKeys_MissingSecurityGroup) {
UA_Client *client = UA_Client_new();
UA_StatusCode retval = encyrptedclientconnect(client);
UA_String wrongSecurityGroupId = UA_STRING("WrongSecurityGroupId");
retval = callSetSecurityKey(client, wrongSecurityGroupId, 1, 2);
ck_assert_msg(retval == UA_STATUSCODE_BADNOTFOUND, "Expected BAD_BADNOTFOUND but erorr code : %s \n", UA_StatusCode_name(retval));
} END_TEST
START_TEST(TestSetSecurityKeys_GOOD) {
UA_Client *client = UA_Client_new();
UA_UInt32 futureKeySize = 2;
UA_UInt32 currentTokenId = 1;
UA_UInt32 startingTokenId = 1;
UA_StatusCode retval = encyrptedclientconnect(client);
UA_PubSubKeyStorage *ks = UA_Server_findKeyStorage(server, securityGroupId);
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
ck_assert_uint_eq(ks->currentItem->keyID, currentTokenId);
startingTokenId = currentTokenId;
UA_PubSubKeyListItem *iterator = TAILQ_FIRST(&ks->keyList);
for (size_t i = 0; i < futureKeySize + 1; i++) {
ck_assert_ptr_ne(iterator, NULL);
ck_assert_uint_eq(iterator->keyID, startingTokenId);
iterator = TAILQ_NEXT(iterator, keyListEntry);
startingTokenId++;
}
ck_assert_uint_eq(ks->keyListSize, futureKeySize+1);
} END_TEST
START_TEST(TestSetSecurityKeys_UpdateCurrentKeyFromExistingList){
UA_Client *client = UA_Client_new();
UA_UInt32 futureKeySize = 2;
UA_UInt32 currentTokenId = 1;
UA_StatusCode retval = encyrptedclientconnect(client);
UA_PubSubKeyStorage *ks = UA_Server_findKeyStorage(server, securityGroupId);
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
futureKeySize = 0;
currentTokenId = 3;
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
ck_assert_uint_eq(ks->currentItem->keyID, currentTokenId);
} END_TEST
START_TEST(TestSetSecurityKeys_UpdateCurrentKeyFromExistingListAndAddNewFutureKeys){
UA_Client *client = UA_Client_new();
UA_UInt32 futureKeySize = 2;
UA_UInt32 currentTokenId = 1;
UA_UInt32 startingTokenId = 1;
size_t keyListSize;
UA_StatusCode retval = encyrptedclientconnect(client);
UA_PubSubKeyStorage *ks = UA_Server_findKeyStorage(server, securityGroupId);
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
keyListSize = ks->keyListSize;
futureKeySize = 3;
currentTokenId = 2;
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
ck_assert_uint_eq(ks->currentItem->keyID, currentTokenId);
/*After updating: KeyListSize = Pervious KeyListSize + FutureKeySize - Duplicated Keys with matching KeyID */
ck_assert_uint_eq(keyListSize + futureKeySize - 1, ks->keyListSize);
UA_PubSubKeyListItem *iterator = TAILQ_FIRST(&ks->keyList);
for (size_t i = 0; i < ks->keyListSize; i++) {
ck_assert_ptr_ne(iterator, NULL);
ck_assert_uint_eq(iterator->keyID, startingTokenId);
iterator = TAILQ_NEXT(iterator, keyListEntry);
startingTokenId++;
}
} END_TEST
START_TEST(TestSetSecurityKeys_ReplaceExistingKeyListWithFetchedKeyList){
UA_Client *client = UA_Client_new();
UA_UInt32 futureKeySize = 2;
UA_UInt32 currentTokenId = 1;
UA_UInt32 startingTokenId = 1;
UA_StatusCode retval = encyrptedclientconnect(client);
UA_PubSubKeyStorage *ks = UA_Server_findKeyStorage(server, securityGroupId);
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
futureKeySize = 3;
currentTokenId = 4;
startingTokenId = currentTokenId;
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
UA_PubSubKeyListItem *iterator = TAILQ_FIRST(&ks->keyList);
for (size_t i = 0; i < futureKeySize + 1; i++) {
ck_assert_ptr_ne(iterator, NULL);
ck_assert_uint_eq(iterator->keyID, startingTokenId);
iterator = TAILQ_NEXT(iterator, keyListEntry);
startingTokenId++;
}
ck_assert_uint_eq(ks->keyListSize, futureKeySize+1);
} END_TEST
int
main(void) {
int number_failed = 0;
TCase *tc_pubsub_sks_push = tcase_create("PubSub SKS Push");
tcase_add_checked_fixture(tc_pubsub_sks_push, setup, teardown);
tcase_add_test(tc_pubsub_sks_push, TestSetSecurityKeys_InsufficientSecurityMode);
tcase_add_test(tc_pubsub_sks_push, TestSetSecurityKeys_MissingSecurityGroup);
tcase_add_test(tc_pubsub_sks_push, TestSetSecurityKeys_GOOD);
tcase_add_test(tc_pubsub_sks_push, TestSetSecurityKeys_UpdateCurrentKeyFromExistingList);
tcase_add_test(tc_pubsub_sks_push, TestSetSecurityKeys_UpdateCurrentKeyFromExistingListAndAddNewFutureKeys);
tcase_add_test(tc_pubsub_sks_push, TestSetSecurityKeys_ReplaceExistingKeyListWithFetchedKeyList);
Suite *s = suite_create("PubSub SKS Push");
suite_add_tcase(s, tc_pubsub_sks_push);
SRunner *sr = srunner_create(s);
srunner_set_fork_status(sr, CK_NOFORK);
srunner_run_all(sr, CK_NORMAL);
number_failed = srunner_ntests_failed(sr);
srunner_free(sr);
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}