550 lines
16 KiB
C
550 lines
16 KiB
C
/*
|
|
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
|
* All Rights Reserved.
|
|
*
|
|
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include <errno.h>
|
|
|
|
#include <osmocom/core/fsm.h>
|
|
#include <osmocom/core/logging.h>
|
|
#include <osmocom/core/tdef.h>
|
|
#include <osmocom/core/utils.h>
|
|
|
|
#include <osmocom/pfcp/pfcp_msg.h>
|
|
#include <osmocom/pfcp/pfcp_endpoint.h>
|
|
|
|
#include <osmocom/upf/upf.h>
|
|
#include <osmocom/upf/up_peer.h>
|
|
#include <osmocom/upf/up_endpoint.h>
|
|
#include <osmocom/upf/up_session.h>
|
|
|
|
enum up_peer_fsm_state {
|
|
UP_PEER_ST_NOT_ASSOCIATED,
|
|
UP_PEER_ST_ASSOCIATED,
|
|
UP_PEER_ST_GRACEFUL_RELEASE,
|
|
UP_PEER_ST_WAIT_USE_COUNT,
|
|
};
|
|
|
|
static const struct value_string up_peer_fsm_event_names[] = {
|
|
OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_SETUP_REQ),
|
|
OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_UPD_REQ),
|
|
OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_REL_REQ),
|
|
OSMO_VALUE_STRING(UP_PEER_EV_RX_SESSION_EST_REQ),
|
|
OSMO_VALUE_STRING(UP_PEER_EV_HEARTBEAT_FAILURE),
|
|
OSMO_VALUE_STRING(UP_PEER_EV_USE_COUNT_ZERO),
|
|
OSMO_VALUE_STRING(UP_PEER_EV_SESSION_TERM),
|
|
{}
|
|
};
|
|
|
|
static struct osmo_fsm up_peer_fsm;
|
|
|
|
static const struct osmo_tdef_state_timeout up_peer_fsm_timeouts[32] = {
|
|
[UP_PEER_ST_GRACEFUL_RELEASE] = { .T = -21 },
|
|
};
|
|
|
|
/* Transition to a state, using the T timer defined in up_peer_fsm_timeouts.
|
|
* Assumes local variable fi exists. */
|
|
#define up_peer_fsm_state_chg(state) \
|
|
osmo_tdef_fsm_inst_state_chg(fi, state, \
|
|
up_peer_fsm_timeouts, \
|
|
osmo_pfcp_tdefs, \
|
|
5)
|
|
|
|
static int up_peer_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line)
|
|
{
|
|
struct up_peer *peer = e->use_count->talloc_object;
|
|
int32_t total;
|
|
int level;
|
|
|
|
if (!e->use)
|
|
return -EINVAL;
|
|
|
|
total = osmo_use_count_total(&peer->use_count);
|
|
|
|
if (total == 0
|
|
|| (total == 1 && old_use_count == 0 && e->count == 1))
|
|
level = LOGL_INFO;
|
|
else
|
|
level = LOGL_DEBUG;
|
|
|
|
LOGPFSMSLSRC(peer->fi, DREF, level, file, line,
|
|
"%s %s: now used by %s\n",
|
|
(e->count - old_use_count) > 0 ? "+" : "-", e->use,
|
|
osmo_use_count_to_str_c(OTC_SELECT, &peer->use_count));
|
|
|
|
if (e->count < 0)
|
|
return -ERANGE;
|
|
|
|
if (total == 0)
|
|
osmo_fsm_inst_dispatch(peer->fi, UP_PEER_EV_USE_COUNT_ZERO, NULL);
|
|
return 0;
|
|
}
|
|
|
|
char *up_peer_remote_addr_str(struct up_peer *peer)
|
|
{
|
|
struct osmo_sockaddr remote_addr = peer->remote_addr;
|
|
|
|
/* Zero the port, it is not interesting information. The port for PFCP is defined fixed, and there is no use
|
|
* printing it in the logs */
|
|
osmo_sockaddr_set_port(&remote_addr.u.sa, 0);
|
|
|
|
return osmo_sockaddr_to_str_c(OTC_SELECT, &remote_addr);
|
|
}
|
|
|
|
static void up_peer_update_id(struct up_peer *peer)
|
|
{
|
|
osmo_fsm_inst_update_id_f_sanitize(peer->fi, '-', "%s", up_peer_remote_addr_str(peer));
|
|
LOGPFSML(peer->fi, LOGL_DEBUG, "Updated id\n");
|
|
}
|
|
|
|
static struct up_peer *up_peer_add(struct up_endpoint *up_endpoint, const struct osmo_sockaddr *remote_addr)
|
|
{
|
|
struct up_peer *peer;
|
|
|
|
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&up_peer_fsm, up_endpoint, NULL, LOGL_DEBUG, NULL);
|
|
OSMO_ASSERT(fi);
|
|
|
|
peer = talloc(fi, struct up_peer);
|
|
OSMO_ASSERT(peer);
|
|
fi->priv = peer;
|
|
|
|
*peer = (struct up_peer) {
|
|
.fi = fi,
|
|
.up_endpoint = up_endpoint,
|
|
.remote_addr = *remote_addr,
|
|
.heartbeat_fi = NULL /* FIXME */,
|
|
.use_count = {
|
|
.talloc_object = peer,
|
|
.use_cb = up_peer_use_cb,
|
|
},
|
|
};
|
|
osmo_use_count_make_static_entries(&peer->use_count, peer->use_count_buf, ARRAY_SIZE(peer->use_count_buf));
|
|
hash_init(peer->sessions_by_up_seid);
|
|
hash_init(peer->sessions_by_cp_seid);
|
|
|
|
osmo_pfcp_bits_set(peer->local_up_features.bits, OSMO_PFCP_UP_FEAT_BUNDL, true);
|
|
osmo_pfcp_bits_set(peer->local_up_features.bits, OSMO_PFCP_UP_FEAT_RTTL, true);
|
|
osmo_pfcp_bits_set(peer->local_up_features.bits, OSMO_PFCP_UP_FEAT_FTUP, true);
|
|
|
|
up_peer_update_id(peer);
|
|
|
|
llist_add(&peer->entry, &up_endpoint->peers);
|
|
return peer;
|
|
}
|
|
|
|
struct up_peer *up_peer_find(struct up_endpoint *up_endpoint, const struct osmo_sockaddr *remote_addr)
|
|
{
|
|
struct up_peer *peer;
|
|
llist_for_each_entry(peer, &up_endpoint->peers, entry) {
|
|
if (osmo_sockaddr_cmp(&peer->remote_addr, remote_addr))
|
|
continue;
|
|
return peer;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct up_peer *up_peer_find_or_add(struct up_endpoint *up_endpoint, const struct osmo_sockaddr *remote_addr)
|
|
{
|
|
struct up_peer *peer = up_peer_find(up_endpoint, remote_addr);
|
|
if (peer)
|
|
return peer;
|
|
return up_peer_add(up_endpoint, remote_addr);
|
|
}
|
|
|
|
int up_peer_tx(struct up_peer *peer, struct osmo_pfcp_msg *m)
|
|
{
|
|
return osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, m);
|
|
}
|
|
|
|
static int up_peer_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
|
{
|
|
//struct up_peer *peer = fi->priv;
|
|
/* Return 1 to terminate FSM instance, 0 to keep running */
|
|
return 1;
|
|
}
|
|
|
|
void up_peer_set_msg_ctx(struct up_peer *peer, struct osmo_pfcp_msg *m)
|
|
{
|
|
OSMO_ASSERT(!m->ctx.peer_fi);
|
|
|
|
m->ctx.peer_fi = peer->fi;
|
|
m->ctx.peer_use_count = &peer->use_count;
|
|
m->ctx.peer_use_token = (m->rx ? UP_USE_MSG_RX : UP_USE_MSG_TX);
|
|
OSMO_ASSERT(osmo_use_count_get_put(m->ctx.peer_use_count, m->ctx.peer_use_token, 1) == 0);
|
|
}
|
|
|
|
struct osmo_pfcp_msg *up_peer_init_tx(struct up_peer *peer, struct osmo_pfcp_msg *in_reply_to,
|
|
enum osmo_pfcp_message_type message_type)
|
|
{
|
|
struct osmo_pfcp_msg *tx;
|
|
if (in_reply_to)
|
|
tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, in_reply_to, message_type);
|
|
else
|
|
tx = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, message_type);
|
|
up_peer_set_msg_ctx(peer, tx);
|
|
return tx;
|
|
}
|
|
|
|
static int up_peer_tx_assoc_setup_resp(struct up_peer *peer, struct osmo_pfcp_msg *m, enum osmo_pfcp_cause cause)
|
|
{
|
|
struct osmo_pfcp_msg *resp;
|
|
|
|
resp = up_peer_init_tx(peer, m, OSMO_PFCP_MSGT_ASSOC_SETUP_RESP);
|
|
|
|
resp->ies.assoc_setup_resp = (struct osmo_pfcp_msg_assoc_setup_resp) {
|
|
.cause = cause,
|
|
.recovery_time_stamp = osmo_pfcp_endpoint_get_recovery_timestamp(g_upf->pfcp.ep->pfcp_ep),
|
|
.up_function_features_present = true,
|
|
.up_function_features = peer->local_up_features,
|
|
};
|
|
|
|
if (osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, resp)) {
|
|
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Error sending response to this message,"
|
|
" cannot associate with peer\n");
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int up_peer_tx_assoc_rel_resp(struct up_peer *peer, struct osmo_pfcp_msg *m, enum osmo_pfcp_cause cause)
|
|
{
|
|
struct osmo_pfcp_msg *resp;
|
|
|
|
resp = up_peer_init_tx(peer, m, OSMO_PFCP_MSGT_ASSOC_RELEASE_RESP);
|
|
|
|
resp->ies.assoc_release_resp = (struct osmo_pfcp_msg_assoc_release_resp) {
|
|
.cause = cause,
|
|
};
|
|
|
|
if (osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, resp)) {
|
|
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Error sending response to this message\n");
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void up_peer_clear_sessions(struct up_peer *peer)
|
|
{
|
|
struct up_session *session;
|
|
int bkt;
|
|
struct hlist_node *tmp;
|
|
int count = 0;
|
|
hash_for_each_safe(peer->sessions_by_up_seid, bkt, tmp, session, node_by_up_seid) {
|
|
count += up_session_discard(session);
|
|
}
|
|
if (count)
|
|
LOGPFSML(peer->fi, LOGL_NOTICE, "terminated %d sessions\n", count);
|
|
}
|
|
|
|
static void up_peer_rx_assoc_setup_req(struct up_peer *peer, struct osmo_pfcp_msg *m)
|
|
{
|
|
struct osmo_fsm_inst *fi = peer->fi;
|
|
enum osmo_pfcp_cause cause = OSMO_PFCP_CAUSE_REQUEST_ACCEPTED;
|
|
|
|
if (fi->state == UP_PEER_ST_ASSOCIATED) {
|
|
/* Retransmissions of the ACK response happen in pfcp_endpoint.c. So if we get this, it is a genuine
|
|
* duplicate association setup request. We could reject it. But why. Just "replace" with the new
|
|
* association. Continue. */
|
|
/* If the peer has restarted, it has forgotten about all sessions. */
|
|
if (peer->remote_recovery_timestamp != m->ies.assoc_setup_req.recovery_time_stamp) {
|
|
LOGPFSML(fi, LOGL_NOTICE, "another Association Setup Request, with different Recovery Timestamp."
|
|
" Clearing sessions, sending ACK.\n");
|
|
up_peer_clear_sessions(peer);
|
|
} else {
|
|
LOGPFSML(fi, LOGL_NOTICE, "another Association Setup Request, with same Recovery Timestamp."
|
|
" Keeping sessions, sending ACK.\n");
|
|
}
|
|
} else if (up_peer_fsm_state_chg(UP_PEER_ST_ASSOCIATED)) {
|
|
/* Not allowed to transition to ST_ASSOCIATED */
|
|
cause = OSMO_PFCP_CAUSE_REQUEST_REJECTED;
|
|
} else {
|
|
/* Successfully transitioned to ST_ASSOCIATED */
|
|
peer->remote_recovery_timestamp = m->ies.assoc_setup_req.recovery_time_stamp;
|
|
peer->remote_node_id = m->ies.assoc_setup_req.node_id;
|
|
if (m->ies.assoc_setup_req.cp_function_features_present)
|
|
peer->peer_cp_features = m->ies.assoc_setup_req.cp_function_features;
|
|
}
|
|
|
|
if (up_peer_tx_assoc_setup_resp(peer, m, cause)
|
|
|| cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED)
|
|
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
|
|
|
|
LOGPFSML(fi, LOGL_NOTICE, "Peer associated, Node-Id=%s. Local UP features: [%s]; Peer CP features: [%s]\n",
|
|
osmo_pfcp_ie_node_id_to_str_c(OTC_SELECT, &peer->remote_node_id),
|
|
osmo_pfcp_bits_to_str_c(OTC_SELECT, peer->local_up_features.bits, osmo_pfcp_up_feature_strs),
|
|
osmo_pfcp_bits_to_str_c(OTC_SELECT, peer->peer_cp_features.bits, osmo_pfcp_cp_feature_strs));
|
|
}
|
|
|
|
static void up_peer_rx_assoc_rel_req(struct up_peer *peer, struct osmo_pfcp_msg *m)
|
|
{
|
|
struct osmo_fsm_inst *fi = peer->fi;
|
|
up_peer_tx_assoc_rel_resp(peer, m, OSMO_PFCP_CAUSE_REQUEST_ACCEPTED);
|
|
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
|
|
}
|
|
|
|
static void up_peer_rx_session_est_req(struct up_peer *peer, struct osmo_pfcp_msg *m)
|
|
{
|
|
enum osmo_pfcp_cause cause = OSMO_PFCP_CAUSE_REQUEST_ACCEPTED;
|
|
struct osmo_pfcp_msg *resp;
|
|
struct up_session *session = up_session_find_or_add(peer, &m->ies.session_est_req.cp_f_seid, NULL);
|
|
|
|
if (!session) {
|
|
cause = OSMO_PFCP_CAUSE_NO_RESOURCES_AVAILABLE;
|
|
goto nack_response;
|
|
}
|
|
|
|
up_session_set_msg_ctx(session, m);
|
|
|
|
if (osmo_fsm_inst_dispatch(session->fi, UP_SESSION_EV_RX_SESSION_EST_REQ, m)) {
|
|
cause = OSMO_PFCP_CAUSE_REQUEST_REJECTED;
|
|
goto nack_response;
|
|
}
|
|
return;
|
|
|
|
nack_response:
|
|
resp = up_peer_init_tx(peer, m, OSMO_PFCP_MSGT_SESSION_EST_RESP);
|
|
resp->h.seid = m->ies.session_est_req.cp_f_seid.seid;
|
|
resp->h.seid_present = true;
|
|
resp->ies.session_est_resp = (struct osmo_pfcp_msg_session_est_resp){
|
|
.cause = cause,
|
|
};
|
|
osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, resp);
|
|
if (session)
|
|
up_session_discard(session);
|
|
}
|
|
|
|
static void up_peer_not_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct up_peer *peer = fi->priv;
|
|
|
|
switch (event) {
|
|
|
|
case UP_PEER_EV_RX_ASSOC_SETUP_REQ:
|
|
up_peer_rx_assoc_setup_req(peer, data);
|
|
break;
|
|
|
|
case UP_PEER_EV_USE_COUNT_ZERO:
|
|
/* Not associated and no pending messages. discard peer. */
|
|
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
|
|
return;
|
|
|
|
default:
|
|
OSMO_ASSERT(false);
|
|
}
|
|
}
|
|
|
|
static void up_peer_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct up_peer *peer = fi->priv;
|
|
|
|
switch (event) {
|
|
|
|
case UP_PEER_EV_RX_ASSOC_SETUP_REQ:
|
|
up_peer_rx_assoc_setup_req(peer, data);
|
|
break;
|
|
|
|
case UP_PEER_EV_RX_ASSOC_UPD_REQ:
|
|
// FIXME
|
|
break;
|
|
|
|
case UP_PEER_EV_RX_SESSION_EST_REQ:
|
|
up_peer_rx_session_est_req(peer, data);
|
|
break;
|
|
|
|
case UP_PEER_EV_HEARTBEAT_FAILURE:
|
|
// FIXME
|
|
break;
|
|
|
|
case UP_PEER_EV_USE_COUNT_ZERO:
|
|
/* Stay associated. */
|
|
return;
|
|
|
|
default:
|
|
OSMO_ASSERT(false);
|
|
}
|
|
}
|
|
|
|
static void up_peer_associated_onleave(struct osmo_fsm_inst *fi, uint32_t next_state)
|
|
{
|
|
struct up_peer *peer = fi->priv;
|
|
if (next_state != UP_PEER_ST_ASSOCIATED)
|
|
LOGPFSML(fi, LOGL_NOTICE, "Peer %s released\n",
|
|
osmo_pfcp_ie_node_id_to_str_c(OTC_SELECT, &peer->remote_node_id));
|
|
}
|
|
|
|
static void up_peer_graceful_release_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
//struct up_peer *peer = fi->priv;
|
|
// FIXME
|
|
}
|
|
|
|
static void up_peer_graceful_release_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct up_peer *peer = fi->priv;
|
|
|
|
switch (event) {
|
|
|
|
case UP_PEER_EV_HEARTBEAT_FAILURE:
|
|
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
|
|
break;
|
|
|
|
case UP_PEER_EV_USE_COUNT_ZERO:
|
|
/* When there are still sessions, stay around. */
|
|
if (!hash_empty(peer->sessions_by_up_seid))
|
|
return;
|
|
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
|
|
return;
|
|
|
|
default:
|
|
OSMO_ASSERT(false);
|
|
}
|
|
}
|
|
|
|
static void up_peer_wait_use_count_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct up_peer *peer = fi->priv;
|
|
up_peer_clear_sessions(peer);
|
|
if (!osmo_use_count_total(&peer->use_count))
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
|
|
}
|
|
|
|
static void up_peer_wait_use_count_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct up_peer *peer = fi->priv;
|
|
switch (event) {
|
|
|
|
case UP_PEER_EV_USE_COUNT_ZERO:
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
|
|
return;
|
|
|
|
case UP_PEER_EV_RX_ASSOC_SETUP_REQ:
|
|
up_peer_rx_assoc_setup_req(peer, data);
|
|
break;
|
|
|
|
default:
|
|
OSMO_ASSERT(false);
|
|
}
|
|
}
|
|
|
|
#define S(x) (1 << (x))
|
|
|
|
static const struct osmo_fsm_state up_peer_fsm_states[] = {
|
|
[UP_PEER_ST_NOT_ASSOCIATED] = {
|
|
.name = "NOT_ASSOCIATED",
|
|
.in_event_mask = 0
|
|
| S(UP_PEER_EV_RX_ASSOC_SETUP_REQ)
|
|
| S(UP_PEER_EV_USE_COUNT_ZERO)
|
|
,
|
|
.out_state_mask = 0
|
|
| S(UP_PEER_ST_ASSOCIATED)
|
|
| S(UP_PEER_ST_WAIT_USE_COUNT)
|
|
,
|
|
.action = up_peer_not_associated_action,
|
|
},
|
|
[UP_PEER_ST_ASSOCIATED] = {
|
|
.name = "ASSOCIATED",
|
|
.in_event_mask = 0
|
|
| S(UP_PEER_EV_RX_ASSOC_SETUP_REQ)
|
|
| S(UP_PEER_EV_RX_ASSOC_UPD_REQ)
|
|
| S(UP_PEER_EV_RX_SESSION_EST_REQ)
|
|
| S(UP_PEER_EV_HEARTBEAT_FAILURE)
|
|
| S(UP_PEER_EV_USE_COUNT_ZERO)
|
|
,
|
|
.out_state_mask = 0
|
|
| S(UP_PEER_ST_ASSOCIATED)
|
|
| S(UP_PEER_ST_GRACEFUL_RELEASE)
|
|
| S(UP_PEER_ST_WAIT_USE_COUNT)
|
|
,
|
|
.action = up_peer_associated_action,
|
|
.onleave = up_peer_associated_onleave,
|
|
},
|
|
[UP_PEER_ST_GRACEFUL_RELEASE] = {
|
|
.name = "GRACEFUL_RELEASE",
|
|
.in_event_mask = 0
|
|
| S(UP_PEER_EV_HEARTBEAT_FAILURE)
|
|
| S(UP_PEER_EV_USE_COUNT_ZERO)
|
|
,
|
|
.out_state_mask = 0
|
|
| S(UP_PEER_ST_WAIT_USE_COUNT)
|
|
,
|
|
.onenter = up_peer_graceful_release_onenter,
|
|
.action = up_peer_graceful_release_action,
|
|
},
|
|
[UP_PEER_ST_WAIT_USE_COUNT] = {
|
|
.name = "WAIT_USE_COUNT",
|
|
.in_event_mask = 0
|
|
| S(UP_PEER_EV_USE_COUNT_ZERO)
|
|
| S(UP_PEER_EV_RX_ASSOC_SETUP_REQ)
|
|
,
|
|
.out_state_mask = 0
|
|
| S(UP_PEER_ST_ASSOCIATED)
|
|
,
|
|
.onenter = up_peer_wait_use_count_onenter,
|
|
.action = up_peer_wait_use_count_action,
|
|
},
|
|
};
|
|
|
|
void up_peer_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
|
{
|
|
struct up_peer *peer = fi->priv;
|
|
LOGPFSML(fi, LOGL_NOTICE, "Peer removed\n");
|
|
llist_del(&peer->entry);
|
|
}
|
|
|
|
static void up_peer_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
switch (event) {
|
|
case UP_PEER_EV_SESSION_TERM:
|
|
/* ignore */
|
|
return;
|
|
case UP_PEER_EV_RX_ASSOC_REL_REQ:
|
|
up_peer_rx_assoc_rel_req(fi->priv, data);
|
|
return;
|
|
default:
|
|
OSMO_ASSERT(false);
|
|
}
|
|
}
|
|
|
|
static struct osmo_fsm up_peer_fsm = {
|
|
.name = "up_peer",
|
|
.log_subsys = DPEER,
|
|
.states = up_peer_fsm_states,
|
|
.num_states = ARRAY_SIZE(up_peer_fsm_states),
|
|
.event_names = up_peer_fsm_event_names,
|
|
.timer_cb = up_peer_fsm_timer_cb,
|
|
.cleanup = up_peer_fsm_cleanup,
|
|
.allstate_event_mask = 0
|
|
| S(UP_PEER_EV_RX_ASSOC_REL_REQ)
|
|
| S(UP_PEER_EV_SESSION_TERM)
|
|
,
|
|
.allstate_action = up_peer_allstate_action,
|
|
};
|
|
|
|
static __attribute__((constructor)) void up_peer_fsm_register(void)
|
|
{
|
|
OSMO_ASSERT(osmo_fsm_register(&up_peer_fsm) == 0);
|
|
}
|
|
|
|
void up_peer_free(struct up_peer *peer)
|
|
{
|
|
osmo_fsm_inst_term(peer->fi, OSMO_FSM_TERM_REGULAR, NULL);
|
|
}
|