30static void w_bytes(UaWriter *w,
const void *src,
size_t n)
32 if (!w->ok || w->n + n > w->cap)
37 memcpy(w->o + w->n, src, n);
41void pc_ua_w_u8(UaWriter *w, uint8_t v)
45void pc_ua_w_u16(UaWriter *w, uint16_t v)
47 uint8_t b[2] = {(uint8_t)v, (uint8_t)(v >> 8)};
50void pc_ua_w_u32(UaWriter *w, uint32_t v)
52 uint8_t b[4] = {(uint8_t)v, (uint8_t)(v >> 8), (uint8_t)(v >> 16), (uint8_t)(v >> 24)};
55void pc_ua_w_u64(UaWriter *w, uint64_t v)
58 for (
int i = 0; i < 8; i++)
60 b[i] = (uint8_t)(v >> (8 * i));
64void pc_ua_w_i32(UaWriter *w, int32_t v)
66 pc_ua_w_u32(w, (uint32_t)v);
68void pc_ua_w_f32(UaWriter *w,
float v)
74void pc_ua_w_f64(UaWriter *w,
double v)
80void pc_ua_w_bool(UaWriter *w,
bool v)
82 pc_ua_w_u8(w, v ? 1 : 0);
84void pc_ua_w_string(UaWriter *w,
const char *s, int32_t len)
89 w_bytes(w, s, (
size_t)len);
93static bool r_take(UaReader *r,
void *dst,
size_t n)
95 if (r->err || r->off + n > r->len)
100 memcpy(dst, r->p + r->off, n);
105uint8_t pc_ua_r_u8(UaReader *r)
111uint16_t pc_ua_r_u16(UaReader *r)
113 uint8_t b[2] = {0, 0};
115 return (uint16_t)(b[0] | (b[1] << 8));
117uint32_t pc_ua_r_u32(UaReader *r)
119 uint8_t b[4] = {0, 0, 0, 0};
121 return (uint32_t)b[0] | ((uint32_t)b[1] << 8) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 24);
123uint64_t pc_ua_r_u64(UaReader *r)
126 if (!r_take(r, b, 8))
131 for (
int i = 0; i < 8; i++)
133 v |= (uint64_t)b[i] << (8 * i);
137int32_t pc_ua_r_i32(UaReader *r)
139 return (int32_t)pc_ua_r_u32(r);
141float pc_ua_r_f32(UaReader *r)
143 uint32_t u = pc_ua_r_u32(r);
148double pc_ua_r_f64(UaReader *r)
150 uint64_t u = pc_ua_r_u64(r);
155bool pc_ua_r_bool(UaReader *r)
157 return pc_ua_r_u8(r) != 0;
159bool pc_ua_r_string(UaReader *r,
char *out,
size_t cap, int32_t *out_len)
161 int32_t len = pc_ua_r_i32(r);
178 if ((
size_t)len + 1 > cap || r->off + (
size_t)len > r->len)
183 memcpy(out, r->p + r->off, (
size_t)len);
185 r->off += (size_t)len;
189static void r_skip(UaReader *r,
size_t n)
191 if (r->err || r->off + n > r->len)
202void pc_ua_w_nodeid_numeric(UaWriter *w, uint16_t ns, uint32_t
id)
204 if (ns == 0 &&
id <= 0xFF)
207 pc_ua_w_u8(w, (uint8_t)
id);
209 else if (ns <= 0xFF &&
id <= 0xFFFF)
212 pc_ua_w_u8(w, (uint8_t)ns);
213 pc_ua_w_u16(w, (uint16_t)
id);
223bool pc_ua_r_nodeid(UaReader *r, UaNodeId *out)
225 uint8_t enc = pc_ua_r_u8(r);
226 uint8_t kind = enc & 0x0F;
233 out->id = pc_ua_r_u8(r);
236 out->ns = pc_ua_r_u8(r);
237 out->id = pc_ua_r_u16(r);
240 out->ns = pc_ua_r_u16(r);
241 out->id = pc_ua_r_u32(r);
246 out->ns = pc_ua_r_u16(r);
247 out->numeric =
false;
248 int32_t l = pc_ua_r_i32(r);
251 r_skip(r, (
size_t)l);
256 out->ns = pc_ua_r_u16(r);
257 out->numeric =
false;
266 int32_t l = pc_ua_r_i32(r);
269 r_skip(r, (
size_t)l);
274 (void)pc_ua_r_u32(r);
280static bool r_ext_object_skip(UaReader *r)
283 if (!pc_ua_r_nodeid(r, &tid))
287 uint8_t body_enc = pc_ua_r_u8(r);
288 if (body_enc == 0x00)
292 int32_t l = pc_ua_r_i32(r);
295 r_skip(r, (
size_t)l);
303static bool r_request_header(UaReader *r, uint32_t *request_handle)
306 pc_ua_r_nodeid(r, &auth);
307 (void)pc_ua_r_u64(r);
308 uint32_t rh = pc_ua_r_u32(r);
311 *request_handle = rh;
313 (void)pc_ua_r_u32(r);
314 int32_t aid = pc_ua_r_i32(r);
317 r_skip(r, (
size_t)aid);
319 (void)pc_ua_r_u32(r);
320 return r_ext_object_skip(r);
325static bool r_msg_preamble(
const uint8_t *msg,
size_t len, UaReader *r, OpcUaMsg *m)
328 if (!pc_opcua_parse_header(msg, len, &h) || memcmp(h.type,
"MSG", 3) != 0)
337 UaReader rr = {msg + 8, len - 8, 0,
false};
339 m->secure_channel_id = pc_ua_r_u32(r);
340 m->token_id = pc_ua_r_u32(r);
341 m->sequence_number = pc_ua_r_u32(r);
342 m->request_id = pc_ua_r_u32(r);
345 if (!pc_ua_r_nodeid(r, &tid))
349 m->type_id = tid.numeric ? tid.id : 0;
350 return r_request_header(r, &m->request_handle);
353int64_t pc_opcua_filetime_from_unix(int64_t unix_seconds)
355 if (unix_seconds <= 0)
359 return (unix_seconds + 11644473600LL) * 10000000LL;
365bool pc_opcua_parse_header(
const uint8_t *buf,
size_t len, UaMsgHeader *h)
367 if (!buf || len < 8 || !h)
371 h->type[0] = (char)buf[0];
372 h->type[1] = (char)buf[1];
373 h->type[2] = (char)buf[2];
374 h->chunk = (char)buf[3];
375 h->size = (uint32_t)buf[4] | ((uint32_t)buf[5] << 8) | ((uint32_t)buf[6] << 16) | ((uint32_t)buf[7] << 24);
379bool pc_opcua_parse_hello(
const uint8_t *msg,
size_t len, OpcUaHello *out)
382 if (!pc_opcua_parse_header(msg, len, &h) || memcmp(h.type,
"HEL", 3) != 0)
386 if (h.size != len || h.size < 8 + 20)
390 UaReader r = {msg + 8, len - 8, 0,
false};
391 out->protocol_version = pc_ua_r_u32(&r);
392 out->recv_buf_size = pc_ua_r_u32(&r);
393 out->send_buf_size = pc_ua_r_u32(&r);
394 out->max_msg_size = pc_ua_r_u32(&r);
395 out->max_chunk_count = pc_ua_r_u32(&r);
399static uint32_t neg(uint32_t client, uint32_t server)
405 return client < server ? client : server;
408size_t pc_opcua_build_ack(
const OpcUaHello *hello, uint8_t *out,
size_t cap)
414 const uint32_t total = 8 + 20;
415 UaWriter w = {out, cap, 0,
true};
420 pc_ua_w_u32(&w, total);
422 pc_ua_w_u32(&w, neg(hello->send_buf_size, PC_OPCUA_BUF));
423 pc_ua_w_u32(&w, neg(hello->recv_buf_size, PC_OPCUA_BUF));
424 pc_ua_w_u32(&w, neg(hello->max_msg_size, PC_OPCUA_BUF));
426 return w.ok ? w.n : 0;
429size_t pc_opcua_build_error(uint32_t error_code,
const char *reason, uint8_t *out,
size_t cap)
435 int32_t rlen = reason ? (int32_t)strnlen(reason, cap) : -1;
438 const uint32_t total = 8 + 4 + 4 + (rlen > 0 ? (uint32_t)rlen : 0);
439 UaWriter w = {out, cap, 0,
true};
444 pc_ua_w_u32(&w, total);
445 pc_ua_w_u32(&w, error_code);
446 pc_ua_w_string(&w, reason, rlen);
447 return w.ok ? w.n : 0;
453bool pc_opcua_parse_open(
const uint8_t *msg,
size_t len, OpcUaOpenChannel *out)
456 if (!pc_opcua_parse_header(msg, len, &h) || memcmp(h.type,
"OPN", 3) != 0)
465 UaReader r = {msg + 8, len - 8, 0,
false};
468 out->secure_channel_id = pc_ua_r_u32(&r);
469 int32_t pol = pc_ua_r_i32(&r);
472 r_skip(&r, (
size_t)pol);
474 int32_t sc = pc_ua_r_i32(&r);
477 r_skip(&r, (
size_t)sc);
479 int32_t rt = pc_ua_r_i32(&r);
482 r_skip(&r, (
size_t)rt);
486 out->sequence_number = pc_ua_r_u32(&r);
487 out->request_id = pc_ua_r_u32(&r);
491 if (!pc_ua_r_nodeid(&r, &tid))
495 if (!(tid.numeric && tid.ns == 0 && tid.id == OPCUA_ID_OPEN_REQ))
501 if (!r_request_header(&r, &out->request_handle))
507 out->client_protocol_version = pc_ua_r_u32(&r);
508 out->security_token_request_type = pc_ua_r_u32(&r);
509 out->message_security_mode = pc_ua_r_u32(&r);
510 int32_t nonce = pc_ua_r_i32(&r);
513 r_skip(&r, (
size_t)nonce);
515 out->requested_lifetime = pc_ua_r_u32(&r);
519size_t pc_opcua_build_open_response(
const OpcUaOpenChannel *req, uint32_t channel_id, uint32_t token_id,
520 uint32_t seq_number, int64_t now_ft, uint32_t lifetime, uint8_t *out,
size_t cap)
526 UaWriter w = {out, cap, 0,
true};
536 pc_ua_w_u32(&w, channel_id);
537 pc_ua_w_string(&w, OPCUA_POLICY_NONE_URI, (int32_t)(
sizeof(OPCUA_POLICY_NONE_URI) - 1));
538 pc_ua_w_string(&w,
nullptr, -1);
539 pc_ua_w_string(&w,
nullptr, -1);
542 pc_ua_w_u32(&w, seq_number);
543 pc_ua_w_u32(&w, req->request_id);
546 pc_ua_w_nodeid_numeric(&w, 0, OPCUA_ID_OPEN_RESP);
549 pc_ua_w_u64(&w, (uint64_t)now_ft);
550 pc_ua_w_u32(&w, req->request_handle);
552 pc_ua_w_u8(&w, 0x00);
554 pc_ua_w_nodeid_numeric(&w, 0, 0);
555 pc_ua_w_u8(&w, 0x00);
559 pc_ua_w_u32(&w, channel_id);
560 pc_ua_w_u32(&w, token_id);
561 pc_ua_w_u64(&w, (uint64_t)now_ft);
562 pc_ua_w_u32(&w, lifetime);
563 pc_ua_w_string(&w,
nullptr, -1);
569 out[4] = (uint8_t)w.n;
570 out[5] = (uint8_t)(w.n >> 8);
571 out[6] = (uint8_t)(w.n >> 16);
572 out[7] = (uint8_t)(w.n >> 24);
581static size_t patch_size(UaWriter *w)
587 w->o[4] = (uint8_t)w->n;
588 w->o[5] = (uint8_t)(w->n >> 8);
589 w->o[6] = (uint8_t)(w->n >> 16);
590 w->o[7] = (uint8_t)(w->n >> 24);
596static void w_msg_prefix(UaWriter *w, uint32_t channel_id, uint32_t token_id, uint32_t seq, uint32_t request_id)
603 pc_ua_w_u32(w, channel_id);
604 pc_ua_w_u32(w, token_id);
606 pc_ua_w_u32(w, request_id);
610static void w_response_header(UaWriter *w, int64_t now_ft, uint32_t request_handle, uint32_t service_result)
612 pc_ua_w_u64(w, (uint64_t)now_ft);
613 pc_ua_w_u32(w, request_handle);
614 pc_ua_w_u32(w, service_result);
617 pc_ua_w_nodeid_numeric(w, 0, 0);
621bool pc_opcua_parse_msg(
const uint8_t *msg,
size_t len, OpcUaMsg *out)
624 if (!pc_opcua_parse_header(msg, len, &h) || memcmp(h.type,
"MSG", 3) != 0)
633 UaReader r = {msg + 8, len - 8, 0,
false};
634 out->secure_channel_id = pc_ua_r_u32(&r);
635 out->token_id = pc_ua_r_u32(&r);
636 out->sequence_number = pc_ua_r_u32(&r);
637 out->request_id = pc_ua_r_u32(&r);
640 if (!pc_ua_r_nodeid(&r, &tid))
644 out->type_id = tid.numeric ? tid.id : 0;
646 return r_request_header(&r, &out->request_handle);
651static const char OPCUA_TRANSPORT_URI[] =
652 "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary";
664 OpcUaServerInfo server_info = {PC_OPCUA_DEFAULT_ENDPOINT, PC_OPCUA_DEFAULT_APP_URI, PC_OPCUA_DEFAULT_APP_NAME};
665 OpcUaReadHandler read_handler =
nullptr;
666 OpcUaWriteHandler write_handler =
nullptr;
667 OpcUaBrowseHandler browse_handler =
nullptr;
669 uint8_t msg[PC_OPCUA_BUF];
671 uint32_t channel_id = 0;
672 uint32_t token_id = 0;
674 uint32_t session_id = 0;
675 uint32_t auth_token = 0;
678static OpcuaCtx s_opcua;
680void pc_opcua_set_endpoint_url(
const char *url)
682 s_opcua.server_info.endpoint_url = url;
685void pc_ua_w_endpoint_description(UaWriter *w,
const OpcUaServerInfo *info)
687 const char *url = (info && info->endpoint_url) ? info->endpoint_url : PC_OPCUA_DEFAULT_ENDPOINT;
688 const char *auri = (info && info->application_uri) ? info->application_uri : PC_OPCUA_DEFAULT_APP_URI;
689 const char *aname = (info && info->application_name) ? info->application_name : PC_OPCUA_DEFAULT_APP_NAME;
691 pc_ua_w_string(w, url, (int32_t)strnlen(url, w->cap));
693 pc_ua_w_string(w, auri, (int32_t)strnlen(auri, w->cap));
694 pc_ua_w_string(w,
"urn:det:opcua", 13);
695 pc_ua_w_localizedtext(w,
nullptr, aname);
697 pc_ua_w_string(w,
nullptr, -1);
698 pc_ua_w_string(w,
nullptr, -1);
700 pc_ua_w_string(w,
nullptr, -1);
702 pc_ua_w_string(w, OPCUA_POLICY_NONE_URI, (int32_t)(
sizeof(OPCUA_POLICY_NONE_URI) - 1));
705 pc_ua_w_string(w,
"anonymous", 9);
707 pc_ua_w_string(w,
nullptr, -1);
708 pc_ua_w_string(w,
nullptr, -1);
709 pc_ua_w_string(w,
nullptr, -1);
710 pc_ua_w_string(w, OPCUA_TRANSPORT_URI, (int32_t)(
sizeof(OPCUA_TRANSPORT_URI) - 1));
714size_t pc_opcua_build_create_session_response(
const OpcUaMsg *req, uint32_t session_id, uint32_t auth_token,
715 double revised_timeout,
const OpcUaServerInfo *info, uint32_t seq,
716 int64_t now_ft, uint8_t *out,
size_t cap)
722 UaWriter w = {out, cap, 0,
true};
723 w_msg_prefix(&w, req->secure_channel_id, req->token_id, seq, req->request_id);
724 pc_ua_w_nodeid_numeric(&w, 0, OPCUA_ID_CREATE_SESSION_RESP);
725 w_response_header(&w, now_ft, req->request_handle, 0);
727 pc_ua_w_nodeid_numeric(&w, 1, session_id);
728 pc_ua_w_nodeid_numeric(&w, 1, auth_token);
729 pc_ua_w_f64(&w, revised_timeout);
730 pc_ua_w_string(&w,
nullptr, -1);
731 pc_ua_w_string(&w,
nullptr, -1);
733 pc_ua_w_endpoint_description(&w, info);
735 pc_ua_w_string(&w,
nullptr, -1);
736 pc_ua_w_string(&w,
nullptr, -1);
738 return patch_size(&w);
741size_t pc_opcua_build_get_endpoints_response(
const OpcUaMsg *req,
const OpcUaServerInfo *info, uint32_t seq,
742 int64_t now_ft, uint8_t *out,
size_t cap)
748 UaWriter w = {out, cap, 0,
true};
749 w_msg_prefix(&w, req->secure_channel_id, req->token_id, seq, req->request_id);
750 pc_ua_w_nodeid_numeric(&w, 0, OPCUA_ID_GET_ENDPOINTS_RESP);
751 w_response_header(&w, now_ft, req->request_handle, 0);
753 pc_ua_w_endpoint_description(&w, info);
754 return patch_size(&w);
757size_t pc_opcua_build_service_fault(
const OpcUaMsg *req, uint32_t service_result, uint32_t seq, int64_t now_ft,
758 uint8_t *out,
size_t cap)
764 UaWriter w = {out, cap, 0,
true};
765 w_msg_prefix(&w, req->secure_channel_id, req->token_id, seq, req->request_id);
766 pc_ua_w_nodeid_numeric(&w, 0, OPCUA_ID_SERVICE_FAULT);
767 w_response_header(&w, now_ft, req->request_handle, service_result);
768 return patch_size(&w);
771size_t pc_opcua_build_activate_session_response(
const OpcUaMsg *req, uint32_t seq, int64_t now_ft, uint8_t *out,
778 UaWriter w = {out, cap, 0,
true};
779 w_msg_prefix(&w, req->secure_channel_id, req->token_id, seq, req->request_id);
780 pc_ua_w_nodeid_numeric(&w, 0, OPCUA_ID_ACTIVATE_SESSION_RESP);
781 w_response_header(&w, now_ft, req->request_handle, 0);
783 pc_ua_w_string(&w,
nullptr, -1);
786 return patch_size(&w);
792void pc_ua_w_variant(UaWriter *w,
const OpcUaVariant *v)
794 if (!v || v->type == OpcUaVariantType::OPCUA_VAR_NULL)
796 pc_ua_w_u8(w, (uint8_t)OpcUaVariantType::OPCUA_VAR_NULL);
799 pc_ua_w_u8(w, (uint8_t)v->type);
802 case OpcUaVariantType::OPCUA_VAR_BOOL:
803 pc_ua_w_bool(w, v->b);
805 case OpcUaVariantType::OPCUA_VAR_INT32:
806 pc_ua_w_i32(w, v->i32);
808 case OpcUaVariantType::OPCUA_VAR_UINT32:
809 pc_ua_w_u32(w, v->u32);
811 case OpcUaVariantType::OPCUA_VAR_INT64:
812 pc_ua_w_u64(w, (uint64_t)v->i64);
814 case OpcUaVariantType::OPCUA_VAR_UINT64:
815 pc_ua_w_u64(w, v->u64);
817 case OpcUaVariantType::OPCUA_VAR_FLOAT:
818 pc_ua_w_f32(w, v->f32);
820 case OpcUaVariantType::OPCUA_VAR_DOUBLE:
821 pc_ua_w_f64(w, v->f64);
823 case OpcUaVariantType::OPCUA_VAR_STRING:
824 pc_ua_w_string(w, v->str, v->str_len);
832void pc_ua_w_datavalue(UaWriter *w,
const OpcUaVariant *v, uint32_t status)
834 bool has_value = v && v->type != OpcUaVariantType::OPCUA_VAR_NULL;
840 if (status != OPCUA_STATUS_GOOD)
847 pc_ua_w_variant(w, v);
849 if (status != OPCUA_STATUS_GOOD)
851 pc_ua_w_u32(w, status);
855bool pc_ua_r_variant(UaReader *r, OpcUaVariant *out)
857 memset(out, 0,
sizeof(*out));
858 uint8_t enc = pc_ua_r_u8(r);
864 out->type = (OpcUaVariantType)(enc & 0x3F);
867 case OpcUaVariantType::OPCUA_VAR_NULL:
869 case OpcUaVariantType::OPCUA_VAR_BOOL:
870 out->b = pc_ua_r_bool(r);
872 case OpcUaVariantType::OPCUA_VAR_INT32:
873 out->i32 = pc_ua_r_i32(r);
875 case OpcUaVariantType::OPCUA_VAR_UINT32:
876 out->u32 = pc_ua_r_u32(r);
878 case OpcUaVariantType::OPCUA_VAR_INT64:
879 out->i64 = (int64_t)pc_ua_r_u64(r);
881 case OpcUaVariantType::OPCUA_VAR_UINT64:
882 out->u64 = pc_ua_r_u64(r);
884 case OpcUaVariantType::OPCUA_VAR_FLOAT:
885 out->f32 = pc_ua_r_f32(r);
887 case OpcUaVariantType::OPCUA_VAR_DOUBLE:
888 out->f64 = pc_ua_r_f64(r);
890 case OpcUaVariantType::OPCUA_VAR_STRING: {
891 int32_t sl = pc_ua_r_i32(r);
895 if (r->off + (
size_t)sl > r->len)
900 out->str = (
const char *)(r->p + r->off);
901 r->off += (size_t)sl;
912bool pc_ua_r_datavalue(UaReader *r, OpcUaVariant *out_value, uint32_t *out_status)
914 memset(out_value, 0,
sizeof(*out_value));
917 *out_status = OPCUA_STATUS_GOOD;
919 uint8_t
mask = pc_ua_r_u8(r);
922 if (!pc_ua_r_variant(r, out_value))
929 uint32_t st = pc_ua_r_u32(r);
937 (void)pc_ua_r_u64(r);
941 (void)pc_ua_r_u16(r);
945 (void)pc_ua_r_u64(r);
949 (void)pc_ua_r_u16(r);
954bool pc_opcua_parse_read(
const uint8_t *msg,
size_t len, OpcUaReadRequest *out)
957 if (!r_msg_preamble(msg, len, &r, &out->msg))
963 (void)pc_ua_r_f64(&r);
964 (void)pc_ua_r_u32(&r);
965 int32_t cnt = pc_ua_r_i32(&r);
966 out->total = (cnt < 0) ? 0 : (uint32_t)cnt;
968 for (int32_t i = 0; i < cnt; i++)
971 if (!pc_ua_r_nodeid(&r, &nid))
975 uint32_t attr = pc_ua_r_u32(&r);
976 int32_t ir = pc_ua_r_i32(&r);
979 r_skip(&r, (
size_t)ir);
981 (void)pc_ua_r_u16(&r);
982 int32_t qn = pc_ua_r_i32(&r);
985 r_skip(&r, (
size_t)qn);
987 if (out->count < PC_OPCUA_READ_MAX)
989 OpcUaReadItem *it = &out->items[out->count++];
992 it->numeric = nid.numeric;
993 it->attribute = attr;
999size_t pc_opcua_build_read_response(
const OpcUaReadRequest *req,
const OpcUaVariant *values,
const uint32_t *statuses,
1000 uint32_t seq, int64_t now_ft, uint8_t *out,
size_t cap)
1006 UaWriter w = {out, cap, 0,
true};
1007 w_msg_prefix(&w, req->msg.secure_channel_id, req->msg.token_id, seq, req->msg.request_id);
1008 pc_ua_w_nodeid_numeric(&w, 0, OPCUA_ID_READ_RESP);
1009 w_response_header(&w, now_ft, req->msg.request_handle, 0);
1011 pc_ua_w_i32(&w, (int32_t)req->count);
1012 for (uint32_t i = 0; i < req->count; i++)
1014 pc_ua_w_datavalue(&w, values ? &values[i] : nullptr, statuses ? statuses[i] : OPCUA_STATUS_GOOD);
1017 return patch_size(&w);
1021void pc_opcua_set_read_handler(OpcUaReadHandler fn)
1023 s_opcua.read_handler = fn;
1029bool pc_opcua_parse_write(
const uint8_t *msg,
size_t len, OpcUaWriteRequest *out)
1032 if (!r_msg_preamble(msg, len, &r, &out->msg))
1037 int32_t cnt = pc_ua_r_i32(&r);
1038 out->total = (cnt < 0) ? 0 : (uint32_t)cnt;
1040 for (int32_t i = 0; i < cnt; i++)
1043 if (!pc_ua_r_nodeid(&r, &nid))
1047 uint32_t attr = pc_ua_r_u32(&r);
1048 int32_t ir = pc_ua_r_i32(&r);
1051 r_skip(&r, (
size_t)ir);
1054 if (!pc_ua_r_datavalue(&r, &val,
nullptr))
1058 if (out->count < PC_OPCUA_WRITE_MAX)
1060 OpcUaWriteItem *it = &out->items[out->count++];
1063 it->numeric = nid.numeric;
1064 it->attribute = attr;
1071size_t pc_opcua_build_write_response(
const OpcUaWriteRequest *req,
const uint32_t *results, uint32_t seq,
1072 int64_t now_ft, uint8_t *out,
size_t cap)
1078 UaWriter w = {out, cap, 0,
true};
1079 w_msg_prefix(&w, req->msg.secure_channel_id, req->msg.token_id, seq, req->msg.request_id);
1080 pc_ua_w_nodeid_numeric(&w, 0, OPCUA_ID_WRITE_RESP);
1081 w_response_header(&w, now_ft, req->msg.request_handle, 0);
1083 pc_ua_w_i32(&w, (int32_t)req->count);
1084 for (uint32_t i = 0; i < req->count; i++)
1086 pc_ua_w_u32(&w, results ? results[i] : OPCUA_STATUS_GOOD);
1089 return patch_size(&w);
1093void pc_opcua_set_write_handler(OpcUaWriteHandler fn)
1095 s_opcua.write_handler = fn;
1101void pc_ua_w_qualifiedname(UaWriter *w, uint16_t ns,
const char *name)
1104 pc_ua_w_string(w, name, name ? (int32_t)strnlen(name, w->cap) : -1);
1107void pc_ua_w_localizedtext(UaWriter *w,
const char *locale,
const char *text)
1118 pc_ua_w_u8(w, mask);
1121 pc_ua_w_string(w, locale, (int32_t)strnlen(locale, w->cap));
1125 pc_ua_w_string(w, text, (int32_t)strnlen(text, w->cap));
1129void pc_ua_w_reference(UaWriter *w,
const OpcUaReference *ref)
1136 pc_ua_w_nodeid_numeric(w, 0, ref->ref_type_id);
1137 pc_ua_w_bool(w, ref->is_forward);
1138 pc_ua_w_nodeid_numeric(w, ref->target_ns, ref->target_id);
1139 pc_ua_w_qualifiedname(w, ref->browse_name_ns, ref->browse_name);
1140 pc_ua_w_localizedtext(w,
nullptr, ref->display_name);
1141 pc_ua_w_u32(w, ref->node_class);
1142 pc_ua_w_nodeid_numeric(w, 0, ref->type_def_id);
1145bool pc_opcua_parse_browse(
const uint8_t *msg,
size_t len, OpcUaBrowseRequest *out)
1148 if (!r_msg_preamble(msg, len, &r, &out->msg))
1155 pc_ua_r_nodeid(&r, &view);
1156 (void)pc_ua_r_u64(&r);
1157 (void)pc_ua_r_u32(&r);
1158 (void)pc_ua_r_u32(&r);
1160 int32_t cnt = pc_ua_r_i32(&r);
1161 out->total = (cnt < 0) ? 0 : (uint32_t)cnt;
1163 for (int32_t i = 0; i < cnt; i++)
1166 if (!pc_ua_r_nodeid(&r, &nid))
1170 (void)pc_ua_r_u32(&r);
1172 pc_ua_r_nodeid(&r, &rt);
1173 (void)pc_ua_r_bool(&r);
1174 (void)pc_ua_r_u32(&r);
1175 (void)pc_ua_r_u32(&r);
1176 if (out->count < PC_OPCUA_BROWSE_MAX)
1178 OpcUaBrowseItem *it = &out->items[out->count++];
1181 it->numeric = nid.numeric;
1187size_t pc_opcua_build_browse_response(
const OpcUaBrowseRequest *req, OpcUaBrowseHandler handler, uint32_t seq,
1188 int64_t now_ft, uint8_t *out,
size_t cap)
1194 UaWriter w = {out, cap, 0,
true};
1195 w_msg_prefix(&w, req->msg.secure_channel_id, req->msg.token_id, seq, req->msg.request_id);
1196 pc_ua_w_nodeid_numeric(&w, 0, OPCUA_ID_BROWSE_RESP);
1197 w_response_header(&w, now_ft, req->msg.request_handle, 0);
1199 pc_ua_w_i32(&w, (int32_t)req->count);
1200 for (uint32_t i = 0; i < req->count; i++)
1202 OpcUaReference refs[PC_OPCUA_REF_MAX];
1203 int32_t n = handler ? handler(req->items[i].ns, req->items[i].id, refs, PC_OPCUA_REF_MAX) : -1;
1204 uint32_t status = (n < 0) ? OPCUA_STATUS_BAD_NODE_ID_UNKNOWN : OPCUA_STATUS_GOOD;
1205 uint32_t nrefs = (n < 0) ? 0 : (uint32_t)n;
1208 pc_ua_w_u32(&w, status);
1209 pc_ua_w_string(&w,
nullptr, -1);
1210 pc_ua_w_i32(&w, (int32_t)nrefs);
1211 for (uint32_t j = 0; j < nrefs; j++)
1213 pc_ua_w_reference(&w, &refs[j]);
1217 return patch_size(&w);
1220size_t pc_opcua_build_close_session_response(
const OpcUaMsg *req, uint32_t seq, int64_t now_ft, uint8_t *out,
1227 UaWriter w = {out, cap, 0,
true};
1228 w_msg_prefix(&w, req->secure_channel_id, req->token_id, seq, req->request_id);
1229 pc_ua_w_nodeid_numeric(&w, 0, OPCUA_ID_CLOSE_SESSION_RESP);
1230 w_response_header(&w, now_ft, req->request_handle, 0);
1231 return patch_size(&w);
1235void pc_opcua_set_browse_handler(OpcUaBrowseHandler fn)
1237 s_opcua.browse_handler = fn;
1249size_t ring_avail(
const TcpConn *c)
1251 return pc_conn_available(c->
id);
1253void ring_peek(
const TcpConn *c,
size_t off, uint8_t *dst,
size_t n)
1255 pc_conn_peek(c->
id, off, dst, n);
1257void ring_consume(
TcpConn *c,
size_t n)
1259 pc_conn_consume(c->
id, n);
1261void raw_send(uint8_t slot,
const void *data,
size_t n)
1263 if (!pc_conn_active(slot) || n == 0)
1270void close_conn(uint8_t slot)
1277void pc_opcua_rx(uint8_t slot)
1279 if (!pc_conn_active(slot))
1289 if (ring_avail(c) < 8)
1295 ring_peek(c, 0, hdr, 8);
1297 if (!pc_opcua_parse_header(hdr, 8, &h) || h.size < 8 || h.size >
sizeof(s_opcua.msg))
1302 if (ring_avail(c) < h.size)
1307 ring_peek(c, 0, s_opcua.msg, h.size);
1308 ring_consume(c, h.size);
1310 if (memcmp(h.type,
"HEL", 3) == 0)
1314 if (pc_opcua_parse_hello(s_opcua.msg, h.size, &hello) &&
1315 (n = pc_opcua_build_ack(&hello, s_opcua.resp,
sizeof(s_opcua.resp))) > 0)
1317 raw_send(slot, s_opcua.resp, n);
1325 else if (memcmp(h.type,
"OPN", 3) == 0)
1327 OpcUaOpenChannel oc;
1328 if (!pc_opcua_parse_open(s_opcua.msg, h.size, &oc))
1333 if (oc.secure_channel_id == 0)
1335 oc.secure_channel_id = ++s_opcua.channel_id;
1337 uint32_t token = ++s_opcua.token_id;
1338 uint32_t seq = ++s_opcua.seq;
1339 uint32_t lifetime = oc.requested_lifetime ? oc.requested_lifetime : 3600000u;
1340 int64_t now = pc_opcua_filetime_from_unix((int64_t)time(
nullptr));
1341 size_t n = pc_opcua_build_open_response(&oc, oc.secure_channel_id, token, seq, now, lifetime, s_opcua.resp,
1342 sizeof(s_opcua.resp));
1345 raw_send(slot, s_opcua.resp, n);
1353 else if (memcmp(h.type,
"MSG", 3) == 0)
1356 if (!pc_opcua_parse_msg(s_opcua.msg, h.size, &m))
1361 int64_t now = pc_opcua_filetime_from_unix((int64_t)time(
nullptr));
1362 uint32_t seq = ++s_opcua.seq;
1364 if (m.type_id == OPCUA_ID_GET_ENDPOINTS_REQ)
1366 n = pc_opcua_build_get_endpoints_response(&m, &s_opcua.server_info, seq, now, s_opcua.resp,
1367 sizeof(s_opcua.resp));
1369 else if (m.type_id == OPCUA_ID_CREATE_SESSION_REQ)
1371 n = pc_opcua_build_create_session_response(&m, ++s_opcua.session_id, ++s_opcua.auth_token, 1200000.0,
1372 &s_opcua.server_info, seq, now, s_opcua.resp,
1373 sizeof(s_opcua.resp));
1375 else if (m.type_id == OPCUA_ID_ACTIVATE_SESSION_REQ)
1377 n = pc_opcua_build_activate_session_response(&m, seq, now, s_opcua.resp,
sizeof(s_opcua.resp));
1379 else if (m.type_id == OPCUA_ID_READ_REQ)
1381 OpcUaReadRequest rr;
1382 if (!pc_opcua_parse_read(s_opcua.msg, h.size, &rr))
1387 OpcUaVariant vals[PC_OPCUA_READ_MAX];
1388 uint32_t sts[PC_OPCUA_READ_MAX];
1389 for (uint32_t i = 0; i < rr.count; i++)
1391 memset(&vals[i], 0,
sizeof(vals[i]));
1392 bool ok = s_opcua.read_handler &&
1393 s_opcua.read_handler(rr.items[i].ns, rr.items[i].id, rr.items[i].attribute, &vals[i]);
1394 sts[i] = ok ? OPCUA_STATUS_GOOD : OPCUA_STATUS_BAD_NODE_ID_UNKNOWN;
1396 n = pc_opcua_build_read_response(&rr, vals, sts, seq, now, s_opcua.resp,
sizeof(s_opcua.resp));
1398 else if (m.type_id == OPCUA_ID_BROWSE_REQ)
1400 OpcUaBrowseRequest br;
1401 if (!pc_opcua_parse_browse(s_opcua.msg, h.size, &br))
1406 n = pc_opcua_build_browse_response(&br, s_opcua.browse_handler, seq, now, s_opcua.resp,
1407 sizeof(s_opcua.resp));
1409 else if (m.type_id == OPCUA_ID_WRITE_REQ)
1411 OpcUaWriteRequest wr;
1412 if (!pc_opcua_parse_write(s_opcua.msg, h.size, &wr))
1417 uint32_t res[PC_OPCUA_WRITE_MAX];
1418 for (uint32_t i = 0; i < wr.count; i++)
1420 res[i] = s_opcua.write_handler ? s_opcua.write_handler(wr.items[i].ns, wr.items[i].id,
1421 wr.items[i].attribute, &wr.items[i].value)
1422 : OPCUA_STATUS_BAD_NODE_ID_UNKNOWN;
1424 n = pc_opcua_build_write_response(&wr, res, seq, now, s_opcua.resp,
sizeof(s_opcua.resp));
1426 else if (m.type_id == OPCUA_ID_CLOSE_SESSION_REQ)
1428 n = pc_opcua_build_close_session_response(&m, seq, now, s_opcua.resp,
sizeof(s_opcua.resp));
1432 n = pc_opcua_build_service_fault(&m, OPCUA_STATUS_BAD_SERVICE_UNSUPPORTED, seq, now, s_opcua.resp,
1433 sizeof(s_opcua.resp));
1437 raw_send(slot, s_opcua.resp, n);
1440 else if (memcmp(h.type,
"CLO", 3) == 0)
1451static const ProtoHandler s_opcua_handler = {
nullptr, pc_opcua_rx,
nullptr,
nullptr};
1454 return &s_opcua_handler;
1459void pc_opcua_rx(uint8_t slot)
uint32_t mask(uint8_t width)
Mask of width low bits (width 32 handled without a 32-bit shift, which is UB).
OPC UA Binary server: handshake + SecureChannel + Session + Read/Write + Browse (PC_ENABLE_OPCUA).
Layer 5 (Session) - per-protocol connection handler dispatch table.
Per-protocol connection event/poll callbacks (Layer 5 dispatch vtable).
A single TCP connection context.
uint8_t id
Fixed slot index (0 … MAX_CONNS-1).
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
void pc_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
void pc_conn_close(uint8_t slot)
Close connection slot gracefully (tcp_close), aborting if the FIN cannot be queued....
bool pc_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.