/* SM service primitive implementation as per 3GPP TS 44.065 */ /* * (C) 2023 by sysmocom - s.f.m.c. GmbH * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #define SM_MSGB_HEADROOM 0 const struct value_string osmo_gprs_sm_prim_sap_names[] = { { OSMO_GPRS_SM_SAP_SMREG, "SMREG" }, { 0, NULL } }; const struct value_string osmo_gprs_sm_smreg_prim_type_names[] = { { OSMO_GPRS_SM_SMREG_PDP_ACTIVATE, "PDP_ACTIVATE" }, { OSMO_GPRS_SM_SMREG_PDP_DEACTIVATE, "PDP_DEACTIVATE" }, { OSMO_GPRS_SM_SMREG_PDP_MODIFY, "PDP_MODIFY"}, { OSMO_GPRS_SM_SMREG_PDP_ACTIVATE_SEC, "PDP_ACTIVATE_SEC" }, { OSMO_GPRS_SM_SMREG_MBMS_ACTIVATE, "MBMS_ACTIVATE" }, { 0, NULL } }; const char *osmo_gprs_sm_prim_name(const struct osmo_gprs_sm_prim *sm_prim) { static char name_buf[256]; const char *sap = osmo_gprs_sm_prim_sap_name(sm_prim->oph.sap); const char *op = get_value_string(osmo_prim_op_names, sm_prim->oph.operation); const char *type; switch (sm_prim->oph.sap) { case OSMO_GPRS_SM_SAP_SMREG: type = osmo_gprs_sm_smreg_prim_type_name(sm_prim->oph.primitive); break; default: type = "unsupported-sm-sap"; } snprintf(name_buf, sizeof(name_buf), "%s-%s.%s", sap, type, op); return name_buf; } static int sm_up_cb_dummy(struct osmo_gprs_sm_prim *sm_prim, void *user_data) { LOGSM(LOGL_INFO, "%s(%s)\n", __func__, osmo_gprs_sm_prim_name(sm_prim)); return 0; } static int sm_sndcp_up_cb_dummy(struct osmo_gprs_sndcp_prim *sndcp_prim, void *user_data) { LOGSM(LOGL_INFO, "%s(%s)\n", __func__, osmo_gprs_sndcp_prim_name(sndcp_prim)); return 0; } static int sm_down_cb_dummy(struct osmo_gprs_sm_prim *sm_prim, void *user_data) { LOGSM(LOGL_INFO, "%s(%s)\n", __func__, osmo_gprs_sm_prim_name(sm_prim)); return 0; } static int sm_gmm_down_cb_dummy(struct osmo_gprs_gmm_prim *gmm_prim, void *user_data) { LOGSM(LOGL_INFO, "%s(%s)\n", __func__, osmo_gprs_gmm_prim_name(gmm_prim)); return 0; } /* Set callback used by SM layer to push primitives to higher layers in protocol stack */ void osmo_gprs_sm_prim_set_up_cb(osmo_gprs_sm_prim_up_cb up_cb, void *up_user_data) { g_sm_ctx->sm_up_cb = up_cb; g_sm_ctx->sm_up_cb_user_data = up_user_data; } /* Set callback used by SM layer to push primitives to SNDCP higher layer in protocol stack */ void osmo_gprs_sm_prim_set_sndcp_up_cb(osmo_gprs_sm_prim_sndcp_up_cb sndcp_up_cb, void *sndcp_up_user_data) { g_sm_ctx->sm_sndcp_up_cb = sndcp_up_cb; g_sm_ctx->sm_sndcp_up_cb_user_data = sndcp_up_user_data; } /* Set callback used by SM layer to push primitives to lower layers in protocol stack */ void osmo_gprs_sm_prim_set_down_cb(osmo_gprs_sm_prim_down_cb down_cb, void *down_user_data) { g_sm_ctx->sm_down_cb = down_cb; g_sm_ctx->sm_down_cb_user_data = down_user_data; } /* Set callback used by SM layer to push primitives to GMM lower layer in protocol stack */ void osmo_gprs_sm_prim_set_gmm_down_cb(osmo_gprs_sm_prim_gmm_down_cb gmm_down_cb, void *gmm_down_user_data) { g_sm_ctx->sm_gmm_down_cb = gmm_down_cb; g_sm_ctx->sm_gmm_down_cb_user_data = gmm_down_user_data; } /******************************** * Primitive allocation: ********************************/ /* allocate a msgb containing a struct osmo_gprs_sm_prim + optional l3 data */ static struct msgb *gprs_sm_prim_msgb_alloc(unsigned int npdu_len) { const int headroom = SM_MSGB_HEADROOM; const int size = headroom + sizeof(struct osmo_gprs_sm_prim) + npdu_len; struct msgb *msg = msgb_alloc_headroom(size, headroom, "sm_prim"); if (!msg) return NULL; msg->l1h = msgb_put(msg, sizeof(struct osmo_gprs_sm_prim)); return msg; } struct osmo_gprs_sm_prim *gprs_sm_prim_alloc(unsigned int sap, unsigned int type, enum osmo_prim_operation operation, unsigned int extra_size) { struct msgb *msg = gprs_sm_prim_msgb_alloc(extra_size); struct osmo_gprs_sm_prim *sm_prim = msgb_sm_prim(msg); osmo_prim_init(&sm_prim->oph, sap, type, operation, msg); return sm_prim; } /*** SMREG ***/ static inline struct osmo_gprs_sm_prim *sm_prim_smreg_alloc(enum osmo_gprs_sm_smreg_prim_type type, enum osmo_prim_operation operation, unsigned int extra_size) { return gprs_sm_prim_alloc(OSMO_GPRS_SM_SAP_SMREG, type, operation, extra_size); } /* TS 24.007 6.5.1.1 SMREG-PDP-ACTIVATE-REQ */ struct osmo_gprs_sm_prim *osmo_gprs_sm_prim_alloc_smreg_pdp_act_req(void) { struct osmo_gprs_sm_prim *sm_prim; sm_prim = sm_prim_smreg_alloc(OSMO_GPRS_SM_SMREG_PDP_ACTIVATE, PRIM_OP_REQUEST, 0); return sm_prim; } /* TS 24.007 6.5.1.2 SMREG-PDP-ACTIVATE-CNF */ struct osmo_gprs_sm_prim *gprs_sm_prim_alloc_smreg_pdp_act_cnf(void) { struct osmo_gprs_sm_prim *sm_prim; sm_prim = sm_prim_smreg_alloc(OSMO_GPRS_SM_SMREG_PDP_ACTIVATE, PRIM_OP_CONFIRM, 0); sm_prim->smreg.pdp_act_cnf.accepted = true; /* TODO: sm_prim->smreg.pdp_act_cnf.acc.* */ return sm_prim; } /* TODO: TS 24.007 6.5.1.3 SMREG-PDP-ACTIVATE-REJ */ /* TS 24.007 6.5.1.4 SMREG-PDP-ACTIVATE-IND */ struct osmo_gprs_sm_prim *gprs_sm_prim_alloc_smreg_pdp_act_ind(void) { struct osmo_gprs_sm_prim *sm_prim; sm_prim = sm_prim_smreg_alloc(OSMO_GPRS_SM_SMREG_PDP_ACTIVATE, PRIM_OP_INDICATION, 0); return sm_prim; } /* TS 24.007 6.5.1.7 SMREG-PDP-ACTIVATE-IND */ struct osmo_gprs_sm_prim *gprs_sm_prim_alloc_smreg_pdp_deact_ind(void) { struct osmo_gprs_sm_prim *sm_prim; sm_prim = sm_prim_smreg_alloc(OSMO_GPRS_SM_SMREG_PDP_DEACTIVATE, PRIM_OP_INDICATION, 0); return sm_prim; } static int gprs_sm_prim_handle_unsupported(struct osmo_gprs_sm_prim *sm_prim) { LOGSM(LOGL_ERROR, "Unsupported sm_prim! %s\n", osmo_gprs_sm_prim_name(sm_prim)); msgb_free(sm_prim->oph.msg); return -ENOTSUP; } static int gprs_sm_prim_handle_gmm_unsupported(struct osmo_gprs_gmm_prim *gmm_prim) { LOGSM(LOGL_ERROR, "Unsupported gmm_prim! %s\n", osmo_gprs_gmm_prim_name(gmm_prim)); msgb_free(gmm_prim->oph.msg); return -ENOTSUP; } static int gprs_sm_prim_handle_sndcp_unsupported(struct osmo_gprs_sndcp_prim *sndcp_prim) { LOGSM(LOGL_ERROR, "Unsupported sndcp_prim! %s\n", osmo_gprs_sndcp_prim_name(sndcp_prim)); msgb_free(sndcp_prim->oph.msg); return -ENOTSUP; } /******************************** * Handling from/to upper layers: ********************************/ int gprs_sm_prim_call_up_cb(struct osmo_gprs_sm_prim *sm_prim) { int rc; if (g_sm_ctx->sm_up_cb) rc = g_sm_ctx->sm_up_cb(sm_prim, g_sm_ctx->sm_up_cb_user_data); else rc = sm_up_cb_dummy(sm_prim, g_sm_ctx->sm_up_cb_user_data); msgb_free(sm_prim->oph.msg); return rc; } /* TS 24.007 6.5.1.1 SMREG-PDP_ACTIVATE.request:*/ static int gprs_sm_prim_handle_smreg_pdp_act_req(struct osmo_gprs_sm_prim *sm_prim) { int rc = 0; struct gprs_sm_ms *ms; struct gprs_sm_entity *sme = NULL; struct osmo_gprs_sm_smreg_prim *smreg = &sm_prim->smreg; OSMO_ASSERT(smreg->pdp_act_req.qos_len <= sizeof(sme->qos)); OSMO_ASSERT(smreg->pdp_act_req.pco_len <= sizeof(sme->pco)); ms = gprs_sm_find_ms_by_id(smreg->ms_id); if (!ms) { ms = gprs_sm_ms_alloc(smreg->ms_id); OSMO_ASSERT(ms); } else { sme = gprs_sm_ms_get_pdp_ctx(ms, smreg->pdp_act_req.nsapi); if (sme) { LOGSME(sme, LOGL_ERROR, "Rx SMREG-PDP-ACT.req for already existing PDP context\n"); return -EINVAL; } } sme = gprs_sm_entity_alloc(ms, smreg->pdp_act_req.nsapi); OSMO_ASSERT(sme); if (smreg->pdp_act_req.llc_sapi != OSMO_GPRS_SM_LLC_SAPI_UNASSIGNED) sme->llc_sapi = smreg->pdp_act_req.llc_sapi; else sme->llc_sapi = OSMO_GPRS_SM_LLC_SAPI_SAPI3; /* default */ sme->pdp_addr_ietf_type = smreg->pdp_act_req.pdp_addr_ietf_type; memcpy(&sme->pdp_addr_v4, &smreg->pdp_act_req.pdp_addr_v4, sizeof(sme->pdp_addr_v4)); memcpy(&sme->pdp_addr_v6, &smreg->pdp_act_req.pdp_addr_v6, sizeof(sme->pdp_addr_v6)); OSMO_STRLCPY_ARRAY(sme->apn, smreg->pdp_act_req.apn); sme->qos_len = smreg->pdp_act_req.qos_len; if (sme->qos_len > 0) memcpy(&sme->qos, &smreg->pdp_act_req.qos, sme->qos_len); sme->pco_len = smreg->pdp_act_req.pco_len; if (sme->pco_len > 0) memcpy(&sme->pco, &smreg->pdp_act_req.pco, sme->pco_len); /* Info required to establish GMM: */ ms->gmm.ptmsi = sm_prim->smreg.pdp_act_req.gmm.ptmsi; ms->gmm.ptmsi_sig = sm_prim->smreg.pdp_act_req.gmm.ptmsi_sig; if (sm_prim->smreg.pdp_act_req.gmm.imsi[0] != '\0') OSMO_STRLCPY_ARRAY(ms->gmm.imsi, sm_prim->smreg.pdp_act_req.gmm.imsi); if (sm_prim->smreg.pdp_act_req.gmm.imei[0] != '\0') OSMO_STRLCPY_ARRAY(ms->gmm.imei, sm_prim->smreg.pdp_act_req.gmm.imei); if (sm_prim->smreg.pdp_act_req.gmm.imeisv[0] != '\0') OSMO_STRLCPY_ARRAY(ms->gmm.imeisv, sm_prim->smreg.pdp_act_req.gmm.imeisv); memcpy(&ms->gmm.ra, &sm_prim->smreg.pdp_act_req.gmm.old_rai, sizeof(ms->gmm.ra)); rc = osmo_fsm_inst_dispatch(sme->ms_fsm.fi, GPRS_SM_MS_EV_TX_ACT_PDP_CTX_REQ, NULL); return rc; } static int gprs_sm_prim_handle_smreg(struct osmo_gprs_sm_prim *sm_prim) { int rc; switch (OSMO_PRIM_HDR(&sm_prim->oph)) { case OSMO_PRIM(OSMO_GPRS_SM_SMREG_PDP_ACTIVATE, PRIM_OP_REQUEST): rc = gprs_sm_prim_handle_smreg_pdp_act_req(sm_prim); break; default: rc = gprs_sm_prim_handle_unsupported(sm_prim); } return rc; } /* SM higher layers push SM primitive down to SM layer: */ int osmo_gprs_sm_prim_upper_down(struct osmo_gprs_sm_prim *sm_prim) { int rc; LOGSM(LOGL_INFO, "Rx from upper layers: %s\n", osmo_gprs_sm_prim_name(sm_prim)); switch (sm_prim->oph.sap) { case OSMO_GPRS_SM_SAP_SMREG: rc = gprs_sm_prim_handle_smreg(sm_prim); break; default: rc = gprs_sm_prim_handle_unsupported(sm_prim); rc = 1; } /* Special return value '1' means: do not free */ if (rc != 1) msgb_free(sm_prim->oph.msg); else rc = 0; return rc; } /* SM layer pushes SNDCP primitive up to higher layers (SNSM): */ int gprs_sm_prim_call_sndcp_up_cb(struct osmo_gprs_sndcp_prim *sndcp_prim) { int rc; if (g_sm_ctx->sm_sndcp_up_cb) rc = g_sm_ctx->sm_sndcp_up_cb(sndcp_prim, g_sm_ctx->sm_sndcp_up_cb_user_data); else rc = sm_sndcp_up_cb_dummy(sndcp_prim, g_sm_ctx->sm_sndcp_up_cb_user_data); /* Special return value '1' means: do not free */ if (rc != 1) msgb_free(sndcp_prim->oph.msg); else rc = 0; return rc; } /* TS 24.007 5.1.2.20 SNSM-ACTIVATE.response: */ static int gprs_sm_prim_handle_snsm_act_resp(struct osmo_gprs_sndcp_prim *sndcp_prim) { int rc; struct gprs_sm_ms *ms; struct gprs_sm_entity *sme; ms = gprs_sm_find_ms_by_tlli(sndcp_prim->snsm.tlli); if (!ms) { LOGSM(LOGL_ERROR, "Rx %s: Unable to find MS with TLLI=0x%08x\n", osmo_gprs_sndcp_prim_name(sndcp_prim), sndcp_prim->snsm.tlli); return -ENOENT; } sme = gprs_sm_ms_get_pdp_ctx(ms, sndcp_prim->snsm.activate_rsp.nsapi); if (!sme) { LOGMS(ms, LOGL_ERROR, "Rx %s: Unable to find NSAPI=%u\n", osmo_gprs_sndcp_prim_name(sndcp_prim), sndcp_prim->snsm.activate_rsp.nsapi); return -ENOENT; } rc = osmo_fsm_inst_dispatch(sme->ms_fsm.fi, GPRS_SM_MS_EV_NSAPI_ACTIVATED, NULL); return rc; } /* TS 24.007 5.1.2.22 SNSM-DEACTIVATE.response: */ static int gprs_sm_prim_handle_snsm_deact_resp(struct osmo_gprs_sndcp_prim *sndcp_prim) { int rc; struct gprs_sm_ms *ms; struct gprs_sm_entity *sme; ms = gprs_sm_find_ms_by_tlli(sndcp_prim->snsm.tlli); if (!ms) { LOGSM(LOGL_ERROR, "Rx %s: Unable to find MS with TLLI=0x%08x\n", osmo_gprs_sndcp_prim_name(sndcp_prim), sndcp_prim->snsm.tlli); return -ENOENT; } sme = gprs_sm_ms_get_pdp_ctx(ms, sndcp_prim->snsm.activate_rsp.nsapi); if (!sme) { LOGMS(ms, LOGL_ERROR, "Rx %s: Unable to find NSAPI=%u\n", osmo_gprs_sndcp_prim_name(sndcp_prim), sndcp_prim->snsm.activate_rsp.nsapi); return -ENOENT; } rc = gprs_sm_submit_smreg_pdp_deact_ind(sme, GSM_CAUSE_REACT_RQD); /* Submitting GMM_RELEASE.ind received the GMM release was delayed until * getting SNSM-DEACT.ind->rsp pingpong, since it would free the sme. Do it now: */ rc = osmo_fsm_inst_dispatch(sme->ms_fsm.fi, GPRS_SM_MS_EV_GMM_RELEASE_IND, NULL); return rc; } /* SNDCP higher layers push SNDCP primitive (SNSM) down to SM layer: */ static int gprs_sm_prim_handle_sndcp_snsm(struct osmo_gprs_sndcp_prim *sndcp_prim) { int rc; switch (OSMO_PRIM_HDR(&sndcp_prim->oph)) { case OSMO_PRIM(OSMO_GPRS_SNDCP_SNSM_ACTIVATE, PRIM_OP_RESPONSE): rc = gprs_sm_prim_handle_snsm_act_resp(sndcp_prim); break; case OSMO_PRIM(OSMO_GPRS_SNDCP_SNSM_DEACTIVATE, PRIM_OP_RESPONSE): rc = gprs_sm_prim_handle_snsm_deact_resp(sndcp_prim); break; case OSMO_PRIM(OSMO_GPRS_SNDCP_SNSM_MODIFY, PRIM_OP_RESPONSE): case OSMO_PRIM(OSMO_GPRS_SNDCP_SNSM_STATUS, PRIM_OP_REQUEST): case OSMO_PRIM(OSMO_GPRS_SNDCP_SNSM_SEQUENCE, PRIM_OP_RESPONSE): case OSMO_PRIM(OSMO_GPRS_SNDCP_SNSM_STOP_ASSIGN, PRIM_OP_RESPONSE): default: rc = gprs_sm_prim_handle_sndcp_unsupported(sndcp_prim); } return rc; } /* SM higher layers push SM primitive down to SM layer: */ int osmo_gprs_sm_prim_sndcp_upper_down(struct osmo_gprs_sndcp_prim *sndcp_prim) { int rc; LOGSM(LOGL_INFO, "Rx from SNDCP layer: %s\n", osmo_gprs_sndcp_prim_name(sndcp_prim)); switch (sndcp_prim->oph.sap) { case OSMO_GPRS_SNDCP_SAP_SNSM: rc = gprs_sm_prim_handle_sndcp_snsm(sndcp_prim); break; default: rc = gprs_sm_prim_handle_sndcp_unsupported(sndcp_prim); rc = 1; } /* Special return value '1' means: do not free */ if (rc != 1) msgb_free(sndcp_prim->oph.msg); else rc = 0; return rc; } /******************************** * Handling from/to lower layers: ********************************/ int gprs_sm_prim_call_down_cb(struct osmo_gprs_sm_prim *sm_prim) { int rc; if (g_sm_ctx->sm_down_cb) rc = g_sm_ctx->sm_down_cb(sm_prim, g_sm_ctx->sm_down_cb_user_data); else rc = sm_down_cb_dummy(sm_prim, g_sm_ctx->sm_down_cb_user_data); msgb_free(sm_prim->oph.msg); return rc; } /* SM lower layers (GMM) push SM primitive up to SM layer: */ int osmo_gprs_sm_prim_lower_up(struct osmo_gprs_sm_prim *sm_prim) { OSMO_ASSERT(g_sm_ctx); OSMO_ASSERT(sm_prim); struct msgb *msg = sm_prim->oph.msg; int rc; LOGSM(LOGL_INFO, "Rx from lower layers: %s\n", osmo_gprs_sm_prim_name(sm_prim)); switch (sm_prim->oph.sap) { default: rc = gprs_sm_prim_handle_unsupported(sm_prim); rc = 1; } /* Special return value '1' means: do not free */ if (rc != 1) msgb_free(msg); else rc = 0; return rc; } int gprs_sm_prim_call_gmm_down_cb(struct osmo_gprs_gmm_prim *gmm_prim) { int rc; if (g_sm_ctx->sm_gmm_down_cb) rc = g_sm_ctx->sm_gmm_down_cb(gmm_prim, g_sm_ctx->sm_gmm_down_cb_user_data); else rc = sm_gmm_down_cb_dummy(gmm_prim, g_sm_ctx->sm_gmm_down_cb_user_data); /* Special return value '1' means: do not free */ if (rc != 1) msgb_free(gmm_prim->oph.msg); else rc = 0; return rc; } /* TS 24.007 9.5.1.2 GMMSM-ESTABLISH-CNF */ static int gprs_sm_prim_handle_gmmsm_establish_cnf(struct osmo_gprs_gmm_prim *gmm_prim) { struct osmo_gprs_gmm_gmmsm_prim *gmmsm = &gmm_prim->gmmsm; struct gprs_sm_entity *sme; int rc; sme = gprs_sm_find_sme_by_sess_id(gmmsm->sess_id); if (!sme) { LOGSM(LOGL_ERROR, "Rx GMMSM-ESTABLISH.cnf for non existing SM Entity\n"); return -EINVAL; } if (gmmsm->establish_cnf.accepted) { /* Update allocated PTMSI: */ if (gmm_prim->gmmsm.establish_cnf.acc.allocated_ptmsi != GSM_RESERVED_TMSI) sme->ms->gmm.ptmsi = gmm_prim->gmmsm.establish_cnf.acc.allocated_ptmsi; sme->ms->gmm.ptmsi_sig = gmm_prim->gmmsm.establish_cnf.acc.allocated_ptmsi_sig; /* Set allocated TLLI: */ sme->ms->gmm.tlli = gmm_prim->gmmsm.establish_cnf.acc.allocated_tlli; /* Set the current RAI: */ memcpy(&sme->ms->gmm.ra, &gmm_prim->gmmsm.establish_cnf.acc.rai, sizeof(sme->ms->gmm.ra)); rc = osmo_fsm_inst_dispatch(sme->ms_fsm.fi, GPRS_SM_MS_EV_RX_GMM_ESTABLISH_CNF, NULL); } else { rc = osmo_fsm_inst_dispatch(sme->ms_fsm.fi, GPRS_SM_MS_EV_RX_GMM_ESTABLISH_REJ, NULL); } return rc; } /* TS 24.007 9.5.1.2 GMMSM-RELEASE-IND */ static int gprs_sm_prim_handle_gmmsm_release_ind(struct osmo_gprs_gmm_prim *gmm_prim) { struct osmo_gprs_gmm_gmmsm_prim *gmmsm = &gmm_prim->gmmsm; struct gprs_sm_entity *sme; int rc; sme = gprs_sm_find_sme_by_sess_id(gmmsm->sess_id); if (!sme) { LOGSM(LOGL_ERROR, "Rx GMMSM-RELEASE.ind for non existing SM Entity sess_id=%u\n", gmmsm->sess_id); return -EINVAL; } rc = osmo_fsm_inst_dispatch(sme->ms_fsm.fi, GPRS_SM_MS_EV_GMM_RELEASE_IND, NULL); return rc; } /* TS 24.007 9.5.1.6 GMMSM-UNITDATA-IND */ static int gprs_sm_prim_handle_gmmsm_unitdata_ind(struct osmo_gprs_gmm_prim *gmm_prim) { struct osmo_gprs_gmm_gmmsm_prim *gmmsm = &gmm_prim->gmmsm; struct gprs_sm_entity *sme; int rc; sme = gprs_sm_find_sme_by_sess_id(gmmsm->sess_id); if (!sme) { LOGSM(LOGL_ERROR, "Rx GMMSM-UNITDATA.ind for non existing SM Entity\n"); return -EINVAL; } rc = gprs_sm_rx(sme, (struct gsm48_hdr *)gmm_prim->gmmsm.unitdata_ind.smpdu, gmm_prim->gmmsm.unitdata_ind.smpdu_len); return rc; } /* Osmocom specific, GMMSM-MODIFY-IND */ static int gprs_sm_prim_handle_gmmsm_modify_ind(struct osmo_gprs_gmm_prim *gmm_prim) { struct osmo_gprs_gmm_gmmsm_prim *gmmsm = &gmm_prim->gmmsm; struct gprs_sm_entity *sme; struct gprs_sm_ms *ms; int rc = 0; sme = gprs_sm_find_sme_by_sess_id(gmmsm->sess_id); if (!sme) { LOGSM(LOGL_ERROR, "Rx GMMSM-MODIFY.ind for non existing SM Entity\n"); return -EINVAL; } ms = sme->ms; /* Update allocated PTMSI: */ if (gmm_prim->gmmsm.modify_ind.allocated_ptmsi != GSM_RESERVED_TMSI) ms->gmm.ptmsi = gmm_prim->gmmsm.modify_ind.allocated_ptmsi; ms->gmm.ptmsi_sig = gmm_prim->gmmsm.modify_ind.allocated_ptmsi_sig; /* Update allocated TLLI: */ ms->gmm.tlli = gmm_prim->gmmsm.modify_ind.allocated_tlli; /* Update the current RAI: */ memcpy(&ms->gmm.ra, &gmm_prim->gmmsm.modify_ind.rai, sizeof(ms->gmm.ra)); if (gmm_prim->gmmsm.modify_ind.pdp_ctx_status_present) gprs_sm_handle_ie_pdp_ctx_status(ms, gmm_prim->gmmsm.modify_ind.pdp_ctx_status); /* Note: sme may be freed here, it needs to be looked up again: * sme = gprs_sm_find_sme_by_sess_id(gmmsm->sess_id); */ /* TODO: Handle gmm_prim->gmmsm.modify_ind.rx_npdu_numbers_list * Submit SNSM-SEQUENCE-IND, see TS 24.007 "C.16(cont’d) Routing Area Update, Inter SGSN" */ return rc; } static int gprs_sm_prim_handle_gmmsm(struct osmo_gprs_gmm_prim *gmm_prim) { int rc = 0; switch (OSMO_PRIM_HDR(&gmm_prim->oph)) { case OSMO_PRIM(OSMO_GPRS_GMM_GMMSM_ESTABLISH, PRIM_OP_CONFIRM): rc = gprs_sm_prim_handle_gmmsm_establish_cnf(gmm_prim); break; case OSMO_PRIM(OSMO_GPRS_GMM_GMMSM_RELEASE, PRIM_OP_INDICATION): rc = gprs_sm_prim_handle_gmmsm_release_ind(gmm_prim); break; case OSMO_PRIM(OSMO_GPRS_GMM_GMMSM_UNITDATA, PRIM_OP_INDICATION): rc = gprs_sm_prim_handle_gmmsm_unitdata_ind(gmm_prim); break; case OSMO_PRIM(OSMO_GPRS_GMM_GMMSM_MODIFY, PRIM_OP_INDICATION): rc = gprs_sm_prim_handle_gmmsm_modify_ind(gmm_prim); break; default: rc = gprs_sm_prim_handle_gmm_unsupported(gmm_prim); rc = 1; break; } return rc; } /* SM lower layers (GMM) push SM primitive up to SM layer: */ int osmo_gprs_sm_prim_gmm_lower_up(struct osmo_gprs_gmm_prim *gmm_prim) { OSMO_ASSERT(g_sm_ctx); OSMO_ASSERT(gmm_prim); struct msgb *msg = gmm_prim->oph.msg; int rc; LOGSM(LOGL_INFO, "Rx from lower layers: %s\n", osmo_gprs_gmm_prim_name(gmm_prim)); switch (gmm_prim->oph.sap) { case OSMO_GPRS_GMM_SAP_GMMSM: rc = gprs_sm_prim_handle_gmmsm(gmm_prim); break; default: rc = gprs_sm_prim_handle_gmm_unsupported(gmm_prim); rc = 1; } /* Special return value '1' means: do not free */ if (rc != 1) msgb_free(msg); else rc = 0; return rc; }