#define MODULE_LOG_PREFIX "config"

#include "globals.h"
#include "module-stat.h"
#include "oscam-aes.h"
#include "oscam-array.h"
#include "oscam-conf.h"
#include "oscam-conf-chk.h"
#include "oscam-conf-mk.h"
#include "oscam-config.h"
#include "oscam-garbage.h"
#include "oscam-lock.h"
#include "oscam-reader.h"
#include "oscam-string.h"
#ifdef MODULE_GBOX
#include "module-gbox.h"
#endif
#ifdef CS_CACHEEX_AIO
#include "module-cacheex.h"
#endif

#define cs_srvr "oscam.server"

extern const struct s_cardreader *cardreaders[];
extern char *RDR_CD_TXT[];

static void reader_label_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		int i, found = 0;
		if(!cs_strlen(value))
			{ return; }
		for(i = 0; i < (int)cs_strlen(value); i++)
		{
			if(value[i] == ' ')
			{
				value[i] = '_';
				found++;
			}
		}
		if(found)
			{ fprintf(stderr, "Configuration reader: corrected label to %s\n", value); }
		cs_strncpy(rdr->label, value, sizeof(rdr->label));
		return;
	}
	fprintf_conf(f, token, "%s\n", rdr->label);
}

static void ecmwhitelist_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		if(cs_strlen(value))
			chk_ecm_whitelist(value, &rdr->ecm_whitelist);
		else
			ecm_whitelist_clear(&rdr->ecm_whitelist);
		return;
	}

	value = mk_t_ecm_whitelist(&rdr->ecm_whitelist);
	if(cs_strlen(value) > 0 || cfg.http_full_cfg)
		{ fprintf_conf(f, token, "%s\n", value); }
	free_mk_t(value);
}

static void ecmheaderwhitelist_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		if(cs_strlen(value))
			chk_ecm_hdr_whitelist(value, &rdr->ecm_hdr_whitelist);
		else
			ecm_hdr_whitelist_clear(&rdr->ecm_hdr_whitelist);
		return;
	}

	value = mk_t_ecm_hdr_whitelist(&rdr->ecm_hdr_whitelist);
	if(cs_strlen(value) > 0 || cfg.http_full_cfg)
		{ fprintf_conf(f, token, "%s\n", value); }
	free_mk_t(value);
}

static void protocol_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		if(cs_strlen(value) == 0)
			{ return; }
		struct protocol_map
		{
			char *name;
			int typ;
		} protocols[] =
		{
			{ "serial",     R_SERIAL },
			{ "camd35",     R_CAMD35 },
			{ "cs378x",     R_CS378X },
			{ "cs357x",     R_CAMD35 },
			{ "camd33",     R_CAMD33 },
			{ "gbox",       R_GBOX },
			{ "cccam",      R_CCCAM },
			{ "cccam_ext",  R_CCCAM },
			{ "cccam_mcs",  R_CCCAM },
			{ "constcw",    R_CONSTCW },
			{ "radegast",   R_RADEGAST },
			{ "scam",       R_SCAM },
			{ "ghttp",      R_GHTTP },
			{ "newcamd",    R_NEWCAMD },
			{ "newcamd525", R_NEWCAMD },
			{ "newcamd524", R_NEWCAMD },
			{ "drecas",     R_DRECAS },
			{ "emu",        R_EMU },
			{ NULL,         0 }
		}, *p;
		int i;
		// Parse card readers
		for(i = 0; cardreaders[i]; i++)
		{
			if(streq(value, cardreaders[i]->desc))
			{
				rdr->crdr = cardreaders[i];
				rdr->typ = cardreaders[i]->typ;
				return;
			}
		}
		// Parse protocols
		for(i = 0, p = &protocols[0]; p->name; p = &protocols[++i])
		{
			if(streq(p->name, value))
			{
				rdr->typ = p->typ;
				break;
			}
		}
		if(rdr->typ == R_NEWCAMD)
			{ rdr->ncd_proto = streq(value, "newcamd524") ? NCD_524 : NCD_525; }
		if(!rdr->typ)
			{
				fprintf(stderr, "ERROR: '%s' is unsupported reader protocol!\n", value);
				rdr->enable = 0;
			}
		return;
	}
	fprintf_conf(f, token, "%s\n", reader_get_type_desc(rdr, 0));
}

static void device_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	int32_t isphysical = !is_network_reader(rdr);
	if(value)
	{
		int32_t i;
		char *ptr, *saveptr1 = NULL;
		for(i = 0, ptr = strtok_r(value, ",", &saveptr1); (i < 3) && (ptr); ptr = strtok_r(NULL, ",", &saveptr1), i++)
		{
			trim(ptr);
			switch(i)
			{
			case 0:
				cs_strncpy(rdr->device, ptr, sizeof(rdr->device));
				break;
			case 1:
				rdr->r_port = atoi(ptr);
				break;
			case 2:
				rdr->l_port = atoi(ptr);
				break;
			}
		}
		return;
	}
	fprintf_conf(f, token, "%s", rdr->device); // it should not have \n at the end
	if((rdr->r_port || cfg.http_full_cfg) && !isphysical)
		{ fprintf(f, ",%d", rdr->r_port); }
	if((rdr->l_port || cfg.http_full_cfg) && !isphysical && strncmp(reader_get_type_desc(rdr, 0), "cccam", 5))
		{ fprintf(f, ",%d", rdr->l_port); }
	fprintf(f, "\n");
}

static void reader_services_fn(const char *token, char *value, void *setting, FILE *f)
{
	services_fn(token, value, setting, f);
	if(value)
	{
		struct s_reader *rdr = container_of(setting, struct s_reader, sidtabs);
		if(rdr)
			{ rdr->changes_since_shareupdate = 1; }
	}
}

static void reader_lb_services_fn(const char *token, char *value, void *setting, FILE *f)
{
	services_fn(token, value, setting, f);
	if(value)
	{
		struct s_reader *rdr = container_of(setting, struct s_reader, lb_sidtabs);
		if(rdr)
			{ rdr->changes_since_shareupdate = 1; }
	}
}

static void reader_caid_fn(const char *token, char *value, void *setting, FILE *f)
{
	check_caidtab_fn(token, value, setting, f);
	if(value)
	{
		struct s_reader *rdr = container_of(setting, struct s_reader, ctab);
		if(rdr)
			{ rdr->changes_since_shareupdate = 1; }
	}
}

#ifdef WITH_CARDREADER
static void boxid_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		rdr->boxid = cs_strlen(value) ? a2i(value, 4) : 0;
		return;
	}
	if(rdr->boxid)
		{ fprintf_conf(f, token, "%08X\n", rdr->boxid); }
	else if(cfg.http_full_cfg)
		{ fprintf_conf(f, token, "\n"); }
}
#endif

#ifdef READER_TONGFANG
static void tongfang3_calibsn_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		rdr->tongfang3_calibsn = strlen(value) ? a2i(value, 4) : 0;
		return;
	}
	if(rdr->tongfang3_calibsn)
		fprintf_conf(f, token, "%08X\n", rdr->tongfang3_calibsn);
	else if(cfg.http_full_cfg)
		{ fprintf_conf(f, token, "\n"); }
}

static void tongfang_boxid_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		rdr->tongfang_boxid = cs_strlen(value) ? a2i(value, 4) : 0;
		return;
	}
	if(rdr->tongfang_boxid)
		{ fprintf_conf(f, token, "%08X\n", rdr->tongfang_boxid); }
	else if(cfg.http_full_cfg)
		{ fprintf_conf(f, token, "\n"); }
}

static void stbid_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		int32_t len = cs_strlen(value);
		if(len != 16)
		{
			rdr->stbid_length = 0;
			memset(rdr->stbid, 0, 8);
		}
		else
		{
			if(key_atob_l(value, rdr->stbid, len))
			{
				fprintf(stderr, "reader stbid parse error, %s=%s\n", token, value);
				rdr->stbid_length = 0;
				memset(rdr->stbid, 0, sizeof(rdr->stbid));
			}
			else
			{
				rdr->stbid_length = len/2;
			}
		}
		return;
	}
	int32_t len = rdr->stbid_length;
	if(len > 0)
	{
		char tmp[len * 2 + 1];
		fprintf_conf(f, "stbid", "%s\n", cs_hexdump(0, rdr->stbid, len, tmp, sizeof(tmp)));
	}
	else if(cfg.http_full_cfg)
	{
		fprintf_conf(f, "stbid", "\n");
	}
}

static void tongfang3_deskey_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		int32_t len = cs_strlen(value);
		if(len != 16)
		{
			rdr->tongfang3_deskey_length = 0;
			memset(rdr->tongfang3_deskey, 0, 8);
		}
		else
		{
			if(key_atob_l(value, rdr->tongfang3_deskey, len))
			{
				fprintf(stderr, "reader tongfang3_deskey parse error, %s=%s\n", token, value);
				rdr->tongfang3_deskey_length = 0;
				memset(rdr->tongfang3_deskey, 0, sizeof(rdr->tongfang3_deskey));
			}
			else
			{
				rdr->tongfang3_deskey_length = len/2;
			}
		}
		return;
	}
	int32_t len = rdr->tongfang3_deskey_length;
	if(len > 0)
	{
		char tmp[len * 2 + 1];
		fprintf_conf(f, "tongfang3_deskey", "%s\n", cs_hexdump(0, rdr->tongfang3_deskey, len, tmp, sizeof(tmp)));
	}
	else if(cfg.http_full_cfg)
	{
		fprintf_conf(f, "tongfang3_deskey", "\n");
	}
}
#endif

#ifdef WITH_CARDREADER
static void cwpkkey_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		int32_t len = strlen(value);
	//	rdr_log(rdr, "CWPK config key length: %16X", len);
		if(len == 0 || len > 32)
		{
			rdr->cwpk_mod_length = 0;
			memset(rdr->cwpk_mod, 0, sizeof(rdr->cwpk_mod));
		}
		else
		{
			if(key_atob_l(value, rdr->cwpk_mod, len))
			{
				fprintf(stderr, "reader cwpkkey parse error, %s=%s\n", token, value);
				rdr->cwpk_mod_length = 0;
				memset(rdr->cwpk_mod, 0, sizeof(rdr->cwpk_mod));
			}
			else
			{
				rdr->cwpk_mod_length = len/2;
			}
		}
		return;
	}
	int32_t len = rdr->cwpk_mod_length;
	if(len > 0)
	{
		char tmp[len * 2 + 1];
		fprintf_conf(f, "cwpkkey", "%s\n", cs_hexdump(0, rdr->cwpk_mod, len, tmp, sizeof(tmp)));
	}
	else if(cfg.http_full_cfg)
		{ fprintf_conf(f, "cwpkkey", "\n"); }
}

static void rsakey_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		int32_t len = cs_strlen(value);
		if(len != 128 && len != 240)
		{
			rdr->rsa_mod_length = 0;
			memset(rdr->rsa_mod, 0, 120);
		}
		else
		{
			if(key_atob_l(value, rdr->rsa_mod, len))
			{
				fprintf(stderr, "reader rsakey parse error, %s=%s\n", token, value);
				rdr->rsa_mod_length = 0;
				memset(rdr->rsa_mod, 0, sizeof(rdr->rsa_mod));
			}
			else
			{
				rdr->rsa_mod_length = len/2;
			}
		}
		return;
	}
	int32_t len = rdr->rsa_mod_length;
	if(len > 0)
	{
		char tmp[len * 2 + 1];
		fprintf_conf(f, "rsakey", "%s\n", cs_hexdump(0, rdr->rsa_mod, len, tmp, sizeof(tmp)));
	}
	else if(cfg.http_full_cfg)
		{ fprintf_conf(f, "rsakey", "\n"); }
}

static void deskey_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		int32_t len = cs_strlen(value);
		if(((len % 16) != 0) || len == 0 || len > 128*2)
		{
			rdr->des_key_length = 0;
			memset(rdr->des_key, 0, sizeof(rdr->des_key));
		}
		else
		{
			if(key_atob_l(value, rdr->des_key, len))
			{
				fprintf(stderr, "reader 3DES key parse error, %s=%s\n", token, value);
				rdr->des_key_length = 0;
				memset(rdr->des_key, 0, sizeof(rdr->des_key));
			}
			else
			{
				rdr->des_key_length = len/2;
			}
		}
		return;
	}
	int32_t len = rdr->des_key_length;
	if(len > 0)
	{
		char tmp[len * 2 + 1];
		fprintf_conf(f, "deskey", "%s\n", cs_hexdump(0, rdr->des_key, len, tmp, sizeof(tmp)));
	}
	else if(cfg.http_full_cfg)
		{ fprintf_conf(f, "deskey", "\n"); }
}

static void boxkey_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		int32_t len = cs_strlen(value);
		if(((len % 8) != 0) || len == 0 || len > 32)
		{
			rdr->boxkey_length = 0;
			memset(rdr->boxkey, 0, sizeof(rdr->boxkey));
		}
		else
		{
			if(key_atob_l(value, rdr->boxkey, len))
			{
				fprintf(stderr, "reader boxkey parse error, %s=%s\n", token, value);
				rdr->boxkey_length = 0;
				memset(rdr->boxkey, 0, sizeof(rdr->boxkey));
			}
			else
			{
				rdr->boxkey_length = len/2;
			}
		}
		return;
	}
	int32_t len = rdr->boxkey_length;
	if(len > 0)
	{
		char tmp[len * 2 + 1];
		fprintf_conf(f, "boxkey", "%s\n", cs_hexdump(0, rdr->boxkey, len, tmp, sizeof(tmp)));
	}
	else if(cfg.http_full_cfg)
		{ fprintf_conf(f, "boxkey", "\n"); }
}
#endif

#if defined(READER_NAGRA) || defined(READER_NAGRA_MERLIN)
static void param_fn(const char *token, char *value, void *setting, long data, FILE *f)
{
	uint8_t *var = setting, valid_len = data & 0xFF;
	uint8_t *var_len = (var + (data >> 8));
	if(value)
	{
		int32_t len = cs_strlen(value);
		if(len != valid_len * 2 || key_atob_l(value, var, len))
		{
			if(len > 0)
				{ fprintf(stderr, "reader %s parse error, %s=%s\n", token, token, value); }
			*var_len = 0;
			memset(var, 0, valid_len);
		}
		else
		{
			*var_len = valid_len; // found and correct
		}
		return;
	}
	if(*var_len)
	{
		char tmp[*var_len * 2 + 1];
		fprintf_conf(f, token, "%s\n", cs_hexdump(0, var, *var_len, tmp, sizeof(tmp)));
	}
	else if(cfg.http_full_cfg)
		{ fprintf_conf(f, token, "\n"); }
}
#endif

static void flags_fn(const char *token, char *value, void *setting, long flag, FILE *f)
{
	uint32_t *var = setting;
	if(value)
	{
		int i = atoi(value);
		if(!i && (*var & flag))
			{ *var -= flag; }
		if(i)
			{ *var |= flag; }
		return;
	}
	if((*var & flag) || cfg.http_full_cfg)
		{ fprintf_conf(f, token, "%d\n", (*var & flag) ? 1 : 0); }
}

#ifdef READER_VIDEOGUARD
static void ins7E_fn(const char *token, char *value, void *setting, long var_size, FILE *f)
{
	uint8_t *var = setting;
	var_size -= 1; // var_size contains sizeof(var) which is [X + 1]
	if(value)
	{
		int32_t len = cs_strlen(value);
		if(len != var_size * 2 || key_atob_l(value, var, len))
		{
			if(len > 0)
				{ fprintf(stderr, "reader %s parse error, %s=%s\n", token, token, value); }
			memset(var, 0, var_size + 1);
		}
		else
		{
			var[var_size] = 1; // found and correct
		}
		return;
	}
	if(var[var_size])
	{
		char tmp[var_size * 2 + 1];
		fprintf_conf(f, token, "%s\n", cs_hexdump(0, var, var_size, tmp, sizeof(tmp)));
	}
	else if(cfg.http_full_cfg)
		{ fprintf_conf(f, token, "\n"); }
}

static void ins42_fn(const char *token, char *value, void *setting, long var_size, FILE *f)
{
	uint8_t *var = setting;
	var_size -= 1; // var_size contains sizeof(var) which is [X + 1]
	if(value)
	{
		int32_t len = cs_strlen(value);
		if(len != var_size * 2 || key_atob_l(value, var, len))
		{
			if(len > 0)
				{ fprintf(stderr, "reader %s parse error, %s=%s\n", token, token, value); }
			memset(var, 0, var_size + 1);
		}
		else
		{
			var[var_size] = 1; // found and correct
		}
		return;
	}
	if(var[var_size])
	{
		char tmp[var_size * 2 + 1];
		fprintf_conf(f, token, "%s\n", cs_hexdump(0, var, var_size, tmp, sizeof(tmp)));
	}
	else if(cfg.http_full_cfg)
		{ fprintf_conf(f, token, "\n"); }
}

static void des_and_3des_key_fn(const char *token, char *value, void *setting, FILE *f)
{
	uint8_t *var = setting;
	if(value)
	{
		int32_t len = cs_strlen(value);
		if(((len != 16) && (len != 32)) || (key_atob_l(value, var, len)))
		{
			if(len > 0)
				{ fprintf(stderr, "reader %s parse error, %s=%s\n", token, token, value); }
			memset(var, 0, 17);
		}
		else
		{
			var[16] = len/2;
		}
		return;
	}
	if(var[16])
	{
		char tmp[var[16] * 2 + 1];
		fprintf_conf(f, token, "%s\n", cs_hexdump(0, var, var[16], tmp, sizeof(tmp)));
	}
	else if(cfg.http_full_cfg)
		{ fprintf_conf(f, token, "\n"); }
}
#endif

#ifdef WITH_CARDREADER
static void atr_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		memset(rdr->atr, 0, sizeof(rdr->atr));
		rdr->atrlen = cs_strlen(value);
		if(rdr->atrlen)
		{
			if(rdr->atrlen > (int32_t)sizeof(rdr->atr) * 2)
				{ rdr->atrlen = (int32_t)sizeof(rdr->atr) * 2; }
			key_atob_l(value, rdr->atr, rdr->atrlen);
		}
		return;
	}
	if(rdr->atr[0] || cfg.http_full_cfg)
	{
		int j;
		fprintf_conf(f, token, "%s", ""); // it should not have \n at the end
		if(rdr->atr[0])
		{
			for(j = 0; j < rdr->atrlen / 2; j++)
			{
				fprintf(f, "%02X", rdr->atr[j]);
			}
		}
		fprintf(f, "\n");
	}
}

static void detect_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		int i;
		for(i = 0; RDR_CD_TXT[i]; i++)
		{
			if(!strcmp(value, RDR_CD_TXT[i]))
			{
				rdr->detect = i;
			}
			else
			{
				if(value[0] == '!' && streq(value + 1, RDR_CD_TXT[i]))
					{ rdr->detect = i | 0x80; }
			}
		}
		return;
	}
	fprintf_conf(f, token, "%s%s\n", rdr->detect & 0x80 ? "!" : "", RDR_CD_TXT[rdr->detect & 0x7f]);
}
#endif

void ftab_fn(const char *token, char *value, void *setting, long ftab_type, FILE *f)
{
	FTAB *ftab = setting;
	if(value)
	{
		if(cs_strlen(value))
			chk_ftab(value, ftab);
		else
			ftab_clear(ftab);
		return;
	}
	if(ftab_type & FTAB_READER)
	{
		struct s_reader *rdr = NULL;
		if(ftab_type & FTAB_PROVID)      { rdr = container_of(setting, struct s_reader, ftab); }
		if(ftab_type & FTAB_CHID)        { rdr = container_of(setting, struct s_reader, fchid); }
		if(ftab_type & FTAB_FBPCAID)     { rdr = container_of(setting, struct s_reader, fallback_percaid); }
		if(ftab_type & FTAB_LOCALCARDS)  { rdr = container_of(setting, struct s_reader, localcards); }
		if(ftab_type & FTAB_IGNCHKSMCAID){ rdr = container_of(setting, struct s_reader, disablecrccws_only_for); }
#ifdef WITH_EMU
		if(ftab_type & FTAB_EMUAU)       { rdr = container_of(setting, struct s_reader, emu_auproviders); }
#endif
#ifdef MODULE_GBOX
		if(ftab_type & FTAB_CCCGBXRESHARE){ rdr = container_of(setting, struct s_reader, ccc_gbx_reshare_ident); }
#endif
		if(rdr)
			{ rdr->changes_since_shareupdate = 1; }
	}
	value = mk_t_ftab(ftab);
	if(cs_strlen(value) > 0 || cfg.http_full_cfg)
		{ fprintf_conf(f, token, "%s\n", value); }
	free_mk_t(value);
}

#ifdef READER_VIACCESS
static void aeskeys_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		parse_aes_keys(rdr, value);
		return;
	}
	value = mk_t_aeskeys(rdr);
	if(cs_strlen(value) > 0 || cfg.http_full_cfg)
		{ fprintf_conf(f, token, "%s\n", value); }
	free_mk_t(value);
}
#endif

static void emmcache_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		rdr->cachemm   = 0;
		rdr->rewritemm = 0;
		rdr->logemm    = 0;
		rdr->deviceemm = 0;
		if(cs_strlen(value))
		{
			int i;
			char *ptr, *saveptr1 = NULL;
			for(i = 0, ptr = strtok_r(value, ",", &saveptr1); (i < 4) && (ptr); ptr = strtok_r(NULL, ",", &saveptr1), i++)
			{
				switch(i)
				{
				case 0:
					rdr->cachemm = atoi(ptr);
					break;
				case 1:
					rdr->rewritemm = atoi(ptr);
					break;
				case 2:
					rdr->logemm = atoi(ptr);
					break;
				case 3:
					rdr->deviceemm = atoi(ptr);
				}
			}
			if(rdr->rewritemm <= 0)
			{
				fprintf(stderr, "Setting reader \"emmcache\" to %i,%d,%i,%i instead of %i,%i,%i,%i.",
						rdr->cachemm, 1, rdr->logemm, rdr->deviceemm,
						rdr->cachemm, rdr->rewritemm, rdr->logemm, rdr->deviceemm);
				fprintf(stderr, "Zero or negative number of rewrites is silly\n");
				rdr->rewritemm = 1;
			}
		}
		return;
	}
	if(rdr->cachemm || rdr->logemm || cfg.http_full_cfg)
		{ fprintf_conf(f, token, "%d,%d,%d,%d\n", rdr->cachemm, rdr->rewritemm, rdr->logemm,rdr->deviceemm); }
}

static void blockemm_bylen_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		char *ptr, *saveptr1 = NULL, dash;
		struct s_emmlen_range *blocklen;
		uint32_t num;

		if(!cs_strlen(value))
		{
			ll_destroy_data(&rdr->blockemmbylen);
			return;
		}

		if(!rdr->blockemmbylen)
			{ rdr->blockemmbylen = ll_create("blockemmbylen"); }
		else
			{ ll_clear_data(rdr->blockemmbylen); }

		for(ptr = strtok_r(value, ",", &saveptr1); ptr;
				ptr = strtok_r(NULL, ",", &saveptr1))
		{
			if(!cs_malloc(&blocklen, sizeof(*blocklen)))
				{ return; }
			num = sscanf(ptr, "%hd%c%hd", &blocklen->min, &dash, &blocklen->max);
			if(num <= 0)
			{
				NULLFREE(blocklen);
				fprintf(stderr, "blockemm-bylen parse error: %s\n", value);
				continue;
			}
			if(num == 1) // single values: x1, x2, x3, ...
				{ blocklen->max = blocklen->min; }
			else if(num == 2) // range values with open end: x1-
				{ blocklen->max = 0; }
			ll_append(rdr->blockemmbylen, blocklen);
		}
		return;
	}
	value = mk_t_emmbylen(rdr);
	if(cs_strlen(value) > 0 || cfg.http_full_cfg)
		{ fprintf_conf(f, token, "%s\n", value); }
	free_mk_t(value);
}

static void nano_fn(const char *token, char *value, void *setting, FILE *f)
{
	uint16_t *nano = setting;
	if(value)
	{
		*nano = 0;
		if(cs_strlen(value) > 0)
		{
			if(streq(value, "all"))
			{
				*nano = 0xFFFF;
			}
			else
			{
				int32_t i;
				char *ptr, *saveptr1 = NULL;
				for(ptr = strtok_r(value, ",", &saveptr1); ptr; ptr = strtok_r(NULL, ",", &saveptr1))
				{
					i = (byte_atob(ptr) % 0x80);
					if(i >= 0 && i <= 16)
						{ *nano |= (1 << i); }
				}
			}
		}
		return;
	}
	value = mk_t_nano(*nano);
	if(cs_strlen(value) > 0 || cfg.http_full_cfg)
		{ fprintf_conf(f, token, "%s\n", value); }
	free_mk_t(value);
}

static void auprovid_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		rdr->auprovid = 0;
		if(cs_strlen(value))
			{ rdr->auprovid = a2i(value, 3); }
		return;
	}
	if(rdr->auprovid)
		{ fprintf_conf(f, token, "%06X\n", rdr->auprovid); }
	else if(cfg.http_full_cfg)
		{ fprintf_conf(f, token, "\n"); }
}

static void ratelimitecm_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		rdr->ratelimitecm = 0;
		if(cs_strlen(value))
		{
			int i;
			rdr->ratelimitecm = atoi(value);
			for(i = 0; i < MAXECMRATELIMIT; i++) // reset all slots
			{
				rdr->rlecmh[i].srvid = -1;
				rdr->rlecmh[i].last.time = -1;
			}
		}
		return;
	}
	if(rdr->ratelimitecm || cfg.http_full_cfg)
		{ fprintf_conf(f, token, "%d\n", rdr->ratelimitecm); }
}

static void ecmunique_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		if(cs_strlen(value) == 0)
		{
			rdr->ecmunique = 0; // default
		}
		else
		{
			rdr->ecmunique = atoi(value);
			if(rdr->ecmunique >= 1)
			{ rdr->ecmunique = 1; }
			else
			{ rdr->ecmunique = 0; }
		}
		return;
	}
	if((rdr->ratelimitecm && rdr->ecmunique != 0) || cfg.http_full_cfg)
		{ fprintf_conf(f, token, "%d\n", rdr->ecmunique); }
}

static void ratelimittime_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		if(cs_strlen(value) == 0)
		{
			if(rdr->ratelimitecm > 0)
			{
				rdr->ratelimittime = 9000; // default 9 seconds
				rdr->srvidholdtime = 2000; // default 2 seconds hold
			}
			else
			{
				rdr->ratelimitecm = 0; // in case someone set a negative value
				rdr->ratelimittime = 0;
				rdr->srvidholdtime = 0;
			}
		}
		else
		{
			rdr->ratelimittime = atoi(value);
			if (rdr->ratelimittime < 60) rdr->ratelimittime *= 1000;
		}
		return;
	}
	if(rdr->ratelimitecm || cfg.http_full_cfg)
		{ fprintf_conf(f, token, "%d\n", rdr->ratelimittime); }
}

static void srvidholdtime_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		if(cs_strlen(value) == 0)
		{
			if(rdr->ratelimitecm > 0)
			{
				rdr->srvidholdtime = 2000; // default 2 seconds hold
			}
			else
			{
				rdr->ratelimitecm = 0; // in case someone set a negative value
				rdr->srvidholdtime = 0;
			}
		}
		else
		{
			rdr->srvidholdtime = atoi(value);
			if (rdr->srvidholdtime < 60) rdr->srvidholdtime *=1000;
		}
		return;
	}
	if(rdr->ratelimitecm || cfg.http_full_cfg)
		{ fprintf_conf(f, token, "%d\n", rdr->srvidholdtime); }
}

static void cooldown_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		if(cs_strlen(value) == 0)
		{
			rdr->cooldown[0] = 0;
			rdr->cooldown[1] = 0;
		}
		else
		{
			int32_t i;
			char *ptr, *saveptr1 = NULL;
			for(i = 0, ptr = strtok_r(value, ",", &saveptr1); (i < 2) && (ptr); ptr = strtok_r(NULL, ",", &saveptr1), i++)
			{
				rdr->cooldown[i] = atoi(ptr);
			}
			if(rdr->cooldown[0] <= 0 || rdr->cooldown[1] <= 0)
			{
				fprintf(stderr, "cooldown must have 2 positive values (x,y) set values %d,%d ! cooldown deactivated\n",
						rdr->cooldown[0], rdr->cooldown[1]);
				rdr->cooldown[0] = 0;
				rdr->cooldown[1] = 0;
			}
		}
		return;
	}
	if(rdr->cooldown[0] || cfg.http_full_cfg)
	{
		fprintf_conf(f, token, "%d,%d\n", rdr->cooldown[0], rdr->cooldown[1]);
	}
}

static void parallelfactor_fn(const char *token, char *value, void *setting, FILE *f)
{
	struct s_reader *rdr = setting;
	if(value)
	{
		if(cs_strlen(value) == 0)
		{
			rdr->parallelfactor = 1.5;  // default
		}
		else
		{
			// Parse float manually to avoid locale issues (strtof uses locale decimal separator)
			// Accept both '.' and ',' as decimal separator
			char tmp[32];
			cs_strncpy(tmp, value, sizeof(tmp));

			// Find decimal separator (either . or ,)
			char *sep = strchr(tmp, '.');
			if(!sep) sep = strchr(tmp, ',');

			if(sep)
			{
				*sep = '\0';  // Split at separator
				int32_t integer_part = atoi(tmp);
				int32_t decimal_part = atoi(sep + 1);
				// Calculate decimal value (assuming 1 decimal place)
				rdr->parallelfactor = (float)integer_part + (float)decimal_part / 10.0f;
			}
			else
			{
				rdr->parallelfactor = (float)atoi(tmp);
			}

			if(rdr->parallelfactor < 0)
				rdr->parallelfactor = 1.5;  // default on negative
		}
		return;
	}
	// Write config: use comma as decimal separator
	if(rdr->parallelfactor != 1.5 || cfg.http_full_cfg)
	{
		char buf[16];
		snprintf(buf, sizeof(buf), "%.1f", rdr->parallelfactor);
		char *dot = strchr(buf, '.');
		if(dot) *dot = ',';
		fprintf_conf(f, token, "%s\n", buf);
	}
}

static void cooldowndelay_fn(const char *UNUSED(token), char *value, void *setting, FILE *UNUSED(f))
{
	struct s_reader *rdr = setting;
	if(value)
	{
		rdr->cooldown[0] = cs_strlen(value) ? atoi(value) : 0;
	}
	// This option is *not* written in the config file.
	// It is only set by WebIf as convenience
}

static void cooldowntime_fn(const char *UNUSED(token), char *value, void *setting, FILE *UNUSED(f))
{
	struct s_reader *rdr = setting;
	if(value)
	{
		if(cs_strlen(value) == 0)
		{
			rdr->cooldown[0] = 0; // no cooling down time means no cooling set
			rdr->cooldown[1] = 0;
		}
		else
		{
			rdr->cooldown[1] = atoi(value);
		}
		return;
	}
	// This option is *not* written in the config file.
	// It is only set by WebIf as convenience
}

void reader_fixups_fn(void *var)
{
	struct s_reader *rdr = var;
#ifdef WITH_LB
	if(rdr->lb_weight > 1000)
		{ rdr->lb_weight = 1000; }
	else if(rdr->lb_weight <= 0)
		{ rdr->lb_weight = 100; }
#endif

#ifdef CS_CACHEEX_AIO
	caidtab2ftab_add(&rdr->cacheex.localgenerated_only_in_caidtab, &rdr->cacheex.lg_only_in_tab);
	caidtab_clear(&rdr->cacheex.localgenerated_only_in_caidtab);
	caidtab2ftab_add(&rdr->cacheex.localgenerated_only_caidtab, &rdr->cacheex.lg_only_tab);
	caidtab_clear(&rdr->cacheex.localgenerated_only_caidtab);
#endif

	if(is_cascading_reader(rdr) && (rdr->typ == R_CAMD35 || rdr->typ == R_CS378X))
	{
#ifdef CS_CACHEEX
		if(rdr && rdr->cacheex.mode>1)
			{ rdr->keepalive = 1; } // with cacheex, it is required!
		else
#endif
		if(rdr->typ == R_CAMD35)
			{ rdr->keepalive = 0; } // with NO-cacheex, and UDP, keepalive is not required!
	}

	// Allocate/reallocate parallel_slots if maxparallel changed
	// Two separate arrays: active slots (maxparallel) and pending slots (round(maxparallel * parallelfactor))
	if(rdr->maxparallel > 0)
	{
		// Set default parallelfactor if negative (not configured)
		// parallelfactor = 0 is valid (disables pending slots)
		if(rdr->parallelfactor < 0)
			rdr->parallelfactor = 1.5;

		// Calculate pending slots size: round(maxparallel * parallelfactor) without math.h
		// parallelfactor = 0 means no pending slots (zapping support disabled)
		int32_t pending_size = (int32_t)(rdr->maxparallel * rdr->parallelfactor + 0.5);

		// Only allocate if not yet allocated (first time setup)
		// Size changes require OSCam restart to take effect
		if(!rdr->parallel_slots)
		{
			if(!cs_malloc(&rdr->parallel_slots, rdr->maxparallel * sizeof(struct s_parallel_slot)))
				{ rdr->maxparallel = 0; }  // disable on allocation failure
			else if(pending_size > 0 && !cs_malloc(&rdr->parallel_slots_prov, pending_size * sizeof(struct s_parallel_slot)))
			{
				NULLFREE(rdr->parallel_slots);  // free first array on second failure
				rdr->maxparallel = 0;
			}
			else
			{
				cs_lock_create(__func__, &rdr->parallel_lock, "parallel_lock", 5000);
				rdr->blocked_services = ll_create("blocked_services");
			}

			rdr->parallel_full = 0;  // reset full flag
		}
	}
	else if(rdr->parallel_slots || rdr->parallel_slots_prov)
	{
		// maxparallel was disabled, free the slots
		NULLFREE(rdr->parallel_slots);
		NULLFREE(rdr->parallel_slots_prov);
		ll_destroy_data(&rdr->blocked_services);
		rdr->parallel_full = 0;
	}
}

#define OFS(X) offsetof(struct s_reader, X)
#define SIZEOF(X) sizeof(((struct s_reader *)0)->X)

static const struct config_list reader_opts[] =
{
	DEF_OPT_FIXUP_FUNC(reader_fixups_fn),
	DEF_OPT_FUNC("label"                          , 0,                                    reader_label_fn),
#ifdef WEBIF
	DEF_OPT_STR("description"                     , OFS(description),                     NULL),
#endif
	DEF_OPT_INT8("enable"                         , OFS(enable),                          1),
	DEF_OPT_FUNC("protocol"                       , 0,                                    protocol_fn),
	DEF_OPT_FUNC("device"                         , 0,                                    device_fn),
	DEF_OPT_UINT8("ipv4force"                     , OFS(ipv4force),                       0),
	DEF_OPT_HEX("key"                             , OFS(ncd_key),                         SIZEOF(ncd_key)),
	DEF_OPT_SSTR("user"                           , OFS(r_usr),                           "", SIZEOF(r_usr)),
	DEF_OPT_SSTR("password"                       , OFS(r_pwd),                           "", SIZEOF(r_pwd)),
	DEF_OPT_SSTR("pincode"                        , OFS(pincode),                         "none", SIZEOF(pincode)),
#ifdef MODULE_GBOX
	DEF_OPT_UINT8("gbox_max_distance"             , OFS(gbox_maxdist),                    DEFAULT_GBOX_MAX_DIST),
	DEF_OPT_UINT8("gbox_max_ecm_send"             , OFS(gbox_maxecmsend),                 DEFAULT_GBOX_MAX_ECM_SEND),
	DEF_OPT_UINT8("gbox_reshare"                  , OFS(gbox_reshare),                    DEFAULT_GBOX_RESHARE),
	DEF_OPT_INT8("cccam_reshare"                  , OFS(gbox_cccam_reshare),             -1),
	DEF_OPT_UINT8("force_remm"                    , OFS(gbox_force_remm),                 0),
	DEF_OPT_FUNC_X("ccc_gbx_reshare_ident"        , OFS(ccc_gbx_reshare_ident),           ftab_fn, FTAB_READER | FTAB_CCCGBXRESHARE),
	DEF_OPT_UINT8("send_offline_cmd"              , OFS(send_offline_cmd),                0),
#endif
	DEF_OPT_STR("readnano"                        , OFS(emmfile),                         NULL),
	DEF_OPT_FUNC("services"                       , OFS(sidtabs),                         reader_services_fn),
	DEF_OPT_FUNC("lb_whitelist_services"          , OFS(lb_sidtabs),                      reader_lb_services_fn),
	DEF_OPT_INT32("inactivitytimeout"             , OFS(tcp_ito),                         DEFAULT_INACTIVITYTIMEOUT),
	DEF_OPT_INT32("reconnecttimeout"              , OFS(tcp_rto),                         DEFAULT_TCP_RECONNECT_TIMEOUT),
	DEF_OPT_INT32("reconnectdelay"                , OFS(tcp_reconnect_delay),             60000),
	DEF_OPT_INT32("resetcycle"                    , OFS(resetcycle),                      0),
	DEF_OPT_INT8("disableserverfilter"            , OFS(ncd_disable_server_filt),         0),
	DEF_OPT_INT8("connectoninit"                  , OFS(ncd_connect_on_init),             0),
	DEF_OPT_UINT8("keepalive"                     , OFS(keepalive),                       0),
#ifdef WITH_CARDREADER
	DEF_OPT_INT8("smargopatch"                    , OFS(smargopatch),                     0),
	DEF_OPT_INT8("autospeed"                      , OFS(autospeed),                       1),
	DEF_OPT_UINT8("sc8in1_dtrrts_patch"           , OFS(sc8in1_dtrrts_patch),             0),
#endif
	DEF_OPT_INT8("fallback"                       , OFS(fallback),                        0),
	DEF_OPT_FUNC_X("fallback_percaid"             , OFS(fallback_percaid),                ftab_fn, FTAB_READER | FTAB_FBPCAID),
	DEF_OPT_FUNC_X("localcards"                   , OFS(localcards),                      ftab_fn, FTAB_READER | FTAB_LOCALCARDS),
	DEF_OPT_FUNC_X("disablecrccws_only_for"       , OFS(disablecrccws_only_for),          ftab_fn, FTAB_READER | FTAB_IGNCHKSMCAID),
#ifdef CS_CACHEEX
	DEF_OPT_INT8("cacheex"                        , OFS(cacheex.mode),                    0),
	DEF_OPT_INT8("cacheex_maxhop"                 , OFS(cacheex.maxhop),                  0),
#ifdef CS_CACHEEX_AIO
	DEF_OPT_INT8("cacheex_maxhop_lg"              , OFS(cacheex.maxhop_lg),                  0),
#endif
	DEF_OPT_FUNC("cacheex_maxhop_percaid"          , OFS(cacheex.maxhop_percaid),           caidvaluetab_fn),
#ifdef CS_CACHEEX_AIO
	DEF_OPT_FUNC("cacheex_maxhop_lg_percaid"       , OFS(cacheex.maxhop_lg_percaid),        caidvaluetab_fn),
#endif
	DEF_OPT_FUNC("cacheex_ecm_filter"             , OFS(cacheex.filter_caidtab),          cacheex_hitvaluetab_fn),
	DEF_OPT_UINT8("cacheex_allow_request"         , OFS(cacheex.allow_request),           0),
	DEF_OPT_UINT8("cacheex_drop_csp"              , OFS(cacheex.drop_csp),                0),
	DEF_OPT_UINT8("cacheex_allow_filter"          , OFS(cacheex.allow_filter),            1),
#ifdef CS_CACHEEX_AIO
	DEF_OPT_UINT8("cacheex_allow_maxhop"          , OFS(cacheex.allow_maxhop),            0),
#endif
	DEF_OPT_UINT8("cacheex_block_fakecws"         , OFS(cacheex.block_fakecws),           0),
#ifdef CS_CACHEEX_AIO
	DEF_OPT_UINT8("cacheex_cw_check_for_push"     , OFS(cacheex.cw_check_for_push),       0),
	DEF_OPT_UINT8("cacheex_lg_only_remote_settings", OFS(cacheex.lg_only_remote_settings), 1),
	DEF_OPT_UINT8("cacheex_localgenerated_only"   , OFS(cacheex.localgenerated_only),     0),
	DEF_OPT_FUNC("cacheex_localgenerated_only_caid", OFS(cacheex.localgenerated_only_caidtab), check_caidtab_fn),
	DEF_OPT_FUNC_X("cacheex_lg_only_tab"          , OFS(cacheex.lg_only_tab),             ftab_fn, FTAB_ACCOUNT),
	DEF_OPT_UINT8("cacheex_lg_only_in_aio_only"	  , OFS(cacheex.lg_only_in_aio_only),     0),
	DEF_OPT_UINT8("cacheex_localgenerated_only_in", OFS(cacheex.localgenerated_only_in),  0),
	DEF_OPT_FUNC("cacheex_localgenerated_only_in_caid", OFS(cacheex.localgenerated_only_in_caidtab), check_caidtab_fn),
	DEF_OPT_FUNC_X("cacheex_lg_only_in_tab"       , OFS(cacheex.lg_only_in_tab),          ftab_fn, FTAB_ACCOUNT),
	DEF_OPT_FUNC("cacheex_nopushafter"            , OFS(cacheex.cacheex_nopushafter_tab), caidvaluetab_fn),
#endif
#endif
	DEF_OPT_FUNC("caid"                           , OFS(ctab),                            reader_caid_fn),
#ifdef WITH_CARDREADER
	DEF_OPT_FUNC("atr"                            , 0,                                    atr_fn),
	DEF_OPT_FUNC("boxid"                          , 0,                                    boxid_fn),
#endif
#ifdef  READER_TONGFANG
	DEF_OPT_FUNC("tongfang3_calibsn"              , 0,                                    tongfang3_calibsn_fn),
	DEF_OPT_FUNC("tongfang_boxid"                 , 0,                                    tongfang_boxid_fn),
	DEF_OPT_FUNC("stbid"                          , 0,                                    stbid_fn),
	DEF_OPT_FUNC("tongfang3_deskey"               , 0,                                    tongfang3_deskey_fn),
#endif
#ifdef WITH_CARDREADER
	DEF_OPT_FUNC("boxkey"                         , 0,                                    boxkey_fn),
	DEF_OPT_FUNC("rsakey"                         , 0,                                    rsakey_fn),
	DEF_OPT_FUNC("cwpkkey"                        , 0,                                    cwpkkey_fn),
	DEF_OPT_FUNC("deskey"                         , 0,                                    deskey_fn),
#endif
#ifdef READER_NAGRA_MERLIN
	DEF_OPT_FUNC_X("mod1"                         , OFS(mod1),                            param_fn, SIZEOF(mod1) ^ (OFS(mod1_length) - OFS(mod1)) << 8),
	DEF_OPT_FUNC_X("idird"                        , OFS(idird),                           param_fn, SIZEOF(idird) ^ (OFS(idird_length) - OFS(idird)) << 8),
	DEF_OPT_FUNC_X("cmd0eprov"                    , OFS(cmd0eprov),                       param_fn, SIZEOF(cmd0eprov) ^ (OFS(cmd0eprov_length) - OFS(cmd0eprov)) << 8),
	DEF_OPT_FUNC_X("mod2"                         , OFS(mod2),                            param_fn, SIZEOF(mod2) ^ (OFS(mod2_length) - OFS(mod2)) << 8),
	DEF_OPT_FUNC_X("key3588"                      , OFS(key3588),                         param_fn, SIZEOF(key3588) ^ (OFS(key3588_length) - OFS(key3588)) << 8),
	DEF_OPT_FUNC_X("key3460"                      , OFS(key3460),                         param_fn, SIZEOF(key3460) ^ (OFS(key3460_length) - OFS(key3460)) << 8),
	DEF_OPT_FUNC_X("key3310"                      , OFS(key3310),                         param_fn, SIZEOF(key3310) ^ (OFS(key3310_length) - OFS(key3310)) << 8),
	DEF_OPT_FUNC_X("data50"                       , OFS(data50),                          param_fn, SIZEOF(data50) ^ (OFS(data50_length) - OFS(data50)) << 8),
	DEF_OPT_FUNC_X("mod50"                        , OFS(mod50),                           param_fn, SIZEOF(mod50) ^ (OFS(mod50_length) - OFS(mod50)) << 8),
	DEF_OPT_FUNC_X("nuid"                         , OFS(nuid),                            param_fn, SIZEOF(nuid) ^ (OFS(nuid_length) - OFS(nuid)) << 8),
	DEF_OPT_FUNC_X("forcepair"                    , OFS(forcepair),                       param_fn, SIZEOF(forcepair) ^ (OFS(forcepair_length) - OFS(forcepair)) << 8),
	DEF_OPT_FUNC_X("otpcsc"                       , OFS(otpcsc),                          param_fn, SIZEOF(otpcsc) ^ (OFS(otpcsc_length) - OFS(otpcsc)) << 8),
	DEF_OPT_FUNC_X("otacsc"                       , OFS(otacsc),                          param_fn, SIZEOF(otacsc) ^ (OFS(otacsc_length) - OFS(otacsc)) << 8),
	DEF_OPT_FUNC_X("cwpkcaid"                     , OFS(cwpkcaid),                        param_fn, SIZEOF(cwpkcaid) ^ (OFS(cwpkcaid_length) - OFS(cwpkcaid)) << 8),
	DEF_OPT_FUNC_X("cwekey0"                      , OFS(cwekey[0]),                       param_fn, SIZEOF(cwekey[0]) ^ (OFS(cwekey_length[0]) - OFS(cwekey[0])) << 8),
	DEF_OPT_FUNC_X("cwekey1"                      , OFS(cwekey[1]),                       param_fn, SIZEOF(cwekey[1]) ^ (OFS(cwekey_length[1]) - OFS(cwekey[1])) << 8),
	DEF_OPT_FUNC_X("cwekey2"                      , OFS(cwekey[2]),                       param_fn, SIZEOF(cwekey[2]) ^ (OFS(cwekey_length[2]) - OFS(cwekey[2])) << 8),
	DEF_OPT_FUNC_X("cwekey3"                      , OFS(cwekey[3]),                       param_fn, SIZEOF(cwekey[3]) ^ (OFS(cwekey_length[3]) - OFS(cwekey[3])) << 8),
	DEF_OPT_FUNC_X("cwekey4"                      , OFS(cwekey[4]),                       param_fn, SIZEOF(cwekey[4]) ^ (OFS(cwekey_length[4]) - OFS(cwekey[4])) << 8),
	DEF_OPT_FUNC_X("cwekey5"                      , OFS(cwekey[5]),                       param_fn, SIZEOF(cwekey[5]) ^ (OFS(cwekey_length[5]) - OFS(cwekey[5])) << 8),
	DEF_OPT_FUNC_X("cwekey6"                      , OFS(cwekey[6]),                       param_fn, SIZEOF(cwekey[6]) ^ (OFS(cwekey_length[6]) - OFS(cwekey[6])) << 8),
	DEF_OPT_FUNC_X("cwekey7"                      , OFS(cwekey[7]),                       param_fn, SIZEOF(cwekey[7]) ^ (OFS(cwekey_length[7]) - OFS(cwekey[7])) << 8),
	DEF_OPT_FUNC_X("cwekey8"                      , OFS(cwekey[8]),                       param_fn, SIZEOF(cwekey[8]) ^ (OFS(cwekey_length[8]) - OFS(cwekey[8])) << 8),
	DEF_OPT_FUNC_X("cwekey9"                      , OFS(cwekey[9]),                       param_fn, SIZEOF(cwekey[9]) ^ (OFS(cwekey_length[9]) - OFS(cwekey[9])) << 8),
	DEF_OPT_FUNC_X("cwekey10"                     , OFS(cwekey[10]),                      param_fn, SIZEOF(cwekey[10]) ^ (OFS(cwekey_length[10]) - OFS(cwekey[10])) << 8),
	DEF_OPT_FUNC_X("cwekey11"                     , OFS(cwekey[11]),                      param_fn, SIZEOF(cwekey[11]) ^ (OFS(cwekey_length[11]) - OFS(cwekey[11])) << 8),
	DEF_OPT_FUNC_X("cwekey12"                     , OFS(cwekey[12]),                      param_fn, SIZEOF(cwekey[12]) ^ (OFS(cwekey_length[12]) - OFS(cwekey[12])) << 8),
	DEF_OPT_FUNC_X("cwekey13"                     , OFS(cwekey[13]),                      param_fn, SIZEOF(cwekey[13]) ^ (OFS(cwekey_length[13]) - OFS(cwekey[13])) << 8),
	DEF_OPT_FUNC_X("cwekey14"                     , OFS(cwekey[14]),                      param_fn, SIZEOF(cwekey[14]) ^ (OFS(cwekey_length[14]) - OFS(cwekey[14])) << 8),
	DEF_OPT_FUNC_X("cwekey15"                     , OFS(cwekey[15]),                      param_fn, SIZEOF(cwekey[15]) ^ (OFS(cwekey_length[15]) - OFS(cwekey[15])) << 8),
	DEF_OPT_FUNC_X("cwekey16"                     , OFS(cwekey[16]),                      param_fn, SIZEOF(cwekey[16]) ^ (OFS(cwekey_length[16]) - OFS(cwekey[16])) << 8),
	DEF_OPT_INT8("forcecwswap"                    , OFS(forcecwswap),                     0),
	DEF_OPT_INT8("evensa"                         , OFS(evensa),                          0),
	DEF_OPT_INT8("forceemmg"                      , OFS(forceemmg),                       0),
	DEF_OPT_INT8("cwpkota"                        , OFS(cwpkota),                         0),
	DEF_OPT_INT8("headermode"                     , OFS(headermode),                      1),
	DEF_OPT_INT8("cak7_mode"                      , OFS(cak7_mode),                       0),
#endif
#if defined(READER_NAGRA)
	DEF_OPT_FUNC_X("cak63nuid"                    , OFS(cak63nuid),                       param_fn, SIZEOF(cak63nuid) ^ (OFS(cak63nuid_length) - OFS(cak63nuid)) << 8),
	DEF_OPT_FUNC_X("cak63cwekey"                  , OFS(cak63cwekey),                     param_fn, SIZEOF(cak63cwekey) ^ (OFS(cak63cwekey_length) - OFS(cak63cwekey)) << 8),
#endif
#if defined(READER_VIDEOGUARD)
	DEF_OPT_INT8("ndsversion"                     , OFS(ndsversion),                      0),
	DEF_OPT_INT32("card_startdate_basemonth"      , OFS(card_startdate_basemonth),        1),
	DEF_OPT_INT32("card_startdate_baseyear"       , OFS(card_startdate_baseyear),         1997),
	DEF_OPT_INT32("card_expiredate_basemonth"     , OFS(card_expiredate_basemonth),       1),
	DEF_OPT_INT32("card_expiredate_baseyear"      , OFS(card_expiredate_baseyear),        1997),
	DEF_OPT_FUNC_X("ins7e"                        , OFS(ins7E),                           ins7E_fn, SIZEOF(ins7E)),
	DEF_OPT_FUNC_X("ins42"                        , OFS(ins42),                           ins42_fn, SIZEOF(ins42)),
	DEF_OPT_FUNC_X("ins7e11"                      , OFS(ins7E11),                         ins7E_fn, SIZEOF(ins7E11)),
	DEF_OPT_FUNC_X("ins2e06"                      , OFS(ins2e06),                         ins7E_fn, SIZEOF(ins2e06)),
	DEF_OPT_FUNC("k1_generic"                     , OFS(k1_generic),                      des_and_3des_key_fn),
	DEF_OPT_FUNC("k1_unique"                      , OFS(k1_unique),                       des_and_3des_key_fn),
	DEF_OPT_INT8("fix07"                          , OFS(fix_07),                          1),
	DEF_OPT_INT8("fix9993"                        , OFS(fix_9993),                        0),
	DEF_OPT_INT8("readtiers"                      , OFS(readtiers),                       1),
#endif
#ifdef READER_IRDETO
	DEF_OPT_INT8("force_irdeto"                   , OFS(force_irdeto),                    0),
#endif
#ifdef READER_CRYPTOWORKS
	DEF_OPT_INT8("needsglobalfirst"               , OFS(needsglobalfirst),                0),
#endif
	DEF_OPT_UINT32("ecmnotfoundlimit"             , OFS(ecmnotfoundlimit),                0),
	DEF_OPT_FUNC("ecmwhitelist"                   , 0,                                    ecmwhitelist_fn),
	DEF_OPT_FUNC("ecmheaderwhitelist"             , 0,                                    ecmheaderwhitelist_fn),
#ifdef WITH_CARDREADER
	DEF_OPT_FUNC("detect"                         , 0,                                    detect_fn),
#ifdef READER_NAGRA
	DEF_OPT_INT8("nagra_read"                     , OFS(nagra_read),                      0),
	DEF_OPT_INT8("detect_seca_nagra_tunneled_card", OFS(detect_seca_nagra_tunneled_card), 1),
#endif
	DEF_OPT_INT32("mhz"                           , OFS(mhz),                             357),
	DEF_OPT_INT32("cardmhz"                       , OFS(cardmhz),                         357),
#endif
#ifdef WITH_AZBOX
	DEF_OPT_INT32("mode"                          , OFS(azbox_mode),                      -1),
#endif
	DEF_OPT_FUNC_X("ident"                        , OFS(ftab),                            ftab_fn, FTAB_READER | FTAB_PROVID),
	DEF_OPT_FUNC_X("chid"                         , OFS(fchid),                           ftab_fn, FTAB_READER | FTAB_CHID),
	DEF_OPT_FUNC("class"                          , OFS(cltab),                           class_fn),
#ifdef READER_VIACCESS
	DEF_OPT_FUNC("aeskeys"                        , 0,                                    aeskeys_fn),
#endif
	DEF_OPT_FUNC("group"                          , OFS(grp),                             group_fn),
	DEF_OPT_FUNC("emmcache"                       , 0,                                    emmcache_fn),
	DEF_OPT_FUNC_X("blockemm-unknown"             , OFS(blockemm),                        flags_fn, EMM_UNKNOWN),
	DEF_OPT_FUNC_X("blockemm-u"                   , OFS(blockemm),                        flags_fn, EMM_UNIQUE),
	DEF_OPT_FUNC_X("blockemm-s"                   , OFS(blockemm),                        flags_fn, EMM_SHARED),
	DEF_OPT_FUNC_X("blockemm-g"                   , OFS(blockemm),                        flags_fn, EMM_GLOBAL),
	DEF_OPT_FUNC_X("saveemm-unknown"              , OFS(saveemm),                         flags_fn, EMM_UNKNOWN),
	DEF_OPT_FUNC_X("saveemm-u"                    , OFS(saveemm),                         flags_fn, EMM_UNIQUE),
	DEF_OPT_FUNC_X("saveemm-s"                    , OFS(saveemm),                         flags_fn, EMM_SHARED),
	DEF_OPT_FUNC_X("saveemm-g"                    , OFS(saveemm),                         flags_fn, EMM_GLOBAL),
	DEF_OPT_FUNC("blockemm-bylen"                 , 0,                                    blockemm_bylen_fn),
#ifdef WITH_LB
	DEF_OPT_INT32("lb_weight"                     , OFS(lb_weight),                       100),
	DEF_OPT_INT8("lb_force_fallback"              , OFS(lb_force_fallback),               0),
#endif
	DEF_OPT_FUNC("savenano"                       , OFS(s_nano),                          nano_fn),
	DEF_OPT_FUNC("blocknano"                      , OFS(b_nano),                          nano_fn),
	DEF_OPT_INT8("dropbadcws"                     , OFS(dropbadcws),                      0),
	DEF_OPT_INT8("disablecrccws"                  , OFS(disablecrccws),                   0),
#ifdef WITH_CARDREADER
	DEF_OPT_INT32("use_gpio"                      , OFS(use_gpio),                        0),
#endif
#ifdef MODULE_PANDORA
	DEF_OPT_UINT8("pand_send_ecm"                 , OFS(pand_send_ecm),                   0),
#endif
#ifdef MODULE_CCCAM
	DEF_OPT_SSTR("cccversion"                     , OFS(cc_version),                      "", SIZEOF(cc_version)),
	DEF_OPT_INT8("cccmaxhops"                     , OFS(cc_maxhops),                      DEFAULT_CC_MAXHOPS),
	DEF_OPT_INT8("cccmindown"                     , OFS(cc_mindown),                      0),
	DEF_OPT_INT8("cccwantemu"                     , OFS(cc_want_emu),                     0),
	DEF_OPT_INT8("ccckeepalive"                   , OFS(cc_keepalive),                    DEFAULT_CC_KEEPALIVE),
	DEF_OPT_INT8("cccreshare"                     , OFS(cc_reshare),                      DEFAULT_CC_RESHARE),
	DEF_OPT_INT32("cccreconnect"                  , OFS(cc_reconnect),                    DEFAULT_CC_RECONNECT),
	DEF_OPT_INT8("ccchop"                         , OFS(cc_hop),                          0),
#endif
#ifdef MODULE_GHTTP
	DEF_OPT_UINT8("use_ssl"                       , OFS(ghttp_use_ssl),                   0),
#endif
#if defined(READER_DRE) || defined(READER_DRECAS)
	DEF_OPT_HEX("force_ua"                        , OFS(force_ua),                        4),
	DEF_OPT_STR("exec_cmd_file"                   , OFS(userscript),                      NULL),
#endif
#ifdef READER_DRECAS
	DEF_OPT_STR("stmkeys"                         , OFS(stmkeys),                         NULL),
#endif
#ifdef WITH_EMU
	DEF_OPT_FUNC_X("emu_auproviders"              , OFS(emu_auproviders),                ftab_fn, FTAB_READER | FTAB_EMUAU),
	DEF_OPT_INT8("emu_datecodedenabled"           , OFS(emu_datecodedenabled),           0),
#endif
	DEF_OPT_INT8("resetalways"                    , OFS(resetalways),                     0),
#ifdef WITH_CARDREADER
	DEF_OPT_INT8("deprecated"                     , OFS(deprecated),                      0),
#endif
	DEF_OPT_INT8("audisabled"                     , OFS(audisabled),                      0),
	DEF_OPT_FUNC("auprovid"                       , 0,                                    auprovid_fn),
	DEF_OPT_FUNC("ratelimitecm"                   , 0,                                    ratelimitecm_fn),
	DEF_OPT_FUNC("ecmunique"                      , 0,                                    ecmunique_fn),
	DEF_OPT_FUNC("ratelimittime"                  , 0,                                    ratelimittime_fn),
	DEF_OPT_FUNC("srvidholdtime"                  , 0,                                    srvidholdtime_fn),
	DEF_OPT_FUNC("cooldown"                       , 0,                                    cooldown_fn),
	DEF_OPT_INT32("maxparallel"                   , OFS(maxparallel),                     0),
	DEF_OPT_FUNC("parallelfactor"                , 0,                                    parallelfactor_fn),
	DEF_OPT_INT32("paralleltimeout"               , OFS(paralleltimeout),                 1000),
	DEF_OPT_FUNC("cooldowndelay"                  , 0,                                    cooldowndelay_fn),
	DEF_OPT_FUNC("cooldowntime"                   , 0,                                    cooldowntime_fn),
#ifdef READER_VIACCESS
	DEF_OPT_UINT8("read_old_classes"              , OFS(read_old_classes),                1),
#endif
	DEF_LAST_OPT
};

static inline bool in_list(const char *token, const char *list[])
{
	int i;
	for(i = 0; list[i]; i++)
	{
		if(streq(token, list[i]))
			{ return true; }
	}
	return false;
}

static bool reader_check_setting(const struct config_list *UNUSED(clist), void *config_data, const char *setting)
{
	struct s_reader *reader = config_data;
	// These are written only when the reader is physical reader
	static const char *hw_only_settings[] =
	{
		"readnano", "resetcycle", "smargopatch", "autospeed", "sc8in1_dtrrts_patch", "boxid","fix07",
		"fix9993", "rsakey", "deskey", "card_startdate_basemonth", "card_startdate_baseyear", "card_expiredate_basemonth", "card_expiredate_baseyear", "ins7e", "ins42", "ins7e11", "ins2e06", "k1_generic", "k1_unique", "force_irdeto", "boxkey",
		"atr", "detect", "nagra_read", "mhz", "cardmhz", "readtiers", "read_old_classes", "use_gpio", "needsglobalfirst",
#ifdef READER_NAGRA_MERLIN
		"mod1", "idird", "cmd0eprov", "mod2", "key3588", "key3460", "key3310", "data50", "mod50", "nuid", "forcepair", "otpcsc", "otacsc", "cwpkcaid", "headermode", "cwekey0", "cwekey1", "cwekey2", "cwekey3", "cwekey4", "cwekey5", "cwekey6", "cwekey7", "cwekey8", "cwekey9", "cwekey10", "cwekey11", "cwekey12", "cwekey13", "cwekey14", "cwekey15", "cwekey16",
#endif
#if defined(READER_NAGRA)
		"cak63nuid", "cak63cwekey",
#endif
#if defined(READER_DRE) || defined(READER_DRECAS)
		"exec_cmd_file",
#endif
#ifdef READER_CONAX
		"cwpkkey",
#endif
#if defined(READER_TONGFANG)
		"tongfang3_deskey", "tongfang3_calibsn", "stbid", "tongfang_boxid",
#endif
#ifdef WITH_AZBOX
		"mode",
#endif
		"resetalways", "deprecated", "ndsversion",
		0
	};
	// These are written only when the reader is network reader
	static const char *network_only_settings[] =
	{
		"user", "inactivitytimeout", "reconnecttimeout",
		0
	};
	if(is_network_reader(reader))
	{
		if(in_list(setting, hw_only_settings))
			{ return false; }
	}
	else
	{
		if(in_list(setting, network_only_settings))
			{ return false; }
	}

	// These are not written in the config file
	static const char *deprecated_settings[] =
	{
		"cooldowndelay", "cooldowntime",
		0
	};
	if(in_list(setting, deprecated_settings))
		{ return false; }

	// Special settings for NEWCAMD
	static const char *newcamd_settings[] =
	{
		"disableserverfilter", "connectoninit",
		0
	};
	if(reader->typ != R_NEWCAMD && in_list(setting, newcamd_settings))
		{ return false; }
#ifdef MODULE_CCCAM
	// These are written only when the reader is CCCAM
	static const char *cccam_settings[] =
	{
		"cccversion", "cccmaxhops", "cccmindown", "cccwantemu", "ccckeepalive",
		"cccreconnect",
		0
	};
	// Special settings for CCCAM
	if(reader->typ != R_CCCAM)
	{
		if(in_list(setting, cccam_settings))
			{ return false; }
	}
	else if(streq(setting, "ccchop"))
	{
		return false;
	}
#endif

#ifdef MODULE_PANDORA
	// Special settings for PANDORA
	if(reader->typ != R_PANDORA && streq(setting, "pand_send_ecm"))
		{ return false; }
#endif

#ifdef MODULE_GBOX
	// These are written only when the reader is GBOX
	static const char *gbox_settings[] =
	{
		"gbox_max_distance", "gbox_max_ecm_send", "gbox_reshare", "cccam_reshare", "force_remm","ccc_gbx_reshare_ident","send_offline_cmd",
		0
	};
	if(reader->typ != R_GBOX)
	{
		if(in_list(setting, gbox_settings))
			{ return false; }
	}
#endif

	return true; // Write the setting
}

void chk_reader(char *token, char *value, struct s_reader *rdr)
{
	if(config_list_parse(reader_opts, token, value, rdr))
		{ return; }
	else if(token[0] != '#')
		{ fprintf(stderr, "Warning: keyword '%s' in reader section not recognized\n", token); }
}

void reader_set_defaults(struct s_reader *rdr)
{
	config_list_set_defaults(reader_opts, rdr);
}

int32_t init_readerdb(void)
{
	configured_readers = ll_create("configured_readers");

	FILE *fp = open_config_file(cs_srvr);
	if(!fp)
		{ return 1; }

	int32_t tag = 0;
	char *value, *token;

	if(!cs_malloc(&token, MAXLINESIZE))
		{ return 1; }

	struct s_reader *rdr;
	if(!cs_malloc(&rdr, sizeof(struct s_reader)))
	{
		NULLFREE(token);
		return 1;
	}

	ll_append(configured_readers, rdr);
	while(fgets(token, MAXLINESIZE, fp))
	{
		int32_t l;
		if((l = cs_strlen(trim(token))) < 3)
			{ continue; }
		if((token[0] == '[') && (token[l - 1] == ']'))
		{
			token[l - 1] = 0;
			tag = (!strcmp("reader", strtolower(token + 1)));
			if(rdr->label[0] && rdr->typ)
			{
				struct s_reader *newreader;
				if(cs_malloc(&newreader, sizeof(struct s_reader)))
				{
					ll_append(configured_readers, newreader);
					rdr = newreader;
				}
			}
			reader_set_defaults(rdr);
			continue;
		}

		if(!tag)
			{ continue; }
		if(!(value = strchr(token, '=')))
			{ continue; }
		*value++ = '\0';
		chk_reader(trim(strtolower(token)), trim(value), rdr);
	}
	NULLFREE(token);
	LL_ITER itr = ll_iter_create(configured_readers);
	while((rdr = ll_iter_next(&itr))) // build active readers list
	{
		reader_fixups_fn(rdr);
		module_reader_set(rdr);
	}
	fclose(fp);
	return (0);
}

void free_reader(struct s_reader *rdr)
{
	NULLFREE(rdr->emmfile);
	if(rdr->parallel_slots || rdr->parallel_slots_prov)
		{ cs_lock_destroy(__func__, &rdr->parallel_lock); }
	NULLFREE(rdr->parallel_slots);
	NULLFREE(rdr->parallel_slots_prov);
	ll_destroy_data(&rdr->blocked_services);

	ecm_whitelist_clear(&rdr->ecm_whitelist);
	ecm_hdr_whitelist_clear(&rdr->ecm_hdr_whitelist);

	ftab_clear(&rdr->fallback_percaid);
	ftab_clear(&rdr->localcards);
	ftab_clear(&rdr->fchid);
	ftab_clear(&rdr->ftab);
	ftab_clear(&rdr->disablecrccws_only_for);
#ifdef MODULE_GBOX
	ftab_clear(&rdr->ccc_gbx_reshare_ident);
#endif

	NULLFREE(rdr->cltab.aclass);
	NULLFREE(rdr->cltab.bclass);

	caidtab_clear(&rdr->ctab);
#ifdef CS_CACHEEX
	cecspvaluetab_clear(&rdr->cacheex.filter_caidtab);
#ifdef CS_CACHEEX_AIO
	caidtab_clear(&rdr->cacheex.localgenerated_only_caidtab);
	caidtab_clear(&rdr->cacheex.localgenerated_only_in_caidtab);
	ftab_clear(&rdr->cacheex.lg_only_tab);
	ftab_clear(&rdr->cacheex.lg_only_in_tab);
	caidvaluetab_clear(&rdr->cacheex.cacheex_nopushafter_tab);
#endif
#endif
	lb_destroy_stats(rdr);

	cs_clear_entitlement(rdr);
	ll_destroy(&rdr->ll_entitlements);

	if(rdr->csystem && rdr->csystem->card_done)
		rdr->csystem->card_done(rdr);
	NULLFREE(rdr->csystem_data);

	ll_destroy_data(&rdr->blockemmbylen);

	ll_destroy_data(&rdr->emmstat);
#ifdef READER_VIACCESS
	aes_clear_entries(&rdr->aes_list);
#endif
	config_list_gc_values(reader_opts, rdr);
	add_garbage(rdr);
}

int32_t free_readerdb(void)
{
	int count = 0;
	struct s_reader *rdr;
	LL_ITER itr = ll_iter_create(configured_readers);
	while((rdr = ll_iter_next(&itr)))
	{
		free_reader(rdr);
		count++;
	}
	cs_log("readerdb %d readers freed", count);
	ll_destroy(&configured_readers);
	return count;
}

int32_t write_server(void)
{
	FILE *f = create_config_file(cs_srvr);
	if(!f)
		{ return 1; }
	struct s_reader *rdr;
	LL_ITER itr = ll_iter_create(configured_readers);
	while((rdr = ll_iter_next(&itr)))
	{
		if(rdr->label[0])
		{
			fprintf(f, "[reader]\n");
			config_list_apply_fixups(reader_opts, rdr);
			config_list_save_ex(f, reader_opts, rdr, cfg.http_full_cfg, reader_check_setting);
			fprintf(f, "\n");
		}
	}
	return flush_config_file(f, cs_srvr);
}

void reload_readerdb(void)
{
	struct s_reader *rdr;
	LL_ITER itr = ll_iter_create(configured_readers);
	while((rdr = ll_iter_next(&itr)))
	{
		// disable the current reader
		rdr->enable = 0;
		restart_cardreader(rdr,1);
	}
	free_readerdb(); // release the old readerdb
	init_readerdb(); // reload the new readerdb
	init_cardreader(); // start the readers
}
