<p>neels has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.osmocom.org/c/osmo-bsc/+/23943">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">lchan and assignment FSMs: make Channel Mode Modify more sane<br><br>The Channel Mode Modify procedure is currently implemented for changing<br>a TCH lchan from signalling to voice mode. For that, however, it is<br>re-using (abusing) the channel activation structs and state transitions,<br>and thus always implies activating a voice stream when the mode<br>modification is done.<br><br>I will add a Channel Mode Modify to enable VAMOS mode soon, so I require<br>separate structs and state transitions which also work on an lchan that<br>already has a voice stream established: a struct lchan_modify_info and<br>LCHAN_EV_REQUEST_MODE_MODIFY, and dedicated assignment FSM state<br>ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED.<br><br>For the part where a Channel Mode Modify enables a voice stream after<br>switching from signalling to speech mode, still use the channel<br>activation code path, but only once the mode modification is done.<br><br>General improvements:<br>- To ask for a mode modification, emit an FSM event that ensures a mode<br> modify only happens when the lchan state allows it.<br>- The new lchan_modify_info struct reflects only those parts that have<br> an effect during a mode modification (before the lchan_activate_info<br> was fully populated, many values not having an effect).<br>- More accurate logging, indicating "Mode Modify" instead of "Channel<br> Activation"<br><br>A TTCN3 test for the Channel Mode Modify procedure is added in<br>Idf4efaed986de0bbd2b663313e837352cc139f0f, and the test passes both<br>before and after this patch is applied.<br><br>Related: SYS#4895<br>Change-Id: I4986844f839b1c9672c61d916eb3d33d0042d747<br>---<br>M doc/assignment-fsm.dot<br>M include/osmocom/bsc/assignment_fsm.h<br>M include/osmocom/bsc/gsm_data.h<br>M include/osmocom/bsc/lchan_fsm.h<br>M src/osmo-bsc/assignment_fsm.c<br>M src/osmo-bsc/lchan_fsm.c<br>6 files changed, 212 insertions(+), 67 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.osmocom.org:29418/osmo-bsc refs/changes/43/23943/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/doc/assignment-fsm.dot b/doc/assignment-fsm.dot</span><br><span>index c218153..336a411 100644</span><br><span>--- a/doc/assignment-fsm.dot</span><br><span>+++ b/doc/assignment-fsm.dot</span><br><span>@@ -12,6 +12,7 @@</span><br><span> gscon2 [label="conn FSM",shape=box3d]</span><br><span> lchan [label="lchan FSM\n(new lchan)",shape=box3d]</span><br><span> old_lchan [label="old lchan",shape=box3d]</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan2 [label="lchan FSM",shape=box3d]</span><br><span> </span><br><span> bssap [label="osmo_bsc_bssap.c",shape=box]</span><br><span> </span><br><span>@@ -22,7 +23,6 @@</span><br><span> bssap -> gscon [label="GSCON_EV_ASSIGNMENT_START\ndata=struct assignment_request",style=dotted]</span><br><span> </span><br><span> gscon -> WAIT_LCHAN_ACTIVE [label="assignment_fsm_start()",style=dotted]</span><br><span style="color: hsl(0, 100%, 40%);">- gscon -> WAIT_LCHAN_ESTABLISHED [label="assignment_fsm_start()\n(mode modify)",style=dotted]</span><br><span> WAIT_LCHAN_ACTIVE -> lchan [label="lchan_activate()\nFOR_ASSIGNMENT",style=dotted]</span><br><span> lchan -> WAIT_LCHAN_ACTIVE [label="ASSIGNMENT_EV_\nLCHAN_\nACTIVE,ERROR",style=dotted]</span><br><span> lchan -> WAIT_LCHAN_ESTABLISHED [label="ASSIGNMENT_EV_\nLCHAN_\nESTABLISHED,ERROR",style=dotted]</span><br><span>@@ -40,4 +40,10 @@</span><br><span> WAIT_MGW_ENDPOINT_TO_MSC -> gscon2 [label="gscon_connect_\nmgw_to_msc()",style=dotted]</span><br><span> gscon2 -> WAIT_MGW_ENDPOINT_TO_MSC [label="ASSIGNMENT_EV_\nMSC_MGW_OK",style=dotted]</span><br><span> terminate -> gscon2 [label="GSCON_EV_\nASSIGNMENT_END",style=dotted]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ WAIT_LCHAN_ACTIVE -> WAIT_LCHAN_MODIFIED [label="assignment_fsm_start()\n(mode modify)"]</span><br><span style="color: hsl(120, 100%, 40%);">+ WAIT_LCHAN_MODIFIED -> lchan2 [label="lchan_mode_modify()\nFOR_ASSIGNMENT",style=dotted]</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan2 -> WAIT_LCHAN_MODIFIED [label="ASSIGNMENT_EV_\nLCHAN_\nMODIFIED,ERROR",style=dotted]</span><br><span style="color: hsl(120, 100%, 40%);">+ WAIT_LCHAN_MODIFIED -> WAIT_MGW_ENDPOINT_TO_MSC [label="needs\nvoice\nstream"]</span><br><span style="color: hsl(120, 100%, 40%);">+ WAIT_LCHAN_MODIFIED -> terminate [label="no change\nin voice\nstream"]</span><br><span> }</span><br><span>diff --git a/include/osmocom/bsc/assignment_fsm.h b/include/osmocom/bsc/assignment_fsm.h</span><br><span>index 156da42..b4af335 100644</span><br><span>--- a/include/osmocom/bsc/assignment_fsm.h</span><br><span>+++ b/include/osmocom/bsc/assignment_fsm.h</span><br><span>@@ -24,11 +24,13 @@</span><br><span> ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE,</span><br><span> ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED,</span><br><span> ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC,</span><br><span style="color: hsl(120, 100%, 40%);">+ ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED,</span><br><span> };</span><br><span> </span><br><span> enum assignment_fsm_event {</span><br><span> ASSIGNMENT_EV_LCHAN_ACTIVE,</span><br><span> ASSIGNMENT_EV_LCHAN_ESTABLISHED,</span><br><span style="color: hsl(120, 100%, 40%);">+ ASSIGNMENT_EV_LCHAN_MODIFIED,</span><br><span> ASSIGNMENT_EV_LCHAN_ERROR,</span><br><span> ASSIGNMENT_EV_MSC_MGW_OK,</span><br><span> ASSIGNMENT_EV_MSC_MGW_FAIL,</span><br><span>diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h</span><br><span>index 9aecd2c..23f44c5 100644</span><br><span>--- a/include/osmocom/bsc/gsm_data.h</span><br><span>+++ b/include/osmocom/bsc/gsm_data.h</span><br><span>@@ -564,6 +564,7 @@</span><br><span> FOR_ASSIGNMENT,</span><br><span> FOR_HANDOVER,</span><br><span> FOR_VTY,</span><br><span style="color: hsl(120, 100%, 40%);">+ FOR_MODE_MODIFY_RTP,</span><br><span> };</span><br><span> </span><br><span> extern const struct value_string lchan_activate_mode_names[];</span><br><span>@@ -589,6 +590,15 @@</span><br><span> uint8_t ta;</span><br><span> };</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+struct lchan_modify_info {</span><br><span style="color: hsl(120, 100%, 40%);">+ enum lchan_activate_mode modify_for;</span><br><span style="color: hsl(120, 100%, 40%);">+ enum gsm48_chan_mode chan_mode;</span><br><span style="color: hsl(120, 100%, 40%);">+ bool requires_voice_stream;</span><br><span style="color: hsl(120, 100%, 40%);">+ uint16_t msc_assigned_cic;</span><br><span style="color: hsl(120, 100%, 40%);">+ /* AMR config */</span><br><span style="color: hsl(120, 100%, 40%);">+ uint16_t s15_s0;</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> struct gsm_lchan {</span><br><span> /* The TS that we're part of */</span><br><span> struct gsm_bts_trx_ts *ts;</span><br><span>@@ -614,6 +624,11 @@</span><br><span> } activate;</span><br><span> </span><br><span> struct {</span><br><span style="color: hsl(120, 100%, 40%);">+ struct lchan_modify_info info;</span><br><span style="color: hsl(120, 100%, 40%);">+ bool concluded;</span><br><span style="color: hsl(120, 100%, 40%);">+ } modify;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ struct {</span><br><span> /* If an event to release the lchan comes in while still waiting for responses, just mark this</span><br><span> * flag, so that the lchan will gracefully release at the next sensible junction. */</span><br><span> bool requested;</span><br><span>diff --git a/include/osmocom/bsc/lchan_fsm.h b/include/osmocom/bsc/lchan_fsm.h</span><br><span>index ded7f54..3276f9d 100644</span><br><span>--- a/include/osmocom/bsc/lchan_fsm.h</span><br><span>+++ b/include/osmocom/bsc/lchan_fsm.h</span><br><span>@@ -58,6 +58,7 @@</span><br><span> </span><br><span> void lchan_activate(struct gsm_lchan *lchan, struct lchan_activate_info *info);</span><br><span> void lchan_ready_to_switch_rtp(struct gsm_lchan *lchan);</span><br><span style="color: hsl(120, 100%, 40%);">+void lchan_mode_modify(struct gsm_lchan *lchan, struct lchan_modify_info *info);</span><br><span> </span><br><span> static inline const char *lchan_state_name(struct gsm_lchan *lchan)</span><br><span> {</span><br><span>diff --git a/src/osmo-bsc/assignment_fsm.c b/src/osmo-bsc/assignment_fsm.c</span><br><span>index faaec53..f1be213 100644</span><br><span>--- a/src/osmo-bsc/assignment_fsm.c</span><br><span>+++ b/src/osmo-bsc/assignment_fsm.c</span><br><span>@@ -241,20 +241,23 @@</span><br><span> static void assignment_success(struct gsm_subscriber_connection *conn)</span><br><span> {</span><br><span> struct gsm_bts *bts;</span><br><span style="color: hsl(120, 100%, 40%);">+ bool lchan_changed = (conn->assignment.new_lchan != NULL);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- /* Take on the new lchan */</span><br><span style="color: hsl(0, 100%, 40%);">- gscon_change_primary_lchan(conn, conn->assignment.new_lchan);</span><br><span style="color: hsl(0, 100%, 40%);">- conn->assignment.new_lchan = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Take on the new lchan. If there only was a Channel Mode Modify, then there is no new lchan to take on. */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (lchan_changed) {</span><br><span style="color: hsl(120, 100%, 40%);">+ gscon_change_primary_lchan(conn, conn->assignment.new_lchan);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- OSMO_ASSERT((bts = conn_get_bts(conn)) != NULL);</span><br><span style="color: hsl(0, 100%, 40%);">- if (is_siemens_bts(bts) && ts_is_tch(conn->lchan->ts)) {</span><br><span style="color: hsl(0, 100%, 40%);">- /* HACK: store the actual Classmark 2 LV from the subscriber and use it here! */</span><br><span style="color: hsl(0, 100%, 40%);">- uint8_t cm2_lv[] = { 0x02, 0x00, 0x00 };</span><br><span style="color: hsl(0, 100%, 40%);">- send_siemens_mrpci(conn->lchan, cm2_lv);</span><br><span style="color: hsl(120, 100%, 40%);">+ OSMO_ASSERT((bts = conn_get_bts(conn)) != NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (is_siemens_bts(bts) && ts_is_tch(conn->lchan->ts)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /* HACK: store the actual Classmark 2 LV from the subscriber and use it here! */</span><br><span style="color: hsl(120, 100%, 40%);">+ uint8_t cm2_lv[] = { 0x02, 0x00, 0x00 };</span><br><span style="color: hsl(120, 100%, 40%);">+ send_siemens_mrpci(conn->lchan, cm2_lv);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* apply LCLS configuration (if any) */</span><br><span style="color: hsl(120, 100%, 40%);">+ lcls_apply_config(conn);</span><br><span> }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">- /* apply LCLS configuration (if any) */</span><br><span style="color: hsl(0, 100%, 40%);">- lcls_apply_config(conn);</span><br><span style="color: hsl(120, 100%, 40%);">+ conn->assignment.new_lchan = NULL;</span><br><span> </span><br><span> send_assignment_complete(conn);</span><br><span> /* If something went wrong during send_assignment_complete(), the fi will be gone from</span><br><span>@@ -267,15 +270,17 @@</span><br><span> return;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- /* Rembered this only for error handling: should assignment fail, assignment_reset() will release</span><br><span style="color: hsl(0, 100%, 40%);">- * the MGW endpoint right away. If successful, the conn continues to use the endpoint. */</span><br><span style="color: hsl(0, 100%, 40%);">- conn->assignment.created_ci_for_msc = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ if (lchan_changed) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Rembered this only for error handling: should assignment fail, assignment_reset() will release</span><br><span style="color: hsl(120, 100%, 40%);">+ * the MGW endpoint right away. If successful, the conn continues to use the endpoint. */</span><br><span style="color: hsl(120, 100%, 40%);">+ conn->assignment.created_ci_for_msc = NULL;</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- /* New RTP information is now accepted */</span><br><span style="color: hsl(0, 100%, 40%);">- conn->user_plane.msc_assigned_cic = conn->assignment.req.msc_assigned_cic;</span><br><span style="color: hsl(0, 100%, 40%);">- osmo_strlcpy(conn->user_plane.msc_assigned_rtp_addr, conn->assignment.req.msc_rtp_addr,</span><br><span style="color: hsl(0, 100%, 40%);">- sizeof(conn->user_plane.msc_assigned_rtp_addr));</span><br><span style="color: hsl(0, 100%, 40%);">- conn->user_plane.msc_assigned_rtp_port = conn->assignment.req.msc_rtp_port;</span><br><span style="color: hsl(120, 100%, 40%);">+ /* New RTP information is now accepted */</span><br><span style="color: hsl(120, 100%, 40%);">+ conn->user_plane.msc_assigned_cic = conn->assignment.req.msc_assigned_cic;</span><br><span style="color: hsl(120, 100%, 40%);">+ osmo_strlcpy(conn->user_plane.msc_assigned_rtp_addr, conn->assignment.req.msc_rtp_addr,</span><br><span style="color: hsl(120, 100%, 40%);">+ sizeof(conn->user_plane.msc_assigned_rtp_addr));</span><br><span style="color: hsl(120, 100%, 40%);">+ conn->user_plane.msc_assigned_rtp_port = conn->assignment.req.msc_rtp_port;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span> </span><br><span> LOG_ASSIGNMENT(conn, LOGL_DEBUG, "Assignment successful\n");</span><br><span> osmo_fsm_inst_term(conn->assignment.fi, OSMO_FSM_TERM_REGULAR, 0);</span><br><span>@@ -285,7 +290,9 @@</span><br><span> </span><br><span> static void assignment_fsm_update_id(struct gsm_subscriber_connection *conn)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">- struct gsm_lchan *new_lchan = conn->assignment.new_lchan;</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Assignment can do a new channel activation, in which case new_lchan points at the new lchan.</span><br><span style="color: hsl(120, 100%, 40%);">+ * Or assignment can Channel Mode Modify the already used lchan, in which case new_lchan == NULL. */</span><br><span style="color: hsl(120, 100%, 40%);">+ struct gsm_lchan *new_lchan = (conn->assignment.new_lchan ? : conn->lchan);</span><br><span> if (!new_lchan) {</span><br><span> osmo_fsm_inst_update_id(conn->assignment.fi, conn->fi->id);</span><br><span> return;</span><br><span>@@ -435,7 +442,8 @@</span><br><span> [CH_RATE_FULL] = "FR",</span><br><span> };</span><br><span> struct osmo_fsm_inst *fi;</span><br><span style="color: hsl(0, 100%, 40%);">- struct lchan_activate_info info;</span><br><span style="color: hsl(120, 100%, 40%);">+ struct lchan_activate_info activ_info;</span><br><span style="color: hsl(120, 100%, 40%);">+ struct lchan_modify_info modif_info;</span><br><span> int i;</span><br><span> </span><br><span> OSMO_ASSERT(conn);</span><br><span>@@ -465,6 +473,8 @@</span><br><span> * LCHAN_EV_REQUEST_MODE_MODIFY in lchan_fsm.c. To not break the lchan, do not even attempt to re-use an lchan</span><br><span> * that already has an RTP stream set up, rather establish a new lchan (that transition is well implemented). */</span><br><span> if (reuse_existing_lchan(conn) && !conn->lchan->fi_rtp) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /* The new lchan is the old lchan, keep new_lchan == NULL. */</span><br><span style="color: hsl(120, 100%, 40%);">+ conn->assignment.new_lchan = NULL;</span><br><span> </span><br><span> /* If the requested mode and the current TCH mode matches up, just send the</span><br><span> * assignment complete directly and be done with the assignment procedure. */</span><br><span>@@ -494,28 +504,17 @@</span><br><span> gsm48_chan_mode_name(conn->lchan->ch_mode_rate.chan_mode),</span><br><span> gsm_lchan_name(conn->lchan));</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- info = (struct lchan_activate_info){</span><br><span style="color: hsl(0, 100%, 40%);">- .activ_for = FOR_ASSIGNMENT,</span><br><span style="color: hsl(0, 100%, 40%);">- .for_conn = conn,</span><br><span style="color: hsl(120, 100%, 40%);">+ modif_info = (struct lchan_modify_info){</span><br><span style="color: hsl(120, 100%, 40%);">+ .modify_for = FOR_ASSIGNMENT,</span><br><span> .chan_mode = conn->lchan->ch_mode_rate.chan_mode,</span><br><span style="color: hsl(0, 100%, 40%);">- .encr = conn->lchan->encr,</span><br><span style="color: hsl(0, 100%, 40%);">- .s15_s0 = conn->lchan->ch_mode_rate.s15_s0,</span><br><span> .requires_voice_stream = conn->assignment.requires_voice_stream,</span><br><span style="color: hsl(120, 100%, 40%);">+ .s15_s0 = conn->lchan->ch_mode_rate.s15_s0,</span><br><span> .msc_assigned_cic = req->msc_assigned_cic,</span><br><span style="color: hsl(0, 100%, 40%);">- .re_use_mgw_endpoint_from_lchan = conn->lchan,</span><br><span style="color: hsl(0, 100%, 40%);">- .ta = conn->lchan->last_ta,</span><br><span style="color: hsl(0, 100%, 40%);">- .ta_known = true,</span><br><span> };</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- osmo_fsm_inst_dispatch(conn->lchan->fi, LCHAN_EV_REQUEST_MODE_MODIFY, &info);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">- /* Since we opted not to allocate a new lchan, the new lchan is still the old lchan. */</span><br><span style="color: hsl(0, 100%, 40%);">- conn->assignment.new_lchan = conn->lchan;</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">- /* Also we need to skip the RR assignment, so we jump forward and wait for the lchan_fsm until it</span><br><span style="color: hsl(0, 100%, 40%);">- * reaches the established state again. */</span><br><span style="color: hsl(0, 100%, 40%);">- assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(120, 100%, 40%);">+ if (assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED))</span><br><span style="color: hsl(120, 100%, 40%);">+ return;</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan_mode_modify(conn->lchan, &modif_info);</span><br><span> return;</span><br><span> }</span><br><span> </span><br><span>@@ -571,7 +570,7 @@</span><br><span> req->use_osmux ? "yes" : "no");</span><br><span> </span><br><span> assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE);</span><br><span style="color: hsl(0, 100%, 40%);">- info = (struct lchan_activate_info){</span><br><span style="color: hsl(120, 100%, 40%);">+ activ_info = (struct lchan_activate_info){</span><br><span> .activ_for = FOR_ASSIGNMENT,</span><br><span> .for_conn = conn,</span><br><span> .chan_mode = conn->lchan->ch_mode_rate.chan_mode,</span><br><span>@@ -583,7 +582,7 @@</span><br><span> .ta = conn->lchan->last_ta,</span><br><span> .ta_known = true,</span><br><span> };</span><br><span style="color: hsl(0, 100%, 40%);">- lchan_activate(conn->assignment.new_lchan, &info);</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan_activate(conn->assignment.new_lchan, &activ_info);</span><br><span> }</span><br><span> </span><br><span> static void assignment_fsm_wait_lchan(struct osmo_fsm_inst *fi, uint32_t event, void *data)</span><br><span>@@ -695,8 +694,10 @@</span><br><span> conn->assignment.req.msc_rtp_addr,</span><br><span> conn->assignment.req.msc_rtp_port);</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ /* Assignment can do a new channel activation, in which case new_lchan points at the new lchan.</span><br><span style="color: hsl(120, 100%, 40%);">+ * Or assignment can Channel Mode Modify the already used lchan, in which case new_lchan == NULL. */</span><br><span> if (!gscon_connect_mgw_to_msc(conn,</span><br><span style="color: hsl(0, 100%, 40%);">- conn->assignment.new_lchan,</span><br><span style="color: hsl(120, 100%, 40%);">+ conn->assignment.new_lchan ? : conn->lchan,</span><br><span> conn->assignment.req.msc_rtp_addr,</span><br><span> conn->assignment.req.msc_rtp_port,</span><br><span> fi,</span><br><span>@@ -742,6 +743,19 @@</span><br><span> }</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static void assignment_fsm_wait_lchan_modified(struct osmo_fsm_inst *fi, uint32_t event, void *data)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ switch (event) {</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ case ASSIGNMENT_EV_LCHAN_MODIFIED:</span><br><span style="color: hsl(120, 100%, 40%);">+ assignment_fsm_post_lchan_established(fi);</span><br><span style="color: hsl(120, 100%, 40%);">+ return;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ default:</span><br><span style="color: hsl(120, 100%, 40%);">+ OSMO_ASSERT(false);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> #define S(x) (1 << (x))</span><br><span> </span><br><span> static const struct osmo_fsm_state assignment_fsm_states[] = {</span><br><span>@@ -754,7 +768,7 @@</span><br><span> .out_state_mask = 0</span><br><span> | S(ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE)</span><br><span> | S(ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE)</span><br><span style="color: hsl(0, 100%, 40%);">- | S(ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED) /* MODE MODIFY */</span><br><span style="color: hsl(120, 100%, 40%);">+ | S(ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED)</span><br><span> ,</span><br><span> },</span><br><span> [ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE] = {</span><br><span>@@ -790,11 +804,22 @@</span><br><span> | S(ASSIGNMENT_EV_MSC_MGW_FAIL)</span><br><span> ,</span><br><span> },</span><br><span style="color: hsl(120, 100%, 40%);">+ [ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED] = {</span><br><span style="color: hsl(120, 100%, 40%);">+ .name = "WAIT_LCHAN_MODIFIED",</span><br><span style="color: hsl(120, 100%, 40%);">+ .action = assignment_fsm_wait_lchan_modified,</span><br><span style="color: hsl(120, 100%, 40%);">+ .in_event_mask = 0</span><br><span style="color: hsl(120, 100%, 40%);">+ | S(ASSIGNMENT_EV_LCHAN_MODIFIED)</span><br><span style="color: hsl(120, 100%, 40%);">+ ,</span><br><span style="color: hsl(120, 100%, 40%);">+ .out_state_mask = 0</span><br><span style="color: hsl(120, 100%, 40%);">+ | S(ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC)</span><br><span style="color: hsl(120, 100%, 40%);">+ ,</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span> };</span><br><span> </span><br><span> static const struct value_string assignment_fsm_event_names[] = {</span><br><span> OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ACTIVE),</span><br><span> OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ESTABLISHED),</span><br><span style="color: hsl(120, 100%, 40%);">+ OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_MODIFIED),</span><br><span> OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ERROR),</span><br><span> OSMO_VALUE_STRING(ASSIGNMENT_EV_MSC_MGW_OK),</span><br><span> OSMO_VALUE_STRING(ASSIGNMENT_EV_MSC_MGW_FAIL),</span><br><span>@@ -807,6 +832,11 @@</span><br><span> void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)</span><br><span> {</span><br><span> struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Assignment can do a new channel activation, in which case new_lchan points at the new lchan.</span><br><span style="color: hsl(120, 100%, 40%);">+ * Or assignment can Channel Mode Modify the already used lchan, in which case new_lchan == NULL. */</span><br><span style="color: hsl(120, 100%, 40%);">+ struct gsm_lchan *new_lchan = conn->assignment.new_lchan ? : conn->lchan;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> switch (event) {</span><br><span> </span><br><span> case ASSIGNMENT_EV_CONN_RELEASING:</span><br><span>@@ -815,10 +845,11 @@</span><br><span> return;</span><br><span> </span><br><span> case ASSIGNMENT_EV_LCHAN_ERROR:</span><br><span style="color: hsl(0, 100%, 40%);">- if (data != conn->assignment.new_lchan)</span><br><span style="color: hsl(120, 100%, 40%);">+ if (data != new_lchan)</span><br><span> return;</span><br><span style="color: hsl(0, 100%, 40%);">- assignment_fail(conn->assignment.new_lchan->activate.gsm0808_error_cause,</span><br><span style="color: hsl(0, 100%, 40%);">- "Failed to activate lchan %s",</span><br><span style="color: hsl(120, 100%, 40%);">+ assignment_fail(new_lchan->activate.gsm0808_error_cause,</span><br><span style="color: hsl(120, 100%, 40%);">+ "Failed to %s lchan %s",</span><br><span style="color: hsl(120, 100%, 40%);">+ conn->assignment.new_lchan ? "activate" : "modify",</span><br><span> gsm_lchan_name(conn->assignment.new_lchan));</span><br><span> return;</span><br><span> </span><br><span>diff --git a/src/osmo-bsc/lchan_fsm.c b/src/osmo-bsc/lchan_fsm.c</span><br><span>index 4eb95ff..e422cf9 100644</span><br><span>--- a/src/osmo-bsc/lchan_fsm.c</span><br><span>+++ b/src/osmo-bsc/lchan_fsm.c</span><br><span>@@ -85,6 +85,53 @@</span><br><span> talloc_free(last_error_was);</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static void lchan_on_mode_modify_success(struct gsm_lchan *lchan)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan->modify.concluded = true;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ switch (lchan->modify.info.modify_for) {</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ case FOR_ASSIGNMENT:</span><br><span style="color: hsl(120, 100%, 40%);">+ osmo_fsm_inst_dispatch(lchan->conn->assignment.fi, ASSIGNMENT_EV_LCHAN_MODIFIED, lchan);</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ default:</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#define lchan_on_mode_modify_failure(lchan, modify_for, for_conn) \</span><br><span style="color: hsl(120, 100%, 40%);">+ _lchan_on_mode_modify_failure(lchan, modify_for, for_conn, \</span><br><span style="color: hsl(120, 100%, 40%);">+ __FILE__, __LINE__)</span><br><span style="color: hsl(120, 100%, 40%);">+static void _lchan_on_mode_modify_failure(struct gsm_lchan *lchan, enum lchan_activate_mode modify_for,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct gsm_subscriber_connection *for_conn,</span><br><span style="color: hsl(120, 100%, 40%);">+ const char *file, int line)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ if (lchan->modify.concluded)</span><br><span style="color: hsl(120, 100%, 40%);">+ return;</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan->modify.concluded = true;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ switch (modify_for) {</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ case FOR_ASSIGNMENT:</span><br><span style="color: hsl(120, 100%, 40%);">+ LOG_LCHAN(lchan, LOGL_NOTICE, "Signalling Assignment FSM of error (%s)\n",</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan->last_error ? : "unknown error");</span><br><span style="color: hsl(120, 100%, 40%);">+ _osmo_fsm_inst_dispatch(for_conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ERROR, lchan,</span><br><span style="color: hsl(120, 100%, 40%);">+ file, line);</span><br><span style="color: hsl(120, 100%, 40%);">+ return;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ case FOR_VTY:</span><br><span style="color: hsl(120, 100%, 40%);">+ LOG_LCHAN(lchan, LOGL_ERROR, "VTY user invoked lchan Channel Mode Modify failed (%s)\n",</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan->last_error ? : "unknown error");</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ default:</span><br><span style="color: hsl(120, 100%, 40%);">+ LOG_LCHAN(lchan, LOGL_ERROR, "lchan Channel Mode Modify failed (%s)\n",</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan->last_error ? : "unknown error");</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /* The idea here is that we must not require to change any lchan state in order to deny a request. */</span><br><span> #define lchan_on_activation_failure(lchan, for_conn, activ_for) \</span><br><span> _lchan_on_activation_failure(lchan, for_conn, activ_for, \</span><br><span>@@ -141,6 +188,10 @@</span><br><span> lchan->last_error ? : "unknown error");</span><br><span> break;</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ case FOR_MODE_MODIFY_RTP:</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan_on_mode_modify_failure(lchan, lchan->modify.info.modify_for, for_conn);</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> default:</span><br><span> LOG_LCHAN(lchan, LOGL_ERROR, "lchan activation failed (%s)\n",</span><br><span> lchan->last_error ? : "unknown error");</span><br><span>@@ -202,6 +253,10 @@</span><br><span> * we will try to roll back a modified RTP connection. */</span><br><span> break;</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ case FOR_MODE_MODIFY_RTP:</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan_on_mode_modify_success(lchan);</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> default:</span><br><span> LOG_LCHAN(lchan, LOGL_NOTICE, "lchan %s fully established\n",</span><br><span> lchan_activate_mode_name(lchan->activate.info.activ_for));</span><br><span>@@ -342,6 +397,27 @@</span><br><span> /* Remain in state UNUSED */</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+void lchan_mode_modify(struct gsm_lchan *lchan, struct lchan_modify_info *info)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ int rc;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ OSMO_ASSERT(lchan && info);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* To make sure that the lchan is actually allowed to initiate Channel Mode Modify, feed through an FSM event.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ rc = osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_REQUEST_MODE_MODIFY, info);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (rc) {</span><br><span style="color: hsl(120, 100%, 40%);">+ LOG_LCHAN(lchan, LOGL_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+ "Channel Mode Modify requested, but cannot dispatch LCHAN_EV_REQUEST_MODE_MODIFY event\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ goto abort;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ return;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+abort:</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan_on_mode_modify_failure(lchan, info->modify_for, lchan->conn);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> static void lchan_fsm_update_id(struct gsm_lchan *lchan)</span><br><span> {</span><br><span> osmo_fsm_inst_update_id_f(lchan->fi, "%u-%u-%u-%s-%u",</span><br><span>@@ -898,7 +974,7 @@</span><br><span> static void lchan_fsm_wait_rr_chan_mode_modify_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)</span><br><span> {</span><br><span> struct gsm_lchan *lchan = lchan_fi_lchan(fi);</span><br><span style="color: hsl(0, 100%, 40%);">- gsm48_lchan_modify(lchan, lchan->activate.info.chan_mode);</span><br><span style="color: hsl(120, 100%, 40%);">+ gsm48_lchan_modify(lchan, lchan->modify.info.chan_mode);</span><br><span> }</span><br><span> </span><br><span> static void lchan_fsm_wait_rr_chan_mode_modify_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)</span><br><span>@@ -938,10 +1014,28 @@</span><br><span> switch (event) {</span><br><span> </span><br><span> case LCHAN_EV_RSL_CHAN_MODE_MODIFY_ACK:</span><br><span style="color: hsl(0, 100%, 40%);">- if (lchan->activate.info.requires_voice_stream)</span><br><span style="color: hsl(120, 100%, 40%);">+ /* The Channel Mode Modify was ACKed, now the requested values become the accepted and used values. */</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan->tch_mode = lchan->modify.info.chan_mode;</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan->s15_s0 = lchan->modify.info.s15_s0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (lchan->modify.info.requires_voice_stream</span><br><span style="color: hsl(120, 100%, 40%);">+ && !lchan->fi_rtp) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Continue with RTP stream establishing as done in lchan_activate(). Place the requested values in</span><br><span style="color: hsl(120, 100%, 40%);">+ * lchan->activate.info and continue with voice stream setup. */</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan->activate.info = (struct lchan_activate_info){</span><br><span style="color: hsl(120, 100%, 40%);">+ /* No action required for when the voice stream is established */</span><br><span style="color: hsl(120, 100%, 40%);">+ .activ_for = FOR_MODE_MODIFY_RTP,</span><br><span style="color: hsl(120, 100%, 40%);">+ .for_conn = lchan->conn,</span><br><span style="color: hsl(120, 100%, 40%);">+ .s15_s0 = lchan->modify.info.s15_s0,</span><br><span style="color: hsl(120, 100%, 40%);">+ .requires_voice_stream = true,</span><br><span style="color: hsl(120, 100%, 40%);">+ .msc_assigned_cic = lchan->modify.info.msc_assigned_cic,</span><br><span style="color: hsl(120, 100%, 40%);">+ };</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan->activate.concluded = false;</span><br><span> lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_ESTABLISH);</span><br><span style="color: hsl(0, 100%, 40%);">- else</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span> lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED);</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan_on_mode_modify_success(lchan);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span> return;</span><br><span> </span><br><span> case LCHAN_EV_RSL_CHAN_MODE_MODIFY_NACK:</span><br><span>@@ -1031,7 +1125,7 @@</span><br><span> static void lchan_fsm_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)</span><br><span> {</span><br><span> struct gsm_lchan *lchan = lchan_fi_lchan(fi);</span><br><span style="color: hsl(0, 100%, 40%);">- struct lchan_activate_info *info;</span><br><span style="color: hsl(120, 100%, 40%);">+ struct lchan_modify_info *modif_info;</span><br><span> struct osmo_mgcpc_ep_ci *use_mgwep_ci;</span><br><span> </span><br><span> switch (event) {</span><br><span>@@ -1069,35 +1163,29 @@</span><br><span> return;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- info = data;</span><br><span style="color: hsl(0, 100%, 40%);">- lchan->activate.info = *info;</span><br><span style="color: hsl(120, 100%, 40%);">+ modif_info = data;</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan->modify.info = *modif_info;</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan->modify.concluded = false;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- if (info->chan_mode == GSM48_CMODE_SPEECH_AMR) {</span><br><span style="color: hsl(0, 100%, 40%);">- if (lchan_mr_config(lchan, info->s15_s0) < 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (gsm48_chan_mode_to_non_vamos(modif_info->chan_mode) == GSM48_CMODE_SPEECH_AMR) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (lchan_mr_config(lchan, modif_info->s15_s0) < 0) {</span><br><span> lchan_fail("Can not generate multirate configuration IE\n");</span><br><span> return;</span><br><span> }</span><br><span> }</span><br><span> </span><br><span> LOG_LCHAN(lchan, LOGL_INFO,</span><br><span style="color: hsl(0, 100%, 40%);">- "Modification requested: %s voice=%s MGW-ci=%s type=%s tch-mode=%s encr-alg=A5/%u ck=%s\n",</span><br><span style="color: hsl(0, 100%, 40%);">- lchan_activate_mode_name(lchan->activate.info.activ_for),</span><br><span style="color: hsl(0, 100%, 40%);">- lchan->activate.info.requires_voice_stream ? "yes" : "no",</span><br><span style="color: hsl(0, 100%, 40%);">- lchan->activate.info.requires_voice_stream ?</span><br><span style="color: hsl(120, 100%, 40%);">+ "Modification requested: %s voice=%s MGW-ci=%s type=%s tch-mode=%s\n",</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan_activate_mode_name(lchan->modify.info.modify_for),</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan->modify.info.requires_voice_stream ? "yes" : "no",</span><br><span style="color: hsl(120, 100%, 40%);">+ lchan->modify.info.requires_voice_stream ?</span><br><span> (use_mgwep_ci ? osmo_mgcpc_ep_ci_name(use_mgwep_ci) : "new")</span><br><span> : "none",</span><br><span> gsm_lchant_name(lchan->type),</span><br><span style="color: hsl(0, 100%, 40%);">- gsm48_chan_mode_name(lchan->tch_mode),</span><br><span style="color: hsl(0, 100%, 40%);">- (lchan->activate.info.encr.alg_id ? : 1) - 1,</span><br><span style="color: hsl(0, 100%, 40%);">- lchan->activate.info.encr.key_len ? osmo_hexdump_nospc(lchan->activate.info.encr.key,</span><br><span style="color: hsl(0, 100%, 40%);">- lchan->activate.info.encr.key_len) : "none");</span><br><span style="color: hsl(120, 100%, 40%);">+ gsm48_chan_mode_name(lchan->tch_mode));</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- /* While the mode is changed the lchan is virtually "not activated", at least</span><br><span style="color: hsl(0, 100%, 40%);">- * from the FSM implementations perspective */</span><br><span style="color: hsl(0, 100%, 40%);">- lchan->activate.concluded = false;</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">- /* Initiate mode modification, start with the MS side (RR) */</span><br><span> lchan_fsm_state_chg(LCHAN_ST_WAIT_RR_CHAN_MODE_MODIFY_ACK);</span><br><span> return;</span><br><span> </span><br><span>@@ -1419,6 +1507,7 @@</span><br><span> | S(LCHAN_EV_RSL_CHAN_MODE_MODIFY_NACK)</span><br><span> ,</span><br><span> .out_state_mask = 0</span><br><span style="color: hsl(120, 100%, 40%);">+ | S(LCHAN_ST_WAIT_RF_RELEASE_ACK)</span><br><span> | S(LCHAN_ST_BORKEN)</span><br><span> | S(LCHAN_ST_WAIT_RLL_RTP_ESTABLISH)</span><br><span> ,</span><br><span>@@ -1531,6 +1620,7 @@</span><br><span> OSMO_VALUE_STRING(LCHAN_EV_RR_CHAN_MODE_MODIFY_ERROR),</span><br><span> OSMO_VALUE_STRING(LCHAN_EV_RSL_CHAN_MODE_MODIFY_ACK),</span><br><span> OSMO_VALUE_STRING(LCHAN_EV_RSL_CHAN_MODE_MODIFY_NACK),</span><br><span style="color: hsl(120, 100%, 40%);">+ OSMO_VALUE_STRING(LCHAN_EV_REQUEST_MODE_MODIFY),</span><br><span> {}</span><br><span> };</span><br><span> </span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/osmo-bsc/+/23943">change 23943</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/c/osmo-bsc/+/23943"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: osmo-bsc </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: I4986844f839b1c9672c61d916eb3d33d0042d747 </div>
<div style="display:none"> Gerrit-Change-Number: 23943 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: neels <nhofmeyr@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>