/**
 * collectd - src/openvpn.c
 * Copyright (C) 2008  Doug MacEachern
 * Copyright (C) 2009  Florian octo Forster
 *
 * This program 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; only version 2 of the License is applicable.
 *
 * 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
 * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
 *
 * Authors:
 *   Doug MacEachern <dougm at hyperic.com>
 *   Florian octo Forster <octo at verplant.org>
 **/

#include "collectd.h"
#include "common.h"
#include "plugin.h"

#define DEFAULT_STATUS_FILE "/etc/openvpn/openvpn-status.log"
#define CLIENT_LIST_PREFIX  "CLIENT_LIST,"

static char *status_file = NULL;

/* For compression stats we need to convert these counters to a rate. */
static counter_t pre_compress_old    = 0;
static counter_t post_compress_old   = 0;
static counter_t pre_decompress_old  = 0;
static counter_t post_decompress_old = 0;
static int compression_counter_valid = 0;

static const char *config_keys[] =
{
	"StatusFile"
};
static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);

/* copy-n-pasted from common.c - changed delim to "," */
static int openvpn_strsplit (char *string, char **fields, size_t size)
{
        size_t i;
        char *ptr;
        char *saveptr;

        i = 0;
        ptr = string;
        saveptr = NULL;
        while ((fields[i] = strtok_r (ptr, ",", &saveptr)) != NULL)
        {
                ptr = NULL;
                i++;

                if (i >= size)
                        break;
        }

        return (i);
} /* int openvpn_strsplit */

static void openvpn_submit (char *name, counter_t rx, counter_t tx)
{
	value_t values[2];
	value_list_t vl = VALUE_LIST_INIT;

	values[0].counter = rx;
	values[1].counter = tx;

	vl.values = values;
	vl.values_len = STATIC_ARRAY_SIZE (values);
	sstrncpy (vl.host, hostname_g, sizeof (vl.host));
	sstrncpy (vl.plugin, "openvpn", sizeof (vl.plugin));
	sstrncpy (vl.plugin_instance, name, sizeof (vl.plugin_instance));
	sstrncpy (vl.type, "if_octets", sizeof (vl.type));

	plugin_dispatch_values (&vl);
} /* void openvpn_submit */

static void compression_submit (char *type_instance, gauge_t ratio)
{
	value_t values[1];
	value_list_t vl = VALUE_LIST_INIT;

	values[0].gauge = ratio;

	vl.values = values;
	vl.values_len = STATIC_ARRAY_SIZE (values);
	sstrncpy (vl.host, hostname_g, sizeof (vl.host));
	sstrncpy (vl.plugin, "openvpn", sizeof (vl.plugin));
	sstrncpy (vl.type, "compression_ratio", sizeof (vl.type));
	sstrncpy (vl.type_instance, type_instance, sizeof (vl.type));

	plugin_dispatch_values (&vl);
} /* void compression_submit */

static int openvpn_read (void)
{
	FILE *fh;
	char buffer[1024];
	char *fields[10];
	const int max_fields = STATIC_ARRAY_SIZE (fields);
	int   fields_num;

	counter_t pre_compress_new    = 0;
	counter_t post_compress_new   = 0;
	counter_t pre_decompress_new  = 0;
	counter_t post_decompress_new = 0;

	/* Clear the least significant four bits, just to make sure all four
	 * counters above are considered to be invalid. */
	compression_counter_valid &= ~0x0f;

	fh = fopen ((status_file != NULL)
			? status_file
			: DEFAULT_STATUS_FILE, "r");
	if (fh == NULL)
		return (-1);

        /* status file is generated by openvpn/multi.c:multi_print_status()
	 * this plugin requires server.conf: "status-version 2"
         * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c
         */
	while (fgets (buffer, sizeof (buffer), fh) != NULL)
	{
		fields_num = openvpn_strsplit (buffer, fields, max_fields);

		/* Expect at least ``key,value''. */
		if (fields_num < 2)
			continue;

		if (strcmp (fields[0], "CLIENT_LIST") == 0)
		{
			char *name;
			counter_t rx;
			counter_t tx;

			/* The line we're expecting has 8 fields. We ignore all lines
			 * with more or less fields. */
			if (fields_num != 8)
				continue;

			name =      fields[1];  /* "Common Name" */
			rx = atoll (fields[4]); /* "Bytes Received */
			tx = atoll (fields[5]); /* "Bytes Sent" */
			openvpn_submit (name, rx, tx);
		}
		else if (strcmp (fields[0], "pre-compress") == 0)
		{
			pre_compress_new = atoll (fields[1]);
			compression_counter_valid |= 0x01;
		}
		else if (strcmp (fields[0], "post-compress") == 0)
		{
			post_compress_new = atoll (fields[1]);
			compression_counter_valid |= 0x02;
		}
		else if (strcmp (fields[0], "pre-decompress") == 0)
		{
			pre_decompress_new = atoll (fields[1]);
			compression_counter_valid |= 0x04;
		}
		else if (strcmp (fields[0], "post-decompress") == 0)
		{
			post_decompress_new = atoll (fields[1]);
			compression_counter_valid |= 0x08;
		}
	}
	fclose (fh);

	/* Check that all four counters are valid, {pre,post}_*_{old,new}. */
	if ((compression_counter_valid & 0x33) == 0x33)
	{
		counter_t pre_diff;
		counter_t post_diff;

		pre_diff = counter_diff (pre_compress_old, pre_compress_new);
		post_diff = counter_diff (post_compress_old, post_compress_new);

		/* If we compress, we're sending. */
		compression_submit ("tx",
				((gauge_t) post_diff) / ((gauge_t) pre_diff));
	}

	/* Now check the other found counters. */
	if ((compression_counter_valid & 0xcc) == 0xcc)
	{
		counter_t pre_diff;
		counter_t post_diff;

		pre_diff = counter_diff (pre_decompress_old, pre_decompress_new);
		post_diff = counter_diff (post_decompress_old, post_decompress_new);

		/* If we decompress, we're receiving. */
		compression_submit ("rx",
				((gauge_t) pre_diff) / ((gauge_t) post_diff));
	}

	/* Now copy all the new counters to the old counters and move the flags
	 * up. */
	pre_compress_old = pre_compress_new;
	post_compress_old = post_compress_new;
	pre_decompress_old = pre_decompress_new;
	post_decompress_old = post_decompress_new;
	compression_counter_valid = (compression_counter_valid & 0x0f) << 4;

	return (0);
} /* int openvpn_read */

static int openvpn_config (const char *key, const char *value)
{
	if (strcasecmp ("StatusFile", key) == 0)
	{
		sfree (status_file);
		status_file = sstrdup (value);
	}
	else
	{
		return (-1);
	}
        return (0);
} /* int openvpn_config */

void module_register (void)
{
	plugin_register_config ("openvpn", openvpn_config,
				config_keys, config_keys_num);
	plugin_register_read ("openvpn", openvpn_read);
} /* void module_register */
