13#if PC_ENABLE_OPCUA_CLIENT
17void pc_opcua_client_init(OpcUaClient *c)
19 memset(c, 0,
sizeof(*c));
25static size_t cw_patch(UaWriter *w)
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);
40static void cw_request_header(OpcUaClient *c, UaWriter *w,
bool with_session_token)
42 if (with_session_token && c->session_auth_id)
44 pc_ua_w_nodeid_numeric(w, c->session_auth_ns, c->session_auth_id);
48 pc_ua_w_nodeid_numeric(w, 0, 0);
51 pc_ua_w_u32(w, ++c->request_handle);
53 pc_ua_w_string(w,
nullptr, -1);
55 pc_ua_w_nodeid_numeric(w, 0, 0);
61static void cw_msg(OpcUaClient *c, UaWriter *w, uint32_t type_id)
68 pc_ua_w_u32(w, c->channel_id);
69 pc_ua_w_u32(w, c->token_id);
70 pc_ua_w_u32(w, ++c->seq);
71 pc_ua_w_u32(w, ++c->request_id);
72 pc_ua_w_nodeid_numeric(w, 0, type_id);
78size_t pc_opcua_client_hello(
const char *endpoint_url, uint8_t *out,
size_t cap)
80 UaWriter w = {out, cap, 0,
true};
87 pc_ua_w_u32(&w, PC_OPCUA_BUF);
88 pc_ua_w_u32(&w, PC_OPCUA_BUF);
91 pc_ua_w_string(&w, endpoint_url, endpoint_url ? (int32_t)strnlen(endpoint_url, w.cap) : -1);
95size_t pc_opcua_client_open(OpcUaClient *c, uint8_t *out,
size_t cap)
97 UaWriter w = {out, cap, 0,
true};
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);
106 pc_ua_w_string(&w,
nullptr, -1);
107 pc_ua_w_u32(&w, ++c->seq);
108 pc_ua_w_u32(&w, ++c->request_id);
109 pc_ua_w_nodeid_numeric(&w, 0, OPCUA_ID_OPEN_REQ);
110 cw_request_header(c, &w,
false);
114 pc_ua_w_string(&w,
nullptr, -1);
115 pc_ua_w_u32(&w, 3600000);
119size_t pc_opcua_client_get_endpoints(OpcUaClient *c,
const char *endpoint_url, uint8_t *out,
size_t cap)
121 UaWriter w = {out, cap, 0,
true};
122 cw_msg(c, &w, OPCUA_ID_GET_ENDPOINTS_REQ);
123 cw_request_header(c, &w,
false);
124 pc_ua_w_string(&w, endpoint_url, endpoint_url ? (int32_t)strnlen(endpoint_url, w.cap) : -1);
130size_t pc_opcua_client_create_session(OpcUaClient *c,
const char *session_name,
const char *endpoint_url, uint8_t *out,
133 UaWriter w = {out, cap, 0,
true};
134 cw_msg(c, &w, OPCUA_ID_CREATE_SESSION_REQ);
135 cw_request_header(c, &w,
false);
137 pc_ua_w_string(&w,
"urn:det:opcua:client", 20);
138 pc_ua_w_string(&w,
"urn:det:opcua", 13);
139 pc_ua_w_localizedtext(&w,
nullptr,
"pc_opcua_client");
141 pc_ua_w_string(&w,
nullptr, -1);
142 pc_ua_w_string(&w,
nullptr, -1);
144 pc_ua_w_string(&w,
nullptr, -1);
145 pc_ua_w_string(&w, endpoint_url, endpoint_url ? (int32_t)strnlen(endpoint_url, w.cap) : -1);
146 pc_ua_w_string(&w, session_name, session_name ? (int32_t)strnlen(session_name, w.cap) : -1);
147 pc_ua_w_string(&w,
nullptr, -1);
148 pc_ua_w_string(&w,
nullptr, -1);
149 pc_ua_w_f64(&w, 1200000.0);
154size_t pc_opcua_client_activate_session(OpcUaClient *c, uint8_t *out,
size_t cap)
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);
160 pc_ua_w_string(&w,
nullptr, -1);
164 pc_ua_w_nodeid_numeric(&w, 0, 321);
165 pc_ua_w_u8(&w, 0x01);
166 pc_ua_w_i32(&w, 4 + 9);
167 pc_ua_w_string(&w,
"anonymous", 9);
168 pc_ua_w_string(&w,
nullptr, -1);
169 pc_ua_w_string(&w,
nullptr, -1);
173size_t pc_opcua_client_read(OpcUaClient *c,
const OpcUaReadItem *items, uint32_t n, uint8_t *out,
size_t cap)
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);
180 pc_ua_w_i32(&w, (int32_t)n);
181 for (uint32_t i = 0; i < n; i++)
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);
187 pc_ua_w_string(&w,
nullptr, -1);
192size_t pc_opcua_client_browse(OpcUaClient *c, uint16_t ns, uint32_t
id, uint8_t *out,
size_t cap)
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);
202 pc_ua_w_nodeid_numeric(&w, ns,
id);
204 pc_ua_w_nodeid_numeric(&w, 0, 0);
205 pc_ua_w_bool(&w,
true);
207 pc_ua_w_u32(&w, 0x3F);
211size_t pc_opcua_client_write(OpcUaClient *c,
const OpcUaWriteItem *items, uint32_t n, uint8_t *out,
size_t cap)
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);
217 for (uint32_t i = 0; i < n; i++)
219 pc_ua_w_nodeid_numeric(&w, items[i].ns, items[i].
id);
220 pc_ua_w_u32(&w, items[i].attribute);
221 pc_ua_w_string(&w,
nullptr, -1);
222 pc_ua_w_datavalue(&w, &items[i].value, OPCUA_STATUS_GOOD);
227size_t pc_opcua_client_close_session(OpcUaClient *c, uint8_t *out,
size_t cap)
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);
236size_t pc_opcua_client_close_channel(uint8_t *out,
size_t cap)
238 UaWriter w = {out, cap, 0,
true};
250static void cr_skip(UaReader *r,
size_t n)
254 if (r->err || r->off + n > r->len)
262static void cr_skip_string(UaReader *r)
264 int32_t l = pc_ua_r_i32(r);
267 cr_skip(r, (
size_t)l);
271static void cr_skip_string_array(UaReader *r)
273 int32_t n = pc_ua_r_i32(r);
274 for (int32_t i = 0; i < n; i++)
282static bool cr_response_header(UaReader *r, uint32_t *service_result)
284 (void)pc_ua_r_u64(r);
285 (void)pc_ua_r_u32(r);
286 uint32_t svc = pc_ua_r_u32(r);
289 *service_result = svc;
292 cr_skip_string_array(r);
294 pc_ua_r_nodeid(r, &ah);
300static bool cr_msg_open(
const uint8_t *msg,
size_t len, UaReader *r, uint32_t expect_type, uint32_t *service_result)
303 if (!pc_opcua_parse_header(msg, len, &h) || memcmp(h.type,
"MSG", 3) != 0 || h.size != len)
307 *r = UaReader{msg + 8, len - 8, 0,
false};
308 (void)pc_ua_r_u32(r);
309 (void)pc_ua_r_u32(r);
310 (void)pc_ua_r_u32(r);
311 (void)pc_ua_r_u32(r);
313 if (!pc_ua_r_nodeid(r, &tid))
317 if (!(tid.numeric && tid.id == expect_type))
321 return cr_response_header(r, service_result);
327bool pc_opcua_client_on_ack(
const uint8_t *msg,
size_t len, OpcUaAckInfo *out)
330 if (!pc_opcua_parse_header(msg, len, &h) || memcmp(h.type,
"ACK", 3) != 0)
334 if (h.size != len || len < 8 + 20)
338 UaReader r = {msg + 8, len - 8, 0,
false};
339 (void)pc_ua_r_u32(&r);
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);
347bool pc_opcua_client_on_open(OpcUaClient *c,
const uint8_t *msg,
size_t len)
350 if (!pc_opcua_parse_header(msg, len, &h) || memcmp(h.type,
"OPN", 3) != 0 || h.size != len)
354 UaReader r = {msg + 8, len - 8, 0,
false};
355 (void)pc_ua_r_u32(&r);
359 (void)pc_ua_r_u32(&r);
360 (void)pc_ua_r_u32(&r);
362 if (!pc_ua_r_nodeid(&r, &tid) || !(tid.numeric && tid.id == OPCUA_ID_OPEN_RESP))
367 if (!cr_response_header(&r, &svc))
371 (void)pc_ua_r_u32(&r);
372 c->channel_id = pc_ua_r_u32(&r);
373 c->token_id = pc_ua_r_u32(&r);
374 (void)pc_ua_r_u64(&r);
375 (void)pc_ua_r_u32(&r);
376 return !r.err && svc == OPCUA_STATUS_GOOD;
379int32_t pc_opcua_client_on_get_endpoints(
const uint8_t *msg,
size_t len)
383 if (!cr_msg_open(msg, len, &r, OPCUA_ID_GET_ENDPOINTS_RESP, &svc) || svc != OPCUA_STATUS_GOOD)
387 int32_t cnt = pc_ua_r_i32(&r);
388 return (cnt < 0 || r.err) ? -1 : cnt;
391bool pc_opcua_client_on_create_session(OpcUaClient *c,
const uint8_t *msg,
size_t len)
395 if (!cr_msg_open(msg, len, &r, OPCUA_ID_CREATE_SESSION_RESP, &svc))
400 pc_ua_r_nodeid(&r, &sid);
402 pc_ua_r_nodeid(&r, &atok);
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;
409bool pc_opcua_client_on_activate_session(
const uint8_t *msg,
size_t len)
413 if (!cr_msg_open(msg, len, &r, OPCUA_ID_ACTIVATE_SESSION_RESP, &svc))
417 return svc == OPCUA_STATUS_GOOD;
420int32_t pc_opcua_client_on_read(
const uint8_t *msg,
size_t len, OpcUaVariant *vals, uint32_t *statuses, uint32_t max)
424 if (!cr_msg_open(msg, len, &r, OPCUA_ID_READ_RESP, &svc) || svc != OPCUA_STATUS_GOOD)
428 int32_t cnt = pc_ua_r_i32(&r);
434 for (int32_t i = 0; i < cnt; i++)
436 uint8_t
mask = pc_ua_r_u8(&r);
438 memset(&v, 0,
sizeof(v));
439 uint32_t st = OPCUA_STATUS_GOOD;
442 OpcUaVariantType vt = (OpcUaVariantType)pc_ua_r_u8(&r);
446 case OpcUaVariantType::OPCUA_VAR_BOOL:
447 v.b = pc_ua_r_bool(&r);
449 case OpcUaVariantType::OPCUA_VAR_INT32:
450 v.i32 = pc_ua_r_i32(&r);
452 case OpcUaVariantType::OPCUA_VAR_UINT32:
453 v.u32 = pc_ua_r_u32(&r);
455 case OpcUaVariantType::OPCUA_VAR_FLOAT:
456 v.f32 = pc_ua_r_f32(&r);
458 case OpcUaVariantType::OPCUA_VAR_DOUBLE:
459 v.f64 = pc_ua_r_f64(&r);
461 case OpcUaVariantType::OPCUA_VAR_STRING: {
462 int32_t sl = pc_ua_r_i32(&r);
466 v.str = (
const char *)(r.p + r.off);
467 cr_skip(&r, (
size_t)sl);
478 st = pc_ua_r_u32(&r);
488 statuses[out_n] = st;
493 return r.err ? -1 : (int32_t)out_n;
496int32_t pc_opcua_client_on_write(
const uint8_t *msg,
size_t len, uint32_t *results, uint32_t max)
500 if (!cr_msg_open(msg, len, &r, OPCUA_ID_WRITE_RESP, &svc) || svc != OPCUA_STATUS_GOOD)
504 int32_t cnt = pc_ua_r_i32(&r);
510 for (int32_t i = 0; i < cnt; i++)
512 uint32_t st = pc_ua_r_u32(&r);
522 return r.err ? -1 : (int32_t)out_n;
525int32_t pc_opcua_client_on_browse(
const uint8_t *msg,
size_t len, OpcUaClientRef *refs, uint32_t max)
529 if (!cr_msg_open(msg, len, &r, OPCUA_ID_BROWSE_RESP, &svc) || svc != OPCUA_STATUS_GOOD)
533 int32_t nres = pc_ua_r_i32(&r);
539 for (int32_t ri = 0; ri < nres; ri++)
541 (void)pc_ua_r_u32(&r);
543 int32_t nrefs = pc_ua_r_i32(&r);
544 for (int32_t j = 0; j < nrefs; j++)
547 memset(&ref, 0,
sizeof(ref));
549 pc_ua_r_nodeid(&r, &rt);
550 ref.ref_type_id = rt.id;
551 ref.is_forward = pc_ua_r_bool(&r);
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);
558 pc_ua_r_string(&r, ref.browse_name,
sizeof(ref.browse_name), &bnl);
559 uint8_t m = pc_ua_r_u8(&r);
568 ref.node_class = pc_ua_r_u32(&r);
570 pc_ua_r_nodeid(&r, &td);
581 return r.err ? -1 : (int32_t)out_n;
uint32_t mask(uint8_t width)
Mask of width low bits (width 32 handled without a 32-bit shift, which is UB).