Rework the CONNECTING state to dynamically select an MME from the pool via mme_registry:mme_select/1, passing the eNB's Tracking Area Codes (from the ?'id-SupportedTAs' IE of the S1 SETUP REQUEST) and a list of already-tried MMEs, so successive attempts pick a different candidate.
On connection failure (SCTP establishment timeout or error), or when the selected MME rejects the S1 SETUP REQUEST or fails to respond in time, the FSM re-enters the CONNECTING state rather than terminating. This triggers another mme_select/1 call with the failed MME added to the tried_mmes list. S1 SETUP FAILURE PDUs from the MME are intentionally not forwarded to the eNB, so the retry is fully transparent.
Once mme_select/1 exhausts all candidates it returns 'error'; at that point the FSM builds and sends an S1 SETUP FAILURE PDU to the eNB and terminates.
enb_registry: rework handling of eNB property updates
The idea of an enb_event/0 containing the current MME connection state and the associated information (Global-eNB-ID, eNB/MME conn info) looked promising initially, however turned out to be impractical in light of ongoing MME pooling related changes.
* remove type enb_state/0 * rename type enb_event/0 -> enb_prop/0 * rename function enb_event/2 -> enb_update/2 (now private) * enb_prop/0: separate the state from other properties * enb_update/2: accept a list of enb_prop/0 - enb_proplist/0 * add typed notify_*() helpers that group related property updates, ensuring consistency and serving as the sole public call sites * notify_mme_connecting(): explicitly clears mme_conn_info * openapi: EnbItem.state reflects the actual enb_proxy FSM state
enb_registry: rework handling of eNB property updates
The idea of an enb_event/0 containing the current MME connection state and the associated information (Global-eNB-ID, eNB/MME conn info) looked promising initially, however turned out to be impractical in light of ongoing MME pooling related changes.
* remove type enb_state/0 * rename type enb_event/0 -> enb_prop/0 * rename function enb_event/2 -> enb_update/2 (now private) * enb_prop/0: separate the state from other properties * enb_update/2: accept a list of enb_prop/0 - enb_proplist/0 * add typed notify_*() helpers that group related property updates, ensuring consistency and serving as the sole public call sites * notify_mme_connecting(): explicitly clears mme_conn_info * openapi: EnbItem.state reflects the actual enb_proxy FSM state
enb_registry: fix duplicate registration check in enb_register/0
next_handle was bound in the function head and reused in the case pattern {ok, Handle}, turning what looks like a binding into an equality test against next_handle. The duplicate check therefore only fired if the previously assigned handle happened to equal the current next_handle counter, which is essentially never true.
Fix by removing next_handle from the function head pattern and reading it with S#state.next_handle inside the error branch, so that Handle in {ok, Handle} is always a fresh binding that matches any existing handle.
In Erlang a function body returns only its last expression. The comma between the two enb_filter_by_sub_field/2 calls caused the result of the addr check to be silently discarded, so the filter only tested the port. Replace the comma with andalso to require both to match.
s1ap_proxy: fix discarded result of handle_ies/3 in HO REQ ACK handler
The call processing the optional E-RABFailedToSetupListHOReqAck IE did not pattern-match its return value, causing any errors returned by handle_ies/3 to be silently swallowed. Bind the result to {_, S2} to make the intent explicit.
The watchdog process is spawned bare (no link/monitor), so if it crashes its failure goes unnoticed, and if pfcp_peer restarts the orphaned watchdog may fire a stale cast at the new process.
The watchdog process exists solely to send a delayed message to pfcp_peer on timeout, and to be cancelled (by receiving heartbeat_response) when the response arrives. That's exactly what erlang:start_timer/3 does natively - no separate process needed.
enb_registry: fix pattern match in handle_info 'DOWN' handler
PIDs maps pid() => enb_handle() (an integer), so maps:find/2 returns {ok, SomeHandle}. The old pattern {ok, Pid} tried to match an integer against the already-bound pid() variable, which never succeeds.
This handler is only called on abnormal termination (process crash), so the broken match caused the registry entry to never be cleaned up in that case. Fix by using a fresh {ok, Handle} binding and removing the now-redundant follow-up maps:get/2 call.
Two comments copy-pasted from session_establish were left saying "ESTABLISH Req" in session_modify and session_delete handlers where "MODIFY Req" and "DELETE Req" are correct respectively. Also fix typo "unieue" -> "unique" in init/1.
PFCP sequence numbers are 24-bit (3GPP TS 29.244 section 7.2.2.2), but the counter was incremented without wrapping, eventually exceeding the type spec after 16,777,215 requests. Introduce PFCP_SEQ_NR_MAX and use it in the type spec and in the increment expression.
pfcp_peer: replace deprecated erlang:timestamp/0 with os:system_time/1
erlang:timestamp/0 is deprecated since OTP 18. os:system_time(second) is the modern replacement and yields the result directly in seconds, removing the need to reassemble the {MegaSec, Sec, _} tuple.