% 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 % An eUICC procedure in the context of this module has nothing to do with any of the procedures specified in % GSMA SGP.22 or SGP.32. In this module an eUICC procedure is a virtual procedure were states in the % euicc table are set. -module(mnesia_db_euicc). -include_lib("stdlib/include/qlc.hrl"). -include("mnesia_db_rest.hrl"). -include("mnesia_db_euicc.hrl"). % euicc functions, to be called by the eIM code (from inside) -export([counter_tick/1, state_get/2, state_set/3, create_if_not_exist/1]). % debugging -export([dump/0]). % trigger recurring events (called automatically by timer from this module) -export([timer_rest/0]). trans_create_if_not_exist(EidValue) -> {ok, CounterValue} = application:get_env(onomondo_eim, counter_value), {ok, ConsumerEuicc} = application:get_env(onomondo_eim, consumer_euicc), Row = #euicc{ eidValue = EidValue, counterValue = CounterValue, consumerEuicc = ConsumerEuicc, associationToken = 1, signPubKey = <<>>, signAlgo = <<"prime256v1">>, stateChangeCauseList = [] }, Q = qlc:q([X#euicc.eidValue || X <- mnesia:table(euicc), X#euicc.eidValue == EidValue]), Present = qlc:e(Q), case Present of [] -> mnesia:write(Row); _ -> present end. % Create a new eUICC state data entry create_if_not_exist(EidValue) -> Trans = fun() -> trans_create_if_not_exist(EidValue) end, {atomic, Result} = mnesia:transaction(Trans), case Result of ok -> logger:info("eUICC: creating new eUICC state data entry,~neID=~p~n", [EidValue]), ok; present -> ok; _ -> logger:error( "eUICC: cannot create new eUICC state data entry, database error,~neID=~p~n", [ EidValue ] ), error end. % get an incremented counterValue (and store the incremented counterValue as the current counterValue) counter_tick(EidValue) -> Trans = fun() -> Q = qlc:q([X || X <- mnesia:table(euicc), X#euicc.eidValue == EidValue]), Rows = qlc:e(Q), case Rows of [Row | _] -> CounterValue = Row#euicc.counterValue + 1, ok = mnesia:write(Row#euicc{counterValue = CounterValue}), {ok, CounterValue}; [] -> error; _ -> error end end, {atomic, Result} = mnesia:transaction(Trans), case Result of {ok, CounterValue} -> logger:info("eUICC: incrementing counterValue,~neID=~p, counter=~p~n", [ EidValue, CounterValue ]), {ok, CounterValue}; _ -> logger:error("eUICC: cannot increment counterValue, database error,~neID=~p~n", [ EidValue ]), error end. trans_state_set(EidValue, Name, Value) -> trans_create_if_not_exist(EidValue), Q = qlc:q([X || X <- mnesia:table(euicc), X#euicc.eidValue == EidValue]), Rows = qlc:e(Q), case Rows of [Row | []] -> case Name of counterValue -> mnesia:write(Row#euicc{counterValue = Value}); consumerEuicc -> mnesia:write(Row#euicc{consumerEuicc = Value}); associationToken -> mnesia:write(Row#euicc{associationToken = Value}); signPubKey -> mnesia:write(Row#euicc{signPubKey = Value}); signAlgo -> mnesia:write(Row#euicc{signAlgo = Value}); stateChangeCauseList -> mnesia:write(Row#euicc{stateChangeCauseList = Value}); _ -> throw(badState) end; [] -> throw(eidUnknown); _ -> throw(undefinedError) end. trans_state_get(EidValue, Name) -> Q = qlc:q([X || X <- mnesia:table(euicc), X#euicc.eidValue == EidValue]), Rows = qlc:e(Q), case Rows of [Row | _] -> case Name of counterValue -> Row#euicc.counterValue; consumerEuicc -> Row#euicc.consumerEuicc; associationToken -> Row#euicc.associationToken; signPubKey -> Row#euicc.signPubKey; signAlgo -> Row#euicc.signAlgo; stateChangeCauseList -> Row#euicc.stateChangeCauseList; _ -> throw(badState) end; [] -> throw(eidUnknown); _ -> throw(undefinedError) end. % Get an eUICC state by its name (atom) state_get(EidValue, Name) -> Trans = fun() -> trans_state_get(EidValue, Name) end, Result = mnesia:transaction(Trans), case Result of {atomic, Value} -> logger:info("eUICC: reading eUICC state,~neID=~p, name=~p, value=~p~n", [ EidValue, Name, Value ]), {ok, Value}; {aborted, {throw, ErrorCode}} -> logger:error( "eUICC: reading of eUICC state failed with error code ~p,~neID=~p, name=~p~n", [ ErrorCode, EidValue, Name ] ), error; _ -> logger:error("eUICC: reading of eUICC state failed,~neID=~p, name=~p~n", [ EidValue, Name ]), error end. % Update an eUICC state by its name (atom) state_set(EidValue, Name, Value) -> Trans = fun() -> trans_state_set(EidValue, Name, Value) end, Result = mnesia:transaction(Trans), case Result of {atomic, ok} -> logger:info("eUICC: writing eUICC state,~neID=~p, name=~p, value=~p~n", [ EidValue, Name, Value ]), ok; {aborted, {throw, ErrorCode}} -> logger:error( "eUICC: writing of eUICC state failed with error code ~p,~neID=~p, name=~p~n", [ ErrorCode, EidValue, Name ] ), error; _ -> logger:error("eUICC: writing of eUICC state failed,~neID=~p, name=~p~n", [ EidValue, Name ]), error end. timer_rest_fetch() -> % Fetch the rest resources with incoming orders from the rest table and set their status (wihich is initially set % to "new" to "work", so that the REST API user knows that the order has been accepted for processing. Finally, we % will return the fetched rest resources back to the caller, so that he can proceed with the processing of the % orders. SetStatus = fun({ResourceId, _EidValue, _Order}) -> mnesia_db_rest:trans_set_status(ResourceId, work, [], none) end, Trans = fun() -> Q = qlc:q([ {X#rest.resourceId, X#rest.eidValue, X#rest.order} || X <- mnesia:table(rest), X#rest.status == new, X#rest.facility == euicc ]), Rows = qlc:e(Q), case Rows of [] -> []; Rows -> [SetStatus(Row) || Row <- Rows], Rows end end, Result = mnesia:transaction(Trans), case Result of {atomic, Resources} -> {ok, Resources}; _ -> logger:error("eUICC: order fetch failed, database error~n"), error end. timer_rest_process(RestResource) -> % Process the order contained in the given REST resource and return the outcome when done. HandleStateSet = fun(EidValue, State) -> case State of {[{Name, Value}]} -> ok = trans_state_set(EidValue, binary_to_atom(Name), Value), {[{Name, Value}]}; _ -> throw(badOrder) end end, HandleStateGet = fun(EidValue, Name) -> {[{Name, trans_state_get(EidValue, binary_to_atom(Name))}]} end, Trans = fun() -> {_ResourceId, EidValue, Order} = RestResource, case Order of {[{<<"euicc">>, {[{<<"set">>, States}]}}]} -> [{[{euiccStateResult, [HandleStateSet(EidValue, State) || State <- States]}]}]; {[{<<"euicc">>, {[{<<"get">>, States}]}}]} -> [{[{euiccStateResult, [HandleStateGet(EidValue, State) || State <- States]}]}]; _ -> throw(badOrder) end end, Result = mnesia:transaction(Trans), case Result of {atomic, Outcome} -> Outcome; {aborted, {throw, ErrorCode}} -> logger:error("eUICC: order process failed with error code ~p~n", [ErrorCode]), [{[{procedureError, ErrorCode}]}]; _ -> logger:error("eUICC: order process failed, database error~n"), [{[{procedureError, undefinedError}]}] end. timer_rest_finish(RestResource, Outcome) -> % Write the given outcome to the given REST resource and set the status to "done" to notify the REST API user that % the order processing has finished. Trans = fun() -> {ResourceId, _EidValue, _Order} = RestResource, mnesia_db_rest:trans_set_status(ResourceId, done, Outcome, none) end, Result = mnesia:transaction(Trans), case Result of {atomic, ok} -> ok; {aborted, {throw, ErrorCode}} -> logger:error("eUICC: order finish failed with error code ~p~n", [ErrorCode]), error; _ -> logger:error("eUICC: order finish failed, database error~n"), error end. % Handle euicc table related REST resources in regular intervals. timer_rest() -> % Fetch REST resources with new orders from rest table and mark them as work in progress {ok, RestResources} = timer_rest_fetch(), % Process each REST resource individually ProcessRestResource = fun(RestResource) -> Outcome = timer_rest_process(RestResource), timer_rest_finish(RestResource, Outcome) end, [ProcessRestResource(RestResource) || RestResource <- RestResources], % Next euicc procedure in 1 sec. {ok, _} = timer:apply_after(1000, mnesia_db_euicc, timer_rest, []), ok. % Dump all eUICC states we are aware of dump() -> Trans = fun() -> Q = qlc:q([X || X <- mnesia:table(euicc)]), qlc:e(Q) end, {atomic, Result} = mnesia:transaction(Trans), Result.