/** Converts a serialized cache entry back into a structure
 *
 * @param c Cache entry to populate (should already be allocated)
 * @param in String representation of cache entry.
 * @param inlen Length of string. May be < 0 in which case strlen will be
 *	used to calculate the length of the string.
 * @return 0 on success, -1 on error.
 */
int cache_deserialize(rlm_cache_entry_t *c, char *in, ssize_t inlen)
{
	vp_cursor_t packet, control, reply;

	TALLOC_CTX *store = NULL;
	char *p, *q;

	store = talloc_pool(c, 1024);
	if (!store) return -1;

	if (inlen < 0) inlen = strlen(in);

	fr_cursor_init(&packet, &c->packet);
	fr_cursor_init(&control, &c->control);
	fr_cursor_init(&reply, &c->reply);

	p = in;

	while (((size_t)(p - in)) < (size_t)inlen) {
		value_pair_map_t *map = NULL;
		VALUE_PAIR *vp = NULL;
		ssize_t len;

		q = strchr(p, '\n');
		if (!q) break;	/* List should also be terminated with a \n */
		*q = '\0';

		if (map_afrom_attr_str(store, &map, p,
				       REQUEST_CURRENT, PAIR_LIST_REQUEST,
				       REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) {
			fr_strerror_printf("Failed parsing pair: %s", p);
			goto error;
		}

		if (map->lhs->type != TMPL_TYPE_ATTR) {
			fr_strerror_printf("Pair left hand side \"%s\" parsed as %s, needed attribute.  "
					   "Check local dictionaries", map->lhs->name,
					   fr_int2str(tmpl_names, map->lhs->type, "<INVALID>"));
			goto error;
		}

		if (map->rhs->type != TMPL_TYPE_LITERAL) {
			fr_strerror_printf("Pair right hand side \"%s\" parsed as %s, needed literal.  "
					   "Check serialized data quoting", map->rhs->name,
					   fr_int2str(tmpl_names, map->rhs->type, "<INVALID>"));
			goto error;
		}

		/*
		 *	Convert literal to a type appropriate for
		 *	the VP.
		 */
		if (tmpl_cast_in_place(map->rhs, map->lhs->tmpl_da->type, map->lhs->tmpl_da) < 0) goto error;

		vp = pairalloc(c, map->lhs->tmpl_da);
		len = value_data_copy(vp, &vp->data, map->rhs->tmpl_data_type,
				      &map->rhs->tmpl_data_value, map->rhs->tmpl_data_length);
		if (len < 0) goto error;

		/*
		 *	Pull out the special attributes, and set the
		 *	relevant cache entry fields.
		 */
		if (vp->da->vendor == 0) switch (vp->da->attr) {
		case PW_CACHE_CREATED:
			c->created = vp->vp_date;
			talloc_free(vp);
			goto next;

		case PW_CACHE_EXPIRES:
			c->expires = vp->vp_date;
			talloc_free(vp);
			goto next;

		default:
			break;
		}

		switch (map->lhs->tmpl_list) {
		case PAIR_LIST_REQUEST:
			fr_cursor_insert(&packet, vp);
			break;

		case PAIR_LIST_CONTROL:
			fr_cursor_insert(&control, vp);
			break;

		case PAIR_LIST_REPLY:
			fr_cursor_insert(&reply, vp);
			break;

		default:
			fr_strerror_printf("Invalid cache list for pair: %s", p);
		error:
			talloc_free(vp);
			talloc_free(map);
			return -1;
		}
	next:
		p = q + 1;
		talloc_free(map);
	}

	return 0;
}
/** Create and insert a cache entry.
 *
 * @return
 *	- #RLM_MODULE_OK on success.
 *	- #RLM_MODULE_UPDATED if we merged the cache entry.
 *	- #RLM_MODULE_FAIL on failure.
 */
static rlm_rcode_t cache_insert(rlm_cache_t *inst, REQUEST *request, rlm_cache_handle_t **handle,
				char const *key, int ttl)
{
	vp_map_t		const *map;
	vp_map_t		**last, *c_map;

	VALUE_PAIR		*vp;
	bool			merge = false;
	rlm_cache_entry_t	*c;

	TALLOC_CTX		*pool;

	if ((inst->max_entries > 0) && inst->module->count &&
	    (inst->module->count(inst, request, handle) > inst->max_entries)) {
		RWDEBUG("Cache is full: %d entries", inst->max_entries);
		return RLM_MODULE_FAIL;
	}

	c = cache_alloc(inst, request);
	if (!c) return RLM_MODULE_FAIL;

	c->key = talloc_typed_strdup(c, key);
	c->created = c->expires = request->timestamp;
	c->expires += ttl;

	last = &c->maps;

	RDEBUG("Creating new cache entry");

	/*
	 *	Alloc a pool so we don't have excessive mallocs when
	 *	gathering VALUE_PAIRs to cache.
	 */
	pool = talloc_pool(NULL, 1024);
	for (map = inst->maps; map != NULL; map = map->next) {
		VALUE_PAIR	*to_cache = NULL;
		vp_cursor_t	cursor;

		rad_assert(map->lhs && map->rhs);

		/*
		 *	Calling map_to_vp gives us exactly the same result,
		 *	as if this were an update section.
		 */
		if (map_to_vp(pool, &to_cache, request, map, NULL) < 0) {
			RDEBUG("Skipping %s", map->rhs->name);
			continue;
		}

		for (vp = fr_cursor_init(&cursor, &to_cache);
		     vp;
		     vp = fr_cursor_next(&cursor)) {
			/*
			 *	Prevent people from accidentally caching
			 *	cache control attributes.
			 */
			if (map->rhs->type == TMPL_TYPE_LIST) switch (vp->da->attr) {
			case PW_CACHE_TTL:
			case PW_CACHE_STATUS_ONLY:
			case PW_CACHE_READ_ONLY:
			case PW_CACHE_MERGE:
			case PW_CACHE_ENTRY_HITS:
				RDEBUG2("Skipping %s", vp->da->name);
				continue;

			default:
				break;
			}

			RINDENT();
			if (RDEBUG_ENABLED2) map_debug_log(request, map, vp);
			REXDENT();

			MEM(c_map = talloc_zero(c, vp_map_t));
			c_map->op = map->op;

			/*
			 *	Now we turn the VALUE_PAIRs into maps.
			 */
			switch (map->lhs->type) {
			/*
			 *	Attributes are easy, reuse the LHS, and create a new
			 *	RHS with the value_data_t from the VALUE_PAIR.
			 */
			case TMPL_TYPE_ATTR:
				c_map->lhs = map->lhs;	/* lhs shouldn't be touched, so this is ok */
			do_rhs:
				MEM(c_map->rhs = tmpl_init(talloc(c_map, vp_tmpl_t),
							   TMPL_TYPE_DATA, map->rhs->name, map->rhs->len));
				if (value_data_copy(c_map->rhs, &c_map->rhs->tmpl_data_value,
						    vp->da->type, &vp->data) < 0) {
					REDEBUG("Failed copying attribute value");
					talloc_free(pool);
					talloc_free(c);
					return RLM_MODULE_FAIL;
				}
				c_map->rhs->tmpl_data_type = vp->da->type;
				break;

			/*
			 *	Lists are weird... We need to fudge a new LHS template,
			 *	which is a combination of the LHS list and the attribute.
			 */
			case TMPL_TYPE_LIST:
			{
				char attr[256];

				MEM(c_map->lhs = tmpl_init(talloc(c_map, vp_tmpl_t),
							   TMPL_TYPE_ATTR, map->lhs->name, map->lhs->len));
				c_map->lhs->tmpl_da = vp->da;
				c_map->lhs->tmpl_tag = vp->tag;
				c_map->lhs->tmpl_list = map->lhs->tmpl_list;
				c_map->lhs->tmpl_num = map->lhs->tmpl_num;
				c_map->lhs->tmpl_request = map->lhs->tmpl_request;

				/*
				 *	We need to rebuild the attribute name, to be the
				 *	one we copied from the source list.
				 */
				c_map->lhs->len = tmpl_prints(attr, sizeof(attr), c_map->lhs, NULL);
				c_map->lhs->name = talloc_strdup(map->lhs, attr);
			}
				goto do_rhs;

			default:
				rad_assert(0);
			}
			*last = c_map;
			last = &(*last)->next;
		}
		talloc_free_children(pool); /* reset pool state */
	}
	talloc_free(pool);

	/*
	 *	Check to see if we need to merge the entry into the request
	 */
	vp = pairfind(request->config, PW_CACHE_MERGE, 0, TAG_ANY);
	if (vp && (vp->vp_integer > 0)) merge = true;

	if (merge) cache_merge(inst, request, c);

	for (;;) {
		cache_status_t ret;

		ret = inst->module->insert(inst, request, handle, c);
		switch (ret) {
		case CACHE_RECONNECT:
			if (cache_reconnect(inst, request, handle) == 0) continue;
			return RLM_MODULE_FAIL;

		case CACHE_OK:
			RDEBUG("Commited entry, TTL %d seconds", ttl);
			cache_free(inst, &c);
			return merge ? RLM_MODULE_UPDATED :
				       RLM_MODULE_OK;

		default:
			talloc_free(c);	/* Failed insertion - use talloc_free not the driver free */
			return RLM_MODULE_FAIL;
		}
	}
}