ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
opcua_client.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file pc_opcua_client.cpp
6 * @brief OPC UA Binary client - request builders + response parsers (implementation).
7 *
8 * Pure byte-buffer logic reusing the opcua.h codec; no transport, no heap, no stdlib.
9 */
10
12
13#if PC_ENABLE_OPCUA_CLIENT
14
15#include <string.h>
16
17void pc_opcua_client_init(OpcUaClient *c)
18{
19 memset(c, 0, sizeof(*c));
20}
21
22// ---------------------------------------------------------------------------
23// Builder helpers
24// ---------------------------------------------------------------------------
25static size_t cw_patch(UaWriter *w)
26{
27 if (!w->ok)
28 {
29 return 0;
30 }
31 w->o[4] = (uint8_t)w->n;
32 w->o[5] = (uint8_t)(w->n >> 8);
33 w->o[6] = (uint8_t)(w->n >> 16);
34 w->o[7] = (uint8_t)(w->n >> 24);
35 return w->n;
36}
37
38// RequestHeader: the session AuthenticationToken (if available and requested) or a
39// null NodeId, then the fixed trailer. RequestHandle increments per request.
40static void cw_request_header(OpcUaClient *c, UaWriter *w, bool with_session_token)
41{
42 if (with_session_token && c->session_auth_id)
43 {
44 pc_ua_w_nodeid_numeric(w, c->session_auth_ns, c->session_auth_id);
45 }
46 else
47 {
48 pc_ua_w_nodeid_numeric(w, 0, 0);
49 }
50 pc_ua_w_u64(w, 0); // Timestamp (unset)
51 pc_ua_w_u32(w, ++c->request_handle); // RequestHandle
52 pc_ua_w_u32(w, 0); // ReturnDiagnostics
53 pc_ua_w_string(w, nullptr, -1); // AuditEntryId
54 pc_ua_w_u32(w, 0); // TimeoutHint
55 pc_ua_w_nodeid_numeric(w, 0, 0); // AdditionalHeader NodeId (null)
56 pc_ua_w_u8(w, 0x00); // ... ExtensionObject "no body"
57}
58
59// MSG envelope prefix: header (size placeholder) + SymmetricSecurityHeader (TokenId)
60// + SequenceHeader (SequenceNumber, RequestId) + body TypeId.
61static void cw_msg(OpcUaClient *c, UaWriter *w, uint32_t type_id)
62{
63 pc_ua_w_u8(w, 'M');
64 pc_ua_w_u8(w, 'S');
65 pc_ua_w_u8(w, 'G');
66 pc_ua_w_u8(w, 'F');
67 pc_ua_w_u32(w, 0); // size placeholder
68 pc_ua_w_u32(w, c->channel_id); // SecureChannelId
69 pc_ua_w_u32(w, c->token_id); // SymmetricSecurityHeader.TokenId
70 pc_ua_w_u32(w, ++c->seq); // SequenceNumber
71 pc_ua_w_u32(w, ++c->request_id); // RequestId
72 pc_ua_w_nodeid_numeric(w, 0, type_id);
73}
74
75// ---------------------------------------------------------------------------
76// Request builders
77// ---------------------------------------------------------------------------
78size_t pc_opcua_client_hello(const char *endpoint_url, uint8_t *out, size_t cap)
79{
80 UaWriter w = {out, cap, 0, true};
81 pc_ua_w_u8(&w, 'H');
82 pc_ua_w_u8(&w, 'E');
83 pc_ua_w_u8(&w, 'L');
84 pc_ua_w_u8(&w, 'F');
85 pc_ua_w_u32(&w, 0); // size placeholder
86 pc_ua_w_u32(&w, 0); // ProtocolVersion
87 pc_ua_w_u32(&w, PC_OPCUA_BUF); // ReceiveBufferSize
88 pc_ua_w_u32(&w, PC_OPCUA_BUF); // SendBufferSize
89 pc_ua_w_u32(&w, 0); // MaxMessageSize (no limit)
90 pc_ua_w_u32(&w, 0); // MaxChunkCount
91 pc_ua_w_string(&w, endpoint_url, endpoint_url ? (int32_t)strnlen(endpoint_url, w.cap) : -1);
92 return cw_patch(&w);
93}
94
95size_t pc_opcua_client_open(OpcUaClient *c, uint8_t *out, size_t cap)
96{
97 UaWriter w = {out, cap, 0, true};
98 pc_ua_w_u8(&w, 'O');
99 pc_ua_w_u8(&w, 'P');
100 pc_ua_w_u8(&w, 'N');
101 pc_ua_w_u8(&w, 'F');
102 pc_ua_w_u32(&w, 0); // size placeholder
103 pc_ua_w_u32(&w, 0); // SecureChannelId (0 = request a new channel)
104 pc_ua_w_string(&w, OPCUA_POLICY_NONE_URI, (int32_t)(sizeof(OPCUA_POLICY_NONE_URI) - 1));
105 pc_ua_w_string(&w, nullptr, -1); // SenderCertificate
106 pc_ua_w_string(&w, nullptr, -1); // ReceiverCertificateThumbprint
107 pc_ua_w_u32(&w, ++c->seq); // SequenceNumber
108 pc_ua_w_u32(&w, ++c->request_id); // RequestId
109 pc_ua_w_nodeid_numeric(&w, 0, OPCUA_ID_OPEN_REQ);
110 cw_request_header(c, &w, false);
111 pc_ua_w_u32(&w, 0); // ClientProtocolVersion
112 pc_ua_w_u32(&w, 0); // RequestType = Issue
113 pc_ua_w_u32(&w, 1); // MessageSecurityMode = None
114 pc_ua_w_string(&w, nullptr, -1); // ClientNonce
115 pc_ua_w_u32(&w, 3600000); // RequestedLifetime (ms)
116 return cw_patch(&w);
117}
118
119size_t pc_opcua_client_get_endpoints(OpcUaClient *c, const char *endpoint_url, uint8_t *out, size_t cap)
120{
121 UaWriter w = {out, cap, 0, true};
122 cw_msg(c, &w, OPCUA_ID_GET_ENDPOINTS_REQ);
123 cw_request_header(c, &w, false); // GetEndpoints needs no session
124 pc_ua_w_string(&w, endpoint_url, endpoint_url ? (int32_t)strnlen(endpoint_url, w.cap) : -1); // EndpointUrl
125 pc_ua_w_i32(&w, -1); // LocaleIds[] (null)
126 pc_ua_w_i32(&w, -1); // ProfileUris[] (null)
127 return cw_patch(&w);
128}
129
130size_t pc_opcua_client_create_session(OpcUaClient *c, const char *session_name, const char *endpoint_url, uint8_t *out,
131 size_t cap)
132{
133 UaWriter w = {out, cap, 0, true};
134 cw_msg(c, &w, OPCUA_ID_CREATE_SESSION_REQ);
135 cw_request_header(c, &w, false);
136 // ClientDescription (ApplicationDescription).
137 pc_ua_w_string(&w, "urn:det:opcua:client", 20); // ApplicationUri
138 pc_ua_w_string(&w, "urn:det:opcua", 13); // ProductUri
139 pc_ua_w_localizedtext(&w, nullptr, "pc_opcua_client"); // ApplicationName
140 pc_ua_w_u32(&w, 1); // ApplicationType = Client
141 pc_ua_w_string(&w, nullptr, -1); // GatewayServerUri
142 pc_ua_w_string(&w, nullptr, -1); // DiscoveryProfileUri
143 pc_ua_w_i32(&w, -1); // DiscoveryUrls[] (null)
144 pc_ua_w_string(&w, nullptr, -1); // ServerUri
145 pc_ua_w_string(&w, endpoint_url, endpoint_url ? (int32_t)strnlen(endpoint_url, w.cap) : -1); // EndpointUrl
146 pc_ua_w_string(&w, session_name, session_name ? (int32_t)strnlen(session_name, w.cap) : -1); // SessionName
147 pc_ua_w_string(&w, nullptr, -1); // ClientNonce (ByteString)
148 pc_ua_w_string(&w, nullptr, -1); // ClientCertificate
149 pc_ua_w_f64(&w, 1200000.0); // RequestedSessionTimeout
150 pc_ua_w_u32(&w, 0); // MaxResponseMessageSize
151 return cw_patch(&w);
152}
153
154size_t pc_opcua_client_activate_session(OpcUaClient *c, uint8_t *out, size_t cap)
155{
156 UaWriter w = {out, cap, 0, true};
157 cw_msg(c, &w, OPCUA_ID_ACTIVATE_SESSION_REQ);
158 cw_request_header(c, &w, true);
159 pc_ua_w_string(&w, nullptr, -1); // ClientSignature.Algorithm
160 pc_ua_w_string(&w, nullptr, -1); // ClientSignature.Signature
161 pc_ua_w_i32(&w, -1); // ClientSoftwareCertificates[] (null)
162 pc_ua_w_i32(&w, -1); // LocaleIds[] (null)
163 // UserIdentityToken: ExtensionObject(AnonymousIdentityToken i=321) with a String PolicyId body.
164 pc_ua_w_nodeid_numeric(&w, 0, 321); // AnonymousIdentityToken_Encoding_DefaultBinary
165 pc_ua_w_u8(&w, 0x01); // ExtensionObject body = ByteString
166 pc_ua_w_i32(&w, 4 + 9); // body length = encoded String "anonymous" (4-byte len + 9 chars)
167 pc_ua_w_string(&w, "anonymous", 9); // PolicyId
168 pc_ua_w_string(&w, nullptr, -1); // UserTokenSignature.Algorithm
169 pc_ua_w_string(&w, nullptr, -1); // UserTokenSignature.Signature
170 return cw_patch(&w);
171}
172
173size_t pc_opcua_client_read(OpcUaClient *c, const OpcUaReadItem *items, uint32_t n, uint8_t *out, size_t cap)
174{
175 UaWriter w = {out, cap, 0, true};
176 cw_msg(c, &w, OPCUA_ID_READ_REQ);
177 cw_request_header(c, &w, true);
178 pc_ua_w_f64(&w, 0.0); // MaxAge
179 pc_ua_w_u32(&w, 0); // TimestampsToReturn (Source)
180 pc_ua_w_i32(&w, (int32_t)n); // NodesToRead count
181 for (uint32_t i = 0; i < n; i++)
182 {
183 pc_ua_w_nodeid_numeric(&w, items[i].ns, items[i].id);
184 pc_ua_w_u32(&w, items[i].attribute);
185 pc_ua_w_string(&w, nullptr, -1); // IndexRange
186 pc_ua_w_u16(&w, 0); // DataEncoding QualifiedName.ns
187 pc_ua_w_string(&w, nullptr, -1); // QualifiedName.name
188 }
189 return cw_patch(&w);
190}
191
192size_t pc_opcua_client_browse(OpcUaClient *c, uint16_t ns, uint32_t id, uint8_t *out, size_t cap)
193{
194 UaWriter w = {out, cap, 0, true};
195 cw_msg(c, &w, OPCUA_ID_BROWSE_REQ);
196 cw_request_header(c, &w, true);
197 pc_ua_w_nodeid_numeric(&w, 0, 0); // View.ViewId (null)
198 pc_ua_w_u64(&w, 0); // View.Timestamp
199 pc_ua_w_u32(&w, 0); // View.ViewVersion
200 pc_ua_w_u32(&w, 0); // RequestedMaxReferencesPerNode
201 pc_ua_w_i32(&w, 1); // NodesToBrowse count
202 pc_ua_w_nodeid_numeric(&w, ns, id); // BrowseDescription.NodeId
203 pc_ua_w_u32(&w, 0); // BrowseDirection (Forward)
204 pc_ua_w_nodeid_numeric(&w, 0, 0); // ReferenceTypeId (null = all)
205 pc_ua_w_bool(&w, true); // IncludeSubtypes
206 pc_ua_w_u32(&w, 0); // NodeClassMask
207 pc_ua_w_u32(&w, 0x3F); // ResultMask
208 return cw_patch(&w);
209}
210
211size_t pc_opcua_client_write(OpcUaClient *c, const OpcUaWriteItem *items, uint32_t n, uint8_t *out, size_t cap)
212{
213 UaWriter w = {out, cap, 0, true};
214 cw_msg(c, &w, OPCUA_ID_WRITE_REQ);
215 cw_request_header(c, &w, true);
216 pc_ua_w_i32(&w, (int32_t)n); // NodesToWrite count
217 for (uint32_t i = 0; i < n; i++)
218 {
219 pc_ua_w_nodeid_numeric(&w, items[i].ns, items[i].id); // NodeId
220 pc_ua_w_u32(&w, items[i].attribute); // AttributeId
221 pc_ua_w_string(&w, nullptr, -1); // IndexRange
222 pc_ua_w_datavalue(&w, &items[i].value, OPCUA_STATUS_GOOD); // Value (DataValue, value-only)
223 }
224 return cw_patch(&w);
225}
226
227size_t pc_opcua_client_close_session(OpcUaClient *c, uint8_t *out, size_t cap)
228{
229 UaWriter w = {out, cap, 0, true};
230 cw_msg(c, &w, OPCUA_ID_CLOSE_SESSION_REQ);
231 cw_request_header(c, &w, true);
232 pc_ua_w_bool(&w, true); // DeleteSubscriptions
233 return cw_patch(&w);
234}
235
236size_t pc_opcua_client_close_channel(uint8_t *out, size_t cap)
237{
238 UaWriter w = {out, cap, 0, true};
239 pc_ua_w_u8(&w, 'C');
240 pc_ua_w_u8(&w, 'L');
241 pc_ua_w_u8(&w, 'O');
242 pc_ua_w_u8(&w, 'F');
243 pc_ua_w_u32(&w, 0); // size placeholder (patched to 8)
244 return cw_patch(&w);
245}
246
247// ---------------------------------------------------------------------------
248// Parser helpers
249// ---------------------------------------------------------------------------
250static void cr_skip(UaReader *r, size_t n)
251{
252 // r->err is never already set on entry: both call sites only skip when a just-read length is > 0,
253 // and a latched reader decodes every length as 0 - so that operand is excluded below.
254 if (r->err || r->off + n > r->len) // GCOVR_EXCL_LINE
255 {
256 r->err = true;
257 return;
258 }
259 r->off += n;
260}
261
262static void cr_skip_string(UaReader *r)
263{
264 int32_t l = pc_ua_r_i32(r);
265 if (l > 0)
266 {
267 cr_skip(r, (size_t)l);
268 }
269}
270
271static void cr_skip_string_array(UaReader *r)
272{
273 int32_t n = pc_ua_r_i32(r);
274 for (int32_t i = 0; i < n; i++)
275 {
276 cr_skip_string(r);
277 }
278}
279
280// Consume a ResponseHeader (SecurityPolicy None encoding: empty diagnostics, no body
281// AdditionalHeader). Captures the ServiceResult.
282static bool cr_response_header(UaReader *r, uint32_t *service_result)
283{
284 (void)pc_ua_r_u64(r); // Timestamp
285 (void)pc_ua_r_u32(r); // RequestHandle
286 uint32_t svc = pc_ua_r_u32(r);
287 if (service_result) // GCOVR_EXCL_LINE file-static; every call site passes the address of its own svc
288 {
289 *service_result = svc;
290 }
291 (void)pc_ua_r_u8(r); // ServiceDiagnostics (DiagnosticInfo, empty)
292 cr_skip_string_array(r); // StringTable
293 UaNodeId ah;
294 pc_ua_r_nodeid(r, &ah); // AdditionalHeader NodeId
295 (void)pc_ua_r_u8(r); // AdditionalHeader ExtensionObject (no body)
296 return !r->err;
297}
298
299// Open a MSG response: validate type + body TypeId, then consume the ResponseHeader.
300static bool cr_msg_open(const uint8_t *msg, size_t len, UaReader *r, uint32_t expect_type, uint32_t *service_result)
301{
302 UaMsgHeader h;
303 if (!pc_opcua_parse_header(msg, len, &h) || memcmp(h.type, "MSG", 3) != 0 || h.size != len)
304 {
305 return false;
306 }
307 *r = UaReader{msg + 8, len - 8, 0, false};
308 (void)pc_ua_r_u32(r); // SecureChannelId
309 (void)pc_ua_r_u32(r); // TokenId
310 (void)pc_ua_r_u32(r); // SequenceNumber
311 (void)pc_ua_r_u32(r); // RequestId
312 UaNodeId tid;
313 if (!pc_ua_r_nodeid(r, &tid))
314 {
315 return false;
316 }
317 if (!(tid.numeric && tid.id == expect_type))
318 {
319 return false;
320 }
321 return cr_response_header(r, service_result);
322}
323
324// ---------------------------------------------------------------------------
325// Response parsers
326// ---------------------------------------------------------------------------
327bool pc_opcua_client_on_ack(const uint8_t *msg, size_t len, OpcUaAckInfo *out)
328{
329 UaMsgHeader h;
330 if (!pc_opcua_parse_header(msg, len, &h) || memcmp(h.type, "ACK", 3) != 0)
331 {
332 return false;
333 }
334 if (h.size != len || len < 8 + 20)
335 {
336 return false;
337 }
338 UaReader r = {msg + 8, len - 8, 0, false};
339 (void)pc_ua_r_u32(&r); // ProtocolVersion
340 out->recv_buf_size = pc_ua_r_u32(&r);
341 out->send_buf_size = pc_ua_r_u32(&r);
342 out->max_msg_size = pc_ua_r_u32(&r);
343 out->max_chunk_count = pc_ua_r_u32(&r);
344 return !r.err;
345}
346
347bool pc_opcua_client_on_open(OpcUaClient *c, const uint8_t *msg, size_t len)
348{
349 UaMsgHeader h;
350 if (!pc_opcua_parse_header(msg, len, &h) || memcmp(h.type, "OPN", 3) != 0 || h.size != len)
351 {
352 return false;
353 }
354 UaReader r = {msg + 8, len - 8, 0, false};
355 (void)pc_ua_r_u32(&r); // SecureChannelId (asymmetric security header)
356 cr_skip_string(&r); // SecurityPolicyUri
357 cr_skip_string(&r); // SenderCertificate
358 cr_skip_string(&r); // ReceiverCertificateThumbprint
359 (void)pc_ua_r_u32(&r); // SequenceNumber
360 (void)pc_ua_r_u32(&r); // RequestId
361 UaNodeId tid;
362 if (!pc_ua_r_nodeid(&r, &tid) || !(tid.numeric && tid.id == OPCUA_ID_OPEN_RESP))
363 {
364 return false;
365 }
366 uint32_t svc = 0;
367 if (!cr_response_header(&r, &svc))
368 {
369 return false;
370 }
371 (void)pc_ua_r_u32(&r); // ServerProtocolVersion
372 c->channel_id = pc_ua_r_u32(&r); // SecurityToken.ChannelId
373 c->token_id = pc_ua_r_u32(&r); // SecurityToken.TokenId
374 (void)pc_ua_r_u64(&r); // CreatedAt
375 (void)pc_ua_r_u32(&r); // RevisedLifetime
376 return !r.err && svc == OPCUA_STATUS_GOOD;
377}
378
379int32_t pc_opcua_client_on_get_endpoints(const uint8_t *msg, size_t len)
380{
381 UaReader r;
382 uint32_t svc = 0;
383 if (!cr_msg_open(msg, len, &r, OPCUA_ID_GET_ENDPOINTS_RESP, &svc) || svc != OPCUA_STATUS_GOOD)
384 {
385 return -1;
386 }
387 int32_t cnt = pc_ua_r_i32(&r); // Endpoints[] count
388 return (cnt < 0 || r.err) ? -1 : cnt;
389}
390
391bool pc_opcua_client_on_create_session(OpcUaClient *c, const uint8_t *msg, size_t len)
392{
393 UaReader r;
394 uint32_t svc = 0;
395 if (!cr_msg_open(msg, len, &r, OPCUA_ID_CREATE_SESSION_RESP, &svc))
396 {
397 return false;
398 }
399 UaNodeId sid;
400 pc_ua_r_nodeid(&r, &sid); // SessionId
401 UaNodeId atok;
402 pc_ua_r_nodeid(&r, &atok); // AuthenticationToken
403 c->session_auth_ns = atok.ns;
404 c->session_auth_id = atok.id;
405 c->session_auth_numeric = atok.numeric;
406 return !r.err && svc == OPCUA_STATUS_GOOD;
407}
408
409bool pc_opcua_client_on_activate_session(const uint8_t *msg, size_t len)
410{
411 UaReader r;
412 uint32_t svc = 0;
413 if (!cr_msg_open(msg, len, &r, OPCUA_ID_ACTIVATE_SESSION_RESP, &svc))
414 {
415 return false;
416 }
417 return svc == OPCUA_STATUS_GOOD;
418}
419
420int32_t pc_opcua_client_on_read(const uint8_t *msg, size_t len, OpcUaVariant *vals, uint32_t *statuses, uint32_t max)
421{
422 UaReader r;
423 uint32_t svc = 0;
424 if (!cr_msg_open(msg, len, &r, OPCUA_ID_READ_RESP, &svc) || svc != OPCUA_STATUS_GOOD)
425 {
426 return -1;
427 }
428 int32_t cnt = pc_ua_r_i32(&r); // Results count
429 if (cnt < 0)
430 {
431 return -1;
432 }
433 uint32_t out_n = 0;
434 for (int32_t i = 0; i < cnt; i++)
435 {
436 uint8_t mask = pc_ua_r_u8(&r);
437 OpcUaVariant v;
438 memset(&v, 0, sizeof(v));
439 uint32_t st = OPCUA_STATUS_GOOD;
440 if (mask & 0x01) // Value (Variant) present
441 {
442 OpcUaVariantType vt = (OpcUaVariantType)pc_ua_r_u8(&r);
443 v.type = vt;
444 switch (vt)
445 {
446 case OpcUaVariantType::OPCUA_VAR_BOOL:
447 v.b = pc_ua_r_bool(&r);
448 break;
449 case OpcUaVariantType::OPCUA_VAR_INT32:
450 v.i32 = pc_ua_r_i32(&r);
451 break;
452 case OpcUaVariantType::OPCUA_VAR_UINT32:
453 v.u32 = pc_ua_r_u32(&r);
454 break;
455 case OpcUaVariantType::OPCUA_VAR_FLOAT:
456 v.f32 = pc_ua_r_f32(&r);
457 break;
458 case OpcUaVariantType::OPCUA_VAR_DOUBLE:
459 v.f64 = pc_ua_r_f64(&r);
460 break;
461 case OpcUaVariantType::OPCUA_VAR_STRING: {
462 int32_t sl = pc_ua_r_i32(&r);
463 v.str_len = sl;
464 if (sl > 0)
465 {
466 v.str = (const char *)(r.p + r.off); // points into msg
467 cr_skip(&r, (size_t)sl);
468 }
469 break;
470 }
471 default:
472 r.err = true; // unsupported Variant type
473 break;
474 }
475 }
476 if (mask & 0x02) // StatusCode present
477 {
478 st = pc_ua_r_u32(&r);
479 }
480 if (out_n < max)
481 {
482 if (vals)
483 {
484 vals[out_n] = v;
485 }
486 if (statuses)
487 {
488 statuses[out_n] = st;
489 }
490 out_n++;
491 }
492 }
493 return r.err ? -1 : (int32_t)out_n;
494}
495
496int32_t pc_opcua_client_on_write(const uint8_t *msg, size_t len, uint32_t *results, uint32_t max)
497{
498 UaReader r;
499 uint32_t svc = 0;
500 if (!cr_msg_open(msg, len, &r, OPCUA_ID_WRITE_RESP, &svc) || svc != OPCUA_STATUS_GOOD)
501 {
502 return -1;
503 }
504 int32_t cnt = pc_ua_r_i32(&r); // Results count
505 if (cnt < 0)
506 {
507 return -1;
508 }
509 uint32_t out_n = 0;
510 for (int32_t i = 0; i < cnt; i++)
511 {
512 uint32_t st = pc_ua_r_u32(&r);
513 if (out_n < max)
514 {
515 if (results)
516 {
517 results[out_n] = st;
518 }
519 out_n++;
520 }
521 }
522 return r.err ? -1 : (int32_t)out_n;
523}
524
525int32_t pc_opcua_client_on_browse(const uint8_t *msg, size_t len, OpcUaClientRef *refs, uint32_t max)
526{
527 UaReader r;
528 uint32_t svc = 0;
529 if (!cr_msg_open(msg, len, &r, OPCUA_ID_BROWSE_RESP, &svc) || svc != OPCUA_STATUS_GOOD)
530 {
531 return -1;
532 }
533 int32_t nres = pc_ua_r_i32(&r); // Results count
534 if (nres < 0)
535 {
536 return -1;
537 }
538 uint32_t out_n = 0;
539 for (int32_t ri = 0; ri < nres; ri++)
540 {
541 (void)pc_ua_r_u32(&r); // BrowseResult.StatusCode
542 cr_skip_string(&r); // ContinuationPoint (ByteString)
543 int32_t nrefs = pc_ua_r_i32(&r);
544 for (int32_t j = 0; j < nrefs; j++)
545 {
546 OpcUaClientRef ref;
547 memset(&ref, 0, sizeof(ref));
548 UaNodeId rt;
549 pc_ua_r_nodeid(&r, &rt);
550 ref.ref_type_id = rt.id;
551 ref.is_forward = pc_ua_r_bool(&r);
552 UaNodeId tgt;
553 pc_ua_r_nodeid(&r, &tgt);
554 ref.target_ns = tgt.ns;
555 ref.target_id = tgt.id;
556 (void)pc_ua_r_u16(&r); // BrowseName.NamespaceIndex
557 int32_t bnl = 0;
558 pc_ua_r_string(&r, ref.browse_name, sizeof(ref.browse_name), &bnl);
559 uint8_t m = pc_ua_r_u8(&r); // DisplayName LocalizedText mask
560 if (m & 0x01)
561 {
562 cr_skip_string(&r); // Locale
563 }
564 if (m & 0x02)
565 {
566 cr_skip_string(&r); // Text
567 }
568 ref.node_class = pc_ua_r_u32(&r);
569 UaNodeId td;
570 pc_ua_r_nodeid(&r, &td); // TypeDefinition
571 if (out_n < max)
572 {
573 if (refs)
574 {
575 refs[out_n] = ref;
576 }
577 out_n++;
578 }
579 }
580 }
581 return r.err ? -1 : (int32_t)out_n;
582}
583
584#endif // PC_ENABLE_OPCUA_CLIENT
uint32_t mask(uint8_t width)
Mask of width low bits (width 32 handled without a 32-bit shift, which is UB).
Definition crc.h:61