% Copyright (c) 2025 Onomondo ApS & sysmocom - s.f.m.c. GmbH. All rights reserved. % % SPDX-License-Identifier: AGPL-3.0-only % % Author: Philipp Maier / sysmocom - s.f.m.c. GmbH -module(mnesia_db). -include_lib("stdlib/include/qlc.hrl"). -include("mnesia_db_rest.hrl"). -include("mnesia_db_work.hrl"). -include("mnesia_db_euicc.hrl"). % Initialization, startup -export([init/0]). % Initialize databse scheme, tables and check the database for unfinished orders init() -> % Create an initial mnesia scheme. mnesia:stop(), case mnesia:create_schema([node()]) of {error, {_, {already_exists, _}}} -> ok; ok -> logger:notice(" mnesia database schema created\n") end, ok = mnesia:start(), logger:notice(" mnesia started\n"), % The rest table is a persistent table, so even after a crash it will be possible to continue pending orders. case mnesia:create_table( rest, [ {attributes, record_info(fields, rest)}, {disc_copies, [node()]}, {type, set} ] ) of {aborted, {already_exists, rest}} -> ok; {atomic, ok} -> logger:notice(" rest table created\n") end, % The work table is volatile. It only contains intermediate results. When the eIM is restarted and there is a % pending order that is worked on. Then all the work is lost. This is intentional since we want to avoid keeping % states that have gotten inconsistent anyway. case mnesia:create_table( work, [ {attributes, record_info(fields, work)}, {type, set} ] ) of {aborted, {already_exists, work}} -> ok; {atomic, ok} -> logger:notice(" work table created\n") end, % The euicc table will store eUICC states, such as the eID and the counterValue that is required for the replay % protection. case mnesia:create_table( euicc, [ {attributes, record_info(fields, euicc)}, {disc_copies, [node()]}, {type, set} ] ) of {aborted, {already_exists, euicc}} -> ok; {atomic, ok} -> logger:notice(" euicc table created\n") end, % Wait until the mnesia tables become available. case mnesia:wait_for_tables([rest, work], 60000) of {timeout, _} -> logger:error(" unable to synchronize mnesia tables\n"), throw("normal operation not possible"); ok -> ok end, % Look through the rest table and mark all items that were in state "work" as "done" and put an appropriate outcome % into the rest table. This way we tell the REST API user that the processing of the order was interrupted due to a % crash/restart of the eIM. The API user is then expected to delete the item and (if necessary) re-submit the order. Trans = fun() -> Q = qlc:q([X#rest.resourceId || X <- mnesia:table(rest), X#rest.status == work]), ResourceIds = qlc:e(Q), lists:foreach( fun(ResourceId) -> mnesia_db_rest:trans_set_status( ResourceId, done, [{[{procedureError, abortedOrder}]}], none ) end, ResourceIds ) end, {atomic, ok} = mnesia:transaction(Trans), % Start recurring event cycles ok = mnesia_db_euicc:timer_rest(), ok = mnesia_db_rest:timer_cleanup(), ok.