Skip to content

Commit fc55583

Browse files
committed
fix(dup): fix possible crash on interface-specific volatile triggers
previously, when 1. installing a volatile trigger for a specific interface 2. the interface results in a cache miss but is valid for the device the volatile trigger would be installed but the device status would not be updated. this results in a crash when the trigger generated events, as the logic expects the interfaces to be in the data updater state. by just loading the interfaces normally, we avoid this possible crash Signed-off-by: Francesco Noacco <francesco.noacco@secomind.com>
1 parent b15b5bb commit fc55583

4 files changed

Lines changed: 80 additions & 38 deletions

File tree

apps/astarte_data_updater_plant/lib/astarte_data_updater_plant/data_updater/core/trigger.ex

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ defmodule Astarte.DataUpdaterPlant.DataUpdater.Core.Trigger do
3535
alias Astarte.Core.Triggers.DataTrigger
3636
alias Astarte.Core.InterfaceDescriptor
3737
alias Astarte.Core.Mapping.EndpointsAutomaton
38-
alias Astarte.DataAccess.Interface, as: InterfaceQueries
3938
alias Astarte.DataUpdaterPlant.TriggerPolicy.Queries, as: PolicyQueries
4039
alias Astarte.Core.Triggers.SimpleTriggersProtobuf.DeviceTrigger, as: ProtobufDeviceTrigger
4140
alias Astarte.Core.Triggers.SimpleTriggersProtobuf.DataTrigger, as: ProtobufDataTrigger
@@ -376,38 +375,17 @@ defmodule Astarte.DataUpdaterPlant.DataUpdater.Core.Trigger do
376375
{:data_trigger, %ProtobufDataTrigger{interface_name: "*"}} ->
377376
{:ok, load_trigger(new_state, trigger, target)}
378377

379-
{:data_trigger,
380-
%ProtobufDataTrigger{
381-
interface_name: interface_name,
382-
interface_major: major,
383-
match_path: "/*"
384-
}} ->
385-
with :ok <-
386-
InterfaceQueries.check_if_interface_exists(state.realm, interface_name, major) do
387-
{:ok, new_state}
388-
else
389-
{:error, reason} ->
390-
# State rollback here
391-
{{:error, reason}, state}
392-
end
393-
394378
{:data_trigger,
395379
%ProtobufDataTrigger{
396380
interface_name: interface_name,
397381
interface_major: major,
398382
match_path: match_path
399383
}} ->
400-
with {:ok, %InterfaceDescriptor{automaton: automaton}} <-
401-
InterfaceQueries.fetch_interface_descriptor(state.realm, interface_name, major),
402-
{:ok, _endpoint_id} <- EndpointsAutomaton.resolve_path(match_path, automaton) do
403-
{:ok, new_state}
384+
with {:ok, descriptor, new_state} <- handle_cache_miss(new_state, interface_name),
385+
:ok <- check_interface_major_version(descriptor, major),
386+
:ok <- check_trigger_path(match_path, descriptor.automaton) do
387+
{:ok, load_trigger(new_state, trigger, target)}
404388
else
405-
{:error, :not_found} ->
406-
{{:error, :invalid_match_path}, state}
407-
408-
{:guessed, _} ->
409-
{{:error, :invalid_match_path}, state}
410-
411389
{:error, reason} ->
412390
# State rollback here
413391
{{:error, reason}, state}
@@ -419,6 +397,32 @@ defmodule Astarte.DataUpdaterPlant.DataUpdater.Core.Trigger do
419397
end
420398
end
421399

400+
defp handle_cache_miss(state, interface_name) do
401+
case Core.Interface.maybe_handle_cache_miss(nil, interface_name, state) do
402+
{:ok, _interface_descriptor, _new_state} = ok -> ok
403+
{:error, :interface_loading_failed} -> {:error, :interface_not_found}
404+
end
405+
end
406+
407+
defp check_trigger_path("/*", _automaton) do
408+
:ok
409+
end
410+
411+
defp check_trigger_path(path, automaton) do
412+
case EndpointsAutomaton.resolve_path(path, automaton) do
413+
{:ok, _endpoint_id} -> :ok
414+
{:guessed, _} -> {:error, :invalid_match_path}
415+
{:error, :not_found} -> {:error, :invalid_match_path}
416+
end
417+
end
418+
419+
defp check_interface_major_version(descriptor, major) do
420+
case descriptor.major_version do
421+
^major -> :ok
422+
_ -> {:error, :interface_major_version_mismatch}
423+
end
424+
end
425+
422426
def handle_delete_volatile_trigger(state, trigger_id) do
423427
{new_volatile, maybe_trigger} =
424428
Enum.reduce(state.volatile_triggers, {[], nil}, fn item, {acc, found} ->

apps/astarte_data_updater_plant/test/astarte_data_updater_plant/data_updater/core/trigger_handler_test.exs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,24 @@ defmodule Astarte.DataUpdaterPlant.DataUpdater.Core.TriggerHandlerTest do
6868
property "successfully install volatile data trigger for specific interface", %{
6969
state: state,
7070
realm_name: realm_name,
71-
device: device
71+
device: device,
72+
individual_datastream_device_interface: interface,
73+
registered_paths: registered_paths
7274
} do
75+
path =
76+
registered_paths[{interface.name, interface.major_version}]
77+
|> Enum.random()
78+
7379
simple_trigger =
7480
%SimpleTriggerContainer{
7581
simple_trigger: {
7682
:data_trigger,
7783
%DataTrigger{
7884
version: 1,
79-
interface_name: "com.test.SimpleStreamTest",
80-
interface_major: 1,
85+
interface_name: interface.name,
86+
interface_major: interface.major_version,
8187
data_trigger_type: :INCOMING_DATA,
82-
match_path: "/0/value",
88+
match_path: path,
8389
value_match_operator: :LESS_THAN,
8490
known_value: Cyanide.encode!(%{v: 100})
8591
}

apps/astarte_data_updater_plant/test/astarte_data_updater_plant/data_updater/time_based_actions_test.exs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,23 @@ defmodule Astarte.DataUpdaterPlant.TimeBasedActionsTest do
949949
timestamp,
950950
trigger_opts \\ []
951951
) do
952+
match_path = trigger_opts[:match_path] || "/0/value"
953+
954+
interface_params = %{
955+
interface_name: trigger_opts[:interface_name] || "com.test.SimpleStreamTest",
956+
version_major: trigger_opts[:interface_major] || 1,
957+
version_minor: 1,
958+
type: :datastream,
959+
ownership: :device,
960+
aggregation: :individual,
961+
mappings: [
962+
%{
963+
endpoint: trigger_opts[:endpoint] || match_path,
964+
value_type: :integer
965+
}
966+
]
967+
}
968+
952969
trigger_data =
953970
case trigger_type do
954971
:device_trigger ->
@@ -966,10 +983,10 @@ defmodule Astarte.DataUpdaterPlant.TimeBasedActionsTest do
966983
simple_trigger: {
967984
:data_trigger,
968985
%DataTrigger{
969-
interface_name: trigger_opts[:interface_name] || "com.test.SimpleStreamTest",
970-
interface_major: trigger_opts[:interface_major] || 1,
986+
interface_name: interface_params.interface_name,
987+
interface_major: interface_params.version_major,
971988
data_trigger_type: trigger_opts[:data_trigger_type] || :INCOMING_DATA,
972-
match_path: trigger_opts[:match_path] || "/0/value",
989+
match_path: match_path,
973990
value_match_operator: trigger_opts[:value_match_operator] || :LESS_THAN,
974991
known_value: trigger_opts[:known_value] || Cyanide.encode!(%{v: 100})
975992
}
@@ -993,7 +1010,11 @@ defmodule Astarte.DataUpdaterPlant.TimeBasedActionsTest do
9931010
volatile_trigger_id = :crypto.strong_rand_bytes(16)
9941011
ref = if trigger_type == :data_trigger, do: 2, else: 1
9951012

996-
insert_device_and_start_data_updater(realm, device_id)
1013+
ensure_interface_exists(realm, interface_params)
1014+
1015+
insert_device_and_start_data_updater(realm, device_id,
1016+
introspection: %{interface_params.interface_name => interface_params.version_major}
1017+
)
9971018

9981019
:ok =
9991020
DataUpdater.handle_install_volatile_trigger(
@@ -1025,4 +1046,19 @@ defmodule Astarte.DataUpdaterPlant.TimeBasedActionsTest do
10251046
setup_data_updater(realm_name, encoded_device_id)
10261047
:ok
10271048
end
1049+
1050+
defp ensure_interface_exists(realm_name, interface) do
1051+
case Astarte.DataAccess.Interface.check_if_interface_exists(
1052+
realm_name,
1053+
interface.interface_name,
1054+
interface.version_major
1055+
) do
1056+
:ok ->
1057+
:ok
1058+
1059+
{:error, :interface_not_found} ->
1060+
interface_json = Jason.encode!(interface)
1061+
Astarte.RealmManagement.Engine.install_interface(realm_name, interface_json)
1062+
end
1063+
end
10281064
end

apps/astarte_data_updater_plant/test/astarte_data_updater_plant/data_updater_test.exs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1710,10 +1710,6 @@ defmodule Astarte.DataUpdaterPlant.DataUpdaterTest do
17101710
|> SimpleTriggerContainer.encode()
17111711
end
17121712

1713-
defp generate_trigger_target() do
1714-
generate_trigger_target(nil)
1715-
end
1716-
17171713
defp generate_trigger_target(realm) do
17181714
%TriggerTargetContainer{
17191715
trigger_target: {

0 commit comments

Comments
 (0)