25static void w_bytes(UaWriter *w,
const void *src,
size_t n)
27 if (!w->ok || w->n + n > w->cap)
32 memcpy(w->o + w->n, src, n);
36void ua_w_u8(UaWriter *w, uint8_t v)
40void ua_w_u16(UaWriter *w, uint16_t v)
42 uint8_t b[2] = {(uint8_t)v, (uint8_t)(v >> 8)};
45void ua_w_u32(UaWriter *w, uint32_t v)
47 uint8_t b[4] = {(uint8_t)v, (uint8_t)(v >> 8), (uint8_t)(v >> 16), (uint8_t)(v >> 24)};
50void ua_w_u64(UaWriter *w, uint64_t v)
53 for (
int i = 0; i < 8; i++)
54 b[i] = (uint8_t)(v >> (8 * i));
57void ua_w_i32(UaWriter *w, int32_t v)
59 ua_w_u32(w, (uint32_t)v);
61void ua_w_f32(UaWriter *w,
float v)
67void ua_w_f64(UaWriter *w,
double v)
73void ua_w_bool(UaWriter *w,
bool v)
75 ua_w_u8(w, v ? 1 : 0);
77void ua_w_string(UaWriter *w,
const char *s, int32_t len)
81 w_bytes(w, s, (
size_t)len);
84static bool r_take(UaReader *r,
void *dst,
size_t n)
86 if (r->err || r->off + n > r->len)
91 memcpy(dst, r->p + r->off, n);
96uint8_t ua_r_u8(UaReader *r)
102uint16_t ua_r_u16(UaReader *r)
104 uint8_t b[2] = {0, 0};
106 return (uint16_t)(b[0] | (b[1] << 8));
108uint32_t ua_r_u32(UaReader *r)
110 uint8_t b[4] = {0, 0, 0, 0};
112 return (uint32_t)b[0] | ((uint32_t)b[1] << 8) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 24);
114uint64_t ua_r_u64(UaReader *r)
117 if (!r_take(r, b, 8))
120 for (
int i = 0; i < 8; i++)
121 v |= (uint64_t)b[i] << (8 * i);
124int32_t ua_r_i32(UaReader *r)
126 return (int32_t)ua_r_u32(r);
128float ua_r_f32(UaReader *r)
130 uint32_t u = ua_r_u32(r);
135double ua_r_f64(UaReader *r)
137 uint64_t u = ua_r_u64(r);
142bool ua_r_bool(UaReader *r)
144 return ua_r_u8(r) != 0;
146bool ua_r_string(UaReader *r,
char *out,
size_t cap, int32_t *out_len)
148 int32_t len = ua_r_i32(r);
159 if ((
size_t)len + 1 > cap || r->off + (
size_t)len > r->len)
164 memcpy(out, r->p + r->off, (
size_t)len);
166 r->off += (size_t)len;
170static void r_skip(UaReader *r,
size_t n)
172 if (r->err || r->off + n > r->len)
183void ua_w_nodeid_numeric(UaWriter *w, uint16_t ns, uint32_t
id)
185 if (ns == 0 &&
id <= 0xFF)
188 ua_w_u8(w, (uint8_t)
id);
190 else if (ns <= 0xFF &&
id <= 0xFFFF)
193 ua_w_u8(w, (uint8_t)ns);
194 ua_w_u16(w, (uint16_t)
id);
204bool ua_r_nodeid(UaReader *r, UaNodeId *out)
206 uint8_t enc = ua_r_u8(r);
207 uint8_t kind = enc & 0x0F;
214 out->id = ua_r_u8(r);
217 out->ns = ua_r_u8(r);
218 out->id = ua_r_u16(r);
221 out->ns = ua_r_u16(r);
222 out->id = ua_r_u32(r);
227 out->ns = ua_r_u16(r);
228 out->numeric =
false;
229 int32_t l = ua_r_i32(r);
231 r_skip(r, (
size_t)l);
235 out->ns = ua_r_u16(r);
236 out->numeric =
false;
245 int32_t l = ua_r_i32(r);
247 r_skip(r, (
size_t)l);
255static bool r_ext_object_skip(UaReader *r)
258 if (!ua_r_nodeid(r, &tid))
260 uint8_t body_enc = ua_r_u8(r);
261 if (body_enc == 0x00)
263 int32_t l = ua_r_i32(r);
265 r_skip(r, (
size_t)l);
272static bool r_request_header(UaReader *r, uint32_t *request_handle)
275 ua_r_nodeid(r, &auth);
277 uint32_t rh = ua_r_u32(r);
279 *request_handle = rh;
281 int32_t aid = ua_r_i32(r);
283 r_skip(r, (
size_t)aid);
285 return r_ext_object_skip(r);
290static bool r_msg_preamble(
const uint8_t *msg,
size_t len, UaReader *r, OpcUaMsg *m)
293 if (!opcua_parse_header(msg, len, &h) || memcmp(h.type,
"MSG", 3) != 0)
298 UaReader rr = {msg + 8, len - 8, 0,
false};
300 m->secure_channel_id = ua_r_u32(r);
301 m->token_id = ua_r_u32(r);
302 m->sequence_number = ua_r_u32(r);
303 m->request_id = ua_r_u32(r);
306 if (!ua_r_nodeid(r, &tid))
308 m->type_id = tid.numeric ? tid.id : 0;
309 return r_request_header(r, &m->request_handle);
312int64_t opcua_filetime_from_unix(int64_t unix_seconds)
314 if (unix_seconds <= 0)
316 return (unix_seconds + 11644473600LL) * 10000000LL;
322bool opcua_parse_header(
const uint8_t *buf,
size_t len, UaMsgHeader *h)
324 if (!buf || len < 8 || !h)
326 h->type[0] = (char)buf[0];
327 h->type[1] = (char)buf[1];
328 h->type[2] = (char)buf[2];
329 h->chunk = (char)buf[3];
330 h->size = (uint32_t)buf[4] | ((uint32_t)buf[5] << 8) | ((uint32_t)buf[6] << 16) | ((uint32_t)buf[7] << 24);
334bool opcua_parse_hello(
const uint8_t *msg,
size_t len, OpcUaHello *out)
337 if (!opcua_parse_header(msg, len, &h) || memcmp(h.type,
"HEL", 3) != 0)
339 if (h.size != len || h.size < 8 + 20)
341 UaReader r = {msg + 8, len - 8, 0,
false};
342 out->protocol_version = ua_r_u32(&r);
343 out->recv_buf_size = ua_r_u32(&r);
344 out->send_buf_size = ua_r_u32(&r);
345 out->max_msg_size = ua_r_u32(&r);
346 out->max_chunk_count = ua_r_u32(&r);
350static uint32_t neg(uint32_t client, uint32_t server)
354 return client < server ? client : server;
357size_t opcua_build_ack(
const OpcUaHello *hello, uint8_t *out,
size_t cap)
361 const uint32_t total = 8 + 20;
362 UaWriter w = {out, cap, 0,
true};
369 ua_w_u32(&w, neg(hello->send_buf_size, DETWS_OPCUA_BUF));
370 ua_w_u32(&w, neg(hello->recv_buf_size, DETWS_OPCUA_BUF));
371 ua_w_u32(&w, neg(hello->max_msg_size, DETWS_OPCUA_BUF));
373 return w.ok ? w.n : 0;
379bool opcua_parse_open(
const uint8_t *msg,
size_t len, OpcUaOpenChannel *out)
382 if (!opcua_parse_header(msg, len, &h) || memcmp(h.type,
"OPN", 3) != 0)
387 UaReader r = {msg + 8, len - 8, 0,
false};
390 out->secure_channel_id = ua_r_u32(&r);
391 int32_t pol = ua_r_i32(&r);
393 r_skip(&r, (
size_t)pol);
394 int32_t sc = ua_r_i32(&r);
396 r_skip(&r, (
size_t)sc);
397 int32_t rt = ua_r_i32(&r);
399 r_skip(&r, (
size_t)rt);
402 out->sequence_number = ua_r_u32(&r);
403 out->request_id = ua_r_u32(&r);
407 if (!ua_r_nodeid(&r, &tid))
409 if (!(tid.numeric && tid.ns == 0 && tid.id == OPCUA_ID_OPEN_REQ))
413 if (!r_request_header(&r, &out->request_handle))
417 out->client_protocol_version = ua_r_u32(&r);
418 out->security_token_request_type = ua_r_u32(&r);
419 out->message_security_mode = ua_r_u32(&r);
420 int32_t nonce = ua_r_i32(&r);
422 r_skip(&r, (
size_t)nonce);
423 out->requested_lifetime = ua_r_u32(&r);
427size_t opcua_build_open_response(
const OpcUaOpenChannel *req, uint32_t channel_id, uint32_t token_id,
428 uint32_t seq_number, int64_t now_ft, uint32_t lifetime, uint8_t *out,
size_t cap)
432 UaWriter w = {out, cap, 0,
true};
442 ua_w_u32(&w, channel_id);
443 ua_w_string(&w, OPCUA_POLICY_NONE_URI, (int32_t)(
sizeof(OPCUA_POLICY_NONE_URI) - 1));
444 ua_w_string(&w,
nullptr, -1);
445 ua_w_string(&w,
nullptr, -1);
448 ua_w_u32(&w, seq_number);
449 ua_w_u32(&w, req->request_id);
452 ua_w_nodeid_numeric(&w, 0, OPCUA_ID_OPEN_RESP);
455 ua_w_u64(&w, (uint64_t)now_ft);
456 ua_w_u32(&w, req->request_handle);
460 ua_w_nodeid_numeric(&w, 0, 0);
465 ua_w_u32(&w, channel_id);
466 ua_w_u32(&w, token_id);
467 ua_w_u64(&w, (uint64_t)now_ft);
468 ua_w_u32(&w, lifetime);
469 ua_w_string(&w,
nullptr, -1);
473 out[4] = (uint8_t)w.n;
474 out[5] = (uint8_t)(w.n >> 8);
475 out[6] = (uint8_t)(w.n >> 16);
476 out[7] = (uint8_t)(w.n >> 24);
485static size_t patch_size(UaWriter *w)
489 w->o[4] = (uint8_t)w->n;
490 w->o[5] = (uint8_t)(w->n >> 8);
491 w->o[6] = (uint8_t)(w->n >> 16);
492 w->o[7] = (uint8_t)(w->n >> 24);
498static void w_msg_prefix(UaWriter *w, uint32_t channel_id, uint32_t token_id, uint32_t seq, uint32_t request_id)
505 ua_w_u32(w, channel_id);
506 ua_w_u32(w, token_id);
508 ua_w_u32(w, request_id);
512static void w_response_header(UaWriter *w, int64_t now_ft, uint32_t request_handle, uint32_t service_result)
514 ua_w_u64(w, (uint64_t)now_ft);
515 ua_w_u32(w, request_handle);
516 ua_w_u32(w, service_result);
519 ua_w_nodeid_numeric(w, 0, 0);
523bool opcua_parse_msg(
const uint8_t *msg,
size_t len, OpcUaMsg *out)
526 if (!opcua_parse_header(msg, len, &h) || memcmp(h.type,
"MSG", 3) != 0)
531 UaReader r = {msg + 8, len - 8, 0,
false};
532 out->secure_channel_id = ua_r_u32(&r);
533 out->token_id = ua_r_u32(&r);
534 out->sequence_number = ua_r_u32(&r);
535 out->request_id = ua_r_u32(&r);
538 if (!ua_r_nodeid(&r, &tid))
540 out->type_id = tid.numeric ? tid.id : 0;
542 return r_request_header(&r, &out->request_handle);
547static const char OPCUA_TRANSPORT_URI[] =
548 "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary";
560 OpcUaServerInfo server_info = {DETWS_OPCUA_DEFAULT_ENDPOINT, DETWS_OPCUA_DEFAULT_APP_URI,
561 DETWS_OPCUA_DEFAULT_APP_NAME};
562 OpcUaReadHandler read_handler =
nullptr;
563 OpcUaWriteHandler write_handler =
nullptr;
564 OpcUaBrowseHandler browse_handler =
nullptr;
566 uint8_t msg[DETWS_OPCUA_BUF];
568 uint32_t channel_id = 0;
569 uint32_t token_id = 0;
571 uint32_t session_id = 0;
572 uint32_t auth_token = 0;
575static OpcuaCtx s_opcua;
577void opcua_set_endpoint_url(
const char *url)
579 s_opcua.server_info.endpoint_url = url;
582void ua_w_endpoint_description(UaWriter *w,
const OpcUaServerInfo *info)
584 const char *url = (info && info->endpoint_url) ? info->endpoint_url : DETWS_OPCUA_DEFAULT_ENDPOINT;
585 const char *auri = (info && info->application_uri) ? info->application_uri : DETWS_OPCUA_DEFAULT_APP_URI;
586 const char *aname = (info && info->application_name) ? info->application_name : DETWS_OPCUA_DEFAULT_APP_NAME;
588 ua_w_string(w, url, (int32_t)strnlen(url, w->cap));
590 ua_w_string(w, auri, (int32_t)strnlen(auri, w->cap));
591 ua_w_string(w,
"urn:det:opcua", 13);
592 ua_w_localizedtext(w,
nullptr, aname);
594 ua_w_string(w,
nullptr, -1);
595 ua_w_string(w,
nullptr, -1);
597 ua_w_string(w,
nullptr, -1);
599 ua_w_string(w, OPCUA_POLICY_NONE_URI, (int32_t)(
sizeof(OPCUA_POLICY_NONE_URI) - 1));
602 ua_w_string(w,
"anonymous", 9);
604 ua_w_string(w,
nullptr, -1);
605 ua_w_string(w,
nullptr, -1);
606 ua_w_string(w,
nullptr, -1);
607 ua_w_string(w, OPCUA_TRANSPORT_URI, (int32_t)(
sizeof(OPCUA_TRANSPORT_URI) - 1));
611size_t opcua_build_create_session_response(
const OpcUaMsg *req, uint32_t session_id, uint32_t auth_token,
612 double revised_timeout,
const OpcUaServerInfo *info, uint32_t seq,
613 int64_t now_ft, uint8_t *out,
size_t cap)
617 UaWriter w = {out, cap, 0,
true};
618 w_msg_prefix(&w, req->secure_channel_id, req->token_id, seq, req->request_id);
619 ua_w_nodeid_numeric(&w, 0, OPCUA_ID_CREATE_SESSION_RESP);
620 w_response_header(&w, now_ft, req->request_handle, 0);
622 ua_w_nodeid_numeric(&w, 1, session_id);
623 ua_w_nodeid_numeric(&w, 1, auth_token);
624 ua_w_f64(&w, revised_timeout);
625 ua_w_string(&w,
nullptr, -1);
626 ua_w_string(&w,
nullptr, -1);
628 ua_w_endpoint_description(&w, info);
630 ua_w_string(&w,
nullptr, -1);
631 ua_w_string(&w,
nullptr, -1);
633 return patch_size(&w);
636size_t opcua_build_get_endpoints_response(
const OpcUaMsg *req,
const OpcUaServerInfo *info, uint32_t seq,
637 int64_t now_ft, uint8_t *out,
size_t cap)
641 UaWriter w = {out, cap, 0,
true};
642 w_msg_prefix(&w, req->secure_channel_id, req->token_id, seq, req->request_id);
643 ua_w_nodeid_numeric(&w, 0, OPCUA_ID_GET_ENDPOINTS_RESP);
644 w_response_header(&w, now_ft, req->request_handle, 0);
646 ua_w_endpoint_description(&w, info);
647 return patch_size(&w);
650size_t opcua_build_service_fault(
const OpcUaMsg *req, uint32_t service_result, uint32_t seq, int64_t now_ft,
651 uint8_t *out,
size_t cap)
655 UaWriter w = {out, cap, 0,
true};
656 w_msg_prefix(&w, req->secure_channel_id, req->token_id, seq, req->request_id);
657 ua_w_nodeid_numeric(&w, 0, OPCUA_ID_SERVICE_FAULT);
658 w_response_header(&w, now_ft, req->request_handle, service_result);
659 return patch_size(&w);
662size_t opcua_build_activate_session_response(
const OpcUaMsg *req, uint32_t seq, int64_t now_ft, uint8_t *out,
667 UaWriter w = {out, cap, 0,
true};
668 w_msg_prefix(&w, req->secure_channel_id, req->token_id, seq, req->request_id);
669 ua_w_nodeid_numeric(&w, 0, OPCUA_ID_ACTIVATE_SESSION_RESP);
670 w_response_header(&w, now_ft, req->request_handle, 0);
672 ua_w_string(&w,
nullptr, -1);
675 return patch_size(&w);
681void ua_w_variant(UaWriter *w,
const OpcUaVariant *v)
683 if (!v || v->type == OpcUaVariantType::OPCUA_VAR_NULL)
685 ua_w_u8(w, (uint8_t)OpcUaVariantType::OPCUA_VAR_NULL);
688 ua_w_u8(w, (uint8_t)v->type);
691 case OpcUaVariantType::OPCUA_VAR_BOOL:
694 case OpcUaVariantType::OPCUA_VAR_INT32:
697 case OpcUaVariantType::OPCUA_VAR_UINT32:
700 case OpcUaVariantType::OPCUA_VAR_FLOAT:
703 case OpcUaVariantType::OPCUA_VAR_DOUBLE:
706 case OpcUaVariantType::OPCUA_VAR_STRING:
707 ua_w_string(w, v->str, v->str_len);
715void ua_w_datavalue(UaWriter *w,
const OpcUaVariant *v, uint32_t status)
717 bool has_value = v && v->type != OpcUaVariantType::OPCUA_VAR_NULL;
721 if (status != OPCUA_STATUS_GOOD)
726 if (status != OPCUA_STATUS_GOOD)
730bool ua_r_variant(UaReader *r, OpcUaVariant *out)
732 memset(out, 0,
sizeof(*out));
733 uint8_t enc = ua_r_u8(r);
739 out->type = (OpcUaVariantType)(enc & 0x3F);
742 case OpcUaVariantType::OPCUA_VAR_NULL:
744 case OpcUaVariantType::OPCUA_VAR_BOOL:
745 out->b = ua_r_bool(r);
747 case OpcUaVariantType::OPCUA_VAR_INT32:
748 out->i32 = ua_r_i32(r);
750 case OpcUaVariantType::OPCUA_VAR_UINT32:
751 out->u32 = ua_r_u32(r);
753 case OpcUaVariantType::OPCUA_VAR_FLOAT:
754 out->f32 = ua_r_f32(r);
756 case OpcUaVariantType::OPCUA_VAR_DOUBLE:
757 out->f64 = ua_r_f64(r);
759 case OpcUaVariantType::OPCUA_VAR_STRING: {
760 int32_t sl = ua_r_i32(r);
764 if (r->off + (
size_t)sl > r->len)
769 out->str = (
const char *)(r->p + r->off);
770 r->off += (size_t)sl;
781bool ua_r_datavalue(UaReader *r, OpcUaVariant *out_value, uint32_t *out_status)
783 memset(out_value, 0,
sizeof(*out_value));
785 *out_status = OPCUA_STATUS_GOOD;
786 uint8_t mask = ua_r_u8(r);
789 if (!ua_r_variant(r, out_value))
794 uint32_t st = ua_r_u32(r);
809bool opcua_parse_read(
const uint8_t *msg,
size_t len, OpcUaReadRequest *out)
812 if (!r_msg_preamble(msg, len, &r, &out->msg))
818 int32_t cnt = ua_r_i32(&r);
819 out->total = (cnt < 0) ? 0 : (uint32_t)cnt;
821 for (int32_t i = 0; i < cnt; i++)
824 if (!ua_r_nodeid(&r, &nid))
826 uint32_t attr = ua_r_u32(&r);
827 int32_t ir = ua_r_i32(&r);
829 r_skip(&r, (
size_t)ir);
831 int32_t qn = ua_r_i32(&r);
833 r_skip(&r, (
size_t)qn);
834 if (out->count < DETWS_OPCUA_READ_MAX)
836 OpcUaReadItem *it = &out->items[out->count++];
839 it->numeric = nid.numeric;
840 it->attribute = attr;
846size_t opcua_build_read_response(
const OpcUaReadRequest *req,
const OpcUaVariant *values,
const uint32_t *statuses,
847 uint32_t seq, int64_t now_ft, uint8_t *out,
size_t cap)
851 UaWriter w = {out, cap, 0,
true};
852 w_msg_prefix(&w, req->msg.secure_channel_id, req->msg.token_id, seq, req->msg.request_id);
853 ua_w_nodeid_numeric(&w, 0, OPCUA_ID_READ_RESP);
854 w_response_header(&w, now_ft, req->msg.request_handle, 0);
856 ua_w_i32(&w, (int32_t)req->count);
857 for (uint32_t i = 0; i < req->count; i++)
858 ua_w_datavalue(&w, values ? &values[i] : nullptr, statuses ? statuses[i] : OPCUA_STATUS_GOOD);
860 return patch_size(&w);
864void opcua_set_read_handler(OpcUaReadHandler fn)
866 s_opcua.read_handler = fn;
872bool opcua_parse_write(
const uint8_t *msg,
size_t len, OpcUaWriteRequest *out)
875 if (!r_msg_preamble(msg, len, &r, &out->msg))
878 int32_t cnt = ua_r_i32(&r);
879 out->total = (cnt < 0) ? 0 : (uint32_t)cnt;
881 for (int32_t i = 0; i < cnt; i++)
884 if (!ua_r_nodeid(&r, &nid))
886 uint32_t attr = ua_r_u32(&r);
887 int32_t ir = ua_r_i32(&r);
889 r_skip(&r, (
size_t)ir);
891 if (!ua_r_datavalue(&r, &val,
nullptr))
893 if (out->count < DETWS_OPCUA_WRITE_MAX)
895 OpcUaWriteItem *it = &out->items[out->count++];
898 it->numeric = nid.numeric;
899 it->attribute = attr;
906size_t opcua_build_write_response(
const OpcUaWriteRequest *req,
const uint32_t *results, uint32_t seq, int64_t now_ft,
907 uint8_t *out,
size_t cap)
911 UaWriter w = {out, cap, 0,
true};
912 w_msg_prefix(&w, req->msg.secure_channel_id, req->msg.token_id, seq, req->msg.request_id);
913 ua_w_nodeid_numeric(&w, 0, OPCUA_ID_WRITE_RESP);
914 w_response_header(&w, now_ft, req->msg.request_handle, 0);
916 ua_w_i32(&w, (int32_t)req->count);
917 for (uint32_t i = 0; i < req->count; i++)
918 ua_w_u32(&w, results ? results[i] : OPCUA_STATUS_GOOD);
920 return patch_size(&w);
924void opcua_set_write_handler(OpcUaWriteHandler fn)
926 s_opcua.write_handler = fn;
932void ua_w_qualifiedname(UaWriter *w, uint16_t ns,
const char *name)
935 ua_w_string(w, name, name ? (int32_t)strnlen(name, w->cap) : -1);
938void ua_w_localizedtext(UaWriter *w,
const char *locale,
const char *text)
947 ua_w_string(w, locale, (int32_t)strnlen(locale, w->cap));
949 ua_w_string(w, text, (int32_t)strnlen(text, w->cap));
952void ua_w_reference(UaWriter *w,
const OpcUaReference *ref)
959 ua_w_nodeid_numeric(w, 0, ref->ref_type_id);
960 ua_w_bool(w, ref->is_forward);
961 ua_w_nodeid_numeric(w, ref->target_ns, ref->target_id);
962 ua_w_qualifiedname(w, ref->browse_name_ns, ref->browse_name);
963 ua_w_localizedtext(w,
nullptr, ref->display_name);
964 ua_w_u32(w, ref->node_class);
965 ua_w_nodeid_numeric(w, 0, ref->type_def_id);
968bool opcua_parse_browse(
const uint8_t *msg,
size_t len, OpcUaBrowseRequest *out)
971 if (!r_msg_preamble(msg, len, &r, &out->msg))
976 ua_r_nodeid(&r, &view);
981 int32_t cnt = ua_r_i32(&r);
982 out->total = (cnt < 0) ? 0 : (uint32_t)cnt;
984 for (int32_t i = 0; i < cnt; i++)
987 if (!ua_r_nodeid(&r, &nid))
991 ua_r_nodeid(&r, &rt);
995 if (out->count < DETWS_OPCUA_BROWSE_MAX)
997 OpcUaBrowseItem *it = &out->items[out->count++];
1000 it->numeric = nid.numeric;
1006size_t opcua_build_browse_response(
const OpcUaBrowseRequest *req, OpcUaBrowseHandler handler, uint32_t seq,
1007 int64_t now_ft, uint8_t *out,
size_t cap)
1011 UaWriter w = {out, cap, 0,
true};
1012 w_msg_prefix(&w, req->msg.secure_channel_id, req->msg.token_id, seq, req->msg.request_id);
1013 ua_w_nodeid_numeric(&w, 0, OPCUA_ID_BROWSE_RESP);
1014 w_response_header(&w, now_ft, req->msg.request_handle, 0);
1016 ua_w_i32(&w, (int32_t)req->count);
1017 for (uint32_t i = 0; i < req->count; i++)
1019 OpcUaReference refs[DETWS_OPCUA_REF_MAX];
1020 int32_t n = handler ? handler(req->items[i].ns, req->items[i].id, refs, DETWS_OPCUA_REF_MAX) : -1;
1021 uint32_t status = (n < 0) ? OPCUA_STATUS_BAD_NODE_ID_UNKNOWN : OPCUA_STATUS_GOOD;
1022 uint32_t nrefs = (n < 0) ? 0 : (uint32_t)n;
1025 ua_w_u32(&w, status);
1026 ua_w_string(&w,
nullptr, -1);
1027 ua_w_i32(&w, (int32_t)nrefs);
1028 for (uint32_t j = 0; j < nrefs; j++)
1029 ua_w_reference(&w, &refs[j]);
1032 return patch_size(&w);
1035size_t opcua_build_close_session_response(
const OpcUaMsg *req, uint32_t seq, int64_t now_ft, uint8_t *out,
size_t cap)
1039 UaWriter w = {out, cap, 0,
true};
1040 w_msg_prefix(&w, req->secure_channel_id, req->token_id, seq, req->request_id);
1041 ua_w_nodeid_numeric(&w, 0, OPCUA_ID_CLOSE_SESSION_RESP);
1042 w_response_header(&w, now_ft, req->request_handle, 0);
1043 return patch_size(&w);
1047void opcua_set_browse_handler(OpcUaBrowseHandler fn)
1049 s_opcua.browse_handler = fn;
1065size_t ring_avail(
const TcpConn *c)
1067 return det_conn_available(c->
id);
1069void ring_peek(
const TcpConn *c,
size_t off, uint8_t *dst,
size_t n)
1071 det_conn_peek(c->
id, off, dst, n);
1073void ring_consume(
TcpConn *c,
size_t n)
1075 det_conn_consume(c->
id, n);
1077void raw_send(uint8_t slot,
const void *data,
size_t n)
1079 if (!det_conn_active(slot) || n == 0)
1084void close_conn(uint8_t slot)
1091void opcua_rx(uint8_t slot)
1093 if (!det_conn_active(slot))
1101 if (ring_avail(c) < 8)
1105 ring_peek(c, 0, hdr, 8);
1107 if (!opcua_parse_header(hdr, 8, &h) || h.size < 8 || h.size >
sizeof(s_opcua.msg))
1112 if (ring_avail(c) < h.size)
1115 ring_peek(c, 0, s_opcua.msg, h.size);
1116 ring_consume(c, h.size);
1118 if (memcmp(h.type,
"HEL", 3) == 0)
1122 if (opcua_parse_hello(s_opcua.msg, h.size, &hello) &&
1123 (n = opcua_build_ack(&hello, s_opcua.resp,
sizeof(s_opcua.resp))) > 0)
1124 raw_send(slot, s_opcua.resp, n);
1131 else if (memcmp(h.type,
"OPN", 3) == 0)
1133 OpcUaOpenChannel oc;
1134 if (!opcua_parse_open(s_opcua.msg, h.size, &oc))
1139 if (oc.secure_channel_id == 0)
1140 oc.secure_channel_id = ++s_opcua.channel_id;
1141 uint32_t token = ++s_opcua.token_id;
1142 uint32_t seq = ++s_opcua.seq;
1143 uint32_t lifetime = oc.requested_lifetime ? oc.requested_lifetime : 3600000u;
1144 int64_t now = opcua_filetime_from_unix((int64_t)time(
nullptr));
1145 size_t n = opcua_build_open_response(&oc, oc.secure_channel_id, token, seq, now, lifetime, s_opcua.resp,
1146 sizeof(s_opcua.resp));
1148 raw_send(slot, s_opcua.resp, n);
1155 else if (memcmp(h.type,
"MSG", 3) == 0)
1158 if (!opcua_parse_msg(s_opcua.msg, h.size, &m))
1163 int64_t now = opcua_filetime_from_unix((int64_t)time(
nullptr));
1164 uint32_t seq = ++s_opcua.seq;
1166 if (m.type_id == OPCUA_ID_GET_ENDPOINTS_REQ)
1167 n = opcua_build_get_endpoints_response(&m, &s_opcua.server_info, seq, now, s_opcua.resp,
1168 sizeof(s_opcua.resp));
1169 else if (m.type_id == OPCUA_ID_CREATE_SESSION_REQ)
1170 n = opcua_build_create_session_response(&m, ++s_opcua.session_id, ++s_opcua.auth_token, 1200000.0,
1171 &s_opcua.server_info, seq, now, s_opcua.resp,
1172 sizeof(s_opcua.resp));
1173 else if (m.type_id == OPCUA_ID_ACTIVATE_SESSION_REQ)
1174 n = opcua_build_activate_session_response(&m, seq, now, s_opcua.resp,
sizeof(s_opcua.resp));
1175 else if (m.type_id == OPCUA_ID_READ_REQ)
1177 OpcUaReadRequest rr;
1178 if (!opcua_parse_read(s_opcua.msg, h.size, &rr))
1183 OpcUaVariant vals[DETWS_OPCUA_READ_MAX];
1184 uint32_t sts[DETWS_OPCUA_READ_MAX];
1185 for (uint32_t i = 0; i < rr.count; i++)
1187 memset(&vals[i], 0,
sizeof(vals[i]));
1188 bool ok = s_opcua.read_handler &&
1189 s_opcua.read_handler(rr.items[i].ns, rr.items[i].id, rr.items[i].attribute, &vals[i]);
1190 sts[i] = ok ? OPCUA_STATUS_GOOD : OPCUA_STATUS_BAD_NODE_ID_UNKNOWN;
1192 n = opcua_build_read_response(&rr, vals, sts, seq, now, s_opcua.resp,
sizeof(s_opcua.resp));
1194 else if (m.type_id == OPCUA_ID_BROWSE_REQ)
1196 OpcUaBrowseRequest br;
1197 if (!opcua_parse_browse(s_opcua.msg, h.size, &br))
1202 n = opcua_build_browse_response(&br, s_opcua.browse_handler, seq, now, s_opcua.resp,
1203 sizeof(s_opcua.resp));
1205 else if (m.type_id == OPCUA_ID_WRITE_REQ)
1207 OpcUaWriteRequest wr;
1208 if (!opcua_parse_write(s_opcua.msg, h.size, &wr))
1213 uint32_t res[DETWS_OPCUA_WRITE_MAX];
1214 for (uint32_t i = 0; i < wr.count; i++)
1215 res[i] = s_opcua.write_handler ? s_opcua.write_handler(wr.items[i].ns, wr.items[i].id,
1216 wr.items[i].attribute, &wr.items[i].value)
1217 : OPCUA_STATUS_BAD_NODE_ID_UNKNOWN;
1218 n = opcua_build_write_response(&wr, res, seq, now, s_opcua.resp,
sizeof(s_opcua.resp));
1220 else if (m.type_id == OPCUA_ID_CLOSE_SESSION_REQ)
1221 n = opcua_build_close_session_response(&m, seq, now, s_opcua.resp,
sizeof(s_opcua.resp));
1223 n = opcua_build_service_fault(&m, OPCUA_STATUS_BAD_SERVICE_UNSUPPORTED, seq, now, s_opcua.resp,
1224 sizeof(s_opcua.resp));
1226 raw_send(slot, s_opcua.resp, n);
1228 else if (memcmp(h.type,
"CLO", 3) == 0)
1239static const ProtoHandler s_opcua_handler = {
nullptr, opcua_rx,
nullptr,
nullptr};
1242 return &s_opcua_handler;
1247void opcua_rx(uint8_t slot)
OPC UA Binary server: handshake + SecureChannel + Session + Read/Write + Browse (DETWS_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).
bool det_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
void det_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
void det_conn_close(uint8_t slot)
Close connection slot gracefully (tcp_close), aborting if the FIN cannot be queued....
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.