aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2019-06-13 09:41:58 +0200
committerHarald Welte <laforge@gnumonks.org>2019-09-02 12:06:25 +0200
commitd41b7c7f830e90c7c4ce1d8ed97f13cfd3ed8cad (patch)
tree7a8a53f81cd42f34c8c761ef5c8bbd7a05a9944c
parent9508e2232f72a368ab87b5903e0764c4ae4985ad (diff)
Cell Broadcast: CBSP and CBCH scheduling support
This adds code to handle CBSP (Cell Broadcast Service Protocol) from the CBC (Cell Broadcast Centre), as well as BSC-internal data structures for scheduling the various SMSCB on the CBCH of each BTS. There are currently one known shortcoming in the code: We don't yet verify if keepalives are received within repetition period. Change-Id: Ia0a0de862a104d0f447a5d6e56c7c83981b825c7
-rw-r--r--doc/manuals/chapters/smscb.adoc82
-rw-r--r--doc/manuals/osmobsc-usermanual.adoc4
-rw-r--r--doc/manuals/vty/bsc_vty_reference.xml79
-rw-r--r--include/osmocom/bsc/Makefile.am1
-rw-r--r--include/osmocom/bsc/bsc_msc_data.h3
-rw-r--r--include/osmocom/bsc/debug.h1
-rw-r--r--include/osmocom/bsc/gsm_data.h53
-rw-r--r--include/osmocom/bsc/smscb.h59
-rw-r--r--include/osmocom/bsc/vty.h1
-rw-r--r--src/osmo-bsc/Makefile.am4
-rw-r--r--src/osmo-bsc/abis_rsl.c35
-rw-r--r--src/osmo-bsc/bsc_init.c15
-rw-r--r--src/osmo-bsc/bsc_vty.c14
-rw-r--r--src/osmo-bsc/cbch_scheduler.c287
-rw-r--r--src/osmo-bsc/cbsp_link.c417
-rw-r--r--src/osmo-bsc/gsm_data.c9
-rw-r--r--src/osmo-bsc/osmo_bsc_main.c13
-rw-r--r--src/osmo-bsc/smscb.c823
-rw-r--r--tests/handover/Makefile.am5
19 files changed, 1898 insertions, 7 deletions
diff --git a/doc/manuals/chapters/smscb.adoc b/doc/manuals/chapters/smscb.adoc
new file mode 100644
index 000000000..f7469a375
--- /dev/null
+++ b/doc/manuals/chapters/smscb.adoc
@@ -0,0 +1,82 @@
+[[smscb]]
+== SMSCB (Cell Broadcast)
+
+OsmoBSC supports SMS Cell Broadcast (SMSCB) services (CBS). This
+includes the CBSP protocol to interact with a CBC (Cell Broadcast
+Centre) such as OsmoCBC, as well as the scheduling of SMSCB messages on
+both the BASIC and EXTENDED CBCH and transmission of related RSL
+messages to the attached BTS.
+
+More high-level information can be found at
+https://en.wikipedia.org/wiki/Cell_Broadcast and the related
+specification is <<3gpp-ts-23-041>>.
+
+In order to use SMSCB with OsmoBSC, you will need to
+
+* Configure the CBSP server and/or client
+* Use a channel combination including a CBCH on the BTSs
+
+=== Enabling a CBCH channel combination
+
+On the Um interface, SMSCB are transmitted via the CBCH (Cell Broadcast
+Channel). The CBCH is a separate downlink-only logical channel which
+must be activated on any of the BTSs requiring CBSP support.
+
+The channel combination is configured in the `timeslot` node of each TRX.
+
+The two `phys_chan_config` supporting CBCH are `CCCH+SDCCH4+CBCH` and
+`SDCCH/8+CBCH`. Please note that the CBCH steals one of the SDCCH, so
+a SDCCH/4 will only have three remaining SDCCH, and a SDCCH/8 will
+have only seven remaining SDCCH.
+
+=== Configuring the CBSP connection
+
+CBSP is the protocol between BSC and CBC. It operates over TCP.
+
+According to 3GPP TS 48.049, a BSC typically operates as a TCP server,
+and the CBC connects as TCP client. This would require the CBC to have
+out-of-band knowledge of all the BSCs in the network (and their IP
+addresses).
+
+In order to comply with the specifications, OsmoBSC supports this mode
+of operation as CBSP TCP server. However, to make network operation and
+configuration more simple, it also can operate in TCP client mode,
+connecting to the CBC. This way the all the BSCs need to know is the CBC IP
+address, but not vice-versa.
+
+The BSC can operate both CBSP TCP server and CBSP TCP client mode in
+parallel.
+
+The CBC related configuration of OsmoBSC can be found in the `cbc` configuration
+node of the VTY interface.
+
+.Example: Configure CBSP TCP client to connect to CBC at 1.2.3.4:48049
+----
+OsmoBSC> enable
+OsmoBSC# configure terminal
+OsmoBSC(config)# cbc
+OsmoBSC(config-cbc)# remote-ip 1.2.3.4
+OsmoBSC(config-cbc)# remote-port 48049
+OsmoBSC(config-cbc)# end
+----
+
+.Example: Disable CBSP TCP client
+----
+OsmoBSC> enable
+OsmoBSC# configure terminal
+OsmoBSC(config)# cbc
+OsmoBSC(config-cbc)# no remote-ip
+OsmoBSC(config-cbc)# end
+----
+
+.Example: Configure CBSP TCP server to listen for CBC at 127.0.0.2:9999
+----
+OsmoBSC> enable
+OsmoBSC# configure terminal
+OsmoBSC(config)# cbc
+OsmoBSC(config-cbc)# listen-ip 127.0.0.2
+OsmoBSC(config-cbc)# listen-port 9999
+OsmoBSC(config-cbc)# end
+----
+
+For more details on the available configuration commands, please check the OsmoBSC VTY Reference.
diff --git a/doc/manuals/osmobsc-usermanual.adoc b/doc/manuals/osmobsc-usermanual.adoc
index 766a11f40..26f49da03 100644
--- a/doc/manuals/osmobsc-usermanual.adoc
+++ b/doc/manuals/osmobsc-usermanual.adoc
@@ -24,6 +24,8 @@ include::./common/chapters/bsc.adoc[]
include::{srcdir}/chapters/handover.adoc[]
+include::{srcdir}/chapters/smscb.adoc[]
+
include::./common/chapters/counters-overview.adoc[]
include::{srcdir}/chapters/counters.adoc[]
@@ -34,8 +36,6 @@ include::./common/chapters/control_if.adoc[]
include::{srcdir}/chapters/control.adoc[]
-include::./common/chapters/cell-broadcast.adoc[]
-
include::{srcdir}/chapters/osmux_bsc.adoc[]
include::./common/chapters/port_numbers.adoc[]
diff --git a/doc/manuals/vty/bsc_vty_reference.xml b/doc/manuals/vty/bsc_vty_reference.xml
index 85c0cb7e7..6a3e4fb57 100644
--- a/doc/manuals/vty/bsc_vty_reference.xml
+++ b/doc/manuals/vty/bsc_vty_reference.xml
@@ -320,7 +320,7 @@
<param name='MASK' doc='List of logging categories to log, e.g. &apos;abc:mno:xyz&apos;. Available log categories depend on the specific application, refer to the &apos;logging level&apos; command. Optionally add individual log levels like &apos;abc,1:mno,3:xyz,5&apos;, where the level numbers are LOGL_DEBUG=1 LOGL_INFO=3 LOGL_NOTICE=5 LOGL_ERROR=7 LOGL_FATAL=8' />
</params>
</command>
- <command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
+ <command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|cbs|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
<params>
<param name='logging' doc='Configure logging' />
<param name='level' doc='Set the log level for a specified category' />
@@ -343,6 +343,7 @@
<param name='chan' doc='lchan FSM' />
<param name='ts' doc='timeslot FSM' />
<param name='as' doc='assignment FSM' />
+ <param name='cbs' doc='Cell Broadcast System' />
<param name='lglobal' doc='Library-internal global log family' />
<param name='llapd' doc='LAPD in libosmogsm' />
<param name='linp' doc='A-bis Intput Subsystem' />
@@ -514,6 +515,22 @@
<param name='all' doc='Display a list of all FSM instances of all finite state machine' />
</params>
</command>
+ <command id='show cbc'>
+ <params>
+ <param name='show' doc='Show running system information' />
+ <param name='cbc' doc='Display state of CBC / CBSP' />
+ </params>
+ </command>
+ <command id='show bts &lt;0-255&gt; smscb [(basic|extended)]'>
+ <params>
+ <param name='show' doc='Show running system information' />
+ <param name='bts' doc='Display information about a BTS' />
+ <param name='&lt;0-255&gt;' doc='BTS number' />
+ <param name='smscb' doc='SMS Cell Broadcast State' />
+ <param name='[basic]' doc='Show only information related to CBCH BASIC' />
+ <param name='[extended]' doc='Show only information related to CBCH EXTENDED' />
+ </params>
+ </command>
<command id='show statistics'>
<params>
<param name='show' doc='Show running system information' />
@@ -971,7 +988,7 @@
<param name='MASK' doc='List of logging categories to log, e.g. &apos;abc:mno:xyz&apos;. Available log categories depend on the specific application, refer to the &apos;logging level&apos; command. Optionally add individual log levels like &apos;abc,1:mno,3:xyz,5&apos;, where the level numbers are LOGL_DEBUG=1 LOGL_INFO=3 LOGL_NOTICE=5 LOGL_ERROR=7 LOGL_FATAL=8' />
</params>
</command>
- <command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
+ <command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|cbs|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
<params>
<param name='logging' doc='Configure logging' />
<param name='level' doc='Set the log level for a specified category' />
@@ -994,6 +1011,7 @@
<param name='chan' doc='lchan FSM' />
<param name='ts' doc='timeslot FSM' />
<param name='as' doc='assignment FSM' />
+ <param name='cbs' doc='Cell Broadcast System' />
<param name='lglobal' doc='Library-internal global log family' />
<param name='llapd' doc='LAPD in libosmogsm' />
<param name='linp' doc='A-bis Intput Subsystem' />
@@ -1356,6 +1374,16 @@
<param name='all' doc='Display a list of all FSM instances of all finite state machine' />
</params>
</command>
+ <command id='show bts &lt;0-255&gt; smscb [(basic|extended)]'>
+ <params>
+ <param name='show' doc='Show running system information' />
+ <param name='bts' doc='Display information about a BTS' />
+ <param name='&lt;0-255&gt;' doc='BTS number' />
+ <param name='smscb' doc='SMS Cell Broadcast State' />
+ <param name='[basic]' doc='Show only information related to CBCH BASIC' />
+ <param name='[extended]' doc='Show only information related to CBCH EXTENDED' />
+ </params>
+ </command>
<command id='show statistics'>
<params>
<param name='show' doc='Show running system information' />
@@ -1735,6 +1763,11 @@
<param name='e1_input' doc='Configure E1/T1/J1 TDM input' />
</params>
</command>
+ <command id='cbc'>
+ <params>
+ <param name='cbc' doc='Configure CBSP Link to Cell Broadcast Centre' />
+ </params>
+ </command>
<command id='msc [&lt;0-1000&gt;]'>
<params>
<param name='msc' doc='Configure MSC details' />
@@ -1833,7 +1866,7 @@
<param name='[last]' doc='Log source file info at the end of a log line. If omitted, log source file info just before the log text.' />
</params>
</command>
- <command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
+ <command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|cbs|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
<params>
<param name='logging' doc='Configure logging' />
<param name='level' doc='Set the log level for a specified category' />
@@ -1856,6 +1889,7 @@
<param name='chan' doc='lchan FSM' />
<param name='ts' doc='timeslot FSM' />
<param name='as' doc='assignment FSM' />
+ <param name='cbs' doc='Cell Broadcast System' />
<param name='lglobal' doc='Library-internal global log family' />
<param name='llapd' doc='LAPD in libosmogsm' />
<param name='linp' doc='A-bis Intput Subsystem' />
@@ -4930,4 +4964,43 @@
</params>
</command>
</node>
+ <node id='config-cbc'>
+ <name>config-cbc</name>
+ <command id='remote-ip A.B.C.D'>
+ <params>
+ <param name='remote-ip' doc='IP Address of the Cell Broadcast Centre' />
+ <param name='A.B.C.D' doc='IP Address of the Cell Broadcast Centre' />
+ </params>
+ </command>
+ <command id='no remote-ip'>
+ <params>
+ <param name='no' doc='Negate a command or set its defaults' />
+ <param name='remote-ip' doc='Remove IP address of CBC; disables outbound CBSP connections' />
+ </params>
+ </command>
+ <command id='remote-port &lt;1-65535&gt;'>
+ <params>
+ <param name='remote-port' doc='TCP Port number of the Cell Broadcast Centre (Default: 48049)' />
+ <param name='&lt;1-65535&gt;' doc='TCP Port number of the Cell Broadcast Centre (Default: 48049)' />
+ </params>
+ </command>
+ <command id='listen-port &lt;1-65535&gt;'>
+ <params>
+ <param name='listen-port' doc='Local TCP port at which BSC listens for incoming CBSP connections from CBC' />
+ <param name='&lt;1-65535&gt;' doc='Local TCP port at which BSC listens for incoming CBSP connections from CBC' />
+ </params>
+ </command>
+ <command id='no listen-port'>
+ <params>
+ <param name='no' doc='Negate a command or set its defaults' />
+ <param name='listen-port' doc='Remove CBSP Listen Port; disables inbound CBSP connections' />
+ </params>
+ </command>
+ <command id='listen-ip A.B.C.D'>
+ <params>
+ <param name='listen-ip' doc='Local IP Address where BSC listens for incoming CBC connections (Default: 0.0.0.0)' />
+ <param name='A.B.C.D' doc='Local IP Address where BSC listens for incoming CBC connections' />
+ </params>
+ </command>
+ </node>
</vtydoc>
diff --git a/include/osmocom/bsc/Makefile.am b/include/osmocom/bsc/Makefile.am
index f44e7fc86..396604eb1 100644
--- a/include/osmocom/bsc/Makefile.am
+++ b/include/osmocom/bsc/Makefile.am
@@ -55,4 +55,5 @@ noinst_HEADERS = \
gsm_08_08.h \
penalty_timers.h \
osmo_bsc_lcls.h \
+ smscb.h \
$(NULL)
diff --git a/include/osmocom/bsc/bsc_msc_data.h b/include/osmocom/bsc/bsc_msc_data.h
index 56124830c..b9df4ba8f 100644
--- a/include/osmocom/bsc/bsc_msc_data.h
+++ b/include/osmocom/bsc/bsc_msc_data.h
@@ -150,6 +150,7 @@ struct bsc_msc_data {
/*
* Per BSC data.
*/
+struct bsc_cbc_link;
struct osmo_bsc_data {
struct gsm_network *network;
@@ -167,6 +168,8 @@ struct osmo_bsc_data {
char *ussd_no_msc_txt;
char *acc_lst_name;
+
+ struct bsc_cbc_link *cbc;
};
diff --git a/include/osmocom/bsc/debug.h b/include/osmocom/bsc/debug.h
index 326012185..adc6abbe1 100644
--- a/include/osmocom/bsc/debug.h
+++ b/include/osmocom/bsc/debug.h
@@ -27,6 +27,7 @@ enum {
DCHAN,
DTS,
DAS,
+ DCBS,
Debug_LastEntry,
};
diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h
index d82d1bac8..8dfbc6425 100644
--- a/include/osmocom/bsc/gsm_data.h
+++ b/include/osmocom/bsc/gsm_data.h
@@ -14,6 +14,7 @@
#include <osmocom/core/stat_item.h>
#include <osmocom/gsm/bts_features.h>
#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/protocol/gsm_48_049.h>
#include <osmocom/gsm/gsm0808.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/core/fsm.h>
@@ -957,6 +958,53 @@ struct gsm_bts_ref {
struct gsm_bts *bts;
};
+/* A single Page of a SMSCB message */
+struct bts_smscb_page {
+ /* SMSCB message we're part of */
+ struct bts_smscb_message *msg;
+ /* Page Number within message (1 to 15) */
+ uint8_t nr;
+ /* number of valid blocks in data (up to 4) */
+ uint8_t num_blocks;
+ /* up to four blocks of 22 bytes each */
+ uint8_t data[88];
+};
+
+/* A SMSCB message (received from CBSP) */
+struct bts_smscb_message {
+ /* entry in bts_smscb_chan_state.messages */
+ struct llist_head list;
+ struct {
+ /* input data from CBSP (CBC) side */
+ uint16_t msg_id;
+ uint16_t serial_nr;
+ enum cbsp_category category;
+ uint16_t rep_period;
+ uint16_t num_bcast_req;
+ uint8_t dcs;
+ } input;
+ /* how often have all pages of this message been broadcast? */
+ uint32_t bcast_count;
+ /* actual page data of this message */
+ uint8_t num_pages; /* up to 15 */
+ struct bts_smscb_page page[15];
+};
+
+/* per-channel (basic/extended) CBCH state for a single BTS */
+struct bts_smscb_chan_state {
+ /* back-pointer to BTS */
+ struct gsm_bts *bts;
+ /* list of bts_smscb_message */
+ struct llist_head messages;
+ /* scheduling array; pointer of SMSCB pages */
+ struct bts_smscb_page **sched_arr;
+ size_t sched_arr_size;
+ /* index of the next to be transmitted page into the scheduler array */
+ size_t next_idx;
+ /* number of messages we have to pause due to overflow */
+ uint8_t overflow;
+};
+
/* One BTS */
struct gsm_bts {
/* list header in net->bts_list */
@@ -1213,6 +1261,11 @@ struct gsm_bts {
struct load_counter chan_load_samples[7];
int chan_load_samples_idx;
uint8_t chan_load_avg; /* current channel load average in percent (0 - 100). */
+
+ /* cell broadcast system */
+ struct osmo_timer_list cbch_timer;
+ struct bts_smscb_chan_state cbch_basic;
+ struct bts_smscb_chan_state cbch_extended;
};
/* One rejected BTS */
diff --git a/include/osmocom/bsc/smscb.h b/include/osmocom/bsc/smscb.h
new file mode 100644
index 000000000..22a258da9
--- /dev/null
+++ b/include/osmocom/bsc/smscb.h
@@ -0,0 +1,59 @@
+#pragma once
+#include <osmocom/bsc/gsm_data.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/netif/stream.h>
+#include <osmocom/gsm/cbsp.h>
+
+struct bsc_cbc_link;
+
+/* smscb.c */
+void bts_smscb_del(struct bts_smscb_message *smscb, struct bts_smscb_chan_state *cstate,
+ const char *reason);
+const char *bts_smscb_msg2str(const struct bts_smscb_message *smscb);
+struct bts_smscb_chan_state *bts_get_smscb_chan(struct gsm_bts *bts, bool extended);
+int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec);
+int cbsp_tx_restart(struct bsc_cbc_link *cbc, bool is_emerg);
+const char *bts_smscb_chan_state_name(const struct bts_smscb_chan_state *cstate);
+unsigned int bts_smscb_chan_load_percent(const struct bts_smscb_chan_state *cstate);
+unsigned int bts_smscb_chan_page_count(const struct bts_smscb_chan_state *cstate);
+void smscb_vty_init(void);
+
+/* cbch_scheduler.c */
+int bts_smscb_gen_sched_arr(struct bts_smscb_chan_state *cstate, struct bts_smscb_page ***arr_out);
+struct bts_smscb_page *bts_smscb_pull_page(struct bts_smscb_chan_state *cstate);
+void bts_smscb_page_done(struct bts_smscb_chan_state *cstate, struct bts_smscb_page *page);
+int bts_smscb_rx_cbch_load_ind(struct gsm_bts *bts, bool cbch_extended, bool is_overflow,
+ uint8_t slot_count);
+void bts_cbch_timer_schedule(struct gsm_bts *bts);
+
+/* cbsp_link.c */
+struct bsc_cbc_link {
+ struct gsm_network *net;
+ struct {
+ /* hostname/IP of CBC */
+ char *cbc_hostname;
+ /* TCP port (Default: 48049) of CBC */
+ int cbc_port;
+ /* local listening port (0 for disabling local server) */
+ int listen_port;
+ /* local listening hostname/IP */
+ char *listen_hostname;
+ } config;
+ /* for handling inbound TCP connections */
+ struct {
+ struct osmo_stream_srv *srv;
+ struct osmo_stream_srv_link *link;
+ char *sock_name;
+ struct msgb *msg;
+ } server;
+ /* for handling outbound TCP connections */
+ struct {
+ struct osmo_stream_cli *cli;
+ char *sock_name;
+ struct msgb *msg;
+ } client;
+};
+void cbc_vty_init(void);
+int bsc_cbc_link_restart(void);
+int cbsp_tx_decoded(struct bsc_cbc_link *cbc, struct osmo_cbsp_decoded *decoded);
diff --git a/include/osmocom/bsc/vty.h b/include/osmocom/bsc/vty.h
index 7e3c5053c..10ce16b2d 100644
--- a/include/osmocom/bsc/vty.h
+++ b/include/osmocom/bsc/vty.h
@@ -24,6 +24,7 @@ enum bsc_vty_node {
OM2K_NODE,
OM2K_CON_GROUP_NODE,
BSC_NODE,
+ CBC_NODE,
};
struct log_info;
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am
index d50515b0f..51d887579 100644
--- a/src/osmo-bsc/Makefile.am
+++ b/src/osmo-bsc/Makefile.am
@@ -88,6 +88,9 @@ osmo_bsc_SOURCES = \
rest_octets.c \
system_information.c \
timeslot_fsm.c \
+ smscb.c \
+ cbch_scheduler.c \
+ cbsp_link.c \
$(NULL)
osmo_bsc_LDADD = \
@@ -96,6 +99,7 @@ osmo_bsc_LDADD = \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOVTY_LIBS) \
$(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
$(COVERAGE_LDFLAGS) \
$(LIBOSMOABIS_LIBS) \
$(LIBOSMOSIGTRAN_LIBS) \
diff --git a/src/osmo-bsc/abis_rsl.c b/src/osmo-bsc/abis_rsl.c
index 0b68d7c53..06d19a5d5 100644
--- a/src/osmo-bsc/abis_rsl.c
+++ b/src/osmo-bsc/abis_rsl.c
@@ -1,7 +1,7 @@
/* GSM Radio Signalling Link messages on the A-bis interface
* 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
-/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2008-2019 by Harald Welte <laforge@gnumonks.org>
* (C) 2012 by Holger Hans Peter Freyther
*
* All Rights Reserved
@@ -52,6 +52,7 @@
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/lchan_rtp_fsm.h>
#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/smscb.h>
#define RSL_ALLOC_SIZE 1024
#define RSL_ALLOC_HEADROOM 128
@@ -1489,6 +1490,36 @@ static int rsl_rx_ccch_load(struct msgb *msg)
return 0;
}
+/* 8.5.9 current load on the CBCH (Cell Broadcast) */
+static int rsl_rx_cbch_load(struct msgb *msg)
+{
+ struct e1inp_sign_link *sign_link = msg->dst;
+ struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+ struct gsm_bts *bts = sign_link->trx->bts;
+ bool cbch_extended = false;
+ bool is_overflow = false;
+ int8_t load_info;
+ struct tlv_parsed tp;
+ uint8_t slot_count;
+
+ rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg) - sizeof(*rslh));
+ if (!TLVP_PRESENT(&tp, RSL_IE_CBCH_LOAD_INFO)) {
+ LOG_BTS(bts, DRSL, LOGL_ERROR, "CBCH LOAD IND without mandatory CBCH Load Info IE\n");
+ return -1;
+ }
+ /* 9.4.43 */
+ load_info = *TLVP_VAL(&tp, RSL_IE_CBCH_LOAD_INFO);
+ if (load_info & 0x80)
+ is_overflow = true;
+ slot_count = load_info & 0x0F;
+
+ if (TLVP_PRES_LEN(&tp, RSL_IE_SMSCB_CHAN_INDICATOR, 1) &&
+ (*TLVP_VAL(&tp, RSL_IE_SMSCB_CHAN_INDICATOR) & 0x0F) == 0x01)
+ cbch_extended = true;
+
+ return bts_smscb_rx_cbch_load_ind(bts, cbch_extended, is_overflow, slot_count);
+}
+
/* Ericsson specific: Immediate Assign Sent */
static int rsl_rx_ericsson_imm_assign_sent(struct msgb *msg)
{
@@ -1537,7 +1568,7 @@ static int abis_rsl_rx_cchan(struct msgb *msg)
break;
case RSL_MT_CBCH_LOAD_IND:
/* current load on the CBCH */
- /* FIXME: handle this. Ignore for now */
+ rc = rsl_rx_cbch_load(msg);
break;
case RSL_MT_ERICSSON_IMM_ASS_SENT:
rc = rsl_rx_ericsson_imm_assign_sent(msg);
diff --git a/src/osmo-bsc/bsc_init.c b/src/osmo-bsc/bsc_init.c
index 7d29d4fbe..18776f33d 100644
--- a/src/osmo-bsc/bsc_init.c
+++ b/src/osmo-bsc/bsc_init.c
@@ -37,6 +37,9 @@
#include <osmocom/bsc/gsm_04_08_rr.h>
#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/gsm/protocol/gsm_48_049.h>
+
#include <time.h>
#include <limits.h>
#include <stdbool.h>
@@ -244,6 +247,11 @@ static struct gsm_network *bsc_network_init(void *ctx)
talloc_free(net);
return NULL;
}
+ net->bsc_data->cbc = talloc_zero(net->bsc_data, struct bsc_cbc_link);
+ if (!net->bsc_data->cbc) {
+ talloc_free(net);
+ return NULL;
+ }
/* Init back pointer */
net->bsc_data->auto_off_timeout = -1;
@@ -272,6 +280,13 @@ static struct gsm_network *bsc_network_init(void *ctx)
osmo_timer_setup(&net->t3122_chan_load_timer, update_t3122_chan_load_timer, net);
osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0);
+ net->bsc_data->cbc->net = net;
+ /* no cbc_hostname: client not started by default */
+ net->bsc_data->cbc->config.cbc_port = CBSP_TCP_PORT;
+ /* listen_port == -1: server not started by default */
+ net->bsc_data->cbc->config.listen_port = -1;
+ net->bsc_data->cbc->config.listen_hostname = talloc_strdup(net->bsc_data->cbc, "127.0.0.1");
+
return net;
}
diff --git a/src/osmo-bsc/bsc_vty.c b/src/osmo-bsc/bsc_vty.c
index 6de2d4b4d..06c06de7c 100644
--- a/src/osmo-bsc/bsc_vty.c
+++ b/src/osmo-bsc/bsc_vty.c
@@ -70,6 +70,7 @@
#include <osmocom/bsc/timeslot_fsm.h>
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/lchan_select.h>
+#include <osmocom/bsc/smscb.h>
#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
#include <inttypes.h>
@@ -318,6 +319,14 @@ static void vty_out_neigh_list(struct vty *vty, struct bitvec *bv)
vty_out(vty, " (%d)", count);
}
+static void bts_dump_vty_cbch(struct vty *vty, const struct bts_smscb_chan_state *cstate)
+{
+ vty_out(vty, " CBCH %s: %u messages, %u pages, %lu-entry sched_arr, %u%% load%s",
+ bts_smscb_chan_state_name(cstate), llist_count(&cstate->messages),
+ bts_smscb_chan_page_count(cstate), cstate->sched_arr_size,
+ bts_smscb_chan_load_percent(cstate), VTY_NEWLINE);
+}
+
static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts)
{
unsigned int i;
@@ -504,6 +513,9 @@ static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE);
dump_pchan_load_vty(vty, " ", &pl);
+ bts_dump_vty_cbch(vty, &bts->cbch_basic);
+ bts_dump_vty_cbch(vty, &bts->cbch_extended);
+
vty_out(vty, " Channel Requests : %"PRIu64" total, %"PRIu64" no channel%s",
bts->bts_ctrs->ctr[BTS_CTR_CHREQ_TOTAL].current,
bts->bts_ctrs->ctr[BTS_CTR_CHREQ_NO_CHANNEL].current,
@@ -5427,6 +5439,8 @@ int bsc_vty_init(struct gsm_network *network)
osmo_fsm_vty_add_cmds();
ho_vty_init();
+ cbc_vty_init();
+ smscb_vty_init();
bsc_vty_init_extra();
diff --git a/src/osmo-bsc/cbch_scheduler.c b/src/osmo-bsc/cbch_scheduler.c
new file mode 100644
index 000000000..ef93b7a4a
--- /dev/null
+++ b/src/osmo-bsc/cbch_scheduler.c
@@ -0,0 +1,287 @@
+/* CBCH (Cell Broadcast Channel) Scheduler for OsmoBSC */
+/*
+ * (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/stats.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/abis_rsl.h>
+
+/* add all pages of given SMSCB so they appear as soon as possible *after* (included) base_idx. */
+static int bts_smscb_sched_add_after(struct bts_smscb_page **sched_arr, int sched_arr_size,
+ int base_idx, struct bts_smscb_message *smscb)
+{
+ int arr_idx = base_idx;
+ int i;
+
+ OSMO_ASSERT(smscb->num_pages <= ARRAY_SIZE(smscb->page));
+ for (i = 0; i < smscb->num_pages; i++) {
+ while (sched_arr[arr_idx]) {
+ arr_idx++;
+ if (arr_idx >= sched_arr_size)
+ return -ENOSPC;
+ }
+ sched_arr[arr_idx] = &smscb->page[i];
+ }
+ return arr_idx;
+}
+
+/* add all pages of given smscb so they appear *before* (included) last_idx. */
+static int bts_smscb_sched_add_before(struct bts_smscb_page **sched_arr, int sched_arr_size,
+ int last_idx, struct bts_smscb_message *smscb)
+{
+ int arr_idx = last_idx;
+ int last_used_idx = 0;
+ int i;
+
+ OSMO_ASSERT(smscb->num_pages <= ARRAY_SIZE(smscb->page));
+ OSMO_ASSERT(smscb->num_pages >= 1);
+
+ for (i = smscb->num_pages - 1; i >= 0; i--) {
+ while (sched_arr[arr_idx]) {
+ arr_idx--;
+ if (arr_idx < 0)
+ return -ENOSPC;
+ }
+ sched_arr[arr_idx] = &smscb->page[i];
+ if (i == smscb->num_pages)
+ last_used_idx = i;
+ }
+ return last_used_idx;
+}
+
+/* obtain the least frequently scheduled SMSCB for given SMSCB channel */
+static struct bts_smscb_message *
+bts_smscb_chan_get_least_frequent_smscb(struct bts_smscb_chan_state *cstate)
+{
+ if (llist_empty(&cstate->messages))
+ return NULL;
+ /* messages are expected to be ordered with increasing period, so we're
+ * able to return the last message in the list */
+ return llist_entry(cstate->messages.prev, struct bts_smscb_message, list);
+}
+
+/*! Generate per-BTS SMSCB scheduling array
+ * \param[in] cstate BTS CBCH channel state
+ * \param[out] arr_out return argument for allocated + generated scheduling array
+ * \return size of returned scheduling array arr_out in number of entries; negative on error */
+int bts_smscb_gen_sched_arr(struct bts_smscb_chan_state *cstate, struct bts_smscb_page ***arr_out)
+{
+ struct bts_smscb_message *smscb, *least_freq;
+ struct bts_smscb_page **arr;
+ int arr_size;
+ int rc;
+
+ /* start with one instance of the least frequent message at position 0, as we
+ * need to transmit it exactly once during the duration of the scheduling array */
+ least_freq = bts_smscb_chan_get_least_frequent_smscb(cstate);
+ if (!least_freq) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_DEBUG, "No SMSCB; cannot create schedule array\n");
+ *arr_out = NULL;
+ return 0;
+ }
+ arr_size = least_freq->input.rep_period;
+ arr = talloc_zero_array(cstate->bts, struct bts_smscb_page *, arr_size);
+ OSMO_ASSERT(arr);
+ rc = bts_smscb_sched_add_after(arr, arr_size, 0, least_freq);
+ if (rc < 0) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule first instance of "
+ "very first SMSCB %s ?!?\n", bts_smscb_msg2str(least_freq));
+ talloc_free(arr);
+ return rc;
+ }
+
+ /* continue filling with repetitions of the more frequent messages, starting from
+ * the most frequent message to the least frequent one, repeating them as needed
+ * throughout the duration of the array */
+ llist_for_each_entry(smscb, &cstate->messages, list) {
+ int last_page;
+ if (smscb == least_freq)
+ continue;
+ /* messages are expected to be ordered with increasing period, so we're
+ * starting with the most frequent / shortest period first */
+ rc = bts_smscb_sched_add_after(arr, arr_size, 0, smscb);
+ if (rc < 0) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule first instance of "
+ "SMSCB %s\n", bts_smscb_msg2str(smscb));
+ talloc_free(arr);
+ return rc;
+ }
+ last_page = rc;
+
+ while (last_page < cstate->sched_arr_size) {
+ /* store further instances in a way that the last block of the N+1th instance
+ * happens no later than "interval" after the last block of the Nth instance */
+ rc = bts_smscb_sched_add_before(arr, arr_size,
+ last_page + smscb->input.rep_period, smscb);
+ if (rc < 0) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule further "
+ "SMSCB %s\n", bts_smscb_msg2str(smscb));
+ talloc_free(arr);
+ return rc;
+ }
+ last_page = rc;
+ }
+ }
+ *arr_out = arr;
+ return arr_size;
+}
+
+/*! Pull the next to-be-transmitted SMSCB page out of the scheduler for the given channel */
+struct bts_smscb_page *bts_smscb_pull_page(struct bts_smscb_chan_state *cstate)
+{
+ struct bts_smscb_page *page;
+
+ /* if there are no messages to schedule, there is no array */
+ if (!cstate->sched_arr)
+ return NULL;
+
+ /* obtain the page from the scheduler array */
+ page = cstate->sched_arr[cstate->next_idx];
+
+ /* increment the index for the next call to this function */
+ cstate->next_idx = (cstate->next_idx + 1) % cstate->sched_arr_size;
+
+ /* the array can have gaps in between where there is nothing scheduled */
+ if (!page)
+ return NULL;
+
+ return page;
+}
+
+/*! To be called after bts_smscb_pull_page() in order to update transmission count and
+ * check if SMSCB is complete.
+ * \param[in] cstate BTS CBC channel state
+ * \param[in] page SMSCB Page which had been returned by bts_smscb_pull_page() and which
+ * is no longer needed now */
+void bts_smscb_page_done(struct bts_smscb_chan_state *cstate, struct bts_smscb_page *page)
+{
+ struct bts_smscb_message *smscb = page->msg;
+
+ /* If this is the last page of a SMSCB, increment the SMSCB number-of-xmit counter */
+ if (page->nr == smscb->num_pages) {
+ smscb->bcast_count++;
+ /* Check if the SMSCB transmission duration is now over */
+ if (smscb->bcast_count >= smscb->input.num_bcast_req)
+ bts_smscb_del(smscb, cstate, "COMPLETE");
+ }
+}
+
+
+/***********************************************************************
+ * BTS / RSL side
+ ***********************************************************************/
+
+static void bts_cbch_send_one(struct bts_smscb_chan_state *cstate)
+{
+ struct bts_smscb_page *page;
+ struct gsm_bts *bts = cstate->bts;
+ struct rsl_ie_cb_cmd_type cb_cmd;
+ bool is_extended = false;
+
+ if (cstate == &bts->cbch_extended)
+ is_extended = true;
+
+ if (cstate->overflow) {
+ LOG_BTS(bts, DCBS, LOGL_DEBUG, "Skipping SMSCB due to overflow (%u)\n",
+ cstate->overflow);
+ cstate->overflow--;
+ return;
+ }
+
+ page = bts_smscb_pull_page(cstate);
+ if (!page) {
+ LOG_BTS(bts, DCBS, LOGL_DEBUG, "Skipping SMSCB: No page available\n");
+ return;
+ }
+
+ cb_cmd.spare = 0;
+ cb_cmd.def_bcast = 0;
+ cb_cmd.command = RSL_CB_CMD_TYPE_NORMAL;
+ switch (page->num_blocks) {
+ case 1:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_1;
+ break;
+ case 2:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_2;
+ break;
+ case 3:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_3;
+ break;
+ case 4:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_4;
+ break;
+ default:
+ osmo_panic("SMSCB Page must have 1..4 blocks, not %d\n", page->num_blocks);
+ }
+ rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, is_extended,
+ page->data, sizeof(page->data));
+
+ bts_smscb_page_done(cstate, page);
+}
+
+static void bts_cbch_timer(void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)data;
+
+ bts_cbch_send_one(&bts->cbch_basic);
+ bts_cbch_send_one(&bts->cbch_extended);
+
+ bts_cbch_timer_schedule(bts);
+}
+
+/* There is one SMSCB message (page) per eight 51-multiframes, i.e. 1.882 seconds */
+void bts_cbch_timer_schedule(struct gsm_bts *bts)
+{
+ osmo_timer_setup(&bts->cbch_timer, &bts_cbch_timer, bts);
+ osmo_timer_schedule(&bts->cbch_timer, 1, 882920);
+}
+
+/*! Receive a (decoded) incoming CBCH LOAD IND from given bts. See TS 48.058 8.5.9
+ * \param[in] bts The BTS for which the load indication was received
+ * \param[in] cbch_extended Is this report for extended (true) or basic CBCH
+ * \param[in] is_overflow Is this report and overflow (true) or underflow report
+ * \param[in] slot_count amount of SMSCB messages needed / delay needed */
+int bts_smscb_rx_cbch_load_ind(struct gsm_bts *bts, bool cbch_extended, bool is_overflow,
+ uint8_t slot_count)
+{
+ struct bts_smscb_chan_state *cstate = bts_get_smscb_chan(bts, cbch_extended);
+ int i;
+
+ if (!gsm_bts_get_cbch(bts))
+ return -ENODEV;
+
+ if (is_overflow) {
+ /* halt/delay transmission of further CBCH messages */
+ cstate->overflow = slot_count;
+ } else {
+ for (i = 0; i < slot_count; i++)
+ bts_cbch_send_one(cstate);
+ /* re-schedule the timer to count from now on */
+ bts_cbch_timer_schedule(bts);
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bsc/cbsp_link.c b/src/osmo-bsc/cbsp_link.c
new file mode 100644
index 000000000..8840afa52
--- /dev/null
+++ b/src/osmo-bsc/cbsp_link.c
@@ -0,0 +1,417 @@
+/* CBSP (Cell Broadcast Service Protocol) Handling for OsmoBSC */
+/*
+ * (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/cbsp.h>
+
+/* if a CBC IP/port has been configured, we continuously try to re-establish the TCP
+ * connection (as a client) to the CBC. If none has been configured, and we have a listen
+ * TCP port, we expect the CBC to connect to us. If neither of the two is configured,
+ * CBSP is effectively disabled */
+
+/*********************************************************************************
+ * CBSP Server (inbound TCP connection from CBC)
+ *********************************************************************************/
+
+static int cbsp_srv_closed_cb(struct osmo_stream_srv *conn)
+{
+ struct bsc_cbc_link *cbc = osmo_stream_srv_get_data(conn);
+ //struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
+
+ LOGP(DCBS, LOGL_NOTICE, "CBSP Server lost connection from %s\n", cbc->server.sock_name);
+ talloc_free(cbc->server.sock_name);
+ cbc->server.sock_name = NULL;
+ cbc->server.srv = NULL;
+ return 0;
+}
+
+static int cbsp_srv_cb(struct osmo_stream_srv *conn)
+{
+ struct bsc_cbc_link *cbc = osmo_stream_srv_get_data(conn);
+ struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
+ struct osmo_cbsp_decoded *decoded;
+ struct msgb *msg;
+ int rc;
+
+ /* READ */
+ rc = osmo_cbsp_recv_buffered(cbc, ofd->fd, &msg, &cbc->server.msg);
+ if (rc <= 0) {
+ if (rc == -EAGAIN || rc == -EINTR) {
+ /* more data needs to be read */
+ return 0;
+ } else if (rc == -EPIPE || rc == -ECONNRESET) {
+ /* lost connection */
+ } else if (rc == 0) {
+ /* connection closed */
+ }
+ osmo_stream_srv_destroy(conn);
+ cbc->server.srv = NULL;
+ return -EBADF;
+ }
+ OSMO_ASSERT(msg);
+ decoded = osmo_cbsp_decode(conn, msg);
+ if (decoded) {
+ LOGP(DCBS, LOGL_DEBUG, "Received CBSP %s\n",
+ get_value_string(cbsp_msg_type_names, decoded->msg_type));
+ cbsp_rx_decoded(cbc, decoded);
+ talloc_free(decoded);
+ } else {
+ LOGP(DCBS, LOGL_ERROR, "Unable to decode CBSP %s: '%s'\n",
+ msgb_hexdump(msg), osmo_cbsp_errstr);
+ }
+ msgb_free(msg);
+ return 0;
+
+}
+
+static int cbsp_srv_link_accept_cb(struct osmo_stream_srv_link *link, int fd)
+{
+ struct bsc_cbc_link *cbc = osmo_stream_srv_link_get_data(link);
+ struct osmo_stream_srv *srv;
+
+ LOGP(DCBS, LOGL_INFO, "CBSP Server received inbound connection from CBC: %s\n",
+ osmo_sock_get_name2(fd));
+
+ if (cbc->server.srv) {
+ LOGP(DCBS, LOGL_NOTICE, "CBSP Server refusing further connection (%s) "
+ "while we already have another connection (%s)\n",
+ osmo_sock_get_name2(fd), cbc->server.sock_name);
+ return -1;
+ }
+
+ srv = osmo_stream_srv_create(cbc, link, fd, cbsp_srv_cb, cbsp_srv_closed_cb, cbc);
+ if (!srv) {
+ LOGP(DCBS, LOGL_ERROR, "Unable to create stream server for %s\n",
+ osmo_sock_get_name2(fd));
+ return -1;
+ }
+
+ cbc->server.srv = srv;
+ if (cbc->server.sock_name)
+ talloc_free(cbc->server.sock_name);
+ cbc->server.sock_name = osmo_sock_get_name(cbc, fd);
+ LOGP(DCBS, LOGL_NOTICE, "CBSP Server link established from CBC %s\n", cbc->server.sock_name);
+ /* TODO: introduce ourselves to the peer using some osmcoom extensions */
+ cbsp_tx_restart(cbc, false);
+ return 0;
+}
+
+/*********************************************************************************
+ * CBSP Client (outbound TCP connection to CBC)
+ *********************************************************************************/
+
+static int cbsp_client_connect_cb(struct osmo_stream_cli *cli)
+{
+ struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli);
+ struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli);
+
+ if (cbc->client.sock_name)
+ talloc_free(cbc->client.sock_name);
+ cbc->client.sock_name = osmo_sock_get_name(cbc, ofd->fd);
+
+ LOGP(DCBS, LOGL_NOTICE, "CBSP Client connected to CBC: %s\n", cbc->client.sock_name);
+
+ /* TODO: introduce ourselves to the peer using some osmcoom extensions */
+ cbsp_tx_restart(cbc, false);
+
+ return 0;
+}
+
+static int cbsp_client_disconnect_cb(struct osmo_stream_cli *cli)
+{
+ struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli);
+
+ LOGP(DCBS, LOGL_NOTICE, "CBSP Client lost connection to %s\n", cbc->client.sock_name);
+ talloc_free(cbc->client.sock_name);
+ cbc->client.sock_name = NULL;
+ return 0;
+}
+
+static int cbsp_client_read_cb(struct osmo_stream_cli *cli)
+{
+ struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli);
+ struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli);
+ struct osmo_cbsp_decoded *decoded;
+ struct msgb *msg = NULL;
+ int rc;
+
+ /* READ */
+ rc = osmo_cbsp_recv_buffered(cbc, ofd->fd, &msg, &cbc->client.msg);
+ if (rc <= 0) {
+ if (rc == -EAGAIN || rc == -EINTR) {
+ /* more data needs to be read */
+ return 0;
+ } else if (rc == -EPIPE || rc == -ECONNRESET) {
+ /* lost connection */
+ } else if (rc == 0) {
+ /* connection closed */
+ }
+ osmo_stream_cli_reconnect(cli);
+ return -EBADF;
+ }
+ OSMO_ASSERT(msg);
+ decoded = osmo_cbsp_decode(cli, msg);
+ if (decoded) {
+ LOGP(DCBS, LOGL_DEBUG, "Received CBSP %s\n",
+ get_value_string(cbsp_msg_type_names, decoded->msg_type));
+ cbsp_rx_decoded(cbc, decoded);
+ talloc_free(decoded);
+ } else {
+ LOGP(DCBS, LOGL_ERROR, "Unable to decode CBSP %s: '%s'\n",
+ msgb_hexdump(msg), osmo_cbsp_errstr);
+ }
+ msgb_free(msg);
+ return 0;
+}
+
+int bsc_cbc_link_restart(void)
+{
+ struct bsc_cbc_link *cbc = bsc_gsmnet->bsc_data->cbc;
+
+ /* shut down client, if no longer configured */
+ if (cbc->client.cli && !cbc->config.cbc_hostname) {
+ LOGP(DCBS, LOGL_NOTICE, "Stopping CBSP client\n");
+ osmo_stream_cli_close(cbc->client.cli);
+ osmo_stream_cli_destroy(cbc->client.cli);
+ cbc->client.cli = NULL;
+ }
+
+ /* shut down server, if no longer configured */
+ if (cbc->config.listen_port == -1) {
+ if (cbc->server.srv || cbc->server.link)
+ LOGP(DCBS, LOGL_NOTICE, "Stopping CBSP server\n");
+ if (cbc->server.srv) {
+ osmo_stream_srv_destroy(cbc->server.srv);
+ cbc->server.srv = NULL;
+ }
+ if (cbc->server.link) {
+ osmo_stream_srv_link_close(cbc->server.link);
+ osmo_stream_srv_link_destroy(cbc->server.link);
+ cbc->server.link = NULL;
+ }
+ }
+
+ /* start client, if configured */
+ if (cbc->config.cbc_hostname) {
+ LOGP(DCBS, LOGL_NOTICE, "Starting CBSP Client (to CBC at %s:%u)\n",
+ cbc->config.cbc_hostname, cbc->config.cbc_port);
+ if (!cbc->client.cli) {
+ cbc->client.cli = osmo_stream_cli_create(cbc);
+ osmo_stream_cli_set_data(cbc->client.cli, cbc);
+ osmo_stream_cli_set_connect_cb(cbc->client.cli, cbsp_client_connect_cb);
+ osmo_stream_cli_set_disconnect_cb(cbc->client.cli, cbsp_client_disconnect_cb);
+ osmo_stream_cli_set_read_cb(cbc->client.cli, cbsp_client_read_cb);
+ }
+ /* CBC side */
+ osmo_stream_cli_set_addr(cbc->client.cli, cbc->config.cbc_hostname);
+ osmo_stream_cli_set_port(cbc->client.cli, cbc->config.cbc_port);
+ /* Close/Reconnect? */
+ osmo_stream_cli_open(cbc->client.cli);
+ }
+
+ /* start server, if configured */
+ if (cbc->config.listen_port != -1) {
+ LOGP(DCBS, LOGL_NOTICE, "Starting CBSP Server (bound to %s:%u)\n",
+ cbc->config.listen_hostname, cbc->config.listen_port);
+ if (!cbc->server.srv) {
+ cbc->server.link = osmo_stream_srv_link_create(cbc);
+ osmo_stream_srv_link_set_data(cbc->server.link, cbc);
+ osmo_stream_srv_link_set_accept_cb(cbc->server.link, cbsp_srv_link_accept_cb);
+ }
+ osmo_stream_srv_link_set_addr(cbc->server.link, cbc->config.listen_hostname);
+ osmo_stream_srv_link_set_port(cbc->server.link, cbc->config.listen_port);
+ }
+ return 0;
+}
+
+/*! Encode + Transmit a 'decoded' CBSP message over given CBC link
+ * \param[in] cbc Data structure representing the BSCs link to the CBC
+ * \param[in] cbsp Decoded CBSP message to be transmitted. Ownership is transferred.
+ * \return 0 on success, negative otherwise */
+int cbsp_tx_decoded(struct bsc_cbc_link *cbc, struct osmo_cbsp_decoded *cbsp)
+{
+ struct msgb *msg;
+
+ msg = osmo_cbsp_encode(cbc, cbsp);
+ if (!msg) {
+ LOGP(DCBS, LOGL_ERROR, "Unable to encode CBSP Message Type %s: %s\n",
+ get_value_string(cbsp_msg_type_names, cbsp->msg_type), osmo_cbsp_errstr);
+ talloc_free(cbsp);
+ return -1;
+ }
+ if (cbc->client.cli)
+ osmo_stream_cli_send(cbc->client.cli, msg);
+ else if (cbc->server.srv)
+ osmo_stream_srv_send(cbc->server.srv, msg);
+ else {
+ LOGP(DCBS, LOGL_ERROR, "Discarding CBSP Message, link is down: %s\n", msgb_hexdump(msg));
+ msgb_free(msg);
+ }
+
+ talloc_free(cbsp);
+ return 0;
+}
+
+static struct bsc_cbc_link *vty_cbc_data(struct vty *vty)
+{
+ return bsc_gsmnet->bsc_data->cbc;
+}
+
+/*********************************************************************************
+ * VTY Interface (Configuration + Introspection)
+ *********************************************************************************/
+
+DEFUN(cfg_cbc, cfg_cbc_cmd,
+ "cbc", "Configure CBSP Link to Cell Broadcast Centre\n")
+{
+ vty->node = CBC_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_remote_ip, cfg_cbc_remote_ip_cmd,
+ "remote-ip A.B.C.D",
+ "IP Address of the Cell Broadcast Centre\n"
+ "IP Address of the Cell Broadcast Centre\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ osmo_talloc_replace_string(cbc, &cbc->config.cbc_hostname, argv[0]);
+ return CMD_SUCCESS;
+}
+DEFUN(cfg_cbc_no_remote_ip, cfg_cbc_no_remote_ip_cmd,
+ "no remote-ip",
+ NO_STR "Remove IP address of CBC; disables outbound CBSP connections\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ talloc_free(cbc->config.cbc_hostname);
+ cbc->config.cbc_hostname = NULL;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_remote_port, cfg_cbc_remote_port_cmd,
+ "remote-port <1-65535>",
+ "TCP Port number of the Cell Broadcast Centre (Default: 48049)\n"
+ "TCP Port number of the Cell Broadcast Centre (Default: 48049)\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ cbc->config.cbc_port = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_listen_port, cfg_cbc_listen_port_cmd,
+ "listen-port <1-65535>",
+ "Local TCP port at which BSC listens for incoming CBSP connections from CBC\n"
+ "Local TCP port at which BSC listens for incoming CBSP connections from CBC\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ cbc->config.listen_port = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+DEFUN(cfg_cbc_no_listen_port, cfg_cbc_no_listen_port_cmd,
+ "no listen-port",
+ NO_STR "Remove CBSP Listen Port; disables inbound CBSP connections\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ cbc->config.listen_port = -1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_listen_ip, cfg_cbc_listen_ip_cmd,
+ "listen-ip A.B.C.D",
+ "Local IP Address where BSC listens for incoming CBC connections (Default: 0.0.0.0)\n"
+ "Local IP Address where BSC listens for incoming CBC connections\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ osmo_talloc_replace_string(cbc, &cbc->config.listen_hostname, argv[0]);
+ return CMD_SUCCESS;
+}
+
+static struct cmd_node cbc_node = {
+ CBC_NODE,
+ "%s(config-cbc)# ",
+ 1,
+};
+
+static int config_write_cbc(struct vty *vty)
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+
+ vty_out(vty, "cbc%s", VTY_NEWLINE);
+
+ if (cbc->config.cbc_hostname) {
+ vty_out(vty, " remote-ip %s%s", cbc->config.cbc_hostname, VTY_NEWLINE);
+ vty_out(vty, " remote-port %u%s", cbc->config.cbc_port, VTY_NEWLINE);
+ } else
+ vty_out(vty, " no remote-ip%s", VTY_NEWLINE);
+
+ if (cbc->config.listen_port >= 0) {
+ vty_out(vty, " listen-port %u%s", cbc->config.listen_port, VTY_NEWLINE);
+ vty_out(vty, " listen-ip %s%s", cbc->config.listen_hostname, VTY_NEWLINE);
+ } else
+ vty_out(vty, " no listen-port%s", VTY_NEWLINE);
+
+ return 0;
+}
+
+DEFUN(show_cbc, show_cbc_cmd,
+ "show cbc",
+ SHOW_STR "Display state of CBC / CBSP\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+
+ if (!cbc->config.cbc_hostname)
+ vty_out(vty, "CBSP Client Config: Disabled%s", VTY_NEWLINE);
+ else {
+ vty_out(vty, "CBSP Client Config: CBC IP=%s, CBC Port=%u%s",
+ cbc->config.cbc_hostname, cbc->config.cbc_port, VTY_NEWLINE);
+ vty_out(vty, "CBSP Client Connection: %s%s",
+ cbc->client.sock_name ? cbc->client.sock_name : "Disconnected", VTY_NEWLINE);
+ }
+ if (cbc->config.listen_port < 0)
+ vty_out(vty, "CBSP Server Config: Disabled%s\n", VTY_NEWLINE);
+ else {
+ vty_out(vty, "CBSP Server Config: Listen IP=%s, Port=%u%s\n",
+ cbc->config.listen_hostname, cbc->config.listen_port, VTY_NEWLINE);
+ vty_out(vty, "CBSP Server Connection: %s%s",
+ cbc->server.sock_name ? cbc->server.sock_name : "Disconnected", VTY_NEWLINE);
+ }
+ return CMD_SUCCESS;
+}
+
+void cbc_vty_init(void)
+{
+ install_element(VIEW_NODE, &show_cbc_cmd);
+ install_element(CONFIG_NODE, &cfg_cbc_cmd);
+ install_node(&cbc_node, config_write_cbc);
+ install_element(CBC_NODE, &cfg_cbc_remote_ip_cmd);
+ install_element(CBC_NODE, &cfg_cbc_no_remote_ip_cmd);
+ install_element(CBC_NODE, &cfg_cbc_remote_port_cmd);
+ install_element(CBC_NODE, &cfg_cbc_listen_port_cmd);
+ install_element(CBC_NODE, &cfg_cbc_no_listen_port_cmd);
+ install_element(CBC_NODE, &cfg_cbc_listen_ip_cmd);
+}
diff --git a/src/osmo-bsc/gsm_data.c b/src/osmo-bsc/gsm_data.c
index ea338173e..c2cfacf01 100644
--- a/src/osmo-bsc/gsm_data.c
+++ b/src/osmo-bsc/gsm_data.c
@@ -767,6 +767,12 @@ static const struct gprs_rlc_cfg rlc_cfg_default = {
.initial_mcs = 6,
};
+static void bts_init_cbch_state(struct bts_smscb_chan_state *cstate, struct gsm_bts *bts)
+{
+ cstate->bts = bts;
+ INIT_LLIST_HEAD(&cstate->messages);
+}
+
/* Initialize those parts that don't require osmo-bsc specific dependencies.
* This part is shared among the thin programs in osmo-bsc/src/utils/.
* osmo-bsc requires further initialization that pulls in more dependencies (see
@@ -945,6 +951,9 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num)
}
bts->mr_half.num_modes = 3;
+ bts_init_cbch_state(&bts->cbch_basic, bts);
+ bts_init_cbch_state(&bts->cbch_extended, bts);
+
return bts;
}
diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c
index aba8c40c7..dacd61a08 100644
--- a/src/osmo-bsc/osmo_bsc_main.c
+++ b/src/osmo-bsc/osmo_bsc_main.c
@@ -37,6 +37,7 @@
#include <osmocom/bsc/bsc_subscriber.h>
#include <osmocom/bsc/assignment_fsm.h>
#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/smscb.h>
#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/ctrl/control_if.h>
@@ -310,6 +311,10 @@ static void bootstrap_rsl(struct gsm_bts_trx *trx)
OSMO_ASSERT(ts->fi);
osmo_fsm_inst_dispatch(ts->fi, TS_EV_RSL_READY, NULL);
}
+
+ /* Start CBCH transmit timer if CBCH is present */
+ if (trx->nr == 0 && gsm_bts_get_cbch(trx->bts))
+ bts_cbch_timer_schedule(trx->bts);
}
static void all_ts_dispatch_event(struct gsm_bts_trx *trx, uint32_t event)
@@ -379,6 +384,8 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal,
rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_RSL_FAIL]);
acc_ramp_abort(&trx->bts->acc_ramp);
all_ts_dispatch_event(trx, TS_EV_RSL_DOWN);
+ if (trx->nr == 0)
+ osmo_timer_del(&trx->bts->cbch_timer);
}
gsm_bts_mo_reset(trx->bts);
@@ -764,6 +771,11 @@ static const struct log_info_cat osmo_bsc_categories[] = {
.description = "Local Call, Local Switch",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
+ [DCBS] = {
+ .name = "DCBS",
+ .description = "Cell Broadcast System",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ }
};
@@ -912,6 +924,7 @@ int main(int argc, char **argv)
handover_decision_1_init();
hodec2_init(bsc_gsmnet);
+ bsc_cbc_link_restart();
signal(SIGINT, &signal_handler);
signal(SIGTERM, &signal_handler);
diff --git a/src/osmo-bsc/smscb.c b/src/osmo-bsc/smscb.c
new file mode 100644
index 000000000..6b9608652
--- /dev/null
+++ b/src/osmo-bsc/smscb.c
@@ -0,0 +1,823 @@
+/* SMSCB (SMS Cell Broadcast) Handling for OsmoBSC */
+/*
+ * (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <limits.h>
+
+#include <osmocom/core/stats.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/gsm/cbsp.h>
+#include <osmocom/gsm/protocol/gsm_23_041.h>
+#include <osmocom/gsm/protocol/gsm_48_049.h>
+
+#include <osmocom/netif/stream.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/vty.h>
+
+/*********************************************************************************
+ * Helper Functions
+ *********************************************************************************/
+
+/* replace the old head of an entire list with a new head; effectively moves the entire
+ * list from old to new head */
+static void llist_replace_head(struct llist_head *new, struct llist_head *old)
+{
+ if (llist_empty(old))
+ INIT_LLIST_HEAD(new);
+ else
+ __llist_add(new, old->prev, old->next);
+ INIT_LLIST_HEAD(old);
+}
+
+/*! Obtain SMSCB Channel State for given BTS (basic or extended CBCH) */
+struct bts_smscb_chan_state *bts_get_smscb_chan(struct gsm_bts *bts, bool extended)
+{
+ struct bts_smscb_chan_state *chan_state;
+
+ if (extended)
+ chan_state = &bts->cbch_extended;
+ else
+ chan_state = &bts->cbch_basic;
+
+ return chan_state;
+}
+
+/* do an ordered list insertion. we keep the list with increasing period, i.e. the most
+ * frequent message first */
+static void __bts_smscb_add(struct bts_smscb_chan_state *cstate, struct bts_smscb_message *new)
+{
+ struct bts_smscb_message *tmp, *tmp2;
+
+ if (llist_empty(&cstate->messages)) {
+ llist_add(&new->list, &cstate->messages);
+ return;
+ }
+
+ llist_for_each_entry_safe(tmp, tmp2, &cstate->messages, list) {
+ if (tmp->input.rep_period > new->input.rep_period) {
+ /* we found the first message with longer period than the new message,
+ * we must insert ourselves before that one */
+ __llist_add(&new->list, tmp->list.prev, &tmp->list);
+ return;
+ }
+ }
+}
+
+/* stringify a SMSCB for logging */
+const char *bts_smscb_msg2str(const struct bts_smscb_message *smscb)
+{
+ static char buf[128];
+ snprintf(buf, sizeof(buf), "MsgId=0x%04x/SerialNr=0x%04x/Pages=%u/Period=%u/NumBcastReq=%u",
+ smscb->input.msg_id, smscb->input.serial_nr, smscb->num_pages,
+ smscb->input.rep_period, smscb->input.num_bcast_req);
+ return buf;
+}
+
+const char *bts_smscb_chan_state_name(const struct bts_smscb_chan_state *cstate)
+{
+ if (cstate == &cstate->bts->cbch_basic)
+ return "BASIC";
+ else if (cstate == &cstate->bts->cbch_extended)
+ return "EXTENDED";
+ else
+ return "UNKNOWN";
+}
+
+unsigned int bts_smscb_chan_load_percent(const struct bts_smscb_chan_state *cstate)
+{
+ unsigned int sched_arr_used = 0;
+ unsigned int i;
+
+ if (cstate->sched_arr_size == 0)
+ return 0;
+
+ /* count the number of used slots */
+ for (i = 0; i < cstate->sched_arr_size; i++) {
+ if (cstate->sched_arr[i])
+ sched_arr_used++;
+ }
+
+ OSMO_ASSERT(sched_arr_used <= UINT_MAX/100);
+ return (sched_arr_used * 100) / cstate->sched_arr_size;
+}
+
+unsigned int bts_smscb_chan_page_count(const struct bts_smscb_chan_state *cstate)
+{
+ struct bts_smscb_message *smscb;
+ unsigned int page_count = 0;
+
+ llist_for_each_entry(smscb, &cstate->messages, list)
+ page_count += smscb->num_pages;
+
+ return page_count;
+}
+
+
+/*! Obtain the Cell Global Identifier (CGI) of given BTS; returned in static buffer. */
+static struct osmo_cell_global_id *bts_get_cgi(struct gsm_bts *bts)
+{
+ static struct osmo_cell_global_id cgi;
+ cgi.lai.plmn = bts->network->plmn;
+ cgi.lai.lac = bts->location_area_code;
+ cgi.cell_identity = bts->cell_identity;
+ return &cgi;
+}
+
+/* represents the various lists that the BSC can create as part of a response */
+struct response_state {
+ struct osmo_cbsp_cell_list success; /* osmo_cbsp_cell_ent */
+ struct llist_head fail; /* osmo_cbsp_fail_ent */
+ struct osmo_cbsp_num_compl_list num_completed; /* osmo_cbsp_num_compl_ent */
+ struct osmo_cbsp_loading_list loading; /* osmo_cbsp_loading_ent */
+};
+
+/*! per-BTS callback function used by cbsp_per_bts().
+ * \param[in] bts BTS currently being processed
+ * \param[in] dec decoded CBSP message currently being processed
+ * \param r_state response state accumulating cell lists (success/failure/...)
+ * \param priv opaque private data provided by caller of cbsp_per_bts()
+ * \returns 0 on success; negative TS 48.049 cause value on error */
+typedef int bts_cb_fn(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
+ struct response_state *r_state, void *priv);
+
+/* append a success for given cell to response state */
+static void append_success(struct response_state *r_state, struct gsm_bts *bts)
+{
+ struct osmo_cbsp_cell_ent *cent = talloc_zero(r_state, struct osmo_cbsp_cell_ent);
+ struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
+
+ LOG_BTS(bts, DCBS, LOGL_INFO, "Success\n");
+
+ OSMO_ASSERT(cent);
+
+ cent->cell_id.global = *cgi;
+ llist_add_tail(&cent->list, &r_state->success.list);
+}
+
+/* append a failure for given cell to response state */
+static void append_fail(struct response_state *r_state, struct gsm_bts *bts, uint8_t cause)
+{
+ struct osmo_cbsp_fail_ent *fent = talloc_zero(r_state, struct osmo_cbsp_fail_ent);
+ struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
+
+ LOG_BTS(bts, DCBS, LOGL_NOTICE, "Failure Cause 0x%02x\n", cause);
+
+ OSMO_ASSERT(fent);
+
+ fent->id_discr = CELL_IDENT_WHOLE_GLOBAL;
+ fent->cell_id.global = *cgi;
+ fent->cause = cause;
+ llist_add_tail(&fent->list, &r_state->fail);
+}
+
+/* append a 'number of broadcasts completed' for given cell to response state */
+static void append_bcast_compl(struct response_state *r_state, struct gsm_bts *bts,
+ struct bts_smscb_message *smscb)
+{
+ struct osmo_cbsp_num_compl_ent *cent = talloc_zero(r_state, struct osmo_cbsp_num_compl_ent);
+ struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
+
+ LOG_BTS(bts, DCBS, LOGL_DEBUG, "Number of Broadcasts Completed: %u\n", smscb->bcast_count);
+
+ OSMO_ASSERT(cent);
+
+ r_state->num_completed.id_discr = CELL_IDENT_WHOLE_GLOBAL;
+ cent->cell_id.global = *cgi;
+ if (smscb->bcast_count > INT16_MAX) {
+ cent->num_compl = INT16_MAX;
+ cent->num_bcast_info = 0x01; /* Overflow */
+ } else {
+ cent->num_compl = smscb->bcast_count;
+ cent->num_bcast_info = 0x00;
+ }
+ llist_add_tail(&cent->list, &r_state->num_completed.list);
+}
+
+/*! Iterate over all BTSs, find matching ones, execute command on BTS, add result
+ * to succeeded/failed lists.
+ * \param[in] net GSM network in which we operate
+ * \param[in] caller-allocated Response state structure collecting results
+ * \param[in] cell_list Decoded CBSP cell list describing BTSs to operate on
+ * \param[in] cb_fn Call-back function to call for each matching BTS
+ * \param[in] priv Opqaue private data; passed to cb_fn
+ * */
+static int cbsp_per_bts(struct gsm_network *net, struct response_state *r_state,
+ const struct osmo_cbsp_cell_list *cell_list,
+ bts_cb_fn *cb_fn, const struct osmo_cbsp_decoded *dec, void *priv)
+{
+ struct osmo_cbsp_cell_ent *ent;
+ struct gsm_bts *bts;
+ uint8_t bts_status[net->num_bts];
+ int rc, ret = 0;
+
+ memset(bts_status, 0, sizeof(bts_status));
+ INIT_LLIST_HEAD(&r_state->success.list);
+ INIT_LLIST_HEAD(&r_state->fail);
+ INIT_LLIST_HEAD(&r_state->num_completed.list);
+ INIT_LLIST_HEAD(&r_state->loading.list);
+
+ /* special case as cell_list->list is empty in this case */
+ if (cell_list->id_discr == CELL_IDENT_BSS) {
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ bts_status[bts->nr] = 1;
+ /* call function on this BTS */
+ rc = cb_fn(bts, dec, r_state, priv);
+ if (rc < 0) {
+ append_fail(r_state, bts, -rc);
+ ret = -1;
+ } else
+ append_success(r_state, bts);
+ }
+ } else {
+ /* normal case: iterate over cell list */
+ llist_for_each_entry(ent, &cell_list->list, list) {
+ bool found_at_least_one = false;
+ /* find all matching BTSs for this entry */
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ struct gsm0808_cell_id cell_id = {
+ .id_discr = cell_list->id_discr,
+ .id = ent->cell_id
+ };
+ if (!gsm_bts_matches_cell_id(bts, &cell_id))
+ continue;
+ found_at_least_one = true;
+ /* skip any BTSs which we've already processed */
+ if (bts_status[bts->nr])
+ continue;
+ bts_status[bts->nr] = 1;
+ /* call function on this BTS */
+ rc = cb_fn(bts, dec, r_state, priv);
+ if (rc < 0) {
+ append_fail(r_state, bts, -rc);
+ ret = -1;
+ } else
+ append_success(r_state, bts);
+ }
+ if (!found_at_least_one) {
+ struct osmo_cbsp_fail_ent *fent;
+ LOGP(DCBS, LOGL_NOTICE, "CBSP: Couldn't find a single matching BTS\n");
+ fent = talloc_zero(r_state, struct osmo_cbsp_fail_ent);
+ OSMO_ASSERT(fent);
+ fent->id_discr = cell_list->id_discr;
+ fent->cell_id = ent->cell_id;
+ llist_add_tail(&fent->list, &r_state->fail);
+ ret = -1;
+ }
+ }
+ }
+ return ret;
+}
+
+/*! Find an existing SMSCB message within given BTS.
+ * \param[in] chan_state BTS CBCH channel state
+ * \param[in] msg_id Message Id of to-be-found message
+ * \param[in] serial_nr Serial Number of to-be-found message
+ * \returns SMSCB message if found; NULL otherwise */
+struct bts_smscb_message *bts_find_smscb(struct bts_smscb_chan_state *chan_state,
+ uint16_t msg_id, uint16_t serial_nr)
+{
+ struct bts_smscb_message *smscb;
+
+ llist_for_each_entry(smscb, &chan_state->messages, list) {
+ if (smscb->input.msg_id == msg_id && smscb->input.serial_nr == serial_nr)
+ return smscb;
+ }
+ return NULL;
+}
+
+/*! create a new SMSCB message for specified BTS; don't link it yet.
+ * \param[in] bts BTS for which the SMSCB is to be allocated
+ * \param[in] wrepl CBSP write-replace message
+ * \returns callee-allocated SMSCB message filled with data from wrepl */
+static struct bts_smscb_message *bts_smscb_msg_from_wrepl(struct gsm_bts *bts,
+ const struct osmo_cbsp_write_replace *wrepl)
+{
+ struct bts_smscb_message *smscb = talloc_zero(bts, struct bts_smscb_message);
+ struct osmo_cbsp_content *cont;
+ int i;
+
+ if (!smscb)
+ return NULL;
+
+ OSMO_ASSERT(wrepl->is_cbs);
+
+ /* initialize all pages inside the message */
+ for (i = 0; i < ARRAY_SIZE(smscb->page); i++) {
+ struct bts_smscb_page *page = &smscb->page[i];
+ page->nr = i+1; /* page numbers are 1-based */
+ page->msg = smscb;
+ }
+
+ /* initialize "header" part */
+ smscb->input.msg_id = wrepl->msg_id;
+ smscb->input.serial_nr = wrepl->new_serial_nr;
+ smscb->input.category = wrepl->u.cbs.category;
+ smscb->input.rep_period = wrepl->u.cbs.rep_period;
+ smscb->input.num_bcast_req = wrepl->u.cbs.num_bcast_req;
+ smscb->input.dcs = wrepl->u.cbs.dcs;
+ smscb->num_pages = llist_count(&wrepl->u.cbs.msg_content);
+ if (smscb->num_pages > ARRAY_SIZE(smscb->page)) {
+ LOG_BTS(bts, DCBS, LOGL_ERROR, "SMSCB with too many pages (%u > %lu)\n",
+ smscb->num_pages, ARRAY_SIZE(smscb->page));
+ talloc_free(smscb);
+ return NULL;
+ }
+
+ i = 0;
+ llist_for_each_entry(cont, &wrepl->u.cbs.msg_content, list) {
+ struct gsm23041_msg_param_gsm *msg_param;
+ struct bts_smscb_page *page;
+ size_t bytes_used;
+
+ /* we have just ensured a few lines above that this cannot overflow */
+ page = &smscb->page[i++];
+ msg_param = (struct gsm23041_msg_param_gsm *) &page->data[0];
+
+ /* build 6 byte header according to TS 23.041 9.4.1.2 */
+ osmo_store16be(wrepl->new_serial_nr, &msg_param->serial_nr);
+ osmo_store16be(wrepl->msg_id, &msg_param->message_id);
+ msg_param->dcs = wrepl->u.cbs.dcs;
+ msg_param->page_param.num_pages = smscb->num_pages;
+ msg_param->page_param.page_nr = page->nr;
+
+ OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(cont->data));
+ OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(page->data) - sizeof(*msg_param));
+ memcpy(&msg_param->content, cont->data, cont->user_len);
+ bytes_used = sizeof(*msg_param) + cont->user_len;
+ /* compute number of valid blocks in page */
+ page->num_blocks = bytes_used / 22;
+ if (bytes_used % 22)
+ page->num_blocks += 1;
+ }
+
+ return smscb;
+}
+
+/*! remove a SMSCB message */
+void bts_smscb_del(struct bts_smscb_message *smscb, struct bts_smscb_chan_state *cstate,
+ const char *reason)
+{
+ struct bts_smscb_page **arr;
+ int rc;
+
+ LOG_BTS(cstate->bts, DCBS, LOGL_INFO, "%s Deleting %s (Reason: %s)\n",
+ bts_smscb_chan_state_name(cstate), bts_smscb_msg2str(smscb), reason);
+ llist_del(&smscb->list);
+
+ /* we must recompute the scheduler array here, as the old one will have pointers
+ * to the pages of the just-to-be-deleted message */
+ rc = bts_smscb_gen_sched_arr(cstate, &arr);
+ if (rc < 0) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Cannot generate new CBCH scheduler array after "
+ "removing message %s. WTF?\n", bts_smscb_msg2str(smscb));
+ /* we cannot free the message now, to ensure the page pointers in the old
+ * array are still valid. let's re-add it to keep things sane */
+ __bts_smscb_add(cstate, smscb);
+ } else {
+ /* success */
+ talloc_free(smscb);
+
+ /* replace array with new one */
+ talloc_free(cstate->sched_arr);
+ cstate->sched_arr = arr;
+ cstate->sched_arr_size = rc;
+ cstate->next_idx = 0;
+ }
+}
+
+
+/*********************************************************************************
+ * Transmit of CBSP to CBC
+ *********************************************************************************/
+
+/* transmit a CBSP RESTART message stating all message data was lost for entire BSS */
+int cbsp_tx_restart(struct bsc_cbc_link *cbc, bool is_emerg)
+{
+ struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESTART);
+
+ if (is_emerg)
+ cbsp->u.restart.bcast_msg_type = 0x01;
+ cbsp->u.restart.recovery_ind = 0x01; /* message data lost */
+ cbsp->u.restart.cell_list.id_discr = CELL_IDENT_BSS;
+
+ return cbsp_tx_decoded(cbc, cbsp);
+}
+
+/* transmit a CBSP KEEPALIVE COMPLETE to the CBC */
+static int tx_cbsp_keepalive_compl(struct bsc_cbc_link *cbc)
+{
+ struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KEEP_ALIVE_COMPL);
+ return cbsp_tx_decoded(cbc, cbsp);
+}
+
+/*********************************************************************************
+ * Per-BTS Processing of CBSP from CBC, called via cbsp_per_bts()
+ *********************************************************************************/
+
+/*! Try to execute a write-replace operation; roll-back if it fails.
+ * \param[in] chan_state BTS CBCH channel state
+ * \param[in] extended_cbch Basic (false) or Extended (true) CBCH
+ * \param[in] new_msg New SMSCB message which should be added
+ * \param[in] exclude_msg Existing SMSCB message that shall be replaced (if possible). Can be NULL
+ * \return 0 on success; negative on error */
+static int bts_try_write_replace(struct bts_smscb_chan_state *chan_state,
+ struct bts_smscb_message *new_msg,
+ struct bts_smscb_message *exclude_msg,
+ struct response_state *r_state)
+{
+ struct bts_smscb_page **arr;
+ int rc;
+
+ if (exclude_msg) {
+ /* temporarily remove from list of SMSCB */
+ llist_del(&exclude_msg->list);
+ }
+ /* temporarily add new_msg to list of SMSCB */
+ __bts_smscb_add(chan_state, new_msg);
+
+ /* attempt to create scheduling array */
+ rc = bts_smscb_gen_sched_arr(chan_state, &arr);
+ if (rc < 0) {
+ /* it didn't work out; we couldn't schedule it */
+ /* remove the new message again */
+ llist_del(&new_msg->list);
+ /* up to the caller to free() it */
+ if (exclude_msg) {
+ /* re-add the temporarily removed message */
+ __bts_smscb_add(chan_state, new_msg);
+ }
+ return -1;
+ }
+
+ /* success! */
+ if (exclude_msg) {
+ LOG_BTS(chan_state->bts, DCBS, LOGL_INFO, "%s Replaced MsgId=0x%04x/Serial=0x%04x, "
+ "pages(%u -> %u), period(%u -> %u), num_bcast(%u -> %u)\n",
+ bts_smscb_chan_state_name(chan_state),
+ new_msg->input.msg_id, new_msg->input.serial_nr,
+ exclude_msg->num_pages, new_msg->num_pages,
+ exclude_msg->input.rep_period, new_msg->input.rep_period,
+ exclude_msg->input.num_bcast_req, new_msg->input.num_bcast_req);
+ append_bcast_compl(r_state, chan_state->bts, exclude_msg);
+ talloc_free(exclude_msg);
+ } else
+ LOG_BTS(chan_state->bts, DCBS, LOGL_INFO, "%s Added %s\n",
+ bts_smscb_chan_state_name(chan_state), bts_smscb_msg2str(new_msg));
+
+ /* replace array with new one */
+ talloc_free(chan_state->sched_arr);
+ chan_state->sched_arr = arr;
+ chan_state->sched_arr_size = rc;
+ chan_state->next_idx = 0;
+ return 0;
+}
+
+
+static int bts_rx_write_replace(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
+ struct response_state *r_state, void *priv)
+{
+ const struct osmo_cbsp_write_replace *wrepl = &dec->u.write_replace;
+ bool extended_cbch = wrepl->u.cbs.channel_ind;
+ struct bts_smscb_chan_state *chan_state = bts_get_smscb_chan(bts, extended_cbch);
+ struct bts_smscb_message *smscb;
+ int rc;
+
+ if (!wrepl->is_cbs) {
+ LOG_BTS(bts, DCBS, LOGL_ERROR, "(Primary) Emergency Message not supported\n");
+ return -CBSP_CAUSE_CB_NOT_SUPPORTED;
+ }
+
+ /* check if cell has a CBCH at all */
+ if (!gsm_bts_get_cbch(bts))
+ return -CBSP_CAUSE_CB_NOT_SUPPORTED;
+
+ /* check for duplicate */
+ if (bts_find_smscb(chan_state, wrepl->msg_id, wrepl->new_serial_nr))
+ return -CBSP_CAUSE_MSG_REF_ALREADY_USED;
+
+ if (!wrepl->old_serial_nr) { /* new message */
+ /* create new message */
+ smscb = bts_smscb_msg_from_wrepl(bts, wrepl);
+ if (!smscb)
+ return -CBSP_CAUSE_BSC_MEMORY_EXCEEDED;
+ /* check if scheduling permits this additional message */
+ rc = bts_try_write_replace(chan_state, smscb, NULL, r_state);
+ if (rc < 0) {
+ talloc_free(smscb);
+ return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED;
+ }
+ } else { /* modify / replace existing message */
+ struct bts_smscb_message *smscb_old;
+ /* find existing message */
+ smscb_old = bts_find_smscb(chan_state, wrepl->msg_id, *wrepl->old_serial_nr);
+ if (!smscb_old)
+ return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
+ /* create new message */
+ smscb = bts_smscb_msg_from_wrepl(bts, wrepl);
+ if (!smscb)
+ return -CBSP_CAUSE_BSC_MEMORY_EXCEEDED;
+ /* check if scheduling permits this modified message */
+ rc = bts_try_write_replace(chan_state, smscb, smscb_old, r_state);
+ if (rc < 0) {
+ talloc_free(smscb);
+ return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED;
+ }
+ }
+ return 0;
+}
+
+static int bts_rx_kill(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
+ struct response_state *r_state, void *priv)
+{
+ const struct osmo_cbsp_kill *kill = &dec->u.kill;
+ struct bts_smscb_chan_state *chan_state;
+ struct bts_smscb_message *smscb;
+ bool extended = false;
+
+ if (kill->channel_ind && *kill->channel_ind == 0x01)
+ extended = true;
+ chan_state = bts_get_smscb_chan(bts, extended);
+
+ /* Find message by msg_id + old_serial_nr */
+ smscb = bts_find_smscb(chan_state, kill->msg_id, kill->old_serial_nr);
+ if (!smscb)
+ return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
+
+ /* Remove it */
+ bts_smscb_del(smscb, chan_state, "KILL");
+ return 0;
+}
+
+static int bts_rx_reset(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
+ struct response_state *r_state, void *priv)
+{
+ struct bts_smscb_chan_state *chan_state;
+ struct bts_smscb_message *smscb, *smscb2;
+
+ /* remove all SMSCB from CBCH BASIC this BTS */
+ chan_state = bts_get_smscb_chan(bts, false);
+ llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list)
+ bts_smscb_del(smscb, chan_state, "RESET");
+
+ /* remove all SMSCB from CBCH EXTENDED this BTS */
+ chan_state = bts_get_smscb_chan(bts, true);
+ llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list)
+ bts_smscb_del(smscb, chan_state, "RESET");
+
+ return 0;
+}
+
+/*********************************************************************************
+ * Receive of CBSP from CBC
+ *********************************************************************************/
+
+static int cbsp_rx_write_replace(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ const struct osmo_cbsp_write_replace *wrepl = &dec->u.write_replace;
+ struct gsm_network *net = cbc->net;
+ struct response_state *r_state = talloc_zero(cbc, struct response_state);
+ struct osmo_cbsp_decoded *resp;
+ enum cbsp_channel_ind channel_ind;
+ int rc;
+
+ LOGP(DCBS, LOGL_INFO, "CBSP Rx WRITE_REPLACE (%s)\n", wrepl->is_cbs ? "CBS" : "EMERGENCY");
+
+ rc = cbsp_per_bts(net, r_state, &dec->u.write_replace.cell_list,
+ bts_rx_write_replace, dec, NULL);
+ /* generate response */
+ if (rc < 0) {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_WRITE_REPLACE_FAIL);
+ struct osmo_cbsp_write_replace_failure *fail = &resp->u.write_replace_fail;
+ fail->msg_id = wrepl->msg_id;
+ fail->new_serial_nr = wrepl->new_serial_nr;
+ fail->old_serial_nr = wrepl->old_serial_nr;
+ llist_replace_head(&fail->fail_list, &r_state->fail);
+ fail->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&fail->cell_list.list, &r_state->success.list);
+ if (wrepl->is_cbs) {
+ channel_ind = wrepl->u.cbs.channel_ind;
+ fail->channel_ind = &channel_ind;
+ }
+ if (wrepl->old_serial_nr) {
+ fail->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list);
+ }
+ } else {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_WRITE_REPLACE_COMPL);
+ struct osmo_cbsp_write_replace_complete *compl = &resp->u.write_replace_compl;
+ compl->msg_id = wrepl->msg_id;
+ compl->new_serial_nr = wrepl->new_serial_nr;
+ compl->old_serial_nr = wrepl->old_serial_nr;
+ compl->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&compl->cell_list.list, &r_state->success.list);
+ if (wrepl->is_cbs) {
+ channel_ind = wrepl->u.cbs.channel_ind;
+ compl->channel_ind = &channel_ind;
+ }
+ if (wrepl->old_serial_nr) {
+ compl->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list);
+ }
+ }
+
+ cbsp_tx_decoded(cbc, resp);
+ talloc_free(r_state);
+ return rc;
+}
+
+static int cbsp_rx_keep_alive(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ LOGP(DCBS, LOGL_DEBUG, "CBSP Rx KEEP_ALIVE\n");
+
+ /* FIXME: repetition period */
+ return tx_cbsp_keepalive_compl(cbc);
+}
+
+static int cbsp_rx_kill(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ const struct osmo_cbsp_kill *kill = &dec->u.kill;
+ struct gsm_network *net = cbc->net;
+ struct response_state *r_state = talloc_zero(cbc, struct response_state);
+ struct osmo_cbsp_decoded *resp;
+ int rc;
+
+ LOGP(DCBS, LOGL_DEBUG, "CBSP Rx KILL\n");
+
+ rc = cbsp_per_bts(net, r_state, &dec->u.kill.cell_list, bts_rx_kill, dec, NULL);
+ if (rc < 0) {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KILL_FAIL);
+ struct osmo_cbsp_kill_failure *fail = &resp->u.kill_fail;
+ fail->msg_id = kill->msg_id;
+ fail->old_serial_nr = kill->old_serial_nr;
+ fail->channel_ind = kill->channel_ind;
+ llist_replace_head(&fail->fail_list, &r_state->fail);
+
+ fail->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&fail->cell_list.list, &r_state->success.list);
+
+ fail->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list);
+ } else {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KILL_COMPL);
+ struct osmo_cbsp_kill_complete *compl = &resp->u.kill_compl;
+ compl->msg_id = kill->msg_id;
+ compl->old_serial_nr = kill->old_serial_nr;
+ compl->channel_ind = kill->channel_ind;
+
+ compl->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&compl->cell_list.list, &r_state->success.list);
+
+ compl->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list);
+ }
+
+ cbsp_tx_decoded(cbc, resp);
+ talloc_free(r_state);
+ return rc;
+}
+
+static int cbsp_rx_reset(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ struct gsm_network *net = cbc->net;
+ struct response_state *r_state = talloc_zero(cbc, struct response_state);
+ struct osmo_cbsp_decoded *resp;
+ int rc;
+
+ LOGP(DCBS, LOGL_DEBUG, "CBSP Rx RESET\n");
+
+ rc = cbsp_per_bts(net, r_state, &dec->u.reset.cell_list, bts_rx_reset, dec, NULL);
+ if (rc < 0) {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESET_FAIL);
+ struct osmo_cbsp_reset_failure *fail = &resp->u.reset_fail;
+ llist_replace_head(&fail->fail_list, &r_state->fail);
+
+ fail->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&fail->cell_list.list, &r_state->success.list);
+ } else {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESET_COMPL);
+ struct osmo_cbsp_reset_complete *compl = &resp->u.reset_compl;
+ if (dec->u.reset.cell_list.id_discr == CELL_IDENT_BSS) {
+ /* replace the list of individual cell identities with CELL_IDENT_BSS */
+ compl->cell_list.id_discr = CELL_IDENT_BSS;
+ /* no need to free success_list entries, hierarchical talloc works */
+ } else {
+ compl->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&compl->cell_list.list, &r_state->success.list);
+ }
+ }
+ cbsp_tx_decoded(cbc, resp);
+ talloc_free(r_state);
+ return rc;
+}
+
+
+/*! process an incoming, already decoded CBSP message from the CBC.
+ * \param[in] cbc link to the CBC
+ * \param[in] dec decoded CBSP message structure. Ownership not transferred.
+ * \returns 0 on success; negative on error. */
+int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ int rc = -1;
+
+ switch (dec->msg_type) {
+ case CBSP_MSGT_WRITE_REPLACE: /* create or modify message */
+ rc = cbsp_rx_write_replace(cbc, dec);
+ break;
+ case CBSP_MSGT_KEEP_ALIVE: /* solicit an acknowledgement */
+ rc = cbsp_rx_keep_alive(cbc, dec);
+ break;
+ case CBSP_MSGT_KILL: /* remove message */
+ rc = cbsp_rx_kill(cbc, dec);
+ break;
+ case CBSP_MSGT_RESET: /* stop broadcasting of all messages */
+ rc = cbsp_rx_reset(cbc, dec);
+ break;
+ case CBSP_MSGT_LOAD_QUERY:
+ case CBSP_MSGT_MSG_STATUS_QUERY:
+ case CBSP_MSGT_SET_DRX:
+ LOGP(DCBS, LOGL_ERROR, "Received Unimplemented CBSP Message Type %s",
+ get_value_string(cbsp_msg_type_names, dec->msg_type));
+ /* we should implement those eventually */
+ break;
+ default:
+ LOGP(DCBS, LOGL_ERROR, "Received Unknown/Unexpected CBSP Message Type %s",
+ get_value_string(cbsp_msg_type_names, dec->msg_type));
+ break;
+ }
+ return rc;
+}
+
+/*********************************************************************************
+ * VTY Interface (Introspection)
+ *********************************************************************************/
+
+static void vty_dump_smscb_chan_state(struct vty *vty, const struct bts_smscb_chan_state *cs)
+{
+ const struct bts_smscb_message *sm;
+
+ vty_out(vty, "%s CBCH:%s", cs == &cs->bts->cbch_basic ? "BASIC" : "EXTENDED", VTY_NEWLINE);
+
+ vty_out(vty, " MsgId | SerNo | Pg | Category | Perd | #Tx | #Req | DCS%s", VTY_NEWLINE);
+ vty_out(vty, "-------|-------|----|---------------|------|------|------|----%s", VTY_NEWLINE);
+ llist_for_each_entry(sm, &cs->messages, list) {
+ vty_out(vty, " %04x | %04x | %2u | %13s | %4u | %4u | %4u | %02x%s",
+ sm->input.msg_id, sm->input.serial_nr, sm->num_pages,
+ get_value_string(cbsp_category_names, sm->input.category),
+ sm->input.rep_period, sm->bcast_count, sm->input.num_bcast_req,
+ sm->input.dcs, VTY_NEWLINE);
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+}
+
+DEFUN(bts_show_cbs, bts_show_cbs_cmd,
+ "show bts <0-255> smscb [(basic|extended)]",
+ SHOW_STR "Display information about a BTS\n" "BTS number\n"
+ "SMS Cell Broadcast State\n"
+ "Show only information related to CBCH BASIC\n"
+ "Show only information related to CBCH EXTENDED\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ int bts_nr = atoi(argv[0]);
+ struct gsm_bts *bts;
+
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts = gsm_bts_num(net, bts_nr);
+
+ if (argc < 2 || !strcmp(argv[1], "basic"))
+ vty_dump_smscb_chan_state(vty, &bts->cbch_basic);
+ if (argc < 2 || !strcmp(argv[1], "extended"))
+ vty_dump_smscb_chan_state(vty, &bts->cbch_extended);
+
+ return CMD_SUCCESS;
+}
+
+void smscb_vty_init(void)
+{
+ install_element_ve(&bts_show_cbs_cmd);
+}
diff --git a/tests/handover/Makefile.am b/tests/handover/Makefile.am
index 84c341e25..8bd001259 100644
--- a/tests/handover/Makefile.am
+++ b/tests/handover/Makefile.am
@@ -11,6 +11,7 @@ AM_CFLAGS = \
$(LIBOSMOCTRL_CFLAGS) \
$(LIBOSMOVTY_CFLAGS) \
$(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
$(LIBOSMOSIGTRAN_CFLAGS) \
$(LIBOSMOMGCPCLIENT_CFLAGS) \
$(NULL)
@@ -91,11 +92,15 @@ handover_test_LDADD = \
$(top_builddir)/src/osmo-bsc/rest_octets.o \
$(top_builddir)/src/osmo-bsc/system_information.o \
$(top_builddir)/src/osmo-bsc/timeslot_fsm.o \
+ $(top_builddir)/src/osmo-bsc/smscb.o \
+ $(top_builddir)/src/osmo-bsc/cbch_scheduler.o \
+ $(top_builddir)/src/osmo-bsc/cbsp_link.o \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOCTRL_LIBS) \
$(LIBOSMOVTY_LIBS) \
$(LIBOSMOABIS_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
$(LIBOSMOSIGTRAN_LIBS) \
$(LIBOSMOMGCPCLIENT_LIBS) \
$(NULL)