From 5f82ee5f200f7aaf020f666bb7e4ef5776458b62 Mon Sep 17 00:00:00 2001 From: alde103 Date: Tue, 16 Jun 2020 19:21:21 -0500 Subject: [PATCH 1/2] test free of unexpected memory leaks (Valgrind) --- lib/opc_ua/client.ex | 51 +++++----- lib/opc_ua/common.ex | 41 +++++++- lib/opc_ua/server.ex | 43 +++++---- src/CMakeLists.txt | 2 +- src/common.c | 126 +++++++++++++++++-------- src/opc_ua_client.c | 3 + src/opc_ua_server.c | 99 +++++++++++++++---- test/client_tests/arrays_test.exs | 60 +++++++++++- test/client_tests/terraform_test.exs | 10 +- test/server_tests/discovery_test.exs | 16 ++++ test/server_tests/lifecycle_test.exs | 6 +- test/server_tests/refactor_test.exs | 2 +- test/server_tests/terraform_test.exs | 10 +- test/server_tests/write_event_test.exs | 9 +- 14 files changed, 356 insertions(+), 122 deletions(-) diff --git a/lib/opc_ua/client.ex b/lib/opc_ua/client.ex index 1f04867..f29b523 100644 --- a/lib/opc_ua/client.ex +++ b/lib/opc_ua/client.ex @@ -128,6 +128,7 @@ defmodule OpcUA.Client do quote location: :keep, bind_quoted: [opts: opts] do use GenServer, Keyword.drop(opts, [:configuration]) @behaviour OpcUA.Client + @mix_env Mix.env() alias __MODULE__ @@ -149,7 +150,7 @@ defmodule OpcUA.Client do configuration = apply(__MODULE__, :configuration, [user_initial_params]) monitored_items = apply(__MODULE__, :monitored_items, [user_initial_params]) - OpcUA.Client.set_config(c_pid) + #OpcUA.Client.set_config(c_pid) # configutation = [config: list(), connection: list()] set_client_config(c_pid, configuration, :config) @@ -253,7 +254,12 @@ defmodule OpcUA.Client do config_params = Keyword.get(configuration, type, []) Enum.each(config_params, fn config_param -> - GenServer.call(c_pid, {type, config_param}) + if(@mix_env != :test) do + GenServer.call(c_pid, {type, config_param}) + else + # Valgrind + GenServer.call(c_pid, {type, config_param}, :infinity) + end end) end @@ -340,7 +346,12 @@ defmodule OpcUA.Client do """ @spec connect_by_url(GenServer.server(), list()) :: :ok | {:error, term} | {:error, :einval} def connect_by_url(pid, args) when is_list(args) do - GenServer.call(pid, {:conn, {:by_url, args}}) + if(@mix_env != :test) do + GenServer.call(pid, {:conn, {:by_url, args}}) + else + # Valgrind + GenServer.call(pid, {:conn, {:by_url, args}}, :infinity) + end end @doc """ @@ -353,7 +364,12 @@ defmodule OpcUA.Client do @spec connect_by_username(GenServer.server(), list()) :: :ok | {:error, term} | {:error, :einval} def connect_by_username(pid, args) when is_list(args) do - GenServer.call(pid, {:conn, {:by_username, args}}) + if(@mix_env != :test) do + GenServer.call(pid, {:conn, {:by_username, args}}) + else + # Valgrind + GenServer.call(pid, {:conn, {:by_username, args}}, :infinity) + end end @doc """ @@ -575,32 +591,7 @@ defmodule OpcUA.Client do executable = lib_dir <> "/opc_ua_client" - port = - Port.open({:spawn_executable, to_charlist(executable)}, [ - {:args, []}, - {:packet, 2}, - :use_stdio, - :binary, - :exit_status - ]) - - # # Valgrind - # port = - # Port.open({:spawn_executable, to_charlist("/usr/bin/valgrind.bin")}, [ - # {:args, - # [ - # "-q", - # "--undef-value-errors=no", - # "--leak-check=full", - # "--show-leak-kinds=all", - # # "--track-origins=yes", - # executable - # ]}, - # {:packet, 2}, - # :use_stdio, - # :binary, - # :exit_status - # ]) + port = open_port(executable, use_valgrind?()) state = %State{port: port, controlling_process: controlling_process} {:ok, state} diff --git a/lib/opc_ua/common.ex b/lib/opc_ua/common.ex index 2bdf3f9..48c70e7 100644 --- a/lib/opc_ua/common.ex +++ b/lib/opc_ua/common.ex @@ -14,6 +14,8 @@ defmodule OpcUA.Common do @c_timeout 5000 + @mix_env Mix.env() + defmodule State do @moduledoc false @@ -364,7 +366,12 @@ defmodule OpcUA.Common do @spec read_node_value(GenServer.server(), %NodeId{}, integer()) :: {:ok, term()} | {:error, binary()} | {:error, :einval} def read_node_value(pid, node_id, index \\ 0) do - GenServer.call(pid, {:read, {:value, {node_id, index}}}) + if(@mix_env != :test) do + GenServer.call(pid, {:read, {:value, {node_id, index}}}) + else + # Valgrind + GenServer.call(pid, {:read, {:value, {node_id, index}}}, :infinity) + end end @doc """ @@ -848,6 +855,38 @@ defmodule OpcUA.Common do state end + defp use_valgrind?(), do: System.get_env("USE_VALGRIND", "false") + + defp open_port(executable, "false") do + Port.open({:spawn_executable, to_charlist(executable)}, [ + {:args, []}, + {:packet, 2}, + :use_stdio, + :binary, + :exit_status + ]) + end + + defp open_port(executable, valgrind_env) do + Logger.warn("(#{__MODULE__}) Valgrind Activated: #{inspect valgrind_env}") + Port.open({:spawn_executable, to_charlist("/usr/bin/valgrind.bin")}, [ + {:args, + [ + "-q", + "--undef-value-errors=no", + "--leak-check=full", + "--show-leak-kinds=all", + #"--verbose", + #"--track-origins=yes", + executable + ]}, + {:packet, 2}, + :use_stdio, + :binary, + :exit_status + ]) + end + defp call_port(state, command, caller, arguments) do msg = {command, caller, arguments} send(state.port, {self(), {:command, :erlang.term_to_binary(msg)}}) diff --git a/lib/opc_ua/server.ex b/lib/opc_ua/server.ex index 0547275..28c22e2 100644 --- a/lib/opc_ua/server.ex +++ b/lib/opc_ua/server.ex @@ -95,6 +95,7 @@ defmodule OpcUA.Server do quote location: :keep, bind_quoted: [opts: opts] do use GenServer, Keyword.drop(opts, [:configuration]) @behaviour OpcUA.Server + @mix_env Mix.env() alias __MODULE__ @@ -247,7 +248,12 @@ defmodule OpcUA.Server do """ @spec set_default_config(GenServer.server()) :: :ok | {:error, binary()} | {:error, :einval} def set_default_config(pid) do - GenServer.call(pid, {:config, {:set_default_server_config, nil}}) + if(@mix_env != :test) do + GenServer.call(pid, {:config, {:set_default_server_config, nil}}) + else + # Valgrind + GenServer.call(pid, {:config, {:set_default_server_config, nil}}, :infinity) + end end @doc """ @@ -263,7 +269,12 @@ defmodule OpcUA.Server do """ @spec set_port(GenServer.server(), integer()) :: :ok | {:error, binary()} | {:error, :einval} def set_port(pid, port) when is_integer(port) do - GenServer.call(pid, {:config, {:port, port}}) + if(@mix_env != :test) do + GenServer.call(pid, {:config, {:port, port}}) + else + # Valgrind + GenServer.call(pid, {:config, {:port, port}}, :infinity) + end end @doc """ @@ -322,7 +333,12 @@ defmodule OpcUA.Server do """ @spec discovery_register(GenServer.server(), list()) :: :ok | {:error, binary()} | {:error, :einval} def discovery_register(pid, args) when is_list(args) do - GenServer.call(pid, {:discovery, {:discovery_register, args}}) + if(@mix_env != :test) do + GenServer.call(pid, {:discovery, {:discovery_register, args}}) + else + # Valgrinnd + GenServer.call(pid, {:discovery, {:discovery_register, args}}, :infinity) + end end @doc """ @@ -509,7 +525,7 @@ defmodule OpcUA.Server do @doc false def test(pid) do - GenServer.call(pid, {:test, nil}) + GenServer.call(pid, {:test, nil}, :infinity) end # Handlers @@ -523,24 +539,7 @@ defmodule OpcUA.Server do executable = lib_dir <> "/opc_ua_server" - port = - Port.open({:spawn_executable, to_charlist(executable)}, [ - {:args, []}, - {:packet, 2}, - :use_stdio, - :binary, - :exit_status - ]) - - #Valgrind - # port = - # Port.open({:spawn_executable, to_charlist("/usr/bin/valgrind.bin")}, [ - # {:args, ["-q", "--leak-check=full", "--show-leak-kinds=all", "--track-origins=yes", "--show-reachable=no", executable]}, - # {:packet, 2}, - # :use_stdio, - # :binary, - # :exit_status - # ]) + port = open_port(executable, use_valgrind?()) state = %State{port: port, controlling_process: controlling_process} {:ok, state} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4d9605c..44b5576 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -124,7 +124,7 @@ if(NOT MANUAL_BUILD) set(BASE_FLAGS "${BASE_RELEASE_FLAGS}") endif() - set(BASE_C_FLAGS "${BASE_FLAGS} -std=c99 -lpthread") + set(BASE_C_FLAGS "-g -Wall -Wextra ${BASE_FLAGS} -std=c99 -lpthread") set(BASE_CXX_FLAGS "${BASE_FLAGS}") set (opex62541_PROGRAMS opc_ua_server opc_ua_client client_example server_example) diff --git a/src/common.c b/src/common.c index 6a16e31..962e5ac 100644 --- a/src/common.c +++ b/src/common.c @@ -645,6 +645,11 @@ void encode_array_dimensions_struct(char *resp, int *resp_index, void *data, int ei_encode_empty_list(resp, resp_index); } +void encode_variant_struct(char *resp, int *resp_index, void *data, int data_len) +{ + return; +} + void encode_data_response(char *resp, int *resp_index, void *data, int data_type, int data_len) { switch(data_type) @@ -765,6 +770,10 @@ void encode_data_response(char *resp, int *resp_index, void *data, int data_type encode_array_dimensions_struct(resp, resp_index, data, data_len); break; + case 29: //UA_Variant + encode_variant_struct(resp, resp_index, data, data_len); + break; + default: errx(EXIT_FAILURE, "data_type error"); break; @@ -2100,8 +2109,8 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in UA_Variant value; UA_Variant_init(&value); - char *arg1 = (char *)malloc(0); - char *arg2 = (char *)malloc(0); + char *arg1 = NULL; + char *arg2 = NULL; UA_NodeId_init(&node_id_arg_1); UA_NodeId_init(&node_id_arg_2); @@ -2132,28 +2141,32 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in retval = UA_Server_readValue((UA_Server *)entity, node_id, &value); if(retval != UA_STATUSCODE_GOOD) { - free(arg1); - free(arg2); UA_NodeId_clear(&node_id); UA_NodeId_clear(&node_id_arg_1); UA_NodeId_clear(&node_id_arg_2); + UA_Variant_clear(&value); send_opex_response(retval); return; } if(UA_Variant_isEmpty(&value)) + { + UA_Variant_clear(&value); is_null = true; + } if(UA_Variant_isScalar(&value)) + { + UA_Variant_clear(&value); is_scalar = true; + } if (!is_scalar && !is_null && (value.arrayLength <= data_index)) { - free(arg1); - free(arg2); UA_NodeId_clear(&node_id); UA_NodeId_clear(&node_id_arg_1); UA_NodeId_clear(&node_id_arg_2); + UA_Variant_clear(&value); send_opex_response(UA_STATUSCODE_BADTYPEMISMATCH); return; } @@ -2318,10 +2331,12 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in return; } UA_Double data = double_data; + if (is_scalar || is_null) UA_Variant_setScalar(&value, &data, &UA_TYPES[UA_TYPES_DOUBLE]); else *((UA_Double *)value.data + data_index) = data; + } break; @@ -2330,7 +2345,6 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (ei_get_type(req, req_index, &term_type, &term_size) < 0 || term_type != ERL_BINARY_EXT) errx(EXIT_FAILURE, "Invalid string (size)"); - free(arg1); arg1 = (char *)malloc(term_size + 1); long binary_len; @@ -2343,7 +2357,10 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (is_scalar || is_null) UA_Variant_setScalar(&value, &data, &UA_TYPES[UA_TYPES_STRING]); else + { + UA_String_clear(((UA_String *)value.data + data_index)); *((UA_String *)value.data + data_index) = data; + } } break; @@ -2408,7 +2425,6 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (ei_get_type(req, req_index, &term_type, &term_size) < 0 || term_type != ERL_BINARY_EXT) errx(EXIT_FAILURE, "Invalid byte_string (size)"); - free(arg1); arg1 = (char *)malloc(term_size + 1); long binary_len; @@ -2421,7 +2437,10 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (is_scalar || is_null) UA_Variant_setScalar(&value, &data, &UA_TYPES[UA_TYPES_BYTESTRING]); else + { + UA_ByteString_clear(((UA_ByteString *)value.data + data_index)); *((UA_ByteString *)value.data + data_index) = data; + } } break; @@ -2430,7 +2449,6 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (ei_get_type(req, req_index, &term_type, &term_size) < 0 || term_type != ERL_BINARY_EXT) errx(EXIT_FAILURE, "Invalid xml (size)"); - free(arg1); arg1 = (char *)malloc(term_size + 1); long binary_len; @@ -2443,7 +2461,10 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (is_scalar || is_null) UA_Variant_setScalar(&value, &data, &UA_TYPES[UA_TYPES_XMLELEMENT]); else + { + UA_XmlElement_clear(((UA_XmlElement *)value.data + data_index)); *((UA_XmlElement *)value.data + data_index) = data; + } } break; @@ -2453,7 +2474,10 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (is_scalar || is_null) UA_Variant_setScalar(&value, &node_id_arg_1, &UA_TYPES[UA_TYPES_NODEID]); else + { + UA_NodeId_clear(((UA_NodeId *)value.data + data_index)); *((UA_NodeId *)value.data + data_index) = node_id_arg_1; + } } break; @@ -2463,7 +2487,10 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (is_scalar || is_null) UA_Variant_setScalar(&value, &expanded_node_id_arg_1, &UA_TYPES[UA_TYPES_EXPANDEDNODEID]); else + { + UA_ExpandedNodeId_clear(((UA_ExpandedNodeId *)value.data + data_index)); *((UA_ExpandedNodeId *)value.data + data_index) = expanded_node_id_arg_1; + } } break; @@ -2488,7 +2515,10 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (is_scalar || is_null) UA_Variant_setScalar(&value, &qualified_name, &UA_TYPES[UA_TYPES_QUALIFIEDNAME]); else + { + UA_QualifiedName_clear(((UA_QualifiedName *)value.data + data_index)); *((UA_QualifiedName *)value.data + data_index) = qualified_name; + } } break; @@ -2502,7 +2532,6 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (ei_get_type(req, req_index, &term_type, &term_size) < 0 || term_type != ERL_BINARY_EXT) errx(EXIT_FAILURE, "Invalid locale (size)"); - free(arg1); arg1 = (char *)malloc(term_size + 1); long binary_len; @@ -2515,7 +2544,6 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (ei_get_type(req, req_index, &term_type, &term_size) < 0 || term_type != ERL_BINARY_EXT) errx(EXIT_FAILURE, "Invalid text (size)"); - free(arg2); arg2 = (char *)malloc(term_size + 1); if (ei_decode_binary(req, req_index, arg2, &binary_len) < 0) @@ -2527,7 +2555,10 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (is_scalar || is_null) UA_Variant_setScalar(&value, &data, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); else + { + UA_LocalizedText_clear(((UA_LocalizedText *)value.data + data_index)); *((UA_LocalizedText *)value.data + data_index) = data; + } } break; @@ -2553,7 +2584,10 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (is_scalar || is_null) UA_Variant_setScalar(&value, &data, &UA_TYPES[UA_TYPES_SEMANTICCHANGESTRUCTUREDATATYPE]); else + { + UA_SemanticChangeStructureDataType_clear(((UA_SemanticChangeStructureDataType *)value.data + data_index)); *((UA_SemanticChangeStructureDataType *)value.data + data_index) = data; + } } break; @@ -2562,7 +2596,6 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (ei_get_type(req, req_index, &term_type, &term_size) < 0 || term_type != ERL_BINARY_EXT) errx(EXIT_FAILURE, "Invalid time_string (size)"); - free(arg1); arg1 = (char *)malloc(term_size + 1); long binary_len; @@ -2575,7 +2608,10 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in if (is_scalar || is_null) UA_Variant_setScalar(&value, &data, &UA_TYPES[UA_TYPES_TIMESTRING]); else + { + UA_TimeString_clear(((UA_TimeString *)value.data + data_index)); *((UA_TimeString *)value.data + data_index) = data; + } } break; @@ -2658,9 +2694,6 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in retval = UA_Server_writeValue((UA_Server *)entity, node_id, value); } - - free(arg1); - free(arg2); UA_NodeId_clear(&node_id); UA_NodeId_clear(&node_id_arg_1); @@ -2676,6 +2709,18 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in return; } + if (!is_scalar && !is_null) + { + UA_Variant_clear(&value); + } + else + { + if(arg1 != NULL) + free(arg1); + if(arg1 != NULL) + free(arg2); + } + send_ok_response(); } @@ -3092,6 +3137,7 @@ void handle_write_node_blank_array(void *entity, bool entity_type, const char *r } UA_Variant_clear(&value); + UA_NodeId_clear(&node_id); if(retval != UA_STATUSCODE_GOOD) { send_opex_response(retval); @@ -3484,7 +3530,7 @@ void handle_read_node_array_dimensions(void *entity, bool entity_type, const cha if(retval != UA_STATUSCODE_GOOD) { if (entity_type) { - UA_UInt32_clear(array_dimensions); + free(array_dimensions); } UA_Variant_clear(&variant_array_dimensions); send_opex_response(retval); @@ -3494,7 +3540,7 @@ void handle_read_node_array_dimensions(void *entity, bool entity_type, const cha if(entity_type) { send_data_response(array_dimensions, 28, (int) array_dimensions_size); - UA_UInt32_clear(array_dimensions); + free(array_dimensions); } else { @@ -3693,47 +3739,47 @@ void handle_read_node_value(void *entity, bool entity_type, const char *req, int } if(value->type == &UA_TYPES[UA_TYPES_BOOLEAN]) - send_data_response(value->data + data_index, 0, 0); + send_data_response(((UA_Boolean *)value->data + data_index), 0, 0); else if(value->type == &UA_TYPES[UA_TYPES_SBYTE]) - send_data_response(value->data + data_index, 1, 0); + send_data_response(((UA_SByte *)value->data + data_index), 1, 0); else if(value->type == &UA_TYPES[UA_TYPES_BYTE]) - send_data_response(value->data + data_index, 2, 0); + send_data_response(((UA_Byte *)value->data + data_index), 2, 0); else if(value->type == &UA_TYPES[UA_TYPES_INT16]) - send_data_response(value->data + data_index, 1, 0); + send_data_response(((UA_Int16 *)value->data + data_index), 1, 0); else if(value->type == &UA_TYPES[UA_TYPES_UINT16]) - send_data_response(value->data + data_index, 2, 0); + send_data_response(((UA_UInt16 *)value->data + data_index), 2, 0); else if(value->type == &UA_TYPES[UA_TYPES_INT32]) - send_data_response(value->data + data_index, 1, 0); + send_data_response(((UA_Int32 *)value->data + data_index), 1, 0); else if(value->type == &UA_TYPES[UA_TYPES_UINT32]) - send_data_response(value->data + data_index, 2, 0); + send_data_response(((UA_UInt32 *)value->data + data_index), 2, 0); else if(value->type == &UA_TYPES[UA_TYPES_INT64]) - send_data_response(value->data + data_index, 15, 0); + send_data_response(((UA_Int64 *)value->data + data_index), 15, 0); else if(value->type == &UA_TYPES[UA_TYPES_UINT64]) - send_data_response(value->data + data_index, 16, 0); + send_data_response(((UA_UInt64 *)value->data + data_index), 16, 0); else if(value->type == &UA_TYPES[UA_TYPES_FLOAT]) - send_data_response(value->data + data_index, 17, 0); + send_data_response(((UA_Float *)value->data + data_index), 17, 0); else if(value->type == &UA_TYPES[UA_TYPES_DOUBLE]) - send_data_response(value->data + data_index, 4, 0); + send_data_response(((UA_Double *)value->data + data_index), 4, 0); else if(value->type == &UA_TYPES[UA_TYPES_STRING]) send_data_response((*((UA_String *)value->data + data_index)).data, 5, (*((UA_String *)value->data + data_index)).length); else if(value->type == &UA_TYPES[UA_TYPES_DATETIME]) - send_data_response(value->data + data_index, 15, 0); + send_data_response(((UA_DateTime *)value->data + data_index), 15, 0); else if(value->type == &UA_TYPES[UA_TYPES_GUID]) - send_data_response(value->data + data_index, 18, 0); + send_data_response(((UA_Guid *)value->data + data_index), 18, 0); else if(value->type == &UA_TYPES[UA_TYPES_BYTESTRING]) send_data_response((*((UA_ByteString *)value->data + data_index)).data, 5, (*((UA_ByteString *)value->data + data_index)).length); else if(value->type == &UA_TYPES[UA_TYPES_XMLELEMENT]) send_data_response((*((UA_XmlElement *)value->data + data_index)).data, 5, (*((UA_XmlElement *)value->data + data_index)).length); else if(value->type == &UA_TYPES[UA_TYPES_NODEID]) - send_data_response(value->data + data_index, 12, 0); + send_data_response(((UA_NodeId *)value->data + data_index), 12, 0); else if(value->type == &UA_TYPES[UA_TYPES_EXPANDEDNODEID]) - send_data_response(value->data + data_index, 19, 0); + send_data_response(((UA_ExpandedNodeId *)value->data + data_index), 19, 0); else if(value->type == &UA_TYPES[UA_TYPES_STATUSCODE]) - send_data_response(value->data + data_index, 20, 0); + send_data_response(((UA_StatusCode *)value->data + data_index), 20, 0); else if(value->type == &UA_TYPES[UA_TYPES_QUALIFIEDNAME]) - send_data_response(value->data + data_index, 13, 0); + send_data_response(((UA_QualifiedName *)value->data + data_index), 13, 0); else if(value->type == &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]) - send_data_response(value->data + data_index, 14, 0); + send_data_response(((UA_LocalizedText *)value->data + data_index), 14, 0); // TODO: UA_TYPES_EXTENSIONOBJECT @@ -3744,7 +3790,7 @@ void handle_read_node_value(void *entity, bool entity_type, const char *req, int // TODO: UA_TYPES_DIAGNOSTICINFO else if(value->type == &UA_TYPES[UA_TYPES_SEMANTICCHANGESTRUCTUREDATATYPE]) - send_data_response(value->data + data_index, 21, 0); + send_data_response(((UA_SemanticChangeStructureDataType *)value->data + data_index), 21, 0); else if(value->type == &UA_TYPES[UA_TYPES_TIMESTRING]) send_data_response((*(UA_TimeString *)value->data).data, 5, (*(UA_TimeString *)value->data).length); @@ -3752,11 +3798,11 @@ void handle_read_node_value(void *entity, bool entity_type, const char *req, int // TODO: UA_TYPES_UADPNETWORKMESSAGECONTENTMASK else if(value->type == &UA_TYPES[UA_TYPES_UADPNETWORKMESSAGECONTENTMASK]) - send_data_response(value->data + data_index, 2, 0); + send_data_response(((UA_UadpDataSetMessageContentMask *)value->data + data_index), 2, 0); else if(value->type == &UA_TYPES[UA_TYPES_XVTYPE]) - send_data_response(value->data + data_index, 22, 0); + send_data_response(((UA_XVType *)value->data + data_index), 22, 0); else if(value->type == &UA_TYPES[UA_TYPES_ELEMENTOPERAND]) - send_data_response(value->data + data_index, 2, 0); + send_data_response(((UA_ElementOperand *)value->data + data_index), 2, 0); else send_error_response("eagain"); @@ -3772,7 +3818,7 @@ void handle_read_node_value_by_data_type(void *entity, bool entity_type, const c { int term_size; int term_type; - UA_Variant *value = UA_Variant_new(); + UA_Variant *value; UA_StatusCode retval; if(ei_decode_tuple_header(req, req_index, &term_size) < 0 || diff --git a/src/opc_ua_client.c b/src/opc_ua_client.c index 1d800b0..c4e9e58 100644 --- a/src/opc_ua_client.c +++ b/src/opc_ua_client.c @@ -429,10 +429,13 @@ static void handle_find_servers_on_network(void *entity, bool entity_type, const if(retval != UA_STATUSCODE_GOOD) { send_opex_response(retval); + UA_Array_delete(serverOnNetwork, serverOnNetworkSize, &UA_TYPES[UA_TYPES_SERVERONNETWORK]); return; } send_data_response(serverOnNetwork, 8, serverOnNetworkSize); + + UA_Array_delete(serverOnNetwork, serverOnNetworkSize, &UA_TYPES[UA_TYPES_SERVERONNETWORK]); } /* Gets a list of all registered servers at the given server. diff --git a/src/opc_ua_server.c b/src/opc_ua_server.c index 08e504d..de0b859 100644 --- a/src/opc_ua_server.c +++ b/src/opc_ua_server.c @@ -10,6 +10,14 @@ #include "erlcmd.h" #include "common.h" +typedef struct Users_list{ + size_t list_size; + char **username; + char **password; +}User_list; + +User_list users_list = {.list_size = 0}; + pthread_t server_tid; pthread_attr_t server_attr; UA_Boolean running = true; @@ -53,6 +61,43 @@ dataChangeNotificationCallback(UA_Server *server, UA_UInt32 monitoredItemId, const UA_DataValue *value) { } +void set_users_list_size(int size) +{ + users_list.list_size = size; + users_list.username = (char **)calloc(size, sizeof(char *)); + users_list.password = (char **)calloc(size, sizeof(char *)); +} + +void delete_users_list() +{ + if(users_list.list_size == 0) + return; + + for(size_t i = 0; i < users_list.list_size; i++) + { + free(users_list.username[i]); + free(users_list.password[i]); + } + + users_list.list_size = 0; + free(users_list.username); + free(users_list.password); +} + +void delete_discovery_params() +{ + UA_ServerConfig *config = UA_Server_getConfig(server); + + if(config->applicationDescription.applicationUri.data) + UA_String_clear(&config->applicationDescription.applicationUri); + + if(config->discovery.mdns.mdnsServerName.data) + UA_String_clear(&config->discovery.mdns.mdnsServerName); + + if(discoveryClient) + UA_Client_delete(discoveryClient); +} + /***************************************/ /* Configuration & Lifecycle Functions */ /***************************************/ @@ -136,36 +181,39 @@ static void handle_set_users_and_passwords(void *entity, bool entity_type, const errx(EXIT_FAILURE, ":handle_set_users_and_passwords has an empty list"); UA_UsernamePasswordLogin logins[list_arity]; - + + delete_users_list(); + set_users_list_size(list_arity); + for(size_t i = 0; i < list_arity; i++) { if(ei_decode_tuple_header(req, req_index, &tuple_arity) < 0 || tuple_arity != 2) errx(EXIT_FAILURE, ":handle_set_users_and_passwords requires a 2-tuple, term_size = %d", tuple_arity); if (ei_get_type(req, req_index, &term_type, &term_size) < 0 || term_type != ERL_BINARY_EXT) - errx(EXIT_FAILURE, "Invalid hostname (size)"); + errx(EXIT_FAILURE, "Invalid username (size)"); - char *username; - username = (char *)malloc(term_size + 1); + users_list.username[i] = (char *)malloc(term_size + 1); long binary_len; - if (ei_decode_binary(req, req_index, username, &binary_len) < 0) - errx(EXIT_FAILURE, "Invalid hostname"); - username[binary_len] = '\0'; + if (ei_decode_binary(req, req_index, users_list.username[i], &binary_len) < 0) + errx(EXIT_FAILURE, "Invalid username"); + users_list.username[i][binary_len] = '\0'; if (ei_get_type(req, req_index, &term_type, &term_size) < 0 || term_type != ERL_BINARY_EXT) - errx(EXIT_FAILURE, "Invalid hostname (size)"); + errx(EXIT_FAILURE, "Invalid password (size)"); - char *password; - password = (char *)malloc(term_size + 1); - if (ei_decode_binary(req, req_index, password, &binary_len) < 0) + users_list.password[i] = (char *)malloc(term_size + 1); + if (ei_decode_binary(req, req_index, users_list.password[i], &binary_len) < 0) errx(EXIT_FAILURE, "Invalid hostname"); - password[binary_len] = '\0'; + users_list.password[i][binary_len] = '\0'; - logins[i].username = UA_STRING(username); - logins[i].password = UA_STRING(password); + logins[i].username = UA_STRING(users_list.username[i]); + logins[i].password = UA_STRING(users_list.password[i]); } UA_ServerConfig *config = UA_Server_getConfig(server); - //config->accessControl.clear(&config->accessControl); + config->accessControl.deleteMembers(&config->accessControl); + /* Disable anonymous logins, enable two user/password logins */ + // config->accessControl.clear(&config->accessControl); UA_StatusCode retval = UA_AccessControl_default(config, false, &config->securityPolicies[config->securityPoliciesSize-1].policyUri, list_arity, logins); if(retval != UA_STATUSCODE_GOOD) { send_opex_response(retval); @@ -287,7 +335,10 @@ void handle_set_lds_config(void *entity, bool entity_type, const char *req, int // This is an LDS server only. Set the application type to DISCOVERYSERVER. config->applicationDescription.applicationType = UA_APPLICATIONTYPE_DISCOVERYSERVER; - UA_String_clear(&config->applicationDescription.applicationUri); + + if(config->applicationDescription.applicationUri.data) + UA_String_clear(&config->applicationDescription.applicationUri); + config->applicationDescription.applicationUri = application_uri; // corrupted size vs. prev_size @@ -349,7 +400,9 @@ void handle_discovery_register(void *entity, bool entity_type, const char *req, errx(EXIT_FAILURE, "Invalid application_uri"); application_uri.data[binary_len] = '\0'; - UA_String_clear(&config->applicationDescription.applicationUri); + if(config->applicationDescription.applicationUri.data) + UA_String_clear(&config->applicationDescription.applicationUri); + config->applicationDescription.applicationUri = application_uri; // server_name @@ -363,13 +416,17 @@ void handle_discovery_register(void *entity, bool entity_type, const char *req, errx(EXIT_FAILURE, "Invalid server_name"); server_name.data[binary_len] = '\0'; + if(config->discovery.mdns.mdnsServerName.data) + UA_String_clear(&config->discovery.mdns.mdnsServerName); + config->discovery.mdns.mdnsServerName = server_name; // endpoint if (ei_get_type(req, req_index, &term_type, &term_size) < 0 || term_type != ERL_BINARY_EXT) errx(EXIT_FAILURE, "Invalid endpoint (type)"); - char *endpoint = (char *)malloc(term_size + 1); + //char *endpoint = (char *)malloc(term_size + 1); + char endpoint[term_size + 1]; if (ei_decode_binary(req, req_index, endpoint, &binary_len) < 0) errx(EXIT_FAILURE, "Invalid endpoint"); endpoint[binary_len] = '\0'; @@ -418,7 +475,7 @@ void handle_discovery_unregister(void *entity, bool entity_type, const char *req UA_StatusCode retval = UA_Server_unregister_discovery(server, discoveryClient); UA_Client_disconnect(discoveryClient); - UA_Client_delete(discoveryClient); + //UA_Client_delete(discoveryClient); if(retval != UA_STATUSCODE_GOOD) { send_opex_response(retval); @@ -644,5 +701,9 @@ int main() /* Disconnects the client internally */ free(handler); running = false; + delete_users_list(); + delete_discovery_params(); + // Release threads memory + pthread_join(server_tid, NULL); UA_Server_delete(server); } \ No newline at end of file diff --git a/test/client_tests/arrays_test.exs b/test/client_tests/arrays_test.exs index 3c6453d..cb2bedd 100644 --- a/test/client_tests/arrays_test.exs +++ b/test/client_tests/arrays_test.exs @@ -1,5 +1,5 @@ defmodule ClientArraysTest do - use ExUnit.Case + use ExUnit.Case, async: false alias OpcUA.{NodeId, Server, QualifiedName, Client} @@ -67,9 +67,15 @@ defmodule ClientArraysTest do resp = Client.read_node_array_dimensions(state.c_pid, node_id) assert resp == {:ok, [4]} + + resp = Client.write_node_array_dimensions(state.c_pid, node_id, [4]) + assert resp == :ok + + resp = Client.read_node_array_dimensions(state.c_pid, node_id) + assert resp == {:ok, [4]} end - test "write/read array node by index", state do + test "write/read array node by index (string)", state do node_id = NodeId.new(ns_index: state.ns_index, identifier_type: "string", identifier: "R1_TS1_Temperature") resp = Client.read_node_value(state.c_pid, node_id, 0) @@ -117,4 +123,54 @@ defmodule ClientArraysTest do resp = Client.read_node_value(state.c_pid, node_id, 4) assert resp == {:error, "BadTypeMismatch"} end + + test "write/read array node by index (double)", state do + node_id = NodeId.new(ns_index: state.ns_index, identifier_type: "string", identifier: "R1_TS1_Temperature") + :ok = Server.write_node_blank_array(state.s_pid, node_id, 10, [4]) + + resp = Client.read_node_value(state.c_pid, node_id, 0) + assert resp == {:ok, 0.0} + + resp = Client.read_node_value(state.c_pid, node_id, 1) + assert resp == {:ok, 0.0} + + resp = Client.read_node_value(state.c_pid, node_id, 2) + assert resp == {:ok, 0.0} + + resp = Client.read_node_value(state.c_pid, node_id, 3) + assert resp == {:ok, 0.0} + + resp = Client.read_node_value(state.c_pid, node_id, 4) + assert resp == {:error, "BadTypeMismatch"} + + resp = Client.write_node_value(state.c_pid, node_id, 10, 103.0, 0) + assert resp == :ok + + resp = Client.write_node_value(state.c_pid, node_id, 10, 103103.0, 1) + assert resp == :ok + + resp = Client.write_node_value(state.c_pid, node_id, 10, 103103103.0, 2) + assert resp == :ok + + resp = Client.write_node_value(state.c_pid, node_id, 10, 103103.103, 3) + assert resp == :ok + + resp = Client.write_node_value(state.c_pid, node_id, 10, 103103.0, 4) + assert resp == {:error, "BadTypeMismatch"} + + resp = Client.read_node_value(state.c_pid, node_id, 0) + assert resp == {:ok, 103.0} + + resp = Client.read_node_value(state.c_pid, node_id, 1) + assert resp == {:ok, 103103.0} + + resp = Client.read_node_value(state.c_pid, node_id, 2) + assert resp == {:ok, 103103103.0} + + resp = Client.read_node_value(state.c_pid, node_id, 3) + assert resp == {:ok, 103103.103} + + resp = Client.read_node_value(state.c_pid, node_id, 4) + assert resp == {:error, "BadTypeMismatch"} + end end diff --git a/test/client_tests/terraform_test.exs b/test/client_tests/terraform_test.exs index 2baa153..11d9153 100644 --- a/test/client_tests/terraform_test.exs +++ b/test/client_tests/terraform_test.exs @@ -86,7 +86,13 @@ defmodule ClientTerraformTest do {:ok, localhost} = :inet.gethostname() @configuration_client [ - config: [], + config: [ + set_config: %{ + "requestedSessionTimeout" => 1200000, + "secureChannelLifeTime" => 600000, + "timeout" => 50000 + } + ], conn: [ by_username: [ url: "opc.tcp://#{localhost}:4041/", @@ -139,7 +145,7 @@ defmodule ClientTerraformTest do state end - def read_node_value(pid, node), do: GenServer.call(pid, {:read, node}) + def read_node_value(pid, node), do: GenServer.call(pid, {:read, node}, :infinity) def get_client(pid), do: GenServer.call(pid, {:get_client, nil}) diff --git a/test/server_tests/discovery_test.exs b/test/server_tests/discovery_test.exs index d2fc1ff..2e59006 100644 --- a/test/server_tests/discovery_test.exs +++ b/test/server_tests/discovery_test.exs @@ -3,6 +3,12 @@ defmodule ServerDiscoveryTest do alias OpcUA.{Server, Client} + # Valgrind memory leaks detected + # UA_Server_addPeriodicServerRegisterCallback (open62541.c:46592) + # UA_Server_addPeriodicServerRegisterCallback (open62541.c:46578) + # UA_Server_addPeriodicServerRegisterCallback (open62541.c:46613) + # These memory leaks are managed by open62541 lib and there are freed by UA_Server_addPeriodicServerRegisterCallback with the right arguments. + test "Configure an LDS Server" do {:ok, lds_pid} = Server.start_link() @@ -33,6 +39,16 @@ defmodule ServerDiscoveryTest do # Registration time Process.sleep(1500) assert :ok == Server.discovery_unregister(s_pid) + + assert :ok == + Server.discovery_register(s_pid, + application_uri: "urn:opex62541.test.local_register_server", + server_name: "TestRegister", + endpoint: "opc.tcp://localhost:4840" + ) + + Process.sleep(1500) + assert :ok == Server.discovery_unregister(s_pid) end test "Full Discovery Implementation" do diff --git a/test/server_tests/lifecycle_test.exs b/test/server_tests/lifecycle_test.exs index 6769773..94ffcf9 100644 --- a/test/server_tests/lifecycle_test.exs +++ b/test/server_tests/lifecycle_test.exs @@ -51,7 +51,6 @@ defmodule ServerLifecycleTest do assert server_config["hostname"] == "alde103" end - test "Set port nomber", state do response = Server.set_default_config(state.pid) assert response == :ok @@ -68,11 +67,14 @@ defmodule ServerLifecycleTest do assert response == :ok end - test "start_server", state do + test "Start/stop server", state do response = Server.set_default_config(state.pid) assert response == :ok response = Server.start(state.pid) assert response == :ok + + response = Server.stop_server(state.pid) + assert response == :ok end end diff --git a/test/server_tests/refactor_test.exs b/test/server_tests/refactor_test.exs index f663602..0b1d28d 100644 --- a/test/server_tests/refactor_test.exs +++ b/test/server_tests/refactor_test.exs @@ -9,7 +9,7 @@ defmodule ServerRefactorTest do end test "test", state do - :ok = Server.test(state.pid) + assert :ok == Server.test(state.pid) end test "Set LD_LIBRARY_PATH" do diff --git a/test/server_tests/terraform_test.exs b/test/server_tests/terraform_test.exs index 5406b14..215e5ea 100644 --- a/test/server_tests/terraform_test.exs +++ b/test/server_tests/terraform_test.exs @@ -102,7 +102,13 @@ defmodule ServerTerraformTest do {:ok, _pid} = MyServer.start_link({self(), 103}) {:ok, c_pid} = Client.start_link() - :ok = Client.set_config(c_pid) + + config = %{ + "requestedSessionTimeout" => 1200000, + "secureChannelLifeTime" => 600000, + "timeout" => 50000 + } + :ok = Client.set_config(c_pid, config) %{c_pid: c_pid} end @@ -156,5 +162,7 @@ defmodule ServerTerraformTest do c_response = Client.read_node_value(c_pid, node_id) assert c_response == {:ok, true} assert_receive({node_id, true}, 1000) + + assert :ok == Client.disconnect(c_pid) end end diff --git a/test/server_tests/write_event_test.exs b/test/server_tests/write_event_test.exs index 52f2929..69f7a5d 100644 --- a/test/server_tests/write_event_test.exs +++ b/test/server_tests/write_event_test.exs @@ -73,7 +73,14 @@ defmodule ServerWriteEventTest do {:ok, my_pid} = MyServer.start_link({self(), 103}) {:ok, c_pid} = Client.start_link() - :ok = Client.set_config(c_pid) + + config = %{ + "requestedSessionTimeout" => 1200000, + "secureChannelLifeTime" => 600000, + "timeout" => 50000 + } + + :ok = Client.set_config(c_pid, config) :ok = Client.connect_by_url(c_pid, url: "opc.tcp://alde-Satellite-S845:4840/") %{c_pid: c_pid, my_pid: my_pid} From 02eeb40728eec991a30e11019627c8b1bdc3e16a Mon Sep 17 00:00:00 2001 From: alde103 Date: Wed, 17 Jun 2020 20:45:06 -0500 Subject: [PATCH 2/2] UA_Variant refactor for read funtions --- lib/opc_ua/common.ex | 33 ++ src/common.c | 360 ++++++++++++--------- src/common.h | 5 +- src/opc_ua_client.c | 124 +------ src/opc_ua_server.c | 1 + test/client_tests/arrays_test.exs | 99 ++++-- test/client_tests/write_read_attr_test.exs | 3 + 7 files changed, 337 insertions(+), 288 deletions(-) diff --git a/lib/opc_ua/common.ex b/lib/opc_ua/common.ex index 48c70e7..8e69e33 100644 --- a/lib/opc_ua/common.ex +++ b/lib/opc_ua/common.ex @@ -374,6 +374,21 @@ defmodule OpcUA.Common do end end + @doc """ + Reads 'value' attribute of a node in the server. + Note: If the value is an array you can search a scalar using `index` parameter. + """ + @spec read_node_value_by_index(GenServer.server(), %NodeId{}, integer()) :: + {:ok, term()} | {:error, binary()} | {:error, :einval} + def read_node_value_by_index(pid, node_id, index \\ 0) do + if(@mix_env != :test) do + GenServer.call(pid, {:read, {:value_by_index, {node_id, index}}}) + else + # Valgrind + GenServer.call(pid, {:read, {:value_by_index, {node_id, index}}}, :infinity) + end + end + @doc """ Reads 'Value' attribute (matching data type) of a node in the server. """ @@ -639,6 +654,12 @@ defmodule OpcUA.Common do {:noreply, state} end + def handle_call({:read, {:value_by_index, {node_id, index}}}, caller_info, state) do + c_args = {to_c(node_id), index} + call_port(state, :read_node_value_by_index, caller_info, c_args) + {:noreply, state} + end + def handle_call({:read, {:value_by_data_type, {node_id, data_type}}}, caller_info, state) do c_args = {to_c(node_id), data_type} call_port(state, :read_node_value_by_data_type, caller_info, c_args) @@ -846,6 +867,12 @@ defmodule OpcUA.Common do state end + defp handle_c_response({:read_node_value_by_index, caller_metadata, value_response}, state) do + response = parse_value(value_response) + GenServer.reply(caller_metadata, response) + state + end + defp handle_c_response( {:read_node_value_by_data_type, caller_metadata, value_response}, state @@ -948,6 +975,9 @@ defmodule OpcUA.Common do defp parse_value({:ok, {ns_index, name}}) when is_integer(ns_index), do: {:ok, QualifiedName.new(ns_index: ns_index, name: name)} + defp parse_value({:ok, array}) when is_list(array), + do: {:ok, Enum.map(array, fn(data) -> parse_c_value(data) end)} + defp parse_value(response), do: response defp parse_c_value({ns_index, type, name, name_space_uri, server_index}), @@ -970,6 +1000,9 @@ defmodule OpcUA.Common do defp parse_c_value({ns_index, name}) when is_integer(ns_index), do: QualifiedName.new(ns_index: ns_index, name: name) + defp parse_c_value(array) when is_list(array), + do: Enum.map(array, fn(data) -> parse_c_value(data) end) + defp parse_c_value(response), do: response @doc false diff --git a/src/common.c b/src/common.c index 962e5ac..756e4ee 100644 --- a/src/common.c +++ b/src/common.c @@ -645,9 +645,162 @@ void encode_array_dimensions_struct(char *resp, int *resp_index, void *data, int ei_encode_empty_list(resp, resp_index); } -void encode_variant_struct(char *resp, int *resp_index, void *data, int data_len) +void encode_variant_scalar_struct(char *resp, int *resp_index, void *data, size_t index) { - return; + UA_Variant value = *(UA_Variant *) data; + switch (value.type->typeIndex) + { + case UA_TYPES_BOOLEAN: + ei_encode_boolean(resp, resp_index, *((UA_Boolean *)value.data + index)); + break; + + case UA_TYPES_SBYTE: + ei_encode_long(resp, resp_index, *((UA_SByte *)value.data + index)); + break; + + case UA_TYPES_BYTE: + ei_encode_ulong(resp, resp_index, *((UA_Byte *)value.data + index)); + break; + + case UA_TYPES_INT16: + ei_encode_long(resp, resp_index, *((UA_Int16 *)value.data + index)); + break; + + case UA_TYPES_UINT16: + ei_encode_ulong(resp, resp_index, *((UA_UInt16 *)value.data + index)); + break; + + case UA_TYPES_INT32: + ei_encode_long(resp, resp_index, *((UA_Int32 *)value.data + index)); + break; + + case UA_TYPES_UINT32: + ei_encode_ulong(resp, resp_index, *((UA_UInt32 *)value.data + index)); + break; + + case UA_TYPES_INT64: + ei_encode_longlong(resp, resp_index, *((UA_Int64 *)value.data + index)); + break; + + case UA_TYPES_UINT64: + ei_encode_ulonglong(resp, resp_index, *((UA_UInt64 *)value.data + index)); + break; + + case UA_TYPES_FLOAT: + encode_ua_float(resp, resp_index, ((UA_Float *)value.data + index)); + break; + + case UA_TYPES_DOUBLE: + ei_encode_double(resp, resp_index, *((UA_Double *)value.data + index)); + break; + + case UA_TYPES_STRING: + ei_encode_binary(resp, resp_index, (*((UA_String *)value.data + index)).data, (*((UA_String *)value.data + index)).length); + break; + + case UA_TYPES_DATETIME: + ei_encode_ulonglong(resp, resp_index, *((UA_DateTime *)value.data + index)); + break; + + case UA_TYPES_GUID: + encode_ua_guid(resp, resp_index, ((UA_Guid *)value.data + index)); + break; + + case UA_TYPES_BYTESTRING: + ei_encode_binary(resp, resp_index, (*((UA_ByteString *)value.data + index)).data, (*((UA_ByteString *)value.data + index)).length); + break; + + case UA_TYPES_XMLELEMENT: + ei_encode_binary(resp, resp_index, (*((UA_XmlElement *)value.data + index)).data, (*((UA_XmlElement *)value.data + index)).length); + break; + + case UA_TYPES_NODEID: + encode_node_id(resp, resp_index, ((UA_NodeId *)value.data + index)); + break; + + case UA_TYPES_EXPANDEDNODEID: + encode_expanded_node_id(resp, resp_index, ((UA_ExpandedNodeId *)value.data + index)); + break; + + case UA_TYPES_STATUSCODE: + encode_status_code(resp, resp_index, ((UA_StatusCode *)value.data + index)); + break; + + case UA_TYPES_QUALIFIEDNAME: + encode_qualified_name(resp, resp_index, ((UA_QualifiedName *)value.data + index)); + break; + + case UA_TYPES_LOCALIZEDTEXT: + encode_localized_text(resp, resp_index, ((UA_LocalizedText *)value.data + index)); + break; + + // // TODO: UA_TYPES_EXTENSIONOBJECT + + // // TODO: UA_TYPES_DATAVALUE + + // // TODO: UA_TYPES_VARIANT + + // // TODO: UA_TYPES_DIAGNOSTICINFO + + case UA_TYPES_SEMANTICCHANGESTRUCTUREDATATYPE: + encode_semantic_change_structure_data_type(resp, resp_index, ((UA_SemanticChangeStructureDataType *)value.data + index)); + break; + + case UA_TYPES_TIMESTRING: + ei_encode_binary(resp, resp_index, (*((UA_TimeString *)value.data + index)).data, (*((UA_TimeString *)value.data + index)).length); + break; + + // // TODO: UA_TYPES_VIEWATTRIBUTES + + case UA_TYPES_UADPNETWORKMESSAGECONTENTMASK: + ei_encode_ulong(resp, resp_index, *((UA_UadpDataSetMessageContentMask *)value.data + index)); + break; + + case UA_TYPES_XVTYPE: + encode_xv_type(resp, resp_index, ((UA_XVType *)value.data + index)); + break; + + case UA_TYPES_ELEMENTOPERAND: + ei_encode_long(resp, resp_index, (*((UA_ElementOperand *)value.data + index)).index); + break; + + default: + ei_encode_atom(resp, resp_index, "error"); + break; + } +} + +void encode_variant_array_struct(char *resp, int *resp_index, void *data) +{ + UA_Variant value = *(UA_Variant *) data; + + ei_encode_list_header(resp, resp_index, value.arrayLength); + + for(size_t i = 0; i < value.arrayLength; i++) + { + encode_variant_scalar_struct(resp, resp_index, data, i); + } + + if(value.arrayLength) + ei_encode_empty_list(resp, resp_index); +} + + +void encode_variant_struct(char *resp, int *resp_index, void *data) +{ + if(UA_Variant_isEmpty((UA_Variant *)data)) + { + ei_encode_atom(resp, resp_index, "nil"); + return; + } + + if(UA_Variant_isScalar((UA_Variant *)data)) + { + encode_variant_scalar_struct(resp, resp_index, data, 0); + return; + } + + encode_variant_array_struct(resp, resp_index, data); } void encode_data_response(char *resp, int *resp_index, void *data, int data_type, int data_len) @@ -771,7 +924,7 @@ void encode_data_response(char *resp, int *resp_index, void *data, int data_type break; case 29: //UA_Variant - encode_variant_struct(resp, resp_index, data, data_len); + encode_variant_struct(resp, resp_index, data); break; default: @@ -803,7 +956,6 @@ void decode_caller_metadata(const char *req, int *req_index, const char* cmd) caller_ref = malloc(sizeof(erlang_ref)); if (ei_decode_ref(req, req_index, caller_ref) < 0) errx(EXIT_FAILURE, "Expecting ref"); - } void free_caller_metadata() @@ -856,9 +1008,9 @@ void send_subscription_deleted_response(void *data, int data_type, int data_len) /** * @brief Send changed data back to Elixir in form of {:subscription, {:data, subId, monId, data}} */ -void send_monitored_item_response(void *subscription_id, void *monitored_id, void *data, int data_type, int data_len) +void send_monitored_item_response(void *subscription_id, void *monitored_id, void *data, int data_type) { - char resp[1024]; + char resp[ERLCMD_BUF_SIZE]; long i_struct; int resp_index = sizeof(uint16_t); // Space for payload size resp[resp_index++] = response_id; @@ -871,10 +1023,7 @@ void send_monitored_item_response(void *subscription_id, void *monitored_id, voi encode_data_response(resp, &resp_index, subscription_id, 27, 0); encode_data_response(resp, &resp_index, monitored_id, 27, 0); - if(data_len != -1) - encode_data_response(resp, &resp_index, data, data_type, data_len); - else - ei_encode_atom(resp, &resp_index, "error"); + encode_data_response(resp, &resp_index, data, data_type, 0); erlcmd_send(resp, resp_index); } @@ -903,9 +1052,9 @@ void send_monitored_item_delete_response(void *subscription_id, void *monitored_ /** * @brief Send write data back to Elixir in form of {:write, node_id, value} */ -void send_write_data_response(const UA_NodeId *nodeId, void *data, int data_type, int data_len) +void send_write_data_response(const UA_NodeId *nodeId, void *data, int data_type) { - char resp[1024]; + char resp[ERLCMD_BUF_SIZE]; long i_struct; int resp_index = sizeof(uint16_t); // Space for payload size resp[resp_index++] = response_id; @@ -914,11 +1063,7 @@ void send_write_data_response(const UA_NodeId *nodeId, void *data, int data_type ei_encode_atom(resp, &resp_index, "write"); encode_node_id(resp, &resp_index, (UA_NodeId *) nodeId); - - if(data_len != -1) - encode_data_response(resp, &resp_index, data, data_type, data_len); - else - ei_encode_atom(resp, &resp_index, "error"); + encode_data_response(resp, &resp_index, data, data_type, 0); erlcmd_send(resp, resp_index); } @@ -928,7 +1073,7 @@ void send_write_data_response(const UA_NodeId *nodeId, void *data, int data_type */ void send_data_response(void *data, int data_type, int data_len) { - char resp[1024]; + char resp[ERLCMD_BUF_SIZE]; long i_struct; int resp_index = sizeof(uint16_t); // Space for payload size resp[resp_index++] = response_id; @@ -1015,126 +1160,8 @@ void send_write_response(UA_Server *server, return; } - switch(data->value.type->typeIndex) - { - case UA_TYPES_BOOLEAN: - send_write_data_response(nodeId, data->value.data, 0, 0); - break; - - case UA_TYPES_SBYTE: - send_write_data_response(nodeId, data->value.data, 23, 0); - break; - - case UA_TYPES_BYTE: - send_write_data_response(nodeId, data->value.data, 24, 0); - break; - - case UA_TYPES_INT16: - send_write_data_response(nodeId, data->value.data, 25, 0); - break; - - case UA_TYPES_UINT16: - send_write_data_response(nodeId, data->value.data, 26, 0); - break; - - case UA_TYPES_INT32: - send_write_data_response(nodeId, data->value.data, 1, 0); - break; - - case UA_TYPES_UINT32: - send_write_data_response(nodeId, data->value.data, 2, 0); - break; - - case UA_TYPES_INT64: - send_write_data_response(nodeId, data->value.data, 15, 0); - break; - - case UA_TYPES_UINT64: - send_write_data_response(nodeId, data->value.data, 16, 0); - break; - - case UA_TYPES_FLOAT: - send_write_data_response(nodeId, data->value.data, 17, 0); - break; - - case UA_TYPES_DOUBLE: - send_write_data_response(nodeId, data->value.data, 4, 0); - break; - - case UA_TYPES_STRING: - send_write_data_response(nodeId, (*(UA_String *)data->value.data).data, 5, (*(UA_String *)data->value.data).length); - break; - - case UA_TYPES_DATETIME: - send_write_data_response(nodeId, data->value.data, 15, 0); - break; - - case UA_TYPES_GUID: - send_write_data_response(nodeId, data->value.data, 18, 0); - break; - - case UA_TYPES_BYTESTRING: - send_write_data_response(nodeId, (*(UA_ByteString *)data->value.data).data, 5, (*(UA_ByteString *)data->value.data).length); - break; - - case UA_TYPES_XMLELEMENT: - send_write_data_response(nodeId, (*(UA_XmlElement *)data->value.data).data, 5, (*(UA_XmlElement *)data->value.data).length); - break; - - case UA_TYPES_NODEID: - send_write_data_response(nodeId, data->value.data, 12, 0); - break; - - case UA_TYPES_EXPANDEDNODEID: - send_write_data_response(nodeId, data->value.data, 19, 0); - break; - - case UA_TYPES_STATUSCODE: - send_write_data_response(nodeId, data->value.data, 20, 0); - break; - - case UA_TYPES_QUALIFIEDNAME: - send_write_data_response(nodeId, data->value.data, 13, 0); - break; - - case UA_TYPES_LOCALIZEDTEXT: - send_write_data_response(nodeId, data->value.data, 14, 0); - break; - - // TODO: UA_TYPES_EXTENSIONOBJECT - - // TODO: UA_TYPES_DATAVALUE - - // TODO: UA_TYPES_VARIANT - - // TODO: UA_TYPES_DIAGNOSTICINFO - - case UA_TYPES_SEMANTICCHANGESTRUCTUREDATATYPE: - send_write_data_response(nodeId, data->value.data, 21, 0); - break; - - case UA_TYPES_TIMESTRING: - send_write_data_response(nodeId, (*(UA_TimeString *)data->value.data).data, 5, (*(UA_TimeString *)data->value.data).length); - break; - - // TODO: UA_TYPES_VIEWATTRIBUTES - - case UA_TYPES_UADPNETWORKMESSAGECONTENTMASK: - send_write_data_response(nodeId, data->value.data, 2, 0); - break; - - case UA_TYPES_XVTYPE: - send_write_data_response(nodeId, data->value.data, 22, 0); - break; - - case UA_TYPES_ELEMENTOPERAND: - send_write_data_response(nodeId, data->value.data, 27, 0); - break; - - default: - send_write_data_response(nodeId, data->value.data, 2, -1); - break; - } + UA_Variant variant = data->value; + send_write_data_response(nodeId, &variant, 29); } /******************************/ @@ -1150,7 +1177,6 @@ void handle_add_variable_node(void *entity, bool entity_type, const char *req, i int term_type; UA_StatusCode retval; - if(ei_decode_tuple_header(req, req_index, &term_size) < 0 || term_size != 5) errx(EXIT_FAILURE, ":handle_add_variable_node requires a 5-tuple, term_size = %d", term_size); @@ -2696,13 +2722,7 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in UA_NodeId_clear(&node_id); - UA_NodeId_clear(&node_id_arg_1); - UA_NodeId_clear(&node_id_arg_2); - if(data_type == UA_TYPES_EXPANDEDNODEID) - UA_ExpandedNodeId_clear(&expanded_node_id_arg_1); - if(data_type == UA_TYPES_QUALIFIEDNAME) - UA_QualifiedName_clear(&qualified_name); if(retval != UA_STATUSCODE_GOOD) { send_opex_response(retval); @@ -2719,6 +2739,14 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in free(arg1); if(arg1 != NULL) free(arg2); + + UA_NodeId_clear(&node_id_arg_1); + UA_NodeId_clear(&node_id_arg_2); + + if(data_type == UA_TYPES_EXPANDEDNODEID) + UA_ExpandedNodeId_clear(&expanded_node_id_arg_1); + if(data_type == UA_TYPES_QUALIFIEDNAME) + UA_QualifiedName_clear(&qualified_name); } send_ok_response(); @@ -3718,6 +3746,48 @@ void handle_read_node_value(void *entity, bool entity_type, const char *req, int return; } + send_data_response(value, 29, 0); + + UA_Variant_clear(value); + UA_Variant_delete(value); +} + +/* + * Read 'value' of a node in the server. + */ +void handle_read_node_value_by_index(void *entity, bool entity_type, const char *req, int *req_index) +{ + int term_size; + UA_Variant *value = UA_Variant_new(); + UA_Variant_init(value); + UA_StatusCode retval; + + if(ei_decode_tuple_header(req, req_index, &term_size) < 0 || + term_size != 2) + errx(EXIT_FAILURE, ":handle_read_node_value requires a 2-tuple, term_size = %d", term_size); + + UA_NodeId node_id = assemble_node_id(req, req_index); + + unsigned long data_index; + if (ei_decode_ulong(req, req_index, &data_index) < 0) { + send_error_response("einval"); + return; + } + + if(entity_type) + retval = UA_Client_readValueAttribute((UA_Client *)entity, node_id, value); + else + retval = UA_Server_readValue((UA_Server *)entity, node_id, value); + + UA_NodeId_clear(&node_id); + + if(retval != UA_STATUSCODE_GOOD) { + UA_Variant_clear(value); + UA_Variant_delete(value); + send_opex_response(retval); + return; + } + if(UA_Variant_isEmpty(value)) { UA_Variant_clear(value); UA_Variant_delete(value); diff --git a/src/common.h b/src/common.h index 648338a..ead8f71 100644 --- a/src/common.h +++ b/src/common.h @@ -24,6 +24,7 @@ #include //#define DEBUG +#define ERLCMD_BUF_SIZE 32768 #ifdef DEBUG FILE *log_location; @@ -46,7 +47,6 @@ FILE *log_location; #define ONE_YEAR_MILLIS (1000ULL * 60 * 60 * 24 * 365) uint64_t current_time(); - #endif // UTIL_H static erlang_pid *caller_pid; @@ -67,7 +67,7 @@ void encode_array_dimensions_struct(char *resp, int *resp_index, void *data, int void encode_server_config(char *resp, int *resp_index, void *data); void send_subscription_timeout_response(void *data, int data_type, int data_len); void send_subscription_deleted_response(void *data, int data_type, int data_len); -void send_monitored_item_response(void *subscription_id, void *monitored_id, void *data, int data_type, int data_len); +void send_monitored_item_response(void *subscription_id, void *monitored_id, void *data, int data_type); void send_monitored_item_delete_response(void *subscription_id, void *monitored_id); void send_data_response(void *data, int data_type, int data_len); void send_error_response(const char *reason); @@ -127,4 +127,5 @@ void handle_read_node_historizing(void *entity, bool entity_type, const char *re void handle_read_node_executable(void *entity, bool entity_type, const char *req, int *req_index); void handle_read_node_event_notifier(void *entity, bool entity_type, const char *req, int *req_index); void handle_read_node_value(void *entity, bool entity_type, const char *req, int *req_index); +void handle_read_node_value_by_index(void *entity, bool entity_type, const char *req, int *req_index); void handle_read_node_value_by_data_type(void *entity, bool entity_type, const char *req, int *req_index); \ No newline at end of file diff --git a/src/opc_ua_client.c b/src/opc_ua_client.c index c4e9e58..d189ac7 100644 --- a/src/opc_ua_client.c +++ b/src/opc_ua_client.c @@ -28,127 +28,8 @@ static void deleteSubscriptionCallback(UA_Client *client, UA_UInt32 subscription static void dataChangeNotificationCallback(UA_Client *client, UA_UInt32 subscription_id, void *subContext, UA_UInt32 monitored_id, void *monContext, UA_DataValue *data) { - switch(data->value.type->typeIndex) - { - case UA_TYPES_BOOLEAN: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 0, 0); - break; - - case UA_TYPES_SBYTE: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 23, 0); - break; - - case UA_TYPES_BYTE: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 24, 0); - break; - - case UA_TYPES_INT16: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 25, 0); - break; - - case UA_TYPES_UINT16: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 26, 0); - break; - - case UA_TYPES_INT32: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 1, 0); - break; - - case UA_TYPES_UINT32: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 2, 0); - break; - - case UA_TYPES_INT64: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 15, 0); - break; - - case UA_TYPES_UINT64: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 16, 0); - break; - - case UA_TYPES_FLOAT: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 17, 0); - break; - - case UA_TYPES_DOUBLE: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 4, 0); - break; - - case UA_TYPES_STRING: - send_monitored_item_response(&subscription_id, &monitored_id, (*(UA_String *)data->value.data).data, 5, (*(UA_String *)data->value.data).length); - break; - - case UA_TYPES_DATETIME: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 15, 0); - break; - - case UA_TYPES_GUID: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 18, 0); - break; - - case UA_TYPES_BYTESTRING: - send_monitored_item_response(&subscription_id, &monitored_id, (*(UA_ByteString *)data->value.data).data, 5, (*(UA_ByteString *)data->value.data).length); - break; - - case UA_TYPES_XMLELEMENT: - send_monitored_item_response(&subscription_id, &monitored_id, (*(UA_XmlElement *)data->value.data).data, 5, (*(UA_XmlElement *)data->value.data).length); - break; - - case UA_TYPES_NODEID: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 12, 0); - break; - - case UA_TYPES_EXPANDEDNODEID: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 19, 0); - break; - - case UA_TYPES_STATUSCODE: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 20, 0); - break; - - case UA_TYPES_QUALIFIEDNAME: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 13, 0); - break; - - case UA_TYPES_LOCALIZEDTEXT: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 14, 0); - break; - - // TODO: UA_TYPES_EXTENSIONOBJECT - - // TODO: UA_TYPES_DATAVALUE - - // TODO: UA_TYPES_VARIANT - - // TODO: UA_TYPES_DIAGNOSTICINFO - - case UA_TYPES_SEMANTICCHANGESTRUCTUREDATATYPE: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 21, 0); - break; - - case UA_TYPES_TIMESTRING: - send_monitored_item_response(&subscription_id, &monitored_id, (*(UA_TimeString *)data->value.data).data, 5, (*(UA_TimeString *)data->value.data).length); - break; - - // TODO: UA_TYPES_VIEWATTRIBUTES - - case UA_TYPES_UADPNETWORKMESSAGECONTENTMASK: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 2, 0); - break; - - case UA_TYPES_XVTYPE: - - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 22, 0); - break; - - case UA_TYPES_ELEMENTOPERAND: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 27, 0); - break; - - default: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 2, -1); - break; - } + UA_Variant variant = data->value; + send_monitored_item_response(&subscription_id, &monitored_id, &variant, 29); } static void deleteMonitoredItemCallback(UA_Client *client, UA_UInt32 subscription_id, void *subContext, UA_UInt32 monitored_id, void *monContext) @@ -1031,6 +912,7 @@ static struct request_handler request_handlers[] = { // TODO: Add UA_Server_writeArrayDimensions, inverse name (read) {"write_node_value", handle_write_node_value}, {"read_node_value", handle_read_node_value}, + {"read_node_value_by_index", handle_read_node_value_by_index}, {"read_node_value_by_data_type", handle_read_node_value_by_data_type}, {"write_node_node_id", handle_write_node_node_id}, {"write_node_node_class", handle_write_node_node_class}, diff --git a/src/opc_ua_server.c b/src/opc_ua_server.c index de0b859..4bdb926 100644 --- a/src/opc_ua_server.c +++ b/src/opc_ua_server.c @@ -568,6 +568,7 @@ static struct request_handler request_handlers[] = { // TODO: Add UA_Server_writeArrayDimensions, {"write_node_value", handle_write_node_value}, {"read_node_value", handle_read_node_value}, + {"read_node_value_by_index", handle_read_node_value_by_index}, {"write_node_browse_name", handle_write_node_browse_name}, {"write_node_display_name", handle_write_node_display_name}, {"write_node_description", handle_write_node_description}, diff --git a/test/client_tests/arrays_test.exs b/test/client_tests/arrays_test.exs index cb2bedd..9e94723 100644 --- a/test/client_tests/arrays_test.exs +++ b/test/client_tests/arrays_test.exs @@ -78,19 +78,19 @@ defmodule ClientArraysTest do test "write/read array node by index (string)", state do node_id = NodeId.new(ns_index: state.ns_index, identifier_type: "string", identifier: "R1_TS1_Temperature") - resp = Client.read_node_value(state.c_pid, node_id, 0) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 0) assert resp == {:ok, ""} - resp = Client.read_node_value(state.c_pid, node_id, 1) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 1) assert resp == {:ok, ""} - resp = Client.read_node_value(state.c_pid, node_id, 2) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 2) assert resp == {:ok, ""} - resp = Client.read_node_value(state.c_pid, node_id, 3) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 3) assert resp == {:ok, ""} - resp = Client.read_node_value(state.c_pid, node_id, 4) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 4) assert resp == {:error, "BadTypeMismatch"} resp = Client.write_node_value(state.c_pid, node_id, 11, "alde103_1", 0) @@ -108,39 +108,42 @@ defmodule ClientArraysTest do resp = Client.write_node_value(state.c_pid, node_id, 11, "alde103_error", 4) assert resp == {:error, "BadTypeMismatch"} - resp = Client.read_node_value(state.c_pid, node_id, 0) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 0) assert resp == {:ok, "alde103_1"} - resp = Client.read_node_value(state.c_pid, node_id, 1) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 1) assert resp == {:ok, "alde103_2"} - resp = Client.read_node_value(state.c_pid, node_id, 2) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 2) assert resp == {:ok, "alde103_3"} - resp = Client.read_node_value(state.c_pid, node_id, 3) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 3) assert resp == {:ok, "alde103_4"} - resp = Client.read_node_value(state.c_pid, node_id, 4) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 4) assert resp == {:error, "BadTypeMismatch"} + + resp = Client.read_node_value(state.c_pid, node_id) + assert resp == {:ok, ["alde103_1", "alde103_2", "alde103_3", "alde103_4"]} end test "write/read array node by index (double)", state do node_id = NodeId.new(ns_index: state.ns_index, identifier_type: "string", identifier: "R1_TS1_Temperature") :ok = Server.write_node_blank_array(state.s_pid, node_id, 10, [4]) - resp = Client.read_node_value(state.c_pid, node_id, 0) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 0) assert resp == {:ok, 0.0} - resp = Client.read_node_value(state.c_pid, node_id, 1) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 1) assert resp == {:ok, 0.0} - resp = Client.read_node_value(state.c_pid, node_id, 2) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 2) assert resp == {:ok, 0.0} - resp = Client.read_node_value(state.c_pid, node_id, 3) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 3) assert resp == {:ok, 0.0} - resp = Client.read_node_value(state.c_pid, node_id, 4) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 4) assert resp == {:error, "BadTypeMismatch"} resp = Client.write_node_value(state.c_pid, node_id, 10, 103.0, 0) @@ -158,19 +161,75 @@ defmodule ClientArraysTest do resp = Client.write_node_value(state.c_pid, node_id, 10, 103103.0, 4) assert resp == {:error, "BadTypeMismatch"} - resp = Client.read_node_value(state.c_pid, node_id, 0) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 0) assert resp == {:ok, 103.0} - resp = Client.read_node_value(state.c_pid, node_id, 1) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 1) assert resp == {:ok, 103103.0} - resp = Client.read_node_value(state.c_pid, node_id, 2) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 2) assert resp == {:ok, 103103103.0} - resp = Client.read_node_value(state.c_pid, node_id, 3) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 3) assert resp == {:ok, 103103.103} - resp = Client.read_node_value(state.c_pid, node_id, 4) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 4) + assert resp == {:error, "BadTypeMismatch"} + + resp = Client.read_node_value(state.c_pid, node_id) + assert resp == {:ok, [103.0, 103103.0, 103103103.0, 103103.103]} + end + + test "write/read array node by index (node_id)", state do + node_id = NodeId.new(ns_index: state.ns_index, identifier_type: "string", identifier: "R1_TS1_Temperature") + :ok = Server.write_node_blank_array(state.s_pid, node_id, 16, [4]) + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 0) + assert resp == {:ok, %OpcUA.NodeId{identifier: 0, identifier_type: 0, ns_index: 0}} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 1) + assert resp == {:ok, %OpcUA.NodeId{identifier: 0, identifier_type: 0, ns_index: 0}} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 2) + assert resp == {:ok, %OpcUA.NodeId{identifier: 0, identifier_type: 0, ns_index: 0}} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 3) + assert resp == {:ok, %OpcUA.NodeId{identifier: 0, identifier_type: 0, ns_index: 0}} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 4) assert resp == {:error, "BadTypeMismatch"} + + resp = Client.write_node_value(state.c_pid, node_id, 16, node_id, 0) + assert resp == :ok + + resp = Client.write_node_value(state.c_pid, node_id, 16, node_id, 1) + assert resp == :ok + + resp = Client.write_node_value(state.c_pid, node_id, 16, node_id, 2) + assert resp == :ok + + resp = Client.write_node_value(state.c_pid, node_id, 16, node_id, 3) + assert resp == :ok + + # resp = Client.write_node_value(state.c_pid, node_id, 16, node_id, 4) + # assert resp == {:error, "BadTypeMismatch"} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 0) + assert resp == {:ok, node_id} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 1) + assert resp == {:ok, node_id} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 2) + assert resp == {:ok, node_id} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 3) + assert resp == {:ok, node_id} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 4) + assert resp == {:error, "BadTypeMismatch"} + + resp = Client.read_node_value(state.c_pid, node_id) + assert resp == {:ok, [node_id, node_id, node_id, node_id]} end end diff --git a/test/client_tests/write_read_attr_test.exs b/test/client_tests/write_read_attr_test.exs index fcef690..28eec3b 100644 --- a/test/client_tests/write_read_attr_test.exs +++ b/test/client_tests/write_read_attr_test.exs @@ -226,6 +226,9 @@ defmodule CClientWriteAttrTest do test "Write and Read Value Attributes", %{c_pid: c_pid, ns_index: ns_index} do node_id = NodeId.new(ns_index: ns_index, identifier_type: "string", identifier: "R1_TS1_Temperature") + c_response = Client.read_node_value(c_pid, node_id) + assert c_response == {:ok, nil} + assert :ok == Client.write_node_value(c_pid, node_id, 0, true) c_response = Client.read_node_value(c_pid, node_id) assert c_response == {:ok, true}