-module(opcua_codec_binary). %%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -include_lib("kernel/include/logger.hrl"). -include("opcua.hrl"). -include("opcua_internal.hrl"). -include("opcua_codec_context.hrl"). %%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% API Functions -export([decode/2, decode/3]). -export([encode/2, encode/3]). %%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -spec decode(opcua:codec_spec(), binary()) -> {term(), binary()}. decode(Spec, Data) -> decode(Spec, Data, #{}). -spec encode(opcua:codec_spec(), term()) -> {iolist(), term()}. encode(Spec, Data) -> encode(Spec, Data, #{}). -spec decode(opcua:codec_spec(), binary(), opcua_codec_context:options()) -> {term(), binary()} | {term(), binary(), opcua_codec_conrtext:issues()}. decode(Spec, Data, Opts) -> Ctx = opcua_codec_context:new(decoding, Opts), try decode_type(Ctx, Spec, iolist_to_binary(Data)) of {Result, Rest, Ctx2} -> opcua_codec_context:finalize(Ctx2, Result, Rest) catch T:R:S -> opcua_codec_context:resolve(T, R, S) end. -spec encode(opcua:codec_spec(), term(), opcua_codec_context:options()) -> {iolist(), term()} | {iolist(), term(), opcua_codec_conrtext:issues()}. encode(Spec, Data, Opts) -> Ctx = opcua_codec_context:new(encoding, Opts), try encode_type(Ctx, Spec, Data) of {Result, Rest, Ctx2} -> opcua_codec_context:finalize(Ctx2, Result, Rest) catch T:R:S -> opcua_codec_context:resolve(T, R, S) end. %%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% decoding decode_type(Ctx, [{Key, _} | _] = Spec, Data) when is_atom(Key) -> decode_map(Ctx, Spec, Data); decode_type(Ctx, Spec, Data) when is_list(Spec) -> decode_list(Ctx, Spec, Data); decode_type(Ctx, #opcua_node_id{type = string, value = Name}, Data) when ?IS_BUILTIN_TYPE_NAME(Name) -> decode_builtin(Ctx, Name, Data); decode_type(Ctx, #opcua_node_id{type = numeric, value = Num}, Data) when ?IS_BUILTIN_TYPE_ID(Num) -> decode_builtin(Ctx, opcua_codec:builtin_type_name(Num), Data); decode_type(#ctx{space = Space} = Ctx, #opcua_node_id{} = NodeId, Data) -> case opcua_space:schema(Space, NodeId) of undefined -> opcua_codec_context:issue_schema_not_found(Ctx, NodeId); Schema -> decode_schema(Ctx, Schema, Data) end; decode_type(Ctx, NodeSpec, Data) -> decode_type(Ctx, opcua_node:id(NodeSpec), Data). decode_map(Ctx, Spec, Data) -> decode_map(Ctx, Spec, Data, #{}). decode_map(Ctx, [], Data, Acc) -> {Acc, Data, Ctx}; decode_map(Ctx, [{Key, Spec} | Rest], Data, Acc) when is_atom(Key) -> {Value, Data2, Ctx2} = decode_type(?PUSHK(Ctx, Key), Spec, Data), decode_map(?POPK(Ctx2, Key), Rest, Data2, Acc#{Key => Value}). decode_list(Ctx, Specs, Data) -> decode_list(Ctx, Specs, Data, 0, []). decode_list(Ctx, [], Data, _Idx, Acc) -> {lists:reverse(Acc), Data, Ctx}; decode_list(Ctx, [Spec | Rest], Data, Idx, Acc) -> {Value, Data2, Ctx2} = decode_type(?PUSHK(Ctx, Idx), Spec, Data), decode_list(?POPK(Ctx2, Idx), Rest, Data2, Idx + 1, [Value | Acc]). decode_builtin(Ctx, extension_object, Data) -> decode_extension_object(Ctx, Data); decode_builtin(Ctx, variant, Data) -> decode_variant(Ctx, Data); decode_builtin(Ctx, data_value, Data) -> decode_data_value(Ctx, Data); decode_builtin(Ctx, Type, Data) -> {V, R} = opcua_codec_binary_builtin:decode(Type, Data), {V, R, Ctx}. decode_schema(Ctx, #opcua_structure{with_options = false, fields = Fields}, Data) -> decode_fields(Ctx, Fields, Data); decode_schema(Ctx, #opcua_structure{with_options = true, fields = Fields}, Data) -> {Mask, Data2, Ctx2} = decode_builtin(?PUSHF(Ctx, mask), uint32, Data), decode_masked_fields(?POPF(Ctx2, mask), Mask, Fields, Data2); decode_schema(Ctx, #opcua_union{fields = Fields}, Data) -> {SwitchValue, Data2, Ctx2} = decode_builtin(?PUSHF(Ctx, switch), uint32, Data), resolve_union_value(?POPF(Ctx2, switch), SwitchValue, Fields, Data2); decode_schema(Ctx, #opcua_enum{} = Schema, Data) -> {Value, Data2, Ctx2} = decode_builtin(?PUSHF(Ctx, value), int32, Data), {opcua_codec:resolve_enum(Schema, Value), Data2, ?POPF(Ctx2, value)}; decode_schema(Ctx, #opcua_option_set{mask_type = MaskNodeId} = Schema, Data) -> {Value, Data2, Ctx2} = decode_type(?PUSHF(Ctx, value), MaskNodeId, Data), {opcua_codec:resolve_option_set(Schema, Value), Data2, ?POPF(Ctx2, value)}; decode_schema(Ctx, #opcua_builtin{builtin_node_id = BuiltinNodeId}, Data) -> decode_type(Ctx, BuiltinNodeId, Data). decode_fields(Ctx, Fields, Data) -> decode_fields(Ctx, Fields, Data, #{}). decode_fields(Ctx, [], Data, Acc) -> {Acc, Data, Ctx}; decode_fields(Ctx, [Field | Fields], Data, Acc) -> Name = Field#opcua_field.name, {Value, Data2, Ctx2} = decode_field(?PUSHF(Ctx, Name), Field, Data), Acc2 = maps:put(Name, Value, Acc), decode_fields(?POPF(Ctx2, Name), Fields, Data2, Acc2). decode_field(Ctx, #opcua_field{node_id = NodeId, value_rank = -1}, Data) -> decode_type(Ctx, NodeId, Data); decode_field(Ctx, #opcua_field{node_id = NodeId, value_rank = N}, Data) when N >= 1 -> {Dims, {Data2, Ctx2}} = lists:mapfoldl(fun(_, {D, C}) -> {R, D2, C2} = decode_builtin(C, int32, D), {R, {D2, C2}} end, {Data, Ctx}, lists:seq(1, N)), decode_field_array(Ctx2, NodeId, Dims, Data2, 0, []). %% TODO: check the order, specs are somewhat unclear decode_field_array(Ctx, _NodeId, [-1], Data, _Idx, _Acc) -> {[], Data, Ctx}; decode_field_array(Ctx, NodeId, [Dim], Data, _Idx, _Acc) -> decode_list(Ctx, [NodeId || _ <- lists:seq(1, Dim)], Data); decode_field_array(Ctx, NodeId, [1 | Dims], Data, Idx, Acc) -> {Row, Data2, Ctx2} = decode_field_array(?PUSHK(Ctx, Idx), NodeId, Dims, Data, 0, []), {[Row | Acc], Data2, ?POPK(Ctx2, Idx)}; decode_field_array(Ctx, NodeId, [Dim | Dims], Data, Idx, Acc) -> {Row, Data2, Ctx2} = decode_field_array(?PUSHK(Ctx, Idx), NodeId, Dims, Data, 0, []), decode_field_array(?POPK(Ctx2, Idx), NodeId, [Dim - 1 | Dims], Data2, Idx + 1, [Row|Acc]). decode_masked_fields(Ctx, Mask, Fields, Data) -> BooleanMask = [X==1 || <> <= <>], {BooleanMask1, _} = lists:split(length(Fields), lists:reverse(BooleanMask)), BooleanMask2 = lists:filtermap(fun({_Idx, Boolean}) -> Boolean end, lists:zip( lists:seq(1, length(BooleanMask1)), BooleanMask1)), {IntMask, _} = lists:unzip(BooleanMask2), MaskedFields = [Field || Field = #opcua_field{value=Value, is_optional=Optional} <- Fields, not Optional or lists:member(Value, IntMask)], decode_fields(Ctx, MaskedFields, Data). resolve_union_value(Ctx, SwitchValue, Fields, Data) -> [Field] = [F || F = #opcua_field{value=Value, is_optional=Optional} <- Fields, Optional and (Value == SwitchValue)], decode_fields(Ctx, [Field], Data). decode_extension_object(Ctx, Data) -> {TypeId, <>, Ctx2} = decode_builtin(?PUSHF(Ctx, node_id), node_id, Data), case decode_extension_object(?POPF(Ctx2, node_id), Mask, TypeId, T) of {?UNDEF_EXT_OBJ, _Data2, _Ctx3} = Result -> Result; {#opcua_extension_object{type_id = NodeSpec, encoding = byte_string, body = Body} = ExtObj, Data2, Ctx3} -> %TODO: Figure out if we shouldn't fail when we can't resolve the encoding ? case resolve_encoding(Ctx3, NodeSpec) of {NodeId, Enc} when Enc =:= binary; Enc =:= undefined -> try decode_type(?PUSHF(Ctx3, object), NodeId, Body) of {DecodedBody, _Rest, Ctx4} -> ExtObj2 = ExtObj#opcua_extension_object{type_id = NodeId, body = DecodedBody}, {ExtObj2, Data2, ?POPF(Ctx4, object)} catch ErrorType:ErrorReason:Stack -> opcua_codec_context:catch_and_continue(ErrorType, ErrorReason, Stack, Ctx, ?UNDEF_EXT_OBJ, Data2) end end; {#opcua_extension_object{encoding = EncInfo}, _Data2, Ctx3} -> opcua_codec_context:issue_encoding_not_supported(Ctx3, EncInfo) end. resolve_encoding(#ctx{space = Space}, TypeDescSpec) -> case opcua_space:data_type(Space, TypeDescSpec) of undefined -> {opcua_node:id(TypeDescSpec), undefined}; Result -> Result end. decode_extension_object(Ctx, 16#00, TypeId, T) -> {#opcua_extension_object{type_id = TypeId, encoding = undefined, body = undefined}, T, Ctx}; decode_extension_object(Ctx, 16#01, TypeId, T) -> {Body, T1, Ctx2} = decode_builtin(Ctx, byte_string, T), {#opcua_extension_object{type_id = TypeId, encoding = byte_string, body = Body}, T1, Ctx2}; decode_extension_object(Ctx, 16#02, TypeId, T) -> {Body, T1, Ctx2} = decode_builtin(Ctx, xml, T), {#opcua_extension_object{type_id = TypeId, encoding = xml, body = Body}, T1, Ctx2}; decode_extension_object(Ctx, _, _, _) -> opcua_codec_context:issue(Ctx, bad_extension_object). decode_variant(Ctx, <<0:6, Bin/binary>>) -> {undefined, Bin, Ctx}; decode_variant(Ctx, <>) -> decode_variant(Ctx, TypeId, DimFlag, ArrayFlag, Bin); decode_variant(Ctx, _) -> opcua_codec_context:issue(Ctx, bad_variant). decode_variant(Ctx, TypeId, <<0:1>>, <<0:1>>, Bin) -> TypeName = opcua_codec:builtin_type_name(TypeId), {Value, Bin2, Ctx2} = decode_builtin(?PUSHF(Ctx, value), TypeName, Bin), {#opcua_variant{type = TypeName, value = Value}, Bin2, ?POPF(Ctx2, value)}; decode_variant(Ctx, TypeId, <<0:1>>, <<1:1>>, Bin) -> TypeName = opcua_codec:builtin_type_name(TypeId), {_, Array, Bin2, Ctx2} = decode_array(Ctx, TypeName, Bin), {#opcua_variant{type = TypeName, value = Array}, Bin2, Ctx2}; decode_variant(Ctx, TypeId, <<1:1>>, <<1:1>>, Bin) -> TypeName = opcua_codec:builtin_type_name(TypeId), {MultiArray, Bin2, Ctx2} = decode_multi_array(Ctx, TypeName, Bin), {#opcua_variant{type = TypeName, value = MultiArray}, Bin2, Ctx2}. decode_multi_array(Ctx, Type, Bin) -> {L, ObjectArray, Bin2, Ctx2} = decode_array(Ctx, Type, Bin), {_, DimArray, Bin3, Ctx3} = decode_array(Ctx2, int32, Bin2), ExpSize = lists:foldl(fun(V, A) -> A * V end, 1, DimArray), case L =:= ExpSize of false -> opcua_codec_context:issue(Ctx3, bad_array); true -> {honor_dimensions(L, ObjectArray, DimArray), Bin3, Ctx3} end. decode_array(Ctx, Type, Bin) -> {Length, Bin2, Ctx2} = decode_builtin(?PUSHF(Ctx, length), int32, Bin), decode_array(?POPF(Ctx2, length), Type, Bin2, Length). decode_array(Ctx, _Type, Bin, -1) -> {undefined, undefined, Bin, Ctx}; decode_array(Ctx, Type, Bin, N) -> decode_array(Ctx, Type, Bin, N, N, []). decode_array(Ctx, _Type, Bin, L, 0, Acc) -> {L, lists:reverse(Acc), Bin, Ctx}; decode_array(Ctx, Type, Bin, L, N, Acc) -> {Elem, Bin2, Ctx2} = decode_type(Ctx, Type, Bin), decode_array(Ctx2, Type, Bin2, L, N - 1, [Elem | Acc]). honor_dimensions(L, ObjectArray, [L]) -> ObjectArray; honor_dimensions(ArrayLen, ObjectArray, [Dim | Rest]) -> DimSize = ArrayLen div Dim, [honor_dimensions(DimSize, E, Rest) || E <- lists_nsplit(ObjectArray, DimSize, Dim, [])]. lists_nsplit([], _, 0, Acc) -> lists:reverse(Acc); lists_nsplit(L, S, C, Acc) -> {E, L2} = lists:split(S, L), lists_nsplit(L2, S, C - 1, [E | Acc]). decode_data_value(Ctx, <<0:2, Mask:6/bits, Bin/binary>>) -> Types = [ {value, variant, undefined}, {status, status_code, good}, {source_timestamp, date_time, 0}, {server_timestamp, date_time, 0}, {source_pico_seconds, uint16, 0}, {server_pico_seconds, uint16, 0} ], RecordInfo = {opcua_data_value, record_info(fields, opcua_data_value)}, decode_masked(Ctx, RecordInfo, Mask, Types, Bin); decode_data_value(Ctx, _) -> opcua_codec_context:issue(Ctx, bad_data_value). decode_masked(Ctx, RecordInfo, Mask, Fields, Bin) -> BooleanMask = lists:reverse([X == 1 || <> <= Mask]), {DecFields, Bin2, Ctx2} = lists:foldl(fun ({{N, _, D}, false}, {Acc, B, C}) -> {[{N, D} | Acc], B, C}; ({{N, T, _}, true}, {Acc, B, C}) -> {V, B2, C2} = decode_type(?PUSHF(C, N), T, B), {[{N, V} | Acc], B2, ?POPF(C2, N)} end, {[], Bin, Ctx}, lists:zip(Fields, BooleanMask)), {RecordName, RecordFields} = RecordInfo, Values = [proplists:get_value(Key, DecFields) || Key <- RecordFields], {list_to_tuple([RecordName | Values]), Bin2, Ctx2}. %%% encoding encode_type(Ctx, [{Key, _} | _] = Spec, Data) when is_atom(Key) -> encode_map(Ctx, Spec, Data); encode_type(Ctx, Spec, Data) when is_list(Spec) -> encode_list(Ctx, Spec, Data); encode_type(Ctx, #opcua_node_id{type = string, value = Name}, Data) when ?IS_BUILTIN_TYPE_NAME(Name) -> encode_builtin(Ctx, Name, Data); encode_type(Ctx, #opcua_node_id{type = numeric, value = Num}, Data) when ?IS_BUILTIN_TYPE_ID(Num) -> encode_builtin(Ctx, opcua_codec:builtin_type_name(Num), Data); encode_type(#ctx{space = Space} = Ctx, #opcua_node_id{} = NodeId, Data) -> case opcua_space:schema(Space, NodeId) of undefined -> opcua_codec_context:issue_schema_not_found(Ctx, NodeId); Schema -> encode_schema(Ctx, Schema, Data) end; encode_type(Ctx, NodeSpec, Data) -> encode_type(Ctx, opcua_node:id(NodeSpec), Data). encode_map(Ctx, Spec, Data) -> encode_map(Ctx, Spec, Data, []). encode_map(Ctx, [], Data, Acc) -> {lists:reverse(Acc), Data, Ctx}; encode_map(Ctx, [{Key, Spec} | Rest], Data, Acc) -> case maps:take(Key, Data) of error -> opcua_codec_context:issue(Ctx, missing_key); {Value, Data2} -> {Result, _, Ctx2} = encode_type(?PUSHK(Ctx, Key), Spec, Value), encode_map(?POPK(Ctx2, Key), Rest, Data2, [Result | Acc]) end. encode_list(Ctx, Spec, Data) -> encode_list(Ctx, Spec, Data, 0, []). encode_list(Ctx, [], Data, _Idx, Acc) -> {lists:reverse(Acc), Data, Ctx}; encode_list(Ctx, [Spec | Rest], [Value | Data], Idx, Acc) -> {Result, _, Ctx2} = encode_type(?PUSHK(Ctx, Idx), Spec, Value), encode_list(?POPK(Ctx2, Idx), Rest, Data, Idx + 1, [Result | Acc]). encode_builtin(Ctx, extension_object, ExtensionObject) -> encode_extension_object(Ctx, ExtensionObject); encode_builtin(Ctx, variant, Variant) -> encode_variant(Ctx, Variant); encode_builtin(Ctx, data_value, DataValue) -> encode_data_value(Ctx, DataValue); encode_builtin(Ctx, Type, Data) -> {opcua_codec_binary_builtin:encode(Type, Data), undefined, Ctx}. encode_schema(Ctx, #opcua_structure{with_options = false, fields = Fields}, Data) -> encode_fields(Ctx, Fields, Data); encode_schema(Ctx, #opcua_structure{with_options = true, fields = Fields}, Data) -> encode_masked_fields(Ctx, Fields, Data); encode_schema(Ctx, #opcua_union{fields = Fields}, UnionMap) -> [Name] = maps:keys(UnionMap), [Field] = [Field || Field = #opcua_field{name = FieldName} <- Fields, FieldName == Name], {SwitchValue, _, Ctx2} = encode_builtin(?PUSHF(Ctx, switch), uint32, Field#opcua_field.value), {EncodedValue, _, Ctx3} = encode_field(?POPF(Ctx2, switch), Field, maps:get(Name, UnionMap)), {[SwitchValue, EncodedValue], undefined, Ctx3}; encode_schema(Ctx, #opcua_enum{fields = Fields}, Name) -> [Field] = [Field || Field = #opcua_field{name=FieldName} <- Fields, FieldName==Name], {Value, Data2, Ctx2} = encode_builtin(?PUSHF(Ctx, value), int32, Field#opcua_field.value), {Value, Data2, ?POPF(Ctx2, value)}; encode_schema(Ctx, #opcua_option_set{mask_type = MaskNodeId, fields = Fields}, OptionSet) -> ChosenFields = [Field || Field = #opcua_field{name = Name} <- Fields, lists:member(Name, OptionSet)], Int = lists:foldl(fun(X, Acc) -> Acc bxor (1 bsl X#opcua_field.value) end, 0, ChosenFields), encode_type(Ctx, MaskNodeId, Int); encode_schema(Ctx, #opcua_builtin{builtin_node_id = BuiltinNodeId}, Data) -> encode_type(Ctx, BuiltinNodeId, Data). encode_masked_fields(Ctx, Fields, Data) -> encode_masked_fields(Ctx, Fields, Data, [], []). encode_masked_fields(Ctx, [], Data, Mask, Acc) -> BinFields1 = lists:reverse(Acc), Mask1 = lists:foldl(fun(Bit, M) -> M bxor Bit end, 0, [1 bsl (N-1) || N <- Mask]), {BinMask1, _, Ctx2} = encode_builtin(?PUSHF(Ctx, mask), uint32, Mask1), {iolist_to_binary([BinMask1, BinFields1]), Data, ?POPF(Ctx2, mask)}; encode_masked_fields(Ctx, [Field = #opcua_field{is_optional = false, name = Name} | Fields], Data, Mask, Acc) -> case maps:take(Name, Data) of error -> opcua_codec_context:issue(?PUSHF(Ctx, Name), missing_required_field); {Value, Data2} -> {EncodedField, _, Ctx2} = encode_field(Ctx, Field, Value), encode_masked_fields(Ctx2, Fields, Data2, Mask, [EncodedField | Acc]) end; encode_masked_fields(Ctx, [Field = #opcua_field{is_optional = true, name = Name} | Fields], Data, Mask, Acc) -> case maps:take(Name, Data) of {Value, Data2} -> {EncodedField, _, Ctx2} = encode_field(Ctx, Field, Value), encode_masked_fields(Ctx2, Fields, Data2, [Field#opcua_field.value|Mask], [EncodedField | Acc]); error -> encode_masked_fields(Ctx, Fields, Data, Mask, Acc) end. encode_fields(Ctx, Fields, Data) -> encode_fields(Ctx, Fields, Data, []). encode_fields(Ctx, [], _Data, Acc) -> {iolist_to_binary(lists:reverse(Acc)), undefined, Ctx}; encode_fields(Ctx, [#opcua_field{name = Name} = Field | Fields], Data, Acc) -> case maps:take(Name, Data) of error -> opcua_codec_context:issue(?PUSHF(Ctx, Name), missing_required_field); {Value, Data2} -> {FieldValue, _, Ctx2} = encode_field(Ctx, Field, Value), encode_fields(Ctx2, Fields, Data2, [FieldValue|Acc]) end. encode_field(Ctx, #opcua_field{name = Name, node_id = NodeId, value_rank = -1}, Data) -> {Result, Data2, Ctx2} = encode_type(?PUSHF(Ctx, Name), NodeId, Data), {Result, Data2, ?POPF(Ctx2, Name)}; encode_field(Ctx, #opcua_field{name = Name, node_id = NodeId, value_rank = N}, Array) when N >= 1, is_list(Array) -> {Dims, Ctx2} = resolve_dims(?PUSHF(Ctx, Name), Array, []), {Array2, Ctx3} = encode_array(Ctx2, NodeId, Array, []), {[Dims, Array2], undefined, ?POPF(Ctx3, Name)}. resolve_dims(Ctx, List, Acc) when is_list(List) -> {EncDim, _, Ctx2} = encode_type(Ctx, int32, length(List)), case List of [El | _] -> resolve_dims(Ctx2, El, [EncDim|Acc]); [] -> {lists:reverse([EncDim | Acc]), Ctx2} end; resolve_dims(Ctx, _El, Acc) -> {lists:reverse(Acc), Ctx}. %% TODO: check order here as with decoding encode_array(Ctx, _NodeId, [], Acc) -> {lists:reverse(Acc), Ctx}; encode_array(Ctx, NodeId, [Array | Arrays], Acc) when is_list(Array) -> {BinArray, Ctx2} = encode_array(Ctx, NodeId, Array, []), encode_array(Ctx2, NodeId, Arrays, [BinArray | Acc]); encode_array(Ctx, NodeId, Array, Acc) -> {BinArray, [], Ctx2} = encode_list(Ctx, lists:duplicate(length(Array), NodeId), Array), {[BinArray | Acc], Ctx2}. encode_extension_object(Ctx, ?UNDEF_EXT_OBJ) -> {Result, _, Ctx2} = encode_builtin(Ctx, node_id, ?UNDEF_NODE_ID), {[Result, <<16#00>>], undefined, Ctx2}; encode_extension_object(#ctx{space = Space} = Ctx, #opcua_extension_object{type_id = NodeSpec, encoding = byte_string, body = Body}) -> Id = case opcua_space:type_descriptor(Space, NodeSpec, binary) of #opcua_node_id{} = TypeDescId -> TypeDescId; undefined -> %TODO: figure out what type should be used when there is not type % descriptor. For noew we return the type itself, but maybe % we should be looking up parent types ? Use built-in types % if this is a subtype of one ? opcua_node:id(NodeSpec) end, {EncodedBody, _, Ctx2} = encode_type(Ctx, NodeSpec, Body), {NodeIdData, _, Ctx3} = encode_builtin(Ctx2, node_id, Id), %% NOTE: encoding byte strings also encodes the 'Length' of those as prefix {BodyData, _, Ctx4} = encode_builtin(Ctx3, byte_string, EncodedBody), FlagData = <<16#01>>, {[NodeIdData, FlagData, BodyData], undefined, Ctx4}; encode_extension_object(Ctx, #opcua_extension_object{encoding = EncInfo}) -> opcua_codec_context:issue_encoding_not_supported(Ctx, EncInfo). encode_variant(Ctx, #opcua_variant{type = Type, value = MultiArray}) when is_list(MultiArray) -> TypeId = opcua_codec:builtin_type_id(Type), case pack_array(Ctx, Type, MultiArray) of {0, _, _, Ctx2} -> {<<0:2, TypeId:6>>, undefined, Ctx2}; {Length, Value, [Length], Ctx2} -> {LengthData, _, Ctx3} = encode_builtin(Ctx2, int32, Length), {[<<1:1, 0:1, TypeId:6>>, LengthData, Value], undefined, Ctx3}; {Length, Value, Dims, Ctx2} -> {LengthData, _, Ctx3} = encode_builtin(Ctx2, int32, Length), {DimLengthData, _, Ctx4} = encode_builtin(Ctx3, int32, length(Dims)), {DimsData, Ctx5} = lists:foldl(fun(Dim, {Acc, C}) -> {Res, _, C2} = encode_builtin(C, int32, Dim), {[Res | Acc], C2} end, {[], Ctx4}, Dims), {[<<3:2, TypeId:6>>, LengthData, Value, DimLengthData, DimsData], undefined, Ctx5} end; encode_variant(Ctx, #opcua_variant{type = Type, value = Value}) -> TypeId = opcua_codec:builtin_type_id(Type), {BinValue, _, Ctx2} = encode_builtin(?PUSHF(Ctx, value), Type, Value), {[<<0:2, TypeId:6>>, BinValue], undefined, ?POPF(Ctx2, value)}. pack_array(Ctx, Type, Values) -> {L, Acc, Dims, Ctx2} = pack_array(Ctx, Type, Values, 0, []), {L, lists:reverse(Acc), Dims, Ctx2}. pack_array(Ctx, Type, [Row | Rest] = Values, Idx, Acc) when is_list(Row) -> Dim = length(Values), {DimLen, Acc2, SubDims, Ctx2} = pack_array(Ctx, Type, Row, Idx + 1, Acc), {Len, Acc3, Ctx3} = pack_array_rows(?PUSHK(Ctx2, Idx), Type, Rest, DimLen, Acc2, DimLen, SubDims), {Len, Acc3, [Dim | SubDims], ?POPK(Ctx3, Idx)}; pack_array(Ctx, Type, Values, _Idx, Acc) -> pack_array_values(Ctx, Type, Values, 0, Acc). pack_array_rows(Ctx, _Type, [], Len, Acc, _ExpLen, _ExpDims) -> {Len, Acc, Ctx}; pack_array_rows(Ctx, Type, [Row | Rest], Len, Acc, ExpLen, ExpDims) when is_list(Row) -> case pack_array(Ctx, Type, Row, 0, Acc) of {ExpLen, Acc2, ExpDims, Ctx2} -> pack_array_rows(Ctx2, Type, Rest, Len + ExpLen, Acc2, ExpLen, ExpDims); {_, _, _, Ctx2} -> opcua_codec_context:issue(Ctx2, malformed_array) end; pack_array_rows(Ctx, _Type, _Values, _Len, _Acc, _ExpLen, _ExpDims) -> opcua_codec_context:issue(Ctx, malformed_array). pack_array_values(Ctx, _Type, [], Idx, Acc) -> {Idx, Acc, [Idx], Ctx}; pack_array_values(Ctx, Type, [Val | Rest], Idx, Acc) -> {Data, _, Ctx2} = encode_type(?PUSHK(Ctx, Idx), Type, Val), pack_array_values(?POPK(Ctx2, Idx), Type, Rest, Idx + 1, [Data | Acc]). encode_data_value(Ctx, DataValue) -> #opcua_data_value{ value = Value, status = Status, source_timestamp = SourceTimestamp, source_pico_seconds = SourcePicoSeconds, server_timestamp = ServerTimestamp, server_pico_seconds = ServerPicoSeconds } = DataValue, Types = [ {value, variant, Value}, {status, status_code, maybe_undefined(Status, 0)}, {source_timestamp, date_time, maybe_undefined(SourceTimestamp, 0)}, {source_pico_seconds, uint16, maybe_undefined(SourcePicoSeconds, 0)}, {server_timestamp, date_time, maybe_undefined(ServerTimestamp, 0)}, {server_pico_seconds, uint16, maybe_undefined(ServerPicoSeconds, 0)} ], encode_masked(Ctx, Types). maybe_undefined(Value, Value) -> undefined; maybe_undefined(Value, _Cond) -> Value. encode_masked(Ctx, TypeList) -> encode_masked(Ctx, TypeList, <<>>, []). encode_masked(Ctx, [], Mask, Acc) -> {[<<0:(8-bit_size(Mask)), Mask/bits>>, lists:reverse(Acc)], undefined, Ctx}; encode_masked(Ctx, [{_Name, _Type, undefined} | Rest], Mask, Acc) -> encode_masked(Ctx, Rest, <<0:1, Mask/bits>>, Acc); encode_masked(Ctx, [{Name, Type, Value} | Rest], Mask, Acc) -> {Data, _, Ctx2} = encode_type(?PUSHF(Ctx, Name), Type, Value), encode_masked(?POPF(Ctx2, Name), Rest, <<1:1, Mask/bits>>, [Data | Acc]).