Revision
14401
Author
mrene
Date
2009-07-28 15:19:01 -0500 (Tue, 28 Jul 2009)

Log Message

mod_limit: Refactor and add limit_hash_execute application (see wiki)

Modified Paths

Diff

Modified: freeswitch/trunk/src/mod/applications/mod_limit/mod_limit.c (14400 => 14401)


--- freeswitch/trunk/src/mod/applications/mod_limit/mod_limit.c	2009-07-28 18:50:33 UTC (rev 14400)
+++ freeswitch/trunk/src/mod/applications/mod_limit/mod_limit.c	2009-07-28 20:19:01 UTC (rev 14401)
@@ -338,8 +338,6 @@
 			switch_hash_this(hi, &key, &keylen, &val);
 			
 			item = (limit_hash_item_t*)val;
-			
-			/* We keep the structure even though the count is 0 so we do not allocate too often */
 			item->total_usage--;	
 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Usage for %s is now %d\n", (const char*)key, item->total_usage);
 			
@@ -861,63 +859,26 @@
 	return SWITCH_STATUS_SUCCESS;
 }
 
-#define LIMITHASH_USAGE "<realm> <id> [<max>[/interval]] [number [dialplan [context]]]"
-#define LIMITHASH_DESC "limit access to a resource and transfer to an extension if the limit is exceeded"
-SWITCH_STANDARD_APP(limit_hash_function)
+/* \brief Enforces limit_hash restrictions
+ * \param session current session
+ * \param realm limit realm
+ * \param id limit id
+ * \param max maximum count
+ * \param interval interval for rate limiting
+ * \return SWITCH_TRUE if the access is allowed, SWITCH_FALSE if it isnt
+ */
+static switch_bool_t do_limit_hash(switch_core_session_t *session, const char *realm, const char *id, int max, int interval)
 {
-	int argc = 0;
-	char *argv[6] = { 0 };
-	char *mydata = NULL;
-	char *realm = NULL;
-	char *id = NULL;
+	switch_channel_t *channel = switch_core_session_get_channel(session);
 	char *hashkey = NULL;
-	char *xfer_exten = NULL;
-	int max = -1;
-	int interval = 0;
-	char *szinterval = NULL;
+	switch_bool_t status = SWITCH_TRUE;
 	limit_hash_item_t *item = NULL;
-	switch_channel_t *channel = switch_core_session_get_channel(session);
 	time_t now = switch_epoch_time_now(NULL);
 	limit_hash_private_t *pvt = NULL;
 	uint8_t increment = 1;
 	
-	/* Parse application data  */
-	if (!switch_strlen_zero(data)) {
-		mydata = switch_core_session_strdup(session, data);
-		argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
-	}
-	
-	if (argc < 2) {
-		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "USAGE: limit_hash %s\n", LIMITHASH_USAGE);
-		return;
-	}
-	
-	realm = argv[0];
-	id = argv[1];
-	
-	/* If max is omitted, only act as a counter and skip maximum checks */
-	if (argc > 2) {
-		if ((szinterval = strchr(argv[2], '/')))
-		{
-			*szinterval++ = '\0';
-			interval = atoi(szinterval);
-		}
-		
-		max = atoi(argv[2]);
-		
-		if (max < 0) {
-			max = 0;
-		}
-	}
+	hashkey = switch_core_session_sprintf(session, "%s_%s", realm, id);
 
-	if (argc > 3) {
-		xfer_exten = argv[3];
-	} else {
-		xfer_exten = limit_def_xfer_exten;
-	}
-	
-	hashkey = switch_core_session_sprintf(session, "%s_%s", realm, id);
-	
 	switch_mutex_lock(globals.limit_hash_mutex);
 	/* Check if that realm+id has ever been checked */
 	if (!(item = (limit_hash_item_t*)switch_core_hash_find(globals.limit_hash, hashkey))) {
@@ -927,7 +888,7 @@
 		memset(item, 0, sizeof(limit_hash_item_t));
 		switch_core_hash_insert(globals.limit_hash, hashkey, item);
 	}
-	
+
 	/* Did we already run on this channel before? */
 	if ((pvt = switch_channel_get_private(channel, "limit_hash")))
 	{
@@ -951,32 +912,24 @@
 		} else {
 			/* Always increment rate when its checked as it doesnt depend on the channel */
 			item->rate_usage++;
-			
+
 			if ((max >= 0) && (item->rate_usage > (uint32_t)max)) {
 				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Usage for %s exceeds maximum rate of %d/%ds, now at %d\n", hashkey, max, interval, item->rate_usage);
-				if (*xfer_exten == '!') {
-					switch_channel_hangup(channel, switch_channel_str2cause(xfer_exten+1));
-				} else {
-					switch_ivr_session_transfer(session, xfer_exten, argv[4], argv[5]);
-				}
+				status = SWITCH_FALSE;
 				goto end;
 			}
 		}
 	} else if ((max >= 0) && (item->total_usage + increment > (uint32_t)max)) {
 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Usage for %s is already at max value (%d)\n", hashkey, item->total_usage);
-		if (*xfer_exten == '!') {
-			switch_channel_hangup(channel, switch_channel_str2cause(xfer_exten+1));
-		} else {
-			switch_ivr_session_transfer(session, xfer_exten, argv[4], argv[5]);
-		}
+		status = SWITCH_FALSE;
 		goto end;
 	}
 
 	if (increment) {
 		item->total_usage++;
-		
+
 		switch_core_hash_insert(pvt->hash, hashkey, item);
-		
+
 		if (max == -1) {
 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Usage for %s is now %d\n", hashkey, item->total_usage);
 		} else if (interval == 0) {
@@ -984,29 +937,175 @@
 		} else {
 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Usage for %s is now %d/%d for the last %d seconds\n", hashkey, item->rate_usage, max, interval);
 		}
-		
+
 		limit_fire_event(realm, id, item->total_usage, item->rate_usage, max, max >=0 ? (uint32_t)max : 0);
 	}
-	
+
 	/* Save current usage & rate into channel variables so it can be used later in the dialplan, or added to CDR records */
 	{
 		const char *susage = switch_core_session_sprintf(session, "%d", item->total_usage);
 		const char *srate = switch_core_session_sprintf(session, "%d", item->rate_usage);
-		
+
 		switch_channel_set_variable(channel, "limit_usage", susage);
 		switch_channel_set_variable(channel, switch_core_session_sprintf(session, "limit_usage_%s", hashkey), susage);
-		 
+
 		switch_channel_set_variable(channel, "limit_rate", srate);
 		switch_channel_set_variable(channel, switch_core_session_sprintf(session, "limit_rate_%s", hashkey), srate);
 	}
 
 	switch_core_event_hook_add_state_change(session, hash_state_handler);
 
-	
 end:	
 	switch_mutex_unlock(globals.limit_hash_mutex);
+	return status;
 }
 
+/* !\brief Releases usage of a limit_hash-controlled ressource  */
+static void limit_hash_release(switch_core_session_t *session, const char *realm, const char *id)
+{
+	switch_channel_t *channel = switch_core_session_get_channel(session);
+	limit_hash_private_t *pvt = switch_channel_get_private(channel, "limit_hash");
+	limit_hash_item_t *item = NULL;
+	char *hashkey = switch_core_session_sprintf(session, "%s_%s", realm, id);
+	
+	switch_mutex_lock(globals.limit_hash_mutex);
+	
+	item = (limit_hash_item_t*)switch_core_hash_find(pvt->hash, hashkey);
+	item->total_usage--;	
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Usage for %s is now %d\n", (const char*)hashkey, item->total_usage);
+	
+	switch_core_hash_delete(pvt->hash, hashkey);
+	
+	if (item->total_usage == 0)  {
+		/* Noone is using this item anymore */
+		switch_core_hash_delete(globals.limit_hash, (const char*)hashkey);
+		free(item);
+	}
+	
+	switch_mutex_unlock(globals.limit_hash_mutex);
+}
+
+#define LIMITHASH_USAGE "<realm> <id> [<max>[/interval]] [number [dialplan [context]]]"
+#define LIMITHASH_DESC "limit access to a resource and transfer to an extension if the limit is exceeded"
+SWITCH_STANDARD_APP(limit_hash_function)
+{
+	int argc = 0;
+	char *argv[6] = { 0 };
+	char *mydata = NULL;
+	char *realm = NULL;
+	char *id = NULL;
+	char *xfer_exten = NULL;
+	int max = -1;
+	int interval = 0;
+	char *szinterval = NULL;
+	switch_channel_t *channel = switch_core_session_get_channel(session);
+
+	/* Parse application data  */
+	if (!switch_strlen_zero(data)) {
+		mydata = switch_core_session_strdup(session, data);
+		argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
+	}
+	
+	if (argc < 2) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "USAGE: limit_hash %s\n", LIMITHASH_USAGE);
+		return;
+	}
+	
+	realm = argv[0];
+	id = argv[1];
+	
+	/* If max is omitted, only act as a counter and skip maximum checks */
+	if (argc > 2) {
+		if ((szinterval = strchr(argv[2], '/')))
+		{
+			*szinterval++ = '\0';
+			interval = atoi(szinterval);
+		}
+		
+		max = atoi(argv[2]);
+		
+		if (max < 0) {
+			max = 0;
+		}
+	}
+
+	if (argc > 3) {
+		xfer_exten = argv[3];
+	} else {
+		xfer_exten = limit_def_xfer_exten;
+	}
+	
+	if (!do_limit_hash(session, realm, id, max, interval)) {
+		/* Limit exceeded */
+		if (*xfer_exten == '!') {
+			switch_channel_hangup(channel, switch_channel_str2cause(xfer_exten+1));
+		} else {
+			switch_ivr_session_transfer(session, xfer_exten, argv[4], argv[5]);
+		}
+	}
+}
+
+
+#define LIMITHASHEXECUTE_USAGE "<realm> <id> [<max>[/interval]] [number [dialplan [context]]]"
+#define LIMITHASHEXECUTE_DESC "limit access to a resource. the specified application will only be executed if the resource is available"
+SWITCH_STANDARD_APP(limit_hash_execute_function)
+{
+	int argc = 0;
+	char *argv[6] = { 0 };
+	char *mydata = NULL;
+	char *realm = NULL;
+	char *id = NULL;
+	char *app = NULL;
+	char *app_arg = NULL;
+	int max = -1;
+	int interval = 0;
+	char *szinterval = NULL;
+
+	/* Parse application data  */
+	if (!switch_strlen_zero(data)) {
+		mydata = switch_core_session_strdup(session, data);
+		argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
+	}
+	
+	if (argc < 2) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "USAGE: limit_hash_execute %s\n", LIMITHASHEXECUTE_USAGE);
+		return;
+	}
+	
+	realm = argv[0];
+	id = argv[1];
+	
+	/* If max is omitted, only act as a counter and skip maximum checks */
+	if (argc > 2) {
+		if ((szinterval = strchr(argv[2], '/')))
+		{
+			*szinterval++ = '\0';
+			interval = atoi(szinterval);
+		}
+		
+		max = atoi(argv[2]);
+		
+		if (max < 0) {
+			max = 0;
+		}
+	}
+
+
+	app = argv[3];
+	app_arg = argv[4];
+
+	if (switch_strlen_zero(app)) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing application\n");
+		return;
+	}
+
+	if (do_limit_hash(session, realm, id, max, interval)) {
+		switch_core_session_execute_application(session, app, app_arg);
+		limit_hash_release(session, realm, id);
+	}
+}
+
+
 #define LIMIT_HASH_USAGE_USAGE "<realm> <id>"
 SWITCH_STANDARD_API(limit_hash_usage_function)
 {
@@ -1080,6 +1179,7 @@
 
 	SWITCH_ADD_APP(app_interface, "limit", "Limit", LIMIT_DESC, limit_function, LIMIT_USAGE, SAF_SUPPORT_NOMEDIA);
 	SWITCH_ADD_APP(app_interface, "limit_hash", "Limit (hash)", LIMITHASH_DESC, limit_hash_function, LIMITHASH_USAGE, SAF_SUPPORT_NOMEDIA);
+	SWITCH_ADD_APP(app_interface, "limit_hash_execute", "Limit (hash)", LIMITHASHEXECUTE_USAGE, limit_hash_execute_function, LIMITHASHEXECUTE_USAGE, SAF_SUPPORT_NOMEDIA);
 	SWITCH_ADD_APP(app_interface, "db", "Insert to the db", DB_DESC, db_function, DB_USAGE, SAF_SUPPORT_NOMEDIA);
 	SWITCH_ADD_APP(app_interface, "hash", "Insert into the hashtable", HASH_DESC, hash_function, HASH_USAGE, SAF_SUPPORT_NOMEDIA)
 	SWITCH_ADD_APP(app_interface, "group", "Manage a group", GROUP_DESC, group_function, GROUP_USAGE, SAF_SUPPORT_NOMEDIA);