mirror of
https://github.com/open62541/open62541.git
synced 2026-10-18 05:36:49 +08:00
366 lines
13 KiB
C
366 lines
13 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/. */
|
|
|
|
#include "testing_networklayers.h"
|
|
|
|
#include <open62541/plugin/log_stdout.h>
|
|
|
|
#define TEST_CM_MAX_CONNS 16
|
|
|
|
typedef struct {
|
|
uintptr_t connId;
|
|
void *application;
|
|
void *context;
|
|
UA_ConnectionManager_connectionCallback callback;
|
|
size_t rxCount;
|
|
size_t txCount;
|
|
} TestCMConnection;
|
|
|
|
/* TestCM embeds UA_ConnectionManager as its first member so that a
|
|
* UA_ConnectionManager* can be cast directly to TestCM* and back. */
|
|
typedef struct TestCM {
|
|
UA_ConnectionManager cm;
|
|
void *context; /* opaque user context, get/set via public API */
|
|
UA_ByteString lastSent; /* last buffer stolen by sendWithConnection */
|
|
UA_Boolean logEvents;
|
|
UA_StatusCode (*sendWithConnectionOverload)(UA_ConnectionManager *cm,
|
|
uintptr_t connectionId,
|
|
const UA_KeyValueMap *params,
|
|
UA_ByteString *buf);
|
|
TestCMConnection conns[TEST_CM_MAX_CONNS];
|
|
size_t connCount;
|
|
uintptr_t nextConnId;
|
|
} TestCM;
|
|
|
|
static const char *
|
|
testCM_connectionStateName(UA_ConnectionState state) {
|
|
switch(state) {
|
|
case UA_CONNECTIONSTATE_CLOSED:
|
|
return "closed";
|
|
case UA_CONNECTIONSTATE_OPENING:
|
|
return "opening";
|
|
case UA_CONNECTIONSTATE_ESTABLISHED:
|
|
return "established";
|
|
case UA_CONNECTIONSTATE_CLOSING:
|
|
return "closing";
|
|
case UA_CONNECTIONSTATE_BLOCKING:
|
|
return "blocking";
|
|
case UA_CONNECTIONSTATE_REOPENING:
|
|
return "reopening";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static void
|
|
testCM_logEvent(TestCM *tcm, const char *eventName, uintptr_t connectionId,
|
|
UA_ConnectionState state, const UA_ByteString *msg) {
|
|
if(!tcm->logEvents)
|
|
return;
|
|
|
|
UA_String b64Payload = UA_STRING_NULL;
|
|
UA_ByteString_toBase64(msg, &b64Payload);
|
|
|
|
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
|
|
"[Connection %u] protocol=%S event=%s state=%s payload=%S",
|
|
(unsigned)connectionId, tcm->cm.protocol, eventName,
|
|
testCM_connectionStateName(state), b64Payload);
|
|
|
|
UA_String_clear(&b64Payload);
|
|
}
|
|
|
|
void
|
|
TestConnectionManager_setContext(UA_ConnectionManager *cm, void *context) {
|
|
((TestCM *)(void *)cm)->context = context;
|
|
}
|
|
|
|
void *
|
|
TestConnectionManager_getContext(UA_ConnectionManager *cm) {
|
|
return ((TestCM *)(void *)cm)->context;
|
|
}
|
|
|
|
const UA_ByteString *
|
|
TestConnectionManager_getLastSent(UA_ConnectionManager *cm) {
|
|
return &((TestCM *)(void *)cm)->lastSent;
|
|
}
|
|
|
|
UA_StatusCode
|
|
TestConnectionManager_createConnection(UA_ConnectionManager *cm,
|
|
void *application,
|
|
void *context,
|
|
UA_ConnectionManager_connectionCallback connectionCallback,
|
|
uintptr_t *outConnectionId) {
|
|
TestCM *tcm = (TestCM *)(void *)cm;
|
|
if(!tcm || !connectionCallback || !outConnectionId)
|
|
return UA_STATUSCODE_BADINVALIDARGUMENT;
|
|
|
|
size_t slot = TEST_CM_MAX_CONNS;
|
|
for(size_t i = 0; i < TEST_CM_MAX_CONNS; i++) {
|
|
if(!tcm->conns[i].callback) {
|
|
slot = i;
|
|
break;
|
|
}
|
|
}
|
|
if(slot == TEST_CM_MAX_CONNS)
|
|
return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
|
uintptr_t connId = tcm->nextConnId++;
|
|
tcm->conns[slot].connId = connId;
|
|
tcm->conns[slot].application = application;
|
|
tcm->conns[slot].context = context;
|
|
tcm->conns[slot].callback = connectionCallback;
|
|
tcm->connCount++;
|
|
*outConnectionId = connId;
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
UA_StatusCode
|
|
TestConnectionManager_removeConnection(UA_ConnectionManager *cm,
|
|
uintptr_t connectionId) {
|
|
TestCM *tcm = (TestCM *)(void *)cm;
|
|
if(!tcm)
|
|
return UA_STATUSCODE_BADINVALIDARGUMENT;
|
|
|
|
for(size_t i = 0; i < TEST_CM_MAX_CONNS; i++) {
|
|
if(!tcm->conns[i].callback || tcm->conns[i].connId != connectionId)
|
|
continue;
|
|
tcm->conns[i].callback = NULL;
|
|
tcm->conns[i].context = NULL;
|
|
tcm->conns[i].application = NULL;
|
|
tcm->connCount--;
|
|
if(tcm->connCount == 0 &&
|
|
tcm->cm.eventSource.state == UA_EVENTSOURCESTATE_STOPPING)
|
|
tcm->cm.eventSource.state = UA_EVENTSOURCESTATE_STOPPED;
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
return UA_STATUSCODE_BADNOTFOUND;
|
|
}
|
|
|
|
UA_StatusCode
|
|
TestConnectionManager_inject(UA_ConnectionManager *cm,
|
|
uintptr_t connectionId,
|
|
UA_ConnectionState state,
|
|
const UA_KeyValueMap *params,
|
|
const UA_ByteString *msg) {
|
|
TestCM *tcm = (TestCM *)(void *)cm;
|
|
if(!tcm)
|
|
return UA_STATUSCODE_BADINVALIDARGUMENT;
|
|
|
|
UA_KeyValueMap emptyKvm = UA_KEYVALUEMAP_NULL;
|
|
UA_ByteString emptyMsg = UA_BYTESTRING_NULL;
|
|
const UA_KeyValueMap *injectParams = params ? params : &emptyKvm;
|
|
UA_ByteString injectMsg = msg ? *msg : emptyMsg;
|
|
const char *eventName = (injectMsg.length > 0) ? "receive-injected" : "inject";
|
|
|
|
for(size_t i = 0; i < TEST_CM_MAX_CONNS; i++) {
|
|
if(!tcm->conns[i].callback || tcm->conns[i].connId != connectionId)
|
|
continue;
|
|
|
|
testCM_logEvent(tcm, eventName, connectionId, state, &injectMsg);
|
|
|
|
tcm->conns[i].callback(cm, tcm->conns[i].connId,
|
|
tcm->conns[i].application,
|
|
&tcm->conns[i].context,
|
|
state, injectParams, injectMsg);
|
|
|
|
if(injectMsg.length > 0)
|
|
tcm->conns[i].rxCount++;
|
|
|
|
if(state == UA_CONNECTIONSTATE_CLOSING ||
|
|
state == UA_CONNECTIONSTATE_CLOSED) {
|
|
tcm->conns[i].callback = NULL;
|
|
tcm->conns[i].context = NULL;
|
|
tcm->conns[i].application = NULL;
|
|
tcm->connCount--;
|
|
}
|
|
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
return UA_STATUSCODE_BADNOTFOUND;
|
|
}
|
|
|
|
static UA_StatusCode
|
|
testCM_eventSourceStart(UA_EventSource *es) {
|
|
UA_ConnectionManager *cm = (UA_ConnectionManager *)es;
|
|
TestCM *tcm = (TestCM *)(void *)cm;
|
|
UA_assert(tcm);
|
|
|
|
const UA_Boolean *enabled = (const UA_Boolean *)
|
|
UA_KeyValueMap_getScalar(&cm->eventSource.params,
|
|
UA_QUALIFIEDNAME(0, "log-events"),
|
|
&UA_TYPES[UA_TYPES_BOOLEAN]);
|
|
tcm->logEvents = enabled ? *enabled : false;
|
|
|
|
es->state = UA_EVENTSOURCESTATE_STARTED;
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
static void
|
|
testCM_eventSourceStop(UA_EventSource *es) {
|
|
UA_ConnectionManager *cm = (UA_ConnectionManager *)es;
|
|
TestCM *tcm = (TestCM *)(void *)cm;
|
|
UA_assert(tcm);
|
|
|
|
if(tcm->connCount == 0) {
|
|
es->state = UA_EVENTSOURCESTATE_STOPPED;
|
|
return;
|
|
}
|
|
|
|
es->state = UA_EVENTSOURCESTATE_STOPPING;
|
|
|
|
UA_KeyValueMap emptyKvm = UA_KEYVALUEMAP_NULL;
|
|
UA_ByteString emptyMsg = UA_BYTESTRING_NULL;
|
|
for(size_t i = 0; i < TEST_CM_MAX_CONNS; i++) {
|
|
if(!tcm->conns[i].callback)
|
|
continue;
|
|
testCM_logEvent(tcm, "close", tcm->conns[i].connId,
|
|
UA_CONNECTIONSTATE_CLOSING, &emptyMsg);
|
|
tcm->conns[i].callback(cm, tcm->conns[i].connId,
|
|
tcm->conns[i].application,
|
|
&tcm->conns[i].context,
|
|
UA_CONNECTIONSTATE_CLOSING,
|
|
&emptyKvm, emptyMsg);
|
|
/* Remove the connection entry so connCount drops to 0 and the CM can
|
|
* transition to STOPPED. The callback (e.g. DiscoveryManager) must NOT
|
|
* call closeConnection in response to a CLOSING notification — that is
|
|
* the CM's responsibility during shutdown. */
|
|
tcm->conns[i].callback = NULL;
|
|
tcm->conns[i].context = NULL;
|
|
tcm->conns[i].application = NULL;
|
|
tcm->connCount--;
|
|
}
|
|
es->state = UA_EVENTSOURCESTATE_STOPPED;
|
|
}
|
|
|
|
static UA_StatusCode
|
|
testCM_eventSourceFree(UA_EventSource *es) {
|
|
UA_ConnectionManager *cm = (UA_ConnectionManager *)es;
|
|
TestCM *tcm = (TestCM *)(void *)cm;
|
|
UA_assert(tcm);
|
|
|
|
testCM_eventSourceStop(es);
|
|
UA_KeyValueMap_clear(&cm->eventSource.params);
|
|
UA_ByteString_clear(&tcm->lastSent);
|
|
UA_free(tcm);
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
static UA_StatusCode
|
|
testCM_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId,
|
|
const UA_KeyValueMap *params, UA_ByteString *buf) {
|
|
TestCM *tcm = (TestCM *)(void *)cm;
|
|
testCM_logEvent(tcm, "send", connectionId,
|
|
UA_CONNECTIONSTATE_ESTABLISHED, buf);
|
|
/* Update tx counter if the connection is tracked (opportunistic). */
|
|
for(size_t i = 0; i < TEST_CM_MAX_CONNS; i++) {
|
|
if(tcm->conns[i].callback && tcm->conns[i].connId == connectionId) {
|
|
tcm->conns[i].txCount++;
|
|
break;
|
|
}
|
|
}
|
|
if(tcm->sendWithConnectionOverload)
|
|
return tcm->sendWithConnectionOverload(cm, connectionId, params, buf);
|
|
/* Default: steal the buffer into lastSent */
|
|
UA_ByteString_clear(&tcm->lastSent);
|
|
tcm->lastSent = *buf;
|
|
UA_ByteString_init(buf);
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
static UA_StatusCode
|
|
testCM_openConnection(UA_ConnectionManager *cm, const UA_KeyValueMap *params,
|
|
void *application, void *context,
|
|
UA_ConnectionManager_connectionCallback cb) {
|
|
TestCM *tcm = (TestCM *)(void *)cm;
|
|
(void)params;
|
|
uintptr_t connId;
|
|
UA_StatusCode ret = TestConnectionManager_createConnection(cm, application,
|
|
context, cb, &connId);
|
|
if(ret != UA_STATUSCODE_GOOD)
|
|
return ret;
|
|
testCM_logEvent(tcm, "open", connId, UA_CONNECTIONSTATE_OPENING, NULL);
|
|
UA_KeyValueMap emptyKvm = UA_KEYVALUEMAP_NULL;
|
|
UA_ByteString emptyMsg = UA_BYTESTRING_NULL;
|
|
return TestConnectionManager_inject(cm, connId, UA_CONNECTIONSTATE_ESTABLISHED,
|
|
&emptyKvm, &emptyMsg);
|
|
}
|
|
|
|
static UA_StatusCode
|
|
testCM_closeConnection(UA_ConnectionManager *cm, uintptr_t connectionId) {
|
|
TestCM *tcm = (TestCM *)(void *)cm;
|
|
UA_KeyValueMap emptyKvm = UA_KEYVALUEMAP_NULL;
|
|
UA_ByteString emptyMsg = UA_BYTESTRING_NULL;
|
|
testCM_logEvent(tcm, "close", connectionId, UA_CONNECTIONSTATE_CLOSING, &emptyMsg);
|
|
TestConnectionManager_inject(cm, connectionId, UA_CONNECTIONSTATE_CLOSING,
|
|
&emptyKvm, &emptyMsg);
|
|
return TestConnectionManager_removeConnection(cm, connectionId);
|
|
}
|
|
|
|
UA_StatusCode
|
|
TestConnectionManager_getCounters(UA_ConnectionManager *cm, uintptr_t connectionId,
|
|
size_t *rxCount, size_t *txCount) {
|
|
TestCM *tcm = (TestCM *)(void *)cm;
|
|
for(size_t i = 0; i < TEST_CM_MAX_CONNS; i++) {
|
|
if(!tcm->conns[i].callback || tcm->conns[i].connId != connectionId)
|
|
continue;
|
|
if(rxCount) *rxCount = tcm->conns[i].rxCount;
|
|
if(txCount) *txCount = tcm->conns[i].txCount;
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
return UA_STATUSCODE_BADNOTFOUND;
|
|
}
|
|
|
|
static UA_StatusCode
|
|
testCM_allocNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId,
|
|
UA_ByteString *buf, size_t bufSize) {
|
|
(void)cm;
|
|
(void)connectionId;
|
|
return UA_ByteString_allocBuffer(buf, bufSize);
|
|
}
|
|
|
|
static void
|
|
testCM_freeNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId,
|
|
UA_ByteString *buf) {
|
|
(void)cm;
|
|
(void)connectionId;
|
|
UA_ByteString_clear(buf);
|
|
}
|
|
|
|
UA_ConnectionManager *
|
|
TestConnectionManager_new(const char *protocol,
|
|
const TestConnectionManager_CallbackOverloads *overloads) {
|
|
if(!protocol || !protocol[0])
|
|
return NULL;
|
|
|
|
TestCM *tcm = (TestCM*)UA_calloc(1, sizeof(TestCM));
|
|
if(!tcm)
|
|
return NULL;
|
|
|
|
tcm->nextConnId = 100;
|
|
tcm->logEvents = false;
|
|
|
|
UA_ConnectionManager *cm = &tcm->cm;
|
|
cm->eventSource.next = NULL;
|
|
cm->eventSource.eventSourceType = UA_EVENTSOURCETYPE_CONNECTIONMANAGER;
|
|
cm->eventSource.name = UA_STRING((char*)(uintptr_t)"test-cm");
|
|
cm->eventSource.eventLoop = NULL;
|
|
cm->eventSource.params = UA_KEYVALUEMAP_NULL;
|
|
cm->eventSource.state = UA_EVENTSOURCESTATE_FRESH;
|
|
cm->eventSource.start = testCM_eventSourceStart;
|
|
cm->eventSource.stop = testCM_eventSourceStop;
|
|
cm->eventSource.free = testCM_eventSourceFree;
|
|
cm->protocol = UA_STRING((char*)(uintptr_t)protocol);
|
|
cm->openConnection = (overloads && overloads->openConnection) ?
|
|
overloads->openConnection : testCM_openConnection;
|
|
cm->sendWithConnection = testCM_sendWithConnection;
|
|
cm->closeConnection = (overloads && overloads->closeConnection) ?
|
|
overloads->closeConnection : testCM_closeConnection;
|
|
tcm->sendWithConnectionOverload = overloads ? overloads->sendWithConnection : NULL;
|
|
cm->allocNetworkBuffer = testCM_allocNetworkBuffer;
|
|
cm->freeNetworkBuffer = testCM_freeNetworkBuffer;
|
|
|
|
return cm;
|
|
}
|