/* Handle LCS BSSMAP-LE Perform Location Request */ /* * (C) 2020 by sysmocom - s.f.m.c. GmbH * All Rights Reserved * * Author: Neels Hofmeyr * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include enum smlc_loc_req_fsm_state { SMLC_LOC_REQ_ST_INIT, SMLC_LOC_REQ_ST_WAIT_TA, SMLC_LOC_REQ_ST_GOT_TA, SMLC_LOC_REQ_ST_FAILED, }; static const struct value_string smlc_loc_req_fsm_event_names[] = { OSMO_VALUE_STRING(SMLC_LOC_REQ_EV_RX_TA_RESPONSE), OSMO_VALUE_STRING(SMLC_LOC_REQ_EV_RX_BSSLAP_RESET), OSMO_VALUE_STRING(SMLC_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_ABORT), {} }; static struct osmo_fsm smlc_loc_req_fsm; static const struct osmo_tdef_state_timeout smlc_loc_req_fsm_timeouts[32] = { [SMLC_LOC_REQ_ST_WAIT_TA] = { .T = -12 }, }; /* Transition to a state, using the T timer defined in smlc_loc_req_fsm_timeouts. * The actual timeout value is in turn obtained from network->T_defs. * Assumes local variable fi exists. */ #define smlc_loc_req_fsm_state_chg(FI, STATE) \ osmo_tdef_fsm_inst_state_chg(FI, STATE, \ smlc_loc_req_fsm_timeouts, \ g_smlc_tdefs, \ 5) #define smlc_loc_req_fail(cause, fmt, args...) do { \ LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_ERROR, "Perform Location Request failed in state %s: " fmt "\n", \ smlc_loc_req ? osmo_fsm_inst_state_name(smlc_loc_req->fi) : "NULL", ## args); \ smlc_loc_req->lcs_cause = (struct lcs_cause_ie){ \ .present = true, \ .cause_val = cause, \ }; \ smlc_loc_req_fsm_state_chg(smlc_loc_req->fi, SMLC_LOC_REQ_ST_FAILED); \ } while(0) static struct smlc_loc_req *smlc_loc_req_alloc(void *ctx) { struct smlc_loc_req *smlc_loc_req; struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&smlc_loc_req_fsm, ctx, NULL, LOGL_DEBUG, "no-id"); OSMO_ASSERT(fi); smlc_loc_req = talloc(fi, struct smlc_loc_req); OSMO_ASSERT(smlc_loc_req); fi->priv = smlc_loc_req; *smlc_loc_req = (struct smlc_loc_req){ .fi = fi, }; return smlc_loc_req; } static int smlc_loc_req_start(struct lb_conn *lb_conn, const struct bssmap_le_perform_loc_req *loc_req_pdu) { struct smlc_loc_req *smlc_loc_req; rate_ctr_inc(rate_ctr_group_get_ctr(g_smlc->ctrs, SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_REQUEST)); if (lb_conn->smlc_loc_req) { /* Another request is already pending. If we send Perform Location Abort, the peer doesn't know which * request we would mean. Just drop this on the floor. */ LOG_SMLC_LOC_REQ(lb_conn->smlc_loc_req, LOGL_ERROR, "Ignoring Perform Location Request, another request is still pending\n"); return -EAGAIN; } if (loc_req_pdu->imsi.type == GSM_MI_TYPE_IMSI && (!lb_conn->smlc_subscr || osmo_mobile_identity_cmp(&loc_req_pdu->imsi, &lb_conn->smlc_subscr->imsi))) { struct smlc_subscr *smlc_subscr; struct lb_conn *other_conn; smlc_subscr = smlc_subscr_find_or_create(&loc_req_pdu->imsi, __func__); OSMO_ASSERT(smlc_subscr); if (lb_conn->smlc_subscr && lb_conn->smlc_subscr != smlc_subscr) { LOG_LB_CONN(lb_conn, LOGL_ERROR, "IMSI mismatch: lb_conn has %s, Rx Perform Location Request has %s\n", smlc_subscr_to_str_c(OTC_SELECT, lb_conn->smlc_subscr), smlc_subscr_to_str_c(OTC_SELECT, smlc_subscr)); smlc_subscr_put(smlc_subscr, __func__); return -EINVAL; } /* Find another conn before setting this conn's subscriber */ other_conn = lb_conn_find_by_smlc_subscr(lb_conn->smlc_subscr, __func__); /* Set the subscriber before logging about it, so that it shows as log context */ if (!lb_conn->smlc_subscr) { lb_conn->smlc_subscr = smlc_subscr; smlc_subscr_get(lb_conn->smlc_subscr, SMLC_SUBSCR_USE_LB_CONN); } if (other_conn && other_conn != lb_conn) { LOG_LB_CONN(lb_conn, LOGL_ERROR, "Another conn already active for this subscriber\n"); LOG_LB_CONN(other_conn, LOGL_ERROR, "Another conn opened for this subscriber, discarding\n"); lb_conn_close(other_conn); } smlc_subscr_put(smlc_subscr, __func__); if (other_conn) lb_conn_put(other_conn, __func__); } /* smlc_loc_req has a use count on lb_conn, so its talloc ctx must not be a child of lb_conn. (Otherwise an * lb_conn_put() from smlc_loc_req could cause a free of smlc_loc_req's parent ctx, causing a use after free on * FSM termination.) */ smlc_loc_req = smlc_loc_req_alloc(lb_conn->lb_peer); *smlc_loc_req = (struct smlc_loc_req){ .fi = smlc_loc_req->fi, .lb_conn = lb_conn, .req = *loc_req_pdu, }; smlc_loc_req->latest_cell_id = loc_req_pdu->cell_id; lb_conn->smlc_loc_req = smlc_loc_req; lb_conn_get(smlc_loc_req->lb_conn, LB_CONN_USE_SMLC_LOC_REQ); LOG_LB_CONN(lb_conn, LOGL_INFO, "Rx Perform Location Request (BSSLAP APDU %s), cell id is %s\n", loc_req_pdu->apdu_present ? osmo_bsslap_msgt_name(loc_req_pdu->apdu.msg_type) : "omitted", gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id)); /* state change to start the timeout */ smlc_loc_req_fsm_state_chg(smlc_loc_req->fi, SMLC_LOC_REQ_ST_WAIT_TA); return 0; } static int handle_bssmap_le_conn_oriented_info(struct smlc_loc_req *smlc_loc_req, const struct bssmap_le_conn_oriented_info *coi) { switch (coi->apdu.msg_type) { case BSSLAP_MSGT_TA_RESPONSE: return osmo_fsm_inst_dispatch(smlc_loc_req->fi, SMLC_LOC_REQ_EV_RX_TA_RESPONSE, (void*)&coi->apdu.ta_response); case BSSLAP_MSGT_RESET: return osmo_fsm_inst_dispatch(smlc_loc_req->fi, SMLC_LOC_REQ_EV_RX_BSSLAP_RESET, (void*)&coi->apdu.reset); case BSSLAP_MSGT_ABORT: smlc_loc_req_fail(LCS_CAUSE_REQUEST_ABORTED, "Aborting Location Request due to BSSLAP Abort"); return 0; case BSSLAP_MSGT_REJECT: smlc_loc_req_fail(LCS_CAUSE_REQUEST_ABORTED, "Aborting Location Request due to BSSLAP Reject"); return 0; default: LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_ERROR, "rx BSSLAP APDU with unsupported message type %s\n", osmo_bsslap_msgt_name(coi->apdu.msg_type)); return -ENOTSUP; }; } int smlc_loc_req_rx_bssap_le(struct lb_conn *lb_conn, const struct bssap_le_pdu *bssap_le) { struct smlc_loc_req *smlc_loc_req = lb_conn->smlc_loc_req; const struct bssmap_le_pdu *bssmap_le = &bssap_le->bssmap_le; LOG_LB_CONN(lb_conn, LOGL_DEBUG, "Rx %s\n", osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le)); if (bssap_le->discr != BSSAP_LE_MSG_DISCR_BSSMAP_LE) { LOG_LB_CONN(lb_conn, LOGL_ERROR, "BSSAP-LE discr %d not implemented\n", bssap_le->discr); return -ENOTSUP; } switch (bssmap_le->msg_type) { case BSSMAP_LE_MSGT_PERFORM_LOC_REQ: return smlc_loc_req_start(lb_conn, &bssmap_le->perform_loc_req); case BSSMAP_LE_MSGT_PERFORM_LOC_ABORT: return osmo_fsm_inst_dispatch(smlc_loc_req->fi, SMLC_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_ABORT, (void*)&bssmap_le->perform_loc_abort); case BSSMAP_LE_MSGT_CONN_ORIENTED_INFO: return handle_bssmap_le_conn_oriented_info(smlc_loc_req, &bssmap_le->conn_oriented_info); default: LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_ERROR, "Rx BSSMAP-LE from SMLC with unsupported message type: %s\n", osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le)); return -ENOTSUP; } } void smlc_loc_req_reset(struct lb_conn *lb_conn) { struct smlc_loc_req *smlc_loc_req = lb_conn->smlc_loc_req; if (!smlc_loc_req) return; smlc_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Aborting Location Request due to RESET on Lb"); } static int smlc_loc_req_fsm_timer_cb(struct osmo_fsm_inst *fi) { struct smlc_loc_req *smlc_loc_req = fi->priv; smlc_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Timeout"); return 1; } static void smlc_loc_req_wait_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct smlc_loc_req *smlc_loc_req = fi->priv; struct bssmap_le_pdu bssmap_le; /* Did the original request contain a TA already? */ if (smlc_loc_req->req.apdu_present && smlc_loc_req->req.apdu.msg_type == BSSLAP_MSGT_TA_LAYER3) { smlc_loc_req->ta_present = true; smlc_loc_req->ta = smlc_loc_req->req.apdu.ta_layer3.ta; LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_INFO, "TA = %u\n", smlc_loc_req->ta); smlc_loc_req_fsm_state_chg(smlc_loc_req->fi, SMLC_LOC_REQ_ST_GOT_TA); return; } /* No TA known yet, ask via BSSLAP */ bssmap_le = (struct bssmap_le_pdu){ .msg_type = BSSMAP_LE_MSGT_CONN_ORIENTED_INFO, .conn_oriented_info = { .apdu = { .msg_type = BSSLAP_MSGT_TA_REQUEST, }, }, }; lb_conn_send_bssmap_le(smlc_loc_req->lb_conn, &bssmap_le); } static void update_ci(struct gsm0808_cell_id *cell_id, int16_t new_ci) { struct osmo_cell_global_id cgi = {}; struct gsm0808_cell_id ci = { .id_discr = CELL_IDENT_CI, .id.ci = new_ci, }; /* Set all values from the cell_id to the cgi */ gsm0808_cell_id_to_cgi(&cgi, cell_id); /* Overwrite the CI part */ gsm0808_cell_id_to_cgi(&cgi, &ci); /* write back to cell_id, without changing its type */ gsm0808_cell_id_from_cgi(cell_id, cell_id->id_discr, &cgi); } static void smlc_loc_req_wait_ta_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct smlc_loc_req *smlc_loc_req = fi->priv; const struct bsslap_ta_response *ta_response; const struct bsslap_reset *reset; switch (event) { case SMLC_LOC_REQ_EV_RX_TA_RESPONSE: ta_response = data; smlc_loc_req->ta_present = true; smlc_loc_req->ta = ta_response->ta; update_ci(&smlc_loc_req->latest_cell_id, ta_response->cell_id); LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_INFO, "Rx BSSLAP TA Response: cell id is now %s\n", gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id)); smlc_loc_req_fsm_state_chg(smlc_loc_req->fi, SMLC_LOC_REQ_ST_GOT_TA); return; case SMLC_LOC_REQ_EV_RX_BSSLAP_RESET: reset = data; smlc_loc_req->ta_present = true; smlc_loc_req->ta = reset->ta; update_ci(&smlc_loc_req->latest_cell_id, reset->cell_id); LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_INFO, "Rx BSSLAP Reset: cell id is now %s\n", gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id)); smlc_loc_req_fsm_state_chg(smlc_loc_req->fi, SMLC_LOC_REQ_ST_GOT_TA); return; case SMLC_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_ABORT: LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_INFO, "Rx Perform Location Abort, stopping this request dead\n"); osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, NULL); return; default: OSMO_ASSERT(false); } } static void smlc_loc_req_got_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct smlc_loc_req *smlc_loc_req = fi->priv; struct bssmap_le_pdu bssmap_le; struct osmo_gad location; int rc; if (!smlc_loc_req->ta_present) { smlc_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Internal error: GOT_TA event, but no TA present"); return; } bssmap_le = (struct bssmap_le_pdu){ .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_RESP, .perform_loc_resp = { .location_estimate_present = true, }, }; rc = cell_location_from_ta(&location, &smlc_loc_req->latest_cell_id, smlc_loc_req->ta); if (rc) { smlc_loc_req_fail(LCS_CAUSE_FACILITY_NOTSUPP, "Unable to compose Location Estimate for %s: %s", gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id), rc == -ENOENT ? "No location information for this cell" : "unknown error"); return; } rc = osmo_gad_enc(&bssmap_le.perform_loc_resp.location_estimate, &location); if (rc <= 0) { smlc_loc_req_fail(LCS_CAUSE_FACILITY_NOTSUPP, "Unable to encode Location Estimate for %s (rc=%d)", gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id), rc); return; } LOG_SMLC_LOC_REQ(smlc_loc_req, LOGL_INFO, "Returning location estimate to BSC: %s TA=%u --> %s\n", gsm0808_cell_id_name_c(OTC_SELECT, &smlc_loc_req->latest_cell_id), smlc_loc_req->ta, osmo_gad_to_str_c(OTC_SELECT, &location)); if (lb_conn_send_bssmap_le(smlc_loc_req->lb_conn, &bssmap_le)) { smlc_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Unable to encode/send BSSMAP-LE Perform Location Response"); return; } osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); } static void smlc_loc_req_failed_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct smlc_loc_req *smlc_loc_req = fi->priv; struct bssmap_le_pdu bssmap_le = { .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_RESP, .perform_loc_resp = { .lcs_cause = smlc_loc_req->lcs_cause, }, }; int rc; rc = lb_conn_send_bssmap_le(smlc_loc_req->lb_conn, &bssmap_le); osmo_fsm_inst_term(fi, rc ? OSMO_FSM_TERM_ERROR : OSMO_FSM_TERM_REGULAR, NULL); } void smlc_loc_req_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { struct smlc_loc_req *smlc_loc_req = fi->priv; if (smlc_loc_req->lb_conn && smlc_loc_req->lb_conn->smlc_loc_req == smlc_loc_req) { smlc_loc_req->lb_conn->smlc_loc_req = NULL; lb_conn_put(smlc_loc_req->lb_conn, LB_CONN_USE_SMLC_LOC_REQ); } } #define S(x) (1 << (x)) static const struct osmo_fsm_state smlc_loc_req_fsm_states[] = { [SMLC_LOC_REQ_ST_INIT] = { .name = "INIT", .out_state_mask = 0 | S(SMLC_LOC_REQ_ST_WAIT_TA) | S(SMLC_LOC_REQ_ST_FAILED) , }, [SMLC_LOC_REQ_ST_WAIT_TA] = { .name = "WAIT_TA", .in_event_mask = 0 | S(SMLC_LOC_REQ_EV_RX_TA_RESPONSE) | S(SMLC_LOC_REQ_EV_RX_BSSLAP_RESET) | S(SMLC_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_ABORT) , .out_state_mask = 0 | S(SMLC_LOC_REQ_ST_GOT_TA) | S(SMLC_LOC_REQ_ST_FAILED) , .onenter = smlc_loc_req_wait_ta_onenter, .action = smlc_loc_req_wait_ta_action, }, [SMLC_LOC_REQ_ST_GOT_TA] = { .name = "GOT_TA", .out_state_mask = 0 | S(SMLC_LOC_REQ_ST_FAILED) , .onenter = smlc_loc_req_got_ta_onenter, }, [SMLC_LOC_REQ_ST_FAILED] = { .name = "FAILED", .onenter = smlc_loc_req_failed_onenter, }, }; static struct osmo_fsm smlc_loc_req_fsm = { .name = "smlc_loc_req", .states = smlc_loc_req_fsm_states, .num_states = ARRAY_SIZE(smlc_loc_req_fsm_states), .log_subsys = DLCS, .event_names = smlc_loc_req_fsm_event_names, .timer_cb = smlc_loc_req_fsm_timer_cb, .cleanup = smlc_loc_req_fsm_cleanup, }; static __attribute__((constructor)) void smlc_loc_req_fsm_register(void) { OSMO_ASSERT(osmo_fsm_register(&smlc_loc_req_fsm) == 0); }