High Performance OPC UA Server SDK  1.3.1.248
Minimal Server

This is an example of a minimal OPC UA server with very small memory footprint.

Files used in this lesson:

Overview

The server has a minimal provider with only one own folder and two variables. The server_main.c is similar to the one in Server Getting Started, but it uses a minimal built-in configuration instead of loading it from a configuration file. How this works is shown below in Server Configuration.

The size of the server executable depends mainly on the cmake options used for building the application. So this document will show which options can be disabled and how they affect your code and the resulting application.

The RAM usage at runtime mainly depends on the values set in g_appconfig which is described in the next section.

Server Configuration

The runtime configuration for the SDK is loaded from the g_appconfig variable. The content of this variable is usually loaded from the settings file, but this example sets the members of the variable manually in appconfig.c.

The configuration is targeted on the Micro Embedded Device Server Profile. However, it can be further reduced for even lower memory usage. These are the most important settings regarding memory usage:

ipc.ipc_size
The amount of memory allocated at start-up; most of the memory at runtime is allocated from here. Part of it is used for object pools, the remainder is used for the heap to satisfy dynamic memory allocations. So reducing this value will reduce memory consumption, but also increase the chance of out-of-memory situations.
net.buffer_size
The maximum size of one chunk of a serialized UA message. This is already configured for the minimum size allowed by the OPC UA Specification.
net.num_buffers
The total number of buffers available for transferring UA messages. These buffers are allocated at server start-up and consume quite much of the memory. Reducing them will increase the chance that a message cannot be sent or received.
subscription
The subscription mechanism is quite complex. You can either further decrease the resources used by it, or disable it completely using cmake.

Quick Building Instructions for the Impatient

You you simply want to build a minimal server to see how big it gets you can use one of our default configurations.

The following example works on Linux. You can do the same on Windows using Visual Studio, but then you can somit the 'stripping' step.

$> ./build.sh -m32 -T -p nano -b MinSizeRel clean
$> ls -lh dist32/bin/uaserver_minimal
-rwxr-xr-x 1 john users 293K Sep 29 13:27 dist32/bin/uaserver_minimal
$> strip dist32/bin/uaserver_minimal
$> ls -lh dist32/bin/uaserver_minimal
-rwxr-xr-x 1 john users 235K Sep 29 13:27 dist32/bin/uaserver_minimal

Options explained:

Options Description
-m32 Build for 32bit target on 64bit systems. Don't use this when building on 32 bit systems.
-T Disable unit tests to save time.
-p nano Select 'nano' profile. You can also test this with 'micro' to see the difference.
-b MinSizeRel Configures the compiler to build in release mode and optimize for size.
clean Performs a clean build to clean any build artificats from previous builds.

On Linux executables contains symbol information also in release builds. This is usefull for analyzin coredumps, because you get a meaningful callstack. You can strip these symbols to get smaller executables.

Dispensable Options

There are a lot of cmake options that effect the code size of the resulting application (it also effects memory usage as disabled code will not consume memory). Some of the options are for diagnostic reasons only, for the client side, or rarely used/unimplemented services. It is safe to disable these options unless there is a definite reason to enable them. Most of these options are disabled by default anyway:

Setting Value
BUILD_WITH_IPC_DIAG OFF
CMAKE_BUILD_TYPE MinSizeRel
ENABLE_CLIENT_FUNCTIONALITY OFF
ENABLE_DIAGNOSTICS OFF
ENABLE_SERVICE_ADDNODES OFF
ENABLE_SERVICE_ADDREFERENCES OFF
ENABLE_SERVICE_CANCEL OFF
ENABLE_SERVICE_DELETENODES OFF
ENABLE_SERVICE_DELETEREFERENCES OFF
ENABLE_SERVICE_FINDSERVERSONNETWORK OFF
ENABLE_SERVICE_HISTORYREAD OFF
ENABLE_SERVICE_HISTORYUPDATE OFF
ENABLE_SERVICE_QUERYFIRST OFF
ENABLE_SERVICE_QUERYNEXT OFF
ENABLE_SERVICE_REGISTERSERVER OFF
ENABLE_SERVICE_REGISTERSERVER2 OFF
LIBUATCP_SUPPORT_CLIENT OFF
MEMORY_ENABLE_STAT OFF
MEMORY_ENABLE_TRACE OFF
MEMORY_ENABLE_TRACK_USED_MEM OFF

Disable Crypto

The SDK uses a third-party crypto library, like Openssl or mbedTLS, to perform cryptographic operations. With the following cmake option you will no longer depend on such a library:

Setting Value
BUILD_LIBUACRYPTO OFF

This reduces code for the server as you get rid of the additional library and the SDK code to use the library. It also reduces memory usage as no certificates or private keys need to be loaded or saved in memory. It also gives you more control over the memory as these libraries may use malloc() directly to allocate dynamic memory outside of the ipc heap.

The disadvantage is that your server will not be able to perform any cryptographic operations, so it cannot sign or encrypt any messages. It is not possible to restrict access to the server using certificates. It is still possible to use username authentication tokens, but the password will be transmitted in cleartext and anybody sniffing on the wire can authenticate at the server. So only disable crypto in a network if you have full control over any server and client!

Disable File System Support

To reduce code size it is possible to completely disable file system support for the SDK. This is also useful for devices that have no file system at all. Use the following cmake options:

Setting Value
SUPPORT_FILE_IO OFF
UAPROVIDER_SERVER_USE_STATIC_ADDR ON

Crypto

The server certificate and private key are usually loaded from files. In this example, the files were written as byte array into cert_data.c. At server start-up, the certificate and private key are then loaded manually in server_main.c

#if defined(HAVE_PKI) && !defined(SUPPORT_FILE_IO)
/* use hardcoded certificate when filesystem is not available */
ua_bytestring_attach(&g_sechan_certs[0].own_cert, (char*) g_cert1024_der, (int) sizeof(g_cert1024_der));
ret = crypto_key_from_pem(g_private1024_key, sizeof(g_private1024_key), 0, 0, crypto_key_type_rsa_private, &g_sechan_certs[0].own_priv_key);
if (ret != 0) {
TRACE_ERROR(TRACE_FAC_APPLICATION, "server_main_init: ERROR Could not load private key\n");
goto out_pki;
}
g_appconfig.endpoint.sechan_certs = g_sechan_certs;
g_appconfig.endpoint.num_sechan_certs = countof(g_sechan_certs);
#endif
Note
If you load your certificate and private key this way, do not use the provided cert_data.c file, but create your own with your own private key and own certificate!

Client certificates and CA certificates are usually stored in a PKI store folder. As this is no longer possible, the PKI_STORE_BACKEND is automatically set to the in-memory backend. However, this is not yet completely finished, and it is not clear how to trust client certificates.

Crypto is required for the Embedded UA Server Profile and above.

User Management

Users and groups also cannot be loaded from file, so they are added manually in provider_minimal.c

static int provider_minimal_users(void)
{
int ret = 0;
#if UA_AUTHORIZATION_BACKEND == UA_AUTHORIZATION_BACKEND_INODE
ret = ua_inode_add_user("anonymous", 0);
if (ret != 0) return ret;
ret = ua_inode_add_user("root", 1);
if (ret != 0) return ret;
ret = ua_inode_add_user("joe", 2);
if (ret != 0) return ret;
ret = ua_inode_add_user("john", 3);
if (ret != 0) return ret;
ret = ua_inode_add_user("sue", 4);
if (ret != 0) return ret;
#endif
#if UA_AUTHENTICATION_BACKEND == UA_AUTHENTICATION_BACKEND_INTERNAL
/* add passwords for the users ("anonymous" has no password) */
ret = ua_authentication_add_user(UA_AUTH_HASH_NONE, "root", NULL, "secret");
if (ret != 0) return ret;
ret = ua_authentication_add_user(UA_AUTH_HASH_NONE, "joe", NULL, "god");
if (ret != 0) return ret;
ret = ua_authentication_add_user(UA_AUTH_HASH_NONE, "john", NULL, "master");
if (ret != 0) return ret;
ret = ua_authentication_add_user(UA_AUTH_HASH_NONE, "sue", NULL, "curly");
if (ret != 0) return ret;
#endif
return ret;
}

It is also possible to disable authentication and/or authorization completely with the following cmake options:

Setting Value
UA_AUTHENTICATION_BACKEND null
UA_AUTHORIZATION_BACKEND null

Address Space

The server cannot load the namespaces from a file. By enabling UAPROVIDER_SERVER_USE_STATIC_ADDR the serverprovider uses a static address space that is compiled into the executable. The minimal provider creates its nodes at provider initialization in provider_minimal.c.

Disable Services

The SDK allows to disable further UA services to reduce code size. However, except of Subscription, Write, and Call, these services are required for all server profiles. Services can be disabled by disabling the according ENABLE_SERVICE_<NAME> cmake option.

Subscription

Disabling the Subscription service reduces the code size quite much and, depending on the configuration, also saves a lot of memory. This service can be omitted for the Nano Embedded Device Server Profile. If this profile is sufficient for the server, and clients do not rely on this service, it is save to disable. In this case, any subscription configuration is ignored.

Write

The Write service is optional for all server profiles. If your server does not require or support to write values, this service can be disabled.

Call

The Call service is optional for all server profiles. If you do not have any methods implemented, this service can be disabled.

FindServers and GetEndpoints

The FindServers and GetEndpoints services are required by many clients (e.g. UaExpert) to connect to the server. If your client allows to configure all information retrieved with these services, they can be disabled.

TranslateBrowsePathsToNodeIds

This service is particularly useful for servers with many complex object nodes to retrieve child nodes. If the client does not need it, this service can be disabled.

BrowseNext

The BrowseNext service is required for browsing nodes that have more references than the encoder.max_array_length configuration setting. The implementation of this service in the SDK is rather efficient and allows an infinite number of continuation points without allocating memory. Furthermore, the code size reduction is small, so it is not recommended to disable this service.

RegisterNodes

The RegisterNodes service allows a performance optimization when using the Read service. The implementation of this service in the SDK is rather efficient and allows an infinite number of registered nodes without allocating memory. If this service is disabled, the related UnregisterNodes service must also be disabled. The BrowseNext service depends on this service. It is not recommended to disable the RegisterNodes service.