static void serverOnNetworkCallback(const UA_ServerOnNetwork *serverOnNetwork, UA_Boolean isServerAnnounce, UA_Boolean isTxtReceived, void *data) { if(discovery_url != NULL || !isServerAnnounce) { UA_LOG_DEBUG(logger, UA_LOGCATEGORY_SERVER, "serverOnNetworkCallback called, but discovery URL " "already initialized or is not announcing. Ignoring."); return; // we already have everything we need or we only want server announces } if(self_discovery_url != NULL && UA_String_equal(&serverOnNetwork->discoveryUrl, self_discovery_url)) { // skip self return; } if(!isTxtReceived) return; // we wait until the corresponding TXT record is announced. // Problem: how to handle if a Server does not announce the // optional TXT? // here you can filter for a specific LDS server, e.g. call FindServers on // the serverOnNetwork to make sure you are registering with the correct // LDS. We will ignore this for now UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "Another server announced itself on %.*s", (int)serverOnNetwork->discoveryUrl.length, serverOnNetwork->discoveryUrl.data); if(discovery_url != NULL) UA_free(discovery_url); discovery_url = (char*)UA_malloc(serverOnNetwork->discoveryUrl.length + 1); memcpy(discovery_url, serverOnNetwork->discoveryUrl.data, serverOnNetwork->discoveryUrl.length); discovery_url[serverOnNetwork->discoveryUrl.length] = 0; }
static void GetEndpointsAndCheck(const char* discoveryUrl, const char* filterTransportProfileUri, const UA_String expectedEndpointUrls[], size_t expectedEndpointUrlsSize) { UA_Client *client = UA_Client_new(); UA_ClientConfig_setDefault(UA_Client_getConfig(client)); ck_assert_uint_eq(UA_Client_connect(client, discoveryUrl), UA_STATUSCODE_GOOD); UA_EndpointDescription* endpointArray = NULL; size_t endpointArraySize = 0; UA_String discoveryUrlUA = UA_String_fromChars(discoveryUrl); UA_StatusCode retval = GetEndpoints(client, &discoveryUrlUA, &endpointArraySize, &endpointArray, filterTransportProfileUri); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); UA_String_deleteMembers(&discoveryUrlUA); ck_assert_uint_eq(endpointArraySize , expectedEndpointUrlsSize); for(size_t j = 0; j < endpointArraySize && j < expectedEndpointUrlsSize; j++) { UA_EndpointDescription* endpoint = &endpointArray[j]; ck_assert(UA_String_equal(&endpoint->endpointUrl, &expectedEndpointUrls[j])); } UA_Array_delete(endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); UA_Client_delete(client); }
END_TEST static void handler_events_propagate(UA_Client *lclient, UA_UInt32 subId, void *subContext, UA_UInt32 monId, void *monContext, size_t nEventFields, UA_Variant *eventFields) { UA_Boolean foundSeverity = UA_FALSE; UA_Boolean foundMessage = UA_FALSE; UA_Boolean foundType = UA_FALSE; UA_Boolean foundSource = UA_FALSE; ck_assert_uint_eq(*(UA_UInt32 *) monContext, monitoredItemId); ck_assert_uint_eq(nEventFields, nSelectClauses); // check all event fields for (unsigned int i = 0; i < nEventFields; i++) { // find out which attribute of the event is being looked at if (UA_Variant_hasScalarType(&eventFields[i], &UA_TYPES[UA_TYPES_UINT16])) { // Severity ck_assert_uint_eq(*((UA_UInt16 *) (eventFields[i].data)), 1000); foundSeverity = UA_TRUE; } else if (UA_Variant_hasScalarType(&eventFields[i], &UA_TYPES[UA_TYPES_LOCALIZEDTEXT])) { // Message UA_LocalizedText comp = UA_LOCALIZEDTEXT("en-US", "Generated Event"); ck_assert(UA_String_equal(&((UA_LocalizedText *) eventFields[i].data)->locale, &comp.locale)); ck_assert(UA_String_equal(&((UA_LocalizedText *) eventFields[i].data)->text, &comp.text)); foundMessage = UA_TRUE; } else if (UA_Variant_hasScalarType(&eventFields[i], &UA_TYPES[UA_TYPES_NODEID])) { // either SourceNode or EventType UA_NodeId serverNameSpaceId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_NAMESPACES); if (UA_NodeId_equal((UA_NodeId *)eventFields[i].data, &eventType)) { // EventType foundType = UA_TRUE; } else if (UA_NodeId_equal((UA_NodeId *)eventFields[i].data, &serverNameSpaceId)) { // SourceNode foundSource = UA_TRUE; } else { ck_assert_msg(UA_FALSE, "NodeId doesn't match"); } } else { ck_assert_msg(UA_FALSE, "Field doesn't match"); } } ck_assert_uint_eq(foundMessage, UA_TRUE); ck_assert_uint_eq(foundSeverity, UA_TRUE); ck_assert_uint_eq(foundType, UA_TRUE); ck_assert_uint_eq(foundSource, UA_TRUE); notificationReceived = true; }
} END_TEST START_TEST(GetMaximalConnectionConfigurationAndCompareValues){ UA_NetworkAddressUrlDataType networkAddressUrlData = {UA_STRING("127.0.0.1"), UA_STRING("opc.udp://224.0.0.22:4840/")}; UA_Variant address; UA_Variant_setScalar(&address, &networkAddressUrlData, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); UA_KeyValuePair connectionOptions[3]; connectionOptions[0].key = UA_QUALIFIEDNAME(0, "ttl"); UA_UInt32 ttl = 10; UA_Variant_setScalar(&connectionOptions[0].value, &ttl, &UA_TYPES[UA_TYPES_UINT32]); connectionOptions[1].key = UA_QUALIFIEDNAME(0, "loopback"); UA_Boolean loopback = UA_FALSE; UA_Variant_setScalar(&connectionOptions[1].value, &loopback, &UA_TYPES[UA_TYPES_UINT32]); connectionOptions[2].key = UA_QUALIFIEDNAME(0, "reuse"); UA_Boolean reuse = UA_TRUE; UA_Variant_setScalar(&connectionOptions[2].value, &reuse, &UA_TYPES[UA_TYPES_UINT32]); UA_PubSubConnectionConfig connectionConf; memset(&connectionConf, 0, sizeof(UA_PubSubConnectionConfig)); connectionConf.name = UA_STRING("UADP Connection"); connectionConf.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); connectionConf.enabled = true; connectionConf.publisherId.numeric = 223344; connectionConf.connectionPropertiesSize = 3; connectionConf.connectionProperties = connectionOptions; connectionConf.address = address; UA_NodeId connection; UA_StatusCode retVal = UA_Server_addPubSubConnection(server, &connectionConf, &connection); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); UA_PubSubConnectionConfig connectionConfig; memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); retVal |= UA_Server_getPubSubConnectionConfig(server, connection, &connectionConfig); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); ck_assert(connectionConfig.connectionPropertiesSize == connectionConf.connectionPropertiesSize); ck_assert(UA_String_equal(&connectionConfig.name, &connectionConf.name) == UA_TRUE); ck_assert(UA_String_equal(&connectionConfig.transportProfileUri, &connectionConf.transportProfileUri) == UA_TRUE); UA_NetworkAddressUrlDataType networkAddressUrlDataCopy = *((UA_NetworkAddressUrlDataType *)connectionConfig.address.data); ck_assert(UA_NetworkAddressUrlDataType_calcSizeBinary(&networkAddressUrlDataCopy) == UA_NetworkAddressUrlDataType_calcSizeBinary(&networkAddressUrlData)); for(size_t i = 0; i < connectionConfig.connectionPropertiesSize; i++){ ck_assert(UA_String_equal(&connectionConfig.connectionProperties[i].key.name, &connectionConf.connectionProperties[i].key.name) == UA_TRUE); ck_assert(UA_Variant_calcSizeBinary(&connectionConfig.connectionProperties[i].value) == UA_Variant_calcSizeBinary(&connectionConf.connectionProperties[i].value)); } UA_PubSubConnectionConfig_deleteMembers(&connectionConfig); } END_TEST
static void FindOnNetworkAndCheck(UA_String expectedServerNames[], size_t expectedServerNamesSize, const char *filterUri, const char *filterLocale, const char** filterCapabilities, size_t filterCapabilitiesSize) { UA_Client *client = UA_Client_new(); UA_ClientConfig_setDefault(UA_Client_getConfig(client)); UA_ServerOnNetwork* serverOnNetwork = NULL; size_t serverOnNetworkSize = 0; size_t serverCapabilityFilterSize = 0; UA_String *serverCapabilityFilter = NULL; if(filterCapabilitiesSize) { serverCapabilityFilterSize = filterCapabilitiesSize; serverCapabilityFilter = (UA_String*)UA_malloc(sizeof(UA_String) * filterCapabilitiesSize); for(size_t i = 0; i < filterCapabilitiesSize; i++) serverCapabilityFilter[i] = UA_String_fromChars(filterCapabilities[i]); } UA_StatusCode retval = UA_Client_findServersOnNetwork(client, "opc.tcp://localhost:4840", 0, 0, serverCapabilityFilterSize, serverCapabilityFilter, &serverOnNetworkSize, &serverOnNetwork); if(serverCapabilityFilterSize) UA_Array_delete(serverCapabilityFilter, serverCapabilityFilterSize, &UA_TYPES[UA_TYPES_STRING]); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); // only the discovery server is expected ck_assert_uint_eq(serverOnNetworkSize , expectedServerNamesSize); if(expectedServerNamesSize > 0) ck_assert_ptr_ne(serverOnNetwork, NULL); if(serverOnNetwork != NULL) { for(size_t i = 0; i < expectedServerNamesSize; i++) { UA_Boolean expectedServerNameInServerOnNetwork = false; for(size_t j = 0; j < expectedServerNamesSize && !expectedServerNameInServerOnNetwork; j++) { expectedServerNameInServerOnNetwork = UA_String_equal(&serverOnNetwork[j].serverName, &expectedServerNames[i]); } ck_assert_msg(expectedServerNameInServerOnNetwork, "Expected %.*s in serverOnNetwork list, but not found", expectedServerNames[i].length, expectedServerNames[i].data); } } UA_Array_delete(serverOnNetwork, serverOnNetworkSize, &UA_TYPES[UA_TYPES_SERVERONNETWORK]); UA_Client_disconnect(client); UA_Client_delete(client); }
/* * Get the endpoint from the server, where we can call RegisterServer2 (or RegisterServer). * This is normally the endpoint with highest supported encryption mode. * * @param discoveryServerUrl The discovery url from the remote server * @return The endpoint description (which needs to be freed) or NULL */ static UA_EndpointDescription *getRegisterEndpointFromServer(const char *discoveryServerUrl) { UA_Client *client = UA_Client_new(UA_ClientConfig_default); UA_EndpointDescription *endpointArray = NULL; size_t endpointArraySize = 0; UA_StatusCode retval = UA_Client_getEndpoints(client, discoveryServerUrl, &endpointArraySize, &endpointArray); if (retval != UA_STATUSCODE_GOOD) { UA_Array_delete(endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "GetEndpoints failed with %s", UA_StatusCode_name(retval)); UA_Client_delete(client); return NULL; } UA_LOG_DEBUG(logger, UA_LOGCATEGORY_SERVER, "Server has %ld endpoints", endpointArraySize); UA_EndpointDescription *foundEndpoint = NULL; for (size_t i = 0; i < endpointArraySize; i++) { UA_LOG_DEBUG(logger, UA_LOGCATEGORY_SERVER, "\tURL = %.*s, SecurityMode = %s", (int) endpointArray[i].endpointUrl.length, endpointArray[i].endpointUrl.data, endpointArray[i].securityMode == UA_MESSAGESECURITYMODE_NONE ? "None" : endpointArray[i].securityMode == UA_MESSAGESECURITYMODE_SIGN ? "Sign" : endpointArray[i].securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT ? "SignAndEncrypt" : "Invalid" ); // find the endpoint with highest supported security mode if ((UA_String_equal(&endpointArray[i].securityPolicyUri, &UA_SECURITY_POLICY_NONE_URI) || UA_String_equal(&endpointArray[i].securityPolicyUri, &UA_SECURITY_POLICY_BASIC128_URI)) && ( foundEndpoint == NULL || foundEndpoint->securityMode < endpointArray[i].securityMode)) foundEndpoint = &endpointArray[i]; } UA_EndpointDescription *returnEndpoint = NULL; if (foundEndpoint != NULL) { returnEndpoint = UA_EndpointDescription_new(); UA_EndpointDescription_copy(foundEndpoint, returnEndpoint); } UA_Array_delete(endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); return returnEndpoint; }
} END_TEST START_TEST(AddAndRemoveWriterGroups){ UA_StatusCode retVal; UA_Client *client = UA_Client_new(); UA_ClientConfig_setDefault(UA_Client_getConfig(client)); retVal = UA_Client_connect(client, "opc.tcp://localhost:4840"); if(retVal != UA_STATUSCODE_GOOD) { UA_Client_delete(client); } ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); UA_NodeId createdConnection = addPubSubConnection(); UA_Variant *inputArgument = (UA_Variant *) UA_calloc(1, (sizeof(UA_Variant))); UA_WriterGroupDataType writerGroupDataType; UA_WriterGroupDataType_init(&writerGroupDataType); writerGroupDataType.name = UA_STRING("TestWriterGroup"); writerGroupDataType.enabled = UA_TRUE; writerGroupDataType.publishingInterval = 500; writerGroupDataType.writerGroupId = 1234; UA_Variant_setScalar(inputArgument, &writerGroupDataType, &UA_TYPES[UA_TYPES_WRITERGROUPDATATYPE]); UA_CallMethodRequest callMethodRequest; UA_CallMethodRequest_init(&callMethodRequest); callMethodRequest.inputArgumentsSize = 1; callMethodRequest.inputArguments = inputArgument; callMethodRequest.objectId = createdConnection; callMethodRequest.methodId = UA_NODEID_NUMERIC(0, UA_NS0ID_PUBSUBCONNECTIONTYPE_ADDWRITERGROUP); UA_CallMethodResult result; UA_CallMethodResult_init(&result); result = UA_Server_call(server, &callMethodRequest); ck_assert_int_eq(result.statusCode, UA_STATUSCODE_GOOD); ck_assert_int_eq(1, result.outputArgumentsSize); UA_NodeId createdWriterGroup = UA_NODEID_NULL; if(result.outputArguments->type == &UA_TYPES[UA_TYPES_NODEID]) createdWriterGroup = *((UA_NodeId *) result.outputArguments->data); UA_LocalizedText writerGroupDisplayName; UA_LocalizedText_init(&writerGroupDisplayName); retVal = UA_Server_readDisplayName(server, createdWriterGroup, &writerGroupDisplayName); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); UA_String compareText = UA_STRING("TestWriterGroup"); ck_assert(UA_String_equal(&writerGroupDisplayName.text, &compareText) == UA_TRUE); UA_free(inputArgument); UA_CallMethodResult_deleteMembers(&result); UA_Client_disconnect(client); UA_Client_delete(client); UA_LocalizedText_deleteMembers(&writerGroupDisplayName); } END_TEST
static UA_StatusCode EndpointsHandshake(UA_Client *client) { UA_GetEndpointsRequest request; UA_GetEndpointsRequest_init(&request); request.requestHeader.authenticationToken = client->authenticationToken; request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 10000; request.endpointUrl = client->endpointUrl; UA_String profileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary"); request.profileUris = &profileUri; request.profileUrisSize = 1; UA_GetEndpointsResponse response; UA_GetEndpointsResponse_init(&response); __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_GETENDPOINTSREQUEST], &response, &UA_TYPES[UA_TYPES_GETENDPOINTSRESPONSE]); UA_Boolean endpointFound = UA_FALSE; UA_Boolean tokenFound = UA_FALSE; UA_String securityNone = UA_STRING("http://opcfoundation.org/UA/SecurityPolicy#None"); for(UA_Int32 i = 0; i < response.endpointsSize; i++) { UA_EndpointDescription* endpoint = &response.endpoints[i]; /* look out for an endpoint without security */ if(!UA_String_equal(&endpoint->securityPolicyUri, &securityNone)) continue; endpointFound = UA_TRUE; /* endpoint with no security found */ /* look for a user token policy with an anonymous token */ for(UA_Int32 j=0; j<endpoint->userIdentityTokensSize; ++j) { UA_UserTokenPolicy* userToken = &endpoint->userIdentityTokens[j]; if(userToken->tokenType != UA_USERTOKENTYPE_ANONYMOUS) continue; tokenFound = UA_TRUE; UA_UserTokenPolicy_copy(userToken, &client->token); break; } } UA_GetEndpointsResponse_deleteMembers(&response); if(!endpointFound) { UA_LOG_ERROR(client->logger, UA_LOGCATEGORY_CLIENT, "No suitable endpoint found"); return UA_STATUSCODE_BADINTERNALERROR; } if(!tokenFound) { UA_LOG_ERROR(client->logger, UA_LOGCATEGORY_CLIENT, "No anonymous token found"); return UA_STATUSCODE_BADINTERNALERROR; } return response.responseHeader.serviceResult; }
static void variables_basic(void) { /* Int32 */ UA_Int32 i = 5; UA_Int32 j; UA_Int32_copy(&i, &j); UA_Int32 *ip = UA_Int32_new(); UA_Int32_copy(&i, ip); UA_Int32_delete(ip); /* String */ UA_String s; UA_String_init(&s); /* _init zeroes out the entire memory of the datatype */ char *test = "test"; s.length = strlen(test); s.data = (UA_Byte*)test; UA_String s2; UA_String_copy(&s, &s2); UA_String_deleteMembers(&s2); /* Copying heap-allocated the dynamic content */ UA_String s3 = UA_STRING("test2"); UA_String s4 = UA_STRING_ALLOC("test2"); /* Copies the content to the heap */ UA_Boolean eq = UA_String_equal(&s3, &s4); UA_String_deleteMembers(&s4); if(!eq) return; /* Structured Type */ UA_ReadRequest rr; UA_init(&rr, &UA_TYPES[UA_TYPES_READREQUEST]); /* Generic method */ UA_ReadRequest_init(&rr); /* Shorthand for the previous line */ rr.requestHeader.timestamp = UA_DateTime_now(); /* Members of a structure */ rr.nodesToRead = (UA_ReadValueId *)UA_Array_new(5, &UA_TYPES[UA_TYPES_READVALUEID]); rr.nodesToReadSize = 5; /* Array size needs to be made known */ UA_ReadRequest *rr2 = UA_ReadRequest_new(); UA_copy(&rr, rr2, &UA_TYPES[UA_TYPES_READREQUEST]); UA_ReadRequest_deleteMembers(&rr); UA_ReadRequest_delete(rr2); }
static const UA_VariableNode * getArgumentsVariableNode(UA_Server *server, const UA_MethodNode *ofMethod, UA_String withBrowseName) { UA_NodeId hasProperty = UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY); for(size_t i = 0; i < ofMethod->referencesSize; i++) { if(ofMethod->references[i].isInverse == false && UA_NodeId_equal(&hasProperty, &ofMethod->references[i].referenceTypeId)) { const UA_Node *refTarget = UA_NodeStore_get(server->nodestore, &ofMethod->references[i].targetId.nodeId); if(!refTarget) continue; if(refTarget->nodeClass == UA_NODECLASS_VARIABLE && refTarget->browseName.namespaceIndex == 0 && UA_String_equal(&withBrowseName, &refTarget->browseName.name)) { return (const UA_VariableNode*) refTarget; } } } return NULL; }
UA_StatusCode UA_Client_NamespaceGetIndex(UA_Client *client, UA_String *namespaceUri, UA_UInt16 *namespaceIndex) { UA_ReadRequest request; UA_ReadRequest_init(&request); UA_ReadValueId id; id.attributeId = UA_ATTRIBUTEID_VALUE; id.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_NAMESPACEARRAY); request.nodesToRead = &id; request.nodesToReadSize = 1; UA_ReadResponse response = UA_Client_Service_read(client, request); UA_StatusCode retval = UA_STATUSCODE_GOOD; if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) retval = response.responseHeader.serviceResult; else if(response.resultsSize != 1 || !response.results[0].hasValue) retval = UA_STATUSCODE_BADNODEATTRIBUTESINVALID; else if(response.results[0].value.type != &UA_TYPES[UA_TYPES_STRING]) retval = UA_STATUSCODE_BADTYPEMISMATCH; if(retval != UA_STATUSCODE_GOOD) { UA_ReadResponse_deleteMembers(&response); return retval; } retval = UA_STATUSCODE_BADNOTFOUND; UA_String *ns = response.results[0].value.data; for(size_t i = 0; i < response.results[0].value.arrayLength; i++){ if(UA_String_equal(namespaceUri, &ns[i])) { *namespaceIndex = (UA_UInt16)i; retval = UA_STATUSCODE_GOOD; break; } } UA_ReadResponse_deleteMembers(&response); return retval; }
END_TEST START_TEST(Service_Browse_WithBrowseName) { UA_ServerConfig *config = UA_ServerConfig_new_default(); UA_Server *server = UA_Server_new(config); UA_BrowseDescription bd; UA_BrowseDescription_init(&bd); bd.resultMask = UA_BROWSERESULTMASK_BROWSENAME; bd.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); bd.browseDirection = UA_BROWSEDIRECTION_FORWARD; UA_BrowseResult br = UA_Server_browse(server, 0, &bd); ck_assert_int_eq(br.statusCode, UA_STATUSCODE_GOOD); ck_assert(br.referencesSize > 0); ck_assert(!UA_String_equal(&br.references[0].browseName.name, &UA_STRING_NULL)); UA_BrowseResult_deleteMembers(&br); UA_Server_delete(server); UA_ServerConfig_delete(config); }
static void Operation_Read(UA_Server *server, UA_Session *session, const UA_ReadValueId *id, UA_DataValue *v) { UA_LOG_DEBUG_SESSION(server->config.logger, session, "Read the attribute %i", id->attributeId); /* XML encoding is not supported */ if(id->dataEncoding.name.length > 0 && !UA_String_equal(&binEncoding, &id->dataEncoding.name)) { v->hasStatus = true; v->status = UA_STATUSCODE_BADDATAENCODINGUNSUPPORTED; return; } /* Index range for an attribute other than value */ if(id->indexRange.length > 0 && id->attributeId != UA_ATTRIBUTEID_VALUE) { v->hasStatus = true; v->status = UA_STATUSCODE_BADINDEXRANGENODATA; return; } /* Get the node */ const UA_Node *node = UA_Nodestore_get(server, &id->nodeId); if(!node) { v->hasStatus = true; v->status = UA_STATUSCODE_BADNODEIDUNKNOWN; return; } /* Read the attribute */ UA_StatusCode retval = UA_STATUSCODE_GOOD; switch(id->attributeId) { case UA_ATTRIBUTEID_NODEID: retval = UA_Variant_setScalarCopy(&v->value, &node->nodeId, &UA_TYPES[UA_TYPES_NODEID]); break; case UA_ATTRIBUTEID_NODECLASS: retval = UA_Variant_setScalarCopy(&v->value, &node->nodeClass, &UA_TYPES[UA_TYPES_NODECLASS]); break; case UA_ATTRIBUTEID_BROWSENAME: retval = UA_Variant_setScalarCopy(&v->value, &node->browseName, &UA_TYPES[UA_TYPES_QUALIFIEDNAME]); break; case UA_ATTRIBUTEID_DISPLAYNAME: retval = UA_Variant_setScalarCopy(&v->value, &node->displayName, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); break; case UA_ATTRIBUTEID_DESCRIPTION: retval = UA_Variant_setScalarCopy(&v->value, &node->description, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); break; case UA_ATTRIBUTEID_WRITEMASK: retval = UA_Variant_setScalarCopy(&v->value, &node->writeMask, &UA_TYPES[UA_TYPES_UINT32]); break; case UA_ATTRIBUTEID_USERWRITEMASK: { UA_UInt32 userWriteMask = getUserWriteMask(server, session, node); retval = UA_Variant_setScalarCopy(&v->value, &userWriteMask, &UA_TYPES[UA_TYPES_UINT32]); break; } case UA_ATTRIBUTEID_ISABSTRACT: retval = readIsAbstractAttribute(node, &v->value); break; case UA_ATTRIBUTEID_SYMMETRIC: CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_ReferenceTypeNode*)node)->symmetric, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_INVERSENAME: CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_ReferenceTypeNode*)node)->inverseName, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); break; case UA_ATTRIBUTEID_CONTAINSNOLOOPS: CHECK_NODECLASS(UA_NODECLASS_VIEW); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_ViewNode*)node)->containsNoLoops, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_EVENTNOTIFIER: CHECK_NODECLASS(UA_NODECLASS_VIEW | UA_NODECLASS_OBJECT); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_ViewNode*)node)->eventNotifier, &UA_TYPES[UA_TYPES_BYTE]); break; case UA_ATTRIBUTEID_VALUE: { CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); /* VariableTypes don't have the AccessLevel concept. Always allow reading the value. */ if(node->nodeClass == UA_NODECLASS_VARIABLE) { /* The access to a value variable is granted via the AccessLevel * and UserAccessLevel attributes */ UA_Byte accessLevel = getAccessLevel(server, session, (const UA_VariableNode*)node); if(!(accessLevel & (UA_ACCESSLEVELMASK_READ))) { retval = UA_STATUSCODE_BADNOTREADABLE; break; } accessLevel = getUserAccessLevel(server, session, (const UA_VariableNode*)node); if(!(accessLevel & (UA_ACCESSLEVELMASK_READ))) { retval = UA_STATUSCODE_BADUSERACCESSDENIED; break; } } retval = readValueAttributeComplete(server, session, (const UA_VariableNode*)node, op_timestampsToReturn, &id->indexRange, v); break; } case UA_ATTRIBUTEID_DATATYPE: CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_VariableTypeNode*)node)->dataType, &UA_TYPES[UA_TYPES_NODEID]); break; case UA_ATTRIBUTEID_VALUERANK: CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_VariableTypeNode*)node)->valueRank, &UA_TYPES[UA_TYPES_INT32]); break; case UA_ATTRIBUTEID_ARRAYDIMENSIONS: CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); retval = readArrayDimensionsAttribute((const UA_VariableNode*)node, v); break; case UA_ATTRIBUTEID_ACCESSLEVEL: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_VariableNode*)node)->accessLevel, &UA_TYPES[UA_TYPES_BYTE]); break; case UA_ATTRIBUTEID_USERACCESSLEVEL: { CHECK_NODECLASS(UA_NODECLASS_VARIABLE); UA_Byte userAccessLevel = getUserAccessLevel(server, session, (const UA_VariableNode*)node); retval = UA_Variant_setScalarCopy(&v->value, &userAccessLevel, &UA_TYPES[UA_TYPES_BYTE]); break; } case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_VariableNode*)node)->minimumSamplingInterval, &UA_TYPES[UA_TYPES_DOUBLE]); break; case UA_ATTRIBUTEID_HISTORIZING: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_VariableNode*)node)->historizing, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_EXECUTABLE: CHECK_NODECLASS(UA_NODECLASS_METHOD); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_MethodNode*)node)->executable, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_USEREXECUTABLE: { CHECK_NODECLASS(UA_NODECLASS_METHOD); UA_Boolean userExecutable = getUserExecutable(server, session, (const UA_MethodNode*)node); retval = UA_Variant_setScalarCopy(&v->value, &userExecutable, &UA_TYPES[UA_TYPES_BOOLEAN]); break; } default: retval = UA_STATUSCODE_BADATTRIBUTEIDINVALID; } /* Release nodes */ UA_Nodestore_release(server, node); /* Return error code when reading has failed */ if(retval != UA_STATUSCODE_GOOD) { v->hasStatus = true; v->status = retval; return; } v->hasValue = true; /* Create server timestamp */ if(op_timestampsToReturn == UA_TIMESTAMPSTORETURN_SERVER || op_timestampsToReturn == UA_TIMESTAMPSTORETURN_BOTH) { v->serverTimestamp = UA_DateTime_now(); v->hasServerTimestamp = true; } /* Handle source time stamp */ if(id->attributeId == UA_ATTRIBUTEID_VALUE) { if(op_timestampsToReturn == UA_TIMESTAMPSTORETURN_SERVER || op_timestampsToReturn == UA_TIMESTAMPSTORETURN_NEITHER) { v->hasSourceTimestamp = false; v->hasSourcePicoseconds = false; } else if(!v->hasSourceTimestamp) { v->sourceTimestamp = UA_DateTime_now(); v->hasSourceTimestamp = true; } } }
/* When a change is detected, encoding contains the heap-allocated binary * encoded value. The default for changed is false. */ static UA_StatusCode detectValueChangeWithFilter(UA_Server *server, UA_MonitoredItem *mon, UA_DataValue *value, UA_ByteString *encoding, UA_Boolean *changed) { if(UA_DataType_isNumeric(value->value.type) && (mon->filter.dataChangeFilter.trigger == UA_DATACHANGETRIGGER_STATUSVALUE || mon->filter.dataChangeFilter.trigger == UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP)) { if(mon->filter.dataChangeFilter.deadbandType == UA_DEADBANDTYPE_ABSOLUTE) { if(!updateNeededForFilteredValue(&value->value, &mon->lastValue, mon->filter.dataChangeFilter.deadbandValue)) return UA_STATUSCODE_GOOD; } #ifdef UA_ENABLE_DA else if(mon->filter.dataChangeFilter.deadbandType == UA_DEADBANDTYPE_PERCENT) { UA_QualifiedName qn = UA_QUALIFIEDNAME(0, "EURange"); UA_BrowsePathResult bpr = UA_Server_browseSimplifiedBrowsePath(server, mon->monitoredNodeId, 1, &qn); if(bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) { //if branch is not entried, property has been found UA_BrowsePathResult_deleteMembers(&bpr); return UA_STATUSCODE_GOOD; } const UA_VariableNode* node = (const UA_VariableNode*) UA_Nodestore_getNode(server->nsCtx, &bpr.targets->targetId.nodeId); UA_Range* euRange = (UA_Range*) node->value.data.value.value.data; if(!updateNeededForFilteredPercentValue(&value->value, &mon->lastValue, mon->filter.dataChangeFilter.deadbandValue, euRange)) { if(!updateNeededForStatusCode(value, mon)) //when same value, but different status code is written return UA_STATUSCODE_GOOD; } } #endif } /* Stack-allocate some memory for the value encoding. We might heap-allocate * more memory if needed. This is just enough for scalars and small * structures. */ UA_STACKARRAY(UA_Byte, stackValueEncoding, UA_VALUENCODING_MAXSTACK); UA_ByteString valueEncoding; valueEncoding.data = stackValueEncoding; valueEncoding.length = UA_VALUENCODING_MAXSTACK; /* Encode the value */ UA_Byte *bufPos = valueEncoding.data; const UA_Byte *bufEnd = &valueEncoding.data[valueEncoding.length]; UA_StatusCode retval = UA_encodeBinary(value, &UA_TYPES[UA_TYPES_DATAVALUE], &bufPos, &bufEnd, NULL, NULL); if(retval == UA_STATUSCODE_BADENCODINGERROR) { size_t binsize = UA_calcSizeBinary(value, &UA_TYPES[UA_TYPES_DATAVALUE]); if(binsize == 0) return UA_STATUSCODE_BADENCODINGERROR; if(binsize > UA_VALUENCODING_MAXSTACK) { retval = UA_ByteString_allocBuffer(&valueEncoding, binsize); if(retval == UA_STATUSCODE_GOOD) { bufPos = valueEncoding.data; bufEnd = &valueEncoding.data[valueEncoding.length]; retval = UA_encodeBinary(value, &UA_TYPES[UA_TYPES_DATAVALUE], &bufPos, &bufEnd, NULL, NULL); } } } if(retval != UA_STATUSCODE_GOOD) { if(valueEncoding.data != stackValueEncoding) UA_ByteString_deleteMembers(&valueEncoding); return retval; } /* Has the value changed? */ valueEncoding.length = (uintptr_t)bufPos - (uintptr_t)valueEncoding.data; *changed = (!mon->lastSampledValue.data || !UA_String_equal(&valueEncoding, &mon->lastSampledValue)); /* No change */ if(!(*changed)) { if(valueEncoding.data != stackValueEncoding) UA_ByteString_deleteMembers(&valueEncoding); return UA_STATUSCODE_GOOD; } /* Change detected. Copy encoding on the heap if necessary. */ if(valueEncoding.data == stackValueEncoding) return UA_ByteString_copy(&valueEncoding, encoding); *encoding = valueEncoding; return UA_STATUSCODE_GOOD; }
END_TEST static void FindAndCheck(const UA_String expectedUris[], size_t expectedUrisSize, const UA_String expectedLocales[], const UA_String expectedNames[], const char *filterUri, const char *filterLocale) { UA_Client *client = UA_Client_new(); UA_ClientConfig_setDefault(UA_Client_getConfig(client)); UA_ApplicationDescription* applicationDescriptionArray = NULL; size_t applicationDescriptionArraySize = 0; size_t serverUrisSize = 0; UA_String *serverUris = NULL; if(filterUri) { serverUrisSize = 1; serverUris = UA_String_new(); serverUris[0] = UA_String_fromChars(filterUri); } size_t localeIdsSize = 0; UA_String *localeIds = NULL; if(filterLocale) { localeIdsSize = 1; localeIds = UA_String_new(); localeIds[0] = UA_String_fromChars(filterLocale); } UA_StatusCode retval = UA_Client_findServers(client, "opc.tcp://localhost:4840", serverUrisSize, serverUris, localeIdsSize, localeIds, &applicationDescriptionArraySize, &applicationDescriptionArray); if(filterUri) UA_Array_delete(serverUris, serverUrisSize, &UA_TYPES[UA_TYPES_STRING]); if(filterLocale) UA_Array_delete(localeIds, localeIdsSize, &UA_TYPES[UA_TYPES_STRING]); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); // only the discovery server is expected ck_assert_uint_eq(applicationDescriptionArraySize, expectedUrisSize); ck_assert(applicationDescriptionArray != NULL); for(size_t i = 0; i < expectedUrisSize; ++i) { ck_assert(UA_String_equal(&applicationDescriptionArray[i].applicationUri, &expectedUris[i])); if(expectedNames) ck_assert(UA_String_equal(&applicationDescriptionArray[i].applicationName.text, &expectedNames[i])); if (expectedLocales) ck_assert(UA_String_equal(&applicationDescriptionArray[i].applicationName.locale, &expectedLocales[i])); } UA_Array_delete(applicationDescriptionArray, applicationDescriptionArraySize, &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]); UA_Client_disconnect(client); UA_Client_delete(client); }
/** Reads a single attribute from a node in the nodestore. */ static void readValue(UA_Server *server, UA_TimestampsToReturn timestamps, const UA_ReadValueId *id, UA_DataValue *v) { UA_String binEncoding = UA_STRING("DefaultBinary"); UA_String xmlEncoding = UA_STRING("DefaultXml"); if(id->dataEncoding.name.length >= 0){ if(!UA_String_equal(&binEncoding, &id->dataEncoding.name) && !UA_String_equal(&xmlEncoding, &id->dataEncoding.name)) { v->hasStatus = UA_TRUE; v->status = UA_STATUSCODE_BADDATAENCODINGINVALID; return; } } //index range for a non-value if(id->indexRange.length >= 0 && id->attributeId != UA_ATTRIBUTEID_VALUE){ v->hasStatus = UA_TRUE; v->status = UA_STATUSCODE_BADINDEXRANGENODATA; return; } UA_Node const *node = UA_NodeStore_get(server->nodestore, &(id->nodeId)); if(!node) { v->hasStatus = UA_TRUE; v->status = UA_STATUSCODE_BADNODEIDUNKNOWN; return; } UA_StatusCode retval = UA_STATUSCODE_GOOD; switch(id->attributeId) { case UA_ATTRIBUTEID_NODEID: v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &node->nodeId, &UA_TYPES[UA_TYPES_NODEID]); break; case UA_ATTRIBUTEID_NODECLASS: v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &node->nodeClass, &UA_TYPES[UA_TYPES_INT32]); break; case UA_ATTRIBUTEID_BROWSENAME: v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &node->browseName, &UA_TYPES[UA_TYPES_QUALIFIEDNAME]); break; case UA_ATTRIBUTEID_DISPLAYNAME: retval |= UA_Variant_setScalarCopy(&v->value, &node->displayName, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); if(retval == UA_STATUSCODE_GOOD) v->hasValue = UA_TRUE; break; case UA_ATTRIBUTEID_DESCRIPTION: v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &node->description, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); break; case UA_ATTRIBUTEID_WRITEMASK: v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &node->writeMask, &UA_TYPES[UA_TYPES_UINT32]); break; case UA_ATTRIBUTEID_USERWRITEMASK: v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &node->userWriteMask, &UA_TYPES[UA_TYPES_UINT32]); break; case UA_ATTRIBUTEID_ISABSTRACT: CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE | UA_NODECLASS_OBJECTTYPE | UA_NODECLASS_VARIABLETYPE | UA_NODECLASS_DATATYPE); v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &((const UA_ReferenceTypeNode *)node)->isAbstract, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_SYMMETRIC: CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE); v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &((const UA_ReferenceTypeNode *)node)->symmetric, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_INVERSENAME: CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE); v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &((const UA_ReferenceTypeNode *)node)->inverseName, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); break; case UA_ATTRIBUTEID_CONTAINSNOLOOPS: CHECK_NODECLASS(UA_NODECLASS_VIEW); v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &((const UA_ViewNode *)node)->containsNoLoops, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_EVENTNOTIFIER: CHECK_NODECLASS(UA_NODECLASS_VIEW | UA_NODECLASS_OBJECT); v->hasValue = UA_TRUE; if(node->nodeClass == UA_NODECLASS_VIEW){ retval |= UA_Variant_setScalarCopy(&v->value, &((const UA_ViewNode *)node)->eventNotifier, &UA_TYPES[UA_TYPES_BYTE]); } else { retval |= UA_Variant_setScalarCopy(&v->value, &((const UA_ObjectNode *)node)->eventNotifier, &UA_TYPES[UA_TYPES_BYTE]); } break; case UA_ATTRIBUTEID_VALUE: CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); { if(node->nodeClass != UA_NODECLASS_VARIABLE) { v->hasValue = UA_FALSE; handleSourceTimestamps(timestamps, v); } UA_Boolean hasRange = UA_FALSE; UA_NumericRange range; if(id->indexRange.length > 0) { retval = parse_numericrange(id->indexRange, &range); if(retval != UA_STATUSCODE_GOOD) break; hasRange = UA_TRUE; } const UA_VariableNode *vn = (const UA_VariableNode*)node; if(vn->valueSource == UA_VALUESOURCE_VARIANT) { if(hasRange) retval |= UA_Variant_copyRange(&vn->value.variant, &v->value, range); else retval |= UA_Variant_copy(&vn->value.variant, &v->value); if(retval == UA_STATUSCODE_GOOD) { v->hasValue = UA_TRUE; handleSourceTimestamps(timestamps, v); } } else { UA_DataValue val; UA_DataValue_init(&val); UA_Boolean sourceTimeStamp = (timestamps == UA_TIMESTAMPSTORETURN_SOURCE || timestamps == UA_TIMESTAMPSTORETURN_BOTH); if(hasRange) retval |= vn->value.dataSource.read(vn->value.dataSource.handle, sourceTimeStamp, &range, &val); else retval |= vn->value.dataSource.read(vn->value.dataSource.handle, sourceTimeStamp, UA_NULL, &val); if(retval == UA_STATUSCODE_GOOD) { retval |= UA_DataValue_copy(&val, v); // todo: still too much copying necessary!! vn->value.dataSource.release(vn->value.dataSource.handle, &val); } } if(hasRange) UA_free(range.dimensions); } break; case UA_ATTRIBUTEID_DATATYPE: { CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); const UA_VariableNode *vn = (const UA_VariableNode*)node; if(vn->valueSource == UA_VALUESOURCE_VARIANT) retval = UA_Variant_setScalarCopy(&v->value, &vn->value.variant.type->typeId, &UA_TYPES[UA_TYPES_NODEID]); else { UA_DataValue val; UA_DataValue_init(&val); retval = vn->value.dataSource.read(vn->value.dataSource.handle, UA_FALSE, UA_NULL, &val); if(retval != UA_STATUSCODE_GOOD) break; retval = UA_Variant_setScalarCopy(&v->value, &val.value.type->typeId, &UA_TYPES[UA_TYPES_NODEID]); vn->value.dataSource.release(vn->value.dataSource.handle, &val); } if(retval == UA_STATUSCODE_GOOD) v->hasValue = UA_TRUE; } break; case UA_ATTRIBUTEID_VALUERANK: CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &((const UA_VariableTypeNode *)node)->valueRank, &UA_TYPES[UA_TYPES_INT32]); break; case UA_ATTRIBUTEID_ARRAYDIMENSIONS: CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); { const UA_VariableNode *vn = (const UA_VariableNode *)node; if(vn->valueSource == UA_VALUESOURCE_VARIANT) { retval = UA_Variant_setArrayCopy(&v->value, vn->value.variant.arrayDimensions, vn->value.variant.arrayDimensionsSize, &UA_TYPES[UA_TYPES_INT32]); if(retval == UA_STATUSCODE_GOOD) v->hasValue = UA_TRUE; } else { UA_DataValue val; UA_DataValue_init(&val); retval |= vn->value.dataSource.read(vn->value.dataSource.handle, UA_FALSE, UA_NULL, &val); if(retval != UA_STATUSCODE_GOOD) break; if(!val.hasValue) retval = UA_STATUSCODE_BADNOTREADABLE; else retval = UA_Variant_setArrayCopy(&v->value, val.value.arrayDimensions, val.value.arrayDimensionsSize, &UA_TYPES[UA_TYPES_INT32]); vn->value.dataSource.release(vn->value.dataSource.handle, &val); } } break; case UA_ATTRIBUTEID_ACCESSLEVEL: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &((const UA_VariableNode *)node)->accessLevel, &UA_TYPES[UA_TYPES_BYTE]); break; case UA_ATTRIBUTEID_USERACCESSLEVEL: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &((const UA_VariableNode *)node)->userAccessLevel, &UA_TYPES[UA_TYPES_BYTE]); break; case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &((const UA_VariableNode *)node)->minimumSamplingInterval, &UA_TYPES[UA_TYPES_DOUBLE]); break; case UA_ATTRIBUTEID_HISTORIZING: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &((const UA_VariableNode *)node)->historizing, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_EXECUTABLE: CHECK_NODECLASS(UA_NODECLASS_METHOD); v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &((const UA_MethodNode *)node)->executable, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_USEREXECUTABLE: CHECK_NODECLASS(UA_NODECLASS_METHOD); v->hasValue = UA_TRUE; retval |= UA_Variant_setScalarCopy(&v->value, &((const UA_MethodNode *)node)->userExecutable, &UA_TYPES[UA_TYPES_BOOLEAN]); break; default: v->hasStatus = UA_TRUE; v->status = UA_STATUSCODE_BADATTRIBUTEIDINVALID; break; } UA_NodeStore_release(node); if(retval != UA_STATUSCODE_GOOD) { v->hasStatus = UA_TRUE; v->status = retval; } handleServerTimestamps(timestamps, v); }
} END_TEST START_TEST(AddAndRemovePublishedDataSetFolders){ UA_StatusCode retVal; UA_Client *client = UA_Client_new(); UA_ClientConfig_setDefault(UA_Client_getConfig(client)); retVal = UA_Client_connect(client, "opc.tcp://localhost:4840"); if(retVal != UA_STATUSCODE_GOOD) { UA_Client_delete(client); } ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); UA_String folderName = UA_STRING("TestFolder"); UA_Variant inputArguments; UA_Variant_init(&inputArguments); UA_Variant_setScalar(&inputArguments, &folderName, &UA_TYPES[UA_TYPES_STRING]); UA_CallMethodRequest callMethodRequest; UA_CallMethodRequest_init(&callMethodRequest); callMethodRequest.inputArgumentsSize = 1; callMethodRequest.inputArguments = &inputArguments; callMethodRequest.objectId = UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE_PUBLISHEDDATASETS); callMethodRequest.methodId = UA_NODEID_NUMERIC(0, UA_NS0ID_DATASETFOLDERTYPE_ADDDATASETFOLDER); UA_CallMethodResult result; UA_CallMethodResult_init(&result); result = UA_Server_call(server, &callMethodRequest); ck_assert_int_eq(1, result.outputArgumentsSize); ck_assert_int_eq(result.statusCode, UA_STATUSCODE_GOOD); UA_NodeId createdFolder = UA_NODEID_NULL; if(result.outputArguments->type == &UA_TYPES[UA_TYPES_NODEID]) createdFolder = *((UA_NodeId *) result.outputArguments->data); UA_LocalizedText connectionDisplayName; UA_LocalizedText_init(&connectionDisplayName); retVal = UA_Server_readDisplayName(server, createdFolder, &connectionDisplayName); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); UA_String compareText = UA_STRING("TestFolder"); ck_assert(UA_String_equal(&connectionDisplayName.text, &compareText) == UA_TRUE); retVal = UA_Server_readNodeId(server, createdFolder, &createdFolder); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); UA_CallMethodResult_deleteMembers(&result); UA_LocalizedText_deleteMembers(&connectionDisplayName); //create folder inside the new folder folderName = UA_STRING("TestFolder2"); UA_Variant_init(&inputArguments); UA_Variant_setScalar(&inputArguments, &folderName, &UA_TYPES[UA_TYPES_STRING]); UA_CallMethodRequest_init(&callMethodRequest); callMethodRequest.inputArgumentsSize = 1; callMethodRequest.inputArguments = &inputArguments; callMethodRequest.objectId = createdFolder; callMethodRequest.methodId = UA_NODEID_NUMERIC(0, UA_NS0ID_DATASETFOLDERTYPE_ADDDATASETFOLDER); UA_CallMethodResult_init(&result); result = UA_Server_call(server, &callMethodRequest); ck_assert_int_eq(1, result.outputArgumentsSize); ck_assert_int_eq(result.statusCode, UA_STATUSCODE_GOOD); UA_NodeId createdFolder2 = UA_NODEID_NULL; if(result.outputArguments->type == &UA_TYPES[UA_TYPES_NODEID]) createdFolder2 = *((UA_NodeId *) result.outputArguments->data); UA_LocalizedText_init(&connectionDisplayName); retVal = UA_Server_readDisplayName(server, createdFolder2, &connectionDisplayName); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); compareText = UA_STRING("TestFolder2"); ck_assert(UA_String_equal(&connectionDisplayName.text, &compareText) == UA_TRUE); retVal = UA_Server_readNodeId(server, createdFolder2, &createdFolder2); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); UA_CallMethodResult_deleteMembers(&result); //delete the folder UA_Variant_init(&inputArguments); UA_Variant_setScalar(&inputArguments, &createdFolder, &UA_TYPES[UA_TYPES_NODEID]); UA_CallMethodRequest_init(&callMethodRequest); callMethodRequest.inputArgumentsSize = 1; callMethodRequest.inputArguments = &inputArguments; callMethodRequest.objectId = UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE_PUBLISHEDDATASETS); callMethodRequest.methodId = UA_NODEID_NUMERIC(0, UA_NS0ID_DATASETFOLDERTYPE_REMOVEDATASETFOLDER); result = UA_Server_call(server, &callMethodRequest); ck_assert_int_eq(0, result.outputArgumentsSize); ck_assert_int_eq(result.statusCode, UA_STATUSCODE_GOOD); //check if the node is deleted retVal = UA_Server_readNodeId(server, createdFolder, NULL); ck_assert_int_eq(retVal, UA_STATUSCODE_BADNODEIDUNKNOWN); UA_CallMethodResult_deleteMembers(&result); UA_Client_disconnect(client); UA_Client_delete(client); UA_LocalizedText_deleteMembers(&connectionDisplayName); } END_TEST
/* Combination of UA_Client_getEndpointsInternal and getEndpoints */ static void responseGetEndpoints(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) { UA_EndpointDescription* endpointArray = NULL; size_t endpointArraySize = 0; UA_GetEndpointsResponse* resp; resp = (UA_GetEndpointsResponse*)response; if (resp->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { client->connectStatus = resp->responseHeader.serviceResult; UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "GetEndpointRequest failed with error code %s", UA_StatusCode_name (client->connectStatus)); UA_GetEndpointsResponse_deleteMembers(resp); return; } endpointArray = resp->endpoints; endpointArraySize = resp->endpointsSize; resp->endpoints = NULL; resp->endpointsSize = 0; UA_Boolean endpointFound = false; UA_Boolean tokenFound = false; UA_String securityNone = UA_STRING("http://opcfoundation.org/UA/SecurityPolicy#None"); UA_String binaryTransport = UA_STRING("http://opcfoundation.org/UA-Profile/" "Transport/uatcp-uasc-uabinary"); // TODO: compare endpoint information with client->endpointUri for(size_t i = 0; i < endpointArraySize; ++i) { UA_EndpointDescription* endpoint = &endpointArray[i]; /* look out for binary transport endpoints */ /* Note: Siemens returns empty ProfileUrl, we will accept it as binary */ if(endpoint->transportProfileUri.length != 0 && !UA_String_equal (&endpoint->transportProfileUri, &binaryTransport)) continue; /* Look for an endpoint corresponding to the client security policy */ if(!UA_String_equal(&endpoint->securityPolicyUri, &client->channel.securityPolicy->policyUri)) continue; endpointFound = true; /* Look for a user token policy with an anonymous token */ for(size_t j = 0; j < endpoint->userIdentityTokensSize; ++j) { UA_UserTokenPolicy* userToken = &endpoint->userIdentityTokens[j]; /* Usertokens also have a security policy... */ if(userToken->securityPolicyUri.length > 0 && !UA_String_equal(&userToken->securityPolicyUri, &securityNone)) continue; /* Does the token type match the client configuration? */ if((userToken->tokenType == UA_USERTOKENTYPE_ANONYMOUS && client->config.userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN] && client->config.userIdentityToken.content.decoded.type != NULL) || (userToken->tokenType == UA_USERTOKENTYPE_USERNAME && client->config.userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) || (userToken->tokenType == UA_USERTOKENTYPE_CERTIFICATE && client->config.userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_X509IDENTITYTOKEN]) || (userToken->tokenType == UA_USERTOKENTYPE_ISSUEDTOKEN && client->config.userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_ISSUEDIDENTITYTOKEN])) continue; /* Endpoint with matching usertokenpolicy found */ tokenFound = true; UA_EndpointDescription_deleteMembers(&client->config.endpoint); UA_EndpointDescription_copy(endpoint, &client->config.endpoint); UA_UserTokenPolicy_deleteMembers(&client->config.userTokenPolicy); UA_UserTokenPolicy_copy(userToken, &client->config.userTokenPolicy); break; } } UA_Array_delete(endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); if(!endpointFound) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "No suitable endpoint found"); client->connectStatus = UA_STATUSCODE_BADINTERNALERROR; } else if(!tokenFound) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "No suitable UserTokenPolicy found for the possible endpoints"); client->connectStatus = UA_STATUSCODE_BADINTERNALERROR; } requestSession(client, &requestId); }
void Service_ActivateSession(UA_Server *server, UA_SecureChannel *channel, UA_Session *session, const UA_ActivateSessionRequest *request, UA_ActivateSessionResponse *response) { if(session->validTill < UA_DateTime_now()) { UA_LOG_INFO_SESSION(server->config.logger, session, "ActivateSession: SecureChannel %i wants " "to activate, but the session has timed out", channel->securityToken.channelId); response->responseHeader.serviceResult = UA_STATUSCODE_BADSESSIONIDINVALID; return; } if(request->userIdentityToken.encoding < UA_EXTENSIONOBJECT_DECODED || (request->userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN] && request->userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN])) { UA_LOG_INFO_SESSION(server->config.logger, session, "ActivateSession: SecureChannel %i wants " "to activate, but the UserIdentify token is invalid", channel->securityToken.channelId); response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; return; } UA_String ap = UA_STRING(ANONYMOUS_POLICY); UA_String up = UA_STRING(USERNAME_POLICY); /* Compatibility notice: Siemens OPC Scout v10 provides an empty policyId, this is not okay For compatibility we will assume that empty policyId == ANONYMOUS_POLICY if(token.policyId->data == NULL) response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; */ if(server->config.enableAnonymousLogin && request->userIdentityToken.content.decoded.type == &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN]) { /* anonymous login */ const UA_AnonymousIdentityToken *token = request->userIdentityToken.content.decoded.data; if(token->policyId.data && !UA_String_equal(&token->policyId, &ap)) { response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; return; } } else if(server->config.enableUsernamePasswordLogin && request->userIdentityToken.content.decoded.type == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) { /* username login */ const UA_UserNameIdentityToken *token = request->userIdentityToken.content.decoded.data; if(!UA_String_equal(&token->policyId, &up)) { response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; return; } if(token->encryptionAlgorithm.length > 0) { /* we don't support encryption */ response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; return; } if(token->userName.length == 0 && token->password.length == 0) { /* empty username and password */ response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; return; } /* trying to match pw/username */ UA_Boolean match = false; for(size_t i = 0; i < server->config.usernamePasswordLoginsSize; i++) { UA_String *user = &server->config.usernamePasswordLogins[i].username; UA_String *pw = &server->config.usernamePasswordLogins[i].password; if(UA_String_equal(&token->userName, user) && UA_String_equal(&token->password, pw)) { match = true; break; } } if(!match) { UA_LOG_INFO_SESSION(server->config.logger, session, "ActivateSession: Did not find matching username/password"); response->responseHeader.serviceResult = UA_STATUSCODE_BADUSERACCESSDENIED; return; } } else { /* Unsupported token type */ response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; return; } /* Detach the old SecureChannel */ if(session->channel && session->channel != channel) { UA_LOG_INFO_SESSION(server->config.logger, session, "ActivateSession: Detach from old channel"); UA_SecureChannel_detachSession(session->channel, session); } /* Attach to the SecureChannel and activate */ UA_SecureChannel_attachSession(channel, session); session->activated = true; UA_Session_updateLifetime(session); UA_LOG_INFO_SESSION(server->config.logger, session, "ActivateSession: Session activated"); }
/** Reads a single attribute from a node in the nodestore. */ void Service_Read_single(UA_Server *server, UA_Session *session, const UA_TimestampsToReturn timestamps, const UA_ReadValueId *id, UA_DataValue *v) { if(id->dataEncoding.name.length > 0 && !UA_String_equal(&binEncoding, &id->dataEncoding.name)) { v->hasStatus = UA_TRUE; v->status = UA_STATUSCODE_BADDATAENCODINGINVALID; return; } //index range for a non-value if(id->indexRange.length > 0 && id->attributeId != UA_ATTRIBUTEID_VALUE){ v->hasStatus = UA_TRUE; v->status = UA_STATUSCODE_BADINDEXRANGENODATA; return; } UA_Node const *node = UA_NodeStore_get(server->nodestore, &id->nodeId); if(!node) { v->hasStatus = UA_TRUE; v->status = UA_STATUSCODE_BADNODEIDUNKNOWN; return; } /* When setting the value fails in the switch, we get an error code and set hasValue to false */ UA_StatusCode retval = UA_STATUSCODE_GOOD; v->hasValue = UA_TRUE; switch(id->attributeId) { case UA_ATTRIBUTEID_NODEID: forceVariantSetScalar(&v->value, &node->nodeId, &UA_TYPES[UA_TYPES_NODEID]); break; case UA_ATTRIBUTEID_NODECLASS: forceVariantSetScalar(&v->value, &node->nodeClass, &UA_TYPES[UA_TYPES_INT32]); break; case UA_ATTRIBUTEID_BROWSENAME: forceVariantSetScalar(&v->value, &node->browseName, &UA_TYPES[UA_TYPES_QUALIFIEDNAME]); break; case UA_ATTRIBUTEID_DISPLAYNAME: forceVariantSetScalar(&v->value, &node->displayName, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); break; case UA_ATTRIBUTEID_DESCRIPTION: forceVariantSetScalar(&v->value, &node->description, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); break; case UA_ATTRIBUTEID_WRITEMASK: forceVariantSetScalar(&v->value, &node->writeMask, &UA_TYPES[UA_TYPES_UINT32]); break; case UA_ATTRIBUTEID_USERWRITEMASK: forceVariantSetScalar(&v->value, &node->userWriteMask, &UA_TYPES[UA_TYPES_UINT32]); break; case UA_ATTRIBUTEID_ISABSTRACT: CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE | UA_NODECLASS_OBJECTTYPE | UA_NODECLASS_VARIABLETYPE | UA_NODECLASS_DATATYPE); forceVariantSetScalar(&v->value, &((const UA_ReferenceTypeNode*)node)->isAbstract, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_SYMMETRIC: CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE); forceVariantSetScalar(&v->value, &((const UA_ReferenceTypeNode*)node)->symmetric, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_INVERSENAME: CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE); forceVariantSetScalar(&v->value, &((const UA_ReferenceTypeNode*)node)->inverseName, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); break; case UA_ATTRIBUTEID_CONTAINSNOLOOPS: CHECK_NODECLASS(UA_NODECLASS_VIEW); forceVariantSetScalar(&v->value, &((const UA_ViewNode*)node)->containsNoLoops, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_EVENTNOTIFIER: CHECK_NODECLASS(UA_NODECLASS_VIEW | UA_NODECLASS_OBJECT); forceVariantSetScalar(&v->value, &((const UA_ViewNode*)node)->eventNotifier, &UA_TYPES[UA_TYPES_BYTE]); break; case UA_ATTRIBUTEID_VALUE: CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); retval = getVariableNodeValue((const UA_VariableNode*)node, timestamps, id, v); break; case UA_ATTRIBUTEID_DATATYPE: CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); retval = getVariableNodeDataType((const UA_VariableNode*)node, v); break; case UA_ATTRIBUTEID_VALUERANK: CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); forceVariantSetScalar(&v->value, &((const UA_VariableTypeNode*)node)->valueRank, &UA_TYPES[UA_TYPES_INT32]); break; case UA_ATTRIBUTEID_ARRAYDIMENSIONS: CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); retval = getVariableNodeArrayDimensions((const UA_VariableNode*)node, v); break; case UA_ATTRIBUTEID_ACCESSLEVEL: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); forceVariantSetScalar(&v->value, &((const UA_VariableNode*)node)->accessLevel, &UA_TYPES[UA_TYPES_BYTE]); break; case UA_ATTRIBUTEID_USERACCESSLEVEL: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); forceVariantSetScalar(&v->value, &((const UA_VariableNode*)node)->userAccessLevel, &UA_TYPES[UA_TYPES_BYTE]); break; case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); forceVariantSetScalar(&v->value, &((const UA_VariableNode*)node)->minimumSamplingInterval, &UA_TYPES[UA_TYPES_DOUBLE]); break; case UA_ATTRIBUTEID_HISTORIZING: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); forceVariantSetScalar(&v->value, &((const UA_VariableNode*)node)->historizing, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_EXECUTABLE: CHECK_NODECLASS(UA_NODECLASS_METHOD); forceVariantSetScalar(&v->value, &((const UA_MethodNode*)node)->executable, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_USEREXECUTABLE: CHECK_NODECLASS(UA_NODECLASS_METHOD); forceVariantSetScalar(&v->value, &((const UA_MethodNode*)node)->userExecutable, &UA_TYPES[UA_TYPES_BOOLEAN]); break; default: retval = UA_STATUSCODE_BADATTRIBUTEIDINVALID; break; } if(retval != UA_STATUSCODE_GOOD) { v->hasValue = UA_FALSE; v->hasStatus = UA_TRUE; v->status = retval; } // Todo: what if the timestamp from the datasource are already present? handleServerTimestamps(timestamps, v); }
void Service_ActivateSession(UA_Server *server, UA_SecureChannel *channel, const UA_ActivateSessionRequest *request, UA_ActivateSessionResponse *response) { // make the channel know about the session UA_Session *foundSession = UA_SessionManager_getSession(&server->sessionManager, (const UA_NodeId*)&request->requestHeader.authenticationToken); if(foundSession == UA_NULL) { response->responseHeader.serviceResult = UA_STATUSCODE_BADSESSIONIDINVALID; return; } else if(foundSession->validTill < UA_DateTime_now()) { response->responseHeader.serviceResult = UA_STATUSCODE_BADSESSIONIDINVALID; return; } UA_UserIdentityToken token; UA_UserIdentityToken_init(&token); size_t offset = 0; UA_UserIdentityToken_decodeBinary(&request->userIdentityToken.body, &offset, &token); UA_UserNameIdentityToken username_token; UA_UserNameIdentityToken_init(&username_token); UA_String ap = UA_STRING(ANONYMOUS_POLICY); UA_String up = UA_STRING(USERNAME_POLICY); //(Compatibility notice) //Siemens OPC Scout v10 provides an empty policyId, this is not okay //For compatibility we will assume that empty policyId == ANONYMOUS_POLICY //if(token.policyId.data == UA_NULL) { // /* 1) no policy defined */ // response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; //} else //(End Compatibility notice) if(server->config.Login_enableAnonymous && (token.policyId.data == UA_NULL || UA_String_equal(&token.policyId, &ap))) { /* 2) anonymous logins */ if(foundSession->channel && foundSession->channel != channel) UA_SecureChannel_detachSession(foundSession->channel, foundSession); UA_SecureChannel_attachSession(channel, foundSession); foundSession->activated = UA_TRUE; UA_Session_updateLifetime(foundSession); } else if(server->config.Login_enableUsernamePassword && UA_String_equal(&token.policyId, &up)) { /* 3) username logins */ offset = 0; UA_UserNameIdentityToken_decodeBinary(&request->userIdentityToken.body, &offset, &username_token); if(username_token.encryptionAlgorithm.data != UA_NULL) { /* 3.1) we only support encryption */ response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; } else if(username_token.userName.length == -1 && username_token.password.length == -1){ /* 3.2) empty username and password */ response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; } else { /* 3.3) ok, trying to match the username */ UA_UInt32 i = 0; for(; i < server->config.Login_loginsCount; ++i) { UA_String user = UA_STRING(server->config.Login_usernames[i]); UA_String pw = UA_STRING(server->config.Login_passwords[i]); if(UA_String_equal(&username_token.userName, &user) && UA_String_equal(&username_token.password, &pw)) { /* success - activate */ if(foundSession->channel && foundSession->channel != channel) UA_SecureChannel_detachSession(foundSession->channel, foundSession); UA_SecureChannel_attachSession(channel, foundSession); foundSession->activated = UA_TRUE; UA_Session_updateLifetime(foundSession); break; } } /* no username/pass matched */ if(i >= server->config.Login_loginsCount) response->responseHeader.serviceResult = UA_STATUSCODE_BADUSERACCESSDENIED; } } else { response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; } UA_UserIdentityToken_deleteMembers(&token); UA_UserNameIdentityToken_deleteMembers(&username_token); return; }