dexter has uploaded this change for review. ( https://gerrit.osmocom.org/c/onomondo-eim/+/42950?usp=email )
Change subject: esipa_asn1_handler: replace handle_asn1_notificationList
......................................................................
esipa_asn1_handler: replace handle_asn1_notificationList
The helper function handle_asn1_notificationList can also be
described using the comprehension syntax.
Change-Id: Ia95f56998b4f7a8b0f9fce727d82dedc942e5aeb
Related: SYS#8100
---
M src/esipa_asn1_handler.erl
1 file changed, 2 insertions(+), 9 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/onomondo-eim refs/changes/50/42950/1
diff --git a/src/esipa_asn1_handler.erl b/src/esipa_asn1_handler.erl
index 1815499..466461f 100644
--- a/src/esipa_asn1_handler.erl
+++ b/src/esipa_asn1_handler.erl
@@ -9,14 +9,6 @@
-export([handle_asn1/2]).
-% Helper function to send out a list of notifications
-handle_asn1_notificationList(_Pid, []) ->
- ok;
-handle_asn1_notificationList(Pid, NotificationList) ->
- [PendingNotification | NotificationListTail] = NotificationList,
- handle_asn1(Pid, {handleNotificationEsipa, {pendingNotification, PendingNotification}}),
- handle_asn1_notificationList(Pid, NotificationListTail).
-
%GSMA SGP.32, section 6.3.2.1
handle_asn1(Pid, {initiateAuthenticationRequestEsipa, EsipaReq}) ->
EimTransactionId = maps:get(eimTransactionId, EsipaReq, none),
@@ -384,7 +376,8 @@
),
% then forward the notifications in the included notification list
NotificationList = maps:get(notificationList, EPRAndNotifications),
- handle_asn1_notificationList(Pid, NotificationList);
+ [handle_asn1(Pid, {handleNotificationEsipa, {pendingNotification, PendingNotification}}) ||
+ PendingNotification <- NotificationList];
{ipaEuiccDataResponse, IpaEuiccDataResponse} ->
% drive-by store the eUICC public key so that we can use it later to sign PSMOs or eCOs
{EidValue, _, _} = mnesia_db_work:pickup(Pid, none),
--
To view, visit https://gerrit.osmocom.org/c/onomondo-eim/+/42950?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: onomondo-eim
Gerrit-Branch: master
Gerrit-Change-Id: Ia95f56998b4f7a8b0f9fce727d82dedc942e5aeb
Gerrit-Change-Number: 42950
Gerrit-PatchSet: 1
Gerrit-Owner: dexter <pmaier(a)sysmocom.de>
dexter has uploaded this change for review. ( https://gerrit.osmocom.org/c/onomondo-eim/+/42949?usp=email )
Change subject: esipa_asn1_handler_utils: rename EsipaReq to Debuginfo
......................................................................
esipa_asn1_handler_utils: rename EsipaReq to Debuginfo
The parameter EsipaReq only exists because we pass it as
Debuginfo to mnesia_db_work:finish. Let's rename it to
"Debuginfo" in esipa_asn1_handler_utils as well to make
clear what the purpose of this parameter is.
Change-Id: I8115d382172989b9ef7ae7f97ce39520e3e2dd16
Related: SYS#8100
---
M src/esipa_asn1_handler_utils.erl
1 file changed, 5 insertions(+), 5 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/onomondo-eim refs/changes/49/42949/1
diff --git a/src/esipa_asn1_handler_utils.erl b/src/esipa_asn1_handler_utils.erl
index 171f78b..1b8039f 100644
--- a/src/esipa_asn1_handler_utils.erl
+++ b/src/esipa_asn1_handler_utils.erl
@@ -24,7 +24,7 @@
none
end.
-process_euiccPackageResult(Pid, EuiccPackageResult, EsipaReq, EimTransactionId) ->
+process_euiccPackageResult(Pid, EuiccPackageResult, Debuginfo, EimTransactionId) ->
WorkBind = fun(Map) ->
case maps:is_key(eimTransactionId, Map) of
true ->
@@ -83,18 +83,18 @@
[{[{euiccPackageErrorCode, undefinedError}]}]
end,
- mnesia_db_work:finish(Pid, Outcome, EsipaReq).
+ mnesia_db_work:finish(Pid, Outcome, Debuginfo).
% Handle an EuiccPackageResult, this includes everything from the handling of the work items in mnesia_db, down to
% signature checks and the generation of an appropriate outcome for the REST API.
-handle_euiccPackageResult(Pid, EuiccPackageResult, EsipaReq) ->
+handle_euiccPackageResult(Pid, EuiccPackageResult, Debuginfo) ->
EimTransactionId = eimTransactionId_from_euiccPackageResult(EuiccPackageResult),
{EidValue, _, _} = mnesia_db_work:pickup(Pid, EimTransactionId),
case crypto_utils:verify_euiccPackageResultSigned(EuiccPackageResult, EidValue) of
ok ->
- process_euiccPackageResult(Pid, EuiccPackageResult, EsipaReq, EimTransactionId);
+ process_euiccPackageResult(Pid, EuiccPackageResult, Debuginfo, EimTransactionId);
_ ->
mnesia_db_work:finish(
- Pid, [{[{procedureError, euiccSignatureInvalid}]}], EsipaReq
+ Pid, [{[{procedureError, euiccSignatureInvalid}]}], Debuginfo
)
end.
--
To view, visit https://gerrit.osmocom.org/c/onomondo-eim/+/42949?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: onomondo-eim
Gerrit-Branch: master
Gerrit-Change-Id: I8115d382172989b9ef7ae7f97ce39520e3e2dd16
Gerrit-Change-Number: 42949
Gerrit-PatchSet: 1
Gerrit-Owner: dexter <pmaier(a)sysmocom.de>
dexter has uploaded this change for review. ( https://gerrit.osmocom.org/c/onomondo-eim/+/42951?usp=email )
Change subject: esipa_asn1_handler: use eimTransactionId for ipaEuiccDataResponse
......................................................................
esipa_asn1_handler: use eimTransactionId for ipaEuiccDataResponse
The ipaEuiccDataResponse does currently not use the eimTransactionId,
which was added with SGP.32 V.1.2, let's make use of the eimTransactionId
and move the handler functionality to a dedicated helper function in
esipa_asn_handler_utils.
Change-Id: Ie414eef7fd5c8928dd611503f4dd8d67322c0e80
Related: SYS#8100
---
M src/esipa_asn1_handler.erl
M src/esipa_asn1_handler_utils.erl
M src/esipa_rest_utils.erl
3 files changed, 36 insertions(+), 13 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/onomondo-eim refs/changes/51/42951/1
diff --git a/src/esipa_asn1_handler.erl b/src/esipa_asn1_handler.erl
index 466461f..943c8a8 100644
--- a/src/esipa_asn1_handler.erl
+++ b/src/esipa_asn1_handler.erl
@@ -341,7 +341,9 @@
}}
end;
{edr, Order} ->
- IpaEuiccDataRequest = esipa_rest_utils:edr_order_to_ipaEuiccDataRequest(Order),
+ EimTransactionId = rand:bytes(16),
+ mnesia_db_work:bind(Pid, EimTransactionId),
+ IpaEuiccDataRequest = esipa_rest_utils:edr_order_to_ipaEuiccDataRequest(Order, EimTransactionId),
case IpaEuiccDataRequest of
error ->
ok = mnesia_db_work:finish(
@@ -379,13 +381,9 @@
[handle_asn1(Pid, {handleNotificationEsipa, {pendingNotification, PendingNotification}}) ||
PendingNotification <- NotificationList];
{ipaEuiccDataResponse, IpaEuiccDataResponse} ->
- % drive-by store the eUICC public key so that we can use it later to sign PSMOs or eCOs
- {EidValue, _, _} = mnesia_db_work:pickup(Pid, none),
- crypto_utils:store_euicc_pubkey_from_ipaEuiccDataResponse(
- IpaEuiccDataResponse, EidValue
- ),
- Outcome = esipa_rest_utils:ipaEuiccDataResponse_to_outcome(IpaEuiccDataResponse),
- ok = mnesia_db_work:finish(Pid, Outcome, EsipaReq);
+ ok = esipa_asn1_handler_utils:handle_ipaEuiccDataResponse(
+ Pid, IpaEuiccDataResponse, EsipaReq
+ );
{profileDownloadTriggerResult, _} ->
% The profileDownloadTriggerResult is sent by the IPAd in case a profile was downloaded directly from an
% RSP server, bypassing the eIM (see also SGP.32, section 3.2.3.1). This is a feature that this eIM does
diff --git a/src/esipa_asn1_handler_utils.erl b/src/esipa_asn1_handler_utils.erl
index 1b8039f..d350116 100644
--- a/src/esipa_asn1_handler_utils.erl
+++ b/src/esipa_asn1_handler_utils.erl
@@ -6,7 +6,7 @@
-module(esipa_asn1_handler_utils).
--export([handle_euiccPackageResult/3]).
+-export([handle_euiccPackageResult/3, handle_ipaEuiccDataResponse/3]).
eimTransactionId_from_euiccPackageResult(EuiccPackageResult) ->
case EuiccPackageResult of
@@ -24,6 +24,16 @@
none
end.
+eimTransactionId_from_ipaEuiccDataResponse(IpaEuiccDataResponse) ->
+ case IpaEuiccDataResponse of
+ {ipaEuiccData, IpaEuiccData} ->
+ maps:get(eimTransactionId, IpaEuiccData, none);
+ {ipaEuiccDataResponseError, IpaEuiccDataResponseError} ->
+ maps:get(eimTransactionId, IpaEuiccDataResponseError, none);
+ _ ->
+ none
+ end.
+
process_euiccPackageResult(Pid, EuiccPackageResult, Debuginfo, EimTransactionId) ->
WorkBind = fun(Map) ->
case maps:is_key(eimTransactionId, Map) of
@@ -98,3 +108,16 @@
Pid, [{[{procedureError, euiccSignatureInvalid}]}], Debuginfo
)
end.
+
+% Handle an IpaEuiccDataResponse, this includes everything from the handling of the work items in mnesia_db as well
+% as the generation of an appropriate outcome for the REST API. In case IpaEuiccDataResponse contains an eUICC public
+% key, we will automatically store it so that we can use it to check the signatures of PSMOs and eCOs.
+handle_ipaEuiccDataResponse(Pid, IpaEuiccDataResponse, Debuginfo) ->
+ EimTransactionId = eimTransactionId_from_ipaEuiccDataResponse(IpaEuiccDataResponse),
+ {EidValue, _, _} = mnesia_db_work:pickup(Pid, EimTransactionId),
+
+ % drive-by store the eUICC public key so that we can use it later to check the signatures of PSMOs or eCOs
+ crypto_utils:store_euicc_pubkey_from_ipaEuiccDataResponse(IpaEuiccDataResponse, EidValue),
+
+ Outcome = esipa_rest_utils:ipaEuiccDataResponse_to_outcome(IpaEuiccDataResponse),
+ mnesia_db_work:finish(Pid, Outcome, Debuginfo).
diff --git a/src/esipa_rest_utils.erl b/src/esipa_rest_utils.erl
index e88c58b..4a3d1a3 100644
--- a/src/esipa_rest_utils.erl
+++ b/src/esipa_rest_utils.erl
@@ -12,7 +12,7 @@
euiccPackageResultDataSigned_to_outcome/1,
profileInstallationResult_to_outcome/1,
cancelSessionResponse_to_outcome/1,
- edr_order_to_ipaEuiccDataRequest/1,
+ edr_order_to_ipaEuiccDataRequest/2,
ipaEuiccDataResponse_to_outcome/1
]).
@@ -510,11 +510,13 @@
end.
% Generate an ipaEuiccDataRequest from a eDR Order (JSON REST API)
-edr_order_to_ipaEuiccDataRequest(Order) ->
- % TODO: also add support for the two optional parameters SubjectKeyIdentifier and searchCriteria
+edr_order_to_ipaEuiccDataRequest(Order, EimTransactionId) ->
+ % TODO: also add support for the two optional parameters euiccCiPKIdentifierToBeUsed, searchCriteriaNotification,
+ % and searchCriteriaEuiccPackageResult.
case Order of
{[{<<"edr">>, {[{<<"tagList">>, TagList}]}}]} ->
- {ipaEuiccDataRequest, #{tagList => utils:hex_to_binary(TagList)}};
+ {ipaEuiccDataRequest, #{tagList => utils:hex_to_binary(TagList),
+ eimTransactionId => EimTransactionId}};
_ ->
error
end.
--
To view, visit https://gerrit.osmocom.org/c/onomondo-eim/+/42951?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: onomondo-eim
Gerrit-Branch: master
Gerrit-Change-Id: Ie414eef7fd5c8928dd611503f4dd8d67322c0e80
Gerrit-Change-Number: 42951
Gerrit-PatchSet: 1
Gerrit-Owner: dexter <pmaier(a)sysmocom.de>
Jenkins Builder has posted comments on this change by dexter. ( https://gerrit.osmocom.org/c/onomondo-eim/+/42946?usp=email )
Change subject: mnesia_db: split functionality into dedicated modules
......................................................................
Patch Set 1:
(2 comments)
File src/mnesia_db_rest.erl:
Robot Comment from checkpatch (run ID ):
https://gerrit.osmocom.org/c/onomondo-eim/+/42946/comment/8b46ef81_1de4756f… :
PS1, Line 225: % (hours, days), than this may mean that the REST API user lost interest. In this case the related items shoud be
'shoud' may be misspelled - perhaps 'should'?
File src/mnesia_db_work.erl:
Robot Comment from checkpatch (run ID ):
https://gerrit.osmocom.org/c/onomondo-eim/+/42946/comment/6f688631_9154ec5d… :
PS1, Line 211: % Update a work item that is in progress. This fuction updates the state (any user defined term) of the work item.
'fuction' may be misspelled - perhaps 'function'?
--
To view, visit https://gerrit.osmocom.org/c/onomondo-eim/+/42946?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: onomondo-eim
Gerrit-Branch: master
Gerrit-Change-Id: Ifc649337ab7ce7eb9dc602a1439293130f002f41
Gerrit-Change-Number: 42946
Gerrit-PatchSet: 1
Gerrit-Owner: dexter <pmaier(a)sysmocom.de>
Gerrit-CC: Jenkins Builder
Gerrit-Comment-Date: Wed, 08 Jul 2026 10:09:41 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
dexter has uploaded this change for review. ( https://gerrit.osmocom.org/c/onomondo-eim/+/42946?usp=email )
Change subject: mnesia_db: split functionality into dedicated modules
......................................................................
mnesia_db: split functionality into dedicated modules
The mnesia_db module is responsible to handle three different tables
with different scope. Let's split the functionality into separate
modules to make the code easier to maintain.
Change-Id: Ifc649337ab7ce7eb9dc602a1439293130f002f41
Related: SYS#8100
---
A include/mnesia_db_euicc.hrl
A include/mnesia_db_rest.hrl
A include/mnesia_db_work.hrl
M src/crypto_utils.erl
M src/esipa_asn1_handler.erl
M src/esipa_asn1_handler_utils.erl
M src/esipa_asn1_http_handler.erl
M src/esipa_rest_utils.erl
M src/mnesia_db.erl
A src/mnesia_db_euicc.erl
A src/mnesia_db_rest.erl
A src/mnesia_db_work.erl
M src/rest_handler.erl
13 files changed, 908 insertions(+), 854 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/onomondo-eim refs/changes/46/42946/1
diff --git a/include/mnesia_db_euicc.hrl b/include/mnesia_db_euicc.hrl
new file mode 100644
index 0000000..57578e7
--- /dev/null
+++ b/include/mnesia_db_euicc.hrl
@@ -0,0 +1,10 @@
+% In this table record we save basic meta information about the eUICCs that we manage with this eIM. The information
+% stored is primarily needed to authenticate eUICC Package Results.
+-record(euicc, {
+ eidValue :: binary(), % EID of the related eUICC
+ counterValue :: integer(), % Counter Value (see also SGP.32, section 2.11.1.1.1)
+ consumerEuicc :: boolean(), % flag to mark an eUICC as consumer type (IoT eUICC emulation in onomondo-ipa)
+ associationToken :: integer(), % Association Token (see also SGP.32, section 2.11.1.1.1)
+ signPubKey :: binary(), % Pubkey to authenticate eUICC Package Results (see also SGP.32, section 2.11.2.1)
+ signAlgo :: binary() % Algorithem to authenticate eUICC Package Results (prime256v1 or brainpoolP256r1)
+}).
diff --git a/include/mnesia_db_rest.hrl b/include/mnesia_db_rest.hrl
new file mode 100644
index 0000000..66c6d94
--- /dev/null
+++ b/include/mnesia_db_rest.hrl
@@ -0,0 +1,14 @@
+% In this table record we hold the state of a rest resource. A rest resource is created by the requestor and contains
+% an order, which is processed at the next possible opportunity. The requestor can use the resource to monitor the
+% status of the order he has requested. When processing is done, it is the responsibility f the requestor to request
+% the deletion of the rest resource.
+-record(rest, {
+ resourceId :: binary(), % identifier of the related REST resource
+ facility :: atom(), % an atom describing the facility ("download", "psmp", "eco", "edr" or "euicc")
+ eidValue :: binary(), % EID of the related eUICC
+ order :: tuple(), % order parameters received on creation of the related REST resource
+ status :: atom(), % an atom describing the global processing state of the order ("new", "work", or "done")
+ timestamp :: integer(), % timestamp of the last state change
+ outcome :: any(), % outcome of the requested order
+ debuginfo :: any() % elang term containing debug information (presented as binary string to the requestor)
+}).
diff --git a/include/mnesia_db_work.hrl b/include/mnesia_db_work.hrl
new file mode 100644
index 0000000..b864141
--- /dev/null
+++ b/include/mnesia_db_work.hrl
@@ -0,0 +1,13 @@
+% In this table record we hold the state (work item) of a transaction that is currently processed (worked on). A work
+% item is created when a requiest from the IPAd positively maps to a pending order in the REST table
+% (see mnesia_db_rest). A work item can be understood as the dynamic state of the ongoing connection between eIM and
+% IPAd (and SM-DP+). When the transaction is done and the work is finished, the related work item is deleted and the
+% outcome is recorded at the related REST resource.
+-record(work, {
+ pid :: pid(), % PID of the sub-process (cowbow) on which the work item is processed.
+ resourceId :: binary(), % identifier of the related REST resource
+ transactionId :: binary(), % transaction ID (either eimTransactionId or transactionId, see also SGP.32 and SGP.22)
+ eidValue :: binary(), % EID of the related eUICC
+ order :: tuple(), % order parameters received on creation of the related REST resource
+ state :: any() % intermediate results and states generated while processing the requested order
+}).
diff --git a/src/crypto_utils.erl b/src/crypto_utils.erl
index aca40e9..63abc59 100644
--- a/src/crypto_utils.erl
+++ b/src/crypto_utils.erl
@@ -40,7 +40,7 @@
sign_euiccPackageSigned(EuiccPackageSigned, EidValue) ->
% Read the AssociationToken
- {ok, AssociationToken} = mnesia_db:euicc_param_get(EidValue, associationToken),
+ {ok, AssociationToken} = mnesia_db_euicc:param_get(EidValue, associationToken),
%Format message to be signed
{ok, EuiccPackageSignedEnc} = 'SGP32Definitions':encode(
@@ -64,10 +64,10 @@
verify_signature(Message, Signature, EidValue) ->
DERSignature = plain_to_der(Signature),
- {ok, SubjectPublicKeyHex} = mnesia_db:euicc_param_get(EidValue, signPubKey),
+ {ok, SubjectPublicKeyHex} = mnesia_db_euicc:param_get(EidValue, signPubKey),
SubjectPublicKey = utils:hex_to_binary(SubjectPublicKeyHex),
- {ok, SignAlgo} = mnesia_db:euicc_param_get(EidValue, signAlgo),
+ {ok, SignAlgo} = mnesia_db_euicc:param_get(EidValue, signAlgo),
NamedCurve =
case SignAlgo of
<<"prime256v1">> ->
@@ -108,11 +108,11 @@
end.
verify_euiccPackageResultSigned(EuiccPackageResult, EidValue) ->
- {ok, ConsumerEuicc} = mnesia_db:euicc_param_get(EidValue, consumerEuicc),
+ {ok, ConsumerEuicc} = mnesia_db_euicc:param_get(EidValue, consumerEuicc),
case ConsumerEuicc of
false ->
% Read the AssociationToken
- {ok, AssociationToken} = mnesia_db:euicc_param_get(EidValue, associationToken),
+ {ok, AssociationToken} = mnesia_db_euicc:param_get(EidValue, associationToken),
case EuiccPackageResult of
{euiccPackageResultSigned, EuiccPackageResultSigned} ->
@@ -275,15 +275,15 @@
_ ->
<<"unknown">>
end,
- ok = mnesia_db:euicc_param_set(EidValue, signPubKey, utils:binary_to_hex(SignPubKey)),
- ok = mnesia_db:euicc_param_set(EidValue, signAlgo, SignAlgo),
+ ok = mnesia_db_euicc:param_set(EidValue, signPubKey, utils:binary_to_hex(SignPubKey)),
+ ok = mnesia_db_euicc:param_set(EidValue, signAlgo, SignAlgo),
ok;
_ ->
error
end.
store_euicc_pubkey_from_authenticateResponseOk(AuthRespOk, EidValue) ->
- case mnesia_db:euicc_param_get(EidValue, signPubKey) of
+ case mnesia_db_euicc:param_get(EidValue, signPubKey) of
{ok, <<>>} ->
% There is no public key stored yet for this eUICC, use the public
% key provided in the eUICC certificate
@@ -296,7 +296,7 @@
end.
store_euicc_pubkey_from_ipaEuiccDataResponse(IpaEuiccDataResponse, EidValue) ->
- case mnesia_db:euicc_param_get(EidValue, signPubKey) of
+ case mnesia_db_euicc:param_get(EidValue, signPubKey) of
{ok, <<>>} ->
% There is no public key stored yet for this eUICC, use the public
% key provided in the eUICC certificate
diff --git a/src/esipa_asn1_handler.erl b/src/esipa_asn1_handler.erl
index 111ef17..82a4290 100644
--- a/src/esipa_asn1_handler.erl
+++ b/src/esipa_asn1_handler.erl
@@ -19,10 +19,10 @@
%GSMA SGP.32, section 6.3.2.1
handle_asn1(Pid, {initiateAuthenticationRequestEsipa, EsipaReq}) ->
- {_, _, WorkState} = mnesia_db:work_pickup(Pid, none),
+ {_, _, WorkState} = mnesia_db_work:pickup(Pid, none),
BaseUrl = maps:get(smdpAddress, EsipaReq),
NewWorkState = WorkState#{smdpAddress => BaseUrl},
- mnesia_db:work_update(Pid, NewWorkState),
+ mnesia_db_work:update(Pid, NewWorkState),
% euiccInfo1 is also an optional field in initiateAuthenticationRequestEsipa and a mandatory field in
% initiateAuthenticationRequest. However the field is only missing in case the IPA capability minimizeEsipaBytes is
@@ -39,7 +39,7 @@
case Es9Resp of
{initiateAuthenticationOk, InitAuthOk} ->
TransactionId = maps:get(transactionId, InitAuthOk),
- mnesia_db:work_bind(Pid, TransactionId),
+ mnesia_db_work:bind(Pid, TransactionId),
% TODO: matchingId and ctxParams1 are not defined in the ES9+ InitiateAuthenticationResponse message.
% However in ESipa those fields are optional fields and either one of it should be populated in case an
% AC is used (which we do). This means we should populate those fields. The matchingId can be extracted
@@ -57,7 +57,7 @@
},
{initiateAuthenticationOkEsipa, InitAuthOkEsipa};
{initiateAuthenticationError, InitAuthErr} ->
- ok = mnesia_db:work_finish(
+ ok = mnesia_db_work:finish(
Pid,
[{[{procedureError, initiateAuthenticationError}]}],
EsipaReq
@@ -68,7 +68,7 @@
%GSMA SGP.32, section 6.3.2.2
handle_asn1(Pid, {authenticateClientRequestEsipa, EsipaReq}) ->
TransactionId = maps:get(transactionId, EsipaReq),
- {EidValue, _, WorkState} = mnesia_db:work_pickup(Pid, TransactionId),
+ {EidValue, _, WorkState} = mnesia_db_work:pickup(Pid, TransactionId),
BaseUrl = maps:get(smdpAddress, WorkState),
% setup ES9+ request message
@@ -83,7 +83,7 @@
authenticateServerResponse => {authenticateResponseOk, AuthRespOk}
}};
{authenticateResponseError, AuthRespErr} ->
- ok = mnesia_db:work_finish(
+ ok = mnesia_db_work:finish(
Pid,
[{[{procedureError, authenticateResponseError}]}],
EsipaReq
@@ -106,7 +106,7 @@
{authenticateClientOk, AuthClntRespEs9} ->
{authenticateClientOkDPEsipa, AuthClntRespEs9};
{authenticateClientError, AuthClntErr} ->
- ok = mnesia_db:work_finish(
+ ok = mnesia_db_work:finish(
Pid,
[{[{procedureError, authenticateClientError}]}],
EsipaReq
@@ -117,7 +117,7 @@
%GSMA SGP.32, section 6.3.2.3
handle_asn1(Pid, {getBoundProfilePackageRequestEsipa, EsipaReq}) ->
TransactionId = maps:get(transactionId, EsipaReq),
- {_, _, WorkState} = mnesia_db:work_pickup(Pid, TransactionId),
+ {_, _, WorkState} = mnesia_db_work:pickup(Pid, TransactionId),
BaseUrl = maps:get(smdpAddress, WorkState),
% setup ES9+ request message
@@ -130,7 +130,7 @@
prepareDownloadResponse => {downloadResponseOk, DwnldRespOk}
}};
{downloadResponseError, DwnldRespErr} ->
- ok = mnesia_db:work_finish(
+ ok = mnesia_db_work:finish(
Pid,
[{[{procedureError, downloadResponseError}]}],
EsipaReq
@@ -155,7 +155,7 @@
% however, this eIM does not support the IPA capability minimizeEsipaBytes)
{getBoundProfilePackageOkEsipa, GetBndPrflePkgOk};
{getBoundProfilePackageError, GetBndPrflePkgErr} ->
- ok = mnesia_db:work_finish(
+ ok = mnesia_db_work:finish(
Pid,
[{[{procedureError, getBoundProfilePackageError}]}],
EsipaReq
@@ -166,7 +166,7 @@
%GSMA SGP.32, section 6.3.2.5
handle_asn1(Pid, {cancelSessionRequestEsipa, EsipaReq}) ->
TransactionId = maps:get(transactionId, EsipaReq),
- {_, _, WorkState} = mnesia_db:work_pickup(Pid, TransactionId),
+ {_, _, WorkState} = mnesia_db_work:pickup(Pid, TransactionId),
BaseUrl = maps:get(smdpAddress, WorkState),
% setup ES9+ request message
@@ -179,7 +179,7 @@
cancelSessionResponse => {cancelSessionResponseOk, CancelSessionRespOk}
}};
{cancelSessionResponseError, CancelSessionRespErr} ->
- ok = mnesia_db:work_finish(
+ ok = mnesia_db_work:finish(
Pid,
[{[{procedureError, cancelSessionResponseError}]}],
EsipaReq
@@ -197,7 +197,7 @@
{cancelSessionResponseEs9, Es9Resp} = es9p_client:request_json(Es9Req, BaseUrl),
Outcome = esipa_rest_utils:cancelSessionResponse_to_outcome(CancelSessionResp),
- ok = mnesia_db:work_finish(Pid, Outcome, EsipaReq),
+ ok = mnesia_db_work:finish(Pid, Outcome, EsipaReq),
% setup ESipa response message
% CancelSessionResponseEsipa and CancelSessionResponseEs9 share the exact same definition, so we may convert
@@ -226,7 +226,7 @@
% Notification Receivers (see also GSMA SGP.32, section 3.7) procedure. By then the context in the
% eIM may be long gone. The eIM will be unable to match the ProfileInstallationResult to any
% context but it will foward it to the SMDP+ anyway.
- case mnesia_db:work_bind(Pid, TransactionId) of
+ case mnesia_db_work:bind(Pid, TransactionId) of
ok ->
% A work item exists, foward the ProfileInstallationResult and make its contents
% available to the REST API user
@@ -235,9 +235,9 @@
Outcome = esipa_rest_utils:profileInstallationResult_to_outcome(
PrfleInstRslt
),
- ok = mnesia_db:work_finish(Pid, Outcome, EsipaReq);
+ ok = mnesia_db_work:finish(Pid, Outcome, EsipaReq);
_ ->
- ok = mnesia_db:work_finish(
+ ok = mnesia_db_work:finish(
Pid,
[{[{procedureError, handleNotificationError}]}],
EsipaReq
@@ -280,20 +280,20 @@
% update. Get the notifyStateChange flag like so: NotifStateChg = maps:is_key(notifyStateChange, EsipaReq).
EidValue = maps:get(eidValue, EsipaReq),
- Work = mnesia_db:work_fetch(utils:binary_to_hex(EidValue), Pid),
+ Work = mnesia_db_work:fetch(utils:binary_to_hex(EidValue), Pid),
EsipaResp =
case Work of
{download, Order} ->
% The first time we see a TransactionId is in the SMDP+ response to the
% initiateAuthenticationRequest
{[{<<"download">>, {[{<<"activationCode">>, ActivationCode}]}}]} = Order,
- mnesia_db:work_update(Pid, #{}),
+ mnesia_db_work:update(Pid, #{}),
{profileDownloadTriggerRequest, #{
profileDownloadData => {activationCode, ActivationCode}
}};
{psmo, Order} ->
TransactionIdPsmo = rand:bytes(16),
- mnesia_db:work_bind(Pid, TransactionIdPsmo),
+ mnesia_db_work:bind(Pid, TransactionIdPsmo),
EuiccPackageSigned = esipa_rest_utils:psmo_order_to_euiccPackageSigned(
Order,
EidValue,
@@ -301,7 +301,7 @@
),
case EuiccPackageSigned of
error ->
- ok = mnesia_db:work_finish(
+ ok = mnesia_db_work:finish(
Pid,
[{[{procedureError, badPsmo}]}],
EsipaReq
@@ -319,7 +319,7 @@
end;
{eco, Order} ->
TransactionIdEco = rand:bytes(16),
- mnesia_db:work_bind(Pid, TransactionIdEco),
+ mnesia_db_work:bind(Pid, TransactionIdEco),
EuiccPackageSigned = esipa_rest_utils:eco_order_to_euiccPackageSigned(
Order,
EidValue,
@@ -327,7 +327,7 @@
),
case EuiccPackageSigned of
error ->
- ok = mnesia_db:work_finish(
+ ok = mnesia_db_work:finish(
Pid,
[{[{procedureError, badEco}]}],
EsipaReq
@@ -347,7 +347,7 @@
IpaEuiccDataRequest = esipa_rest_utils:edr_order_to_ipaEuiccDataRequest(Order),
case IpaEuiccDataRequest of
error ->
- ok = mnesia_db:work_finish(
+ ok = mnesia_db_work:finish(
Pid,
[{[{procedureError, badEdr}]}],
EsipaReq
@@ -359,7 +359,7 @@
none ->
{eimPackageError, noEimPackageAvailable};
_ ->
- ok = mnesia_db:work_finish(Pid, [{[{procedureError, badOrder}]}], EsipaReq),
+ ok = mnesia_db_work:finish(Pid, [{[{procedureError, badOrder}]}], EsipaReq),
{eimPackageError, undefinedError}
end,
{getEimPackageResponse, EsipaResp};
@@ -382,12 +382,12 @@
handle_asn1_notificationList(Pid, NotificationList);
{ipaEuiccDataResponse, IpaEuiccDataResponse} ->
% drive-by store the eUICC public key so that we can use it later to sign PSMOs or eCOs
- {EidValue, _, _} = mnesia_db:work_pickup(Pid, none),
+ {EidValue, _, _} = mnesia_db_work:pickup(Pid, none),
crypto_utils:store_euicc_pubkey_from_ipaEuiccDataResponse(
IpaEuiccDataResponse, EidValue
),
Outcome = esipa_rest_utils:ipaEuiccDataResponse_to_outcome(IpaEuiccDataResponse),
- ok = mnesia_db:work_finish(Pid, Outcome, EsipaReq);
+ ok = mnesia_db_work:finish(Pid, Outcome, EsipaReq);
{profileDownloadTriggerResult, _} ->
% The profileDownloadTriggerResult is sent by the IPAd in case a profile was downloaded directly from an
% RSP server, bypassing the eIM (see also SGP.32, section 3.2.3.1). This is a feature that this eIM does
@@ -398,12 +398,12 @@
eimPackageResultErrorCode, EimPackageResultResponseError
),
Outcome = [{[{eimPackageError, EimPackageResultErrorCode}]}],
- ok = mnesia_db:work_finish(Pid, Outcome, EsipaReq)
+ ok = mnesia_db_work:finish(Pid, Outcome, EsipaReq)
end,
{provideEimPackageResultResponse, {emptyResponse, #{}}};
%Unsupported request
handle_asn1(Pid, Request) ->
- mnesia_db:work_finish(Pid, [{[{procedureError, abortedOrder}]}], unsupported),
+ mnesia_db_work:finish(Pid, [{[{procedureError, abortedOrder}]}], unsupported),
logger:info(
"Handling of IPAd request failed, the request type is unsupported,~nRequest=~p,~nPid=~p~n",
[Request, Pid]
diff --git a/src/esipa_asn1_handler_utils.erl b/src/esipa_asn1_handler_utils.erl
index c297638..ac7d6f0 100644
--- a/src/esipa_asn1_handler_utils.erl
+++ b/src/esipa_asn1_handler_utils.erl
@@ -28,16 +28,16 @@
WorkBind = fun(Map) ->
case maps:is_key(eimTransactionId, Map) of
true ->
- mnesia_db:work_bind(Pid, maps:get(eimTransactionId, Map));
+ mnesia_db_work:bind(Pid, maps:get(eimTransactionId, Map));
_ ->
ok
end
end,
CheckCounterValue = fun(Map) ->
- {EidValue, _, _} = mnesia_db:work_pickup(Pid, TransactionId),
+ {EidValue, _, _} = mnesia_db_work:pickup(Pid, TransactionId),
CounterValueIpad = maps:get(counterValue, Map),
- {ok, CounterValueEim} = mnesia_db:euicc_param_get(EidValue, counterValue),
+ {ok, CounterValueEim} = mnesia_db_euicc:param_get(EidValue, counterValue),
case CounterValueIpad of
CounterValueEim ->
ok;
@@ -83,18 +83,18 @@
[{[{euiccPackageErrorCode, undefinedError}]}]
end,
- mnesia_db:work_finish(Pid, Outcome, EsipaReq).
+ mnesia_db_work:finish(Pid, Outcome, EsipaReq).
% Handle an EuiccPackageResult, this includes everything from the handling of the work items in mnesia_db, down to
% signature checks and the generation of an appropriate outcome for the REST API.
handle_euiccPackageResult(Pid, EuiccPackageResult, EsipaReq) ->
TransactionId = transactionId_from_euiccPackageResult(EuiccPackageResult),
- {EidValue, _, _} = mnesia_db:work_pickup(Pid, TransactionId),
+ {EidValue, _, _} = mnesia_db_work:pickup(Pid, TransactionId),
case crypto_utils:verify_euiccPackageResultSigned(EuiccPackageResult, EidValue) of
ok ->
process_euiccPackageResult(Pid, EuiccPackageResult, EsipaReq, TransactionId);
_ ->
- mnesia_db:work_finish(
+ mnesia_db_work:finish(
Pid, [{[{procedureError, euiccSignatureInvalid}]}], EsipaReq
)
end.
diff --git a/src/esipa_asn1_http_handler.erl b/src/esipa_asn1_http_handler.erl
index ff5fcff..5f6ff9d 100644
--- a/src/esipa_asn1_http_handler.erl
+++ b/src/esipa_asn1_http_handler.erl
@@ -60,7 +60,7 @@
normal ->
ok;
_ ->
- mnesia_db:work_finish(
+ mnesia_db_work:finish(
maps:get(pid, Req0), [{[{procedureError, abortedOrder}]}], Reason
),
logger:info(
diff --git a/src/esipa_rest_utils.erl b/src/esipa_rest_utils.erl
index d026dc0..78939ab 100644
--- a/src/esipa_rest_utils.erl
+++ b/src/esipa_rest_utils.erl
@@ -190,7 +190,7 @@
error;
_ ->
{ok, EimId} = application:get_env(onomondo_eim, eim_id),
- {ok, CounterValue} = mnesia_db:euicc_counter_tick(utils:binary_to_hex(EidValue)),
+ {ok, CounterValue} = mnesia_db_euicc:counter_tick(utils:binary_to_hex(EidValue)),
#{
eimId => list_to_binary(EimId),
eidValue => EidValue,
diff --git a/src/mnesia_db.erl b/src/mnesia_db.erl
index 6a2b3e5..cd69e3a 100644
--- a/src/mnesia_db.erl
+++ b/src/mnesia_db.erl
@@ -6,71 +6,13 @@
-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]).
-% REST functions, to be called by the REST server (from outside via cowboy)
--export([rest_list/1, rest_lookup/2, rest_create/3, rest_delete/2]).
-
-% work functions, to be called by the eIM code (from inside)
--export([work_fetch/2, work_pickup/2, work_update/2, work_bind/2, work_finish/3]).
-
-% euicc functions, to be called by the eIM code (from inside)
--export([euicc_counter_tick/1, euicc_param_get/2, euicc_param_set/3]).
-
-% debugging
--export([dump_rest/0, dump_work/0, dump_euicc/0]).
-
-% trigger recurring events (called automatically by timer from this module)
--export([cleanup/0, euicc_setparam/0]).
-
--record(rest, {
- resourceId :: binary(),
- facility :: atom(),
- eidValue :: binary(),
- order :: binary(),
- status :: atom(),
- timestamp :: integer(),
- outcome :: binary(),
- debuginfo :: binary()
-}).
--record(work, {
- pid :: pid(),
- resourceId :: binary(),
- transactionId :: binary(),
- eidValue :: binary(),
- order :: binary(),
- state :: binary()
-}).
--record(euicc, {
- eidValue :: binary(),
- counterValue :: integer(),
- consumerEuicc :: boolean(),
- associationToken :: integer(),
- signPubKey :: binary(),
- signAlgo :: binary()
-}).
-
-% Caution: The status (atom) must be either "new", "work", or "done"
-
-% helper function (to be called from a transaction) to set the status of an item in the rest table.
-trans_rest_set_status(ResourceId, Status, Outcome, Debuginfo) ->
- Q = qlc:q([X || X <- mnesia:table(rest), X#rest.resourceId == ResourceId]),
- Rows = qlc:e(Q),
- Timestamp = os:system_time(seconds),
- case Rows of
- [Row | []] ->
- mnesia:write(Row#rest{
- status = Status, timestamp = Timestamp, outcome = Outcome, debuginfo = Debuginfo
- }),
- ok;
- [] ->
- error;
- _ ->
- error
- end.
-
% Initialize databse scheme, tables and check the database for unfinished orders
init() ->
% Create an initial mnesia scheme.
@@ -154,7 +96,7 @@
ResourceIds = qlc:e(Q),
lists:foreach(
fun(ResourceId) ->
- trans_rest_set_status(
+ mnesia_db_rest:trans_set_status(
ResourceId,
done,
[{[{procedureError, abortedOrder}]}],
@@ -167,748 +109,7 @@
{atomic, ok} = mnesia:transaction(Trans),
% Start recurring event cycles
- ok = cleanup(),
- ok = euicc_setparam(),
+ ok = mnesia_db_euicc:timer_setparam(),
+ ok = mnesia_db_rest:timer_cleanup(),
ok.
-
-% Create REST resource (order)
-rest_create(Facility, EidValue, Order) ->
- ok = euicc_create_if_not_exist(EidValue),
- ResourceId = uuid:uuid_to_string(uuid:get_v4_urandom()),
- Timestamp = os:system_time(seconds),
- Row = #rest{
- resourceId = ResourceId,
- facility = Facility,
- eidValue = EidValue,
- order = Order,
- status = new,
- timestamp = Timestamp,
- outcome = [],
- debuginfo = none
- },
- Trans = fun() ->
- Q = qlc:q([X#rest.resourceId || X <- mnesia:table(rest), X#rest.resourceId == ResourceId]),
- Present = qlc:e(Q),
- case Present of
- [] ->
- mnesia:write(Row);
- _ ->
- error
- end
- end,
- {atomic, Result} = mnesia:transaction(Trans),
- {Result, ResourceId}.
-
-% Lookup REST resource (order)
-rest_lookup(ResourceId, Facility) ->
- Trans = fun() ->
- Q = qlc:q([
- {
- X#rest.status,
- X#rest.timestamp,
- X#rest.eidValue,
- X#rest.order,
- X#rest.outcome,
- X#rest.debuginfo
- }
- || X <- mnesia:table(rest),
- X#rest.resourceId == ResourceId,
- X#rest.facility == Facility
- ]),
- qlc:e(Q)
- end,
- {atomic, Result} = mnesia:transaction(Trans),
- case Result of
- [{Status, Timestamp, EidValue, Order, Outcome, Debuginfo}] ->
- {Status, Timestamp, EidValue, Order, Outcome, Debuginfo};
- [] ->
- none;
- _ ->
- error
- end.
-
-% Delete REST resource (order)
-rest_delete(ResourceId, Facility) ->
- Trans = fun() ->
- QRest = qlc:q([
- X#rest.resourceId
- || X <- mnesia:table(rest),
- X#rest.resourceId == ResourceId,
- X#rest.facility == Facility
- ]),
- RestPresent = qlc:e(QRest),
- case RestPresent of
- [] ->
- none;
- _ ->
- OidRest = {rest, ResourceId},
- ok = mnesia:delete(OidRest),
-
- % There may be an orphaned work item now, which we must also remove. This will also kill
- % the order in case it is currently in progress.
- QWork = qlc:q([
- X#work.pid
- || X <- mnesia:table(work), X#work.resourceId == ResourceId
- ]),
- WorkPresent = qlc:e(QWork),
- case WorkPresent of
- [] ->
- ok;
- [Pid] ->
- OidWork = {work, Pid},
- mnesia:delete(OidWork);
- _ ->
- error
- end
- end
- end,
- {atomic, Result} = mnesia:transaction(Trans),
- Result.
-
-% List the resource identifiers of currently present REST resources
-rest_list(Facility) ->
- Trans = fun() ->
- Q = qlc:q([X#rest.resourceId || X <- mnesia:table(rest), X#rest.facility == Facility]),
- qlc:e(Q)
- end,
- {atomic, Result} = mnesia:transaction(Trans),
- Result.
-
-% Start working on an order by creating a an entry in the work table and marking it's status as "work". After calling
-% this it is the callers responsibility to handle the work item and call rest_finish_order when the work is done.
-work_fetch(EidValue, Pid) ->
- % One process can only work on one work item at a time. The API user must call work_finish before the next work
- % item can be processed. If there is already a pending work item under the given PID, forcefully finish this work
- % item.
- TransPidExists = fun() ->
- Q = qlc:q([X || X <- mnesia:table(work), X#work.pid == Pid]),
- WorkPresent = qlc:e(Q),
- case WorkPresent of
- [] ->
- false;
- _ ->
- true
- end
- end,
-
- {atomic, PidExists} = mnesia:transaction(TransPidExists),
- case PidExists of
- true ->
- work_finish(Pid, [{[{procedureError, stuckOrder}]}], none);
- false ->
- ok
- end,
-
- % Read the next pending REST resource from the rest table and create a related work item. The work item is then
- % in progress.
- Trans = fun() ->
- Q = qlc:q([
- X
- || X <- mnesia:table(rest),
- X#rest.eidValue == EidValue,
- X#rest.status == new,
- X#rest.facility =/= euicc
- ]),
- Rows = qlc:e(Q),
- case Rows of
- [Row | _] ->
- % Create an entry in the work table
- WorkRow = #work{
- pid = Pid,
- resourceId = Row#rest.resourceId,
- transactionId = none,
- eidValue = Row#rest.eidValue,
- order = Row#rest.order,
- state = none
- },
- case mnesia:write(WorkRow) of
- ok ->
- % We are now working on this order
- ok = trans_rest_set_status(Row#rest.resourceId, work, [], none),
- Row;
- _ ->
- error
- end;
- [] ->
- none;
- _ ->
- error
- end
- end,
-
- {atomic, Result} = mnesia:transaction(Trans),
- case Result of
- {rest, _, Facility, _, Order, _, _, _, _} ->
- logger:info(
- "Work: fetching new work item,~nEidValue=~p, Pid=~p, Facility=~p,~nOrder=~p~n",
- [EidValue, Pid, Facility, Order]
- ),
- {Facility, Order};
- none ->
- logger:info("Work: no work item in database,~nEidValue=~p, Pid=~p~n", [EidValue, Pid]),
- none;
- _ ->
- logger:error("Work: cannot fetch work item, database error,~nEidValue=~p, Pid=~p~n", [
- EidValue, Pid
- ]),
- error
- end.
-
-% Bind a work item to a TransactionId. The transactionId has to be a unique identifier that can be used as a secondary
-% key to find a work item in the databse. The binding works in two directions. The work item is first searched by its
-% pid, when found, the transactionId is updated. In case the pid has become invalid, then the work item is searched
-% again by the transactionId and when found, the pid is updated. This function can be called any time after work_fetch
-% was called before. It can also be called multiple times.
-work_bind(Pid, TransactionId) ->
- % Transaction to update the TransactionId. This is the normal case. A work item starts without having a
- % TransactionId assigned. As soon as a (new) TransactionId becomes known, it is updated using this Transaction.
- TransUpdateTrnsId = fun() ->
- Q = qlc:q([X || X <- mnesia:table(work), X#work.pid == Pid]),
- Rows = qlc:e(Q),
- case Rows of
- [Row | _] ->
- mnesia:write(Row#work{transactionId = TransactionId});
- [] ->
- none;
- _ ->
- error
- end
- end,
-
- % Transaction to update the PID. This is a corner case that comes into play in case the PID is lost (the
- % process/connection handling this work item has died). We then try to find the work item by the TransactionId
- % and update its PID.
- TransUpdatePid = fun() ->
- Q = qlc:q([X || X <- mnesia:table(work), X#work.transactionId == TransactionId]),
- Rows = qlc:e(Q),
- case Rows of
- [Row | _] ->
- mnesia:write(Row#work{pid = Pid});
- [] ->
- none;
- _ ->
- error
- end
- end,
-
- {atomic, Result} = mnesia:transaction(TransUpdateTrnsId),
- case Result of
- ok ->
- logger:info("Work: bound work item to transactionId,~nPid=~p, TransactionId=~p~n", [
- Pid, TransactionId
- ]),
- ok;
- none ->
- {atomic, UpdatePidResult} = mnesia:transaction(TransUpdatePid),
- case UpdatePidResult of
- ok ->
- logger:info("Work: bound work item to PID,~nPid=~p, TransactionId=~p~n", [
- Pid, TransactionId
- ]),
- ok;
- none ->
- logger:error(
- "Work: cannot bind work item, transactionId nor PID found,~nPid=~p, TransactionId=~p~n",
- [Pid, TransactionId]
- ),
- error;
- _ ->
- logger:error(
- "Work: cannot bind work item, database error,~nPid=~p, TransactionId=~p, TransUpdatePid~n",
- [Pid, TransactionId]
- ),
- error
- end;
- _ ->
- logger:error(
- "Work: cannot bind work item, database error,~nPid=~p, TransactionId=~p, TransUpdateTrnsId~n",
- [Pid, TransactionId]
- ),
- error
- end.
-
-% Pickup a work item that is in progress. This function can be called any time after work_fetch was called
-% before. It can also be called multiple times.
-work_pickup(Pid, TransactionId) ->
- % In case a TransactionId is provied, bind the PID to this TransactionId,
- WorkBound =
- case TransactionId of
- none ->
- ok;
- _ ->
- work_bind(Pid, TransactionId)
- end,
-
- % Lookup the work state by the given PID
- case WorkBound of
- ok ->
- Trans = fun() ->
- Q = qlc:q([
- {X#work.eidValue, X#work.order, X#work.state}
- || X <- mnesia:table(work), X#work.pid == Pid
- ]),
- qlc:e(Q)
- end,
- {atomic, Result} = mnesia:transaction(Trans),
- case Result of
- [{EidValue, Order, State} | _] ->
- {EidValue, Order, State};
- [] ->
- logger:error(
- "Work: no work item found under specified Pid, already finished?, not fetched?,~nPid=~p~n",
- [Pid]
- ),
- none;
- _ ->
- logger:error("Work: cannot pick up work item, database error,~nPid=~p~n", [Pid]),
- error
- end;
- _ ->
- WorkBound
- end.
-
-% Update a work item that is in progress. This fuction updates the state (any user defined term) of the work item.
-% This function can be called any time after work_fetch was called before. It can also be called multiple times.
-work_update(Pid, State) ->
- Trans = fun() ->
- Q = qlc:q([X || X <- mnesia:table(work), X#work.pid == Pid]),
- Rows = qlc:e(Q),
- case Rows of
- [Row | _] ->
- mnesia:write(Row#work{state = State});
- [] ->
- error;
- _ ->
- error
- end
- end,
-
- {atomic, Result} = mnesia:transaction(Trans),
- case Result of
- ok ->
- logger:info("Work: updating work item,~nPid=~p, State=~p~n", [Pid, State]),
- ok;
- _ ->
- logger:error("Work: cannot update, database error,~nPid=~p, State=~p~n", [Pid, State]),
- error
- end.
-
-% Finish an order that has been worked on. This removes the related entry from the work table and sets the status in
-% the rest table to "done".
-work_finish(Pid, Outcome, Debuginfo) ->
- Trans = fun() ->
- Q = qlc:q([X#work.resourceId || X <- mnesia:table(work), X#work.pid == Pid]),
- Rows = qlc:e(Q),
- case Rows of
- [] ->
- error;
- [ResourceId | _] ->
- Oid = {work, Pid},
- ok = mnesia:delete(Oid),
- ok = trans_rest_set_status(ResourceId, done, Outcome, Debuginfo)
- end
- end,
-
- {atomic, Result} = mnesia:transaction(Trans),
- case Result of
- ok ->
- logger:info("Work: finishing work item,~nPid=~p, Outcome=~p~n", [Pid, Outcome]),
- ok;
- _ ->
- logger:error(
- "Work: cannot finish work item, database error,~nPid=~p, Outcome=~p~n",
- [Pid, Outcome]
- ),
- error
- end.
-
-trans_euicc_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">>
- },
- 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 master data entry
-euicc_create_if_not_exist(EidValue) ->
- Trans = fun() ->
- trans_euicc_create_if_not_exist(EidValue)
- end,
- {atomic, Result} = mnesia:transaction(Trans),
- case Result of
- ok ->
- logger:info("eUICC: creating new master data entry,~neID=~p~n", [EidValue]),
- ok;
- present ->
- ok;
- _ ->
- logger:error("eUICC: cannot create master data entry, database error,~neID=~p~n", [
- EidValue
- ]),
- error
- end.
-
-% get an incremented counterValue (and store the incremented counterValue as the current counterValue)
-euicc_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.
-
-%update one specific parameter in the euicc table
-trans_update_euicc_param(EidValue, Name, Value) ->
- 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});
- _ ->
- error
- end;
- [] ->
- error;
- _ ->
- error
- end.
-
-% Get an eUICC parameter by its name (atom)
-euicc_param_get(EidValue, Name) ->
- Trans = fun() ->
- Q = qlc:q([X || X <- mnesia:table(euicc), X#euicc.eidValue == EidValue]),
- Rows = qlc:e(Q),
- case Rows of
- [Row | _] ->
- case Name of
- counterValue ->
- {ok, Row#euicc.counterValue};
- consumerEuicc ->
- {ok, Row#euicc.consumerEuicc};
- associationToken ->
- {ok, Row#euicc.associationToken};
- signPubKey ->
- {ok, Row#euicc.signPubKey};
- signAlgo ->
- {ok, Row#euicc.signAlgo};
- _ ->
- error
- end;
- [] ->
- error;
- _ ->
- error
- end
- end,
-
- {atomic, Result} = mnesia:transaction(Trans),
- case Result of
- {ok, Value} ->
- logger:info("eUICC: reading eUICC parameter,~neID=~p, name=~p, value=~p~n", [
- EidValue, Name, Value
- ]),
- {ok, Value};
- _ ->
- logger:error("eUICC: cannot read eUICC parameter,~neID=~p, name=~p~n", [EidValue, Name]),
- error
- end.
-
-% Update an eUICC parameter by its name (atom)
-euicc_param_set(EidValue, Name, Value) ->
- Trans = fun() ->
- trans_update_euicc_param(EidValue, Name, Value)
- end,
- {atomic, Result} = mnesia:transaction(Trans),
- case Result of
- ok ->
- logger:info("eUICC: writing eUICC parameter,~neID=~p, name=~p, value=~p~n", [
- EidValue, Name, Value
- ]),
- ok;
- _ ->
- logger:error("eUICC: cannot write eUICC parameter,~neID=~p, name=~p~n", [EidValue, Name]),
- error
- end.
-
-mark_stuck(Timeout) ->
- % There may be corner cases where the order gets stuck because the other entity suddenly stops responding. In
- % this case the order will stall. When it remains stalled for too long (minutes), we should remove all related
- % items from the work table and put an appropriate outcome (error code) into the rest table.
-
- TimestampNow = os:system_time(seconds),
-
- % Remove Resource from work table and set an appropriate status in the rest table
- HandleResource = fun(ResourceId) ->
- % find the pid of the work item that is stuck and then delete it
- Q = qlc:q([X#work.pid || X <- mnesia:table(work), X#work.resourceId == ResourceId]),
- WorkPresent = qlc:e(Q),
- case WorkPresent of
- [Pid] ->
- Oid = {work, Pid},
- ok = mnesia:delete(Oid);
- _ ->
- ok
- end,
-
- % set status in the rest table
- trans_rest_set_status(ResourceId, done, [{[{procedureError, stuckOrder}]}], none)
- end,
-
- % Find all rest resources that stall in status "work" and older than the specified timeout value
- Trans = fun() ->
- Q = qlc:q([
- X#rest.resourceId
- || X <- mnesia:table(rest),
- X#rest.status == work,
- TimestampNow - X#rest.timestamp > Timeout
- ]),
- Rows = qlc:e(Q),
- case Rows of
- [] ->
- ok;
- Rows ->
- [HandleResource(Row) || Row <- Rows],
- ok
- end
- end,
-
- {atomic, Result} = mnesia:transaction(Trans),
- Result.
-
-mark_noshow(Timeout) ->
- % There may be corner cases where the order is never processed because the related IPAd/eUICC never shows up to
- % fetch the related eUICC package. If we see an order staying in status "new" for too long (days, weeks), we should
- % mark it as "done" and put an appropriate outcome (error code) into the rest table.
-
- TimestampNow = os:system_time(seconds),
-
- % Remove Resource from work table and set an appropriate status in the rest table
- HandleResource = fun(ResourceId) ->
- trans_rest_set_status(ResourceId, done, [{[{procedureError, noshowOrder}]}], none)
- end,
-
- % Find all rest resources that stall in status "work" and older than the specified timeout value
- Trans = fun() ->
- Q = qlc:q([
- X#rest.resourceId
- || X <- mnesia:table(rest),
- X#rest.status == new,
- TimestampNow - X#rest.timestamp > Timeout
- ]),
- Rows = qlc:e(Q),
- case Rows of
- [] ->
- ok;
- Rows ->
- [HandleResource(Row) || Row <- Rows],
- ok
- end
- end,
-
- {atomic, Result} = mnesia:transaction(Trans),
- Result.
-
-delete_expired(Timeout) ->
- % There may be cases where orders stay unmaintained for too long. When an order stays in status "done" for too long
- % (hours, days), than this may mean that the REST API user lost interest. In this case the related items shoud be
- % removed from the rest table after a reasonable timeout.
-
- TimestampNow = os:system_time(seconds),
-
- % Remove Resource from the rest table. Since an abandonned order won't have a coresponding work item in the work
- % table we do not have to worry about creating an orphaned work item.
- HandleResource = fun(ResourceId) ->
- Oid = {rest, ResourceId},
- ok = mnesia:delete(Oid)
- end,
-
- % Find all rest resources that linger in the rest table for a long time and are not in the status "new"
- Trans = fun() ->
- Q = qlc:q([
- X#rest.resourceId
- || X <- mnesia:table(rest),
- X#rest.status == done,
- TimestampNow - X#rest.timestamp > Timeout
- ]),
- Rows = qlc:e(Q),
- case Rows of
- [] ->
- ok;
- Rows ->
- [HandleResource(Row) || Row <- Rows],
- ok
- end
- end,
-
- {atomic, Result} = mnesia:transaction(Trans),
- Result.
-
-% Run a cleanup cycle the database
-cleanup() ->
- {ok, RestTimeoutStuck} = application:get_env(onomondo_eim, rest_timeout_stuck),
- {ok, RestTimeoutNoshow} = application:get_env(onomondo_eim, rest_timeout_noshow),
- {ok, RestTimeoutExpired} = application:get_env(onomondo_eim, rest_timeout_expired),
- RcStalled = mark_stuck(RestTimeoutStuck),
- RcNoshow = mark_noshow(RestTimeoutNoshow),
- RcExpired = delete_expired(RestTimeoutExpired),
-
- case {RcStalled, RcNoshow, RcExpired} of
- {ok, ok, ok} ->
- ok;
- _ ->
- logger:error("Cleanup: database error~n"),
- error
- end,
-
- % Next cleanup in 10 secs.
- {ok, _} = timer:apply_after(10000, mnesia_db, cleanup, []),
- ok.
-
-% Run scheduled eUICC procedures
-euicc_setparam() ->
- % 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 parameters in the
- % euicc table are set.
-
- HandleParam = fun(ResourceId, EidValue, Param) ->
- case Param of
- {[{Name, Value}]} ->
- case trans_update_euicc_param(EidValue, binary_to_atom(Name), Value) of
- ok ->
- trans_rest_set_status(
- ResourceId,
- done,
- [{[{euiccUpdateResult, ok}]}],
- none
- );
- _ ->
- trans_rest_set_status(
- ResourceId,
- done,
- [{[{euiccUpdateResult, badParam}]}],
- none
- )
- end;
- _ ->
- trans_rest_set_status(
- ResourceId,
- done,
- [{[{euiccUpdateResult, badParamFormat}]}],
- none
- )
- end
- end,
-
- % Parse order and process each parameter individually
- HandleResource = fun({ResourceId, EidValue, Order}) ->
- trans_euicc_create_if_not_exist(EidValue),
- case Order of
- {[{<<"euicc">>, ParameterList}]} ->
- [HandleParam(ResourceId, EidValue, Param) || Param <- ParameterList],
- ok;
- _ ->
- trans_rest_set_status(ResourceId, done, [{[{procedureError, badOrder}]}], none)
- end
- end,
-
- % Look into facility euicc and find the first entry that is in status "new".
- 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
- [] ->
- ok;
- Rows ->
- [HandleResource(Row) || Row <- Rows],
- ok
- end
- end,
-
- {atomic, Result} = mnesia:transaction(Trans),
- case Result of
- ok ->
- ok;
- _ ->
- logger:error("eUICC: euicc procedure failed, database error~n"),
- error
- end,
-
- % Next euicc procedure in 10 secs.
- {ok, _} = timer:apply_after(10000, mnesia_db, euicc_setparam, []),
- ok.
-
-% Dump all currently pending rest items (for debugging, to be called from console)
-dump_rest() ->
- Trans = fun() ->
- Q = qlc:q([X || X <- mnesia:table(rest)]),
- qlc:e(Q)
- end,
- {atomic, Result} = mnesia:transaction(Trans),
- Result.
-
-% Dump all currently pending work items (for debugging, to be called from console)
-dump_work() ->
- Trans = fun() ->
- Q = qlc:q([X || X <- mnesia:table(work)]),
- qlc:e(Q)
- end,
- {atomic, Result} = mnesia:transaction(Trans),
- Result.
-
-% Dump all eUICCs we are aware of
-dump_euicc() ->
- Trans = fun() ->
- Q = qlc:q([X || X <- mnesia:table(euicc)]),
- qlc:e(Q)
- end,
- {atomic, Result} = mnesia:transaction(Trans),
- Result.
diff --git a/src/mnesia_db_euicc.erl b/src/mnesia_db_euicc.erl
new file mode 100644
index 0000000..099b615
--- /dev/null
+++ b/src/mnesia_db_euicc.erl
@@ -0,0 +1,257 @@
+% Copyright (c) 2025 Onomondo ApS & sysmocom - s.f.m.c. GmbH. All rights reserved.
+%
+% SPDX-License-Identifier: AGPL-3.0-only
+%
+% Author: Philipp Maier <pmaier(a)sysmocom.de> / sysmocom - s.f.m.c. GmbH
+
+-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, param_get/2, param_set/3, create_if_not_exist/1]).
+
+% debugging
+-export([dump/0]).
+
+% trigger recurring events (called automatically by timer from this module)
+-export([timer_setparam/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">>
+ },
+ 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 master 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 master data entry,~neID=~p~n", [EidValue]),
+ ok;
+ present ->
+ ok;
+ _ ->
+ logger:error("eUICC: cannot create master 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.
+
+%update one specific parameter in the euicc table
+trans_update_param(EidValue, Name, Value) ->
+ 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});
+ _ ->
+ error
+ end;
+ [] ->
+ error;
+ _ ->
+ error
+ end.
+
+% Get an eUICC parameter by its name (atom)
+param_get(EidValue, Name) ->
+ Trans = fun() ->
+ Q = qlc:q([X || X <- mnesia:table(euicc), X#euicc.eidValue == EidValue]),
+ Rows = qlc:e(Q),
+ case Rows of
+ [Row | _] ->
+ case Name of
+ counterValue ->
+ {ok, Row#euicc.counterValue};
+ consumerEuicc ->
+ {ok, Row#euicc.consumerEuicc};
+ associationToken ->
+ {ok, Row#euicc.associationToken};
+ signPubKey ->
+ {ok, Row#euicc.signPubKey};
+ signAlgo ->
+ {ok, Row#euicc.signAlgo};
+ _ ->
+ error
+ end;
+ [] ->
+ error;
+ _ ->
+ error
+ end
+ end,
+
+ {atomic, Result} = mnesia:transaction(Trans),
+ case Result of
+ {ok, Value} ->
+ logger:info("eUICC: reading eUICC parameter,~neID=~p, name=~p, value=~p~n", [
+ EidValue, Name, Value
+ ]),
+ {ok, Value};
+ _ ->
+ logger:error("eUICC: cannot read eUICC parameter,~neID=~p, name=~p~n", [EidValue, Name]),
+ error
+ end.
+
+% Update an eUICC parameter by its name (atom)
+param_set(EidValue, Name, Value) ->
+ Trans = fun() ->
+ trans_update_param(EidValue, Name, Value)
+ end,
+ {atomic, Result} = mnesia:transaction(Trans),
+ case Result of
+ ok ->
+ logger:info("eUICC: writing eUICC parameter,~neID=~p, name=~p, value=~p~n", [
+ EidValue, Name, Value
+ ]),
+ ok;
+ _ ->
+ logger:error("eUICC: cannot write eUICC parameter,~neID=~p, name=~p~n", [EidValue, Name]),
+ error
+ end.
+
+% Run scheduled eUICC procedures
+timer_setparam() ->
+ % 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 parameters in the
+ % euicc table are set.
+
+ HandleParam = fun(ResourceId, EidValue, Param) ->
+ case Param of
+ {[{Name, Value}]} ->
+ case trans_update_param(EidValue, binary_to_atom(Name), Value) of
+ ok ->
+ mnesia_db_rest:trans_set_status(
+ ResourceId,
+ done,
+ [{[{euiccUpdateResult, ok}]}],
+ none
+ );
+ _ ->
+ mnesia_db_rest:trans_set_status(
+ ResourceId,
+ done,
+ [{[{euiccUpdateResult, badParam}]}],
+ none
+ )
+ end;
+ _ ->
+ mnesia_db_rest:trans_set_status(
+ ResourceId,
+ done,
+ [{[{euiccUpdateResult, badParamFormat}]}],
+ none
+ )
+ end
+ end,
+
+ % Parse order and process each parameter individually
+ HandleResource = fun({ResourceId, EidValue, Order}) ->
+ trans_create_if_not_exist(EidValue),
+ case Order of
+ {[{<<"euicc">>, ParameterList}]} ->
+ [HandleParam(ResourceId, EidValue, Param) || Param <- ParameterList],
+ ok;
+ _ ->
+ mnesia_db_rest:trans_set_status(ResourceId, done, [{[{procedureError, badOrder}]}], none)
+ end
+ end,
+
+ % Look into facility euicc and find the first entry that is in status "new".
+ 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
+ [] ->
+ ok;
+ Rows ->
+ [HandleResource(Row) || Row <- Rows],
+ ok
+ end
+ end,
+
+ {atomic, Result} = mnesia:transaction(Trans),
+ case Result of
+ ok ->
+ ok;
+ _ ->
+ logger:error("eUICC: euicc procedure failed, database error~n"),
+ error
+ end,
+
+ % Next euicc procedure in 10 secs.
+ {ok, _} = timer:apply_after(10000, mnesia_db_euicc, timer_setparam, []),
+ ok.
+
+% Dump all eUICCs 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.
diff --git a/src/mnesia_db_rest.erl b/src/mnesia_db_rest.erl
new file mode 100644
index 0000000..6a51567
--- /dev/null
+++ b/src/mnesia_db_rest.erl
@@ -0,0 +1,286 @@
+% Copyright (c) 2025 Onomondo ApS & sysmocom - s.f.m.c. GmbH. All rights reserved.
+%
+% SPDX-License-Identifier: AGPL-3.0-only
+%
+% Author: Philipp Maier <pmaier(a)sysmocom.de> / sysmocom - s.f.m.c. GmbH
+
+-module(mnesia_db_rest).
+-include_lib("stdlib/include/qlc.hrl").
+-include("mnesia_db_rest.hrl").
+-include("mnesia_db_work.hrl").
+
+% REST functions, to be called by the REST server (from outside via cowboy)
+-export([list/1, lookup/2, create/3, delete/2]).
+
+% debugging
+-export([dump/0]).
+
+% trigger recurring events (called automatically by timer from this module)
+-export([timer_cleanup/0]).
+
+% transaction functions (to be called from a transaction)
+-export([trans_set_status/4]).
+
+% helper function (to be called from a transaction) to set the status of an item in the rest table. This function
+% is intended to be called from mnesia_db_work to report back the status of a specific work item.
+trans_set_status(ResourceId, Status, Outcome, Debuginfo) ->
+ Q = qlc:q([X || X <- mnesia:table(rest), X#rest.resourceId == ResourceId]),
+ Rows = qlc:e(Q),
+ Timestamp = os:system_time(seconds),
+ case Rows of
+ [Row | []] ->
+ mnesia:write(Row#rest{
+ status = Status, timestamp = Timestamp, outcome = Outcome, debuginfo = Debuginfo
+ }),
+ ok;
+ [] ->
+ error;
+ _ ->
+ error
+ end.
+
+% Create REST resource (order)
+create(Facility, EidValue, Order) ->
+ ok = mnesia_db_euicc:create_if_not_exist(EidValue),
+ ResourceId = uuid:uuid_to_string(uuid:get_v4_urandom()),
+ Timestamp = os:system_time(seconds),
+ Row = #rest{
+ resourceId = ResourceId,
+ facility = Facility,
+ eidValue = EidValue,
+ order = Order,
+ status = new,
+ timestamp = Timestamp,
+ outcome = [],
+ debuginfo = none
+ },
+ Trans = fun() ->
+ Q = qlc:q([X#rest.resourceId || X <- mnesia:table(rest), X#rest.resourceId == ResourceId]),
+ Present = qlc:e(Q),
+ case Present of
+ [] ->
+ mnesia:write(Row);
+ _ ->
+ error
+ end
+ end,
+ {atomic, Result} = mnesia:transaction(Trans),
+ {Result, ResourceId}.
+
+% Lookup REST resource (order)
+lookup(ResourceId, Facility) ->
+ Trans = fun() ->
+ Q = qlc:q([
+ {
+ X#rest.status,
+ X#rest.timestamp,
+ X#rest.eidValue,
+ X#rest.order,
+ X#rest.outcome,
+ X#rest.debuginfo
+ }
+ || X <- mnesia:table(rest),
+ X#rest.resourceId == ResourceId,
+ X#rest.facility == Facility
+ ]),
+ qlc:e(Q)
+ end,
+ {atomic, Result} = mnesia:transaction(Trans),
+ case Result of
+ [{Status, Timestamp, EidValue, Order, Outcome, Debuginfo}] ->
+ {Status, Timestamp, EidValue, Order, Outcome, Debuginfo};
+ [] ->
+ none;
+ _ ->
+ error
+ end.
+
+% Delete REST resource (order)
+delete(ResourceId, Facility) ->
+ Trans = fun() ->
+ QRest = qlc:q([
+ X#rest.resourceId
+ || X <- mnesia:table(rest),
+ X#rest.resourceId == ResourceId,
+ X#rest.facility == Facility
+ ]),
+ RestPresent = qlc:e(QRest),
+ case RestPresent of
+ [] ->
+ none;
+ _ ->
+ OidRest = {rest, ResourceId},
+ ok = mnesia:delete(OidRest),
+
+ % There may be an orphaned work item now, which we must also remove. This will also kill
+ % the order in case it is currently in progress.
+ QWork = qlc:q([
+ X#work.pid
+ || X <- mnesia:table(work), X#work.resourceId == ResourceId
+ ]),
+ WorkPresent = qlc:e(QWork),
+ case WorkPresent of
+ [] ->
+ ok;
+ [Pid] ->
+ OidWork = {work, Pid},
+ mnesia:delete(OidWork);
+ _ ->
+ error
+ end
+ end
+ end,
+ {atomic, Result} = mnesia:transaction(Trans),
+ Result.
+
+% List the resource identifiers of currently present REST resources
+list(Facility) ->
+ Trans = fun() ->
+ Q = qlc:q([X#rest.resourceId || X <- mnesia:table(rest), X#rest.facility == Facility]),
+ qlc:e(Q)
+ end,
+ {atomic, Result} = mnesia:transaction(Trans),
+ Result.
+
+mark_stuck(Timeout) ->
+ % There may be corner cases where the order gets stuck because the other entity suddenly stops responding. In
+ % this case the order will stall. When it remains stalled for too long (minutes), we should remove all related
+ % items from the work table and put an appropriate outcome (error code) into the rest table.
+
+ TimestampNow = os:system_time(seconds),
+
+ % Remove Resource from work table and set an appropriate status in the rest table
+ HandleResource = fun(ResourceId) ->
+ % find the pid of the work item that is stuck and then delete it
+ Q = qlc:q([X#work.pid || X <- mnesia:table(work), X#work.resourceId == ResourceId]),
+ WorkPresent = qlc:e(Q),
+ case WorkPresent of
+ [Pid] ->
+ Oid = {work, Pid},
+ ok = mnesia:delete(Oid);
+ _ ->
+ ok
+ end,
+
+ % set status in the rest table
+ trans_set_status(ResourceId, done, [{[{procedureError, stuckOrder}]}], none)
+ end,
+
+ % Find all rest resources that stall in status "work" and older than the specified timeout value
+ Trans = fun() ->
+ Q = qlc:q([
+ X#rest.resourceId
+ || X <- mnesia:table(rest),
+ X#rest.status == work,
+ TimestampNow - X#rest.timestamp > Timeout
+ ]),
+ Rows = qlc:e(Q),
+ case Rows of
+ [] ->
+ ok;
+ Rows ->
+ [HandleResource(Row) || Row <- Rows],
+ ok
+ end
+ end,
+
+ {atomic, Result} = mnesia:transaction(Trans),
+ Result.
+
+mark_noshow(Timeout) ->
+ % There may be corner cases where the order is never processed because the related IPAd/eUICC never shows up to
+ % fetch the related eUICC package. If we see an order staying in status "new" for too long (days, weeks), we should
+ % mark it as "done" and put an appropriate outcome (error code) into the rest table.
+
+ TimestampNow = os:system_time(seconds),
+
+ % Remove Resource from work table and set an appropriate status in the rest table
+ HandleResource = fun(ResourceId) ->
+ trans_set_status(ResourceId, done, [{[{procedureError, noshowOrder}]}], none)
+ end,
+
+ % Find all rest resources that stall in status "work" and older than the specified timeout value
+ Trans = fun() ->
+ Q = qlc:q([
+ X#rest.resourceId
+ || X <- mnesia:table(rest),
+ X#rest.status == new,
+ TimestampNow - X#rest.timestamp > Timeout
+ ]),
+ Rows = qlc:e(Q),
+ case Rows of
+ [] ->
+ ok;
+ Rows ->
+ [HandleResource(Row) || Row <- Rows],
+ ok
+ end
+ end,
+
+ {atomic, Result} = mnesia:transaction(Trans),
+ Result.
+
+delete_expired(Timeout) ->
+ % There may be cases where orders stay unmaintained for too long. When an order stays in status "done" for too long
+ % (hours, days), than this may mean that the REST API user lost interest. In this case the related items shoud be
+ % removed from the rest table after a reasonable timeout.
+
+ TimestampNow = os:system_time(seconds),
+
+ % Remove Resource from the rest table. Since an abandonned order won't have a coresponding work item in the work
+ % table we do not have to worry about creating an orphaned work item.
+ HandleResource = fun(ResourceId) ->
+ Oid = {rest, ResourceId},
+ ok = mnesia:delete(Oid)
+ end,
+
+ % Find all rest resources that linger in the rest table for a long time and are not in the status "new"
+ Trans = fun() ->
+ Q = qlc:q([
+ X#rest.resourceId
+ || X <- mnesia:table(rest),
+ X#rest.status == done,
+ TimestampNow - X#rest.timestamp > Timeout
+ ]),
+ Rows = qlc:e(Q),
+ case Rows of
+ [] ->
+ ok;
+ Rows ->
+ [HandleResource(Row) || Row <- Rows],
+ ok
+ end
+ end,
+
+ {atomic, Result} = mnesia:transaction(Trans),
+ Result.
+
+% Run a cleanup cycle the database
+timer_cleanup() ->
+ {ok, RestTimeoutStuck} = application:get_env(onomondo_eim, rest_timeout_stuck),
+ {ok, RestTimeoutNoshow} = application:get_env(onomondo_eim, rest_timeout_noshow),
+ {ok, RestTimeoutExpired} = application:get_env(onomondo_eim, rest_timeout_expired),
+ RcStalled = mark_stuck(RestTimeoutStuck),
+ RcNoshow = mark_noshow(RestTimeoutNoshow),
+ RcExpired = delete_expired(RestTimeoutExpired),
+
+ case {RcStalled, RcNoshow, RcExpired} of
+ {ok, ok, ok} ->
+ ok;
+ _ ->
+ logger:error("Cleanup: database error~n"),
+ error
+ end,
+
+ % Next cleanup in 10 secs.
+ {ok, _} = timer:apply_after(10000, mnesia_db_rest, timer_cleanup, []),
+ ok.
+
+% Dump all currently pending rest items (for debugging, to be called from console)
+dump() ->
+ Trans = fun() ->
+ Q = qlc:q([X || X <- mnesia:table(rest)]),
+ qlc:e(Q)
+ end,
+ {atomic, Result} = mnesia:transaction(Trans),
+ Result.
diff --git a/src/mnesia_db_work.erl b/src/mnesia_db_work.erl
new file mode 100644
index 0000000..9a33ebd
--- /dev/null
+++ b/src/mnesia_db_work.erl
@@ -0,0 +1,273 @@
+% Copyright (c) 2025 Onomondo ApS & sysmocom - s.f.m.c. GmbH. All rights reserved.
+%
+% SPDX-License-Identifier: AGPL-3.0-only
+%
+% Author: Philipp Maier <pmaier(a)sysmocom.de> / sysmocom - s.f.m.c. GmbH
+
+-module(mnesia_db_work).
+-include_lib("stdlib/include/qlc.hrl").
+-include("mnesia_db_rest.hrl").
+-include("mnesia_db_work.hrl").
+
+% work functions, to be called by the eIM code (from inside)
+-export([fetch/2, pickup/2, update/2, bind/2, finish/3]).
+
+% debugging
+-export([dump/0]).
+
+% Start working on an order by creating a an entry in the work table and marking it's status as "work". After calling
+% this it is the callers responsibility to handle the work item and call rest_finish_order when the work is done.
+fetch(EidValue, Pid) ->
+ % One process can only work on one work item at a time. The API user must call finish before the next work
+ % item can be processed. If there is already a pending work item under the given PID, forcefully finish this work
+ % item.
+ TransPidExists = fun() ->
+ Q = qlc:q([X || X <- mnesia:table(work), X#work.pid == Pid]),
+ WorkPresent = qlc:e(Q),
+ case WorkPresent of
+ [] ->
+ false;
+ _ ->
+ true
+ end
+ end,
+
+ {atomic, PidExists} = mnesia:transaction(TransPidExists),
+ case PidExists of
+ true ->
+ finish(Pid, [{[{procedureError, stuckOrder}]}], none);
+ false ->
+ ok
+ end,
+
+ % Read the next pending REST resource from the rest table and create a related work item. The work item is then
+ % in progress.
+ Trans = fun() ->
+ Q = qlc:q([
+ X
+ || X <- mnesia:table(rest),
+ X#rest.eidValue == EidValue,
+ X#rest.status == new,
+ X#rest.facility =/= euicc
+ ]),
+ Rows = qlc:e(Q),
+ case Rows of
+ [Row | _] ->
+ % Create an entry in the work table
+ WorkRow = #work{
+ pid = Pid,
+ resourceId = Row#rest.resourceId,
+ transactionId = none,
+ eidValue = Row#rest.eidValue,
+ order = Row#rest.order,
+ state = none
+ },
+ case mnesia:write(WorkRow) of
+ ok ->
+ % We are now working on this order
+ ok = mnesia_db_rest:trans_set_status(Row#rest.resourceId, work, [], none),
+ Row;
+ _ ->
+ error
+ end;
+ [] ->
+ none;
+ _ ->
+ error
+ end
+ end,
+
+ {atomic, Result} = mnesia:transaction(Trans),
+ case Result of
+ {rest, _, Facility, _, Order, _, _, _, _} ->
+ logger:info(
+ "Work: fetching new work item,~nEidValue=~p, Pid=~p, Facility=~p,~nOrder=~p~n",
+ [EidValue, Pid, Facility, Order]
+ ),
+ {Facility, Order};
+ none ->
+ logger:info("Work: no work item in database,~nEidValue=~p, Pid=~p~n", [EidValue, Pid]),
+ none;
+ _ ->
+ logger:error("Work: cannot fetch work item, database error,~nEidValue=~p, Pid=~p~n", [
+ EidValue, Pid
+ ]),
+ error
+ end.
+
+% Bind a work item to a TransactionId. The transactionId has to be a unique identifier that can be used as a secondary
+% key to find a work item in the databse. The binding works in two directions. The work item is first searched by its
+% pid, when found, the transactionId is updated. In case the pid has become invalid, then the work item is searched
+% again by the transactionId and when found, the pid is updated. This function can be called any time after fetch
+% was called before. It can also be called multiple times.
+bind(Pid, TransactionId) ->
+ % Transaction to update the TransactionId. This is the normal case. A work item starts without having a
+ % TransactionId assigned. As soon as a (new) TransactionId becomes known, it is updated using this Transaction.
+ TransUpdateTrnsId = fun() ->
+ Q = qlc:q([X || X <- mnesia:table(work), X#work.pid == Pid]),
+ Rows = qlc:e(Q),
+ case Rows of
+ [Row | _] ->
+ mnesia:write(Row#work{transactionId = TransactionId});
+ [] ->
+ none;
+ _ ->
+ error
+ end
+ end,
+
+ % Transaction to update the PID. This is a corner case that comes into play in case the PID is lost (the
+ % process/connection handling this work item has died). We then try to find the work item by the TransactionId
+ % and update its PID.
+ TransUpdatePid = fun() ->
+ Q = qlc:q([X || X <- mnesia:table(work), X#work.transactionId == TransactionId]),
+ Rows = qlc:e(Q),
+ case Rows of
+ [Row | _] ->
+ mnesia:write(Row#work{pid = Pid});
+ [] ->
+ none;
+ _ ->
+ error
+ end
+ end,
+
+ {atomic, Result} = mnesia:transaction(TransUpdateTrnsId),
+ case Result of
+ ok ->
+ logger:info("Work: bound work item to transactionId,~nPid=~p, TransactionId=~p~n", [
+ Pid, TransactionId
+ ]),
+ ok;
+ none ->
+ {atomic, UpdatePidResult} = mnesia:transaction(TransUpdatePid),
+ case UpdatePidResult of
+ ok ->
+ logger:info("Work: bound work item to PID,~nPid=~p, TransactionId=~p~n", [
+ Pid, TransactionId
+ ]),
+ ok;
+ none ->
+ logger:error(
+ "Work: cannot bind work item, transactionId nor PID found,~nPid=~p, TransactionId=~p~n",
+ [Pid, TransactionId]
+ ),
+ error;
+ _ ->
+ logger:error(
+ "Work: cannot bind work item, database error,~nPid=~p, TransactionId=~p, TransUpdatePid~n",
+ [Pid, TransactionId]
+ ),
+ error
+ end;
+ _ ->
+ logger:error(
+ "Work: cannot bind work item, database error,~nPid=~p, TransactionId=~p, TransUpdateTrnsId~n",
+ [Pid, TransactionId]
+ ),
+ error
+ end.
+
+% Pickup a work item that is in progress. This function can be called any time after fetch was called
+% before. It can also be called multiple times.
+pickup(Pid, TransactionId) ->
+ % In case a TransactionId is provied, bind the PID to this TransactionId,
+ WorkBound =
+ case TransactionId of
+ none ->
+ ok;
+ _ ->
+ bind(Pid, TransactionId)
+ end,
+
+ % Lookup the work state by the given PID
+ case WorkBound of
+ ok ->
+ Trans = fun() ->
+ Q = qlc:q([
+ {X#work.eidValue, X#work.order, X#work.state}
+ || X <- mnesia:table(work), X#work.pid == Pid
+ ]),
+ qlc:e(Q)
+ end,
+ {atomic, Result} = mnesia:transaction(Trans),
+ case Result of
+ [{EidValue, Order, State} | _] ->
+ {EidValue, Order, State};
+ [] ->
+ logger:error(
+ "Work: no work item found under specified Pid, already finished?, not fetched?,~nPid=~p~n",
+ [Pid]
+ ),
+ none;
+ _ ->
+ logger:error("Work: cannot pick up work item, database error,~nPid=~p~n", [Pid]),
+ error
+ end;
+ _ ->
+ WorkBound
+ end.
+
+% Update a work item that is in progress. This fuction updates the state (any user defined term) of the work item.
+% This function can be called any time after fetch was called before. It can also be called multiple times.
+update(Pid, State) ->
+ Trans = fun() ->
+ Q = qlc:q([X || X <- mnesia:table(work), X#work.pid == Pid]),
+ Rows = qlc:e(Q),
+ case Rows of
+ [Row | _] ->
+ mnesia:write(Row#work{state = State});
+ [] ->
+ error;
+ _ ->
+ error
+ end
+ end,
+
+ {atomic, Result} = mnesia:transaction(Trans),
+ case Result of
+ ok ->
+ logger:info("Work: updating work item,~nPid=~p, State=~p~n", [Pid, State]),
+ ok;
+ _ ->
+ logger:error("Work: cannot update, database error,~nPid=~p, State=~p~n", [Pid, State]),
+ error
+ end.
+
+% Finish an order that has been worked on. This removes the related entry from the work table and sets the status in
+% the rest table to "done".
+finish(Pid, Outcome, Debuginfo) ->
+ Trans = fun() ->
+ Q = qlc:q([X#work.resourceId || X <- mnesia:table(work), X#work.pid == Pid]),
+ Rows = qlc:e(Q),
+ case Rows of
+ [] ->
+ error;
+ [ResourceId | _] ->
+ Oid = {work, Pid},
+ ok = mnesia:delete(Oid),
+ ok = mnesia_db_rest:trans_set_status(ResourceId, done, Outcome, Debuginfo)
+ end
+ end,
+
+ {atomic, Result} = mnesia:transaction(Trans),
+ case Result of
+ ok ->
+ logger:info("Work: finishing work item,~nPid=~p, Outcome=~p~n", [Pid, Outcome]),
+ ok;
+ _ ->
+ logger:error(
+ "Work: cannot finish work item, database error,~nPid=~p, Outcome=~p~n",
+ [Pid, Outcome]
+ ),
+ error
+ end.
+
+% Dump all currently pending work items (for debugging, to be called from console)
+dump() ->
+ Trans = fun() ->
+ Q = qlc:q([X || X <- mnesia:table(work)]),
+ qlc:e(Q)
+ end,
+ {atomic, Result} = mnesia:transaction(Trans),
+ Result.
diff --git a/src/rest_handler.erl b/src/rest_handler.erl
index db3f048..9f5ab4d 100644
--- a/src/rest_handler.erl
+++ b/src/rest_handler.erl
@@ -69,7 +69,7 @@
ContentDecoded = jiffy:decode(Content),
{[{<<"eidValue">>, EidValue}, _]} = ContentDecoded,
{[_, {<<"order">>, Order}]} = ContentDecoded,
- {ok, ResourceId} = mnesia_db:rest_create(Facility, EidValue, Order),
+ {ok, ResourceId} = mnesia_db_rest:create(Facility, EidValue, Order),
case cowboy_req:method(Req1) of
<<"POST">> ->
Response = io_lib:format("/~s/lookup/~s", [Facility, ResourceId]),
@@ -89,7 +89,7 @@
"REST: client requests REST resource for lookup,~nPeer=~p, ResourceId=~p, Facility=~p~n",
[maps:get(peer, Req), ResourceId, Facility]
),
- Result = mnesia_db:rest_lookup(ResourceId, Facility),
+ Result = mnesia_db_rest:lookup(ResourceId, Facility),
Response =
case Result of
{Status, Timestamp, EidValue, Order, Outcome, Debuginfo} ->
@@ -124,7 +124,7 @@
"REST: client requests REST resource for delete,~nPeer=~p, ResourceId=~p, Facility=~p~n",
[maps:get(peer, Req), ResourceId, Facility]
),
- Result = mnesia_db:rest_delete(ResourceId, Facility),
+ Result = mnesia_db_rest:delete(ResourceId, Facility),
Response =
case Result of
ok ->
@@ -147,7 +147,7 @@
"REST: client requests REST resource list,~nPeer=~p, Facility=~p~n",
[maps:get(peer, Req), Facility]
),
- Result = mnesia_db:rest_list(Facility),
+ Result = mnesia_db_rest:list(Facility),
ResourceIdList = [list_to_binary(X) || X <- Result],
Response = io_lib:format(
"{\"resourceIdList\": ~s}",
--
To view, visit https://gerrit.osmocom.org/c/onomondo-eim/+/42946?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: onomondo-eim
Gerrit-Branch: master
Gerrit-Change-Id: Ifc649337ab7ce7eb9dc602a1439293130f002f41
Gerrit-Change-Number: 42946
Gerrit-PatchSet: 1
Gerrit-Owner: dexter <pmaier(a)sysmocom.de>
Attention is currently required from: fixeria, laforge.
Hello Jenkins Builder, fixeria, laforge,
I'd like you to reexamine a change. Please visit
https://gerrit.osmocom.org/c/onomondo-eim/+/42874?usp=email
to look at the new patch set (#4).
The following approvals got outdated and were removed:
Verified+1 by Jenkins Builder
Change subject: es9p_client: convert InitiateAuthenticationOkEsipa to match SGP.32 V.1.2
......................................................................
es9p_client: convert InitiateAuthenticationOkEsipa to match SGP.32 V.1.2
In SGP.32 V.1.0.1, InitiateAuthenticationOkEsipa is almost identical to
SGP.22 InitiateAuthenticationOkEs9. However, in SGP.32 V.1.2, the member
euiccCiPKIdToBeUsed is renamed to euiccCiPKIdentifierToBeUsed. This means
it is no longer possible to pass InitiateAuthenticationOkEsipa as
InitiateAuthenticationOkEs9 to ES9+ directly. Instead we have to re-build
the the InitiateAuthenticationOkEs9 from the contents of
InitiateAuthenticationOkEsipa.
Change-Id: I44c60a2f0d1129093ea67908d2eed167643a4a87
Related: SYS#8100
---
M src/esipa_asn1_handler.erl
1 file changed, 8 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/onomondo-eim refs/changes/74/42874/4
--
To view, visit https://gerrit.osmocom.org/c/onomondo-eim/+/42874?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newpatchset
Gerrit-Project: onomondo-eim
Gerrit-Branch: master
Gerrit-Change-Id: I44c60a2f0d1129093ea67908d2eed167643a4a87
Gerrit-Change-Number: 42874
Gerrit-PatchSet: 4
Gerrit-Owner: dexter <pmaier(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Attention: laforge <laforge(a)osmocom.org>
Gerrit-Attention: fixeria <vyanitskiy(a)sysmocom.de>
Attention is currently required from: fixeria, laforge.
dexter has posted comments on this change by dexter. ( https://gerrit.osmocom.org/c/onomondo-eim/+/42874?usp=email )
Change subject: es9p_client: convert InitiateAuthenticationOkEsipa to match SGP.32 V.1.2
......................................................................
Patch Set 4:
(1 comment)
Patchset:
PS3:
> I somehow fail to see how this patch relates to a rename? It is more like an additional structure ne […]
In SGP.32 V.1.0.1, the InitiateAuthenticationOkEsipa was almost identical to the InitiateAuthenticationOkEs9, that is why the direct conversion worked. Now we have to assemble the struct because the euiccCiPKIdToBeUsed now has a different name. That is why the patch looks a bit off. I'll add an explanation in the commit message...
--
To view, visit https://gerrit.osmocom.org/c/onomondo-eim/+/42874?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: onomondo-eim
Gerrit-Branch: master
Gerrit-Change-Id: I44c60a2f0d1129093ea67908d2eed167643a4a87
Gerrit-Change-Number: 42874
Gerrit-PatchSet: 4
Gerrit-Owner: dexter <pmaier(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Attention: laforge <laforge(a)osmocom.org>
Gerrit-Attention: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Comment-Date: Wed, 08 Jul 2026 10:08:57 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: laforge <laforge(a)osmocom.org>
Attention is currently required from: fixeria, laforge.
dexter has posted comments on this change by dexter. ( https://gerrit.osmocom.org/c/onomondo-eim/+/42878?usp=email )
Change subject: esipa_rest_utils: re-align to SGP.32 Section 2.11.2
......................................................................
Patch Set 5:
(1 comment)
Patchset:
PS1:
> @pmaier@sysmocom. […]
I think at the moment we do not have any sort of schema version or REST API version info in general. I have put this on the checklist in the ticket. In any case, I think we should add this later when all patches that touch the REST API made it into master.
--
To view, visit https://gerrit.osmocom.org/c/onomondo-eim/+/42878?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: onomondo-eim
Gerrit-Branch: master
Gerrit-Change-Id: Ib4e6a4b4cb4d26a391346169b9047515496dd308
Gerrit-Change-Number: 42878
Gerrit-PatchSet: 5
Gerrit-Owner: dexter <pmaier(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Attention: laforge <laforge(a)osmocom.org>
Gerrit-Attention: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Comment-Date: Wed, 08 Jul 2026 10:08:57 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: laforge <laforge(a)osmocom.org>
Comment-In-Reply-To: fixeria <vyanitskiy(a)sysmocom.de>