-module(opcua_codec_binary_builtin). -include("opcua.hrl"). -include("opcua_internal.hrl"). -export([encode/2, decode/2]). decode(boolean, <<0, T/binary>>) -> {false, T}; decode(boolean, <<_Bin:1/binary, T/binary>>) -> {true, T}; decode(byte, <>) -> {Byte, T}; decode(sbyte, <>) -> {SByte, T}; decode(uint16, <>) -> {UInt16, T}; decode(uint32, <>) -> {UInt32, T}; decode(uint64, <>) -> {UInt64, T}; decode(int16, <>) -> {Int16, T}; decode(int32, <>) -> {Int32, T}; decode(int64, <>) -> {Int64, T}; decode(float, <>) -> {Float, T}; decode(double, <>) -> {Double, T}; decode(string, <>) when Int32 == -1 -> {undefined, T}; decode(string, <>) -> {String, T}; decode(date_time, Bin) -> decode(int64, Bin); decode(guid, Bin) -> decode_guid(Bin); decode(xml, Bin) -> decode(string, Bin); decode(status_code, Bin) -> {Code, Rest} = decode(uint32, Bin), {opcua_nodeset:status_name(Code, Code), Rest}; decode(byte_string, Bin) -> decode(string, Bin); decode(node_id, <>) -> decode_node_id(Mask, Bin); decode(expanded_node_id, <>) -> decode_expanded_node_id(Mask, Bin); decode(diagnostic_info, <>) -> decode_diagnostic_info(Mask, Bin); decode(qualified_name, Bin) -> decode_qualified_name(Bin); decode(localized_text, <>) -> decode_localized_text(Mask, Bin); decode(_Type, _Bin) -> throw(bad_decoding_error). encode(boolean, false) -> <<0:8>>; encode(boolean, true) -> <<1:8>>; encode(byte, Byte) when is_integer(Byte) -> <>; encode(sbyte, SByte) when is_integer(SByte)-> <>; encode(uint16, UInt16) when is_integer(UInt16)-> <>; encode(uint32, UInt32) when is_integer(UInt32)-> <>; encode(uint64, UInt64) when is_integer(UInt64)-> <>; encode(int16, Int16) when is_integer(Int16)-> <>; encode(int32, Int32) when is_integer(Int32)-> <>; encode(int64, Int64) when is_integer(Int64)-> <>; encode(float, Float) when is_float(Float)-> <>; encode(double, Double) when is_float(Double) -> <>; encode(double, Int) when is_integer(Int) -> <>; encode(string, String) when String == undefined -> <<-1:32/little-signed-integer>>; encode(string, Atom) when is_atom(Atom) -> encode(string, atom_to_binary(Atom)); encode(string, String) when is_binary(String) -> <<(byte_size(String)):32/little-signed-integer, String/binary>>; encode(date_time, DateTime) when is_integer(DateTime) -> encode(int64, DateTime); encode(guid, Guid) when is_binary(Guid) -> encode_guid(Guid); encode(xml, Bin) when is_binary(Bin) -> encode(string, Bin); encode(status_code, Atom) when is_atom(Atom) -> encode(uint32, opcua_nodeset:status_code(Atom)); encode(status_code, UInt32) when is_integer(UInt32), UInt32 >= 0 -> encode(uint32, UInt32); encode(byte_string, Bin) -> encode(string, Bin); encode(node_id, NodeId) -> encode_node_id(NodeId); encode(expanded_node_id, ExpandedNodeId) -> encode_expanded_node_id(ExpandedNodeId); encode(diagnostic_info, DiagnosticInfo) -> encode_diagnostic_info(DiagnosticInfo); encode(qualified_name, QualifiedName) -> encode_qualified_name(QualifiedName); encode(localized_text, LocalizedText) -> encode_localized_text(LocalizedText); encode(Type, Value) -> throw({bad_encoding_error, Type, Value}). %% internal decode_guid(<>) -> {<>, T}. decode_node_id(16#00, <>) -> {#opcua_node_id{ns = 0, type = numeric, value = Id}, T}; decode_node_id(16#01, <>) -> {#opcua_node_id{ns = Ns, type = numeric, value = Id}, T}; decode_node_id(16#02, <>) -> {Id, T} = decode(uint32, Bin), {#opcua_node_id{ns = Ns, type = numeric, value = Id}, T}; decode_node_id(16#03, <>) -> {Id, T} = decode(string, Bin), {#opcua_node_id{ns = Ns, type = string, value = Id}, T}; decode_node_id(16#04, <>) -> {Id, T} = decode(guid, Bin), {#opcua_node_id{ns = Ns, type = guid, value = Id}, T}; decode_node_id(16#05, <>) -> {Id, T} = decode(string, Bin), {#opcua_node_id{ns = Ns, type = opaque, value = Id}, T}. decode_expanded_node_id(Mask, Bin) -> {NodeId, T} = decode_node_id(Mask rem 16#40, Bin), NamespaceUriFlag = (Mask div 16#80 == 1), ServerIndexFlag = ((Mask rem 16#80) div 16#40 == 1), Types = [{namespace_uri, string, undefined}, {server_index, uint32, undefined}], BooleanMask = [NamespaceUriFlag, ServerIndexFlag], RecordInfo = {opcua_expanded_node_id, record_info(fields, opcua_expanded_node_id)}, {ExpandedNodeId, T1} = decode_masked(RecordInfo, BooleanMask, Types, T), {ExpandedNodeId#opcua_expanded_node_id{node_id = NodeId}, T1}. decode_diagnostic_info(<<0:1, Mask:7/bits>>, Bin) -> Types = [{symbolic_id, int32, undefined}, {namespace_uri, int32, undefined}, {locale, int32, undefined}, {localized_text, int32, undefined}, {additional_info, string, undefined}, {inner_status_code, status_code, undefined}, {inner_diagnostic_info, diagnostic_info, undefined}], RecordInfo = {opcua_diagnostic_info, record_info(fields, opcua_diagnostic_info)}, decode_masked(RecordInfo, Mask, Types, Bin). decode_qualified_name(Bin) -> {[NamespaceIndex, Name], T} = decode_multi([uint16, string], Bin), {#opcua_qualified_name{ns = NamespaceIndex, name = Name}, T}. decode_localized_text(<<0:6, Mask:2/bits>>, Bin) -> Types = [{locale, string, undefined}, {text, string, undefined}], RecordInfo = {opcua_localized_text, record_info(fields, opcua_localized_text)}, decode_masked(RecordInfo, Mask, Types, Bin). decode_multi(TypeList, Bin) -> decode_multi(TypeList, Bin, []). decode_multi([], T, Acc) -> {lists:reverse(Acc), T}; decode_multi([Type|TypeList], Bin, Acc) -> {Elem, T} = decode(Type, Bin), decode_multi(TypeList, T, [Elem|Acc]). decode_masked(RecordInfo, Mask, Types, Bin) -> BooleanMask = boolean_mask(Mask), Apply = lists:filter(fun({_, Cond}) -> Cond end, lists:zip(Types, BooleanMask)), Apply1 = element(1, lists:unzip(Apply)), Defaults = lists:map(fun({Name, _, Default}) -> {Name, Default} end, Types -- Apply1), {List, T} = decode_multi(lists:map(fun({_,Type,_}) -> Type end, Apply1), Bin), FinalApply = lists:zip(lists:map(fun({Name,_,_}) -> Name end, Apply1), List), {to_record(RecordInfo, FinalApply ++ Defaults), T}. boolean_mask(Mask) when is_bitstring(Mask) -> lists:reverse([X==1 || <> <= Mask]); boolean_mask(Mask) -> Mask. to_record({RecordName, RecordFields}, Proplist) -> Values = [proplists:get_value(Key, Proplist) || Key <- RecordFields], list_to_tuple([RecordName | Values]). encode_guid(<>) -> <>. encode_node_id(#opcua_node_id{ns = 0, type = numeric, value = Id}) when Id < 256 -> <<16#00:8, Id:8/little-unsigned-integer>>; encode_node_id(#opcua_node_id{ns = Ns, type = numeric, value = Id}) when Id < 65536 -> <<16#01:8, Ns:8/little-unsigned-integer, Id:16/little-unsigned-integer>>; encode_node_id(#opcua_node_id{ns = Ns, type = numeric, value = Id}) when Id < 4294967296 -> BinId = encode(uint32, Id), [<<16#02:8, Ns:16/little-unsigned-integer>>, BinId]; encode_node_id(#opcua_node_id{ns = Ns, type = string, value = Id}) -> BinId = encode(string, Id), [<<16#03:8, Ns:16/little-unsigned-integer>>, BinId]; encode_node_id(#opcua_node_id{ns = Ns, type = guid, value = Id}) -> BinId = encode(guid, Id), [<<16#04:8, Ns:16/little-unsigned-integer>>, BinId]; encode_node_id(#opcua_node_id{ns = Ns, type = opaque, value = Id}) -> BinId = encode(string, Id), [<<16#05:8, Ns:16/little-unsigned-integer>>, BinId]; encode_node_id(NodeSpec) -> encode_node_id(opcua_node:id(NodeSpec)). encode_expanded_node_id(#opcua_node_id{} = NodeId) -> encode_expanded_node_id(#opcua_expanded_node_id{node_id = NodeId}); encode_expanded_node_id(#opcua_expanded_node_id{node_id = #opcua_node_id{ns = NS} = NodeId, namespace_uri = NamespaceUri, server_index = ServerIndex}) -> {NamespaceUriFlag, BinNamespaceUri, NS2} = case NamespaceUri of undefined -> {0, <<>>, NS}; _ -> {16#80, encode(string, NamespaceUri), 0} end, {ServerIndexFlag, BinServerIndex} = case ServerIndex of undefined -> {0, <<>>}; _ -> {16#40, encode(uint32, ServerIndex)} end, EncNodeId = encode_node_id(NodeId#opcua_node_id{ns = NS2}), <> = iolist_to_binary(EncNodeId), [<<(Mask + NamespaceUriFlag + ServerIndexFlag):8>>, Rest, BinNamespaceUri, BinServerIndex]; encode_expanded_node_id(#opcua_expanded_node_id{node_id = NodeSpec} = ExtNodeId) -> NodeId = opcua_node:id(NodeSpec), encode_expanded_node_id(ExtNodeId#opcua_expanded_node_id{node_id = NodeId}); encode_expanded_node_id(NodeSpec) -> encode_expanded_node_id(opcua_node:id(NodeSpec)). encode_diagnostic_info(DI = #opcua_diagnostic_info{}) -> Types = [{int32, DI#opcua_diagnostic_info.symbolic_id}, {int32, DI#opcua_diagnostic_info.namespace_uri}, {int32, DI#opcua_diagnostic_info.locale}, {int32, DI#opcua_diagnostic_info.localized_text}, {string, DI#opcua_diagnostic_info.additional_info}, {status_code, DI#opcua_diagnostic_info.inner_status_code}, {diagnostic_info, DI#opcua_diagnostic_info.inner_diagnostic_info}], encode_masked(Types). encode_qualified_name(#opcua_qualified_name{ns = NamespaceIndex, name = Name}) -> encode_multi([{uint16, NamespaceIndex}, {string, Name}]). encode_localized_text(LocalizedText) when is_binary(LocalizedText) -> encode_localized_text(#opcua_localized_text{locale = undefined, text = LocalizedText}); encode_localized_text(#opcua_localized_text{locale = Locale, text = Text}) -> Types = [{string, Locale}, {string, Text}], encode_masked(Types). encode_multi(TypeList) -> encode_multi(TypeList, <<>>). encode_multi([], Bin) -> Bin; encode_multi([{Type, Value}|TypeList], Bin) -> NewBin = encode(Type, Value), encode_multi(TypeList, <>). encode_masked(TypeList) -> encode_masked(TypeList, <<>>, <<>>). encode_masked([], Mask, Bin) -> <<0:(8-bit_size(Mask)), Mask/bits, Bin/binary>>; encode_masked([{_Type, undefined}|TypeList], Mask, Bin) -> encode_masked(TypeList, <<0:1, Mask/bits>>, Bin); encode_masked([{Type, Value}|TypeList], Mask, Bin) -> NewBin = encode(Type, Value), encode_masked(TypeList, <<1:1, Mask/bits>>, <>).