opc.tcp · UA Binary
OPC UA Client
Put an OPC UA client into your application. The SDK handles secure channels, sessions and certificate validation.
What it does
- opc.tcp transport and UA Binary encoding.
- Basic256Sha256, Aes128_Sha256_RsaOaep and Aes256_Sha256_RsaPss with Sign and SignAndEncrypt; None only when you allow it.
- Anonymous and user name/password identities; trusted certificates named explicitly.
- Endpoint discovery, Browse, Read, Write, Call, Subscriptions and MonitoredItems.
- A C ABI, a C++ RAII header, a Qt client worker, a .NET assembly and an MFC client worker.
Example
Each binding shows code from a real example file in the examples folder of the PDComm SDK. The source file and line range appear above the code.
OPC UA C client
memset(&options, 0, sizeof(options));
options.struct_size = (uint32_t)sizeof(options);
options.version = 1;
status = pdcomm_opcua_client_options_init(&options);
if (status != PDCOMM_OPCUA_OK) { print_error("options", status); goto done; }
options.timeout_ms = timeout;
options.policy = policy;
options.mode = mode;
options.flags = policy == PDCOMM_OPCUA_POLICY_NONE ? PDCOMM_OPCUA_ALLOW_NONE : 0;
options.certificate_der.data = certificate;
options.certificate_der.size = certificate_size;
options.private_key_pem.data = key;
options.private_key_pem.size = key_size;
options.trusted_server_der.data = trust;
options.trusted_server_der.size = trust_size;
status = pdcomm_opcua_client_connect((const uint8_t *)argv[1], strlen(argv[1]), &options, &client);
/* The SDK copied the identity; wipe the private key now. */
example_wipe(key, key_size);
key = NULL;
if (status != PDCOMM_OPCUA_OK) { print_error("connect", status); goto done; }
status = pdcomm_opcua_client_activate_anonymous(client);
if (status != PDCOMM_OPCUA_OK) { print_error("activate", status); goto done; }
printf("Session activated (anonymous)\n");From the OPC UA C client example: open a SignAndEncrypt secure channel with the client certificate, private key and trusted server certificate, then activate an anonymous session.
OPC UA C++ client
auto config = ua::options();
const auto certificate = load(argv[2]);
auto key = load(argv[3]);
const auto trust = load(argv[4]);
config.policy = policy;
config.mode = mode;
config.timeout_ms = 5000;
config.certificate_der = ua::bytes(certificate);
config.private_key_pem = ua::bytes(key);
config.trusted_server_der = ua::bytes(trust);
auto client = ua::client::connect(argv[1], config);
std::fill(key.begin(), key.end(), static_cast<unsigned char>(0)); // The SDK copied the identity.
client.activate_anonymous();
std::cout << "Session activated (anonymous)" << std::endl;
int result = 0;
uint8_t request[256];
uint32_t service = 0, status = 0;
if (write) {
const auto size = ua_encode_write_int32(request, sizeof(request), 2, node, write_value);
const auto body = exchange(client, request, size, std::chrono::seconds(5));
if (ua_decode_write(body.data(), body.size(), &service, &status))
throw std::runtime_error("Malformed WriteResponse");
std::cout << "Write ns=2;i=" << node << " value=" << write_value << " service=" << hex(service)
<< " status=" << hex(status) << std::endl;
if ((service | status) & 0x80000000u) result = 1;
}
const auto size = ua_encode_read(request, sizeof(request), 2, node);
const auto body = exchange(client, request, size, std::chrono::seconds(5));
ua_value value;
if (ua_decode_read(body.data(), body.size(), &service, &value))
throw std::runtime_error("Malformed ReadResponse");
std::cout << "Read ns=2;i=" << node << " value="
<< (value.has_int32 ? std::to_string(value.int32) : std::string("none"))
<< " service=" << hex(service) << " status=" << hex(value.status) << std::endl;
if ((service | value.status) & 0x80000000u) result = 1;From the C++ example: ua::client::connect opens the secure channel, the session is activated anonymously, then the Int32 node ns=2;i=1001 is optionally written and read. Requests progress while the caller polls.
OPC UA .NET client
byte[] key = Load(a[2]);
Client client;
try
{
client = Client.Connect(a[0], new ClientOptions
{
Policy = policy, Mode = mode, AllowNone = policy == SecurityPolicy.None, TimeoutMs = 5000,
CertificateDer = Load(a[1]), PrivateKeyPem = key, TrustedServerDer = Load(a[3]),
});
}
finally { CryptographicOperations.ZeroMemory(key); } // The SDK copied the identity.
using (client)
{
client.ActivateAnonymous();
Console.WriteLine("Session activated (anonymous)");
int result = 0;
if (write is int value)
{
var (service, status) = UaBinary.DecodeWrite(Exchange(client, UaBinary.Write(node, value)));
Console.WriteLine($"Write ns=2;i={node} value={value} service=0x{service:x8} status=0x{status:x8}");
if (((service | status) & 0x80000000u) != 0) result = 1;
}
var (readService, data) = UaBinary.DecodeRead(Exchange(client, UaBinary.Read(node)));
string shown = data?.Value?.ToString() ?? "none";
uint itemStatus = data?.Status ?? 0;
Console.WriteLine($"Read ns=2;i={node} value={shown} service=0x{readService:x8} status=0x{itemStatus:x8}");
if (((readService | itemStatus) & 0x80000000u) != 0) result = 1;
client.Close();
Console.WriteLine("Session closed");
return result;The client mode of the .NET example: Client.Connect takes the certificate, private key and trusted server certificate, the key copy is wiped, then an anonymous session writes and reads the node.
OPC UA Qt client worker
qt_client_configuration configuration;
configuration.endpoint = arguments[1].toUtf8();
configuration.anonymous = true;
if (!load(arguments[2], configuration.certificate_der) ||
!load(arguments[3], configuration.private_key_pem) ||
!load(arguments[4], configuration.trusted_server_der)) {
std::fputs("Error: cannot read the certificate, private key or trusted server certificate\n", stderr);
return 3;
}
int result = 3;
QObject receiver; // Stable receiver context for every queued connection.
auto worker = std::make_unique<qt_client_worker>(configuration);
// The worker copied the configuration; wipe the caller copy of the key.
configuration.private_key_pem.fill('\0');
configuration.private_key_pem.clear();
QObject::connect(worker.get(), &qt_client_worker::ready, &receiver, [&](quintptr) {
uint8_t request[256];
const auto size = ua_encode_read(request, sizeof(request), 2, node);
const auto status = worker->submit(QByteArray(reinterpret_cast<const char*>(request),
static_cast<qsizetype>(size)), 1);
if (status != PDCOMM_OPCUA_OK) {
std::fprintf(stderr, "Error: submit rejected (SDK status %d)\n", static_cast<int>(status));
worker->request_stop(qt_client_stop::abort);
}
}, Qt::QueuedConnection);
QObject::connect(worker.get(), &qt_client_worker::completed, &receiver,
[&](qt_client_response_ptr response) {
const auto body = response->value->copy();
uint32_t service = 0;
ua_value value;
if (ua_decode_read(body.data(), body.size(), &service, &value)) {
std::fputs("Error: malformed ReadResponse\n", stderr);
} else {
std::printf("Read ns=2;i=%u value=%s service=0x%08x status=0x%08x\n", static_cast<unsigned>(node),
value.has_int32 ? QByteArray::number(value.int32).constData() : "none",
static_cast<unsigned>(service), static_cast<unsigned>(value.status));
result = ((service | value.status) & 0x80000000u) ? 1 : 0;
}
worker->request_stop(qt_client_stop::drain);
}, Qt::QueuedConnection);From the QtCore example: qt_client_worker owns the native client on a worker thread and delivers ready, completed, failed and stopped as queued signals to the main thread.
OPC UA MFC client worker
MfcClientConfiguration configuration;
configuration.endpoint = endpoint;
configuration.anonymous = true;
configuration.policy = PDCOMM_OPCUA_POLICY_NONE;
configuration.mode = PDCOMM_OPCUA_MODE_NONE;
configuration.flags = PDCOMM_OPCUA_ALLOW_NONE;
// Normal drain: valid and invalid requests, owned response after worker destruction.
std::shared_ptr<const response> kept;
std::vector<uint8_t> keptBytes;
{
auto worker = std::make_unique<MfcClientWorker>(configuration, receiver, message);
Require(worker->Submit({}, 1) == PDCOMM_OPCUA_INVALID_INPUT, "Empty request rejected");
Require(worker->Submit(kRead, 0) == PDCOMM_OPCUA_INVALID_INPUT, "Zero correlation rejected");
Require(worker->Submit(std::vector<uint8_t>(MfcClientWorker::MaximumQueuedBytes + 1, 1), 9) == PDCOMM_OPCUA_LIMIT,
"Oversize request rejected");
Require(worker->Submit(kRead, 1) == PDCOMM_OPCUA_OK, "Read admitted");
Require(worker->Submit(kRead, 1) == PDCOMM_OPCUA_BUSY, "Duplicate correlation rejected");
Require(worker->Submit(std::vector<uint8_t>(kRead.begin(), kRead.end() - 1), 2) == PDCOMM_OPCUA_OK,
"Truncated request admitted");
Require(worker->Start() && !worker->Start(), "One-shot worker start");
const DWORD uiThread = ::GetCurrentThreadId();
DWORD ownerThread = 0;
bool stopped = false, completed = false, invalid = false;
Pump(host, *worker, message, [&](const MfcClientEvent &event) {
Require(::GetCurrentThreadId() == uiThread, "Events are handled on the receiver thread");
switch (event.kind) {
case MfcClientEventKind::Ready: ownerThread = event.ownerThread; break;
case MfcClientEventKind::Pending: break;
case MfcClientEventKind::Completed:
Require(event.correlation == 1 && event.metadata.binary_id == 634 && event.metadata.service_status == 0,
"Read response metadata");
kept = event.value;
keptBytes = kept->copy();
completed = true;
break;From the MFC example: MfcClientWorker sends the requests on its own thread and signals results with a window message; the UI thread takes the events with Drain. Build it on Windows with MSVC and MFC.
Related modules
MQTT 5.0 · UADP
MQTT for OPC UA PubSub
MQTT 5.0 as the broker transport of OPC UA PubSub. The OPC UA simulator's GUI, headless program and MCP server publish and subscribe UADP messages through a broker.
Learn moreopc.tcp · UA Binary
OPC UA Server
Put an in-memory OPC UA server into your application. Register variables, expose them on secure endpoints and answer clients while you call poll.
Learn more