mirror of
https://github.com/open62541/open62541.git
synced 2026-10-18 13:42:32 +08:00
303 lines
11 KiB
C
303 lines
11 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"
|
|
|
|
#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_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;
|
|
|
|
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;
|
|
|
|
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(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) {
|
|
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;
|
|
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_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;
|
|
/* 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) {
|
|
(void)params;
|
|
uintptr_t connId;
|
|
UA_StatusCode ret = TestConnectionManager_createConnection(cm, application,
|
|
context, cb, &connId);
|
|
if(ret != UA_STATUSCODE_GOOD)
|
|
return ret;
|
|
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) {
|
|
UA_KeyValueMap emptyKvm = UA_KEYVALUEMAP_NULL;
|
|
UA_ByteString emptyMsg = UA_BYTESTRING_NULL;
|
|
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;
|
|
|
|
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;
|
|
}
|