Files
open62541/tests/server/check_server_http.c
Andreas Ebner 16a739a7c3 fix(server): fail startup when no transport can open a server socket
UA_Server_run_startup ignored the driver results and ran without
listening. It now fails if a transport cannot open its socket and
neither another transport nor a reverse connect reaches the server.
2026-10-13 08:55:20 +02:00

2440 lines
111 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 2026 (c) o6 Automation GmbH (Author: Julius Pfrommer)
*/
#include "ua_types_encoding_binary.h"
#include "ua_server_internal.h"
#include <open62541/plugin/log_stdout.h>
#include <open62541/plugin/accesscontrol_default.h>
#include <open62541/client.h>
#include <open62541/client_config_default.h>
#include <open62541/client_highlevel.h>
#ifdef UA_ENABLE_ENCRYPTION
# include <open62541/plugin/certificategroup_default.h>
# include <open62541/plugin/securitypolicy_default.h>
#endif
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#ifdef UA_ENABLE_HTTP_COMPRESSION
# include "../../arch/posix/eventloop_posix_http_compression.h"
#endif
#include <check.h>
#include <stdio.h>
static uintptr_t clientConnectionId;
static UA_ByteString responseBody;
static UA_UInt16 responseStatus;
static UA_Boolean responseIsJson;
static UA_Boolean responseIsGzip;
static UA_Boolean responseComplete;
static void clientCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
void *application, void **connectionContext,
UA_ConnectionState state,
const UA_KeyValueMap *params, UA_ByteString msg);
static UA_ConnectionManager *
getHttpConnectionManager(UA_ServerConfig *config) {
const UA_String protocol = UA_STRING_STATIC("http");
for(UA_EventSource *es = config->eventLoop->eventSources; es;
es = es->next) {
if(es->eventSourceType != UA_EVENTSOURCETYPE_CONNECTIONMANAGER)
continue;
UA_ConnectionManager *cm = (UA_ConnectionManager *)es;
if(UA_String_equal(&cm->protocol, &protocol))
return cm;
}
return NULL;
}
static UA_UInt16
getAdvertisedPort(UA_ServerConfig *config, const char *prefix,
size_t prefixLength, UA_String *advertisedUrl) {
for(size_t i = 0; i < config->applicationDescription.discoveryUrlsSize;
i++) {
const UA_String *url = &config->applicationDescription.discoveryUrls[i];
if(url->length < prefixLength ||
memcmp(url->data, prefix, prefixLength) != 0)
continue;
UA_String hostname = UA_STRING_NULL;
UA_UInt16 port = 0;
ck_assert_uint_eq(UA_parseEndpointUrl(url, &hostname, &port, NULL),
UA_STATUSCODE_GOOD);
if(advertisedUrl)
ck_assert_uint_eq(UA_String_copy(url, advertisedUrl),
UA_STATUSCODE_GOOD);
return port;
}
return 0;
}
static void
openRawHttpClient(UA_ConnectionManager *http, UA_UInt16 port,
UA_Boolean useSSL, const UA_ByteString *caCertificate) {
UA_String address = UA_STRING("127.0.0.1");
UA_UInt16 timeout = 10;
UA_KeyValuePair params[5] = {0};
size_t paramsSize = 0;
#define ADD_OPEN_PARAM(NAME, VALUE, TYPE) \
do { \
params[paramsSize].key = UA_QUALIFIEDNAME(0, NAME); \
UA_Variant_setScalar(&params[paramsSize++].value, VALUE, \
&UA_TYPES[TYPE]); \
} while(0)
ADD_OPEN_PARAM("address", &address, UA_TYPES_STRING);
ADD_OPEN_PARAM("port", &port, UA_TYPES_UINT16);
ADD_OPEN_PARAM("timeout", &timeout, UA_TYPES_UINT16);
ADD_OPEN_PARAM("useSSL", &useSSL, UA_TYPES_BOOLEAN);
if(caCertificate)
ADD_OPEN_PARAM("ca-certificate", (void *)(uintptr_t)caCertificate,
UA_TYPES_BYTESTRING);
#undef ADD_OPEN_PARAM
UA_KeyValueMap map = {paramsSize, params};
clientConnectionId = 0;
ck_assert_uint_eq(http->openConnection(http, &map, NULL, NULL,
clientCallback),
UA_STATUSCODE_GOOD);
ck_assert_uint_ne(clientConnectionId, 0);
}
static const UA_String *
getResponseHeader(const UA_KeyValueMap *params, const char *name) {
const UA_Variant *v =
UA_KeyValueMap_get(params, UA_QUALIFIEDNAME(0, "headers"));
if(!v || !UA_Variant_hasArrayType(v, &UA_TYPES[UA_TYPES_KEYVALUEPAIR]))
return NULL;
UA_String headerName = UA_STRING((char *)(uintptr_t)name);
const UA_KeyValuePair *headers = (const UA_KeyValuePair *)v->data;
for(size_t i = 0; i < v->arrayLength; i++) {
if(UA_String_equal(&headers[i].key.name, &headerName) &&
UA_Variant_hasScalarType(&headers[i].value,
&UA_TYPES[UA_TYPES_STRING]))
return (const UA_String *)headers[i].value.data;
}
return NULL;
}
static UA_ByteString loadFile(const char *path) {
UA_ByteString out = UA_BYTESTRING_NULL;
FILE *f = fopen(path, "rb");
if(!f || fseek(f, 0, SEEK_END) || (out.length = (size_t)ftell(f)) == 0 ||
fseek(f, 0, SEEK_SET) ||
UA_ByteString_allocBuffer(&out, out.length) != UA_STATUSCODE_GOOD ||
fread(out.data, 1, out.length, f) != out.length)
UA_ByteString_clear(&out);
if(f)
fclose(f);
return out;
}
static void clientCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
void *application, void **connectionContext,
UA_ConnectionState state,
const UA_KeyValueMap *params, UA_ByteString msg) {
(void)cm;
(void)application;
(void)connectionContext;
if(state == UA_CONNECTIONSTATE_OPENING) {
clientConnectionId = connectionId;
return;
}
if(state != UA_CONNECTIONSTATE_ESTABLISHED)
return;
const UA_Boolean *complete =
(const UA_Boolean *)UA_KeyValueMap_getScalar(
params, UA_QUALIFIEDNAME(0, "response-complete"),
&UA_TYPES[UA_TYPES_BOOLEAN]);
if(complete && *complete)
responseComplete = true;
const UA_UInt16 *status = (const UA_UInt16 *)UA_KeyValueMap_getScalar(
params, UA_QUALIFIEDNAME(0, "status-code"), &UA_TYPES[UA_TYPES_UINT16]);
if(status)
responseStatus = *status;
const UA_String *contentType = getResponseHeader(params, "content-type");
const UA_String json = UA_STRING_STATIC("application/json");
if(contentType && UA_String_equal(contentType, &json))
responseIsJson = true;
const UA_String *contentEncoding =
getResponseHeader(params, "content-encoding");
const UA_String gzip = UA_STRING_STATIC("gzip");
if(contentEncoding && UA_String_equal_ignorecase(contentEncoding, &gzip))
responseIsGzip = true;
if(msg.length == 0)
return;
size_t oldLength = responseBody.length;
UA_Byte *data =
(UA_Byte *)UA_realloc(responseBody.data, oldLength + msg.length);
ck_assert_ptr_nonnull(data);
responseBody.data = data;
memcpy(&responseBody.data[oldLength], msg.data, msg.length);
responseBody.length += msg.length;
}
static UA_ByteString
encodeServiceRequest(const void *request, const UA_DataType *requestType) {
size_t size = UA_calcSizeBinary(
&requestType->binaryEncodingId, &UA_TYPES[UA_TYPES_NODEID], NULL) +
UA_calcSizeBinary(request, requestType, NULL);
UA_ByteString body;
ck_assert_uint_eq(UA_ByteString_allocBuffer(&body, size),
UA_STATUSCODE_GOOD);
UA_Byte *pos = body.data;
const UA_Byte *end = &body.data[body.length];
ck_assert_uint_eq(UA_encodeBinaryInternal(
&requestType->binaryEncodingId,
&UA_TYPES[UA_TYPES_NODEID],
&pos, &end, NULL, NULL, NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_encodeBinaryInternal(
request, requestType, &pos, &end,
NULL, NULL, NULL), UA_STATUSCODE_GOOD);
return body;
}
#ifdef UA_ENABLE_JSON_ENCODING
static UA_ByteString
encodeServiceRequestJson(const void *request, const UA_DataType *requestType) {
UA_ExtensionObject envelope;
UA_ExtensionObject_setValueNoDelete(
&envelope, (void *)(uintptr_t)request, requestType);
UA_EncodeJsonOptions options;
memset(&options, 0, sizeof(options));
UA_ByteString body = UA_BYTESTRING_NULL;
ck_assert_uint_eq(
UA_encodeJson(&envelope, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT], &body,
&options),
UA_STATUSCODE_GOOD);
return body;
}
#endif
static void
sendHttpPost(UA_ConnectionManager *http, UA_EventLoop *eventLoop,
const UA_String *path, UA_KeyValuePair *headers,
size_t headersSize, UA_ByteString *body,
UA_Boolean expectResponseBody);
#ifdef UA_ENABLE_HTTP_COMPRESSION
static void
sendCodedServiceRequest(UA_ConnectionManager *http, UA_EventLoop *eventLoop,
const UA_String *path, const void *request,
const UA_DataType *requestType, UA_Boolean json,
UA_HTTPContentEncoding requestEncoding,
const char *acceptEncoding, UA_UInt16 expectedStatus) {
UA_ByteString body;
#ifdef UA_ENABLE_JSON_ENCODING
if(json)
body = encodeServiceRequestJson(request, requestType);
else
#else
ck_assert(!json);
#endif
body = encodeServiceRequest(request, requestType);
UA_ByteString compressed = UA_BYTESTRING_NULL;
if(requestEncoding != UA_HTTP_CONTENT_ENCODING_IDENTITY) {
ck_assert_uint_eq(UA_HTTP_compress(requestEncoding, &body, &compressed),
UA_STATUSCODE_GOOD);
UA_ByteString_clear(&body);
body = compressed;
}
UA_String contentType = json ? UA_STRING("application/json") :
UA_STRING("application/octet-stream");
UA_String contentCoding = UA_STRING((char *)(uintptr_t)
UA_HTTP_contentEncodingName(requestEncoding));
UA_String accept = UA_STRING((char *)(uintptr_t)acceptEncoding);
UA_KeyValuePair headers[3] = {0};
headers[0].key = UA_QUALIFIEDNAME(0, "content-type");
UA_Variant_setScalar(&headers[0].value, &contentType,
&UA_TYPES[UA_TYPES_STRING]);
headers[1].key = UA_QUALIFIEDNAME(0, "content-encoding");
UA_Variant_setScalar(&headers[1].value, &contentCoding,
&UA_TYPES[UA_TYPES_STRING]);
headers[2].key = UA_QUALIFIEDNAME(0, "accept-encoding");
UA_Variant_setScalar(&headers[2].value, &accept,
&UA_TYPES[UA_TYPES_STRING]);
sendHttpPost(http, eventLoop, path, headers, 3, &body, false);
ck_assert_uint_eq(responseStatus, expectedStatus);
if(expectedStatus == 200)
ck_assert_uint_gt(responseBody.length, 0);
}
#endif
static void
startHttpPost(UA_ConnectionManager *http, const UA_String *path,
UA_KeyValuePair *headers, size_t headersSize,
UA_ByteString *body) {
UA_ByteString_clear(&responseBody);
responseStatus = 0;
responseIsJson = false;
responseIsGzip = false;
responseComplete = false;
UA_String method = UA_STRING("POST");
UA_KeyValuePair sendParams[3] = {0};
sendParams[0].key = UA_QUALIFIEDNAME(0, "path");
UA_Variant_setScalar(&sendParams[0].value, (void *)(uintptr_t)path,
&UA_TYPES[UA_TYPES_STRING]);
sendParams[1].key = UA_QUALIFIEDNAME(0, "method");
UA_Variant_setScalar(&sendParams[1].value, &method,
&UA_TYPES[UA_TYPES_STRING]);
sendParams[2].key = UA_QUALIFIEDNAME(0, "headers");
UA_Variant_setArray(&sendParams[2].value, headers, headersSize,
&UA_TYPES[UA_TYPES_KEYVALUEPAIR]);
UA_KeyValueMap sendMap = {3, sendParams};
ck_assert_uint_eq(http->sendWithConnection(http, clientConnectionId,
&sendMap, body),
UA_STATUSCODE_GOOD);
}
static void
sendHttpPost(UA_ConnectionManager *http, UA_EventLoop *eventLoop,
const UA_String *path, UA_KeyValuePair *headers,
size_t headersSize, UA_ByteString *body,
UA_Boolean expectResponseBody) {
startHttpPost(http, path, headers, headersSize, body);
for(size_t i = 0; i < 300 && !responseComplete; i++)
eventLoop->run(eventLoop, 20);
ck_assert(responseComplete);
if(expectResponseBody && responseStatus == 200)
ck_assert_uint_gt(responseBody.length, 0);
}
#ifdef UA_ENABLE_SUBSCRIPTIONS
static void
startBinaryServiceRequest(UA_ConnectionManager *http, const UA_String *path,
const void *request,
const UA_DataType *requestType) {
UA_String contentType = UA_STRING("application/octet-stream");
UA_KeyValuePair header = {UA_QUALIFIEDNAME(0, "content-type"), {0}};
UA_Variant_setScalar(&header.value, &contentType,
&UA_TYPES[UA_TYPES_STRING]);
UA_ByteString requestBody = encodeServiceRequest(request, requestType);
startHttpPost(http, path, &header, 1, &requestBody);
}
#endif
static void
sendServiceRequestWithEncoding(UA_ConnectionManager *http,
UA_EventLoop *eventLoop,
const UA_String *path, const void *request,
const UA_DataType *requestType,
UA_Boolean json) {
UA_String contentType = json ? UA_STRING("application/json; charset=utf-8")
: UA_STRING("application/octet-stream");
UA_KeyValuePair header = {UA_QUALIFIEDNAME(0, "content-type"), {0}};
UA_Variant_setScalar(&header.value, &contentType,
&UA_TYPES[UA_TYPES_STRING]);
UA_ByteString requestBody;
#ifdef UA_ENABLE_JSON_ENCODING
if(json)
requestBody = encodeServiceRequestJson(request, requestType);
else
#else
ck_assert(!json);
#endif
requestBody = encodeServiceRequest(request, requestType);
sendHttpPost(http, eventLoop, path, &header, 1, &requestBody, true);
ck_assert_uint_eq(responseStatus, 200);
ck_assert_uint_gt(responseBody.length, 0);
ck_assert_uint_eq(responseIsJson, json);
}
static void
sendServiceRequest(UA_ConnectionManager *http, UA_EventLoop *eventLoop,
const UA_String *path, const void *request,
const UA_DataType *requestType) {
sendServiceRequestWithEncoding(http, eventLoop, path, request, requestType,
false);
}
#ifdef UA_ENABLE_ENCRYPTION
static void
sendSecureServiceRequest(UA_ConnectionManager *http, UA_EventLoop *eventLoop,
const UA_String *path, const UA_String *policyUri,
const void *request, const UA_DataType *requestType) {
UA_String contentType = UA_STRING("application/octet-stream");
UA_KeyValuePair headers[2] = {0};
headers[0].key = UA_QUALIFIEDNAME(0, "content-type");
UA_Variant_setScalar(&headers[0].value, &contentType,
&UA_TYPES[UA_TYPES_STRING]);
headers[1].key = UA_QUALIFIEDNAME(0, "opcua-securitypolicy");
UA_Variant_setScalar(&headers[1].value, (void*)(uintptr_t)policyUri,
&UA_TYPES[UA_TYPES_STRING]);
UA_ByteString body = encodeServiceRequest(request, requestType);
sendHttpPost(http, eventLoop, path, headers, 2, &body, true);
ck_assert_uint_eq(responseStatus, 200);
ck_assert_uint_gt(responseBody.length, 0);
}
static void
setLegacyClientSignature(UA_SecurityPolicy *policy, void *channelContext,
const UA_ByteString *serverCertificate,
const UA_ByteString *serverNonce,
UA_SignatureData *signature) {
ck_assert_uint_eq(
UA_String_copy(&policy->asymSignatureAlgorithm.uri,
&signature->algorithm),
UA_STATUSCODE_GOOD);
size_t signatureSize =
policy->asymSignatureAlgorithm.getLocalSignatureSize(policy,
channelContext);
ck_assert_uint_eq(UA_ByteString_allocBuffer(&signature->signature,
signatureSize),
UA_STATUSCODE_GOOD);
UA_ByteString data = UA_BYTESTRING_NULL;
ck_assert_uint_le(serverCertificate->length,
SIZE_MAX - serverNonce->length);
ck_assert_uint_eq(UA_ByteString_allocBuffer(
&data, serverCertificate->length +
serverNonce->length),
UA_STATUSCODE_GOOD);
memcpy(data.data, serverCertificate->data, serverCertificate->length);
memcpy(data.data + serverCertificate->length, serverNonce->data,
serverNonce->length);
ck_assert_uint_eq(policy->asymSignatureAlgorithm.sign(
policy, channelContext, &data,
&signature->signature),
UA_STATUSCODE_GOOD);
UA_ByteString_clear(&data);
}
#endif
static void
sendInvalidRequest(UA_ConnectionManager *http, UA_EventLoop *eventLoop,
const UA_String *path, const char *contentTypeValue,
const char *contentEncodingValue, const char *payload,
UA_UInt16 expectedStatus) {
UA_String contentType = UA_STRING((char *)(uintptr_t)contentTypeValue);
UA_String contentEncoding = UA_STRING_NULL;
UA_KeyValuePair headers[2] = {0};
headers[0].key = UA_QUALIFIEDNAME(0, "content-type");
UA_Variant_setScalar(&headers[0].value, &contentType,
&UA_TYPES[UA_TYPES_STRING]);
size_t headersSize = 1;
if(contentEncodingValue) {
contentEncoding =
UA_STRING((char *)(uintptr_t)contentEncodingValue);
headers[1].key = UA_QUALIFIEDNAME(0, "content-encoding");
UA_Variant_setScalar(&headers[1].value, &contentEncoding,
&UA_TYPES[UA_TYPES_STRING]);
headersSize++;
}
UA_ByteString body = UA_BYTESTRING_ALLOC((char *)(uintptr_t)payload);
sendHttpPost(http, eventLoop, path, headers, headersSize, &body, false);
ck_assert_uint_eq(responseStatus, expectedStatus);
ck_assert_uint_eq(responseBody.length, 0);
}
#ifdef UA_ENABLE_JSON_ENCODING
static void
sendServiceRequestJson(UA_ConnectionManager *http, UA_EventLoop *eventLoop,
const UA_String *path, const void *request,
const UA_DataType *requestType) {
sendServiceRequestWithEncoding(http, eventLoop, path, request, requestType,
true);
}
static void
decodeJsonResponse(const UA_DataType *expectedType, void *response) {
UA_DecodeJsonOptions options;
memset(&options, 0, sizeof(options));
UA_ExtensionObject envelope;
ck_assert_uint_eq(
UA_decodeJson(&responseBody, &envelope,
&UA_TYPES[UA_TYPES_EXTENSIONOBJECT], &options),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(envelope.encoding, UA_EXTENSIONOBJECT_DECODED);
ck_assert_ptr_eq(envelope.content.decoded.type, expectedType);
ck_assert_uint_eq(UA_copy(envelope.content.decoded.data, response,
expectedType),
UA_STATUSCODE_GOOD);
UA_ExtensionObject_clear(&envelope);
}
#endif
static void
decodeResponseType(const UA_DataType *expectedType, size_t *offset) {
UA_NodeId responseType;
ck_assert_uint_eq(UA_decodeBinaryInternal(&responseBody, offset,
&responseType,
&UA_TYPES[UA_TYPES_NODEID], NULL),
UA_STATUSCODE_GOOD);
ck_assert(UA_NodeId_equal(&responseType, &expectedType->binaryEncodingId));
UA_NodeId_clear(&responseType);
}
START_TEST(binaryServiceOverHttps) {
clientConnectionId = 0;
responseStatus = 0;
responseBody = UA_BYTESTRING_NULL;
UA_Server *server = UA_Server_new();
ck_assert_ptr_nonnull(server);
UA_ServerConfig *config = UA_Server_getConfig(server);
config->tcpEnabled = false;
config->httpEnabled = true;
config->httpCertificate = loadFile("server_cert.der");
config->httpPrivateKey = loadFile("server_key.der");
config->httpListenAddress = UA_STRING_ALLOC("");
ck_assert_uint_gt(config->httpCertificate.length, 0);
ck_assert_uint_gt(config->httpPrivateKey.length, 0);
ck_assert_ptr_nonnull(config->httpListenAddress.data);
UA_UsernamePasswordLogin login = {
UA_STRING_STATIC("user"), UA_STRING_STATIC("password")};
ck_assert_uint_eq(UA_AccessControl_default(
config, true, &UA_SECURITY_POLICY_NONE_URI,
1, &login),
UA_STATUSCODE_GOOD);
UA_Array_delete(config->serverUrls, config->serverUrlsSize,
&UA_TYPES[UA_TYPES_STRING]);
config->serverUrls =
(UA_String *)UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]);
ck_assert_ptr_nonnull(config->serverUrls);
config->serverUrlsSize = 1;
config->serverUrls[0] = UA_STRING_ALLOC("opc.https://127.0.0.1:0/ua");
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
UA_Boolean foundHttpDriver = false;
UA_String httpDriverName = UA_STRING("opc-http");
for(UA_Driver *drv = UA_Server_getDrivers(server); drv; drv = drv->next) {
if(UA_String_equal(&drv->name, &httpDriverName)) {
foundHttpDriver = true;
ck_assert_uint_eq(drv->state, UA_LIFECYCLESTATE_STARTED);
}
}
ck_assert(foundHttpDriver);
UA_UInt16 port = 0;
UA_String advertisedUrl = UA_STRING_NULL;
for(size_t i = 0; i < config->applicationDescription.discoveryUrlsSize;
i++) {
const UA_String *url = &config->applicationDescription.discoveryUrls[i];
if(url->length >= 12 && memcmp(url->data, "opc.https://", 12) == 0) {
UA_String hostname = UA_STRING_NULL;
UA_String urlPath = UA_STRING_NULL;
ck_assert_uint_eq(
UA_parseEndpointUrl(url, &hostname, &port, &urlPath),
UA_STATUSCODE_GOOD);
const UA_String expectedHostname = UA_STRING_STATIC("127.0.0.1");
const UA_String expectedPath = UA_STRING_STATIC("ua");
ck_assert(UA_String_equal(&hostname, &expectedHostname));
ck_assert(UA_String_equal(&urlPath, &expectedPath));
ck_assert_uint_eq(UA_String_copy(url, &advertisedUrl),
UA_STATUSCODE_GOOD);
break;
}
}
ck_assert_uint_ne(port, 0);
char *publicEndpoint = (char *)UA_malloc(advertisedUrl.length + 1);
ck_assert_ptr_nonnull(publicEndpoint);
memcpy(publicEndpoint, advertisedUrl.data, advertisedUrl.length);
publicEndpoint[advertisedUrl.length] = 0;
UA_ClientConfig publicConfig;
memset(&publicConfig, 0, sizeof(publicConfig));
publicConfig.eventLoop = config->eventLoop;
publicConfig.externalEventLoop = true;
ck_assert_uint_eq(UA_ClientConfig_setDefault(&publicConfig),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_ByteString_copy(&config->httpCertificate,
&publicConfig.httpCaCertificate),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_ClientConfig_setAuthenticationUsername(
&publicConfig, "user", "password"),
UA_STATUSCODE_GOOD);
UA_Client *publicClient = UA_Client_newWithConfig(&publicConfig);
ck_assert_ptr_nonnull(publicClient);
ck_assert_uint_eq(UA_Client_connect(publicClient, publicEndpoint),
UA_STATUSCODE_GOOD);
UA_MessageSecurityMode publicMode = UA_MESSAGESECURITYMODE_INVALID;
UA_QualifiedName modeKey = UA_QUALIFIEDNAME(0, "securityMode");
ck_assert_uint_eq(UA_Client_getConnectionAttribute_scalar(
publicClient, modeKey,
&UA_TYPES[UA_TYPES_MESSAGESECURITYMODE],
&publicMode),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(publicMode,
UA_MESSAGESECURITYMODE_SIGNANDENCRYPT);
UA_Boolean foundRemoteAddress = false;
lockServer(server);
UA_SecureChannel *publicChannel;
TAILQ_FOREACH(publicChannel, &server->channels, serverEntry) {
if(publicChannel->transport != UA_SECURECHANNEL_TRANSPORT_HTTP ||
!publicChannel->sessions)
continue;
ck_assert_uint_eq(publicChannel->securityMode,
UA_MESSAGESECURITYMODE_SIGNANDENCRYPT);
foundRemoteAddress |= publicChannel->remoteAddress.length > 0;
}
unlockServer(server);
ck_assert(foundRemoteAddress);
UA_Variant publicValue;
UA_Variant_init(&publicValue);
ck_assert_uint_eq(UA_Client_readValueAttribute(
publicClient,
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE),
&publicValue), UA_STATUSCODE_GOOD);
ck_assert(UA_Variant_isScalar(&publicValue));
UA_Variant_clear(&publicValue);
ck_assert_uint_eq(UA_Client_disconnect(publicClient), UA_STATUSCODE_GOOD);
UA_Client_delete(publicClient);
UA_free(publicEndpoint);
#ifdef UA_ENABLE_JSON_ENCODING
/* Binary and JSON are separate endpoints so a logical SecureChannel is
* pinned to one encoding for its entire lifetime. */
UA_String jsonProfile = UA_STRING(
"http://opcfoundation.org/UA-Profile/Transport/https-uajson");
UA_EndpointDescription *jsonEndpoints = NULL;
size_t jsonEndpointsSize = 0;
lockServer(server);
ck_assert_uint_eq(
setCurrentEndpointsArray(server, advertisedUrl, &jsonProfile, 1,
&jsonEndpoints, &jsonEndpointsSize),
UA_STATUSCODE_GOOD);
unlockServer(server);
ck_assert_uint_gt(jsonEndpointsSize, 0);
const UA_String jsonSuffix = UA_STRING_STATIC("/json");
for(size_t i = 0; i < jsonEndpointsSize; i++) {
ck_assert(UA_String_equal(&jsonEndpoints[i].transportProfileUri,
&jsonProfile));
ck_assert_uint_ge(jsonEndpoints[i].endpointUrl.length,
jsonSuffix.length);
ck_assert_mem_eq(
&jsonEndpoints[i].endpointUrl.data[
jsonEndpoints[i].endpointUrl.length - jsonSuffix.length],
jsonSuffix.data, jsonSuffix.length);
}
UA_Array_delete(jsonEndpoints, jsonEndpointsSize,
&UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
#endif
UA_ConnectionManager *http = getHttpConnectionManager(config);
ck_assert_ptr_nonnull(http);
openRawHttpClient(http, port, true, &config->httpCertificate);
UA_String path = UA_STRING("/ua");
#ifdef UA_ENABLE_JSON_ENCODING
UA_String jsonPath = UA_STRING("/ua/json");
sendInvalidRequest(http, config->eventLoop, &jsonPath, "application/json",
NULL, "{", 400);
sendInvalidRequest(http, config->eventLoop, &path, "application/json",
NULL, "{}", 415);
sendInvalidRequest(http, config->eventLoop, &jsonPath,
"application/octet-stream", NULL, "invalid", 415);
#endif
sendInvalidRequest(http, config->eventLoop, &path,
"application/octet-stream", "gzip", "not-gzip",
#ifdef UA_ENABLE_HTTP_COMPRESSION
400);
#else
415);
#endif
UA_FindServersRequest findRequest;
UA_FindServersRequest_init(&findRequest);
findRequest.requestHeader.timestamp = UA_DateTime_now();
findRequest.requestHeader.requestHandle = 42;
#ifdef UA_ENABLE_HTTP_COMPRESSION
/* OPC UA Binary permits only identity coding. JSON permits gzip, but not
* the generic HTTP manager's deflate extension. */
sendCodedServiceRequest(http, config->eventLoop, &path, &findRequest,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST], false,
UA_HTTP_CONTENT_ENCODING_GZIP, "identity", 415);
sendCodedServiceRequest(http, config->eventLoop, &path, &findRequest,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST], false,
UA_HTTP_CONTENT_ENCODING_IDENTITY,
"gzip, identity;q=0", 406);
# ifdef UA_ENABLE_JSON_ENCODING
sendCodedServiceRequest(http, config->eventLoop, &jsonPath, &findRequest,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST], true,
UA_HTTP_CONTENT_ENCODING_DEFLATE, "identity", 415);
sendCodedServiceRequest(http, config->eventLoop, &jsonPath, &findRequest,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST], true,
UA_HTTP_CONTENT_ENCODING_GZIP,
"gzip, identity;q=0", 200);
ck_assert(responseIsGzip);
# endif
#endif
/* Header names are case-insensitive. Duplicate single-valued OPC UA
* routing headers are rejected before dispatch. */
UA_String mixedContentType = UA_STRING("application/octet-stream");
UA_KeyValuePair mixedHeader = {
UA_QUALIFIEDNAME(0, "CoNtEnT-TyPe"), {0}};
UA_Variant_setScalar(&mixedHeader.value, &mixedContentType,
&UA_TYPES[UA_TYPES_STRING]);
UA_ByteString mixedBody = encodeServiceRequest(
&findRequest, &UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
sendHttpPost(http, config->eventLoop, &path, &mixedHeader, 1,
&mixedBody, true);
ck_assert_uint_eq(responseStatus, 200);
UA_String duplicateType = UA_STRING("application/octet-stream");
UA_KeyValuePair duplicateHeaders[2] = {0};
duplicateHeaders[0].key = UA_QUALIFIEDNAME(0, "content-type");
duplicateHeaders[1].key = UA_QUALIFIEDNAME(0, "Content-Type");
UA_Variant_setScalar(&duplicateHeaders[0].value, &duplicateType,
&UA_TYPES[UA_TYPES_STRING]);
UA_Variant_setScalar(&duplicateHeaders[1].value, &duplicateType,
&UA_TYPES[UA_TYPES_STRING]);
UA_ByteString duplicateBody = encodeServiceRequest(
&findRequest, &UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
sendHttpPost(http, config->eventLoop, &path, duplicateHeaders, 2,
&duplicateBody, false);
ck_assert_uint_eq(responseStatus, 400);
UA_String nonePolicy = UA_SECURITY_POLICY_NONE_URI;
UA_KeyValuePair duplicatePolicyHeaders[3] = {0};
duplicatePolicyHeaders[0].key = UA_QUALIFIEDNAME(0, "content-type");
UA_Variant_setScalar(&duplicatePolicyHeaders[0].value, &duplicateType,
&UA_TYPES[UA_TYPES_STRING]);
duplicatePolicyHeaders[1].key =
UA_QUALIFIEDNAME(0, "opcua-securitypolicy");
duplicatePolicyHeaders[2].key =
UA_QUALIFIEDNAME(0, "OPCUA-SecurityPolicy");
UA_Variant_setScalar(&duplicatePolicyHeaders[1].value, &nonePolicy,
&UA_TYPES[UA_TYPES_STRING]);
UA_Variant_setScalar(&duplicatePolicyHeaders[2].value, &nonePolicy,
&UA_TYPES[UA_TYPES_STRING]);
duplicateBody = encodeServiceRequest(
&findRequest, &UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
sendHttpPost(http, config->eventLoop, &path, duplicatePolicyHeaders, 3,
&duplicateBody, false);
ck_assert_uint_eq(responseStatus, 400);
/* One HTTP body contains exactly one encoded OPC UA service request. */
UA_ByteString trailingBody = encodeServiceRequest(
&findRequest, &UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
UA_Byte *trailingData = (UA_Byte*)UA_realloc(
trailingBody.data, trailingBody.length + 1);
ck_assert_ptr_nonnull(trailingData);
trailingBody.data = trailingData;
trailingBody.data[trailingBody.length++] = 0xff;
UA_String binaryContentType = UA_STRING("application/octet-stream");
UA_KeyValuePair binaryHeader = {
UA_QUALIFIEDNAME(0, "content-type"), {0}};
UA_Variant_setScalar(&binaryHeader.value, &binaryContentType,
&UA_TYPES[UA_TYPES_STRING]);
sendHttpPost(http, config->eventLoop, &path, &binaryHeader, 1,
&trailingBody, false);
ck_assert_uint_eq(responseStatus, 400);
ck_assert_uint_eq(responseBody.length, 0);
sendServiceRequest(http, config->eventLoop, &path, &findRequest,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
size_t offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_FINDSERVERSRESPONSE], &offset);
UA_FindServersResponse response;
UA_FindServersResponse_init(&response);
ck_assert_uint_eq(
UA_decodeBinaryInternal(&responseBody, &offset, &response,
&UA_TYPES[UA_TYPES_FINDSERVERSRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(response.responseHeader.requestHandle, 42);
ck_assert_uint_eq(response.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
ck_assert_uint_gt(response.serversSize, 0);
UA_Boolean foundAdvertisedUrl = false;
for(size_t i = 0; i < response.serversSize; i++) {
for(size_t j = 0; j < response.servers[i].discoveryUrlsSize; j++) {
if(UA_String_equal(&response.servers[i].discoveryUrls[j],
&advertisedUrl))
foundAdvertisedUrl = true;
}
}
ck_assert(foundAdvertisedUrl);
UA_FindServersResponse_clear(&response);
#ifdef UA_ENABLE_JSON_ENCODING
sendServiceRequestJson(http, config->eventLoop, &jsonPath, &findRequest,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
UA_FindServersResponse_init(&response);
decodeJsonResponse(&UA_TYPES[UA_TYPES_FINDSERVERSRESPONSE], &response);
ck_assert_uint_eq(response.responseHeader.requestHandle, 42);
ck_assert_uint_eq(response.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
UA_FindServersResponse_clear(&response);
#endif
/* CreateSession, ActivateSession and Read use separate HTTP requests but
* must resolve to one persistent logical SecureChannel. */
UA_CreateSessionRequest createRequest;
UA_CreateSessionRequest_init(&createRequest);
createRequest.requestHeader.timestamp = UA_DateTime_now();
createRequest.requestHeader.requestHandle = 43;
createRequest.clientDescription.applicationType = UA_APPLICATIONTYPE_CLIENT;
createRequest.clientDescription.applicationUri =
UA_STRING_ALLOC("urn:open62541:http-test-client");
createRequest.clientDescription.productUri =
UA_STRING_ALLOC("urn:open62541:http-test");
createRequest.clientDescription.applicationName =
UA_LOCALIZEDTEXT_ALLOC("en", "HTTP test client");
ck_assert_uint_eq(UA_String_copy(&advertisedUrl,
&createRequest.endpointUrl),
UA_STATUSCODE_GOOD);
createRequest.sessionName = UA_STRING_ALLOC("HTTP test session");
createRequest.requestedSessionTimeout = 60000.0;
createRequest.maxResponseMessageSize = 0;
sendServiceRequest(http, config->eventLoop, &path, &createRequest,
&UA_TYPES[UA_TYPES_CREATESESSIONREQUEST]);
UA_CreateSessionRequest_clear(&createRequest);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE], &offset);
UA_CreateSessionResponse createResponse;
UA_CreateSessionResponse_init(&createResponse);
ck_assert_uint_eq(
UA_decodeBinaryInternal(&responseBody, &offset, &createResponse,
&UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(createResponse.responseHeader.requestHandle, 43);
ck_assert_uint_eq(createResponse.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(createResponse.authenticationToken.identifierType,
UA_NODEIDTYPE_BYTESTRING);
ck_assert_uint_ge(
createResponse.authenticationToken.identifier.byteString.length, 32);
UA_NodeId authenticationToken;
UA_NodeId_init(&authenticationToken);
ck_assert_uint_eq(UA_NodeId_copy(&createResponse.authenticationToken,
&authenticationToken),
UA_STATUSCODE_GOOD);
UA_String anonymousPolicyId = UA_STRING_NULL;
UA_Boolean foundHttpsBinaryProfile = false;
#ifdef UA_ENABLE_JSON_ENCODING
UA_Boolean foundHttpsJsonProfile = false;
#endif
const UA_String httpsBinaryProfile = UA_STRING_STATIC(
"http://opcfoundation.org/UA-Profile/Transport/https-uabinary");
#ifdef UA_ENABLE_JSON_ENCODING
const UA_String httpsJsonProfile = UA_STRING_STATIC(
"http://opcfoundation.org/UA-Profile/Transport/https-uajson");
#endif
for(size_t i = 0; i < createResponse.serverEndpointsSize; i++) {
UA_EndpointDescription *ed = &createResponse.serverEndpoints[i];
if(UA_String_equal(&ed->transportProfileUri, &httpsBinaryProfile)) {
ck_assert_uint_eq(ed->securityMode,
UA_MESSAGESECURITYMODE_SIGNANDENCRYPT);
foundHttpsBinaryProfile = true;
}
#ifdef UA_ENABLE_JSON_ENCODING
if(UA_String_equal(&ed->transportProfileUri, &httpsJsonProfile)) {
ck_assert_uint_eq(ed->securityMode,
UA_MESSAGESECURITYMODE_SIGNANDENCRYPT);
foundHttpsJsonProfile = true;
}
#endif
for(size_t j = 0; j < ed->userIdentityTokensSize; j++) {
UA_UserTokenPolicy *utp = &ed->userIdentityTokens[j];
if(utp->tokenType == UA_USERTOKENTYPE_ANONYMOUS &&
anonymousPolicyId.length == 0)
ck_assert_uint_eq(UA_String_copy(&utp->policyId,
&anonymousPolicyId),
UA_STATUSCODE_GOOD);
}
}
ck_assert_uint_gt(anonymousPolicyId.length, 0);
ck_assert(foundHttpsBinaryProfile);
#ifdef UA_ENABLE_JSON_ENCODING
ck_assert(foundHttpsJsonProfile);
#endif
UA_CreateSessionResponse_clear(&createResponse);
UA_ActivateSessionRequest activateRequest;
UA_ActivateSessionRequest_init(&activateRequest);
activateRequest.requestHeader.timestamp = UA_DateTime_now();
activateRequest.requestHeader.requestHandle = 44;
ck_assert_uint_eq(UA_NodeId_copy(&authenticationToken,
&activateRequest.requestHeader.authenticationToken),
UA_STATUSCODE_GOOD);
UA_AnonymousIdentityToken anonymousToken;
UA_AnonymousIdentityToken_init(&anonymousToken);
ck_assert_uint_eq(UA_String_copy(&anonymousPolicyId,
&anonymousToken.policyId),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(
UA_ExtensionObject_setValueCopy(
&activateRequest.userIdentityToken, &anonymousToken,
&UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN]),
UA_STATUSCODE_GOOD);
UA_AnonymousIdentityToken_clear(&anonymousToken);
#ifdef UA_ENABLE_JSON_ENCODING
/* An AuthenticationToken cannot move from its Binary channel to the JSON
* endpoint. The request gets a JSON ServiceFault and the Binary channel
* remains usable for the subsequent activation. */
sendServiceRequestJson(http, config->eventLoop, &jsonPath,
&activateRequest,
&UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST]);
UA_ServiceFault crossEncodingFault;
UA_ServiceFault_init(&crossEncodingFault);
decodeJsonResponse(&UA_TYPES[UA_TYPES_SERVICEFAULT], &crossEncodingFault);
ck_assert_uint_eq(
crossEncodingFault.responseHeader.serviceResult,
UA_STATUSCODE_BADSESSIONIDINVALID);
UA_ServiceFault_clear(&crossEncodingFault);
#endif
sendServiceRequest(http, config->eventLoop, &path, &activateRequest,
&UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST]);
UA_ActivateSessionRequest_clear(&activateRequest);
UA_ActivateSessionResponse activateResponse;
UA_ActivateSessionResponse_init(&activateResponse);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE], &offset);
ck_assert_uint_eq(
UA_decodeBinaryInternal(&responseBody, &offset, &activateResponse,
&UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(activateResponse.responseHeader.requestHandle, 44);
ck_assert_uint_eq(activateResponse.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
UA_ActivateSessionResponse_clear(&activateResponse);
/* Losing the HTTP carrier must not tear down the logical channel selected
* by the AuthenticationToken. Reopen the transport and continue the same
* Session below. */
UA_UInt32 logicalChannelId = 0;
lockServer(server);
UA_Session *sessionBeforeReconnect =
getSessionByToken(server, &authenticationToken);
ck_assert_ptr_nonnull(sessionBeforeReconnect);
ck_assert_ptr_nonnull(sessionBeforeReconnect->channel);
ck_assert_uint_eq(sessionBeforeReconnect->channel->transport,
UA_SECURECHANNEL_TRANSPORT_HTTP);
ck_assert_uint_eq(sessionBeforeReconnect->channel->encoding,
UA_SECURECHANNEL_ENCODING_BINARY);
logicalChannelId = sessionBeforeReconnect->channel->securityToken.channelId;
unlockServer(server);
ck_assert_uint_eq(http->closeConnection(http, clientConnectionId),
UA_STATUSCODE_GOOD);
for(size_t i = 0; i < 20; i++)
config->eventLoop->run(config->eventLoop, 20);
openRawHttpClient(http, port, true, &config->httpCertificate);
lockServer(server);
UA_Session *sessionAfterReconnect =
getSessionByToken(server, &authenticationToken);
ck_assert_ptr_nonnull(sessionAfterReconnect);
ck_assert_ptr_nonnull(sessionAfterReconnect->channel);
ck_assert_uint_eq(sessionAfterReconnect->channel->securityToken.channelId,
logicalChannelId);
unlockServer(server);
UA_ReadRequest readRequest;
UA_ReadRequest_init(&readRequest);
readRequest.requestHeader.timestamp = UA_DateTime_now();
readRequest.requestHeader.requestHandle = 45;
ck_assert_uint_eq(UA_NodeId_copy(&authenticationToken,
&readRequest.requestHeader.authenticationToken),
UA_STATUSCODE_GOOD);
readRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_NEITHER;
readRequest.nodesToRead =
(UA_ReadValueId *)UA_Array_new(1, &UA_TYPES[UA_TYPES_READVALUEID]);
ck_assert_ptr_nonnull(readRequest.nodesToRead);
readRequest.nodesToReadSize = 1;
readRequest.nodesToRead[0].nodeId =
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE);
readRequest.nodesToRead[0].attributeId = UA_ATTRIBUTEID_VALUE;
sendServiceRequest(http, config->eventLoop, &path, &readRequest,
&UA_TYPES[UA_TYPES_READREQUEST]);
UA_ReadRequest_clear(&readRequest);
UA_ReadResponse readResponse;
UA_ReadResponse_init(&readResponse);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_READRESPONSE], &offset);
ck_assert_uint_eq(
UA_decodeBinaryInternal(&responseBody, &offset, &readResponse,
&UA_TYPES[UA_TYPES_READRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(readResponse.responseHeader.requestHandle, 45);
ck_assert_uint_eq(readResponse.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(readResponse.resultsSize, 1);
ck_assert_uint_eq(readResponse.results[0].status, UA_STATUSCODE_GOOD);
UA_ReadResponse_clear(&readResponse);
#ifdef UA_ENABLE_SUBSCRIPTIONS
UA_CreateSubscriptionRequest subscriptionRequest;
UA_CreateSubscriptionRequest_init(&subscriptionRequest);
subscriptionRequest.requestHeader.timestamp = UA_DateTime_now();
subscriptionRequest.requestHeader.requestHandle = 48;
ck_assert_uint_eq(UA_NodeId_copy(
&authenticationToken,
&subscriptionRequest.requestHeader.authenticationToken),
UA_STATUSCODE_GOOD);
subscriptionRequest.requestedPublishingInterval = 50.0;
subscriptionRequest.requestedLifetimeCount = 100;
subscriptionRequest.requestedMaxKeepAliveCount = 1;
subscriptionRequest.publishingEnabled = true;
sendServiceRequest(http, config->eventLoop, &path, &subscriptionRequest,
&UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONREQUEST]);
UA_CreateSubscriptionRequest_clear(&subscriptionRequest);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONRESPONSE], &offset);
UA_CreateSubscriptionResponse subscriptionResponse;
UA_CreateSubscriptionResponse_init(&subscriptionResponse);
ck_assert_uint_eq(
UA_decodeBinaryInternal(
&responseBody, &offset, &subscriptionResponse,
&UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(subscriptionResponse.responseHeader.requestHandle, 48);
ck_assert_uint_eq(subscriptionResponse.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
UA_UInt32 subscriptionId = subscriptionResponse.subscriptionId;
ck_assert_uint_ne(subscriptionId, 0);
UA_CreateSubscriptionResponse_clear(&subscriptionResponse);
/* Publish is intentionally long-standing. Its HTTP request remains alive
* until the subscription produces the keep-alive response. */
UA_PublishRequest publishRequest;
UA_PublishRequest_init(&publishRequest);
publishRequest.requestHeader.timestamp = UA_DateTime_now();
publishRequest.requestHeader.requestHandle = 49;
publishRequest.requestHeader.timeoutHint = 5000;
ck_assert_uint_eq(UA_NodeId_copy(
&authenticationToken,
&publishRequest.requestHeader.authenticationToken),
UA_STATUSCODE_GOOD);
sendServiceRequest(http, config->eventLoop, &path, &publishRequest,
&UA_TYPES[UA_TYPES_PUBLISHREQUEST]);
UA_PublishRequest_clear(&publishRequest);
UA_PublishResponse publishResponse;
UA_PublishResponse_init(&publishResponse);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_PUBLISHRESPONSE], &offset);
ck_assert_uint_eq(
UA_decodeBinaryInternal(&responseBody, &offset, &publishResponse,
&UA_TYPES[UA_TYPES_PUBLISHRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(publishResponse.responseHeader.requestHandle, 49);
ck_assert_uint_eq(publishResponse.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(publishResponse.subscriptionId, subscriptionId);
UA_PublishResponse_clear(&publishResponse);
#endif
/* A second logical client using the same endpoint and SecurityPolicy gets
* a distinct internal channel. Its AuthenticationToken routes subsequent
* physical HTTP requests back to that channel. */
UA_CreateSessionRequest secondCreateRequest;
UA_CreateSessionRequest_init(&secondCreateRequest);
secondCreateRequest.requestHeader.timestamp = UA_DateTime_now();
secondCreateRequest.requestHeader.requestHandle = 47;
secondCreateRequest.clientDescription.applicationType =
UA_APPLICATIONTYPE_CLIENT;
secondCreateRequest.clientDescription.applicationUri =
UA_STRING_ALLOC("urn:open62541:second-http-test-client");
secondCreateRequest.clientDescription.productUri =
UA_STRING_ALLOC("urn:open62541:http-test");
secondCreateRequest.clientDescription.applicationName =
UA_LOCALIZEDTEXT_ALLOC("en", "Second HTTP test client");
ck_assert_uint_eq(UA_String_copy(&advertisedUrl,
&secondCreateRequest.endpointUrl),
UA_STATUSCODE_GOOD);
secondCreateRequest.sessionName =
UA_STRING_ALLOC("Second HTTP test session");
secondCreateRequest.requestedSessionTimeout = 60000.0;
sendServiceRequest(http, config->eventLoop, &path, &secondCreateRequest,
&UA_TYPES[UA_TYPES_CREATESESSIONREQUEST]);
UA_CreateSessionRequest_clear(&secondCreateRequest);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE], &offset);
UA_CreateSessionResponse secondCreateResponse;
UA_CreateSessionResponse_init(&secondCreateResponse);
ck_assert_uint_eq(
UA_decodeBinaryInternal(&responseBody, &offset, &secondCreateResponse,
&UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(secondCreateResponse.responseHeader.requestHandle, 47);
ck_assert_uint_eq(secondCreateResponse.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(secondCreateResponse.authenticationToken.identifierType,
UA_NODEIDTYPE_BYTESTRING);
ck_assert_uint_ge(
secondCreateResponse.authenticationToken.identifier.byteString.length,
32);
ck_assert(!UA_NodeId_equal(&authenticationToken,
&secondCreateResponse.authenticationToken));
UA_NodeId secondAuthenticationToken;
UA_NodeId_init(&secondAuthenticationToken);
ck_assert_uint_eq(UA_NodeId_copy(
&secondCreateResponse.authenticationToken,
&secondAuthenticationToken),
UA_STATUSCODE_GOOD);
UA_CreateSessionResponse_clear(&secondCreateResponse);
UA_ActivateSessionRequest secondActivateRequest;
UA_ActivateSessionRequest_init(&secondActivateRequest);
secondActivateRequest.requestHeader.timestamp = UA_DateTime_now();
secondActivateRequest.requestHeader.requestHandle = 52;
ck_assert_uint_eq(UA_NodeId_copy(
&secondAuthenticationToken,
&secondActivateRequest.requestHeader.authenticationToken),
UA_STATUSCODE_GOOD);
secondActivateRequest.userIdentityToken.encoding =
UA_EXTENSIONOBJECT_DECODED;
secondActivateRequest.userIdentityToken.content.decoded.type =
&UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN];
secondActivateRequest.userIdentityToken.content.decoded.data =
UA_AnonymousIdentityToken_new();
ck_assert_ptr_nonnull(
secondActivateRequest.userIdentityToken.content.decoded.data);
UA_AnonymousIdentityToken *secondAnonymous =
(UA_AnonymousIdentityToken *)
secondActivateRequest.userIdentityToken.content.decoded.data;
ck_assert_uint_eq(UA_String_copy(&anonymousPolicyId,
&secondAnonymous->policyId),
UA_STATUSCODE_GOOD);
sendServiceRequest(http, config->eventLoop, &path, &secondActivateRequest,
&UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST]);
UA_ActivateSessionRequest_clear(&secondActivateRequest);
UA_ActivateSessionResponse secondActivateResponse;
UA_ActivateSessionResponse_init(&secondActivateResponse);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE], &offset);
ck_assert_uint_eq(
UA_decodeBinaryInternal(&responseBody, &offset, &secondActivateResponse,
&UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(secondActivateResponse.responseHeader.requestHandle, 52);
ck_assert_uint_eq(secondActivateResponse.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
UA_ActivateSessionResponse_clear(&secondActivateResponse);
#ifdef UA_ENABLE_SUBSCRIPTIONS
UA_CreateSubscriptionRequest secondSubscriptionRequest;
UA_CreateSubscriptionRequest_init(&secondSubscriptionRequest);
secondSubscriptionRequest.requestHeader.timestamp = UA_DateTime_now();
secondSubscriptionRequest.requestHeader.requestHandle = 53;
ck_assert_uint_eq(UA_NodeId_copy(
&secondAuthenticationToken,
&secondSubscriptionRequest.requestHeader.authenticationToken),
UA_STATUSCODE_GOOD);
secondSubscriptionRequest.requestedPublishingInterval = 5000.0;
secondSubscriptionRequest.requestedLifetimeCount = 100;
secondSubscriptionRequest.requestedMaxKeepAliveCount = 10;
secondSubscriptionRequest.publishingEnabled = true;
sendServiceRequest(http, config->eventLoop, &path,
&secondSubscriptionRequest,
&UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONREQUEST]);
UA_CreateSubscriptionRequest_clear(&secondSubscriptionRequest);
UA_CreateSubscriptionResponse secondSubscriptionResponse;
UA_CreateSubscriptionResponse_init(&secondSubscriptionResponse);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONRESPONSE], &offset);
ck_assert_uint_eq(
UA_decodeBinaryInternal(
&responseBody, &offset, &secondSubscriptionResponse,
&UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(
secondSubscriptionResponse.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
UA_CreateSubscriptionResponse_clear(&secondSubscriptionResponse);
#endif
lockServer(server);
UA_Session *firstSession = getSessionByToken(server, &authenticationToken);
UA_Session *secondSession =
getSessionByToken(server, &secondAuthenticationToken);
ck_assert_ptr_nonnull(firstSession);
ck_assert_ptr_nonnull(secondSession);
ck_assert_ptr_nonnull(firstSession->channel);
ck_assert_ptr_nonnull(secondSession->channel);
ck_assert_ptr_ne(firstSession->channel, secondSession->channel);
ck_assert_uint_ne(firstSession->channel->securityToken.channelId,
secondSession->channel->securityToken.channelId);
UA_UInt32 secondLogicalChannelId =
secondSession->channel->securityToken.channelId;
unlockServer(server);
#ifdef UA_ENABLE_SUBSCRIPTIONS
/* Keep a Publish request accepted while its logical channel closes. HTTP
* can still return a terminal ServiceFault while the channel is CLOSING. */
UA_PublishRequest pendingPublishRequest;
UA_PublishRequest_init(&pendingPublishRequest);
pendingPublishRequest.requestHeader.timestamp = UA_DateTime_now();
pendingPublishRequest.requestHeader.requestHandle = 54;
pendingPublishRequest.requestHeader.timeoutHint = 60000;
ck_assert_uint_eq(UA_NodeId_copy(
&secondAuthenticationToken,
&pendingPublishRequest.requestHeader.authenticationToken),
UA_STATUSCODE_GOOD);
startBinaryServiceRequest(http, &path, &pendingPublishRequest,
&UA_TYPES[UA_TYPES_PUBLISHREQUEST]);
UA_PublishRequest_clear(&pendingPublishRequest);
UA_Boolean publishQueued = false;
for(size_t i = 0; i < 100 && !publishQueued; i++) {
config->eventLoop->run(config->eventLoop, 1);
lockServer(server);
secondSession = getSessionByToken(server, &secondAuthenticationToken);
publishQueued = secondSession && secondSession->responseQueueSize == 1;
unlockServer(server);
}
ck_assert(publishQueued);
ck_assert(!responseComplete);
/* Dropping the HTTP carrier abandons this long-poll without detaching the
* Session or leaving a stale Publish entry behind. */
ck_assert_uint_eq(http->closeConnection(http, clientConnectionId),
UA_STATUSCODE_GOOD);
for(size_t i = 0; i < 300; i++) {
config->eventLoop->run(config->eventLoop, 10);
lockServer(server);
secondSession = getSessionByToken(server, &secondAuthenticationToken);
UA_Boolean drained =
secondSession && secondSession->responseQueueSize == 0;
unlockServer(server);
if(drained)
break;
}
lockServer(server);
secondSession = getSessionByToken(server, &secondAuthenticationToken);
ck_assert_ptr_nonnull(secondSession);
ck_assert_uint_eq(secondSession->responseQueueSize, 0);
unlockServer(server);
openRawHttpClient(http, port, true, &config->httpCertificate);
UA_PublishRequest_init(&pendingPublishRequest);
pendingPublishRequest.requestHeader.timestamp = UA_DateTime_now();
pendingPublishRequest.requestHeader.requestHandle = 55;
pendingPublishRequest.requestHeader.timeoutHint = 60000;
ck_assert_uint_eq(UA_NodeId_copy(
&secondAuthenticationToken,
&pendingPublishRequest.requestHeader.authenticationToken),
UA_STATUSCODE_GOOD);
startBinaryServiceRequest(http, &path, &pendingPublishRequest,
&UA_TYPES[UA_TYPES_PUBLISHREQUEST]);
UA_PublishRequest_clear(&pendingPublishRequest);
publishQueued = false;
for(size_t i = 0; i < 100 && !publishQueued; i++) {
config->eventLoop->run(config->eventLoop, 1);
lockServer(server);
secondSession = getSessionByToken(server, &secondAuthenticationToken);
publishQueued = secondSession && secondSession->responseQueueSize == 1;
unlockServer(server);
}
ck_assert(publishQueued);
ck_assert(!responseComplete);
#endif
/* The public close API drains the logical HTTP channel without closing
* the shared listener or the sibling client's channel. */
ck_assert_uint_eq(UA_Server_closeSecureChannel(
server, secondLogicalChannelId,
UA_SHUTDOWNREASON_CLOSE),
UA_STATUSCODE_GOOD);
#ifdef UA_ENABLE_SUBSCRIPTIONS
for(size_t i = 0; i < 300 && !responseComplete; i++)
config->eventLoop->run(config->eventLoop, 20);
ck_assert(responseComplete);
ck_assert_uint_eq(responseStatus, 200);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_SERVICEFAULT], &offset);
UA_ServiceFault closeFault;
UA_ServiceFault_init(&closeFault);
ck_assert_uint_eq(
UA_decodeBinaryInternal(&responseBody, &offset, &closeFault,
&UA_TYPES[UA_TYPES_SERVICEFAULT], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(closeFault.responseHeader.requestHandle, 55);
ck_assert_uint_eq(closeFault.responseHeader.serviceResult,
UA_STATUSCODE_BADSECURECHANNELCLOSED);
UA_ServiceFault_clear(&closeFault);
#endif
lockServer(server);
firstSession = getSessionByToken(server, &authenticationToken);
secondSession = getSessionByToken(server, &secondAuthenticationToken);
ck_assert_ptr_nonnull(firstSession);
ck_assert_ptr_nonnull(firstSession->channel);
ck_assert_uint_eq(firstSession->channel->securityToken.channelId,
logicalChannelId);
/* Activated Sessions survive transport loss detached from the channel. */
ck_assert_ptr_nonnull(secondSession);
ck_assert_ptr_null(secondSession->channel);
UA_Session_remove(server, secondSession, UA_SHUTDOWNREASON_CLOSE);
unlockServer(server);
UA_CloseSessionRequest closeRequest;
UA_CloseSessionRequest_init(&closeRequest);
closeRequest.requestHeader.timestamp = UA_DateTime_now();
closeRequest.requestHeader.requestHandle = 46;
closeRequest.deleteSubscriptions = true;
ck_assert_uint_eq(UA_NodeId_copy(&authenticationToken,
&closeRequest.requestHeader.authenticationToken),
UA_STATUSCODE_GOOD);
sendServiceRequest(http, config->eventLoop, &path, &closeRequest,
&UA_TYPES[UA_TYPES_CLOSESESSIONREQUEST]);
UA_CloseSessionRequest_clear(&closeRequest);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_CLOSESESSIONRESPONSE], &offset);
UA_CloseSessionResponse closeResponse;
UA_CloseSessionResponse_init(&closeResponse);
ck_assert_uint_eq(
UA_decodeBinaryInternal(&responseBody, &offset, &closeResponse,
&UA_TYPES[UA_TYPES_CLOSESESSIONRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(closeResponse.responseHeader.requestHandle, 46);
ck_assert_uint_eq(closeResponse.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
UA_CloseSessionResponse_clear(&closeResponse);
UA_String_clear(&anonymousPolicyId);
UA_String_clear(&advertisedUrl);
UA_ByteString_clear(&responseBody);
lockServer(server);
ck_assert_ptr_null(getSessionByToken(server, &authenticationToken));
ck_assert_ptr_null(getSessionByToken(server, &secondAuthenticationToken));
ck_assert_uint_eq(server->sessionCount, 0);
UA_SecureChannel *remainingChannel;
TAILQ_FOREACH(remainingChannel, &server->channels, serverEntry)
ck_assert_uint_ne(remainingChannel->securityToken.channelId,
logicalChannelId);
unlockServer(server);
UA_NodeId_clear(&authenticationToken);
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
lockServer(server);
ck_assert_uint_eq(server->sessionCount, 0);
unlockServer(server);
UA_NodeId_clear(&secondAuthenticationToken);
/* A restart recreates, rather than accumulates, HTTPS endpoint records. */
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
for(UA_Driver *drv = UA_Server_getDrivers(server); drv; drv = drv->next) {
if(UA_String_equal(&drv->name, &httpDriverName))
ck_assert_uint_eq(drv->state, UA_LIFECYCLESTATE_STARTED);
}
size_t httpsDiscoveryUrls = 0;
port = 0;
for(size_t i = 0; i < config->applicationDescription.discoveryUrlsSize;
i++) {
const UA_String *url = &config->applicationDescription.discoveryUrls[i];
if(url->length < 12 || memcmp(url->data, "opc.https://", 12) != 0)
continue;
httpsDiscoveryUrls++;
UA_String hostname = UA_STRING_NULL;
UA_String urlPath = UA_STRING_NULL;
ck_assert_uint_eq(UA_parseEndpointUrl(url, &hostname, &port, &urlPath),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_String_copy(url, &advertisedUrl),
UA_STATUSCODE_GOOD);
}
ck_assert_uint_eq(httpsDiscoveryUrls, 1);
ck_assert_uint_ne(port, 0);
openRawHttpClient(http, port, true, &config->httpCertificate);
findRequest.requestHeader.timestamp = UA_DateTime_now();
findRequest.requestHeader.requestHandle = 50;
sendServiceRequest(http, config->eventLoop, &path, &findRequest,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_FINDSERVERSRESPONSE], &offset);
UA_FindServersResponse_init(&response);
ck_assert_uint_eq(
UA_decodeBinaryInternal(&responseBody, &offset, &response,
&UA_TYPES[UA_TYPES_FINDSERVERSRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(response.responseHeader.requestHandle, 50);
ck_assert_uint_eq(response.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
UA_FindServersResponse_clear(&response);
UA_String_clear(&advertisedUrl);
UA_ByteString_clear(&responseBody);
UA_FindServersRequest_clear(&findRequest);
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
UA_Server_delete(server);
}
END_TEST
START_TEST(oversizedServerResponseReturnsServiceFault) {
UA_Server *server = UA_Server_new();
ck_assert_ptr_nonnull(server);
UA_ServerConfig *serverConfig = UA_Server_getConfig(server);
serverConfig->tcpEnabled = false;
serverConfig->httpEnabled = true;
serverConfig->httpAllowUnencrypted = true;
serverConfig->httpMaxMsgSize = 4096;
serverConfig->httpListenAddress = UA_STRING_ALLOC("127.0.0.1");
UA_Array_delete(serverConfig->serverUrls, serverConfig->serverUrlsSize,
&UA_TYPES[UA_TYPES_STRING]);
serverConfig->serverUrls =
(UA_String *)UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]);
ck_assert_ptr_nonnull(serverConfig->serverUrls);
serverConfig->serverUrlsSize = 1;
serverConfig->serverUrls[0] =
UA_STRING_ALLOC("opc.http://127.0.0.1:0/limited");
UA_ByteString largeValue = UA_BYTESTRING_NULL;
ck_assert_uint_eq(UA_ByteString_allocBuffer(&largeValue, 16 * 1024),
UA_STATUSCODE_GOOD);
memset(largeValue.data, 0x5a, largeValue.length);
UA_VariableAttributes attributes = UA_VariableAttributes_default;
ck_assert_uint_eq(UA_Variant_setScalarCopy(
&attributes.value, &largeValue,
&UA_TYPES[UA_TYPES_BYTESTRING]),
UA_STATUSCODE_GOOD);
const UA_NodeId largeValueNode = UA_NODEID_NUMERIC(1, 6002);
ck_assert_uint_eq(UA_Server_addVariableNode(
server, largeValueNode,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "LimitedHttpValue"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attributes, NULL, NULL),
UA_STATUSCODE_GOOD);
UA_VariableAttributes_clear(&attributes);
UA_ByteString_clear(&largeValue);
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
UA_String endpointUrl = UA_STRING_NULL;
ck_assert_uint_ne(getAdvertisedPort(serverConfig, "opc.http://", 11,
&endpointUrl), 0);
char *endpoint = (char *)UA_malloc(endpointUrl.length + 1);
ck_assert_ptr_nonnull(endpoint);
memcpy(endpoint, endpointUrl.data, endpointUrl.length);
endpoint[endpointUrl.length] = 0;
UA_ClientConfig clientConfig;
memset(&clientConfig, 0, sizeof(clientConfig));
clientConfig.eventLoop = serverConfig->eventLoop;
clientConfig.externalEventLoop = true;
ck_assert_uint_eq(UA_ClientConfig_setDefault(&clientConfig),
UA_STATUSCODE_GOOD);
clientConfig.httpAllowUnencrypted = true;
clientConfig.httpMaxMsgSize = 32 * 1024;
UA_Client *client = UA_Client_newWithConfig(&clientConfig);
ck_assert_ptr_nonnull(client);
ck_assert_uint_eq(UA_Client_connect(client, endpoint), UA_STATUSCODE_GOOD);
UA_Variant value;
UA_Variant_init(&value);
ck_assert_uint_eq(UA_Client_readValueAttribute(client, largeValueNode, &value),
UA_STATUSCODE_BADRESPONSETOOLARGE);
UA_Variant_clear(&value);
/* The ServiceFault is request-local and does not poison the binding. */
UA_Variant_init(&value);
ck_assert_uint_eq(UA_Client_readValueAttribute(
client, UA_NODEID_NUMERIC(
0, UA_NS0ID_SERVER_SERVERSTATUS_STATE),
&value),
UA_STATUSCODE_GOOD);
UA_Variant_clear(&value);
ck_assert_uint_eq(UA_Client_disconnect(client), UA_STATUSCODE_GOOD);
UA_Client_delete(client);
UA_free(endpoint);
UA_String_clear(&endpointUrl);
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
UA_Server_delete(server);
}
END_TEST
#ifdef UA_ENABLE_ENCRYPTION
static size_t
countAuthenticatedHttpSecureChannels(UA_Server *server) {
size_t count = 0;
lockServer(server);
UA_SecureChannel *channel;
TAILQ_FOREACH(channel, &server->channels, serverEntry) {
if(channel->transport == UA_SECURECHANNEL_TRANSPORT_HTTP &&
channel->remoteCertificate.length > 0) {
ck_assert_uint_eq(channel->securityMode,
UA_MESSAGESECURITYMODE_SIGNANDENCRYPT);
count++;
}
}
unlockServer(server);
return count;
}
START_TEST(securePolicyServiceOverHttps) {
clientConnectionId = 0;
responseStatus = 0;
responseBody = UA_BYTESTRING_NULL;
UA_ByteString serverCertificate = loadFile("server_cert.der");
UA_ByteString serverPrivateKey = loadFile("server_key.der");
UA_ByteString clientCertificate = loadFile("client_cert.der");
UA_ByteString clientPrivateKey = loadFile("client_key.der");
ck_assert_uint_gt(serverCertificate.length, 0);
ck_assert_uint_gt(serverPrivateKey.length, 0);
ck_assert_uint_gt(clientCertificate.length, 0);
ck_assert_uint_gt(clientPrivateKey.length, 0);
UA_Server *server = UA_Server_new();
ck_assert_ptr_nonnull(server);
UA_ServerConfig *config = UA_Server_getConfig(server);
config->tcpEnabled = false;
config->httpEnabled = true;
config->httpListenAddress = UA_STRING_ALLOC("");
ck_assert_uint_eq(UA_ServerConfig_addSecurityPolicyBasic256Sha256(
config, &serverCertificate, &serverPrivateKey),
UA_STATUSCODE_GOOD);
const UA_String securePolicyUri = UA_STRING_STATIC(
"http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256");
ck_assert_uint_eq(UA_ServerConfig_addEndpoint(
config, securePolicyUri,
UA_MESSAGESECURITYMODE_SIGN),
UA_STATUSCODE_GOOD);
UA_UsernamePasswordLogin login = {
UA_STRING_STATIC("user"), UA_STRING_STATIC("password")};
ck_assert_uint_eq(UA_AccessControl_default(
config, true, &UA_SECURITY_POLICY_NONE_URI, 1,
&login),
UA_STATUSCODE_GOOD);
UA_TrustListDataType trustList;
UA_TrustListDataType_init(&trustList);
trustList.specifiedLists = UA_TRUSTLISTMASKS_TRUSTEDCERTIFICATES;
trustList.trustedCertificates = &clientCertificate;
trustList.trustedCertificatesSize = 1;
UA_NodeId applicationGroup = UA_NS0ID(
SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTAPPLICATIONGROUP);
ck_assert_uint_eq(UA_CertificateGroup_Memorystore(
&config->secureChannelPKI, &applicationGroup,
&trustList, config->logging, NULL),
UA_STATUSCODE_GOOD);
UA_String_clear(&config->applicationDescription.applicationUri);
config->applicationDescription.applicationUri =
UA_STRING_ALLOC("urn:open62541.unconfigured.application");
config->httpCertificate = serverCertificate;
config->httpPrivateKey = serverPrivateKey;
serverCertificate = UA_BYTESTRING_NULL;
serverPrivateKey = UA_BYTESTRING_NULL;
UA_Array_delete(config->serverUrls, config->serverUrlsSize,
&UA_TYPES[UA_TYPES_STRING]);
config->serverUrls =
(UA_String*)UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]);
ck_assert_ptr_nonnull(config->serverUrls);
config->serverUrlsSize = 1;
config->serverUrls[0] = UA_STRING_ALLOC("opc.https://127.0.0.1:0/secure");
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
UA_UInt16 port = 0;
UA_String advertisedUrl = UA_STRING_NULL;
for(size_t i = 0;
i < config->applicationDescription.discoveryUrlsSize; i++) {
const UA_String *url = &config->applicationDescription.discoveryUrls[i];
if(url->length < 12 || memcmp(url->data, "opc.https://", 12) != 0)
continue;
UA_String hostname = UA_STRING_NULL;
UA_String urlPath = UA_STRING_NULL;
ck_assert_uint_eq(UA_parseEndpointUrl(url, &hostname, &port, &urlPath),
UA_STATUSCODE_GOOD);
const UA_String expectedPath = UA_STRING_STATIC("secure");
if(UA_String_equal(&urlPath, &expectedPath)) {
ck_assert_uint_eq(UA_String_copy(url, &advertisedUrl),
UA_STATUSCODE_GOOD);
break;
}
}
ck_assert_uint_ne(port, 0);
/* Exercise the same policy through the public client, including the TLS
* trust path and the logical SecureChannel CreateSession signatures. */
char *publicEndpoint = (char *)UA_malloc(advertisedUrl.length + 1);
ck_assert_ptr_nonnull(publicEndpoint);
memcpy(publicEndpoint, advertisedUrl.data, advertisedUrl.length);
publicEndpoint[advertisedUrl.length] = 0;
UA_ClientConfig publicConfig;
memset(&publicConfig, 0, sizeof(publicConfig));
publicConfig.eventLoop = config->eventLoop;
publicConfig.externalEventLoop = true;
ck_assert_uint_eq(
UA_ClientConfig_setDefaultEncryption(
&publicConfig, clientCertificate, clientPrivateKey,
&config->httpCertificate, 1, NULL, 0),
UA_STATUSCODE_GOOD);
publicConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
publicConfig.securityPolicyUri = UA_STRING_ALLOC(
"http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256");
ck_assert_ptr_nonnull(publicConfig.securityPolicyUri.data);
ck_assert_uint_eq(UA_ClientConfig_setAuthenticationUsername(
&publicConfig, "user", "password"),
UA_STATUSCODE_GOOD);
UA_String_clear(&publicConfig.clientDescription.applicationUri);
publicConfig.clientDescription.applicationUri =
UA_STRING_ALLOC("urn:open62541.client.application");
ck_assert_ptr_nonnull(publicConfig.clientDescription.applicationUri.data);
ck_assert_uint_eq(UA_ByteString_copy(&config->httpCertificate,
&publicConfig.httpCaCertificate),
UA_STATUSCODE_GOOD);
UA_Client *publicClient = UA_Client_newWithConfig(&publicConfig);
ck_assert_ptr_nonnull(publicClient);
ck_assert_uint_eq(UA_Client_connect(publicClient, publicEndpoint),
UA_STATUSCODE_GOOD);
UA_MessageSecurityMode publicMode = UA_MESSAGESECURITYMODE_INVALID;
UA_QualifiedName modeKey = UA_QUALIFIEDNAME(0, "securityMode");
ck_assert_uint_eq(UA_Client_getConnectionAttribute_scalar(
publicClient, modeKey,
&UA_TYPES[UA_TYPES_MESSAGESECURITYMODE], &publicMode),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(publicMode,
UA_MESSAGESECURITYMODE_SIGNANDENCRYPT);
UA_Variant publicValue;
UA_Variant_init(&publicValue);
ck_assert_uint_eq(UA_Client_readValueAttribute(
publicClient,
UA_NODEID_NUMERIC(
0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME),
&publicValue),
UA_STATUSCODE_GOOD);
ck_assert(UA_Variant_hasScalarType(&publicValue,
&UA_TYPES[UA_TYPES_DATETIME]));
UA_Variant_clear(&publicValue);
/* Replacing the trust list revalidates existing HTTP SecureChannels. The
* rejected channel is torn down without requiring a physical socket close. */
ck_assert_uint_eq(countAuthenticatedHttpSecureChannels(server), 1);
ck_assert_uint_eq(UA_Server_addCertificates(
server, applicationGroup, NULL, 0, NULL, 0,
true, false),
UA_STATUSCODE_GOOD);
for(size_t i = 0;
i < 20 && countAuthenticatedHttpSecureChannels(server) != 0; i++)
config->eventLoop->run(config->eventLoop, 1);
ck_assert_uint_eq(countAuthenticatedHttpSecureChannels(server), 0);
UA_Client_delete(publicClient);
UA_free(publicEndpoint);
/* Restore the client trust for the raw HTTPS service tests below. */
ck_assert_uint_eq(UA_Server_addCertificates(
server, applicationGroup, &clientCertificate, 1,
NULL, 0, true, false),
UA_STATUSCODE_GOOD);
config->eventLoop->run(config->eventLoop, 1);
UA_ConnectionManager *http = getHttpConnectionManager(config);
ck_assert_ptr_nonnull(http);
openRawHttpClient(http, port, true, &config->httpCertificate);
UA_SecurityPolicy clientPolicy;
memset(&clientPolicy, 0, sizeof(clientPolicy));
ck_assert_uint_eq(UA_SecurityPolicy_Basic256Sha256(
&clientPolicy, clientCertificate, clientPrivateKey,
UA_Log_Stdout),
UA_STATUSCODE_GOOD);
void *clientChannelContext = NULL;
ck_assert_uint_eq(clientPolicy.newChannelContext(
&clientPolicy, &config->httpCertificate,
&clientChannelContext),
UA_STATUSCODE_GOOD);
UA_ByteString clientNonce = UA_BYTESTRING_NULL;
ck_assert_uint_eq(UA_ByteString_allocBuffer(&clientNonce, 32),
UA_STATUSCODE_GOOD);
for(size_t i = 0; i < clientNonce.length; i++)
clientNonce.data[i] = (UA_Byte)(0xa0 + i);
UA_CreateSessionRequest createRequest;
UA_CreateSessionRequest_init(&createRequest);
createRequest.requestHeader.timestamp = UA_DateTime_now();
createRequest.requestHeader.requestHandle = 100;
createRequest.clientDescription.applicationType = UA_APPLICATIONTYPE_CLIENT;
createRequest.clientDescription.applicationUri =
UA_STRING_ALLOC("urn:open62541.client.application");
createRequest.clientDescription.productUri =
UA_STRING_ALLOC("urn:open62541:http-secure-test");
createRequest.clientDescription.applicationName =
UA_LOCALIZEDTEXT_ALLOC("en", "HTTPS secure client");
ck_assert_uint_eq(UA_ByteString_copy(&clientCertificate,
&createRequest.clientCertificate),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_ByteString_copy(&clientNonce,
&createRequest.clientNonce),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_String_copy(&advertisedUrl,
&createRequest.endpointUrl),
UA_STATUSCODE_GOOD);
createRequest.sessionName = UA_STRING_ALLOC("HTTPS secure session");
createRequest.requestedSessionTimeout = 60000.0;
UA_String path = UA_STRING("/secure");
sendSecureServiceRequest(http, config->eventLoop, &path, &securePolicyUri,
&createRequest,
&UA_TYPES[UA_TYPES_CREATESESSIONREQUEST]);
UA_CreateSessionRequest_clear(&createRequest);
size_t offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE], &offset);
UA_CreateSessionResponse createResponse;
UA_CreateSessionResponse_init(&createResponse);
ck_assert_uint_eq(UA_decodeBinaryInternal(
&responseBody, &offset, &createResponse,
&UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(createResponse.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
ck_assert(UA_ByteString_equal(&createResponse.serverCertificate,
&config->httpCertificate));
ck_assert_uint_ge(createResponse.serverNonce.length, 32);
UA_ByteString createSignatureData = UA_BYTESTRING_NULL;
ck_assert_uint_eq(UA_ByteString_allocBuffer(
&createSignatureData,
clientCertificate.length + clientNonce.length),
UA_STATUSCODE_GOOD);
memcpy(createSignatureData.data, clientCertificate.data,
clientCertificate.length);
memcpy(createSignatureData.data + clientCertificate.length,
clientNonce.data, clientNonce.length);
ck_assert_uint_eq(clientPolicy.asymSignatureAlgorithm.verify(
&clientPolicy, clientChannelContext,
&createSignatureData,
&createResponse.serverSignature.signature),
UA_STATUSCODE_GOOD);
UA_ByteString_clear(&createSignatureData);
UA_String anonymousPolicyId = UA_STRING_NULL;
for(size_t i = 0; i < createResponse.serverEndpointsSize; i++) {
UA_EndpointDescription *endpoint = &createResponse.serverEndpoints[i];
if(!UA_String_equal(&endpoint->securityPolicyUri, &securePolicyUri))
continue;
for(size_t j = 0; j < endpoint->userIdentityTokensSize; j++) {
UA_UserTokenPolicy *token = &endpoint->userIdentityTokens[j];
if(token->tokenType == UA_USERTOKENTYPE_ANONYMOUS &&
anonymousPolicyId.length == 0)
ck_assert_uint_eq(UA_String_copy(&token->policyId,
&anonymousPolicyId),
UA_STATUSCODE_GOOD);
}
}
ck_assert_uint_gt(anonymousPolicyId.length, 0);
UA_ActivateSessionRequest activateRequest;
UA_ActivateSessionRequest_init(&activateRequest);
activateRequest.requestHeader.timestamp = UA_DateTime_now();
activateRequest.requestHeader.requestHandle = 101;
ck_assert_uint_eq(UA_NodeId_copy(&createResponse.authenticationToken,
&activateRequest.requestHeader.authenticationToken),
UA_STATUSCODE_GOOD);
setLegacyClientSignature(&clientPolicy, clientChannelContext,
&createResponse.serverCertificate,
&createResponse.serverNonce,
&activateRequest.clientSignature);
activateRequest.clientSignature.signature.data[0] ^= 0x01;
UA_AnonymousIdentityToken anonymousToken;
UA_AnonymousIdentityToken_init(&anonymousToken);
ck_assert_uint_eq(UA_String_copy(&anonymousPolicyId,
&anonymousToken.policyId),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_ExtensionObject_setValueCopy(
&activateRequest.userIdentityToken, &anonymousToken,
&UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN]),
UA_STATUSCODE_GOOD);
UA_AnonymousIdentityToken_clear(&anonymousToken);
sendSecureServiceRequest(http, config->eventLoop, &path, &securePolicyUri,
&activateRequest,
&UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST]);
UA_ActivateSessionRequest_clear(&activateRequest);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_SERVICEFAULT], &offset);
UA_ServiceFault fault;
UA_ServiceFault_init(&fault);
ck_assert_uint_eq(UA_decodeBinaryInternal(
&responseBody, &offset, &fault,
&UA_TYPES[UA_TYPES_SERVICEFAULT], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(fault.responseHeader.serviceResult,
UA_STATUSCODE_BADAPPLICATIONSIGNATUREINVALID);
UA_ServiceFault_clear(&fault);
UA_ActivateSessionRequest_init(&activateRequest);
activateRequest.requestHeader.timestamp = UA_DateTime_now();
activateRequest.requestHeader.requestHandle = 102;
ck_assert_uint_eq(UA_NodeId_copy(&createResponse.authenticationToken,
&activateRequest.requestHeader.authenticationToken),
UA_STATUSCODE_GOOD);
setLegacyClientSignature(&clientPolicy, clientChannelContext,
&createResponse.serverCertificate,
&createResponse.serverNonce,
&activateRequest.clientSignature);
UA_AnonymousIdentityToken_init(&anonymousToken);
ck_assert_uint_eq(UA_String_copy(&anonymousPolicyId,
&anonymousToken.policyId),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_ExtensionObject_setValueCopy(
&activateRequest.userIdentityToken, &anonymousToken,
&UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN]),
UA_STATUSCODE_GOOD);
UA_AnonymousIdentityToken_clear(&anonymousToken);
sendSecureServiceRequest(http, config->eventLoop, &path, &securePolicyUri,
&activateRequest,
&UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST]);
UA_ActivateSessionRequest_clear(&activateRequest);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE], &offset);
UA_ActivateSessionResponse activateResponse;
UA_ActivateSessionResponse_init(&activateResponse);
ck_assert_uint_eq(UA_decodeBinaryInternal(
&responseBody, &offset, &activateResponse,
&UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(activateResponse.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
UA_ActivateSessionResponse_clear(&activateResponse);
UA_ReadRequest readRequest;
UA_ReadRequest_init(&readRequest);
readRequest.requestHeader.timestamp = UA_DateTime_now();
readRequest.requestHeader.requestHandle = 103;
ck_assert_uint_eq(UA_NodeId_copy(&createResponse.authenticationToken,
&readRequest.requestHeader.authenticationToken),
UA_STATUSCODE_GOOD);
readRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_NEITHER;
readRequest.nodesToRead =
(UA_ReadValueId*)UA_Array_new(1, &UA_TYPES[UA_TYPES_READVALUEID]);
ck_assert_ptr_nonnull(readRequest.nodesToRead);
readRequest.nodesToReadSize = 1;
readRequest.nodesToRead[0].nodeId =
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE);
readRequest.nodesToRead[0].attributeId = UA_ATTRIBUTEID_VALUE;
sendSecureServiceRequest(http, config->eventLoop, &path, &securePolicyUri,
&readRequest, &UA_TYPES[UA_TYPES_READREQUEST]);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_READRESPONSE], &offset);
UA_ReadResponse readResponse;
UA_ReadResponse_init(&readResponse);
ck_assert_uint_eq(UA_decodeBinaryInternal(
&responseBody, &offset, &readResponse,
&UA_TYPES[UA_TYPES_READRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(readResponse.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
UA_ReadResponse_clear(&readResponse);
const UA_String nonePolicyUri = UA_SECURITY_POLICY_NONE_URI;
sendSecureServiceRequest(http, config->eventLoop, &path, &nonePolicyUri,
&readRequest, &UA_TYPES[UA_TYPES_READREQUEST]);
UA_ReadRequest_clear(&readRequest);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_SERVICEFAULT], &offset);
UA_ServiceFault_init(&fault);
ck_assert_uint_eq(UA_decodeBinaryInternal(
&responseBody, &offset, &fault,
&UA_TYPES[UA_TYPES_SERVICEFAULT], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_ne(fault.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
UA_ServiceFault_clear(&fault);
UA_CloseSessionRequest closeRequest;
UA_CloseSessionRequest_init(&closeRequest);
closeRequest.requestHeader.timestamp = UA_DateTime_now();
closeRequest.requestHeader.requestHandle = 104;
ck_assert_uint_eq(UA_NodeId_copy(&createResponse.authenticationToken,
&closeRequest.requestHeader.authenticationToken),
UA_STATUSCODE_GOOD);
closeRequest.deleteSubscriptions = true;
sendSecureServiceRequest(http, config->eventLoop, &path, &securePolicyUri,
&closeRequest,
&UA_TYPES[UA_TYPES_CLOSESESSIONREQUEST]);
UA_CloseSessionRequest_clear(&closeRequest);
offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_CLOSESESSIONRESPONSE], &offset);
UA_CloseSessionResponse closeResponse;
UA_CloseSessionResponse_init(&closeResponse);
ck_assert_uint_eq(UA_decodeBinaryInternal(
&responseBody, &offset, &closeResponse,
&UA_TYPES[UA_TYPES_CLOSESESSIONRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(closeResponse.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
UA_CloseSessionResponse_clear(&closeResponse);
lockServer(server);
ck_assert_ptr_null(getSessionByToken(
server, &createResponse.authenticationToken));
unlockServer(server);
UA_CreateSessionResponse_clear(&createResponse);
UA_String_clear(&anonymousPolicyId);
UA_String_clear(&advertisedUrl);
UA_ByteString_clear(&clientNonce);
UA_ByteString_clear(&responseBody);
clientPolicy.deleteChannelContext(&clientPolicy, clientChannelContext);
clientPolicy.clear(&clientPolicy);
UA_ByteString_clear(&clientCertificate);
UA_ByteString_clear(&clientPrivateKey);
UA_StatusCode closeStatus =
http->closeConnection(http, clientConnectionId);
ck_assert(closeStatus == UA_STATUSCODE_GOOD ||
closeStatus == UA_STATUSCODE_BADNOTFOUND);
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
UA_Server_delete(server);
}
END_TEST
#endif
START_TEST(unencryptedHttpRequiresExplicitOptIn) {
UA_Server *server = UA_Server_new();
ck_assert_ptr_nonnull(server);
UA_ServerConfig *config = UA_Server_getConfig(server);
config->tcpEnabled = false;
config->httpEnabled = true;
UA_Array_delete(config->serverUrls, config->serverUrlsSize,
&UA_TYPES[UA_TYPES_STRING]);
config->serverUrls =
(UA_String *)UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]);
ck_assert_ptr_nonnull(config->serverUrls);
config->serverUrlsSize = 1;
config->serverUrls[0] = UA_STRING_ALLOC("opc.http://localhost:0/ua");
ck_assert_uint_eq(UA_Server_run_startup(server),
UA_STATUSCODE_BADCONFIGURATIONERROR);
UA_Server_delete(server);
}
END_TEST
START_TEST(binaryServiceOverUnencryptedHttp) {
clientConnectionId = 0;
responseBody = UA_BYTESTRING_NULL;
UA_Server *server = UA_Server_new();
ck_assert_ptr_nonnull(server);
UA_ServerConfig *config = UA_Server_getConfig(server);
config->tcpEnabled = false;
config->httpEnabled = true;
config->httpAllowUnencrypted = true;
config->httpListenAddress = UA_STRING_ALLOC("");
UA_Array_delete(config->serverUrls, config->serverUrlsSize,
&UA_TYPES[UA_TYPES_STRING]);
config->serverUrls =
(UA_String *)UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]);
ck_assert_ptr_nonnull(config->serverUrls);
config->serverUrlsSize = 1;
config->serverUrls[0] = UA_STRING_ALLOC("opc.http://localhost:0/ua");
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
UA_String advertisedUrl = UA_STRING_NULL;
UA_UInt16 port = getAdvertisedPort(config, "opc.http://", 11,
&advertisedUrl);
ck_assert_uint_ne(port, 0);
UA_ConnectionManager *http = getHttpConnectionManager(config);
ck_assert_ptr_nonnull(http);
openRawHttpClient(http, port, false, NULL);
UA_FindServersRequest request;
UA_FindServersRequest_init(&request);
request.requestHeader.timestamp = UA_DateTime_now();
request.requestHeader.requestHandle = 201;
UA_String path = UA_STRING("/ua");
sendServiceRequest(http, config->eventLoop, &path, &request,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
size_t offset = 0;
decodeResponseType(&UA_TYPES[UA_TYPES_FINDSERVERSRESPONSE], &offset);
UA_FindServersResponse response;
UA_FindServersResponse_init(&response);
ck_assert_uint_eq(UA_decodeBinaryInternal(
&responseBody, &offset, &response,
&UA_TYPES[UA_TYPES_FINDSERVERSRESPONSE], NULL),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(response.responseHeader.requestHandle, 201);
ck_assert_uint_eq(response.responseHeader.serviceResult,
UA_STATUSCODE_GOOD);
UA_FindServersResponse_clear(&response);
UA_EndpointDescription *endpoints = NULL;
size_t endpointsSize = 0;
lockServer(server);
ck_assert_uint_eq(setCurrentEndpointsArray(server, advertisedUrl, NULL, 0,
&endpoints, &endpointsSize),
UA_STATUSCODE_GOOD);
unlockServer(server);
const UA_String binaryProfile = UA_STRING_STATIC(
"http://open62541.org/UA-Profile/Transport/http-uabinary");
UA_Boolean foundBinaryProfile = false;
for(size_t i = 0; i < endpointsSize; i++) {
if(UA_String_equal(&endpoints[i].transportProfileUri, &binaryProfile))
foundBinaryProfile = true;
}
ck_assert(foundBinaryProfile);
UA_Array_delete(endpoints, endpointsSize,
&UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
UA_FindServersRequest_clear(&request);
UA_String_clear(&advertisedUrl);
UA_ByteString_clear(&responseBody);
UA_StatusCode closeStatus = http->closeConnection(http, clientConnectionId);
ck_assert(closeStatus == UA_STATUSCODE_GOOD ||
closeStatus == UA_STATUSCODE_BADNOTFOUND);
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
UA_Server_delete(server);
}
END_TEST
static void
setServerUrls(UA_ServerConfig *config, const char **urls, size_t urlsSize) {
UA_Array_delete(config->serverUrls, config->serverUrlsSize,
&UA_TYPES[UA_TYPES_STRING]);
config->serverUrls =
(UA_String *)UA_Array_new(urlsSize, &UA_TYPES[UA_TYPES_STRING]);
ck_assert_ptr_nonnull(config->serverUrls);
config->serverUrlsSize = urlsSize;
for(size_t i = 0; i < urlsSize; i++)
config->serverUrls[i] = UA_STRING_ALLOC(urls[i]);
}
/* The TCP port is taken by a first server. The second server still starts, as
* it can be reached over HTTP. */
START_TEST(startupListensOnHttpWithoutTcpSocket) {
UA_Server *first = UA_Server_new();
ck_assert_ptr_nonnull(first);
UA_ServerConfig *firstConfig = UA_Server_getConfig(first);
firstConfig->tcpReuseAddr = false;
const char *firstUrls[] = {"opc.tcp://127.0.0.1:0"};
setServerUrls(firstConfig, firstUrls, 1);
ck_assert_uint_eq(UA_Server_run_startup(first), UA_STATUSCODE_GOOD);
UA_UInt16 tcpPort = getAdvertisedPort(firstConfig, "opc.tcp://", 10, NULL);
ck_assert_uint_ne(tcpPort, 0);
UA_Server *server = UA_Server_new();
ck_assert_ptr_nonnull(server);
UA_ServerConfig *config = UA_Server_getConfig(server);
config->tcpReuseAddr = false;
config->httpEnabled = true;
config->httpAllowUnencrypted = true;
config->httpListenAddress = UA_STRING_ALLOC("");
char tcpUrl[64];
snprintf(tcpUrl, sizeof(tcpUrl), "opc.tcp://127.0.0.1:%u", (unsigned)tcpPort);
const char *urls[] = {tcpUrl, "opc.http://localhost:0/ua"};
setServerUrls(config, urls, 2);
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
ck_assert_int_eq(UA_Server_getLifecycleState(server),
UA_LIFECYCLESTATE_STARTED);
ck_assert_uint_ne(getAdvertisedPort(config, "opc.http://", 11, NULL), 0);
/* The failed TCP transport leaves no housekeeping behind */
const UA_BinaryProtocolManager *tcp =
(const UA_BinaryProtocolManager *)server->binaryDriver;
ck_assert_int_eq(tcp->drv.state, UA_LIFECYCLESTATE_STOPPED);
ck_assert_uint_eq(tcp->houseKeepingCallbackId, 0);
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
UA_Server_delete(server);
ck_assert_uint_eq(UA_Server_run_shutdown(first), UA_STATUSCODE_GOOD);
UA_Server_delete(first);
}
END_TEST
#ifdef UA_ENABLE_ENCRYPTION
START_TEST(unencryptedHttpPasswordNeedsIndependentOptIn) {
UA_Server *server = UA_Server_new();
ck_assert_ptr_nonnull(server);
UA_ServerConfig *config = UA_Server_getConfig(server);
config->tcpEnabled = false;
config->httpEnabled = true;
config->httpAllowUnencrypted = true;
config->httpListenAddress = UA_STRING_ALLOC("");
UA_ByteString certificate = loadFile("server_cert.der");
UA_ByteString privateKey = loadFile("server_key.der");
ck_assert_uint_eq(UA_ServerConfig_addSecurityPolicyBasic256Sha256(
config, &certificate, &privateKey),
UA_STATUSCODE_GOOD);
UA_UsernamePasswordLogin login = {
UA_STRING_STATIC("user"), UA_STRING_STATIC("password")};
ck_assert_uint_eq(UA_AccessControl_default(
config, true, &UA_SECURITY_POLICY_NONE_URI, 1,
&login),
UA_STATUSCODE_GOOD);
UA_ByteString_clear(&certificate);
UA_ByteString_clear(&privateKey);
UA_Array_delete(config->serverUrls, config->serverUrlsSize,
&UA_TYPES[UA_TYPES_STRING]);
config->serverUrls =
(UA_String *)UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]);
ck_assert_ptr_nonnull(config->serverUrls);
config->serverUrlsSize = 1;
config->serverUrls[0] = UA_STRING_ALLOC("opc.http://localhost:0/ua");
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
UA_String advertisedUrl = UA_STRING_NULL;
ck_assert_uint_ne(getAdvertisedPort(config, "opc.http://", 11,
&advertisedUrl),
0);
UA_EndpointDescription *endpoints = NULL;
size_t endpointsSize = 0;
lockServer(server);
ck_assert_uint_eq(setCurrentEndpointsArray(server, advertisedUrl, NULL, 0,
&endpoints, &endpointsSize),
UA_STATUSCODE_GOOD);
unlockServer(server);
UA_Boolean foundEncryptedUsername = false;
for(size_t i = 0; i < endpointsSize; i++) {
for(size_t j = 0; j < endpoints[i].userIdentityTokensSize; j++) {
UA_UserTokenPolicy *utp = &endpoints[i].userIdentityTokens[j];
if(utp->tokenType != UA_USERTOKENTYPE_USERNAME)
continue;
ck_assert_uint_gt(utp->securityPolicyUri.length, 0);
ck_assert(!UA_String_equal(&utp->securityPolicyUri,
&UA_SECURITY_POLICY_NONE_URI));
foundEncryptedUsername = true;
}
}
ck_assert(foundEncryptedUsername);
UA_Array_delete(endpoints, endpointsSize,
&UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
/* Plaintext passwords require their own, independent opt-in. */
config->allowNonePolicyPassword = true;
endpoints = NULL;
endpointsSize = 0;
lockServer(server);
ck_assert_uint_eq(setCurrentEndpointsArray(server, advertisedUrl, NULL, 0,
&endpoints, &endpointsSize),
UA_STATUSCODE_GOOD);
unlockServer(server);
UA_Boolean foundPlainUsername = false;
for(size_t i = 0; i < endpointsSize; i++) {
for(size_t j = 0; j < endpoints[i].userIdentityTokensSize; j++) {
UA_UserTokenPolicy *utp = &endpoints[i].userIdentityTokens[j];
if(utp->tokenType == UA_USERTOKENTYPE_USERNAME &&
utp->securityPolicyUri.length == 0)
foundPlainUsername = true;
}
}
ck_assert(foundPlainUsername);
UA_Array_delete(endpoints, endpointsSize,
&UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
UA_String_clear(&advertisedUrl);
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
UA_Server_delete(server);
}
END_TEST
#endif
START_TEST(emptyAdvertisedHostnameRejected) {
UA_Server *server = UA_Server_new();
ck_assert_ptr_nonnull(server);
UA_ServerConfig *config = UA_Server_getConfig(server);
config->tcpEnabled = false;
config->httpEnabled = true;
config->httpCertificate = loadFile("server_cert.der");
config->httpPrivateKey = loadFile("server_key.der");
config->httpListenAddress = UA_STRING_ALLOC("");
UA_Array_delete(config->serverUrls, config->serverUrlsSize,
&UA_TYPES[UA_TYPES_STRING]);
config->serverUrls =
(UA_String *)UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]);
ck_assert_ptr_nonnull(config->serverUrls);
config->serverUrlsSize = 1;
config->serverUrls[0] = UA_STRING_ALLOC("opc.https://:0/ua");
ck_assert_uint_eq(UA_Server_run_startup(server),
UA_STATUSCODE_BADCONFIGURATIONERROR);
UA_Server_delete(server);
}
END_TEST
START_TEST(duplicateHttpSchemeRejected) {
UA_Server *server = UA_Server_new();
ck_assert_ptr_nonnull(server);
UA_ServerConfig *config = UA_Server_getConfig(server);
config->tcpEnabled = false;
config->httpEnabled = true;
config->httpAllowUnencrypted = true;
UA_Array_delete(config->serverUrls, config->serverUrlsSize,
&UA_TYPES[UA_TYPES_STRING]);
config->serverUrls =
(UA_String *)UA_Array_new(2, &UA_TYPES[UA_TYPES_STRING]);
ck_assert_ptr_nonnull(config->serverUrls);
config->serverUrlsSize = 2;
config->serverUrls[0] = UA_STRING_ALLOC("opc.http://localhost:0/one");
config->serverUrls[1] = UA_STRING_ALLOC("opc.http://localhost:0/two");
ck_assert_uint_eq(UA_Server_run_startup(server),
UA_STATUSCODE_BADCONFIGURATIONERROR);
UA_Server_delete(server);
}
END_TEST
START_TEST(httpAndHttpsListenersCoexist) {
UA_Server *server = UA_Server_new();
ck_assert_ptr_nonnull(server);
UA_ServerConfig *config = UA_Server_getConfig(server);
config->tcpEnabled = false;
config->httpEnabled = true;
config->httpAllowUnencrypted = true;
config->httpCertificate = loadFile("server_cert.der");
config->httpPrivateKey = loadFile("server_key.der");
config->httpListenAddress = UA_STRING_ALLOC("");
UA_Array_delete(config->serverUrls, config->serverUrlsSize,
&UA_TYPES[UA_TYPES_STRING]);
config->serverUrls =
(UA_String *)UA_Array_new(2, &UA_TYPES[UA_TYPES_STRING]);
ck_assert_ptr_nonnull(config->serverUrls);
config->serverUrlsSize = 2;
config->serverUrls[0] = UA_STRING_ALLOC("opc.http://localhost:0/plain");
config->serverUrls[1] = UA_STRING_ALLOC("opc.https://localhost:0/secure");
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
UA_UInt16 httpPort =
getAdvertisedPort(config, "opc.http://", 11, NULL);
UA_UInt16 httpsPort =
getAdvertisedPort(config, "opc.https://", 12, NULL);
ck_assert_uint_ne(httpPort, 0);
ck_assert_uint_ne(httpsPort, 0);
UA_ConnectionManager *http = getHttpConnectionManager(config);
ck_assert_ptr_nonnull(http);
UA_FindServersRequest request;
UA_FindServersRequest_init(&request);
request.requestHeader.timestamp = UA_DateTime_now();
UA_String plainPath = UA_STRING("/plain");
UA_String securePath = UA_STRING("/secure");
openRawHttpClient(http, httpPort, false, NULL);
sendServiceRequest(http, config->eventLoop, &plainPath, &request,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
sendInvalidRequest(http, config->eventLoop, &securePath,
"application/octet-stream", NULL, "invalid", 404);
ck_assert_uint_eq(http->closeConnection(http, clientConnectionId),
UA_STATUSCODE_GOOD);
openRawHttpClient(http, httpsPort, true, &config->httpCertificate);
sendServiceRequest(http, config->eventLoop, &securePath, &request,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
sendInvalidRequest(http, config->eventLoop, &plainPath,
"application/octet-stream", NULL, "invalid", 404);
ck_assert_uint_eq(http->closeConnection(http, clientConnectionId),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
UA_Server_delete(server);
}
END_TEST
static UA_Server *
newHttpsServerForPath(const char *url) {
UA_Server *server = UA_Server_new();
ck_assert_ptr_nonnull(server);
UA_ServerConfig *config = UA_Server_getConfig(server);
config->tcpEnabled = false;
config->httpEnabled = true;
config->httpCertificate = loadFile("server_cert.der");
config->httpPrivateKey = loadFile("server_key.der");
config->httpListenAddress = UA_STRING_ALLOC("");
UA_Array_delete(config->serverUrls, config->serverUrlsSize,
&UA_TYPES[UA_TYPES_STRING]);
config->serverUrls =
(UA_String *)UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]);
ck_assert_ptr_nonnull(config->serverUrls);
config->serverUrlsSize = 1;
config->serverUrls[0] = UA_STRING_ALLOC(url);
return server;
}
START_TEST(httpEndpointPathsAreCanonical) {
UA_Server *server =
newHttpsServerForPath("opc.https://localhost:0/");
UA_ServerConfig *config = UA_Server_getConfig(server);
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
UA_String url = UA_STRING_NULL;
UA_UInt16 port = getAdvertisedPort(config, "opc.https://", 12, &url);
ck_assert_uint_ne(port, 0);
ck_assert_uint_gt(url.length, 0);
ck_assert_uint_eq(url.data[url.length - 1], '/');
/* A canonical root endpoint accepts both its direct and conventional
* discovery routes. JSON is a separate route derived from the same
* endpoint instead of separately stored endpoint state. */
UA_ConnectionManager *http = getHttpConnectionManager(config);
ck_assert_ptr_nonnull(http);
openRawHttpClient(http, port, true, &config->httpCertificate);
UA_FindServersRequest request;
UA_FindServersRequest_init(&request);
request.requestHeader.timestamp = UA_DateTime_now();
UA_String rootPath = UA_STRING("/");
UA_String rootDiscoveryPath = UA_STRING("/discovery");
sendServiceRequest(http, config->eventLoop, &rootPath, &request,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
sendServiceRequest(http, config->eventLoop, &rootDiscoveryPath, &request,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
#ifdef UA_ENABLE_JSON_ENCODING
UA_String rootJsonPath = UA_STRING("/json");
UA_String rootJsonDiscoveryPath = UA_STRING("/json/discovery");
sendServiceRequestJson(http, config->eventLoop, &rootJsonPath, &request,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
sendServiceRequestJson(http, config->eventLoop,
&rootJsonDiscoveryPath, &request,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
#endif
ck_assert_uint_eq(http->closeConnection(http, clientConnectionId),
UA_STATUSCODE_GOOD);
UA_EndpointDescription *endpoints = NULL;
size_t endpointsSize = 0;
lockServer(server);
ck_assert_uint_eq(setCurrentEndpointsArray(server, url, NULL, 0,
&endpoints, &endpointsSize),
UA_STATUSCODE_GOOD);
unlockServer(server);
#ifdef UA_ENABLE_JSON_ENCODING
UA_Boolean foundRootJson = false;
const UA_String jsonSuffix = UA_STRING_STATIC("/json");
for(size_t i = 0; i < endpointsSize; i++) {
UA_String *endpointUrl = &endpoints[i].endpointUrl;
if(endpointUrl->length >= jsonSuffix.length &&
memcmp(&endpointUrl->data[endpointUrl->length - jsonSuffix.length],
jsonSuffix.data, jsonSuffix.length) == 0)
foundRootJson = true;
}
ck_assert(foundRootJson);
UA_Array_delete(endpoints, endpointsSize,
&UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
endpoints = NULL;
endpointsSize = 0;
const UA_String mirroredJson =
UA_STRING_STATIC("opc.https://mirror.invalid:1234/json");
lockServer(server);
ck_assert_uint_eq(setCurrentEndpointsArray(server, mirroredJson, NULL, 0,
&endpoints, &endpointsSize),
UA_STATUSCODE_GOOD);
unlockServer(server);
UA_Boolean foundMirroredBinaryRoot = false;
const UA_String mirroredBinary =
UA_STRING_STATIC("opc.https://mirror.invalid:1234/");
for(size_t i = 0; i < endpointsSize; i++) {
if(UA_String_equal(&endpoints[i].endpointUrl, &mirroredBinary))
foundMirroredBinaryRoot = true;
}
ck_assert(foundMirroredBinaryRoot);
#endif
UA_Array_delete(endpoints, endpointsSize,
&UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
UA_String_clear(&url);
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
UA_Server_delete(server);
server = newHttpsServerForPath("opc.https://localhost:0/factory/");
config = UA_Server_getConfig(server);
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
port = getAdvertisedPort(config, "opc.https://", 12, &url);
ck_assert_uint_ne(port, 0);
const UA_String factoryPath = UA_STRING_STATIC("/factory");
ck_assert_uint_ge(url.length, factoryPath.length);
ck_assert_mem_eq(&url.data[url.length - factoryPath.length],
factoryPath.data, factoryPath.length);
http = getHttpConnectionManager(config);
ck_assert_ptr_nonnull(http);
openRawHttpClient(http, port, true, &config->httpCertificate);
sendServiceRequest(http, config->eventLoop, &factoryPath, &request,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
UA_String factoryDiscoveryPath = UA_STRING("/factory/discovery");
sendServiceRequest(http, config->eventLoop, &factoryDiscoveryPath,
&request, &UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
#ifdef UA_ENABLE_JSON_ENCODING
UA_String factoryJsonPath = UA_STRING("/factory/json");
UA_String factoryJsonDiscoveryPath =
UA_STRING("/factory/json/discovery");
sendServiceRequestJson(http, config->eventLoop, &factoryJsonPath, &request,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
sendServiceRequestJson(http, config->eventLoop,
&factoryJsonDiscoveryPath, &request,
&UA_TYPES[UA_TYPES_FINDSERVERSREQUEST]);
#endif
ck_assert_uint_eq(http->closeConnection(http, clientConnectionId),
UA_STATUSCODE_GOOD);
UA_String_clear(&url);
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
UA_Server_delete(server);
server = newHttpsServerForPath("opc.https://localhost:0/factory/json");
ck_assert_uint_eq(UA_Server_run_startup(server),
UA_STATUSCODE_BADCONFIGURATIONERROR);
UA_Server_delete(server);
}
END_TEST
static int
runPythonHttpServer(const char *portString, const char *completionMarker) {
unsigned long parsedPort = strtoul(portString, NULL, 10);
if(parsedPort == 0 || parsedPort > 65535)
return EXIT_FAILURE;
char url[128];
int urlLength = snprintf(url, sizeof(url),
"opc.http://localhost:%lu/ua", parsedPort);
if(urlLength <= 0 || (size_t)urlLength >= sizeof(url))
return EXIT_FAILURE;
UA_Server *server = UA_Server_new();
if(!server)
return EXIT_FAILURE;
UA_ServerConfig *config = UA_Server_getConfig(server);
config->tcpEnabled = false;
config->httpEnabled = true;
config->httpAllowUnencrypted = true;
config->httpListenAddress = UA_STRING_ALLOC("127.0.0.1");
UA_Array_delete(config->serverUrls, config->serverUrlsSize,
&UA_TYPES[UA_TYPES_STRING]);
config->serverUrls =
(UA_String *)UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]);
if(!config->serverUrls) {
UA_Server_delete(server);
return EXIT_FAILURE;
}
config->serverUrlsSize = 1;
config->serverUrls[0] = UA_STRING_ALLOC(url);
UA_StatusCode res = UA_Server_run_startup(server);
if(res == UA_STATUSCODE_GOOD) {
for(size_t i = 0; i < 5000; i++) {
config->eventLoop->run(config->eventLoop, 10);
FILE *marker = fopen(completionMarker, "rb");
if(marker) {
fclose(marker);
break;
}
}
res = UA_Server_run_shutdown(server);
}
UA_Server_delete(server);
return res == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
int main(int argc, char **argv) {
if(argc == 4 && strcmp(argv[1], "--python-http-server") == 0)
return runPythonHttpServer(argv[2], argv[3]);
Suite *suite = suite_create("OPC UA HTTP server");
TCase *tc = tcase_create("binary services");
tcase_set_timeout(tc, 30);
tcase_add_test(tc, binaryServiceOverHttps);
tcase_add_test(tc, unencryptedHttpRequiresExplicitOptIn);
tcase_add_test(tc, binaryServiceOverUnencryptedHttp);
tcase_add_test(tc, startupListensOnHttpWithoutTcpSocket);
tcase_add_test(tc, oversizedServerResponseReturnsServiceFault);
#ifdef UA_ENABLE_ENCRYPTION
tcase_add_test(tc, securePolicyServiceOverHttps);
tcase_add_test(tc, unencryptedHttpPasswordNeedsIndependentOptIn);
#endif
tcase_add_test(tc, emptyAdvertisedHostnameRejected);
tcase_add_test(tc, duplicateHttpSchemeRejected);
tcase_add_test(tc, httpAndHttpsListenersCoexist);
tcase_add_test(tc, httpEndpointPathsAreCanonical);
suite_add_tcase(suite, tc);
SRunner *runner = srunner_create(suite);
srunner_run_all(runner, CK_NORMAL);
int failed = srunner_ntests_failed(runner);
srunner_free(runner);
return failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}