diff --git a/Makefile.conf.template b/Makefile.conf.template
index 2ad5de067c3..bcc9d42f2c6 100644
--- a/Makefile.conf.template
+++ b/Makefile.conf.template
@@ -28,6 +28,7 @@
#identity= Adds support for SIP Identity (see RFC 4474). | SSL library, typically libssl
#jabber= Integrates XODE XML parser for parsing Jabber messages | Expat library.
#json= Introduces a new type of variable that provides both serialization and de-serialization from JSON format. | JSON library, libjson
+#launch_darkly= Implements an interface to the Launch Darkly feature management cloud
#ldap= Implements an LDAP search interface for OpenSIPS | OpenLDAP library & development files, typically libldap and libldap-dev
#lua= Easily implement your own OpenSIPS extensions in Lua | liblua5.1-0-dev, libmemcache-dev and libmysqlclient-dev
#httpd= Provides an HTTP transport layer implementation for OpenSIPS. | libmicrohttpd
@@ -67,7 +68,7 @@
#xmpp= Gateway between OpenSIPS and a jabber server. It enables the exchange of IMs between SIP clients and XMPP(jabber) clients. | parsing/building XML files, typically libexpat1-devel
#uuid= UUID generator | uuid-dev
-exclude_modules?= aaa_diameter aaa_radius auth_jwt b2b_logic_xml cachedb_cassandra cachedb_couchbase cachedb_memcached cachedb_mongodb cachedb_redis carrierroute cgrates compression cpl_c db_berkeley db_http db_mysql db_oracle db_perlvdb db_postgres db_sqlite db_unixodbc dialplan emergency event_rabbitmq event_kafka h350 httpd identity jabber json ldap lua mi_xmlrpc_ng mmgeoip osp perl pi_http presence presence_dialoginfo presence_mwi presence_xml presence_dfks proto_sctp proto_tls proto_wss pua pua_bla pua_dialoginfo pua_mi pua_usrloc pua_xmpp python regex rabbitmq rabbitmq_consumer rest_client rls siprec sngtc snmpstats stir_shaken tls_mgm tls_openssl tls_wolfssl uuid xcap xcap_client xml xmpp
+exclude_modules?= aaa_diameter aaa_radius auth_jwt b2b_logic_xml cachedb_cassandra cachedb_couchbase cachedb_memcached cachedb_mongodb cachedb_redis carrierroute cgrates compression cpl_c db_berkeley db_http db_mysql db_oracle db_perlvdb db_postgres db_sqlite db_unixodbc dialplan emergency event_rabbitmq event_kafka h350 httpd identity jabber json launch_darkly ldap lua mi_xmlrpc_ng mmgeoip osp perl pi_http presence presence_dialoginfo presence_mwi presence_xml presence_dfks proto_sctp proto_tls proto_wss pua pua_bla pua_dialoginfo pua_mi pua_usrloc pua_xmpp python regex rabbitmq rabbitmq_consumer rest_client rls siprec sngtc snmpstats stir_shaken tls_mgm tls_openssl tls_wolfssl uuid xcap xcap_client xml xmpp
include_modules?=
diff --git a/modules/launch_darkly/Makefile b/modules/launch_darkly/Makefile
new file mode 100644
index 00000000000..6475b424a3e
--- /dev/null
+++ b/modules/launch_darkly/Makefile
@@ -0,0 +1,12 @@
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=launch_darkly.so
+
+
+DEFS+=-I$(LOCALBASE)/include
+LIBS+=-L$(LOCALBASE)/lib -l ldserverapi -l curl -l pthread -l m -l pcre
+
+include ../../Makefile.modules
+
diff --git a/modules/launch_darkly/doc/contributors.xml b/modules/launch_darkly/doc/contributors.xml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/modules/launch_darkly/doc/launch_darkly.xml b/modules/launch_darkly/doc/launch_darkly.xml
new file mode 100644
index 00000000000..1603c68d525
--- /dev/null
+++ b/modules/launch_darkly/doc/launch_darkly.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+%docentities;
+
+]>
+
+
+
+ launch_darkly Module
+ &osipsname;
+
+
+
+ &admin;
+ &contrib;
+
+ &docCopyrights;
+ ©right; 2023 Five9 Inc.
+
diff --git a/modules/launch_darkly/doc/launch_darkly_admin.xml b/modules/launch_darkly/doc/launch_darkly_admin.xml
new file mode 100644
index 00000000000..6fe2a373351
--- /dev/null
+++ b/modules/launch_darkly/doc/launch_darkly_admin.xml
@@ -0,0 +1,235 @@
+
+
+
+
+ &adminguide;
+
+
+ Overview
+
+ This module implements support for the
+ Launch Darkly feature
+ management cloud. The module provide the conectivity to the cloud and
+ the ability to query for feature flags.
+
+
+ OpenSIPS uses the server side C/C++ SDK provided by Launch Darkly.
+
+
+
+
+ Dependencies
+
+ &osips; Modules
+
+ The following modules must be loaded before this module:
+
+
+
+ none.
+
+
+
+
+
+
+ External Libraries or Applications
+
+ The following libraries or applications must be installed before
+ running &osips; with this module loaded:
+
+
+
+ ldserverapi
+
+
+
+
+
+ ldserverapi must be compiled and installed
+ from the official
+ GITHUB repository .
+
+
+ The instructions for a quick installations of the library (note that it has to be compiled as shared lib in order to be compatible with the OpenSIPS modules):
+
+
+...
+ $ git clone https://github.com/launchdarkly/c-server-sdk.git
+ $ cd c-server-sdk
+ $ cmake -DBUILD_SHARED_LIBS=On -DBUILD_TESTING=OFF .
+ $ sudo make install
+...
+
+
+
+
+
+ Exported Parameters
+
+
+ sdk_key (string)
+
+ The LaunchDarkly SDK key used to connect to the service. This
+ is a mandatory parameter.
+
+
+ Set sdk_key parameter
+
+...
+modparam("launch_darkly", "sdk_key", "sdk-12345678-abcd-12ab-1234-0123456789abc")
+...
+
+
+
+
+
+ ld_log_level (string)
+
+ The LaunchDarkly specific log level to be used by the LD SDK/libray to
+ log its internal messages. Note that these log produced by the LD
+ library (according to this ld_log_level) will be further subject to
+ filtering according to the overall OpenSIPS log_level.
+
+
+ Accepted values are
+ LD_LOG_FATAL,
+ LD_LOG_CRITICAL,
+ LD_LOG_ERROR,
+ LD_LOG_WARNING,
+ LD_LOG_INFO,
+ LD_LOG_DEBUG,
+ LD_LOG_TRACE.
+
+
+ If not set or set to an unsupported value, the
+ LD_LOG_WARNING level will be used by default.
+
+
+ Set log_level parameter
+
+...
+modparam("launch_darkly", "ld_log_level", "LD_LOG_CRITICAL")
+...
+
+
+
+
+
+ connect_wait (integer)
+
+ The time to wait (in miliseconds) when connecting to the LD service.
+ An initial failure in connecting to the LD service may be addressed
+ by increasing this wait value.
+
+
+ The default value is 500 miliseconds.
+
+
+ Set connect_wait parameter
+
+...
+modparam("launch_darkly", "connect_wait", 100)
+...
+
+
+
+
+
+ re_init_interval (integer)
+
+ The minimum time interval (in seconds) to try again to init
+ the LD client in the situation when the module was not able to init
+ the LC connection at startup. In case of such failure, the module will
+ automatically re-try to init its LD client on-demand, whnever the
+ feature flag is checked from script, but not sooner than
+ `re_init_interval`. Note: if there are no flag checkings to be
+ performed, the re-init may be attempted longer than `re_init_interval`.
+
+
+ The default value is 10 seconds.
+
+
+ Set re_init_interval parameter
+
+...
+modparam("launch_darkly", "re_init_interval", 30)
+...
+
+
+
+
+
+
+
+
+ Exported Functions
+
+
+ ld_feature_enabled( flag, user, [user_extra], [fallback])
+
+
+ Function to evaluate a LaunchDarkly boolean feature flag
+
+
+ Returns 1 if the flag was found TRUE
+ or -1 otherwise.
+
+
+ In case of error, the fallback (TRUE or FALSE) value will be
+ returned In such cases, a "fallback" TRUE is returned as 2 and a
+ fallback FALSE as -2, so you can may a difference between a real
+ TRUE (returned by the LD service) and a fallback TRUE due to an
+ error.
+
+
+ This function can be used from any route.
+
+
+ The function has the following parameters:
+
+
+
+
+ flag (string) - the key of the flag
+ to evaluate. May not be NULL or empty.
+
+
+
+
+ user (string) - the user to evaluate
+ the flag against. May not be NULL or empty.
+
+
+
+
+ user_extra (AVP, optional) - an AVP
+ holding one or multiple key-value attributes to be
+ attached to the user. The format of the AVP value is
+ "key=value".
+
+
+
+
+ fallback (int, optional) - the value
+ to be returned on error. By default FALSE will be returned.
+
+
+
+
+
+ ld_feature_enabled() function usage
+
+ ...
+ $avp(extra) = "domainId=123456";
+ if (ld_feature_enabled("my-flag","opensips", $avp(extra), false))
+ xlog("-------TRUE\n");
+ else
+ xlog("-------FALSE\n");
+ ...
+
+
+
+
+
+
diff --git a/modules/launch_darkly/launch_darkly.c b/modules/launch_darkly/launch_darkly.c
new file mode 100644
index 00000000000..a3459a9cdba
--- /dev/null
+++ b/modules/launch_darkly/launch_darkly.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 Five9 Inc.
+ *
+ * This file is part of opensips, a free SIP server.
+ *
+ * opensips is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * opensips 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ */
+
+#include "../../sr_module.h"
+#include "../../ut.h"
+#include "../../mod_fix.h"
+#include "ld_ops.h"
+
+static int mod_init(void);
+static int child_init(int);
+
+static int fixup_check_avp(void** param);
+
+static int w_ld_feature_enabled(struct sip_msg *sip_msg, str *feat, str *user,
+ pv_spec_t *user_extra_avp, int* fallback);
+
+static char *ld_log_level_s = "LD_LOG_WARNING";
+
+
+static const param_export_t mod_params[] = {
+ {"sdk_key", STR_PARAM, &sdk_key},
+ {"ld_log_level", STR_PARAM, &ld_log_level_s},
+ {"connect_wait", INT_PARAM, &connect_wait},
+ {"re_init_interval", INT_PARAM, &re_init_interval},
+ {0,0,0}
+};
+
+static const cmd_export_t cmds[] = {
+ {"ld_feature_enabled",(cmd_function)w_ld_feature_enabled, {
+ {CMD_PARAM_STR, 0, 0},
+ {CMD_PARAM_STR, 0, 0},
+ {CMD_PARAM_VAR|CMD_PARAM_OPT, fixup_check_avp, 0},
+ {CMD_PARAM_INT|CMD_PARAM_OPT, 0, 0},
+ {0,0,0}},
+ ALL_ROUTES},
+ {0,0,{{0,0,0}},0}
+};
+
+struct module_exports exports = {
+ "launch_darkly", /* module name */
+ MOD_TYPE_DEFAULT, /* class of this module */
+ MODULE_VERSION,
+ DEFAULT_DLFLAGS, /* dlopen flags */
+ NULL, /* load function */
+ NULL, /* OpenSIPS module dependencies */
+ cmds, /* exported functions */
+ NULL, /* exported async functions */
+ mod_params, /* exported parameters */
+ NULL, /* exported statistics */
+ NULL, /* exported MI functions */
+ NULL, /* exported pseudo-variables */
+ NULL, /* exported transformations */
+ NULL, /* extra processes */
+ NULL, /* module pre-initialization function */
+ mod_init, /* module initialization function */
+ NULL, /* response handling function */
+ NULL, /* destroy function */
+ child_init, /* per-child init function */
+ NULL /* reload confirm function */
+};
+
+
+static int fixup_check_avp(void** param)
+{
+ if (((pv_spec_t *)*param)->type!=PVT_AVP) {
+ LM_ERR("the return parameter must be an AVP\n");
+ return E_SCRIPT;
+ }
+
+ return 0;
+}
+
+
+static int mod_init(void)
+{
+ if (sdk_key == NULL) {
+ LM_ERR("SDK key not configured via modparam!\n");
+ return -1;
+ }
+
+ set_ld_log_level(ld_log_level_s);
+
+ return 0;
+}
+
+
+static int child_init(int rank)
+{
+ if (ld_init_child() < 0) {
+ LM_ERR("cannot init writing pipe\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int w_ld_feature_enabled(struct sip_msg *sip_msg, str *feat, str *user,
+ pv_spec_t *user_extra_avp, int *fallback)
+{
+ return ld_feature_enabled( feat, user,
+ user_extra_avp ? user_extra_avp->pvp.pvn.u.isname.name.n : -1,
+ fallback ? *fallback : -1 );
+}
+
diff --git a/modules/launch_darkly/ld_ops.c b/modules/launch_darkly/ld_ops.c
new file mode 100644
index 00000000000..2f1b0fb5e38
--- /dev/null
+++ b/modules/launch_darkly/ld_ops.c
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2023 Five9 Inc.
+ *
+ * This file is part of opensips, a free SIP server.
+ *
+ * opensips is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * opensips 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ */
+
+
+#include
+
+#include "../../pvar.h"
+#include "../../ut.h"
+
+unsigned int connect_wait = 500; //milliseconds
+unsigned int re_init_interval = 10; //seconds
+char * sdk_key = NULL;
+
+static struct LDConfig *ld_cfg = NULL;
+static struct LDClient *ld_client = NULL;
+static unsigned int last_init_attempt_time = 0;
+static int ld_log_level = LD_LOG_WARNING;
+
+void set_ld_log_level( char *log_level_s)
+{
+ if (strcasecmp( log_level_s, "LD_LOG_FATAL")==0)
+ ld_log_level = LD_LOG_FATAL;
+ else
+ if (strcasecmp( log_level_s, "LD_LOG_CRITICAL")==0)
+ ld_log_level = LD_LOG_CRITICAL;
+ else
+ if (strcasecmp( log_level_s, "LD_LOG_ERROR")==0)
+ ld_log_level = LD_LOG_ERROR;
+ else
+ if (strcasecmp( log_level_s, "LD_LOG_WARNING")==0)
+ ld_log_level = LD_LOG_WARNING;
+ else
+ if (strcasecmp( log_level_s, "LD_LOG_INFO")==0)
+ ld_log_level = LD_LOG_INFO;
+ else
+ if (strcasecmp( log_level_s, "LD_LOG_DEBUG")==0)
+ ld_log_level = LD_LOG_DEBUG;
+ else
+ if (strcasecmp( log_level_s, "LD_LOG_TRACE")==0)
+ ld_log_level = LD_LOG_TRACE;
+ else {
+ LM_WARN("unrecognized '%s' LG log level, using LD_LOG_WARNING\n",
+ log_level_s);
+ }
+}
+
+
+static void _oss_logger(const LDLogLevel level, const char *const text)
+{
+ /*
+ enum LDLogLevel {
+ LD_LOG_FATAL = 0, LD_LOG_CRITICAL, LD_LOG_ERROR, LD_LOG_WARNING,
+ LD_LOG_INFO, LD_LOG_DEBUG, LD_LOG_TRACE }
+ */
+ int log_map[LD_LOG_TRACE+1] = {L_ALERT,L_CRIT,L_ERR,L_WARN,
+ L_INFO, L_DBG, L_DBG};
+
+ LM_GEN( log_map[level], "[LD] %s\n", text);
+ return;
+}
+
+
+
+static int ld_client_init_attempt(void)
+{
+ /* maybe already connected? */
+ if (ld_client)
+ return 0;
+
+ /* too soon to retry a new connect ?*/
+ if (last_init_attempt_time!=0 &&
+ (last_init_attempt_time + re_init_interval > get_ticks()) )
+ return -2;
+
+ LM_DBG("attempting LD client re-init\n");
+
+ /* we do expect a valid ld config here */
+ ld_client = LDClientInit( ld_cfg, connect_wait);
+ if (!LDClientIsInitialized(ld_client)) {
+ //LDClientClose(ld_client); this triggered a double free in LD lib :-/
+ ld_client = NULL;
+ last_init_attempt_time = get_ticks();
+ return -1;
+ }
+ last_init_attempt_time = 0;
+
+ return 0;
+}
+
+
+int ld_init_child(void)
+{
+ LDConfigureGlobalLogger( ld_log_level, _oss_logger);
+
+ LDGlobalInit();
+
+ LM_DBG("LD globally initialized, proceeding with the connect\n");
+
+ ld_cfg = LDConfigNew( sdk_key );
+ if (ld_cfg==NULL) {
+ LM_ERR("failed to perform LD config\n");
+ return -1;
+ }
+
+ if (ld_client_init_attempt()!=0)
+ LM_ERR("LD client failed to initialize, proceeding offline\n");
+ else
+ LM_DBG("LD client initialized\n");
+
+ return 0;
+}
+
+
+int ld_feature_enabled(str *feat, str *user, int user_extra_avp_id,
+ int fallback)
+{
+ struct LDUser *ld_user;
+ struct LDJSON *ld_extra, *ld_val;
+ struct LDDetails ld_details;
+ LDBoolean ld_res;
+ struct usr_avp *avp;
+ int_str val;
+ str s_nt, extra_key, extra_val;
+ char *p;
+
+ if (ld_client==NULL && ld_client_init_attempt()<0) {
+ LM_ERR("not having a connected LD client :(\n");
+ goto error;
+ }
+
+ if (pkg_nt_str_dup( &s_nt, user)<0) {
+ LM_ERR("failed to pkg_nt duplicate the user\n");
+ goto error;
+ }
+ ld_user = LDUserNew( s_nt.s );
+ pkg_free(s_nt.s);
+ if (ld_user==NULL) {
+ return -1;
+ LM_ERR("failed to create new LD user\n");
+ goto error;
+ }
+
+ /* do we have custom key-val pairs to add to the user? */
+ if (user_extra_avp_id>=0) {
+ avp = NULL;
+ ld_extra = NULL;
+ /* iterate all the AVPs with the keys */
+ while ((avp=search_first_avp(AVP_VAL_STR,user_extra_avp_id,&val,avp))!=NULL) {
+ /* split and evaluate the value part */
+ if ( (p=q_memchr( val.s.s, '=', val.s.len))==NULL) {
+ LM_ERR("extra <%.*s> has no key separtor '=', discarding\n",
+ val.s.len, val.s.s);
+ continue;
+ }
+ extra_key.s = val.s.s;
+ extra_key.len = p-val.s.s;
+ p++;
+ if (p==val.s.s+val.s.len) {
+ LM_ERR("extra <%.*s> has no value, discarding\n",
+ val.s.len, val.s.s);
+ continue;
+ }
+ extra_val.s = p;
+ extra_val.len = val.s.s+val.s.len-p;
+
+ /* add the new extra to the user */
+ if (ld_extra==NULL) {
+ ld_extra = LDNewObject();
+ if (ld_extra==NULL) {
+ LM_ERR("failed to create new user object\n");
+ goto error1;
+ }
+ }
+
+ /* create the new value */
+ if (pkg_nt_str_dup( &s_nt, &extra_val)<0) {
+ LM_ERR("failed to pkg_nt duplicate the extra value\n");
+ goto error1;
+ }
+ ld_val = LDNewText( s_nt.s );
+ pkg_free(s_nt.s);
+ if (ld_val==NULL) {
+ LM_ERR("failed create new extra LD val\n");
+ goto error1;
+ }
+
+ /* add the value as key */
+ if (pkg_nt_str_dup( &s_nt, &extra_key)<0) {
+ LM_ERR("failed to pkg_nt duplicate the extra key\n");
+ goto error1;
+ }
+ if (!LDObjectSetKey( ld_extra, s_nt.s, ld_val)) {
+ LM_ERR("failed to add new key+val to user extra\n");
+ pkg_free(s_nt.s);
+ goto error1;
+ }
+ pkg_free(s_nt.s);
+
+ }
+
+ if (ld_extra)
+ LDUserSetCustom(ld_user, ld_extra);
+ }
+
+ /* now, run the check */
+ if (pkg_nt_str_dup( &s_nt, feat)<0) {
+ LM_ERR("failed to pkg_nt duplicate the feature name\n");
+ goto error1;
+ }
+ ld_res = LDBoolVariation( ld_client, ld_user, s_nt.s,
+ fallback?LDBooleanTrue:LDBooleanFalse, &ld_details);
+ ld_res = ld_res ? 1 : -1;
+
+ /* any error ? */
+ if (ld_details.reason==LD_ERROR) {
+ ld_res = 2 * ld_res; //return some internal error indication
+ switch (ld_details.extra.errorKind) {
+ case LD_CLIENT_NOT_READY:
+ LM_BUG("LD client not initialized at this point!?!\n");
+ break;
+ case LD_NULL_KEY:
+ LM_ERR("LD flag key is empty/NULL\n");
+ break;
+ case LD_STORE_ERROR:
+ LM_ERR("LD internal exception with the flag store\n");
+ break;
+ case LD_FLAG_NOT_FOUND:
+ LM_ERR("the caller provided a flag key that did not match any known flag\n");
+ break;
+ case LD_USER_NOT_SPECIFIED:
+ LM_ERR("LD user is empty/NULL!\n");
+ break;
+ case LD_CLIENT_NOT_SPECIFIED:
+ LM_BUG("LD client is NULL?!?!\n");
+ break;
+ case LD_MALFORMED_FLAG:
+ LM_ERR("internal inconsistency in the flag data, a rule specified a nonexistent variation\n");
+ break;
+ case LD_WRONG_TYPE:
+ LM_ERR("the result value was not of the requested type- expected LDBoolVariation\n");
+ break;
+ case LD_OOM:
+ LM_ERR("LD clientran out of memory.\n");
+ break;
+ default:
+ LM_ERR("unknown %d error reported by LDBoolVariation\n",ld_details.extra.errorKind);
+ break;
+ }
+ }
+
+ LM_DBG("feature flag %s is %s\n", s_nt.s, (ld_res>0)?"TRUE":"FALSE");
+ pkg_free(s_nt.s);
+
+ LDUserFree(ld_user);
+ return ld_res;
+
+error1:
+ LDUserFree(ld_user);
+error:
+ return fallback?2:-2;
+}
diff --git a/modules/launch_darkly/ld_ops.h b/modules/launch_darkly/ld_ops.h
new file mode 100644
index 00000000000..e0c5fc0e421
--- /dev/null
+++ b/modules/launch_darkly/ld_ops.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 Five9 Inc.
+ *
+ * This file is part of opensips, a free SIP server.
+ *
+ * opensips is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * opensips 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ */
+
+
+#ifndef _LD_OPS_H_
+#define _LD_OPS_H_
+
+#include "../../str.h"
+
+extern char* sdk_key;
+extern unsigned int connect_wait;
+extern unsigned int re_init_interval;
+
+void set_ld_log_level( char *log_level_s);
+
+int ld_init_child(void);
+
+int ld_feature_enabled(str *feat, str *user, int user_extra_avp_id,
+ int fallback);
+
+#endif