aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorNeels Hofmeyr <nhofmeyr@sysmocom.de>2022-01-13 23:18:02 +0100
committerNeels Hofmeyr <nhofmeyr@sysmocom.de>2023-03-18 03:05:34 +0100
commitbd5f8e900b030f6857ae6e7e3c4421dcd2d2be9d (patch)
tree5fb38addd1f28fdb1a7464660d773b63e04b0003 /src
parent11a746acd3de12e8b067283f977c6f7eaeb3ada1 (diff)
do CN CRCX first
In order to send the MSC's RTP endpoint IP address+port in the initial SDP, move the MGCP CRCX up to an earlier point in the sequence of establishing a voice call. Update the voice call sequence chart to show the effects. Though the semantic change is rather simple, the patch is rather huge -- things have to happen in a different order, and async waits have to happen at different times. The new codec filter helps to carry codec resolution information across the newly arranged code paths. Related: SYS#5066 Change-Id: Ie433db1ba0c46d4b97538a969233c155cefac21c
Diffstat (limited to 'src')
-rw-r--r--src/libmsc/gsm_04_08_cc.c167
-rw-r--r--src/libmsc/msc_a.c152
2 files changed, 249 insertions, 70 deletions
diff --git a/src/libmsc/gsm_04_08_cc.c b/src/libmsc/gsm_04_08_cc.c
index dd2c77654..3154c2ab8 100644
--- a/src/libmsc/gsm_04_08_cc.c
+++ b/src/libmsc/gsm_04_08_cc.c
@@ -571,8 +571,6 @@ static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg)
unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
struct tlv_parsed tp;
struct gsm_mncc setup;
- struct sdp_msg *sdp;
- int rc;
gsm48_start_guard_timer(trans);
@@ -597,6 +595,8 @@ static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg)
msgb_free(gcr_msg);
}
+ OSMO_ASSERT(trans->msc_a);
+
tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
/* emergency setup is identified by msg_type */
if (msg_type == GSM48_MT_CC_EMERG_SETUP) {
@@ -666,8 +666,6 @@ static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg)
TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1);
}
- new_cc_state(trans, GSM_CSTATE_INITIATED);
-
/* MO call leg starting, gather all codec information so far known: */
codec_filter_init(&trans->cc.codecs);
codec_filter_set_ran(&trans->cc.codecs, trans->msc_a->c.ran->type);
@@ -682,25 +680,66 @@ static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg)
rate_ctr_inc(rate_ctr_group_get_ctr(trans->net->msc_ctrs, MSC_CTR_CALL_MO_SETUP));
- /* FUTURE: so far this is composing SDP although the RTP address is not established yet (sending 0.0.0.0:0).
- * Subsequent patch 'do CN CRCX first' (Ie433db1ba0c46d4b97538a969233c155cefac21c) changes the ordering so that
- * the CN CRCX is completed before dispatching CC SETUP to remote, so that a valid RTP address is set. */
+ new_cc_state(trans, GSM_CSTATE_INITIATED);
+
+ /* To complete the MNCC_SETUP_IND, we need to provide an RTP address and port. First instruct the MGW to create
+ * a CN-side RTP conn, and continue with MNCC_SETUP_IND once that is done. Leave trans.cc in GSM_CSTATE_NULL and
+ * note down the msg_type to indicate that we indeed composed an MNCC_SETUP_IND for later. */
+ setup.msg_type = MNCC_SETUP_IND;
+ trans->cc.msg = setup;
+ return msc_a_try_call_assignment(trans);
+ /* continue in gsm48_cc_rx_setup_cn_local_rtp_port_known() */
+}
+
+/* Callback for MNCC_SETUP_IND waiting for the core network RTP port to be established by the MGW (via msc_a) */
+void gsm48_cc_rx_setup_cn_local_rtp_port_known(struct gsm_trans *trans)
+{
+ struct msc_a *msc_a = trans->msc_a;
+ struct gsm_mncc setup = trans->cc.msg;
+ struct osmo_sockaddr_str *rtp_cn_local;
+ struct sdp_msg *sdp;
+ int rc;
+
+ if (trans->cc.state != GSM_CSTATE_INITIATED
+ || setup.msg_type != MNCC_SETUP_IND) {
+ LOG_TRANS(trans, LOGL_ERROR,
+ "Unexpected CC state. Expected GSM_CSTATE_INITIATED and a buffered MNCC_SETUP_IND message,"
+ " found CC state %d and msg_type %s\n",
+ trans->cc.state, get_mncc_name(setup.msg_type));
+ trans->callref = 0;
+ trans_free(trans);
+ return;
+ }
+
+ if (!msc_a) {
+ LOG_TRANS(trans, LOGL_ERROR, "No connection for CC trans\n");
+ trans->callref = 0;
+ trans_free(trans);
+ return;
+ }
+
+ /* 'setup' above has taken the value of trans->cc.msg, we can now clear that. */
+ trans->cc.msg = (struct gsm_mncc){};
+
+ /* Insert the CN side RTP port now available into SDP and compose SDP string */
+ rtp_cn_local = call_leg_local_ip(msc_a->cc.call_leg, RTP_TO_CN);
+ if (!osmo_sockaddr_str_is_nonzero(rtp_cn_local)) {
+ LOG_TRANS(trans, LOGL_ERROR, "Cannot compose SDP for MNCC_SETUP_IND: no RTP set up for the CN side\n");
+ trans_free(trans);
+ return;
+ }
+ codec_filter_set_local_rtp(&trans->cc.codecs, rtp_cn_local);
sdp = trans->cc.codecs.result.audio_codecs.count ? &trans->cc.codecs.result : NULL;
rc = sdp_msg_to_sdp_str_buf(setup.sdp, sizeof(setup.sdp), sdp);
if (rc >= sizeof(setup.sdp)) {
LOG_TRANS(trans, LOGL_ERROR, "MNCC_SETUP_IND: SDP too long (%d > %zu bytes)\n", rc, sizeof(setup.sdp));
trans_free(trans);
- return -EINVAL;
+ return;
}
/* indicate setup to MNCC */
mncc_recvmsg(trans->net, trans, MNCC_SETUP_IND, &setup);
-
- /* MNCC code will modify the channel asynchronously, we should
- * ipaccess-bind only after the modification has been made to the
- * lchan->tch_mode */
- return 0;
}
static void rx_mncc_sdp(struct gsm_trans *trans, uint32_t mncc_msg_type, const char *sdp)
@@ -903,8 +942,6 @@ static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg)
/* IMSI of called subscriber */
OSMO_STRLCPY_ARRAY(call_conf.imsi, trans->vsub->imsi);
- new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
-
/* Assign call (if not done yet) */
rc = msc_a_try_call_assignment(trans);
@@ -913,8 +950,48 @@ static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg)
if (rc)
return rc;
- return mncc_recvmsg(trans->net, trans, MNCC_CALL_CONF_IND,
- &call_conf);
+ /* Directly ack with MNCC_CALL_CONF_IND, not yet containing SDP or RTP IP:port information. */
+ new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
+ return mncc_recvmsg(trans->net, trans, MNCC_CALL_CONF_IND, &call_conf);
+}
+
+static int mncc_recv_rtp(struct gsm_network *net, struct gsm_trans *trans, uint32_t callref,
+ int cmd, struct osmo_sockaddr_str *rtp_addr, uint32_t payload_type,
+ uint32_t payload_msg_type, const struct sdp_msg *sdp);
+
+static int gsm48_cc_mt_rtp_port_and_codec_known(struct gsm_trans *trans)
+{
+ struct msc_a *msc_a = trans->msc_a;
+ struct osmo_sockaddr_str *rtp_cn_local;
+ struct gsm_mncc_rtp;
+
+ if (!msc_a) {
+ LOG_TRANS(trans, LOGL_ERROR, "No connection for CC trans\n");
+ trans->callref = 0;
+ trans_free(trans);
+ return -EINVAL;
+ }
+
+ /* Insert the CN side RTP port now available into SDP */
+ rtp_cn_local = call_leg_local_ip(msc_a->cc.call_leg, RTP_TO_CN);
+ if (!rtp_cn_local) {
+ LOG_TRANS(trans, LOGL_ERROR, "Cannot compose SDP for MNCC_RTP_CREATE: no RTP set up for the CN side\n");
+ trans_free(trans);
+ return -EINVAL;
+ }
+ codec_filter_set_local_rtp(&trans->cc.codecs, rtp_cn_local);
+
+ codec_filter_run(&trans->cc.codecs);
+ LOG_TRANS(trans, LOGL_DEBUG, "codecs: %s\n", codec_filter_to_str(&trans->cc.codecs));
+
+ /* If we haven't completed Assignment yet, don't sent MNCC_RTP_CREATE */
+ if (!sdp_audio_codec_is_set(&trans->cc.codecs.assignment)) {
+ LOG_TRANS(trans, LOGL_DEBUG, "no codec confirmed by Assignment yet\n");
+ return 0;
+ }
+
+ return mncc_recv_rtp(msc_a_net(msc_a), trans, trans->callref, MNCC_RTP_CREATE, rtp_cn_local, 0, 0,
+ &trans->cc.codecs.result);
}
static int gsm48_cc_tx_call_proc_and_assign(struct gsm_trans *trans, void *arg)
@@ -1895,6 +1972,62 @@ static int tch_rtp_create(struct gsm_network *net, const struct gsm_mncc_rtp *rt
return msc_a_try_call_assignment(trans);
}
+int cc_on_cn_local_rtp_port_known(struct gsm_trans *cc_trans)
+{
+ /* Depending on MO or MT call, dispatch the event differently */
+ switch (cc_trans->cc.state) {
+ case GSM_CSTATE_INITIATED:
+ if (cc_trans->cc.msg.msg_type != MNCC_SETUP_IND) {
+ LOG_TRANS(cc_trans, LOGL_ERROR, "Assuming MO call, expected MNCC_SETUP_IND to be prepared\n");
+ return -EINVAL;
+ }
+ /* This is the MO call leg, waiting for a CN RTP be able to send initial MNCC_SETUP_IND. */
+ gsm48_cc_rx_setup_cn_local_rtp_port_known(cc_trans);
+ return 0;
+
+ case GSM_CSTATE_MO_TERM_CALL_CONF:
+ /* This is the MT call leg, waiting for a CN RTP to be able to send MNCC_CALL_CONF_IND. */
+ return gsm48_cc_mt_rtp_port_and_codec_known(cc_trans);
+
+ default:
+ LOG_TRANS(cc_trans, LOGL_ERROR, "CN RTP address available, but in unexpected state %d\n",
+ cc_trans->cc.state);
+ return -EINVAL;
+ }
+}
+
+int cc_on_assignment_done(struct gsm_trans *trans)
+{
+ struct msc_a *msc_a = trans->msc_a;
+
+ switch (trans->cc.state) {
+ case GSM_CSTATE_INITIATED:
+ case GSM_CSTATE_MO_CALL_PROC:
+ /* MO call */
+ break;
+
+ case GSM_CSTATE_CALL_RECEIVED:
+ case GSM_CSTATE_MO_TERM_CALL_CONF:
+ /* MT call */
+ break;
+
+ case GSM_CSTATE_ACTIVE:
+ /* already active. MNCC finished before Abis completed the Assignment. */
+ break;
+
+ default:
+ LOG_TRANS(trans, LOGL_ERROR, "Assignment done in unexpected CC state: %d\n", trans->cc.state);
+ return -EINVAL;
+ }
+
+ if (!call_leg_local_ip(msc_a->cc.call_leg, RTP_TO_CN)) {
+ LOG_TRANS(trans, LOGL_DEBUG,
+ "Assignment complete, but still waiting for the CRCX OK on the CN side RTP\n");
+ return 0;
+ }
+ return gsm48_tch_rtp_create(trans);
+}
+
/* Trigger TCH_RTP_CREATE acknowledgement */
int gsm48_tch_rtp_create(struct gsm_trans *trans)
{
diff --git a/src/libmsc/msc_a.c b/src/libmsc/msc_a.c
index a7424e03c..c022ee2d4 100644
--- a/src/libmsc/msc_a.c
+++ b/src/libmsc/msc_a.c
@@ -564,6 +564,66 @@ static void msc_a_fsm_authenticated(struct osmo_fsm_inst *fi, uint32_t event, vo
}
}
+static struct call_leg *msc_a_ensure_call_leg(struct msc_a *msc_a, struct gsm_trans *for_cc_trans)
+{
+ struct call_leg *cl = msc_a->cc.call_leg;
+ struct gsm_network *net = msc_a_net(msc_a);
+
+ /* Ensure that events about RTP endpoints coming from the msc_a->cc.call_leg know which gsm_trans to abort on
+ * error */
+ if (!msc_a->cc.active_trans)
+ msc_a->cc.active_trans = for_cc_trans;
+ if (msc_a->cc.active_trans != for_cc_trans) {
+ LOG_TRANS(for_cc_trans, LOGL_ERROR,
+ "Cannot create call leg, another trans is already active for this conn\n");
+ return NULL;
+ }
+
+ if (!cl) {
+ cl = msc_a->cc.call_leg = call_leg_alloc(msc_a->c.fi,
+ MSC_EV_CALL_LEG_TERM,
+ MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE,
+ MSC_EV_CALL_LEG_RTP_COMPLETE);
+ OSMO_ASSERT(cl);
+
+ if (net->use_osmux != OSMUX_USAGE_OFF) {
+ struct msc_i *msc_i = msc_a_msc_i(msc_a);
+ if (msc_i->c.remote_to) {
+ /* TODO: investigate what to do in this case */
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Osmux not yet supported for inter-MSC");
+ } else {
+ cl->ran_peer_supports_osmux = msc_i->ran_conn->ran_peer->remote_supports_osmux;
+ }
+ }
+
+ }
+ return cl;
+}
+
+int msc_a_ensure_cn_local_rtp(struct msc_a *msc_a, struct gsm_trans *cc_trans)
+{
+ struct call_leg *cl;
+ struct rtp_stream *rtp_to_ran;
+
+ cl = msc_a_ensure_call_leg(msc_a, cc_trans);
+ if (!cl)
+ return -EINVAL;
+ rtp_to_ran = cl->rtp[RTP_TO_RAN];
+
+ if (call_leg_local_ip(cl, RTP_TO_CN)) {
+ /* Already has an RTP address and port towards the CN, continue right away. */
+ return osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, cl->rtp[RTP_TO_CN]);
+ }
+
+ /* No CN RTP address available yet, ask the MGW to create one.
+ * Set a codec to be used: if Assignment on the RAN side is already done, take the same codec as the RTP_TO_RAN.
+ * If no RAN side RTP is established, try to guess a preliminary codec from SDP -- before Assignment, picking a
+ * codec from the SDP is more politeness/avoiding confusion than necessity. The actual codec to be used would be
+ * determined later. If no codec could be determined, pass none for the time being. */
+ return call_leg_ensure_ci(cl, RTP_TO_CN, cc_trans->callref, cc_trans,
+ rtp_to_ran->codecs_known ? &rtp_to_ran->codecs : NULL, NULL);
+}
+
/* The MGW has given us a local IP address for the RAN side. Ready to start the Assignment of a voice channel. */
static void msc_a_call_leg_ran_local_addr_available(struct msc_a *msc_a)
{
@@ -617,15 +677,6 @@ static void msc_a_call_leg_ran_local_addr_available(struct msc_a *msc_a)
}
}
-static void msc_a_call_leg_cn_local_addr_available(struct msc_a *msc_a, struct gsm_trans *cc_trans)
-{
- if (gsm48_tch_rtp_create(cc_trans)) {
- LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot inform MNCC of RTP address\n");
- trans_free(cc_trans);
- return;
- }
-}
-
static struct gsm_trans *find_waiting_call(struct msc_a *msc_a)
{
struct gsm_trans *trans;
@@ -719,10 +770,7 @@ static void msc_a_fsm_communicating(struct osmo_fsm_inst *fi, uint32_t event, vo
msc_a_call_leg_ran_local_addr_available(msc_a);
return;
case RTP_TO_CN:
- /* The rtp_stream has gotten the new RTP address and port from the MGW. Also update the codecs
- * filter result to convey this RTP address and port towards the remote call leg. */
- codec_filter_set_local_rtp(&msc_a->cc.active_trans->cc.codecs, &rtps->local);
- msc_a_call_leg_cn_local_addr_available(msc_a, rtps->for_trans);
+ cc_on_cn_local_rtp_port_known(rtps->for_trans);
return;
default:
LOG_MSC_A(msc_a, LOGL_ERROR, "Invalid data for %s\n", osmo_fsm_event_name(fi->fsm, event));
@@ -1341,7 +1389,7 @@ static void msc_a_up_call_assignment_complete(struct msc_a *msc_a, const struct
struct rtp_stream *rtps_to_ran = msc_a->cc.call_leg ? msc_a->cc.call_leg->rtp[RTP_TO_RAN] : NULL;
const struct gsm0808_speech_codec *codec_if_known = ac->assignment_complete.codec_present ?
&ac->assignment_complete.codec : NULL;
- const struct codec_mapping *codec_cn = NULL;
+ const struct codec_mapping *codec_cn;
if (!rtps_to_ran) {
LOG_MSC_A(msc_a, LOGL_ERROR, "Rx Assignment Complete, but no RTP stream is set up\n");
@@ -1400,23 +1448,19 @@ static void msc_a_up_call_assignment_complete(struct msc_a *msc_a, const struct
if (rtps_to_ran->use_osmux)
rtp_stream_set_remote_osmux_cid(rtps_to_ran,
ac->assignment_complete.osmux_cid);
-
rtp_stream_commit(rtps_to_ran);
/* Remember the Codec List (BSS Supported) */
if (ac->assignment_complete.codec_list_bss_supported)
codec_filter_set_bss(&cc_trans->cc.codecs, ac->assignment_complete.codec_list_bss_supported);
- /* Setup CN side endpoint CI:
- * Now that
- * - the first CI has been created and a definitive endpoint name is assigned to the call_leg's MGW
- * endpoint,
- * - the Assignment has chosen a speech codec
- * go on to create the CN side RTP stream's CI. */
codec_filter_run(&cc_trans->cc.codecs);
- if (call_leg_ensure_ci(msc_a->cc.call_leg, RTP_TO_CN, cc_trans->callref, cc_trans,
- &cc_trans->cc.codecs.result.audio_codecs, NULL)) {
- LOG_MSC_A_CAT(msc_a, DCC, LOGL_ERROR, "Error creating MGW CI towards CN\n");
+ LOG_TRANS(cc_trans, LOGL_INFO, "Assignment Complete: RAN: %s, CN: %s\n",
+ sdp_audio_codecs_to_str(&rtps_to_ran->codecs),
+ sdp_audio_codecs_to_str(&cc_trans->cc.codecs.result.audio_codecs));
+
+ if (cc_on_assignment_done(cc_trans)) {
+ /* If an error occurred, it was logged in cc_assignment_done() */
call_leg_release(msc_a->cc.call_leg);
return;
}
@@ -1807,10 +1851,9 @@ int msc_tx_common_id(struct msc_a *msc_a, enum msc_role to_role)
static int msc_a_start_assignment(struct msc_a *msc_a, struct gsm_trans *cc_trans)
{
- struct call_leg *cl = msc_a->cc.call_leg;
- struct msc_i *msc_i = msc_a_msc_i(msc_a);
- struct gsm_network *net = msc_a_net(msc_a);
- struct sdp_audio_codecs *codecs;
+ struct call_leg *cl;
+ bool cn_rtp_available;
+ bool ran_rtp_available;
OSMO_ASSERT(!msc_a->cc.active_trans);
msc_a->cc.active_trans = cc_trans;
@@ -1818,36 +1861,39 @@ static int msc_a_start_assignment(struct msc_a *msc_a, struct gsm_trans *cc_tran
cc_trans->cc.codecs.assignment = (struct sdp_audio_codec){};
OSMO_ASSERT(cc_trans && cc_trans->type == TRANS_CC);
+ cl = msc_a_ensure_call_leg(msc_a, cc_trans);
+ if (!cl)
+ return -EINVAL;
- if (!cl) {
- cl = msc_a->cc.call_leg = call_leg_alloc(msc_a->c.fi,
- MSC_EV_CALL_LEG_TERM,
- MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE,
- MSC_EV_CALL_LEG_RTP_COMPLETE);
- OSMO_ASSERT(cl);
- }
+ /* See if we can set a preliminary codec. If not, pass none for the time being. */
+ codec_filter_run(&cc_trans->cc.codecs);
- if (net->use_osmux != OSMUX_USAGE_OFF) {
- msc_i = msc_a_msc_i(msc_a);
- if (msc_i->c.remote_to) {
- /* TODO: investigate what to do in this case */
- LOG_MSC_A(msc_a, LOGL_ERROR, "Osmux not yet supported for inter-MSC");
- } else {
- cl->ran_peer_supports_osmux = msc_i->ran_conn->ran_peer->remote_supports_osmux;
- }
+ cn_rtp_available = call_leg_local_ip(cl, RTP_TO_CN);
+ ran_rtp_available = call_leg_local_ip(cl, RTP_TO_RAN);
+
+ /* Set up RTP ports for both RAN and CN side. Even though we ask for both at the same time, the
+ * osmo_mgcpc_ep_fsm automagically waits for the first CRCX to complete before firing the second CRCX. The one
+ * issued first here will also be the first CRCX sent to the MGW. Usually both still need to be set up. */
+ if (!cn_rtp_available)
+ call_leg_ensure_ci(cl, RTP_TO_CN, cc_trans->callref, cc_trans,
+ &cc_trans->cc.codecs.result.audio_codecs, NULL);
+ if (!ran_rtp_available) {
+ struct sdp_audio_codecs *codecs;
+ if (msc_a->c.ran->force_mgw_codecs_to_ran.count)
+ codecs = &msc_a->c.ran->force_mgw_codecs_to_ran;
+ else
+ codecs = &cc_trans->cc.codecs.result.audio_codecs;
+ return call_leg_ensure_ci(cl, RTP_TO_RAN, cc_trans->callref, cc_trans, codecs, NULL);
}
- /* Make sure an MGW endpoint towards RAN is present. If it is already set up, "skip" to
- * MSC_EV_CALL_LEG_LOCAL_ADDR_AVAILABLE immediately. If not, set it up. */
- if (call_leg_local_ip(cl, RTP_TO_RAN))
+ /* Should these already be set up, immediately continue by retriggering the events signalling that the RTP
+ * ports are available. The ordering is: first CN, then RAN. */
+ if (cn_rtp_available && ran_rtp_available)
return osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, cl->rtp[RTP_TO_RAN]);
-
- codec_filter_run(&cc_trans->cc.codecs);
- if (msc_a->c.ran->force_mgw_codecs_to_ran.count)
- codecs = &msc_a->c.ran->force_mgw_codecs_to_ran;
- else
- codecs = &cc_trans->cc.codecs.result.audio_codecs;
- return call_leg_ensure_ci(msc_a->cc.call_leg, RTP_TO_RAN, cc_trans->callref, cc_trans, codecs, NULL);
+ else if (cn_rtp_available)
+ return osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, cl->rtp[RTP_TO_CN]);
+ /* Otherwise wait for MGCP response and continue from there. */
+ return 0;
}
int msc_a_try_call_assignment(struct gsm_trans *cc_trans)