#include <stdio.h>
#include "mls_level.h"
#include <sepol/policydb/ebitmap.h>

mls_level_t *mls_level_from_string(char *mls_context)
{
	char delim;
	char *scontextp, *p, *lptr;
	mls_level_t *l;

	if (!mls_context) {
		return NULL;
	}

	l = (mls_level_t *) calloc(1, sizeof(mls_level_t));

	/* Extract low sensitivity. */
	scontextp = p = mls_context;
	while (*p && *p != ':' && *p != '-')
		p++;

	delim = *p;
	if (delim != 0)
		*p++ = 0;

	if (*scontextp != 's')
		goto err;
	l->sens = atoi(scontextp + 1);

	if (delim == ':') {
		/* Extract category set. */
		while (1) {
			scontextp = p;
			while (*p && *p != ',' && *p != '-')
				p++;
			delim = *p;
			if (delim != 0)
				*p++ = 0;

			/* Separate into level if exists */
			if ((lptr = strchr(scontextp, '.')) != NULL) {
				/* Remove '.' */
				*lptr++ = 0;
			}

			if (*scontextp != 'c')
				goto err;
			int bit = atoi(scontextp + 1);
			if (ebitmap_set_bit(&l->cat, bit, 1))
				goto err;

			/* If level, set all categories in level */
			if (lptr) {
				if (*lptr != 'c')
					goto err;
				int ubit = atoi(lptr + 1);
				int i;
				for (i = bit + 1; i <= ubit; i++) {
					if (ebitmap_set_bit
					    (&l->cat, i, 1))
						goto err;
				}
			}

			if (delim != ',')
				break;
		}
	}

	return l;

      err:
	free(l);
	return NULL;
}

/*
 * Return the length in bytes for the MLS fields of the
 * security context string representation of `context'.
 */
unsigned int mls_compute_string_len(mls_level_t *l)
{
	unsigned int len = 0;
	char temp[16];
	unsigned int i, level = 0;
	ebitmap_node_t *cnode;

	if (!l)
		return 0;

	len += snprintf(temp, sizeof(temp), "s%d", l->sens);

	ebitmap_for_each_bit(&l->cat, cnode, i) {
		if (ebitmap_node_get_bit(cnode, i)) {
			if (level) {
				level++;
				continue;
			}

			len++; /* : or ,` */

			len += snprintf(temp, sizeof(temp), "c%d", i);
			level++;
		} else {
			if (level > 1)
				len += snprintf(temp, sizeof(temp), ".c%d", i-1);
			level = 0;
		}
	}

	/* Handle case where last category is the end of level */
	if (level > 1)
		len += snprintf(temp, sizeof(temp), ".c%d", i-1);
	return len;
}

char *mls_level_to_string(mls_level_t *l)
{
	unsigned int wrote_sep, len = mls_compute_string_len(l);
	unsigned int i, level = 0;
	ebitmap_node_t *cnode;
	wrote_sep = 0;

	if (len == 0)
		return NULL;
	char *result = (char *)malloc(len + 1);
	char *p = result;

	p += sprintf(p, "s%d", l->sens);

	/* categories */
	ebitmap_for_each_bit(&l->cat, cnode, i) {
		if (ebitmap_node_get_bit(cnode, i)) {
			if (level) {
				level++;
				continue;
			}

			if (!wrote_sep) {
				*p++ = ':';
				wrote_sep = 1;
			} else
				*p++ = ',';
			p += sprintf(p, "c%d", i);
			level++;
		} else {
			if (level > 1) {
				if (level > 2)
					*p++ = '.';
				else
					*p++ = ',';

				p += sprintf(p, "c%d", i-1);
			}
			level = 0;
		}
	}
	/* Handle case where last category is the end of level */
	if (level > 1) {
		if (level > 2)
			*p++ = '.';
		else
			*p++ = ',';

		p += sprintf(p, "c%d", i-1);
	}

	*(result + len) = 0;
	return result;
}
