aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--openbsc/include/openbsc/db.h4
-rw-r--r--openbsc/include/openbsc/gsm_data.h10
-rw-r--r--openbsc/include/openbsc/gsm_subscriber.h5
-rw-r--r--openbsc/src/libbsc/bsc_vty.c4
-rw-r--r--openbsc/src/libbsc/net_init.c10
-rw-r--r--openbsc/src/libmsc/ctrl_commands.c6
-rw-r--r--openbsc/src/libmsc/db.c31
-rw-r--r--openbsc/src/libmsc/gsm_04_08.c10
-rw-r--r--openbsc/src/libmsc/gsm_subscriber.c9
-rw-r--r--openbsc/src/libmsc/vty_interface_layer3.c31
-rw-r--r--openbsc/tests/db/db_test.c30
-rw-r--r--openbsc/tests/vty_test_runner.py51
12 files changed, 141 insertions, 60 deletions
diff --git a/openbsc/include/openbsc/db.h b/openbsc/include/openbsc/db.h
index 6ffe1ade9..bb90705ab 100644
--- a/openbsc/include/openbsc/db.h
+++ b/openbsc/include/openbsc/db.h
@@ -20,6 +20,8 @@
#ifndef _DB_H
#define _DB_H
+#include <stdbool.h>
+
#include "gsm_subscriber.h"
struct gsm_equipment;
@@ -36,7 +38,7 @@ int db_fini(void);
/* subscriber management */
struct gsm_subscriber *db_create_subscriber(const char *imsi, uint64_t smin,
- uint64_t smax);
+ uint64_t smax, bool alloc_exten);
struct gsm_subscriber *db_get_subscriber(enum gsm_subscriber_field field,
const char *subscr);
int db_sync_subscriber(struct gsm_subscriber *subscriber);
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
index e7cd520d2..bdcd0e0dc 100644
--- a/openbsc/include/openbsc/gsm_data.h
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -4,6 +4,7 @@
#include <stdint.h>
#include <regex.h>
#include <sys/types.h>
+#include <stdbool.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/select.h>
@@ -21,12 +22,6 @@ struct gsm_subscriber_group;
#define OBSC_LINKID_CB(__msgb) (__msgb)->cb[3]
-enum gsm_subscr_creation_mode {
- GSM_SUBSCR_DONT_CREATE = 0,
- GSM_SUBSCR_CREAT_W_RAND_EXT = 1,
- GSM_SUBSCR_CREAT_W_REGEXP = 2,
-};
-
enum gsm_security_event {
GSM_SECURITY_NOAVAIL,
GSM_SECURITY_AUTH_FAILED,
@@ -289,7 +284,8 @@ struct gsm_network {
struct osmo_bsc_data *bsc_data;
/* subscriber related features */
- int subscr_creation_mode;
+ bool auto_create_subscr;
+ bool auto_assign_exten;
uint64_t ext_min;
uint64_t ext_max;
struct gsm_subscriber_group *subscr_group;
diff --git a/openbsc/include/openbsc/gsm_subscriber.h b/openbsc/include/openbsc/gsm_subscriber.h
index 3cba5d1ae..3d7c2449b 100644
--- a/openbsc/include/openbsc/gsm_subscriber.h
+++ b/openbsc/include/openbsc/gsm_subscriber.h
@@ -5,6 +5,8 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/protocol/gsm_23_003.h>
+#include <stdbool.h>
+
#define GSM_NAME_LENGTH 160
#define GSM_EXTENSION_LENGTH 15 /* MSISDN can only be 15 digits length */
@@ -90,8 +92,7 @@ enum gsm_subscriber_update_reason {
struct gsm_subscriber *subscr_get(struct gsm_subscriber *subscr);
struct gsm_subscriber *subscr_put(struct gsm_subscriber *subscr);
struct gsm_subscriber *subscr_create_subscriber(struct gsm_subscriber_group *sgrp,
- const char *imsi, uint64_t smin,
- uint64_t smax);
+ const char *imsi);
struct gsm_subscriber *subscr_get_by_tmsi(struct gsm_subscriber_group *sgrp,
uint32_t tmsi);
struct gsm_subscriber *subscr_get_by_imsi(struct gsm_subscriber_group *sgrp,
diff --git a/openbsc/src/libbsc/bsc_vty.c b/openbsc/src/libbsc/bsc_vty.c
index 04f2cc690..b0e87648e 100644
--- a/openbsc/src/libbsc/bsc_vty.c
+++ b/openbsc/src/libbsc/bsc_vty.c
@@ -195,6 +195,10 @@ static void net_dump_vty(struct vty *vty, struct gsm_network *net)
if (net->authorized_reg_str)
vty_out(vty, ", authorized regexp: %s", net->authorized_reg_str);
vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, " Auto create subscriber: %s%s",
+ net->auto_create_subscr ? "yes" : "no", VTY_NEWLINE);
+ vty_out(vty, " Auto assign extension: %s%s",
+ net->auto_assign_exten ? "yes" : "no", VTY_NEWLINE);
vty_out(vty, " Location updating reject cause: %u%s",
net->reject_cause, VTY_NEWLINE);
vty_out(vty, " Encryption: A5/%u%s", net->a5_encryption,
diff --git a/openbsc/src/libbsc/net_init.c b/openbsc/src/libbsc/net_init.c
index 4636d579e..0e99097e0 100644
--- a/openbsc/src/libbsc/net_init.c
+++ b/openbsc/src/libbsc/net_init.c
@@ -21,10 +21,13 @@
#include <openbsc/osmo_msc_data.h>
#include <openbsc/gsm_subscriber.h>
+#include <stdbool.h>
+
struct gsm_network *gsm_network_init(uint16_t country_code, uint16_t network_code,
int (*mncc_recv)(struct gsm_network *, struct msgb *))
{
struct gsm_network *net;
+ const char *default_regexp = "*";
net = talloc_zero(tall_bsc_ctx, struct gsm_network);
if (!net)
@@ -42,13 +45,18 @@ struct gsm_network *gsm_network_init(uint16_t country_code, uint16_t network_cod
return NULL;
}
+ if (gsm_parse_reg(net, &net->authorized_regexp, &net->authorized_reg_str, 1,
+ &default_regexp) != 0)
+ return NULL;
+
/* Init back pointer */
net->bsc_data->auto_off_timeout = -1;
net->bsc_data->network = net;
INIT_LLIST_HEAD(&net->bsc_data->mscs);
net->subscr_group->net = net;
- net->subscr_creation_mode = GSM_SUBSCR_CREAT_W_RAND_EXT;
+ net->auto_create_subscr = true;
+ net->auto_assign_exten = true;
net->country_code = country_code;
net->network_code = network_code;
diff --git a/openbsc/src/libmsc/ctrl_commands.c b/openbsc/src/libmsc/ctrl_commands.c
index a02db36b9..79e136d52 100644
--- a/openbsc/src/libmsc/ctrl_commands.c
+++ b/openbsc/src/libmsc/ctrl_commands.c
@@ -25,6 +25,8 @@
#include <openbsc/db.h>
#include <openbsc/debug.h>
+#include <stdbool.h>
+
static bool alg_supported(const char *alg)
{
/*
@@ -96,9 +98,7 @@ static int set_subscriber_modify(struct ctrl_cmd *cmd, void *data)
subscr = subscr_get_by_imsi(net->subscr_group, imsi);
if (!subscr)
- subscr = subscr_create_subscriber(net->subscr_group, imsi,
- net->ext_min,
- net->ext_max);
+ subscr = subscr_create_subscriber(net->subscr_group, imsi);
if (!subscr)
goto fail;
diff --git a/openbsc/src/libmsc/db.c b/openbsc/src/libmsc/db.c
index b3671393a..68eba3e17 100644
--- a/openbsc/src/libmsc/db.c
+++ b/openbsc/src/libmsc/db.c
@@ -23,6 +23,7 @@
#include <inttypes.h>
#include <libgen.h>
#include <stdio.h>
+#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
@@ -519,7 +520,7 @@ int db_fini(void)
}
struct gsm_subscriber *db_create_subscriber(const char *imsi, uint64_t smin,
- uint64_t smax)
+ uint64_t smax, bool alloc_exten)
{
dbi_result result;
struct gsm_subscriber *subscr;
@@ -551,7 +552,8 @@ struct gsm_subscriber *db_create_subscriber(const char *imsi, uint64_t smin,
strncpy(subscr->imsi, imsi, sizeof(subscr->imsi)-1);
dbi_result_free(result);
LOGP(DDB, LOGL_INFO, "New Subscriber: ID %llu, IMSI %s\n", subscr->id, subscr->imsi);
- db_subscriber_alloc_exten(subscr, smin, smax);
+ if (alloc_exten)
+ db_subscriber_alloc_exten(subscr, smin, smax);
return subscr;
}
@@ -956,8 +958,11 @@ int db_sync_subscriber(struct gsm_subscriber *subscriber)
dbi_conn_quote_string_copy(conn,
subscriber->name, &q_name);
- dbi_conn_quote_string_copy(conn,
- subscriber->extension, &q_extension);
+ if (subscriber->extension[0] != '\0')
+ dbi_conn_quote_string_copy(conn,
+ subscriber->extension, &q_extension);
+ else
+ q_extension = strdup("NULL");
if (subscriber->tmsi != GSM_RESERVED_TMSI) {
sprintf(tmsi, "%u", subscriber->tmsi);
@@ -1062,15 +1067,17 @@ int db_subscriber_delete(struct gsm_subscriber *subscr)
}
dbi_result_free(result);
- result = dbi_conn_queryf(conn,
- "DELETE FROM SMS WHERE src_addr=%s OR dest_addr=%s",
- subscr->extension, subscr->extension);
- if (!result) {
- LOGP(DDB, LOGL_ERROR,
- "Failed to delete SMS for %llu\n", subscr->id);
- return -1;
+ if (subscr->extension[0] != '\0') {
+ result = dbi_conn_queryf(conn,
+ "DELETE FROM SMS WHERE src_addr=%s OR dest_addr=%s",
+ subscr->extension, subscr->extension);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to delete SMS for %llu\n", subscr->id);
+ return -1;
+ }
+ dbi_result_free(result);
}
- dbi_result_free(result);
result = dbi_conn_queryf(conn,
"DELETE FROM VLR WHERE subscriber_id=%llu",
diff --git a/openbsc/src/libmsc/gsm_04_08.c b/openbsc/src/libmsc/gsm_04_08.c
index 67044971b..1b02efe42 100644
--- a/openbsc/src/libmsc/gsm_04_08.c
+++ b/openbsc/src/libmsc/gsm_04_08.c
@@ -530,15 +530,13 @@ static int mm_tx_identity_req(struct gsm_subscriber_connection *conn, uint8_t id
static struct gsm_subscriber *subscr_create(const struct gsm_network *net,
const char *imsi)
{
- if (net->subscr_creation_mode == GSM_SUBSCR_DONT_CREATE)
+ if (!net->auto_create_subscr)
return NULL;
- if (net->subscr_creation_mode & GSM_SUBSCR_CREAT_W_REGEXP)
- if (!subscr_regexp_check(net, imsi))
- return NULL;
+ if (!subscr_regexp_check(net, imsi))
+ return NULL;
- return subscr_create_subscriber(net->subscr_group, imsi, net->ext_min,
- net->ext_max);
+ return subscr_create_subscriber(net->subscr_group, imsi);
}
/* Parse Chapter 9.2.11 Identity Response */
diff --git a/openbsc/src/libmsc/gsm_subscriber.c b/openbsc/src/libmsc/gsm_subscriber.c
index 1dc2cc26d..08198c765 100644
--- a/openbsc/src/libmsc/gsm_subscriber.c
+++ b/openbsc/src/libmsc/gsm_subscriber.c
@@ -26,6 +26,7 @@
#include <string.h>
#include <assert.h>
#include <time.h>
+#include <stdbool.h>
#include <osmocom/core/talloc.h>
@@ -203,10 +204,12 @@ void subscr_remove_request(struct subscr_request *request)
}
struct gsm_subscriber *subscr_create_subscriber(struct gsm_subscriber_group *sgrp,
- const char *imsi, uint64_t smin,
- uint64_t smax)
+ const char *imsi)
{
- struct gsm_subscriber *subscr = db_create_subscriber(imsi, smin, smax);
+ struct gsm_subscriber *subscr = db_create_subscriber(imsi,
+ sgrp->net->ext_min,
+ sgrp->net->ext_max,
+ sgrp->net->auto_assign_exten);
if (subscr)
subscr->group = sgrp;
return subscr;
diff --git a/openbsc/src/libmsc/vty_interface_layer3.c b/openbsc/src/libmsc/vty_interface_layer3.c
index a035bf976..74da1d7dc 100644
--- a/openbsc/src/libmsc/vty_interface_layer3.c
+++ b/openbsc/src/libmsc/vty_interface_layer3.c
@@ -242,9 +242,7 @@ DEFUN(subscriber_create,
if (subscr)
db_sync_subscriber(subscr);
else {
- subscr = subscr_create_subscriber(gsmnet->subscr_group, argv[0],
- gsmnet->ext_min,
- gsmnet->ext_max);
+ subscr = subscr_create_subscriber(gsmnet->subscr_group, argv[0]);
if (!subscr) {
vty_out(vty, "%% No subscriber created for IMSI %s%s",
@@ -1044,6 +1042,8 @@ DEFUN(cfg_nitb_subscr_random, cfg_nitb_subscr_random_cmd,
{
struct gsm_network *gsmnet = gsmnet_from_vty(vty);
uint64_t mi = atoi(argv[0]), ma = atoi(argv[1]);
+ gsmnet->auto_create_subscr = true;
+ gsmnet->auto_assign_exten = true;
if (mi >= ma) {
vty_out(vty, "Incorrect range: %s >= %s, expected MIN < MAX%s",
argv[0], argv[1], VTY_NEWLINE);
@@ -1055,15 +1055,13 @@ DEFUN(cfg_nitb_subscr_random, cfg_nitb_subscr_random_cmd,
}
DEFUN(cfg_nitb_subscr_create, cfg_nitb_subscr_create_cmd,
- "subscriber-create-on-demand [regexp]",
+ "subscriber-create-on-demand [no-extension]",
"Make a new record when a subscriber is first seen.\n"
- "Create subscribers only if IMSI matches the regexp specified in "
- "authorized-regexp command\n")
+ "Do not automatically assign extension to created subscribers\n")
{
struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- gsmnet->subscr_creation_mode = GSM_SUBSCR_CREAT_W_RAND_EXT;
- if (argc)
- gsmnet->subscr_creation_mode |= GSM_SUBSCR_CREAT_W_REGEXP;
+ gsmnet->auto_create_subscr = true;
+ gsmnet->auto_assign_exten = argc ? false : true;
return CMD_SUCCESS;
}
@@ -1072,7 +1070,7 @@ DEFUN(cfg_nitb_no_subscr_create, cfg_nitb_no_subscr_create_cmd,
NO_STR "Make a new record when a subscriber is first seen.\n")
{
struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- gsmnet->subscr_creation_mode = GSM_SUBSCR_DONT_CREATE;
+ gsmnet->auto_create_subscr = false;
return CMD_SUCCESS;
}
@@ -1097,12 +1095,15 @@ DEFUN(cfg_nitb_no_assign_tmsi, cfg_nitb_no_assign_tmsi_cmd,
static int config_write_nitb(struct vty *vty)
{
struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- enum gsm_subscr_creation_mode scm = gsmnet->subscr_creation_mode;
- const char *reg = (scm & GSM_SUBSCR_CREAT_W_REGEXP) ? " regexp" : "",
- *pref = scm ? "" : "no ";
+
vty_out(vty, "nitb%s", VTY_NEWLINE);
- vty_out(vty, " %ssubscriber-create-on-demand%s%s",
- pref, reg, VTY_NEWLINE);
+ if (!gsmnet->auto_create_subscr)
+ vty_out(vty, " no subscriber-create-on-demand%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " subscriber-create-on-demand%s%s",
+ gsmnet->auto_assign_exten ? "" : " no-extension",
+ VTY_NEWLINE);
+
if (gsmnet->ext_min != GSM_MIN_EXTEN || gsmnet->ext_max != GSM_MAX_EXTEN)
vty_out(vty, " subscriber-create-on-demand random %"PRIu64" %"
PRIu64"%s", gsmnet->ext_min, gsmnet->ext_max,
diff --git a/openbsc/tests/db/db_test.c b/openbsc/tests/db/db_test.c
index dc814813d..755a6e9eb 100644
--- a/openbsc/tests/db/db_test.c
+++ b/openbsc/tests/db/db_test.c
@@ -28,6 +28,7 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
+#include <stdbool.h>
#include <inttypes.h>
static struct gsm_network dummy_net;
@@ -159,12 +160,13 @@ static void test_sms_migrate(void)
subscr_put(rcv_subscr);
}
-static void test_subs(const char *alice_imsi, char *imei1, char *imei2)
+static void test_subs(const char *imsi, char *imei1, char *imei2, bool make_ext)
{
struct gsm_subscriber *alice = NULL, *alice_db;
char scratch_str[256];
- alice = db_create_subscriber(alice_imsi, GSM_MIN_EXTEN, GSM_MAX_EXTEN);
+ alice = db_create_subscriber(imsi, GSM_MIN_EXTEN, GSM_MAX_EXTEN,
+ make_ext);
db_subscriber_assoc_imei(alice, imei1);
if (imei2)
db_subscriber_assoc_imei(alice, imei2);
@@ -177,7 +179,7 @@ static void test_subs(const char *alice_imsi, char *imei1, char *imei2)
COMPARE(alice, alice_db);
SUBSCR_PUT(alice_db);
/* Get by IMSI */
- alice_db = db_get_subscriber(GSM_SUBSCRIBER_IMSI, alice_imsi);
+ alice_db = db_get_subscriber(GSM_SUBSCRIBER_IMSI, imsi);
COMPARE(alice, alice_db);
SUBSCR_PUT(alice_db);
/* Get by id */
@@ -187,8 +189,14 @@ static void test_subs(const char *alice_imsi, char *imei1, char *imei2)
SUBSCR_PUT(alice_db);
/* Get by extension */
alice_db = db_get_subscriber(GSM_SUBSCRIBER_EXTENSION, alice->extension);
- COMPARE(alice, alice_db);
- SUBSCR_PUT(alice_db);
+ if (alice_db) {
+ if (!make_ext)
+ printf("FAIL: bogus extension created for IMSI %s\n",
+ imsi);
+ COMPARE(alice, alice_db);
+ SUBSCR_PUT(alice_db);
+ } else if (make_ext)
+ printf("FAIL: no subscriber extension for IMSI %s\n", imsi);
SUBSCR_PUT(alice);
}
@@ -217,18 +225,22 @@ int main()
struct gsm_subscriber *alice_db;
char *alice_imsi = "3243245432345";
- alice = db_create_subscriber(alice_imsi, GSM_MIN_EXTEN, GSM_MAX_EXTEN);
+ alice = db_create_subscriber(alice_imsi, GSM_MIN_EXTEN, GSM_MAX_EXTEN,
+ true);
db_sync_subscriber(alice);
alice_db = db_get_subscriber(GSM_SUBSCRIBER_IMSI, alice->imsi);
COMPARE(alice, alice_db);
SUBSCR_PUT(alice_db);
SUBSCR_PUT(alice);
- test_subs("3693245423445", "1234567890", NULL);
- test_subs("9993245423445", "1234567890", "6543560920");
+ test_subs("3693245423445", "1234567890", NULL, true);
+ test_subs("9993245423445", "1234567890", "6543560920", true);
+ test_subs("3123122223445", "1234567890", NULL, false);
+ test_subs("9123121223445", "1234567890", "6543560920", false);
/* create it again and see it fails */
- alice = db_create_subscriber(alice_imsi, GSM_MIN_EXTEN, GSM_MAX_EXTEN);
+ alice = db_create_subscriber(alice_imsi, GSM_MIN_EXTEN, GSM_MAX_EXTEN,
+ true);
OSMO_ASSERT(!alice);
test_sms();
diff --git a/openbsc/tests/vty_test_runner.py b/openbsc/tests/vty_test_runner.py
index bae18669c..23939e84c 100644
--- a/openbsc/tests/vty_test_runner.py
+++ b/openbsc/tests/vty_test_runner.py
@@ -244,7 +244,7 @@ class TestVTYNITB(TestVTYGenericBSC):
self.vty.command("configure terminal")
self.vty.command("nitb")
self.assertTrue(self.vty.verify("subscriber-create-on-demand", ['']))
- self.assertTrue(self.vty.verify("subscriber-create-on-demand regexp", ['']))
+ self.assertTrue(self.vty.verify("subscriber-create-on-demand no-extension", ['']))
self.vty.command("end")
def testSi2Q(self):
@@ -400,6 +400,9 @@ class TestVTYNITB(TestVTYGenericBSC):
self.vty.enable()
imsi = "204300854013739"
+ imsi2 = "222301824913762"
+ imsi3 = "333500854113763"
+ imsi4 = "444583744053764"
# Initially we don't have this subscriber
self.vty.verify('show subscriber imsi '+imsi, ['% No subscriber found for imsi '+imsi])
@@ -407,14 +410,60 @@ class TestVTYNITB(TestVTYGenericBSC):
# Lets create one
res = self.vty.command('subscriber create imsi '+imsi)
self.assert_(res.find(" IMSI: "+imsi) > 0)
+ self.assert_(res.find("Extension") > 0)
# Now we have it
res = self.vty.command('show subscriber imsi '+imsi)
self.assert_(res.find(" IMSI: "+imsi) > 0)
+ # With narrow random interval
+ self.vty.command("configure terminal")
+ self.vty.command("nitb")
+ self.assertTrue(self.vty.verify("subscriber-create-on-demand", ['']))
+ # wrong interval
+ res = self.vty.command("subscriber-create-on-demand random 221 122")
+ # error string will contain arguments
+ self.assert_(res.find("122") > 0)
+ self.assert_(res.find("221") > 0)
+ # correct interval - silent ok
+ self.assertTrue(self.vty.verify("subscriber-create-on-demand random 221 222", ['']))
+ self.vty.command("end")
+
+ res = self.vty.command('subscriber create imsi ' + imsi2)
+ self.assert_(res.find(" IMSI: " + imsi2) > 0)
+ self.assert_(res.find("221") > 0 or res.find("222") > 0)
+ self.assert_(res.find(" Extension: ") > 0)
+
+ # Without extension
+ self.vty.command("configure terminal")
+ self.vty.command("nitb")
+ self.assertTrue(self.vty.verify("subscriber-create-on-demand no-extension", ['']))
+ self.vty.command("end")
+ res = self.vty.command('subscriber create imsi ' + imsi3)
+ self.assert_(res.find(" IMSI: " + imsi3) > 0)
+ self.assertEquals(res.find("Extension"), -1)
+
+ # With extension again
+ self.vty.command("configure terminal")
+ self.vty.command("nitb")
+ self.assertTrue(self.vty.verify("no subscriber-create-on-demand", ['']))
+ self.assertTrue(self.vty.verify("subscriber-create-on-demand", ['']))
+ self.assertTrue(self.vty.verify("subscriber-create-on-demand random 221 666", ['']))
+ self.vty.command("end")
+
+ res = self.vty.command('subscriber create imsi ' + imsi4)
+ self.assert_(res.find(" IMSI: " + imsi4) > 0)
+ self.assert_(res.find(" Extension: ") > 0)
+
# Delete it
res = self.vty.command('subscriber imsi ' + imsi + ' delete')
self.assert_("" == res)
+ res = self.vty.command('subscriber imsi ' + imsi2 + ' delete')
+ self.assert_("" == res)
+ res = self.vty.command('subscriber imsi ' + imsi3 + ' delete')
+ self.assert_("" == res)
+ res = self.vty.command('subscriber imsi ' + imsi4 + ' delete')
+ self.assert_("" == res)
# Now it should not be there anymore
res = self.vty.command('show subscriber imsi '+imsi)