diff --git a/lib/exmbus/crypto.ex b/lib/exmbus/crypto.ex index 6635507..ca96c4b 100644 --- a/lib/exmbus/crypto.ex +++ b/lib/exmbus/crypto.ex @@ -26,12 +26,12 @@ defmodule Exmbus.Crypto do iv :: iodata(), data :: iodata(), flag_or_options :: crypto_opts() | boolean() - ) :: {:ok, binary} | {:error, crypto_error()} + ) :: {:ok, binary} | {:error, {:crypto_error, crypto_error()}} def crypto_one_time(cipher, key, iv, data, flag_or_options) do try do {:ok, :crypto.crypto_one_time(cipher, key, iv, data, flag_or_options)} catch - :error, {_tag, _c_file_info, _description} = e -> {:error, e} + :error, {_tag, _c_file_info, _description} = e -> {:error, {:crypto_error, e}} end end @@ -43,7 +43,7 @@ defmodule Exmbus.Crypto do key :: iodata(), data :: iodata(), flag_or_options :: crypto_opts() | boolean() - ) :: {:ok, binary} | {:error, crypto_error()} + ) :: {:ok, binary} | {:error, {:crypto_error, crypto_error()}} def crypto_one_time(cipher, key, data, flag_or_options) do try do {:ok, :crypto.crypto_one_time(cipher, key, data, flag_or_options)} diff --git a/lib/exmbus/parser/afl.ex b/lib/exmbus/parser/afl.ex index 5056f38..e7edd61 100644 --- a/lib/exmbus/parser/afl.ex +++ b/lib/exmbus/parser/afl.ex @@ -82,9 +82,23 @@ defmodule Exmbus.Parser.Afl do # see table in module doc # we can parse up to the MAC def parse(%{bin: <<0x90, afll::8, afl_bytes::binary-size(afll), rest::binary>>} = ctx) do - <> = afl_bytes - {:ok, fcl} = FragmentationControlField.decode(fcl_bytes) + with <> <- afl_bytes, + {:ok, fcl} <- FragmentationControlField.decode(fcl_bytes) do + parse_fields(ctx, fcl, bytes, rest) + else + _ -> {:halt, Context.add_error(ctx, {:invalid_afl, :truncated})} + end + end + def parse(%{bin: <<0x90, _rest::binary>>} = ctx) do + {:halt, Context.add_error(ctx, {:invalid_afl, :truncated})} + end + + def parse(%{bin: <>} = ctx) do + {:halt, Context.add_error(ctx, {:ci_not_afl, ci})} + end + + defp parse_fields(ctx, fcl, bytes, rest) do # our "accumulator" for the AFL layer afl = %__MODULE__{fcl: fcl} @@ -119,10 +133,6 @@ defmodule Exmbus.Parser.Afl do end end - def parse(%{bin: <>} = ctx) do - {:halt, Context.add_error(ctx, {:ci_not_afl, ci})} - end - @doc """ Verifies the MAC of the AFL. NOTE: This handler should be attached _after_ the TPL has been parsed, diff --git a/lib/exmbus/parser/dll/mbus.ex b/lib/exmbus/parser/dll/mbus.ex index b872c8b..a8476d1 100644 --- a/lib/exmbus/parser/dll/mbus.ex +++ b/lib/exmbus/parser/dll/mbus.ex @@ -30,15 +30,19 @@ defmodule Exmbus.Parser.Dll.Mbus do end end - defp _parse(<>, ctx) do - {:ok, control} = decode_c_field(c) - - dll = %__MODULE__{ - control: control, - address: a - } + def parse(ctx), do: {:halt, Context.add_error(ctx, {:invalid_dll, :mbus})} - {:next, %{ctx | dll: dll, bin: rest}} + defp _parse(<>, ctx) do + with {:ok, control} <- decode_c_field(c) do + dll = %__MODULE__{ + control: control, + address: a + } + + {:next, %{ctx | dll: dll, bin: rest}} + else + {:error, reason} -> {:halt, Context.add_error(ctx, reason)} + end end def unparse(%{dll: nil} = ctx) do @@ -69,24 +73,38 @@ defmodule Exmbus.Parser.Dll.Mbus do def direction(%__MODULE__{control: :rsp_ud}), do: {:ok, :from_meter} defp decode_c_field(<<0::1, 1::1, _fcb::1, _fcv::1, 0x0::4>>), - do: raise("SND-NKE not implemented") + do: {:error, {:not_implemented, :snd_nke}} defp decode_c_field(<<0::1, 1::1, _fcb::1, _fcv::1, 0x3::4>>), - do: raise("SND-UD/SND-UD2 not implemented") + do: {:error, {:not_implemented, :snd_ud}} defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x4::4>>), do: {:ok, :snd_nr} - defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x5::4>>), do: raise("SND-UD3 not implemented") + + defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x5::4>>), + do: {:error, {:not_implemented, :snd_ud3}} + defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x6::4>>), do: {:ok, :snd_ir} - defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x7::4>>), do: raise("ACC-NR not implemented") - defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x8::4>>), do: raise("ACC-DMD not implemented") - defp decode_c_field(<<0::1, 1::1, _fcb::1, 1::1, 0xA::4>>), do: raise("REQ-UD1 not implemented") - defp decode_c_field(<<0::1, 1::1, _fcb::1, 1::1, 0xB::4>>), do: raise("REQ-UD2 not implemented") - defp decode_c_field(<<0::1, 0::1, _acd::1, _dfc::1, 0x0::4>>), do: raise("ACK not implemented") - defp decode_c_field(<<0::1, 0::1, _acd::1, _dfc::1, 0x1::4>>), do: raise("NACK not implemented") + defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x7::4>>), + do: {:error, {:not_implemented, :acc_nr}} + + defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x8::4>>), + do: {:error, {:not_implemented, :acc_dmd}} + + defp decode_c_field(<<0::1, 1::1, _fcb::1, 1::1, 0xA::4>>), + do: {:error, {:not_implemented, :req_ud1}} + + defp decode_c_field(<<0::1, 1::1, _fcb::1, 1::1, 0xB::4>>), + do: {:error, {:not_implemented, :req_ud2}} + + defp decode_c_field(<<0::1, 0::1, _acd::1, _dfc::1, 0x0::4>>), + do: {:error, {:not_implemented, :ack}} + + defp decode_c_field(<<0::1, 0::1, _acd::1, _dfc::1, 0x1::4>>), + do: {:error, {:not_implemented, :nack}} defp decode_c_field(<<0::1, 0::1, _acd::1, _dfc::1, 0x6::4>>), - do: raise("CNF-IR not implemented") + do: {:error, {:not_implemented, :cnf_ir}} defp decode_c_field(<<0::1, 0::1, _acd::1, _dfc::1, 0x8::4>>), do: {:ok, :rsp_ud} end diff --git a/lib/exmbus/parser/dll/wmbus.ex b/lib/exmbus/parser/dll/wmbus.ex index 480a477..364dda4 100644 --- a/lib/exmbus/parser/dll/wmbus.ex +++ b/lib/exmbus/parser/dll/wmbus.ex @@ -89,6 +89,8 @@ defmodule Exmbus.Parser.Dll.Wmbus do end end + defp do_parse(ctx), do: {:halt, Context.add_error(ctx, {:invalid_dll, :wmbus})} + def unparse(%{dll: nil} = ctx) do {:next, ctx} end @@ -137,24 +139,38 @@ defmodule Exmbus.Parser.Dll.Wmbus do def direction(%__MODULE__{control: :rsp_ud}), do: {:ok, :from_meter} defp decode_c_field(<<0::1, 1::1, _fcb::1, _fcv::1, 0x0::4>>), - do: raise("SND-NKE not implemented") + do: {:error, {:not_implemented, :snd_nke}} defp decode_c_field(<<0::1, 1::1, _fcb::1, _fcv::1, 0x3::4>>), - do: raise("SND-UD/SND-UD2 not implemented") + do: {:error, {:not_implemented, :snd_ud}} defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x4::4>>), do: {:ok, :snd_nr} - defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x5::4>>), do: raise("SND-UD3 not implemented") + + defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x5::4>>), + do: {:error, {:not_implemented, :snd_ud3}} + defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x6::4>>), do: {:ok, :snd_ir} - defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x7::4>>), do: raise("ACC-NR not implemented") - defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x8::4>>), do: raise("ACC-DMD not implemented") - defp decode_c_field(<<0::1, 1::1, _fcb::1, 1::1, 0xA::4>>), do: raise("REQ-UD1 not implemented") - defp decode_c_field(<<0::1, 1::1, _fcb::1, 1::1, 0xB::4>>), do: raise("REQ-UD2 not implemented") - defp decode_c_field(<<0::1, 0::1, _acd::1, _dfc::1, 0x0::4>>), do: raise("ACK not implemented") - defp decode_c_field(<<0::1, 0::1, _acd::1, _dfc::1, 0x1::4>>), do: raise("NACK not implemented") + defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x7::4>>), + do: {:error, {:not_implemented, :acc_nr}} + + defp decode_c_field(<<0::1, 1::1, _fcb::1, 0::1, 0x8::4>>), + do: {:error, {:not_implemented, :acc_dmd}} + + defp decode_c_field(<<0::1, 1::1, _fcb::1, 1::1, 0xA::4>>), + do: {:error, {:not_implemented, :req_ud1}} + + defp decode_c_field(<<0::1, 1::1, _fcb::1, 1::1, 0xB::4>>), + do: {:error, {:not_implemented, :req_ud2}} + + defp decode_c_field(<<0::1, 0::1, _acd::1, _dfc::1, 0x0::4>>), + do: {:error, {:not_implemented, :ack}} + + defp decode_c_field(<<0::1, 0::1, _acd::1, _dfc::1, 0x1::4>>), + do: {:error, {:not_implemented, :nack}} defp decode_c_field(<<0::1, 0::1, _acd::1, _dfc::1, 0x6::4>>), - do: raise("CNF-IR not implemented") + do: {:error, {:not_implemented, :cnf_ir}} defp decode_c_field(<<0::1, 0::1, _acd::1, _dfc::1, 0x8::4>>), do: {:ok, :rsp_ud} diff --git a/lib/exmbus/parser/ell.ex b/lib/exmbus/parser/ell.ex index 04927e4..1c24f40 100644 --- a/lib/exmbus/parser/ell.ex +++ b/lib/exmbus/parser/ell.ex @@ -38,14 +38,16 @@ defmodule Exmbus.Parser.Ell do # > Table 44 below, shows the complete extension block in this case. # Fields: CC, ACC def parse(%{bin: <<0x8C, cc::binary-size(1), acc, rest::binary>>} = ctx) do - {:ok, control} = CommunicationControl.decode(cc) + with {:ok, control} <- CommunicationControl.decode(cc) do + ell = %Unencrypted{ + communication_control: control, + access_no: acc + } - ell = %Unencrypted{ - communication_control: control, - access_no: acc - } - - {:next, %{ctx | ell: ell, bin: rest}} + {:next, %{ctx | ell: ell, bin: rest}} + else + {:error, reason} -> {:halt, Context.add_error(ctx, {:ell_parse_error, reason, ci: 0x8C})} + end end # > This value of the CI-field is used if data encryption at the link layer is used in the frame. @@ -58,16 +60,18 @@ defmodule Exmbus.Parser.Ell do rest::binary>> } = ctx ) do - {:ok, control} = CommunicationControl.decode(cc) - {:ok, session_number} = SessionNumber.decode(sn) - - ell = %Encrypted{ - communication_control: control, - access_no: acc, - session_number: session_number - } - - {:next, %{ctx | ell: ell, bin: <>}} + with {:ok, control} <- CommunicationControl.decode(cc), + {:ok, session_number} <- SessionNumber.decode(sn) do + ell = %Encrypted{ + communication_control: control, + access_no: acc, + session_number: session_number + } + + {:next, %{ctx | ell: ell, bin: <>}} + else + {:error, reason} -> {:halt, Context.add_error(ctx, {:ell_parse_error, reason, ci: 0x8D})} + end end # > This value of the CI-field is used if data encryption at the link layer is not used in the frame. @@ -87,20 +91,26 @@ defmodule Exmbus.Parser.Ell do # > This extended link layer specifies the receiver address. # > Table 47 below shows the complete extension block in this case. # Fields: CC, ACC, M2, A2, SN, PayloadCRC - def parse(%{ - bin: - <<0x8F, _cc::binary-size(1), _acc, _m2::binary-size(2), _a2::binary-size(6), - _sn::binary-size(4), _payload_crc::binary-size(2), _rest::binary>> - }) do - raise "TODO: ELL IV" + def parse( + %{ + bin: + <<0x8F, _cc::binary-size(1), _acc, _m2::binary-size(2), _a2::binary-size(6), + _sn::binary-size(4), _payload_crc::binary-size(2), _rest::binary>> + } = ctx + ) do + {:halt, Context.add_error(ctx, {:not_implemented, :ell_iv})} end # > The variable Extended Link Layer allows to select optional ELL fields separately. # > The shadowed rows of Table 48 shall always be present. # > The other fields are optional and can be selected in case they are needed. # > The table defines the ordering of the fields. - def parse(%{bin: <<0x86, _rest::binary>>}) do - raise "TODO: ELL V" + def parse(%{bin: <<0x86, _rest::binary>>} = ctx) do + {:halt, Context.add_error(ctx, {:not_implemented, :ell_v})} + end + + def parse(%{bin: <>} = ctx) when ci in [0x8C, 0x8D, 0x8E, 0x8F] do + {:halt, Context.add_error(ctx, {:ell_parse_error, :truncated, ci: ci})} end def parse(%{bin: <>} = ctx) do diff --git a/lib/exmbus/parser/ell/session_number.ex b/lib/exmbus/parser/ell/session_number.ex index 2ca27a6..e409264 100644 --- a/lib/exmbus/parser/ell/session_number.ex +++ b/lib/exmbus/parser/ell/session_number.ex @@ -23,7 +23,7 @@ defmodule Exmbus.Parser.Ell.SessionNumber do :aes_128_ctr enc -> - raise "ELL session number encryption mode #{enc} is reserved for future use, see 13.2.11 of EN 13757-4:2019" + throw({:error, {:reserved_ell_session_encryption_mode, enc}}) end {:ok, @@ -35,6 +35,8 @@ defmodule Exmbus.Parser.Ell.SessionNumber do # inter-minute session session: session }} + catch + {:error, _} = e -> e end def encode(%__MODULE__{encryption: mode, minutes: m, session: s}) do diff --git a/lib/exmbus/parser/tpl.ex b/lib/exmbus/parser/tpl.ex index 503ef58..bafcd62 100644 --- a/lib/exmbus/parser/tpl.ex +++ b/lib/exmbus/parser/tpl.ex @@ -35,9 +35,13 @@ defmodule Exmbus.Parser.Tpl do def parse(ctx) do # guard against fragmented messages, which we don't currently have a good solution for. if is_struct(ctx.afl, Afl) and Afl.fragmented?(ctx.afl) do - raise "AFL is fragmented, cannot parse TPL" + {:halt, Context.add_error(ctx, {:not_implemented, :fragmented_afl})} + else + do_parse(ctx) end + end + defp do_parse(ctx) do # Allow only TPL and APL CI codes. # If if hit an ELL, AFL or similar, we error out. case CI.lookup(ctx.bin) do @@ -70,18 +74,17 @@ defmodule Exmbus.Parser.Tpl do # short def _parse(%{bin: <<0x6A, rest::binary>>} = ctx) do case parse_tpl_header_short(rest) do - {:ok, header, rest} -> - finalize_tpl(:format_frame, header, rest, ctx) - # NOTE: short header cannot currently return error, - # so dialyzer will complain if we try to handle an error from it: - # {:error, reason} -> {:halt, Context.add_error(ctx, reason)} + {:ok, header, rest} -> finalize_tpl(:format_frame, header, rest, ctx) + {:error, reason} -> {:halt, Context.add_error(ctx, reason)} end end # long def _parse(%{bin: <<0x6B, rest::binary>>} = ctx) do - {:ok, header, rest} = parse_tpl_header_long(rest) - finalize_tpl(:format_frame, header, rest, ctx) + case parse_tpl_header_long(rest) do + {:ok, header, rest} -> finalize_tpl(:format_frame, header, rest, ctx) + {:error, reason} -> {:halt, Context.add_error(ctx, reason)} + end end ## @@ -95,18 +98,17 @@ defmodule Exmbus.Parser.Tpl do # MBus full frame short def _parse(%{bin: <<0x7A, rest::binary>>} = ctx) do case parse_tpl_header_short(rest) do - {:ok, header, rest} -> - finalize_tpl(:full_frame, header, rest, ctx) - # NOTE: short header cannot currently return error, - # so dialyzer will complain if we try to handle an error from it: - # {:error, reason} -> {:halt, Context.add_error(ctx, reason)} + {:ok, header, rest} -> finalize_tpl(:full_frame, header, rest, ctx) + {:error, reason} -> {:halt, Context.add_error(ctx, reason)} end end # MBus full frame long def _parse(%{bin: <<0x72, rest::binary>>} = ctx) do - {:ok, header, rest} = parse_tpl_header_long(rest) - finalize_tpl(:full_frame, header, rest, ctx) + case parse_tpl_header_long(rest) do + {:ok, header, rest} -> finalize_tpl(:full_frame, header, rest, ctx) + {:error, reason} -> {:halt, Context.add_error(ctx, reason)} + end end ## @@ -115,8 +117,10 @@ defmodule Exmbus.Parser.Tpl do # MBus compact long def _parse(%{bin: <<0x73, rest::binary>>} = ctx) do - {:ok, header, rest} = parse_tpl_header_long(rest) - finalize_tpl(:compact_frame, header, rest, ctx) + case parse_tpl_header_long(rest) do + {:ok, header, rest} -> finalize_tpl(:compact_frame, header, rest, ctx) + {:error, reason} -> {:halt, Context.add_error(ctx, reason)} + end end # MBus compact none @@ -127,11 +131,8 @@ defmodule Exmbus.Parser.Tpl do # MBus compact short def _parse(%{bin: <<0x7B, rest::binary>>} = ctx) do case parse_tpl_header_short(rest) do - {:ok, header, rest} -> - finalize_tpl(:compact_frame, header, rest, ctx) - # NOTE: short header cannot currently return error, - # so dialyzer will complain if we try to handle an error from it: - # {:error, reason} -> {:halt, Context.add_error(ctx, reason)} + {:ok, header, rest} -> finalize_tpl(:compact_frame, header, rest, ctx) + {:error, reason} -> {:halt, Context.add_error(ctx, reason)} end end @@ -165,6 +166,8 @@ defmodule Exmbus.Parser.Tpl do end end + defp parse_tpl_header_short(_rest), do: {:error, {:invalid_tpl_header, :short}} + @doc """ Decode a TPL long header. @@ -198,9 +201,8 @@ defmodule Exmbus.Parser.Tpl do } {:ok, header, rest} - else - {:error, reason} -> - {:error, reason, rest} end end + + def parse_tpl_header_long(_rest), do: {:error, {:invalid_tpl_header, :long}} end diff --git a/lib/exmbus/parser/tpl/configuration_field.ex b/lib/exmbus/parser/tpl/configuration_field.ex index bc8a653..7ca834b 100644 --- a/lib/exmbus/parser/tpl/configuration_field.ex +++ b/lib/exmbus/parser/tpl/configuration_field.ex @@ -120,36 +120,39 @@ defmodule Exmbus.Parser.Tpl.ConfigurationField do counter_bits = if(z == 1, do: 32, else: 0) key_version_bits = if(v == 1, do: 8, else: 0) - << - counter::little-size(counter_bits), - key_version::little-size(key_version_bits), - rest::binary - >> = rest - - cf = %__MODULE__{ - hop_count: 0, - repeater_access: 0, - content_of_message: cc, - syncrony: false, - accessibility: false, - bidirectional: false, - mode: 7, - blocks: blocks, - padding: p == 1, - content_index: iiii, - counter: if(z == 1, do: counter), - key_version: if(v == 1, do: key_version), - key_id: kkkk, - kdf: dd - } - - {:ok, cf, rest} + if bit_size(rest) < counter_bits + key_version_bits do + {:error, {:invalid_configuration_field, :truncated}} + else + << + counter::little-size(counter_bits), + key_version::little-size(key_version_bits), + rest::binary + >> = rest + + cf = %__MODULE__{ + hop_count: 0, + repeater_access: 0, + content_of_message: cc, + syncrony: false, + accessibility: false, + bidirectional: false, + mode: 7, + blocks: blocks, + padding: p == 1, + content_index: iiii, + counter: if(z == 1, do: counter), + key_version: if(v == 1, do: key_version), + key_id: kkkk, + kdf: dd + } + + {:ok, cf, rest} + end end - # raise if unknown encryption mode - def parse(<<_::8, _::3, mode::5, _rest::binary>> = bin) do - <> = bin - - raise "Encryption mode #{mode} not implemented. configuration field bits were #{Exmbus.Debug.to_bits(cfbin)}" + def parse(<<_::8, _::3, mode::5, _rest::binary>>) do + {:error, {:not_implemented, {:encryption_mode, mode}}} end + + def parse(_bin), do: {:error, {:invalid_configuration_field, :truncated}} end diff --git a/test/crash_resistance_test.exs b/test/crash_resistance_test.exs index 4977eeb..e43dba4 100644 --- a/test/crash_resistance_test.exs +++ b/test/crash_resistance_test.exs @@ -3,6 +3,8 @@ defmodule CrashResistanceTest do Test that the parser can handle unexpected frames without crashing. """ alias Exmbus.Parser.Context + alias Exmbus.Parser.Afl + alias Exmbus.Parser.Afl.FragmentationControlField use ExUnit.Case, async: true @@ -41,4 +43,57 @@ defmodule CrashResistanceTest do assert {:error, ctx} = Exmbus.parse(<<>>, Context.new(handlers: [handler])) assert ctx.errors == [{handler, {:unexpected_return, {:error, :reason}}}] end + + test "unsupported wmbus dll control field is a parse error" do + handler = &Exmbus.Parser.Dll.parse/1 + + frame = + Base.decode16!( + "2E4093157856341233037A2A0020255923C95AAA26D1B2E7493B013EC4A6F6D3529B520EDFF0EA6DEFC99D6D69EBF3" + ) + + assert {:error, %{errors: [{^handler, {:not_implemented, :snd_nke}}]}} = + Exmbus.parse(frame, key: []) + end + + test "unsupported mbus dll control field is a parse error" do + handler = &Exmbus.Parser.Dll.parse/1 + frame = <<0x68, 3, 3, 0x68, 0x40, 0x00, 0x78, 0xB8, 0x16>> + + assert {:error, %{errors: [{^handler, {:not_implemented, :snd_nke}}]}} = + Exmbus.parse(frame) + end + + test "truncated tpl headers are parse errors" do + handler = &Exmbus.Parser.Tpl.parse/1 + ctx = Context.new(handlers: [handler]) + + assert {:error, %{errors: [{^handler, {:invalid_tpl_header, :short}}]}} = + Exmbus.parse(<<0x7A>>, ctx) + end + + test "fragmented afl is a parse error" do + handler = &Exmbus.Parser.Tpl.parse/1 + fcl = %FragmentationControlField{fragment_id: 1} + ctx = Context.new(handlers: [handler], afl: %Afl{fcl: fcl}) + + assert {:error, %{errors: [{^handler, {:not_implemented, :fragmented_afl}}]}} = + Exmbus.parse(<<0x7A>>, ctx) + end + + test "truncated afl is a parse error" do + handler = &Exmbus.Parser.Afl.parse/1 + ctx = Context.new(handlers: [handler]) + + assert {:error, %{errors: [{^handler, {:invalid_afl, :truncated}}]}} = + Exmbus.parse(<<0x90, 1, 0>>, ctx) + end + + test "unsupported ell variants are parse errors" do + handler = &Exmbus.Parser.Ell.parse/1 + ctx = Context.new(handlers: [handler]) + + assert {:error, %{errors: [{^handler, {:not_implemented, :ell_v}}]}} = + Exmbus.parse(<<0x86>>, ctx) + end end diff --git a/test/parser/tpl_test.exs b/test/parser/tpl_test.exs index d2d50bd..d97ecd9 100644 --- a/test/parser/tpl_test.exs +++ b/test/parser/tpl_test.exs @@ -3,4 +3,24 @@ defmodule Parser.TplTest do doctest Exmbus.Parser.Tpl, import: true doctest Exmbus.Parser.Tpl.Device, import: true doctest Exmbus.Parser.Tpl.ConfigurationField, import: true + + alias Exmbus.Parser.Tpl.ConfigurationField + + test "truncated configuration field returns error" do + assert {:error, {:invalid_configuration_field, :truncated}} = ConfigurationField.parse(<<0>>) + end + + test "truncated mode 7 configuration field extension returns error" do + bin = <<0::4, 0::1, 0::3, 0::2, 1::1, 7::5, 0::1, 0::1, 0::2, 0::4>> + + assert {:error, {:invalid_configuration_field, :truncated}} = ConfigurationField.parse(bin) + end + + test "unsupported short tpl configuration mode returns parse error" do + handler = &Exmbus.Parser.Tpl.parse/1 + ctx = Exmbus.Parser.Context.new(handlers: [handler]) + + assert {:error, %{errors: [{^handler, {:not_implemented, {:encryption_mode, 1}}}]}} = + Exmbus.parse(<<0x7A, 0, 0, 0, 0::3, 1::5>>, ctx) + end end