/* 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 #include #include "open62541/types.h" #include "open62541/types_generated.h" #include "open62541/util.h" #include "eventloop_posix_http_compression.h" #include "testing_clock.h" #include #include #include #include #ifndef _WIN32 # include # include # include # include #endif #include "testing-plugins/testing_clock.h" #define COUNT 2 unsigned int messageCount = 0; static UA_UInt16 httpPort = 8000; static void setup(void) { messageCount = 0; const char *portString = getenv("OPEN62541_TEST_HTTP_PORT"); if(!portString) return; unsigned long port = strtoul(portString, NULL, 10); ck_assert_uint_gt(port, 0); ck_assert_uint_le(port, UINT16_MAX); httpPort = (UA_UInt16)port; } static void teardown(void) {} 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 stopEventLoop(UA_EventLoop *eventLoop) { eventLoop->stop(eventLoop); for(size_t i = 0; i < 200 && eventLoop->state != UA_EVENTLOOPSTATE_STOPPED; i++) eventLoop->run(eventLoop, 20); ck_assert_uint_eq(eventLoop->state, UA_EVENTLOOPSTATE_STOPPED); eventLoop->free(eventLoop); } static void connectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId, void *application, void **connectionContext, UA_ConnectionState status, const UA_KeyValueMap *params, UA_ByteString msg) { const UA_UInt64 *contentLength; if(params) { contentLength = (const UA_UInt64 *) UA_KeyValueMap_getScalar(params, UA_QUALIFIEDNAME(0, "content-length"), &UA_TYPES[UA_TYPES_UINT64]); } if(status == UA_CONNECTIONSTATE_CLOSED) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "USER\t| Closing connection"); messageCount = 0; } else if(msg.length > 0) { static UA_UInt64 consumed = 0; char data[msg.length + 1]; memcpy(data, msg.data, msg.length); data[msg.length] = '\0'; UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "USER %lu\t| Received Data %zu bytes: %s", connectionId, msg.length, data); consumed += msg.length; if(consumed >= *contentLength) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "USER %lu\t| End of Response", connectionId); consumed = 0; messageCount++; } } else if(status == UA_CONNECTIONSTATE_OPENING) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "USER\t| Opening connection"); uintptr_t *id = *(uintptr_t**)connectionContext; *id = connectionId; } else if(status == UA_CONNECTIONSTATE_ESTABLISHED) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "USER\t| Established connection"); } } START_TEST(sendGetRequest) { UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("httpCM")); UA_EventLoop *el = UA_EventLoop_new_POSIX(UA_Log_Stdout); el->registerEventSource(el, &cm->eventSource); el->start(el); UA_String address = UA_STRING("127.0.0.1"); UA_UInt16 port = httpPort; UA_Boolean useSSL = false; UA_UInt16 timeout = 30; size_t paramsSize = 4; UA_KeyValuePair params[paramsSize]; params[0].key = UA_QUALIFIEDNAME(0, "address"); UA_Variant_setScalar(¶ms[0].value, &address, &UA_TYPES[UA_TYPES_STRING]); params[1].key = UA_QUALIFIEDNAME(0, "port"); UA_Variant_setScalar(¶ms[1].value, &port, &UA_TYPES[UA_TYPES_UINT16]); params[2].key = UA_QUALIFIEDNAME(0, "useSSL"); UA_Variant_setScalar(¶ms[2].value, &useSSL, &UA_TYPES[UA_TYPES_BOOLEAN]); params[3].key = UA_QUALIFIEDNAME(0, "timeout"); UA_Variant_setScalar(¶ms[3].value, &timeout, &UA_TYPES[UA_TYPES_UINT16]); UA_KeyValueMap kvm = {paramsSize, params}; uintptr_t connectionIds[COUNT] = {0}; UA_StatusCode res = cm->openConnection(cm, &kvm, NULL, &connectionIds[0], connectionCallback); ck_assert(res == UA_STATUSCODE_GOOD); /* A second connection on the shared context can override its timeout. */ timeout = 15; res = cm->openConnection(cm, &kvm, NULL, &connectionIds[1], connectionCallback); ck_assert(res == UA_STATUSCODE_GOOD); for(int i = 0; i < COUNT; i++) { res = cm->sendWithConnection(cm, connectionIds[i], &UA_KEYVALUEMAP_NULL, NULL); ck_assert(res == UA_STATUSCODE_GOOD); } ck_assert_uint_eq(messageCount, 0); for(int i = 0; i < 20; i++) { el->run(el,100); } ck_assert_uint_eq(messageCount, COUNT); for(int i = 0; i < COUNT; i++) { res = cm->closeConnection(cm, connectionIds[i]); ck_assert(res == UA_STATUSCODE_GOOD); } for(int i = 0; i < 20; i++) { el->run(el,100); } /* Stop the EventLoop */ int max_stop_iteration_count = 20; int iteration = 0; el->stop(el); while(el->state != UA_EVENTLOOPSTATE_STOPPED && iteration < max_stop_iteration_count) { UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); iteration++; } ck_assert(el->state == UA_EVENTLOOPSTATE_STOPPED); el->free(el); el = NULL; } END_TEST START_TEST(sendPostRequest) { UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("httpCM")); UA_EventLoop *el = UA_EventLoop_new_POSIX(UA_Log_Stdout); el->registerEventSource(el, &cm->eventSource); el->start(el); UA_String address = UA_STRING("127.0.0.1"); UA_UInt16 port = httpPort; UA_Boolean useSSL = false; size_t paramsSize = 3; UA_KeyValuePair params[paramsSize]; params[0].key = UA_QUALIFIEDNAME(0, "address"); UA_Variant_setScalar(¶ms[0].value, &address, &UA_TYPES[UA_TYPES_STRING]); params[1].key = UA_QUALIFIEDNAME(0, "port"); UA_Variant_setScalar(¶ms[1].value, &port, &UA_TYPES[UA_TYPES_UINT16]); params[2].key = UA_QUALIFIEDNAME(0, "useSSL"); UA_Variant_setScalar(¶ms[2].value, &useSSL, &UA_TYPES[UA_TYPES_BOOLEAN]); UA_KeyValueMap kvm = {paramsSize, params}; UA_String path = UA_STRING("/post"); UA_String method = UA_STRING("POST"); size_t sendParamsSize = 3; UA_KeyValuePair sendParams[sendParamsSize]; sendParams[0].key = UA_QUALIFIEDNAME(0, "path"); UA_Variant_setScalar(&sendParams[0].value, &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]); UA_UInt32 requestHandle = 0; sendParams[2].key = UA_QUALIFIEDNAME(0, "handle"); UA_Variant_setScalar(&sendParams[2].value, &requestHandle, &UA_TYPES[UA_TYPES_UINT32]); UA_KeyValueMap sendKvm = {sendParamsSize, sendParams}; uintptr_t connectionId = 0; UA_StatusCode res = cm->openConnection(cm, &kvm, NULL, &connectionId, connectionCallback); ck_assert(res == UA_STATUSCODE_GOOD); for(int i = 0; i < COUNT; i++) { requestHandle = (UA_UInt32)i; UA_ByteString msg = UA_BYTESTRING_ALLOC("text=hallo&send=data"); res = cm->sendWithConnection(cm, connectionId, &sendKvm, &msg); ck_assert(res == UA_STATUSCODE_GOOD); } ck_assert_uint_eq(messageCount, 0); for(int i = 0; i < 20; i++) { el->run(el,100); } ck_assert_uint_eq(messageCount, COUNT); res = cm->closeConnection(cm, connectionId); ck_assert(res == UA_STATUSCODE_GOOD); for(int i = 0; i < 20; i++) { el->run(el,100); } /* Stop the EventLoop */ int max_stop_iteration_count = 20; int iteration = 0; el->stop(el); while(el->state != UA_EVENTLOOPSTATE_STOPPED && iteration < max_stop_iteration_count) { UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); iteration++; } ck_assert(el->state == UA_EVENTLOOPSTATE_STOPPED); el->free(el); el = NULL; } END_TEST typedef struct { uintptr_t listenerId; uintptr_t clientId; uintptr_t acceptedConnectionId; UA_UInt16 port; UA_Boolean requestReceived; UA_Boolean responseReceived; UA_Boolean responseHeaderReceived; UA_UInt16 responseStatus; } HTTPServerTestContext; static void serverConnectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId, void *application, void **connectionContext, UA_ConnectionState state, const UA_KeyValueMap *params, UA_ByteString msg) { (void)connectionContext; HTTPServerTestContext *ctx = (HTTPServerTestContext*)application; const UA_UInt16 *listenPort = (const UA_UInt16*)UA_KeyValueMap_getScalar( params, UA_QUALIFIEDNAME(0, "listen-port"), &UA_TYPES[UA_TYPES_UINT16]); if(listenPort) { ctx->listenerId = connectionId; ctx->port = *listenPort; return; } if(state == UA_CONNECTIONSTATE_OPENING) { ctx->clientId = connectionId; return; } const UA_String *method = (const UA_String*)UA_KeyValueMap_getScalar( params, UA_QUALIFIEDNAME(0, "method"), &UA_TYPES[UA_TYPES_STRING]); const UA_String *path = (const UA_String*)UA_KeyValueMap_getScalar( params, UA_QUALIFIEDNAME(0, "path"), &UA_TYPES[UA_TYPES_STRING]); if(method && path && connectionId != ctx->clientId) { const UA_Byte expected[] = {0x00, 0x01, 0x7f, 0xff}; const UA_String expectedMethod = UA_STRING_STATIC("POST"); const UA_String expectedPath = UA_STRING_STATIC("/binary"); ck_assert(UA_String_equal(method, &expectedMethod)); ck_assert(UA_String_equal(path, &expectedPath)); ck_assert_uint_eq(msg.length, sizeof(expected)); ck_assert_mem_eq(msg.data, expected, sizeof(expected)); const UA_Variant *headerValue = UA_KeyValueMap_get( params, UA_QUALIFIEDNAME(0, "headers")); ck_assert_ptr_nonnull(headerValue); ck_assert_ptr_eq(headerValue->type, &UA_TYPES[UA_TYPES_KEYVALUEPAIR]); UA_KeyValuePair *requestHeaders = (UA_KeyValuePair*)headerValue->data; UA_Boolean foundSecurityPolicy = false; for(size_t i = 0; i < headerValue->arrayLength; i++) { const UA_String expectedHeader = UA_STRING_STATIC("opcua-securitypolicy"); if(!UA_String_equal(&requestHeaders[i].key.name, &expectedHeader)) continue; ck_assert(UA_Variant_hasScalarType( &requestHeaders[i].value, &UA_TYPES[UA_TYPES_STRING])); const UA_String expectedValue = UA_STRING_STATIC( "http://opcfoundation.org/UA/SecurityPolicy#None"); ck_assert(UA_String_equal( (UA_String*)requestHeaders[i].value.data, &expectedValue)); foundSecurityPolicy = true; } ck_assert(foundSecurityPolicy); ctx->acceptedConnectionId = connectionId; ctx->requestReceived = true; UA_String contentType = UA_STRING("application/octet-stream"); UA_KeyValuePair headers[1] = {0}; headers[0].key = UA_QUALIFIEDNAME(0, "content-type"); UA_Variant_setScalar(&headers[0].value, &contentType, &UA_TYPES[UA_TYPES_STRING]); UA_UInt16 statusCode = 201; UA_KeyValuePair responseParameters[2] = {0}; responseParameters[0].key = UA_QUALIFIEDNAME(0, "status-code"); UA_Variant_setScalar(&responseParameters[0].value, &statusCode, &UA_TYPES[UA_TYPES_UINT16]); responseParameters[1].key = UA_QUALIFIEDNAME(0, "headers"); UA_Variant_setArray(&responseParameters[1].value, headers, 1, &UA_TYPES[UA_TYPES_KEYVALUEPAIR]); UA_KeyValueMap responseMap = {2, responseParameters}; UA_ByteString response = UA_BYTESTRING_ALLOC("binary-response"); ck_assert_uint_eq(cm->sendWithConnection(cm, connectionId, &responseMap, &response), UA_STATUSCODE_GOOD); return; } if(connectionId == ctx->clientId) { const UA_UInt16 *statusCode = (const UA_UInt16*)UA_KeyValueMap_getScalar( params, UA_QUALIFIEDNAME(0, "status-code"), &UA_TYPES[UA_TYPES_UINT16]); if(statusCode) ctx->responseStatus = *statusCode; const UA_Variant *responseHeaders = UA_KeyValueMap_get( params, UA_QUALIFIEDNAME(0, "headers")); if(responseHeaders) { UA_KeyValuePair *headers = (UA_KeyValuePair*)responseHeaders->data; for(size_t i = 0; i < responseHeaders->arrayLength; i++) { const UA_String contentTypeName = UA_STRING_STATIC("content-type"); if(UA_String_equal(&headers[i].key.name, &contentTypeName)) ctx->responseHeaderReceived = true; } } } if(connectionId == ctx->clientId && msg.length > 0) { ck_assert_uint_eq(msg.length, strlen("binary-response")); ck_assert_mem_eq(msg.data, "binary-response", msg.length); ctx->responseReceived = true; } } START_TEST(serverBinaryRoundtrip) { HTTPServerTestContext ctx = {0}; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("httpCM")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); UA_String address = UA_STRING("127.0.0.1"); UA_UInt16 port = 0; UA_Boolean listen = true; UA_KeyValuePair listenerParameters[3] = {0}; listenerParameters[0].key = UA_QUALIFIEDNAME(0, "address"); UA_Variant_setScalar(&listenerParameters[0].value, &address, &UA_TYPES[UA_TYPES_STRING]); listenerParameters[1].key = UA_QUALIFIEDNAME(0, "port"); UA_Variant_setScalar(&listenerParameters[1].value, &port, &UA_TYPES[UA_TYPES_UINT16]); listenerParameters[2].key = UA_QUALIFIEDNAME(0, "listen"); UA_Variant_setScalar(&listenerParameters[2].value, &listen, &UA_TYPES[UA_TYPES_BOOLEAN]); UA_KeyValueMap listenerMap = {3, listenerParameters}; ck_assert_uint_eq(cm->openConnection(cm, &listenerMap, &ctx, NULL, serverConnectionCallback), UA_STATUSCODE_GOOD); ck_assert_uint_ne(ctx.listenerId, 0); ck_assert_uint_ne(ctx.port, 0); port = ctx.port; UA_KeyValuePair clientParameters[2] = {0}; clientParameters[0].key = UA_QUALIFIEDNAME(0, "address"); UA_Variant_setScalar(&clientParameters[0].value, &address, &UA_TYPES[UA_TYPES_STRING]); clientParameters[1].key = UA_QUALIFIEDNAME(0, "port"); UA_Variant_setScalar(&clientParameters[1].value, &port, &UA_TYPES[UA_TYPES_UINT16]); UA_KeyValueMap clientMap = {2, clientParameters}; ck_assert_uint_eq(cm->openConnection(cm, &clientMap, &ctx, NULL, serverConnectionCallback), UA_STATUSCODE_GOOD); ck_assert_uint_ne(ctx.clientId, 0); UA_String method = UA_STRING("POST"); UA_String path = UA_STRING("/binary"); UA_String securityPolicy = UA_STRING("http://opcfoundation.org/UA/SecurityPolicy#None"); UA_KeyValuePair headers[1] = {0}; headers[0].key = UA_QUALIFIEDNAME(0, "opcua-securitypolicy"); UA_Variant_setScalar(&headers[0].value, &securityPolicy, &UA_TYPES[UA_TYPES_STRING]); UA_KeyValuePair sendParameters[3] = {0}; sendParameters[0].key = UA_QUALIFIEDNAME(0, "method"); UA_Variant_setScalar(&sendParameters[0].value, &method, &UA_TYPES[UA_TYPES_STRING]); sendParameters[1].key = UA_QUALIFIEDNAME(0, "path"); UA_Variant_setScalar(&sendParameters[1].value, &path, &UA_TYPES[UA_TYPES_STRING]); sendParameters[2].key = UA_QUALIFIEDNAME(0, "headers"); UA_Variant_setArray(&sendParameters[2].value, headers, 1, &UA_TYPES[UA_TYPES_KEYVALUEPAIR]); UA_KeyValueMap sendMap = {3, sendParameters}; const UA_Byte requestData[] = {0x00, 0x01, 0x7f, 0xff}; UA_ByteString request = UA_BYTESTRING_NULL; ck_assert_uint_eq(UA_ByteString_allocBuffer(&request, sizeof(requestData)), UA_STATUSCODE_GOOD); memcpy(request.data, requestData, sizeof(requestData)); ck_assert_uint_eq(cm->sendWithConnection(cm, ctx.clientId, &sendMap, &request), UA_STATUSCODE_GOOD); for(size_t i = 0; i < 200 && !ctx.responseReceived; i++) eventLoop->run(eventLoop, 20); ck_assert(ctx.requestReceived); ck_assert(ctx.responseReceived); ck_assert_uint_eq(ctx.responseStatus, 201); ck_assert(ctx.responseHeaderReceived); UA_StatusCode closeStatus = cm->closeConnection(cm, ctx.clientId); ck_assert(closeStatus == UA_STATUSCODE_GOOD || closeStatus == UA_STATUSCODE_BADNOTFOUND); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); eventLoop->stop(eventLoop); for(size_t i = 0; i < 100 && eventLoop->state != UA_EVENTLOOPSTATE_STOPPED; i++) eventLoop->run(eventLoop, 20); ck_assert_uint_eq(eventLoop->state, UA_EVENTLOOPSTATE_STOPPED); eventLoop->free(eventLoop); } END_TEST typedef enum { HTTP_TEST_ECHO, HTTP_TEST_EMPTY_RESPONSE, HTTP_TEST_NO_RESPONSE, HTTP_TEST_DUPLICATE_RESPONSE, HTTP_TEST_CLOSE_CONNECTION, HTTP_TEST_CLOSE_LISTENER, HTTP_TEST_FIXED_RESPONSE, HTTP_TEST_ONE_REQUEST_FAILS, HTTP_TEST_ONE_REQUEST_STALLS, HTTP_TEST_DUPLICATE_ENCODING_RESPONSE } HTTPTestMode; typedef struct { uintptr_t listenerId; uintptr_t clientId; uintptr_t acceptedConnectionId; uintptr_t stalledConnectionId; UA_UInt16 port; HTTPTestMode mode; size_t requestCount; size_t acceptedConnectionCount; size_t acceptedClosingCount; size_t clientClosingCount; size_t responseCount; size_t responseCompleteCount; size_t scalarHandleCallbacks; size_t arrayHandleCallbacks; size_t serverBodyBytes; size_t clientBodyBytes; size_t fixedResponseSize; UA_Boolean bodyMismatch; UA_Boolean responseHeaderReceived; UA_StatusCode firstSendStatus; UA_StatusCode secondSendStatus; UA_StatusCode sendAfterCloseStatus; UA_UInt16 responseStatus; UA_StatusCode lastRequestStatus; size_t goodRequestCompletions; size_t badRequestCompletions; } HTTPTestContext; static void fillPattern(UA_Byte *data, size_t length, size_t offset) { for(size_t i = 0; i < length; i++) data[i] = (UA_Byte)((offset + i) % 251); } static void advancedHTTPCallback(UA_ConnectionManager *cm, uintptr_t connectionId, void *application, void **connectionContext, UA_ConnectionState state, const UA_KeyValueMap *params, UA_ByteString msg) { HTTPTestContext *ctx = (HTTPTestContext*)application; const UA_UInt16 *listenPort = (const UA_UInt16*)UA_KeyValueMap_getScalar( params, UA_QUALIFIEDNAME(0, "listen-port"), &UA_TYPES[UA_TYPES_UINT16]); if(listenPort) { ctx->listenerId = connectionId; ctx->port = *listenPort; return; } if(state == UA_CONNECTIONSTATE_OPENING) { ctx->clientId = connectionId; return; } if(state == UA_CONNECTIONSTATE_CLOSING) { ck_assert_ptr_nonnull(connectionContext); if(connectionId == ctx->clientId) ctx->clientClosingCount++; else if(connectionId != ctx->listenerId) ctx->acceptedClosingCount++; return; } const UA_String *method = (const UA_String*)UA_KeyValueMap_getScalar( params, UA_QUALIFIEDNAME(0, "method"), &UA_TYPES[UA_TYPES_STRING]); if(!method && connectionId != ctx->clientId) { ck_assert_ptr_nonnull(connectionContext); ck_assert_ptr_null(*connectionContext); ck_assert_uint_ne(connectionId, ctx->listenerId); *connectionContext = ctx; ctx->acceptedConnectionCount++; return; } if(method) { ck_assert_ptr_nonnull(connectionContext); ck_assert_ptr_eq(*connectionContext, ctx); const UA_ByteString *requestRandom = (const UA_ByteString*)UA_KeyValueMap_getScalar( params, UA_QUALIFIEDNAME(0, "request-random"), &UA_TYPES[UA_TYPES_BYTESTRING]); ck_assert_ptr_nonnull(requestRandom); ck_assert_uint_eq(requestRandom->length, 32); ctx->acceptedConnectionId = connectionId; ctx->requestCount++; for(size_t i = 0; i < msg.length; i++) { if(msg.data[i] != (UA_Byte)((ctx->serverBodyBytes + i) % 251)) ctx->bodyMismatch = true; } ctx->serverBodyBytes += msg.length; if(ctx->mode == HTTP_TEST_CLOSE_LISTENER) { ck_assert_uint_eq(cm->closeConnection(cm, ctx->listenerId), UA_STATUSCODE_GOOD); return; } const UA_String *path = (const UA_String *)UA_KeyValueMap_getScalar( params, UA_QUALIFIEDNAME(0, "path"), &UA_TYPES[UA_TYPES_STRING]); const UA_String failPath = UA_STRING_STATIC("/fail"); if(ctx->mode == HTTP_TEST_ONE_REQUEST_FAILS && path && UA_String_equal(path, &failPath)) { ck_assert_uint_eq(cm->closeConnection(cm, connectionId), UA_STATUSCODE_GOOD); return; } const UA_String slowPath = UA_STRING_STATIC("/slow"); if(ctx->mode == HTTP_TEST_ONE_REQUEST_STALLS && path && UA_String_equal(path, &slowPath)) { ctx->stalledConnectionId = connectionId; return; } if(ctx->mode == HTTP_TEST_NO_RESPONSE) return; if(ctx->mode == HTTP_TEST_CLOSE_CONNECTION) { ck_assert_uint_eq(cm->closeConnection(cm, connectionId), UA_STATUSCODE_GOOD); UA_ByteString late = UA_BYTESTRING_ALLOC("late"); ctx->sendAfterCloseStatus = cm->sendWithConnection( cm, connectionId, &UA_KEYVALUEMAP_NULL, &late); ck_assert_ptr_null(late.data); return; } UA_ByteString response = UA_BYTESTRING_NULL; size_t responseSize = msg.length; if(ctx->mode == HTTP_TEST_EMPTY_RESPONSE) responseSize = 0; else if(ctx->mode == HTTP_TEST_FIXED_RESPONSE || ctx->mode == HTTP_TEST_ONE_REQUEST_FAILS) responseSize = ctx->fixedResponseSize; if(responseSize > 0) { ck_assert_uint_eq(UA_ByteString_allocBuffer(&response, responseSize), UA_STATUSCODE_GOOD); fillPattern(response.data, response.length, 0); } UA_UInt16 statusCode = 202; UA_String responseHeader = UA_STRING("present"); UA_String identityEncoding = UA_STRING("identity"); UA_KeyValuePair headers[3] = {0}; headers[0].key = UA_QUALIFIEDNAME(0, "x-response"); UA_Variant_setScalar(&headers[0].value, &responseHeader, &UA_TYPES[UA_TYPES_STRING]); size_t headersSize = 1; if(ctx->mode == HTTP_TEST_DUPLICATE_ENCODING_RESPONSE) { headers[1].key = UA_QUALIFIEDNAME(0, "content-encoding"); UA_Variant_setScalar(&headers[1].value, &identityEncoding, &UA_TYPES[UA_TYPES_STRING]); headers[2].key = UA_QUALIFIEDNAME(0, "Content-Encoding"); UA_Variant_setScalar(&headers[2].value, &identityEncoding, &UA_TYPES[UA_TYPES_STRING]); headersSize = 3; } UA_KeyValuePair responseParams[2] = {0}; responseParams[0].key = UA_QUALIFIEDNAME(0, "status-code"); UA_Variant_setScalar(&responseParams[0].value, &statusCode, &UA_TYPES[UA_TYPES_UINT16]); responseParams[1].key = UA_QUALIFIEDNAME(0, "headers"); UA_Variant_setArray(&responseParams[1].value, headers, headersSize, &UA_TYPES[UA_TYPES_KEYVALUEPAIR]); UA_KeyValueMap responseMap = {2, responseParams}; ctx->firstSendStatus = cm->sendWithConnection( cm, connectionId, &responseMap, &response); if(ctx->mode == HTTP_TEST_DUPLICATE_RESPONSE) { UA_ByteString duplicate = UA_BYTESTRING_ALLOC("duplicate"); ctx->secondSendStatus = cm->sendWithConnection( cm, connectionId, &responseMap, &duplicate); ck_assert_ptr_null(duplicate.data); } return; } if(connectionId != ctx->clientId) return; const UA_Variant *requestHandle = UA_KeyValueMap_get( params, UA_QUALIFIEDNAME(0, "handle")); if(requestHandle) { if(UA_Variant_hasScalarType(requestHandle, &UA_TYPES[UA_TYPES_UINT32])) { ck_assert_uint_eq(*(UA_UInt32*)requestHandle->data, 17); ctx->scalarHandleCallbacks++; } else if(UA_Variant_hasArrayType( requestHandle, &UA_TYPES[UA_TYPES_UINT16])) { ck_assert(UA_Variant_hasArrayType(requestHandle, &UA_TYPES[UA_TYPES_UINT16])); ck_assert_uint_eq(requestHandle->arrayLength, 2); const UA_UInt16 *values = (const UA_UInt16*)requestHandle->data; ck_assert_uint_eq(values[0], 23); ck_assert_uint_eq(values[1], 42); ctx->arrayHandleCallbacks++; } else ck_assert(UA_Variant_hasScalarType( requestHandle, &UA_TYPES[UA_TYPES_UINT64])); } const UA_Boolean *responseComplete = (const UA_Boolean*)UA_KeyValueMap_getScalar( params, UA_QUALIFIEDNAME(0, "response-complete"), &UA_TYPES[UA_TYPES_BOOLEAN]); if(responseComplete) { ck_assert(*responseComplete); ctx->responseCompleteCount++; const UA_StatusCode *requestStatus = (const UA_StatusCode *)UA_KeyValueMap_getScalar( params, UA_QUALIFIEDNAME(0, "request-status"), &UA_TYPES[UA_TYPES_STATUSCODE]); ck_assert_ptr_nonnull(requestStatus); ctx->lastRequestStatus = *requestStatus; if(*requestStatus == UA_STATUSCODE_GOOD) ctx->goodRequestCompletions++; else ctx->badRequestCompletions++; } const UA_UInt16 *statusCode = (const UA_UInt16*)UA_KeyValueMap_getScalar( params, UA_QUALIFIEDNAME(0, "status-code"), &UA_TYPES[UA_TYPES_UINT16]); if(statusCode) { ctx->responseStatus = *statusCode; if(msg.length == 0 && !responseComplete) ctx->responseCount++; } const UA_Variant *responseHeaders = UA_KeyValueMap_get( params, UA_QUALIFIEDNAME(0, "headers")); if(responseHeaders) { UA_KeyValuePair *headers = (UA_KeyValuePair*)responseHeaders->data; const UA_String expectedName = UA_STRING_STATIC("x-response"); for(size_t i = 0; i < responseHeaders->arrayLength; i++) { if(UA_String_equal(&headers[i].key.name, &expectedName)) ctx->responseHeaderReceived = true; } } for(size_t i = 0; i < msg.length; i++) { if(msg.data[i] != (UA_Byte)((ctx->clientBodyBytes + i) % 251)) ctx->bodyMismatch = true; } ctx->clientBodyBytes += msg.length; } static void runUntilCount(UA_EventLoop *eventLoop, const size_t *value, size_t expected, size_t iterations) { for(size_t i = 0; i < iterations && *value < expected; i++) eventLoop->run(eventLoop, 20); ck_assert_uint_ge(*value, expected); } #ifndef _WIN32 static int connectHttpRaw(UA_UInt16 port) { struct sockaddr_in address = {0}; address.sin_family = AF_INET; address.sin_port = htons(port); ck_assert_int_eq(inet_pton(AF_INET, "127.0.0.1", &address.sin_addr), 1); int fd = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); ck_assert_int_eq(connect(fd, (struct sockaddr*)&address, sizeof(address)), 0); return fd; } static void runUntilSocketReadable(UA_EventLoop *eventLoop, int fd, size_t iterations) { for(size_t i = 0; i < iterations; i++) { char byte; ssize_t received = recv(fd, &byte, 1, MSG_PEEK | MSG_DONTWAIT); if(received >= 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) return; eventLoop->run(eventLoop, 20); } ck_abort_msg("HTTP socket did not become readable"); } static ssize_t readSocketUntilClosed(UA_EventLoop *eventLoop, int fd, UA_Byte *buffer, size_t capacity, size_t iterations) { size_t total = 0; for(size_t i = 0; i < iterations && total < capacity; i++) { ssize_t received = recv(fd, &buffer[total], capacity - total, MSG_DONTWAIT); if(received > 0) { total += (size_t)received; continue; } if(received == 0) return (ssize_t)total; if(errno != EAGAIN && errno != EWOULDBLOCK) return total > 0 ? (ssize_t)total : -1; eventLoop->run(eventLoop, 20); } return (ssize_t)total; } #endif static void closeHTTPConnectionIfPresent(UA_ConnectionManager *cm, uintptr_t connectionId) { UA_StatusCode status = cm->closeConnection(cm, connectionId); ck_assert(status == UA_STATUSCODE_GOOD || status == UA_STATUSCODE_BADNOTFOUND); } static void openHTTPListener(UA_ConnectionManager *cm, HTTPTestContext *ctx, UA_UInt16 timeout, UA_UInt32 recvMax, UA_UInt32 sendMax, UA_Boolean useSSL, const UA_ByteString *certificate, const UA_ByteString *privateKey, const UA_ByteString *caCertificate) { UA_UInt16 port = 0; UA_Boolean listen = true; UA_KeyValuePair parameters[8] = {0}; size_t size = 0; parameters[size].key = UA_QUALIFIEDNAME(0, "port"); UA_Variant_setScalar(¶meters[size++].value, &port, &UA_TYPES[UA_TYPES_UINT16]); parameters[size].key = UA_QUALIFIEDNAME(0, "listen"); UA_Variant_setScalar(¶meters[size++].value, &listen, &UA_TYPES[UA_TYPES_BOOLEAN]); parameters[size].key = UA_QUALIFIEDNAME(0, "timeout"); UA_Variant_setScalar(¶meters[size++].value, &timeout, &UA_TYPES[UA_TYPES_UINT16]); if(recvMax) { parameters[size].key = UA_QUALIFIEDNAME(0, "recv-max-message-size"); UA_Variant_setScalar(¶meters[size++].value, &recvMax, &UA_TYPES[UA_TYPES_UINT32]); } if(sendMax) { parameters[size].key = UA_QUALIFIEDNAME(0, "send-max-message-size"); UA_Variant_setScalar(¶meters[size++].value, &sendMax, &UA_TYPES[UA_TYPES_UINT32]); } if(useSSL) { parameters[size].key = UA_QUALIFIEDNAME(0, "useSSL"); UA_Variant_setScalar(¶meters[size++].value, &useSSL, &UA_TYPES[UA_TYPES_BOOLEAN]); parameters[size].key = UA_QUALIFIEDNAME(0, "certificate"); UA_Variant_setScalar(¶meters[size++].value, (void*)(uintptr_t)certificate, &UA_TYPES[UA_TYPES_BYTESTRING]); parameters[size].key = UA_QUALIFIEDNAME(0, "private-key"); UA_Variant_setScalar(¶meters[size++].value, (void*)(uintptr_t)privateKey, &UA_TYPES[UA_TYPES_BYTESTRING]); } if(caCertificate) { parameters[size].key = UA_QUALIFIEDNAME(0, "ca-certificate"); UA_Variant_setScalar(¶meters[size++].value, (void*)(uintptr_t)caCertificate, &UA_TYPES[UA_TYPES_BYTESTRING]); } UA_KeyValueMap map = {size, parameters}; ck_assert_uint_eq(cm->openConnection(cm, &map, ctx, NULL, advancedHTTPCallback), UA_STATUSCODE_GOOD); ck_assert_uint_ne(ctx->listenerId, 0); ck_assert_uint_ne(ctx->port, 0); } static void openHTTPClient(UA_ConnectionManager *cm, HTTPTestContext *ctx, UA_UInt32 recvMax, UA_UInt32 sendMax, UA_Boolean useSSL, const UA_ByteString *certificate, const UA_ByteString *privateKey, const UA_ByteString *caCertificate) { UA_String address = UA_STRING("127.0.0.1"); UA_KeyValuePair parameters[8] = {0}; size_t size = 0; parameters[size].key = UA_QUALIFIEDNAME(0, "address"); UA_Variant_setScalar(¶meters[size++].value, &address, &UA_TYPES[UA_TYPES_STRING]); parameters[size].key = UA_QUALIFIEDNAME(0, "port"); UA_Variant_setScalar(¶meters[size++].value, &ctx->port, &UA_TYPES[UA_TYPES_UINT16]); if(recvMax) { parameters[size].key = UA_QUALIFIEDNAME(0, "recv-max-message-size"); UA_Variant_setScalar(¶meters[size++].value, &recvMax, &UA_TYPES[UA_TYPES_UINT32]); } if(sendMax) { parameters[size].key = UA_QUALIFIEDNAME(0, "send-max-message-size"); UA_Variant_setScalar(¶meters[size++].value, &sendMax, &UA_TYPES[UA_TYPES_UINT32]); } if(useSSL) { parameters[size].key = UA_QUALIFIEDNAME(0, "useSSL"); UA_Variant_setScalar(¶meters[size++].value, &useSSL, &UA_TYPES[UA_TYPES_BOOLEAN]); if(certificate) { parameters[size].key = UA_QUALIFIEDNAME(0, "certificate"); UA_Variant_setScalar(¶meters[size++].value, (void*)(uintptr_t)certificate, &UA_TYPES[UA_TYPES_BYTESTRING]); parameters[size].key = UA_QUALIFIEDNAME(0, "private-key"); UA_Variant_setScalar(¶meters[size++].value, (void*)(uintptr_t)privateKey, &UA_TYPES[UA_TYPES_BYTESTRING]); } parameters[size].key = UA_QUALIFIEDNAME(0, "ca-certificate"); UA_Variant_setScalar(¶meters[size++].value, (void*)(uintptr_t)caCertificate, &UA_TYPES[UA_TYPES_BYTESTRING]); } UA_KeyValueMap map = {size, parameters}; ck_assert_uint_eq(cm->openConnection(cm, &map, ctx, NULL, advancedHTTPCallback), UA_STATUSCODE_GOOD); ck_assert_uint_ne(ctx->clientId, 0); } static void openHTTPClientWithTimeout(UA_ConnectionManager *cm, HTTPTestContext *ctx, UA_UInt16 timeout) { UA_String address = UA_STRING("127.0.0.1"); UA_KeyValuePair parameters[3] = {0}; parameters[0].key = UA_QUALIFIEDNAME(0, "address"); UA_Variant_setScalar(¶meters[0].value, &address, &UA_TYPES[UA_TYPES_STRING]); parameters[1].key = UA_QUALIFIEDNAME(0, "port"); UA_Variant_setScalar(¶meters[1].value, &ctx->port, &UA_TYPES[UA_TYPES_UINT16]); parameters[2].key = UA_QUALIFIEDNAME(0, "timeout"); UA_Variant_setScalar(¶meters[2].value, &timeout, &UA_TYPES[UA_TYPES_UINT16]); UA_KeyValueMap map = {3, parameters}; ck_assert_uint_eq(cm->openConnection(cm, &map, ctx, NULL, advancedHTTPCallback), UA_STATUSCODE_GOOD); ck_assert_uint_ne(ctx->clientId, 0); } static void openHTTPClientAt(UA_ConnectionManager *cm, HTTPTestContext *ctx, UA_String *address, UA_UInt16 port) { UA_KeyValuePair parameters[2] = {0}; parameters[0].key = UA_QUALIFIEDNAME(0, "address"); UA_Variant_setScalar(¶meters[0].value, address, &UA_TYPES[UA_TYPES_STRING]); parameters[1].key = UA_QUALIFIEDNAME(0, "port"); UA_Variant_setScalar(¶meters[1].value, &port, &UA_TYPES[UA_TYPES_UINT16]); UA_KeyValueMap map = {2, parameters}; ck_assert_uint_eq(cm->openConnection(cm, &map, ctx, NULL, advancedHTTPCallback), UA_STATUSCODE_GOOD); ck_assert_uint_ne(ctx->clientId, 0); } static void sendPatternRequest(UA_ConnectionManager *cm, HTTPTestContext *ctx, size_t size) { UA_String method = UA_STRING("POST"); UA_String path = UA_STRING("/test"); UA_KeyValuePair parameters[2] = {0}; parameters[0].key = UA_QUALIFIEDNAME(0, "method"); UA_Variant_setScalar(¶meters[0].value, &method, &UA_TYPES[UA_TYPES_STRING]); parameters[1].key = UA_QUALIFIEDNAME(0, "path"); UA_Variant_setScalar(¶meters[1].value, &path, &UA_TYPES[UA_TYPES_STRING]); UA_KeyValueMap map = {2, parameters}; UA_ByteString body = UA_BYTESTRING_NULL; if(size > 0) { ck_assert_uint_eq(UA_ByteString_allocBuffer(&body, size), UA_STATUSCODE_GOOD); fillPattern(body.data, body.length, 0); } ck_assert_uint_eq(cm->sendWithConnection(cm, ctx->clientId, &map, size ? &body : NULL), UA_STATUSCODE_GOOD); } static void sendHandledRequestWithTimeout(UA_ConnectionManager *cm, HTTPTestContext *ctx, const char *pathValue, UA_UInt16 timeout) { UA_String method = UA_STRING("POST"); UA_String path = UA_STRING((char *)(uintptr_t)pathValue); UA_UInt64 requestHandle = 5381; for(const char *pos = pathValue; *pos; pos++) requestHandle = requestHandle * 33u + (UA_Byte)*pos; UA_KeyValuePair parameters[4] = {0}; parameters[0].key = UA_QUALIFIEDNAME(0, "method"); UA_Variant_setScalar(¶meters[0].value, &method, &UA_TYPES[UA_TYPES_STRING]); parameters[1].key = UA_QUALIFIEDNAME(0, "path"); UA_Variant_setScalar(¶meters[1].value, &path, &UA_TYPES[UA_TYPES_STRING]); parameters[2].key = UA_QUALIFIEDNAME(0, "handle"); UA_Variant_setScalar(¶meters[2].value, &requestHandle, &UA_TYPES[UA_TYPES_UINT64]); size_t parametersSize = 3; if(timeout > 0) { parameters[parametersSize].key = UA_QUALIFIEDNAME(0, "timeout"); UA_Variant_setScalar(¶meters[parametersSize++].value, &timeout, &UA_TYPES[UA_TYPES_UINT16]); } UA_KeyValueMap map = {parametersSize, parameters}; ck_assert_uint_eq(cm->sendWithConnection(cm, ctx->clientId, &map, NULL), UA_STATUSCODE_GOOD); } static void sendHandledRequest(UA_ConnectionManager *cm, HTTPTestContext *ctx, const char *pathValue) { sendHandledRequestWithTimeout(cm, ctx, pathValue, 0); } START_TEST(serverLargeAndEmptyBodies) { HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_ECHO; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 0, false, NULL, NULL, NULL); openHTTPClient(cm, &ctx, 0, 0, false, NULL, NULL, NULL); const size_t largeSize = 128 * 1024 + 17; sendPatternRequest(cm, &ctx, largeSize); runUntilCount(eventLoop, &ctx.clientBodyBytes, largeSize, 500); ck_assert_uint_eq(ctx.serverBodyBytes, largeSize); ck_assert_uint_eq(ctx.responseStatus, 202); ck_assert(ctx.responseHeaderReceived); ck_assert(!ctx.bodyMismatch); ctx.mode = HTTP_TEST_EMPTY_RESPONSE; sendPatternRequest(cm, &ctx, 0); runUntilCount(eventLoop, &ctx.responseCount, 2, 200); ck_assert_uint_eq(ctx.requestCount, 2); closeHTTPConnectionIfPresent(cm, ctx.clientId); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); stopEventLoop(eventLoop); } END_TEST START_TEST(clientRequestHandles) { HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_FIXED_RESPONSE; ctx.fixedResponseSize = 2; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 0, false, NULL, NULL, NULL); openHTTPClient(cm, &ctx, 0, 0, false, NULL, NULL, NULL); UA_String method = UA_STRING("POST"); UA_String path = UA_STRING("/handles"); UA_UInt32 scalarHandle = 17; UA_KeyValuePair scalarParams[3] = {0}; scalarParams[0].key = UA_QUALIFIEDNAME(0, "method"); UA_Variant_setScalar(&scalarParams[0].value, &method, &UA_TYPES[UA_TYPES_STRING]); scalarParams[1].key = UA_QUALIFIEDNAME(0, "path"); UA_Variant_setScalar(&scalarParams[1].value, &path, &UA_TYPES[UA_TYPES_STRING]); scalarParams[2].key = UA_QUALIFIEDNAME(0, "handle"); UA_Variant_setScalar(&scalarParams[2].value, &scalarHandle, &UA_TYPES[UA_TYPES_UINT32]); UA_KeyValueMap scalarMap = {3, scalarParams}; ck_assert_uint_eq(cm->sendWithConnection(cm, ctx.clientId, &scalarMap, NULL), UA_STATUSCODE_GOOD); ck_assert_uint_eq(cm->sendWithConnection(cm, ctx.clientId, &scalarMap, NULL), UA_STATUSCODE_BADINVALIDSTATE); scalarHandle = 99; /* The request retained a deep copy. */ UA_UInt16 arrayHandle[2] = {23, 42}; UA_KeyValuePair arrayParams[3] = {0}; arrayParams[0] = scalarParams[0]; arrayParams[1] = scalarParams[1]; arrayParams[2].key = UA_QUALIFIEDNAME(0, "handle"); UA_Variant_setArray(&arrayParams[2].value, arrayHandle, 2, &UA_TYPES[UA_TYPES_UINT16]); UA_KeyValueMap arrayMap = {3, arrayParams}; ck_assert_uint_eq(cm->sendWithConnection(cm, ctx.clientId, &arrayMap, NULL), UA_STATUSCODE_GOOD); arrayHandle[0] = 100; arrayHandle[1] = 101; runUntilCount(eventLoop, &ctx.responseCompleteCount, 2, 300); ck_assert_uint_eq(ctx.requestCount, 2); ck_assert_uint_eq(ctx.clientBodyBytes, 4); ck_assert_uint_ge(ctx.scalarHandleCallbacks, 3); ck_assert_uint_ge(ctx.arrayHandleCallbacks, 3); /* Legacy requests remain supported, but ambiguous concurrency is rejected. */ ctx.mode = HTTP_TEST_NO_RESPONSE; ck_assert_uint_eq(cm->sendWithConnection(cm, ctx.clientId, &UA_KEYVALUEMAP_NULL, NULL), UA_STATUSCODE_GOOD); ck_assert_uint_eq(cm->sendWithConnection(cm, ctx.clientId, &UA_KEYVALUEMAP_NULL, NULL), UA_STATUSCODE_BADINVALIDSTATE); closeHTTPConnectionIfPresent(cm, ctx.clientId); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); stopEventLoop(eventLoop); } END_TEST START_TEST(serverRequestLifecycle) { HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_DUPLICATE_RESPONSE; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 0, false, NULL, NULL, NULL); openHTTPClient(cm, &ctx, 0, 0, false, NULL, NULL, NULL); sendPatternRequest(cm, &ctx, 4); runUntilCount(eventLoop, &ctx.responseCompleteCount, 1, 200); ck_assert_uint_eq(ctx.firstSendStatus, UA_STATUSCODE_GOOD); ck_assert_uint_eq(ctx.secondSendStatus, UA_STATUSCODE_BADINVALIDSTATE); ck_assert_uint_eq(ctx.acceptedClosingCount, 0); ctx.mode = HTTP_TEST_CLOSE_CONNECTION; sendPatternRequest(cm, &ctx, 4); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 1, 200); ck_assert(ctx.sendAfterCloseStatus == UA_STATUSCODE_BADINVALIDSTATE || ctx.sendAfterCloseStatus == UA_STATUSCODE_BADNOTFOUND); ck_assert_uint_eq(ctx.acceptedClosingCount, 1); closeHTTPConnectionIfPresent(cm, ctx.clientId); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); stopEventLoop(eventLoop); } END_TEST START_TEST(serverBrokenCarrierIsolation) { HTTPTestContext first = {0}; first.mode = HTTP_TEST_NO_RESPONSE; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &first, 30, 0, 0, false, NULL, NULL, NULL); openHTTPClient(cm, &first, 0, 0, false, NULL, NULL, NULL); /* Leave one request outstanding, then break only its client-side carrier. */ sendPatternRequest(cm, &first, 0); runUntilCount(eventLoop, &first.requestCount, 1, 100); HTTPTestContext sibling = {0}; sibling.port = first.port; openHTTPClient(cm, &sibling, 0, 0, false, NULL, NULL, NULL); ck_assert_uint_eq(cm->closeConnection(cm, first.clientId), UA_STATUSCODE_GOOD); runUntilCount(eventLoop, &first.acceptedClosingCount, 1, 100); runUntilCount(eventLoop, &first.clientClosingCount, 1, 100); /* The sibling uses the same listener and must remain fully usable. */ first.mode = HTTP_TEST_FIXED_RESPONSE; first.fixedResponseSize = 2; sendPatternRequest(cm, &sibling, 0); runUntilCount(eventLoop, &sibling.responseCompleteCount, 1, 100); ck_assert_uint_eq(first.requestCount, 2); ck_assert_uint_eq(sibling.clientBodyBytes, 2); ck_assert_uint_eq(first.acceptedClosingCount, 1); closeHTTPConnectionIfPresent(cm, sibling.clientId); ck_assert_uint_eq(cm->closeConnection(cm, first.listenerId), UA_STATUSCODE_GOOD); runUntilCount(eventLoop, &first.acceptedClosingCount, 2, 100); stopEventLoop(eventLoop); } END_TEST START_TEST(clientRequestFailureIsolation) { HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_ONE_REQUEST_FAILS; ctx.fixedResponseSize = 2; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 0, false, NULL, NULL, NULL); openHTTPClient(cm, &ctx, 0, 0, false, NULL, NULL, NULL); sendHandledRequest(cm, &ctx, "/fail"); sendHandledRequest(cm, &ctx, "/ok"); runUntilCount(eventLoop, &ctx.responseCompleteCount, 2, 200); ck_assert_uint_eq(ctx.goodRequestCompletions, 1); ck_assert_uint_eq(ctx.badRequestCompletions, 1); ck_assert_uint_eq(ctx.clientBodyBytes, 2); ck_assert_uint_eq(ctx.clientClosingCount, 0); closeHTTPConnectionIfPresent(cm, ctx.clientId); closeHTTPConnectionIfPresent(cm, ctx.listenerId); stopEventLoop(eventLoop); } END_TEST START_TEST(clientSynchronousConnectionFailure) { HTTPTestContext ctx = {0}; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); /* Some libwebsockets resolvers report a DNS failure from inside * lws_client_connect_via_info. The callback must see an already-linked * request and cleanup must defer freeing it until the connect call * returns. */ UA_String address = UA_STRING("open62541.invalid"); openHTTPClientAt(cm, &ctx, &address, 80); sendHandledRequest(cm, &ctx, "/dns-failure"); runUntilCount(eventLoop, &ctx.responseCompleteCount, 1, 100); ck_assert_uint_eq(ctx.badRequestCompletions, 1); closeHTTPConnectionIfPresent(cm, ctx.clientId); stopEventLoop(eventLoop); } END_TEST START_TEST(clientRequestTimeoutIsolation) { HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_ONE_REQUEST_STALLS; ctx.fixedResponseSize = 2; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 0, false, NULL, NULL, NULL); openHTTPClient(cm, &ctx, 0, 0, false, NULL, NULL, NULL); /* The slow request overrides the binding timeout. Its sibling must finish * normally and the shared client binding must remain usable. */ sendHandledRequestWithTimeout(cm, &ctx, "/slow", 1); sendHandledRequest(cm, &ctx, "/ok"); runUntilCount(eventLoop, &ctx.responseCompleteCount, 2, 200); ck_assert_uint_eq(ctx.goodRequestCompletions, 1); ck_assert_uint_eq(ctx.badRequestCompletions, 1); ck_assert_uint_eq(ctx.clientClosingCount, 0); ck_assert_uint_ne(ctx.stalledConnectionId, 0); /* A response attempted after the timed-out carrier was destroyed cannot * generate a second callback or become associated with another request. */ UA_UInt16 statusCode = 200; UA_KeyValuePair responseParam = { UA_QUALIFIEDNAME(0, "status-code"), {0}}; UA_Variant_setScalar(&responseParam.value, &statusCode, &UA_TYPES[UA_TYPES_UINT16]); UA_KeyValueMap responseMap = {1, &responseParam}; UA_ByteString late = UA_BYTESTRING_ALLOC("late"); UA_StatusCode lateStatus = cm->sendWithConnection( cm, ctx.stalledConnectionId, &responseMap, &late); ck_assert(lateStatus == UA_STATUSCODE_GOOD || lateStatus == UA_STATUSCODE_BADNOTFOUND); ck_assert_ptr_null(late.data); for(size_t i = 0; i < 20; i++) eventLoop->run(eventLoop, 10); ck_assert_uint_eq(ctx.responseCompleteCount, 2); sendHandledRequest(cm, &ctx, "/ok"); runUntilCount(eventLoop, &ctx.responseCompleteCount, 3, 100); ck_assert_uint_eq(ctx.goodRequestCompletions, 2); ck_assert_uint_eq(ctx.badRequestCompletions, 1); ck_assert_uint_eq(ctx.clientClosingCount, 0); closeHTTPConnectionIfPresent(cm, ctx.clientId); closeHTTPConnectionIfPresent(cm, ctx.listenerId); stopEventLoop(eventLoop); } END_TEST START_TEST(serverResponseFailureIsolation) { #ifndef _WIN32 HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_FIXED_RESPONSE; ctx.fixedResponseSize = 8 * 1024 * 1024; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 0, false, NULL, NULL, NULL); struct sockaddr_in serverAddress; memset(&serverAddress, 0, sizeof(serverAddress)); serverAddress.sin_family = AF_INET; serverAddress.sin_port = htons(ctx.port); ck_assert_int_eq(inet_pton(AF_INET, "127.0.0.1", &serverAddress.sin_addr), 1); int fd = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); ck_assert_int_eq(connect(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)), 0); const char request[] = "POST /large HTTP/1.1\r\nHost: localhost\r\n" "Content-Length: 0\r\nConnection: keep-alive\r\n\r\n"; ck_assert_int_eq(send(fd, request, sizeof(request) - 1, 0), (ssize_t)(sizeof(request) - 1)); runUntilCount(eventLoop, &ctx.requestCount, 1, 100); struct linger reset = {1, 0}; ck_assert_int_eq(setsockopt(fd, SOL_SOCKET, SO_LINGER, &reset, sizeof(reset)), 0); close(fd); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 1, 200); /* A failed response carrier does not affect a new sibling carrier. */ ctx.fixedResponseSize = 2; openHTTPClient(cm, &ctx, 0, 0, false, NULL, NULL, NULL); sendPatternRequest(cm, &ctx, 0); runUntilCount(eventLoop, &ctx.responseCompleteCount, 1, 100); ck_assert_uint_eq(ctx.requestCount, 2); ck_assert_uint_eq(ctx.clientBodyBytes, 2); closeHTTPConnectionIfPresent(cm, ctx.clientId); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); stopEventLoop(eventLoop); #endif } END_TEST START_TEST(serverListenerCloseWithIdleCarrier) { #ifndef _WIN32 HTTPTestContext ctx = {0}; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 0, false, NULL, NULL, NULL); struct sockaddr_in serverAddress; memset(&serverAddress, 0, sizeof(serverAddress)); serverAddress.sin_family = AF_INET; serverAddress.sin_port = htons(ctx.port); ck_assert_int_eq(inet_pton(AF_INET, "127.0.0.1", &serverAddress.sin_addr), 1); int fd = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); ck_assert_int_eq(connect(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)), 0); for(size_t i = 0; i < 10; i++) eventLoop->run(eventLoop, 20); /* Shutdown must retain the listener until this idle carrier is detached. */ ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); for(size_t i = 0; i < 10; i++) eventLoop->run(eventLoop, 20); close(fd); stopEventLoop(eventLoop); #endif } END_TEST START_TEST(serverListenerCloseFromRequestCallback) { HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_CLOSE_LISTENER; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 0, false, NULL, NULL, NULL); openHTTPClient(cm, &ctx, 0, 0, false, NULL, NULL, NULL); /* Listener teardown is requested from inside the accepted connection's * request callback. Destruction is deferred until the callback has fully * returned, then the accepted carrier and client request complete once. */ sendPatternRequest(cm, &ctx, 0); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 1, 100); runUntilCount(eventLoop, &ctx.responseCompleteCount, 1, 100); ck_assert_uint_eq(ctx.requestCount, 1); ck_assert_uint_eq(ctx.acceptedClosingCount, 1); ck_assert_uint_eq(ctx.responseCompleteCount, 1); ck_assert_uint_ne(ctx.lastRequestStatus, UA_STATUSCODE_GOOD); closeHTTPConnectionIfPresent(cm, ctx.clientId); stopEventLoop(eventLoop); } END_TEST #ifdef UA_ENABLE_HTTP_COMPRESSION static int connectRawHttp(UA_UInt16 port) { struct sockaddr_in serverAddress; memset(&serverAddress, 0, sizeof(serverAddress)); serverAddress.sin_family = AF_INET; serverAddress.sin_port = htons(port); ck_assert_int_eq(inet_pton(AF_INET, "127.0.0.1", &serverAddress.sin_addr), 1); int fd = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); ck_assert_int_eq(connect(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)), 0); return fd; } static size_t findHeaderEnd(const UA_Byte *response, size_t length) { for(size_t i = 0; i + 3 < length; i++) { if(memcmp(&response[i], "\r\n\r\n", 4) == 0) return i + 4; } return 0; } START_TEST(serverContentEncodingAndExpansionLimit) { #ifndef _WIN32 HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_ECHO; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); UA_UInt16 port = 0; UA_UInt16 timeout = 30; UA_UInt32 wireLimit = 1024; UA_UInt32 expandedLimit = 8192; UA_Boolean listen = true; UA_KeyValuePair parameters[5] = {0}; parameters[0].key = UA_QUALIFIEDNAME(0, "port"); UA_Variant_setScalar(¶meters[0].value, &port, &UA_TYPES[UA_TYPES_UINT16]); parameters[1].key = UA_QUALIFIEDNAME(0, "listen"); UA_Variant_setScalar(¶meters[1].value, &listen, &UA_TYPES[UA_TYPES_BOOLEAN]); parameters[2].key = UA_QUALIFIEDNAME(0, "timeout"); UA_Variant_setScalar(¶meters[2].value, &timeout, &UA_TYPES[UA_TYPES_UINT16]); parameters[3].key = UA_QUALIFIEDNAME(0, "recv-max-message-size"); UA_Variant_setScalar(¶meters[3].value, &wireLimit, &UA_TYPES[UA_TYPES_UINT32]); parameters[4].key = UA_QUALIFIEDNAME( 0, "recv-max-decompressed-message-size"); UA_Variant_setScalar(¶meters[4].value, &expandedLimit, &UA_TYPES[UA_TYPES_UINT32]); UA_KeyValueMap map = {5, parameters}; ck_assert_uint_eq(cm->openConnection(cm, &map, &ctx, NULL, advancedHTTPCallback), UA_STATUSCODE_GOOD); UA_ByteString input = UA_BYTESTRING_NULL; ck_assert_uint_eq(UA_ByteString_allocBuffer(&input, 4096), UA_STATUSCODE_GOOD); fillPattern(input.data, input.length, 0); UA_ByteString compressed = UA_BYTESTRING_NULL; ck_assert_uint_eq(UA_HTTP_compress(UA_HTTP_CONTENT_ENCODING_GZIP, &input, &compressed), UA_STATUSCODE_GOOD); ck_assert_uint_lt(compressed.length, wireLimit); int fd = connectRawHttp(ctx.port); char header[512]; int headerLength = snprintf( header, sizeof(header), "POST /compressed HTTP/1.1\r\nHost: localhost\r\n" "Content-Encoding: gzip\r\nAccept-Encoding: gzip\r\n" "Content-Length: %zu\r\nConnection: close\r\n\r\n", compressed.length); ck_assert_int_gt(headerLength, 0); ck_assert_int_eq(send(fd, header, (size_t)headerLength, 0), headerLength); ck_assert_int_eq(send(fd, compressed.data, compressed.length, 0), (ssize_t)compressed.length); runUntilCount(eventLoop, &ctx.requestCount, 1, 200); runUntilSocketReadable(eventLoop, fd, 100); UA_Byte response[16384]; ssize_t received = readSocketUntilClosed( eventLoop, fd, response, sizeof(response) - 1, 100); ck_assert_int_gt(received, 0); close(fd); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 1, 200); response[received] = 0; ck_assert_ptr_nonnull(strstr((char*)response, "HTTP/1.1 202")); ck_assert_ptr_nonnull(strstr((char*)response, "Content-Encoding: gzip")); size_t bodyOffset = findHeaderEnd(response, (size_t)received); ck_assert_uint_gt(bodyOffset, 0); UA_ByteString encodedResponse = { (size_t)received - bodyOffset, &response[bodyOffset] }; UA_ByteString decodedResponse = UA_BYTESTRING_NULL; ck_assert_uint_eq(UA_HTTP_decompress(UA_HTTP_CONTENT_ENCODING_GZIP, &encodedResponse, 8192, &decodedResponse), UA_STATUSCODE_GOOD); ck_assert(UA_ByteString_equal(&input, &decodedResponse)); UA_ByteString_clear(&decodedResponse); UA_ByteString_clear(&compressed); UA_ByteString_clear(&input); /* A small wire body that expands past the independent limit gets 413 and * is never delivered to the application. */ ck_assert_uint_eq(UA_ByteString_allocBuffer(&input, expandedLimit + 1), UA_STATUSCODE_GOOD); memset(input.data, 0, input.length); ck_assert_uint_eq(UA_HTTP_compress(UA_HTTP_CONTENT_ENCODING_GZIP, &input, &compressed), UA_STATUSCODE_GOOD); fd = connectRawHttp(ctx.port); headerLength = snprintf( header, sizeof(header), "POST /bomb HTTP/1.1\r\nHost: localhost\r\n" "Content-Encoding: gzip\r\nContent-Length: %zu\r\n" "Connection: close\r\n\r\n", compressed.length); ck_assert_int_eq(send(fd, header, (size_t)headerLength, 0), headerLength); ck_assert_int_eq(send(fd, compressed.data, compressed.length, 0), (ssize_t)compressed.length); runUntilSocketReadable(eventLoop, fd, 100); received = readSocketUntilClosed( eventLoop, fd, response, sizeof(response) - 1, 100); ck_assert_int_gt(received, 0); close(fd); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 2, 200); response[received] = 0; ck_assert_ptr_nonnull(strstr((char*)response, "HTTP/1.1 413")); ck_assert_uint_eq(ctx.requestCount, 1); UA_ByteString_clear(&compressed); UA_ByteString_clear(&input); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); stopEventLoop(eventLoop); #endif } END_TEST START_TEST(clientCompressedResponse) { #ifndef _WIN32 HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_FIXED_RESPONSE; ctx.fixedResponseSize = 4096; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 0, false, NULL, NULL, NULL); UA_String address = UA_STRING("127.0.0.1"); UA_UInt32 wireLimit = 1024; UA_UInt32 expandedLimit = 8192; UA_KeyValuePair parameters[4] = {0}; parameters[0].key = UA_QUALIFIEDNAME(0, "address"); UA_Variant_setScalar(¶meters[0].value, &address, &UA_TYPES[UA_TYPES_STRING]); parameters[1].key = UA_QUALIFIEDNAME(0, "port"); UA_Variant_setScalar(¶meters[1].value, &ctx.port, &UA_TYPES[UA_TYPES_UINT16]); parameters[2].key = UA_QUALIFIEDNAME(0, "recv-max-message-size"); UA_Variant_setScalar(¶meters[2].value, &wireLimit, &UA_TYPES[UA_TYPES_UINT32]); parameters[3].key = UA_QUALIFIEDNAME( 0, "recv-max-decompressed-message-size"); UA_Variant_setScalar(¶meters[3].value, &expandedLimit, &UA_TYPES[UA_TYPES_UINT32]); UA_KeyValueMap map = {4, parameters}; ck_assert_uint_eq(cm->openConnection(cm, &map, &ctx, NULL, advancedHTTPCallback), UA_STATUSCODE_GOOD); sendPatternRequest(cm, &ctx, 0); runUntilCount(eventLoop, &ctx.responseCompleteCount, 1, 200); ck_assert_uint_eq(ctx.clientBodyBytes, ctx.fixedResponseSize); ck_assert(!ctx.bodyMismatch); ck_assert_uint_eq(ctx.responseStatus, 202); closeHTTPConnectionIfPresent(cm, ctx.clientId); closeHTTPConnectionIfPresent(cm, ctx.listenerId); stopEventLoop(eventLoop); #endif } END_TEST #endif START_TEST(clientRejectsDuplicateContentEncoding) { HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_DUPLICATE_ENCODING_RESPONSE; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 0, false, NULL, NULL, NULL); openHTTPClientWithTimeout(cm, &ctx, 1); sendPatternRequest(cm, &ctx, 0); runUntilCount(eventLoop, &ctx.responseCompleteCount, 1, 200); ck_assert_uint_eq(ctx.responseCompleteCount, 1); ck_assert_uint_ne(ctx.lastRequestStatus, UA_STATUSCODE_GOOD); closeHTTPConnectionIfPresent(cm, ctx.clientId); closeHTTPConnectionIfPresent(cm, ctx.listenerId); stopEventLoop(eventLoop); } END_TEST START_TEST(serverTimeoutAndListenerClose) { HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_NO_RESPONSE; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 1, 0, 0, false, NULL, NULL, NULL); openHTTPClient(cm, &ctx, 0, 0, false, NULL, NULL, NULL); sendPatternRequest(cm, &ctx, 0); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 1, 150); ck_assert_uint_eq(ctx.acceptedClosingCount, 1); runUntilCount(eventLoop, &ctx.responseCompleteCount, 1, 100); ck_assert_uint_ne(ctx.lastRequestStatus, UA_STATUSCODE_GOOD); ck_assert_uint_eq(ctx.clientClosingCount, 0); sendPatternRequest(cm, &ctx, 0); runUntilCount(eventLoop, &ctx.requestCount, 2, 100); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 2, 100); ck_assert_uint_eq(ctx.acceptedClosingCount, 2); closeHTTPConnectionIfPresent(cm, ctx.clientId); stopEventLoop(eventLoop); } END_TEST START_TEST(serverReceiveAndKeepAliveTimeouts) { #ifndef _WIN32 HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_FIXED_RESPONSE; ctx.fixedResponseSize = 2; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 1, 0, 0, false, NULL, NULL, NULL); struct sockaddr_in serverAddress; memset(&serverAddress, 0, sizeof(serverAddress)); serverAddress.sin_family = AF_INET; serverAddress.sin_port = htons(ctx.port); ck_assert_int_eq(inet_pton(AF_INET, "127.0.0.1", &serverAddress.sin_addr), 1); /* A client that never completes its headers cannot retain the raw WSI. */ int fd = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); ck_assert_int_eq(connect(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)), 0); const char partialHeader[] = "POST /slow HTTP/1.1\r\nHost:"; ck_assert_int_eq(send(fd, partialHeader, sizeof(partialHeader) - 1, 0), (ssize_t)(sizeof(partialHeader) - 1)); for(size_t i = 0; i < 75; i++) eventLoop->run(eventLoop, 20); char byte; ssize_t received = recv(fd, &byte, 1, MSG_DONTWAIT); ck_assert(received == 0 || (received < 0 && errno != EAGAIN && errno != EWOULDBLOCK)); ck_assert_uint_eq(ctx.requestCount, 0); close(fd); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 1, 100); /* An incomplete request body times out the accepted connection without * delivering a request to the application. */ fd = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); ck_assert_int_eq(connect(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)), 0); const char partialBody[] = "POST /slow-body HTTP/1.1\r\n" "Host: 127.0.0.1\r\n" "Content-Length: 4\r\n\r\n" "x"; ck_assert_int_eq(send(fd, partialBody, sizeof(partialBody) - 1, 0), (ssize_t)(sizeof(partialBody) - 1)); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 2, 100); ck_assert_uint_eq(ctx.requestCount, 0); close(fd); /* Body progress within the absolute request deadline is accepted. The * keep-alive socket is then bounded again after the response completes. */ fd = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); ck_assert_int_eq(connect(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)), 0); const char progressHeader[] = "POST /progress HTTP/1.1\r\n" "Host: 127.0.0.1\r\n" "Content-Length: 3\r\n" "Connection: keep-alive\r\n\r\n"; ck_assert_int_eq(send(fd, progressHeader, sizeof(progressHeader) - 1, 0), (ssize_t)(sizeof(progressHeader) - 1)); for(UA_Byte value = 0; value < 3; value++) { ck_assert_int_eq(send(fd, &value, 1, 0), 1); if(value < 2) { for(size_t i = 0; i < 15; i++) eventLoop->run(eventLoop, 20); } } runUntilCount(eventLoop, &ctx.requestCount, 1, 100); ck_assert_uint_eq(ctx.requestCount, 1); char response[1024]; runUntilSocketReadable(eventLoop, fd, 100); received = recv(fd, response, sizeof(response), 0); ck_assert_int_gt(received, 0); /* LWS can write the header and body in separate callbacks. Drain both * before checking that the later keep-alive timeout closed the socket. */ for(size_t i = 0; i < 10; i++) { eventLoop->run(eventLoop, 10); while(recv(fd, response, sizeof(response), MSG_DONTWAIT) > 0) {} } for(size_t i = 0; i < 75; i++) eventLoop->run(eventLoop, 20); received = recv(fd, &byte, 1, MSG_DONTWAIT); ck_assert(received == 0 || (received < 0 && errno != EAGAIN && errno != EWOULDBLOCK)); close(fd); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 3, 100); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); stopEventLoop(eventLoop); #endif } END_TEST START_TEST(serverConnectionCapAndRequestDeadline) { #ifndef _WIN32 HTTPTestContext ctx = {0}; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); UA_UInt16 port = 0, timeout = 1; UA_UInt32 maxConnections = 1; UA_Boolean listen = true; UA_KeyValuePair parameters[4] = {0}; parameters[0].key = UA_QUALIFIEDNAME(0, "port"); UA_Variant_setScalar(¶meters[0].value, &port, &UA_TYPES[UA_TYPES_UINT16]); parameters[1].key = UA_QUALIFIEDNAME(0, "listen"); UA_Variant_setScalar(¶meters[1].value, &listen, &UA_TYPES[UA_TYPES_BOOLEAN]); parameters[2].key = UA_QUALIFIEDNAME(0, "timeout"); UA_Variant_setScalar(¶meters[2].value, &timeout, &UA_TYPES[UA_TYPES_UINT16]); parameters[3].key = UA_QUALIFIEDNAME(0, "max-connections"); UA_Variant_setScalar(¶meters[3].value, &maxConnections, &UA_TYPES[UA_TYPES_UINT32]); UA_KeyValueMap map = {4, parameters}; ck_assert_uint_eq(cm->openConnection(cm, &map, &ctx, NULL, advancedHTTPCallback), UA_STATUSCODE_GOOD); int first = connectHttpRaw(ctx.port); runUntilCount(eventLoop, &ctx.acceptedConnectionCount, 1, 30); int second = connectHttpRaw(ctx.port); for(size_t i = 0; i < 10; i++) eventLoop->run(eventLoop, 20); ck_assert_uint_eq(ctx.acceptedConnectionCount, 1); /* Each fragment arrives before the inactivity timeout. The incomplete * request must still close at the absolute one-second deadline. */ const char fragment[] = "GET /"; ck_assert_int_eq(send(first, fragment, sizeof(fragment) - 1, 0), (ssize_t)(sizeof(fragment) - 1)); for(size_t i = 0; i < 20; i++) eventLoop->run(eventLoop, 20); ck_assert_int_eq(send(first, "slow", 4, 0), 4); for(size_t i = 0; i < 20; i++) eventLoop->run(eventLoop, 20); const char finalFragment[] = " HTTP/1.1\r\nHost:"; ck_assert_int_eq(send(first, finalFragment, sizeof(finalFragment) - 1, 0), (ssize_t)(sizeof(finalFragment) - 1)); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 1, 25); ck_assert_uint_eq(ctx.requestCount, 0); close(first); close(second); int third = connectHttpRaw(ctx.port); runUntilCount(eventLoop, &ctx.acceptedConnectionCount, 2, 30); close(third); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); stopEventLoop(eventLoop); #endif } END_TEST START_TEST(clientServerMutualTls) { HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_EMPTY_RESPONSE; UA_ByteString certificate = loadFile("server_cert.der"); UA_ByteString privateKey = loadFile("server_key.der"); ck_assert_ptr_nonnull(certificate.data); ck_assert_ptr_nonnull(privateKey.data); UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("https")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 0, true, &certificate, &privateKey, &certificate); openHTTPClient(cm, &ctx, 0, 0, true, &certificate, &privateKey, &certificate); sendPatternRequest(cm, &ctx, 0); runUntilCount(eventLoop, &ctx.responseCount, 1, 300); ck_assert_uint_eq(ctx.requestCount, 1); ck_assert_uint_eq(ctx.responseStatus, 202); ck_assert(ctx.responseHeaderReceived); closeHTTPConnectionIfPresent(cm, ctx.clientId); runUntilCount(eventLoop, &ctx.clientClosingCount, 1, 100); /* The listener CA makes a client certificate mandatory. */ openHTTPClient(cm, &ctx, 0, 0, true, NULL, NULL, &certificate); sendPatternRequest(cm, &ctx, 0); runUntilCount(eventLoop, &ctx.responseCompleteCount, 2, 200); ck_assert_uint_ne(ctx.lastRequestStatus, UA_STATUSCODE_GOOD); ck_assert_uint_eq(ctx.requestCount, 1); closeHTTPConnectionIfPresent(cm, ctx.clientId); runUntilCount(eventLoop, &ctx.clientClosingCount, 2, 100); UA_ByteString_clear(&certificate); UA_ByteString_clear(&privateKey); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); stopEventLoop(eventLoop); } END_TEST START_TEST(messageSizeLimits) { /* Client send limit. */ HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_EMPTY_RESPONSE; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 4, 0, false, NULL, NULL, NULL); openHTTPClient(cm, &ctx, 0, 4, false, NULL, NULL, NULL); UA_ByteString tooLarge = UA_BYTESTRING_ALLOC("12345"); ck_assert_uint_eq(cm->sendWithConnection(cm, ctx.clientId, &UA_KEYVALUEMAP_NULL, &tooLarge), UA_STATUSCODE_BADREQUESTTOOLARGE); ck_assert_ptr_null(tooLarge.data); /* Listener receive limit on a client without a send limit. */ sendPatternRequest(cm, &ctx, 4); runUntilCount(eventLoop, &ctx.responseCount, 1, 200); ck_assert_uint_eq(ctx.requestCount, 1); closeHTTPConnectionIfPresent(cm, ctx.clientId); runUntilCount(eventLoop, &ctx.clientClosingCount, 1, 100); openHTTPClient(cm, &ctx, 0, 0, false, NULL, NULL, NULL); sendPatternRequest(cm, &ctx, 5); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 2, 200); ck_assert_uint_eq(ctx.requestCount, 1); closeHTTPConnectionIfPresent(cm, ctx.clientId); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); stopEventLoop(eventLoop); /* Listener response and client receive limits. */ memset(&ctx, 0, sizeof(ctx)); ctx.mode = HTTP_TEST_FIXED_RESPONSE; ctx.fixedResponseSize = 5; cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 4, false, NULL, NULL, NULL); openHTTPClient(cm, &ctx, 4, 0, false, NULL, NULL, NULL); sendPatternRequest(cm, &ctx, 0); runUntilCount(eventLoop, &ctx.requestCount, 1, 100); ck_assert_uint_eq(ctx.firstSendStatus, UA_STATUSCODE_BADRESPONSETOOLARGE); ck_assert_uint_eq(cm->closeConnection(cm, ctx.acceptedConnectionId), UA_STATUSCODE_GOOD); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 1, 100); closeHTTPConnectionIfPresent(cm, ctx.clientId); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); stopEventLoop(eventLoop); memset(&ctx, 0, sizeof(ctx)); ctx.mode = HTTP_TEST_FIXED_RESPONSE; ctx.fixedResponseSize = 5; cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 0, false, NULL, NULL, NULL); openHTTPClient(cm, &ctx, 4, 0, false, NULL, NULL, NULL); sendPatternRequest(cm, &ctx, 0); runUntilCount(eventLoop, &ctx.responseCompleteCount, 1, 200); ck_assert_uint_eq(ctx.lastRequestStatus, UA_STATUSCODE_BADRESPONSETOOLARGE); ck_assert_uint_eq(ctx.clientBodyBytes, 0); closeHTTPConnectionIfPresent(cm, ctx.clientId); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); stopEventLoop(eventLoop); } END_TEST START_TEST(parameterValidation) { UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); UA_UInt16 port = 0; UA_Boolean listen = true; UA_Boolean validate = true; UA_KeyValuePair parameters[4] = {0}; parameters[0].key = UA_QUALIFIEDNAME(0, "port"); UA_Variant_setScalar(¶meters[0].value, &port, &UA_TYPES[UA_TYPES_UINT16]); parameters[1].key = UA_QUALIFIEDNAME(0, "listen"); UA_Variant_setScalar(¶meters[1].value, &listen, &UA_TYPES[UA_TYPES_BOOLEAN]); parameters[2].key = UA_QUALIFIEDNAME(0, "validate"); UA_Variant_setScalar(¶meters[2].value, &validate, &UA_TYPES[UA_TYPES_BOOLEAN]); UA_KeyValueMap validMap = {3, parameters}; ck_assert_uint_eq(cm->openConnection(cm, &validMap, NULL, NULL, NULL), UA_STATUSCODE_GOOD); UA_Boolean useSSL = true; parameters[3].key = UA_QUALIFIEDNAME(0, "useSSL"); UA_Variant_setScalar(¶meters[3].value, &useSSL, &UA_TYPES[UA_TYPES_BOOLEAN]); UA_KeyValueMap incompleteTlsMap = {4, parameters}; ck_assert_uint_eq(cm->openConnection(cm, &incompleteTlsMap, NULL, NULL, NULL), UA_STATUSCODE_BADINVALIDARGUMENT); const char *oldStringParameters[] = { "username", "password", "client-key-password" }; UA_String oldStringValue = UA_STRING("unused"); for(size_t i = 0; i < 3; i++) { parameters[3].key = UA_QUALIFIEDNAME(0, (char*)(uintptr_t)oldStringParameters[i]); UA_Variant_setScalar(¶meters[3].value, &oldStringValue, &UA_TYPES[UA_TYPES_STRING]); UA_KeyValueMap oldMap = {4, parameters}; ck_assert_uint_ne(cm->openConnection(cm, &oldMap, NULL, NULL, NULL), UA_STATUSCODE_GOOD); } const char *oldByteStringParameters[] = { "ca-cert", "client-cert", "client-key" }; UA_ByteString oldByteStringValue = UA_BYTESTRING("unused"); for(size_t i = 0; i < 3; i++) { parameters[3].key = UA_QUALIFIEDNAME( 0, (char*)(uintptr_t)oldByteStringParameters[i]); UA_Variant_setScalar(¶meters[3].value, &oldByteStringValue, &UA_TYPES[UA_TYPES_BYTESTRING]); UA_KeyValueMap oldMap = {4, parameters}; ck_assert_uint_ne(cm->openConnection(cm, &oldMap, NULL, NULL, NULL), UA_STATUSCODE_GOOD); } UA_String oldHeader = UA_STRING("x-test=value"); UA_KeyValuePair oldSendParameter = {0}; oldSendParameter.key = UA_QUALIFIEDNAME(0, "header"); UA_Variant_setScalar(&oldSendParameter.value, &oldHeader, &UA_TYPES[UA_TYPES_STRING]); UA_KeyValueMap oldSendMap = {1, &oldSendParameter}; UA_ByteString body = UA_BYTESTRING_ALLOC("consumed"); ck_assert_uint_ne(cm->sendWithConnection(cm, 1, &oldSendMap, &body), UA_STATUSCODE_GOOD); ck_assert_ptr_null(body.data); UA_UInt16 invalidStatus = 99; UA_KeyValuePair invalidStatusParameter = {0}; invalidStatusParameter.key = UA_QUALIFIEDNAME(0, "status-code"); UA_Variant_setScalar(&invalidStatusParameter.value, &invalidStatus, &UA_TYPES[UA_TYPES_UINT16]); UA_KeyValueMap invalidStatusMap = {1, &invalidStatusParameter}; ck_assert_uint_eq(cm->sendWithConnection(cm, 1, &invalidStatusMap, NULL), UA_STATUSCODE_BADINVALIDARGUMENT); UA_String contentLength = UA_STRING("5"); UA_KeyValuePair managedHeader = {0}; managedHeader.key = UA_QUALIFIEDNAME(0, "content-length"); UA_Variant_setScalar(&managedHeader.value, &contentLength, &UA_TYPES[UA_TYPES_STRING]); UA_KeyValuePair headersParameter = {0}; headersParameter.key = UA_QUALIFIEDNAME(0, "headers"); UA_Variant_setArray(&headersParameter.value, &managedHeader, 1, &UA_TYPES[UA_TYPES_KEYVALUEPAIR]); UA_KeyValueMap managedHeaderMap = {1, &headersParameter}; ck_assert_uint_eq(cm->sendWithConnection(cm, 1, &managedHeaderMap, NULL), UA_STATUSCODE_BADINVALIDARGUMENT); UA_String injectedValue = UA_STRING("ok\r\nx-injected: yes"); managedHeader.key = UA_QUALIFIEDNAME(0, "x-test"); UA_Variant_setScalar(&managedHeader.value, &injectedValue, &UA_TYPES[UA_TYPES_STRING]); ck_assert_uint_eq(cm->sendWithConnection(cm, 1, &managedHeaderMap, NULL), UA_STATUSCODE_BADINVALIDARGUMENT); UA_String validValue = UA_STRING("value"); managedHeader.key = UA_QUALIFIEDNAME(0, "x invalid"); UA_Variant_setScalar(&managedHeader.value, &validValue, &UA_TYPES[UA_TYPES_STRING]); ck_assert_uint_eq(cm->sendWithConnection(cm, 1, &managedHeaderMap, NULL), UA_STATUSCODE_BADINVALIDARGUMENT); stopEventLoop(eventLoop); } END_TEST START_TEST(chunkedRequestAndKeepAlive) { #ifndef _WIN32 HTTPTestContext ctx = {0}; ctx.mode = HTTP_TEST_FIXED_RESPONSE; ctx.fixedResponseSize = 2; UA_ConnectionManager *cm = UA_ConnectionManager_new_HTTP(UA_STRING("http")); UA_EventLoop *eventLoop = UA_EventLoop_new_POSIX(UA_Log_Stdout); ck_assert_ptr_nonnull(cm); ck_assert_ptr_nonnull(eventLoop); ck_assert_uint_eq(eventLoop->registerEventSource(eventLoop, &cm->eventSource), UA_STATUSCODE_GOOD); ck_assert_uint_eq(eventLoop->start(eventLoop), UA_STATUSCODE_GOOD); openHTTPListener(cm, &ctx, 30, 0, 0, false, NULL, NULL, NULL); int fd = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); struct sockaddr_in serverAddress; memset(&serverAddress, 0, sizeof(serverAddress)); serverAddress.sin_family = AF_INET; serverAddress.sin_port = htons(ctx.port); ck_assert_int_eq(inet_pton(AF_INET, "127.0.0.1", &serverAddress.sin_addr), 1); ck_assert_int_eq(connect(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)), 0); int fd2 = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd2, 0); ck_assert_int_eq(connect(fd2, (struct sockaddr*)&serverAddress, sizeof(serverAddress)), 0); const char chunkedHeaders[] = "POST /chunked-a HTTP/1.1\r\n" "Host: 127.0.0.1\r\n" "Transfer-Encoding: chunked\r\n" "Connection: keep-alive\r\n\r\n"; const char chunkedHeaders2[] = "POST /chunked-b HTTP/1.1\r\n" "Host: 127.0.0.1\r\n" "Transfer-Encoding: chunked\r\n" "Connection: keep-alive\r\n\r\n"; ck_assert_int_eq(send(fd, chunkedHeaders, sizeof(chunkedHeaders) - 1, 0), (ssize_t)(sizeof(chunkedHeaders) - 1)); ck_assert_int_eq(send(fd2, chunkedHeaders2, sizeof(chunkedHeaders2) - 1, 0), (ssize_t)(sizeof(chunkedHeaders2) - 1)); const UA_Byte firstChunk[] = {'2', '\r', '\n', 0, 1, '\r', '\n'}; const UA_Byte secondChunk[] = { '3', '\r', '\n', 2, 3, 4, '\r', '\n', '0', '\r', '\n', '\r', '\n' }; const UA_Byte firstChunk2[] = {'2', '\r', '\n', 5, 6, '\r', '\n'}; const UA_Byte secondChunk2[] = { '3', ';', 'x', '=', 'y', '\r', '\n', 7, 8, 9, '\r', '\n', '0', '\r', '\n', 'X', '-', 'T', 'r', 'a', 'i', 'l', 'e', 'r', ':', ' ', 'o', 'k', '\r', '\n', '\r', '\n' }; /* Feed the first chunk across every possible callback boundary and * interleave a second client's chunks. Each raw WSI owns its parser. */ for(size_t i = 0; i < sizeof(firstChunk); i++) { ck_assert_int_eq(send(fd, &firstChunk[i], 1, 0), 1); eventLoop->run(eventLoop, 0); } ck_assert_int_eq(send(fd2, firstChunk2, sizeof(firstChunk2), 0), (ssize_t)sizeof(firstChunk2)); ck_assert_int_eq(send(fd, secondChunk, sizeof(secondChunk), 0), (ssize_t)sizeof(secondChunk)); runUntilCount(eventLoop, &ctx.requestCount, 1, 200); ck_assert_uint_eq(ctx.requestCount, 1); ck_assert_uint_eq(ctx.acceptedConnectionCount, 2); ck_assert_uint_eq(ctx.serverBodyBytes, 5); ck_assert(!ctx.bodyMismatch); char response[2048]; runUntilSocketReadable(eventLoop, fd, 100); ssize_t received = recv(fd, response, sizeof(response), 0); ck_assert_int_gt(received, 0); close(fd); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 1, 200); ck_assert_int_eq(send(fd2, secondChunk2, sizeof(secondChunk2), 0), (ssize_t)sizeof(secondChunk2)); runUntilCount(eventLoop, &ctx.requestCount, 2, 200); ck_assert_uint_eq(ctx.requestCount, 2); ck_assert_uint_eq(ctx.serverBodyBytes, 10); ck_assert(!ctx.bodyMismatch); runUntilSocketReadable(eventLoop, fd2, 100); received = recv(fd2, response, sizeof(response), 0); ck_assert_int_gt(received, 0); close(fd2); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 2, 200); /* Two requests over one HTTP/1.1 connection exercise keep-alive reuse. */ fd = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); ck_assert_int_eq(connect(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)), 0); const char firstRequest[] = "CUSTOM /first HTTP/1.1\r\n" "Host: 127.0.0.1\r\n" "Connection: keep-alive\r\n\r\n"; ck_assert_int_eq(send(fd, firstRequest, sizeof(firstRequest) - 1, 0), (ssize_t)(sizeof(firstRequest) - 1)); runUntilCount(eventLoop, &ctx.requestCount, 3, 200); ck_assert_uint_eq(ctx.requestCount, 3); ck_assert_uint_eq(ctx.acceptedConnectionCount, 3); uintptr_t keepAliveConnectionId = ctx.acceptedConnectionId; runUntilSocketReadable(eventLoop, fd, 100); received = recv(fd, response, sizeof(response), 0); ck_assert_int_gt(received, 0); const char secondRequest[] = "GET /second HTTP/1.1\r\n" "Host: 127.0.0.1\r\n" "Connection: close\r\n\r\n"; ck_assert_int_eq(send(fd, secondRequest, sizeof(secondRequest) - 1, 0), (ssize_t)(sizeof(secondRequest) - 1)); runUntilCount(eventLoop, &ctx.requestCount, 4, 200); ck_assert_uint_eq(ctx.requestCount, 4); ck_assert_uint_eq(ctx.acceptedConnectionId, keepAliveConnectionId); ck_assert_uint_eq(ctx.acceptedConnectionCount, 3); runUntilSocketReadable(eventLoop, fd, 100); received = recv(fd, response, sizeof(response), 0); ck_assert_int_gt(received, 0); close(fd); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 3, 200); /* Ambiguous framing is rejected before delivery. */ fd = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); ck_assert_int_eq(connect(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)), 0); const char ambiguousRequest[] = "POST /ambiguous HTTP/1.1\r\n" "Host: 127.0.0.1\r\n" "Content-Length: 1\r\n" "Transfer-Encoding: chunked\r\n\r\n"; ck_assert_int_eq(send(fd, ambiguousRequest, sizeof(ambiguousRequest) - 1, 0), (ssize_t)(sizeof(ambiguousRequest) - 1)); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 4, 200); ck_assert_uint_eq(ctx.requestCount, 4); ck_assert_uint_eq(ctx.acceptedConnectionCount, 4); close(fd); /* Malformed chunk framing is rejected by the vendored parser. */ fd = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); ck_assert_int_eq(connect(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)), 0); const char malformedChunk[] = "POST /malformed HTTP/1.1\r\n" "Host: 127.0.0.1\r\n" "Transfer-Encoding: chunked\r\n\r\n" "z\r\n"; ck_assert_int_eq(send(fd, malformedChunk, sizeof(malformedChunk) - 1, 0), (ssize_t)(sizeof(malformedChunk) - 1)); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 5, 200); ck_assert_uint_eq(ctx.requestCount, 4); ck_assert_uint_eq(ctx.acceptedConnectionCount, 5); close(fd); /* A second request must not enter while the first request on the same * accepted connection is still waiting for its response. */ ctx.mode = HTTP_TEST_NO_RESPONSE; fd = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); ck_assert_int_eq(connect(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)), 0); ck_assert_int_eq(send(fd, firstRequest, sizeof(firstRequest) - 1, 0), (ssize_t)(sizeof(firstRequest) - 1)); runUntilCount(eventLoop, &ctx.requestCount, 5, 200); ck_assert_uint_eq(ctx.acceptedConnectionCount, 6); ck_assert_int_eq(send(fd, secondRequest, sizeof(secondRequest) - 1, 0), (ssize_t)(sizeof(secondRequest) - 1)); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 6, 200); ck_assert_uint_eq(ctx.requestCount, 5); close(fd); /* The same invariant applies when two requests arrive in one read. The * parser delivers the first request only and rejects the pipelined tail. */ ctx.mode = HTTP_TEST_FIXED_RESPONSE; fd = socket(AF_INET, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); ck_assert_int_eq(connect(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)), 0); const char pipelinedRequests[] = "GET /pipeline-first HTTP/1.1\r\n" "Host: 127.0.0.1\r\n" "Connection: keep-alive\r\n\r\n" "GET /pipeline-second HTTP/1.1\r\n" "Host: 127.0.0.1\r\n" "Connection: close\r\n\r\n"; ck_assert_int_eq(send(fd, pipelinedRequests, sizeof(pipelinedRequests) - 1, 0), (ssize_t)(sizeof(pipelinedRequests) - 1)); runUntilCount(eventLoop, &ctx.acceptedClosingCount, 7, 200); ck_assert_uint_eq(ctx.requestCount, 6); ck_assert_uint_eq(ctx.acceptedConnectionCount, 7); close(fd); ck_assert_uint_eq(cm->closeConnection(cm, ctx.listenerId), UA_STATUSCODE_GOOD); stopEventLoop(eventLoop); #endif } END_TEST int main(void) { Suite *s = suite_create("Test HTTP EventLoop"); TCase *tc = tcase_create("test cases"); tcase_add_checked_fixture(tc, setup, teardown); tcase_add_test(tc, sendGetRequest); tcase_add_test(tc, sendPostRequest); tcase_add_test(tc, serverBinaryRoundtrip); suite_add_tcase(s, tc); TCase *largeBodies = tcase_create("large-and-empty-bodies"); tcase_add_test(largeBodies, serverLargeAndEmptyBodies); suite_add_tcase(s, largeBodies); TCase *lifecycle = tcase_create("request-lifecycle"); tcase_add_test(lifecycle, serverRequestLifecycle); tcase_add_test(lifecycle, serverBrokenCarrierIsolation); tcase_add_test(lifecycle, clientRequestFailureIsolation); tcase_add_test(lifecycle, clientSynchronousConnectionFailure); tcase_add_test(lifecycle, clientRequestTimeoutIsolation); tcase_add_test(lifecycle, serverResponseFailureIsolation); tcase_add_test(lifecycle, serverListenerCloseWithIdleCarrier); tcase_add_test(lifecycle, serverListenerCloseFromRequestCallback); suite_add_tcase(s, lifecycle); TCase *timeouts = tcase_create("timeouts-and-listener-close"); tcase_add_test(timeouts, serverTimeoutAndListenerClose); tcase_add_test(timeouts, serverReceiveAndKeepAliveTimeouts); tcase_add_test(timeouts, serverConnectionCapAndRequestDeadline); suite_add_tcase(s, timeouts); TCase *tls = tcase_create("mutual-tls"); tcase_add_test(tls, clientServerMutualTls); suite_add_tcase(s, tls); TCase *limits = tcase_create("message-size-limits"); tcase_add_test(limits, messageSizeLimits); suite_add_tcase(s, limits); #ifdef UA_ENABLE_HTTP_COMPRESSION TCase *compression = tcase_create("content-encoding"); tcase_add_test(compression, serverContentEncodingAndExpansionLimit); tcase_add_test(compression, clientCompressedResponse); suite_add_tcase(s, compression); #endif TCase *validation = tcase_create("parameter-validation"); tcase_add_test(validation, parameterValidation); suite_add_tcase(s, validation); TCase *handles = tcase_create("client-request-handles"); tcase_add_test(handles, clientRequestHandles); tcase_add_test(handles, clientRejectsDuplicateContentEncoding); suite_add_tcase(s, handles); TCase *http11 = tcase_create("chunked-and-keep-alive"); tcase_add_test(http11, chunkedRequestAndKeepAlive); suite_add_tcase(s, http11); SRunner *sr = srunner_create(s); srunner_set_fork_status(sr, CK_NOFORK); srunner_run_all (sr, CK_NORMAL); int number_failed = srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; }