#define MODULE_LOG_PREFIX "cwccheck"

#include "globals.h"
#ifdef CW_CYCLE_CHECK

#include "module-cw-cycle-check.h"
#include "oscam-chk.h"
#include "oscam-client.h"
#include "oscam-ecm.h"
#include "oscam-lock.h"
#include "oscam-string.h"
#include "oscam-cache.h"

struct s_cwc_md5
{
	uint8_t                 md5[CS_ECMSTORESIZE];
	uint32_t                csp_hash;
	uint8_t                 cw[16];
};

struct s_cw_cycle_check
{
	uint8_t                 cw[16];
	time_t                  time;
	time_t                  locktime; // lock in learning
	uint16_t                caid;
	uint16_t                sid;
	uint16_t                chid;
	uint32_t                provid;
	int16_t                 ecmlen;
	int8_t                  stage;
	int32_t                 cycletime;
	int32_t                 dyncycletime;
	int8_t                  nextcyclecw;
	struct s_cwc_md5        ecm_md5[15]; // max 15 old ecm md5 /csp-hashs
	int8_t                  cwc_hist_entry;
	uint8_t                 old;
	int8_t                  stage4_repeat;
	struct s_cw_cycle_check *prev;
	struct s_cw_cycle_check *next;
};

extern CS_MUTEX_LOCK cwcycle_lock;

static struct s_cw_cycle_check *cw_cc_list;
static int32_t cw_cc_list_size;
static time_t last_cwcyclecleaning;

/*
 * Check for CW CYCLE
 */

static uint8_t chk_is_pos_fallback(ECM_REQUEST *er, char *reader)
{
	struct s_ecm_answer *ea;
	struct s_reader *fbrdr;
	char fb_reader[64];

	for(ea = er->matching_rdr; ea; ea = ea->next)
	{
		if(ea->reader)
		{
			fbrdr = ea->reader;
			snprintf(fb_reader, sizeof(fb_reader), "%s", ea->reader->label);
			if(!strcmp(reader, fb_reader) && chk_is_fixed_fallback(fbrdr, er))
			{
				cs_log("cyclecheck [check Fixed FB] %s is set as fixed fallback", reader);
				return 1;
			}
		}
	}
	return 0;
}

static inline uint8_t checkECMD5CW(uint8_t *ecmd5_cw)
{
	int8_t i;
	for(i = 0; i < CS_ECMSTORESIZE; i++)
		if(ecmd5_cw[i]) { return 1; }
	return 0;
}

/*
 * countCWpart is to prevent like this
 * D41A1A08B01DAD7A 0F1D0A36AF9777BD found -> ok
 * E9151917B01DAD7A 0F1D0A36AF9777BD found last -> worng (freeze), but for cwc is ok
 * 7730F59C6653A55E D3822A7F133D3C8C cwc bad -> but cw is right, cwc out of step
 */
static uint8_t countCWpart(ECM_REQUEST *er, struct s_cw_cycle_check *cwc)
{
	uint8_t eo = cwc->nextcyclecw ? 0 : 8;
	int8_t i, ret = 0;

#ifdef WITH_DEBUG
	if(cs_dblevel & D_CWC)
	{
		char cwc_cw[9 * 3];
		char er_cw[9 * 3];
		cs_hexdump(0, cwc->cw + eo, 8, cwc_cw, sizeof(cwc_cw));
		cs_hexdump(0, er->cw + eo, 8, er_cw, sizeof(er_cw));
		cs_log_dbg(D_CWC, "cyclecheck [countCWpart] er-cw %s", er_cw);
		cs_log_dbg(D_CWC, "cyclecheck [countCWpart] cw-cw %s", cwc_cw);
	}
#endif

	for(i = 0; i < 8; i++)
	{
		if(cwc->cw[i + eo] == er->cw[i + eo])
		{
			ret++;
		}
	}


	if(ret > cfg.cwcycle_sensitive)
	{
		cs_log("cyclecheck [countCWpart] new cw is to like old one (unused part), sensitive %d, same bytes %d", cfg.cwcycle_sensitive, ret);
	}
	return ret;
}

static uint8_t checkvalidCW(ECM_REQUEST *er)
{
	uint8_t ret = 1;

	// Skip check for BISS1 - cw could be indeed zero
	// Skip check for BISS2 - we use the extended cw, so the "simple" cw is always zero
	if(chk_is_null_CW(er->cw) && !caid_is_biss(er->caid))
	{ er->rc = E_NOTFOUND; }

	if(er->rc == E_NOTFOUND)
	{ return 0; } // wrong leave the check

	if(checkCWpart(er->cw, 0) && checkCWpart(er->cw, 1))
	{ return 1; } // cw1 and cw2 is filled -> we can check for cwc

	if((!checkCWpart(er->cw, 0) || !checkCWpart(er->cw, 1)) && caid_is_videoguard(er->caid))
	{
		cs_log("CAID: %04X uses obviously half cycle cw's : NO need to check it with CWC! Remove CAID: %04X from CWC Config!", er->caid, er->caid);
		ret = 0; // cw1 or cw2 is null
	}

	return ret;
}

void cleanupcwcycle(void)
{
	time_t now = time(NULL);
	if(last_cwcyclecleaning + 120 > now) // only clean once every 2min
		{ return; }

	last_cwcyclecleaning = now;
	int32_t count = 0, kct = cfg.keepcycletime * 60 + 30; // if keepcycletime is set, wait more before deleting
	struct s_cw_cycle_check *prv = NULL, *currentnode = NULL, *temp = NULL;

	bool bcleanup = false;

	// write lock
	cs_writelock(__func__, &cwcycle_lock);
	for(currentnode = cw_cc_list, prv = NULL; currentnode; prv = currentnode, currentnode = currentnode->next, count++)   // First Remove old Entries
	{
		if((now - currentnode->time) <= kct) // delete Entry which old to hold list small
		{
			continue;
		}
		cs_log_dbg(D_CWC, "cyclecheck [Cleanup] diff: %" PRId64 " kct: %i", (int64_t)(now - currentnode->time), kct);
		if(prv != NULL)
		{
			prv->next  = NULL;
		}
		else
		{
			cw_cc_list = NULL;
		}
		bcleanup = true;
		break; // we need only once, all follow to old
	}
	cs_writeunlock(__func__, &cwcycle_lock);
	while(currentnode != NULL)
	{
		temp = currentnode->next;
		if(!currentnode->old)
			{ cw_cc_list_size--; }
		NULLFREE(currentnode);
		currentnode = temp;
	}
	if(bcleanup)
		{ cs_log_dbg(D_CWC, "cyclecheck [Cleanup] list new size: %d (realsize: %d)", cw_cc_list_size, count); }
}

static int32_t checkcwcycle_int(ECM_REQUEST *er, char *er_ecmf , char *user, uint8_t *cw , char *reader, uint8_t cycletime_fr, uint8_t next_cw_cycle_fr)
{

	int8_t i, ret = 6; // ret = 6 no checked
	int8_t cycleok = -1;
	time_t now = er->tps.time; //time(NULL);
	uint8_t need_new_entry = 1, upd_entry = 1;
	char cwstr[17 * 3]; // cw to check

	char cwc_ecmf[ECM_FMT_LEN];
	char cwc_cw[17 * 3];
#ifdef WITH_DEBUG
	char cwc_md5[17 * 3];
	char cwc_csp[5 * 3];
#endif
	int8_t n = 1, m = 1, k;
	int32_t mcl = cfg.maxcyclelist;
	struct s_cw_cycle_check *currentnode = NULL, *cwc = NULL;

	/*for(list = cw_cc_list; list; list = list->next) { // List all Entries in Log for DEBUG
	    cs_log_dbg(D_CWC, "cyclecheck: [LIST] %04X@%06X:%04X OLD: %i Time: %ld DifftoNow: %ld Stage: %i cw: %s", list->caid, list->provid, list->sid, list->old, list->time, now - list->time, list->stage, cs_hexdump(0, list->cw, 16, cwstr, sizeof(cwstr)));

	}*/

	if(!checkvalidCW(er))
	{ return 3; } //cwc ign

	//read lock
	cs_readlock(__func__, &cwcycle_lock);
	bool readlocked = true;
	for(currentnode = cw_cc_list; currentnode; currentnode = currentnode->next)
	{
		if(currentnode->caid != er->caid || currentnode->provid != er->prid || currentnode->sid != er->srvid || currentnode->chid != er->chid)
		{
			continue;
		}
		if(er->ecmlen != 0 && currentnode->ecmlen != 0)
		{
			if(currentnode->ecmlen != er->ecmlen)
			{
				cs_log_dbg(D_CWC, "cyclecheck [other ECM LEN] -> don't check");
				continue;
			}

		}
		need_new_entry = 0; // we got a entry for caid/prov/sid so we dont need new one

#ifdef WITH_DEBUG
		if(cs_dblevel & D_CWC)
		{
			cs_hexdump(0, cw, 16, cwstr, sizeof(cwstr)); //checked cw for log
		}
#endif
		if(cs_malloc(&cwc, sizeof(struct s_cw_cycle_check)))
		{
			memcpy(cwc, currentnode, sizeof(struct s_cw_cycle_check)); //copy current to new

			if(!currentnode->old)
			{
				currentnode->old = 1; //need later to counting
				cw_cc_list_size--;
			}
			//now we have all data and can leave read lock
			cs_readunlock(__func__, &cwcycle_lock);
			readlocked = false;
#ifdef WITH_DEBUG
			if(cs_dblevel & D_CWC)
			{
				cs_hexdump(0, cwc->ecm_md5[cwc->cwc_hist_entry].md5, 16, cwc_md5, sizeof(cwc_md5));
				cs_hexdump(0, (void *)&cwc->ecm_md5[cwc->cwc_hist_entry].csp_hash, 4, cwc_csp, sizeof(cwc_csp));
				cs_hexdump(0, cwc->cw, 16, cwc_cw, sizeof(cwc_cw));
				ecmfmt(cwc_ecmf, ECM_FMT_LEN, cwc->caid, 0, cwc->provid, cwc->chid, 0, cwc->sid, cwc->ecmlen, cwc_md5, cwc_csp, cwc_cw, 0, 0, NULL, NULL);
			}
#endif

// Cycletime over Cacheex
			if (cfg.cwcycle_usecwcfromce)
			{
				if(cycletime_fr > 0 && next_cw_cycle_fr < 2)
				{
					cs_log_dbg(D_CWC, "cyclecheck [Use Info in Request] Client: %s cycletime: %isek - nextcwcycle: CW%i for %04X@%06X:%04X", user, cycletime_fr, next_cw_cycle_fr, er->caid, er->prid, er->srvid);
					cwc->stage = 3;
					cwc->cycletime = cycletime_fr;
					cwc->nextcyclecw = next_cw_cycle_fr;
					ret = 8;
					if(memcmp(cwc->cw, cw, 16) == 0) //check if the store cw the same like the current
					{
						cs_log_dbg(D_CWC, "cyclecheck [Dump Stored CW] Client: %s EA: %s CW: %s Time: %" PRId64, user, cwc_ecmf, cwc_cw, (int64_t)cwc->time);
						cs_log_dbg(D_CWC, "cyclecheck [Dump CheckedCW] Client: %s EA: %s CW: %s Time: %" PRId64 " Timediff: %" PRId64, user, er_ecmf, cwstr, (int64_t)now, (int64_t)now - cwc->time);
						if(now - cwc->time >= cwc->cycletime - cwc->dyncycletime)
						{
							cs_log_dbg(D_CWC, "cyclecheck [Same CW but much too late] Client: %s EA: %s CW: %s Time: %" PRId64 " Timediff: %" PRId64, user, er_ecmf, cwstr, (int64_t)now, (int64_t)now - cwc->time);
							ret = cfg.cwcycle_dropold ? 2 : 4;
						}
						else
						{
						ret = 4; // Return 4 same CW
						}
						upd_entry = 0;
					}
					break;
				}
			}
//
			if(cwc->stage == 3 && cwc->nextcyclecw < 2 && now - cwc->time < cwc->cycletime * 2 - cwc->dyncycletime - 1)    // Check for Cycle no need to check Entries others like stage 3
			{
				/*for (k=0; k<15; k++) { // debug md5
				            cs_log_dbg(D_CWC, "cyclecheck [checksumlist[%i]]: ecm_md5: %s csp-hash: %d Entry: %i", k, cs_hexdump(0, cwc->ecm_md5[k].md5, 16, ecm_md5, sizeof(ecm_md5)), cwc->ecm_md5[k].csp_hash, cwc->cwc_hist_entry);
				} */

					// first we check if the store cw the same like the current
					if(memcmp(cwc->cw, cw, 16) == 0)
					{
						cs_log_dbg(D_CWC, "cyclecheck [Dump Stored CW] Client: %s EA: %s CW: %s Time: %" PRId64, user, cwc_ecmf, cwc_cw, (int64_t)cwc->time);
						cs_log_dbg(D_CWC, "cyclecheck [Dump CheckedCW] Client: %s EA: %s CW: %s Time: %" PRId64 " Timediff: %" PRId64, user, er_ecmf, cwstr, (int64_t)now, (int64_t)now - cwc->time);
						if(now - cwc->time >= cwc->cycletime - cwc->dyncycletime)
						{
							cs_log_dbg(D_CWC, "cyclecheck [Same CW but much too late] Client: %s EA: %s CW: %s Time: %" PRId64 " Timediff: %" PRId64, user, er_ecmf, cwstr, (int64_t)now, (int64_t)now - cwc->time);
							ret = cfg.cwcycle_dropold ? 2 : 4;
						}
						else
						{
						ret = 4;  // Return 4 same CW
						}
						upd_entry = 0;
						break;
					}

					if(cwc->nextcyclecw == 0)    //CW0 must Cycle
					{
						for(i = 0; i < 8; i++)
						{
							if(cwc->cw[i] == cw[i])
							{
								cycleok = 0; //means CW0 Cycle OK
							}
							else
							{
								cycleok = -1;
								break;
							}
						}
					}
					else if(cwc->nextcyclecw == 1)     //CW1 must Cycle
					{
						for(i = 0; i < 8; i++)
						{
							if(cwc->cw[i + 8] == cw[i + 8])
							{
								cycleok = 1; //means CW1 Cycle OK
							}
							else
							{
								cycleok = -1;
								break;
							}
						}
					}

					if(cycleok >= 0 && cfg.cwcycle_sensitive && countCWpart(er, cwc) >= cfg.cwcycle_sensitive)  //2,3,4, 0 = off
					{
						cycleok = -2;
					}

				if(cycleok >= 0)
				{
					ret = 0;  // return Code 0 Cycle OK
					if(cycleok == 0)
					{
						cwc->nextcyclecw = 1;
						er->cwc_next_cw_cycle = 1;
						if(cwc->cycletime < 128 && (!(cwc->caid == 0x0100 && cwc->provid == 0x00006A))) // make sure cycletime is lower dez 128 because share over cacheex buf[18] bit 8 is used for cwc_next_cw_cycle
							{ er->cwc_cycletime = cwc->cycletime; }
						cs_log_dbg(D_CWC, "cyclecheck [Valid CW 0 Cycle] Client: %s EA: %s Timediff: %" PRId64 " Stage: %i Cycletime: %i dyncycletime: %i nextCycleCW = CW%i from Reader: %s", user, er_ecmf, (int64_t)now - cwc->time, cwc->stage, cwc->cycletime, cwc->dyncycletime, cwc->nextcyclecw, reader);
					}
					else if(cycleok == 1)
					{
						cwc->nextcyclecw = 0;
						er->cwc_next_cw_cycle = 0;
						if(cwc->cycletime < 128 && (!(cwc->caid == 0x0100 && cwc->provid == 0x00006A))) // make sure cycletime is lower dez 128 because share over cacheex buf[18] bit 8 is used for cwc_next_cw_cycle
							{ er->cwc_cycletime = cwc->cycletime; }
						cs_log_dbg(D_CWC, "cyclecheck [Valid CW 1 Cycle] Client: %s EA: %s Timediff: %" PRId64 " Stage: %i Cycletime: %i dyncycletime: %i nextCycleCW = CW%i from Reader: %s", user, er_ecmf, (int64_t)now - cwc->time, cwc->stage, cwc->cycletime, cwc->dyncycletime, cwc->nextcyclecw, reader);
					}
					cs_log_dbg(D_CWC, "cyclecheck [Dump Stored CW] Client: %s EA: %s CW: %s Time: %" PRId64, user, cwc_ecmf, cwc_cw, (int64_t)cwc->time);
					cs_log_dbg(D_CWC, "cyclecheck [Dump CheckedCW] Client: %s EA: %s CW: %s Time: %" PRId64 " Timediff: %" PRId64, user, er_ecmf, cwstr, (int64_t)now, (int64_t)now - cwc->time);
				}
				else
				{

					for(k = 0; k < 15; k++)  // check for old ECMs
					{
#ifdef CS_CACHEEX
						if((checkECMD5CW(er->ecmd5) && checkECMD5CW(cwc->ecm_md5[k].md5) && !(memcmp(er->ecmd5, cwc->ecm_md5[k].md5, sizeof(er->ecmd5)))) || (er->csp_hash && cwc->ecm_md5[k].csp_hash && er->csp_hash == cwc->ecm_md5[k].csp_hash))
#else
						if((memcmp(er->ecmd5, cwc->ecm_md5[k].md5, sizeof(er->ecmd5))) == 0)
#endif
						{
							cs_log_dbg(D_CWC, "cyclecheck [OLD] [CheckedECM] Client: %s EA: %s", user, er_ecmf);
#ifdef WITH_DEBUG
							if(cs_dblevel & D_CWC)
							{
								cs_hexdump(0, cwc->ecm_md5[k].md5, 16, cwc_md5, sizeof(cwc_md5));
								cs_hexdump(0, (void *)&cwc->ecm_md5[k].csp_hash, 4, cwc_csp, sizeof(cwc_csp));
								cs_log_dbg(D_CWC, "cyclecheck [OLD] [Stored ECM] Client: %s EA: %s.%s", user, cwc_md5, cwc_csp);
							}
#endif
							if(!cfg.cwcycle_dropold && !memcmp(cwc->ecm_md5[k].cw, cw, 16))
								{ ret = 4; }
							else
								{ ret = 2; } // old ER
							upd_entry = 0;
							break;
						}
					}
					if(!upd_entry) { break; }
					if(cycleok == -2)
						{ cs_log_dbg(D_CWC, "cyclecheck [ATTENTION!! NON Valid CW] Client: %s EA: %s Timediff: %" PRId64 " Stage: %i Cycletime: %i dyncycletime: %i nextCycleCW = CW%i from Reader: %s", user, er_ecmf, (int64_t)now - cwc->time, cwc->stage, cwc->cycletime, cwc->dyncycletime, cwc->nextcyclecw, reader); }
					else
						{ cs_log_dbg(D_CWC, "cyclecheck [ATTENTION!! NON Valid CW Cycle] NO CW Cycle detected! Client: %s EA: %s Timediff: %" PRId64 " Stage: %i Cycletime: %i dyncycletime: %i nextCycleCW = CW%i from Reader: %s", user, er_ecmf, (int64_t)now - cwc->time, cwc->stage, cwc->cycletime, cwc->dyncycletime, cwc->nextcyclecw, reader); }
					cs_log_dbg(D_CWC, "cyclecheck [Dump Stored CW] Client: %s EA: %s CW: %s Time: %" PRId64, user, cwc_ecmf, cwc_cw, (int64_t)cwc->time);
					cs_log_dbg(D_CWC, "cyclecheck [Dump CheckedCW] Client: %s EA: %s CW: %s Time: %" PRId64 " Timediff: %" PRId64, user, er_ecmf, cwstr, (int64_t)now, (int64_t)now - cwc->time);
					ret = 1; // bad cycle
					upd_entry = 0;
					if(cfg.cwcycle_allowbadfromffb)
					{
						if(chk_is_pos_fallback(er, reader))
								{
									ret = 5;
									cwc->stage = 4;
									upd_entry = 1;
									cwc->nextcyclecw = 2;
									break;
								}
							}
					break;
				}
			}
			else
			{
				if(cwc->stage == 3)
				{
					if(cfg.keepcycletime > 0 && now - cwc->time < cfg.keepcycletime * 60)    // we are in keepcycletime window
					{
						cwc->stage++;   // go to stage 4
						cs_log_dbg(D_CWC, "cyclecheck [Set Stage 4] for Entry: %s Cycletime: %i -> Entry too old but in keepcycletime window - no cycletime learning - only check which CW must cycle", cwc_ecmf, cwc->cycletime);
					}
					else
					{
						cwc->stage--; // go one stage back, we are not in keepcycletime window
						cs_log_dbg(D_CWC, "cyclecheck [Back to Stage 2] for Entry: %s Cycletime: %i -> new cycletime learning", cwc_ecmf, cwc->cycletime);
					}
					memset(cwc->cw, 0, sizeof(cwc->cw)); //fake cw for stage 2/4
					ret = 3;
					cwc->nextcyclecw = 2;
				}
			}
			if(upd_entry)    //  learning stages
			{
				if(now > cwc->locktime)
				{
					int16_t diff = now - cwc->time - cwc->cycletime;
					if(cwc->stage <= 0)    // stage 0 is passed; we update the cw's and time and store cycletime
					{
						// if(cwc->cycletime == now - cwc->time)    // if we got a stable cycletime we go to stage 1
						if(diff > -2 && diff < 2)    // if we got a stable cycletime we go to stage 1
						{
							cwc->cycletime = now - cwc->time;
							cs_log_dbg(D_CWC, "cyclecheck [Set Stage 1] %s Cycletime: %i Lockdiff: %" PRId64, cwc_ecmf, cwc->cycletime, (int64_t)now - cwc->locktime);
							cwc->stage++; // increase stage
						}
						else
						{
							cs_log_dbg(D_CWC, "cyclecheck [Stay on Stage 0] %s Cycletime: %i -> no constant CW-Change-Time", cwc_ecmf, cwc->cycletime);
						}

					}
					else if(cwc->stage == 1)     // stage 1 is passed; we update the cw's and time and store cycletime
					{
						// if(cwc->cycletime == now - cwc->time)    // if we got a stable cycletime we go to stage 2
						if(diff > -2 && diff < 2)    // if we got a stable cycletime we go to stage 2
						{
							cwc->cycletime = now - cwc->time;
							cs_log_dbg(D_CWC, "cyclecheck [Set Stage 2] %s Cycletime: %i Lockdiff: %" PRId64, cwc_ecmf, cwc->cycletime, (int64_t)now - cwc->locktime);
							cwc->stage++; // increase stage
						}
						else
						{
							cs_log_dbg(D_CWC, "cyclecheck [Back to Stage 0] for Entry %s Cycletime: %i -> no constant CW-Change-Time", cwc_ecmf, cwc->cycletime);
							cwc->stage--;
						}
					}
					else if(cwc->stage == 2)     // stage 2 is passed; we update the cw's and compare cycletime
					{
						// if(cwc->cycletime == now - cwc->time && cwc->cycletime > 0)    // if we got a stable cycletime we go to stage 3
						if(diff > -2 && diff < 2 && cwc->cycletime > 0)    // if we got a stable cycletime we go to stage 3
						{
							cwc->cycletime = now - cwc->time;
							n = memcmp(cwc->cw, cw, 8);
							m = memcmp(cwc->cw + 8, cw + 8, 8);
							if(n == 0)
							{
								cwc->nextcyclecw = 1;
							}
							if(m == 0)
							{
								cwc->nextcyclecw = 0;
							}
							if(n == m || !checkECMD5CW(cw)) { cwc->nextcyclecw = 2; }  //be sure only one cw part cycle and is valid
							if(cwc->nextcyclecw < 2)
							{
								cs_log_dbg(D_CWC, "cyclecheck [Set Stage 3] %s Cycletime: %i Lockdiff: %" PRId64 " nextCycleCW = CW%i", cwc_ecmf, cwc->cycletime, (int64_t)now - cwc->locktime, cwc->nextcyclecw);
								cs_log_dbg(D_CWC, "cyclecheck [Set Cycletime %i] for Entry: %s -> now we can check CW's", cwc->cycletime, cwc_ecmf);
								cwc->stage = 3; // increase stage
							}
							else
							{
								cs_log_dbg(D_CWC, "cyclecheck [Back to Stage 1] for Entry %s Cycletime: %i -> no CW-Cycle in Learning Stage", cwc_ecmf, cwc->cycletime);  // if a server asked only every twice ECM we got a stable cycletime*2 ->but thats wrong
								cwc->stage = 1;
							}

						}
						else
						{

							cs_log_dbg(D_CWC, "cyclecheck [Back to Stage 1] for Entry %s Cycletime: %i -> no constant CW-Change-Time", cwc_ecmf, cwc->cycletime);
							cwc->stage = 1;
						}
					}
					else if(cwc->stage == 4)	// we got a early learned cycletime.. use this cycletime and check only which cw cycle
					{
						n = memcmp(cwc->cw, cw, 8);
						m = memcmp(cwc->cw + 8, cw + 8, 8);
						if(n == 0)
						{
							cwc->nextcyclecw = 1;
						}
						if(m == 0)
						{
							cwc->nextcyclecw = 0;
						}
						if(n == m || !checkECMD5CW(cw)) { cwc->nextcyclecw = 2; }  //be sure only one cw part cycle and is valid
						if(cwc->nextcyclecw < 2)
						{
							cs_log_dbg(D_CWC, "cyclecheck [Back to Stage 3] %s Cycletime: %i Lockdiff: %" PRId64 " nextCycleCW = CW%i", cwc_ecmf, cwc->cycletime, (int64_t)now - cwc->locktime, cwc->nextcyclecw);
							cs_log_dbg(D_CWC, "cyclecheck [Set old Cycletime %i] for Entry: %s -> now we can check CW's", cwc->cycletime, cwc_ecmf);
							cwc->stage = 3; // go back to stage 3
						}
						else
						{
							cs_log_dbg(D_CWC, "cyclecheck [Stay on Stage %d] for Entry %s Cycletime: %i no cycle detect!", cwc->stage, cwc_ecmf, cwc->cycletime);
							if (cwc->stage4_repeat > 12)
							{
								cwc->stage = 1;
								cs_log_dbg(D_CWC, "cyclecheck [Back to Stage 1] too much cyclefailure, maybe cycletime not correct %s Cycletime: %i Lockdiff: %" PRId64 " nextCycleCW = CW%i", cwc_ecmf, cwc->cycletime, (int64_t)now - cwc->locktime, cwc->nextcyclecw);
							}
						}
						cwc->stage4_repeat++;
						ret = ret == 3 ? 3 : 7; // IGN for first stage4 otherwise LEARN
					}
					if(cwc->stage == 3)
					{
						cwc->locktime = 0;
						cwc->stage4_repeat = 0;
					}
					else
					{
						if(cwc->stage < 3) { cwc->cycletime = now - cwc->time; }
						cwc->locktime = now + (get_fallbacktimeout(cwc->caid) / 1000);
					}
				}
				else if(cwc->stage != 3)
				{
					cs_log_dbg(D_CWC, "cyclecheck [Ignore this EA] for LearningStages because of locktime EA: %s Lockdiff: %" PRId64, cwc_ecmf, (int64_t)now - cwc->locktime);
					upd_entry = 0;
				}

				if(cwc->stage == 3)     // we stay in Stage 3 so we update only time and cw
				{
					if(now - cwc->time > cwc->cycletime)
					{
						cwc->dyncycletime = now - cwc->time - cwc->cycletime;
					}
					else
					{
						cwc->dyncycletime = 0;
					}
				}
			}
		}
		else
		{
			upd_entry = 0;
			cwc = NULL;
		}
		break;
	}

	if (readlocked)
	{
		cs_readunlock(__func__, &cwcycle_lock);
	}

	if(need_new_entry)
	{
		cs_readunlock(__func__, &cwcycle_lock);
		if(cw_cc_list_size <= mcl)    //only add when we have space
		{
			struct s_cw_cycle_check *new = NULL;
			if(cs_malloc(&new, sizeof(struct s_cw_cycle_check)))    // store cw on top in cyclelist
			{
				memcpy(new->cw, cw, sizeof(new->cw));
				// csp cache got no ecm and no md5 hash
				memcpy(new->ecm_md5[0].md5, er->ecmd5, sizeof(er->ecmd5));
#ifdef CS_CACHEEX
				new->ecm_md5[0].csp_hash = er->csp_hash; // we got no ecm_md5 so CSP-Hash could be necessary
#else
				new->ecm_md5[0].csp_hash = 0; //fake CSP-Hash we got a ecm_md5 so CSP-Hash is not necessary
#endif
				memcpy(new->ecm_md5[0].cw, cw, sizeof(new->cw));
				new->ecmlen = er->ecmlen;
				new->cwc_hist_entry = 0;
				new->caid = er->caid;
				new->provid = er->prid;
				new->sid = er->srvid;
				new->chid = er->chid;
				new->time = now;
				new->locktime = now + (get_fallbacktimeout(er->caid) / 1000);
				new->dyncycletime = 0; // to react of share timings
// cycletime over Cacheex
				new->stage = (cfg.cwcycle_usecwcfromce && cycletime_fr > 0 && next_cw_cycle_fr < 2) ? 3 : 0;
				new->cycletime = (cfg.cwcycle_usecwcfromce && cycletime_fr > 0 && next_cw_cycle_fr < 2) ? cycletime_fr : 99;
				new->nextcyclecw = (cfg.cwcycle_usecwcfromce && cycletime_fr > 0 && next_cw_cycle_fr < 2) ? next_cw_cycle_fr : 2; //2=we dont know which next cw Cycle;  0= next cw Cycle CW0; 1= next cw Cycle CW1;
				ret = (cycletime_fr > 0 && next_cw_cycle_fr < 2) ? 8 : 6;
//
				new->prev = new->next = NULL;
				new->old = 0;
				new->stage4_repeat = 0;
				//write lock
				cs_writelock(__func__, &cwcycle_lock);
				if(cw_cc_list)    // the new entry on top
				{
					cw_cc_list->prev = new;
					new->next = cw_cc_list;
				}
				cw_cc_list = new;
				cw_cc_list_size++;
				//write unlock /
				cs_writeunlock(__func__, &cwcycle_lock);

				cs_log_dbg(D_CWC, "cyclecheck [Store New Entry] %s Time: %" PRId64 " Stage: %i Cycletime: %i Locktime: %" PRId64, er_ecmf, (int64_t)new->time, new->stage, new->cycletime, (int64_t)new->locktime);
			}
		}
		else
		{
			cs_log("cyclecheck [Store New Entry] Max List arrived -> dont store new Entry list_size: %i, mcl: %i", cw_cc_list_size, mcl);
		}
	}
	else if(upd_entry && cwc)
	{
		cwc->prev = cwc->next = NULL;
		cwc->old = 0;
		memcpy(cwc->cw, cw, sizeof(cwc->cw));
		cwc->time = now;
		cwc->cwc_hist_entry++;
		if(cwc->cwc_hist_entry > 14)     //ringbuffer for md5
		{
			cwc->cwc_hist_entry = 0;
		}
		// csp cache got no ecm and no md5 hash
		memcpy(cwc->ecm_md5[cwc->cwc_hist_entry].md5, er->ecmd5, sizeof(cwc->ecm_md5[0].md5));
#ifdef CS_CACHEEX
		cwc->ecm_md5[cwc->cwc_hist_entry].csp_hash = er->csp_hash;
#else
		cwc->ecm_md5[cwc->cwc_hist_entry].csp_hash = 0; //fake CSP-Hash for logging
#endif
		memcpy(cwc->ecm_md5[cwc->cwc_hist_entry].cw, cw, sizeof(cwc->cw));
		cwc->ecmlen = er->ecmlen;
		//write lock /
		cs_writelock(__func__, &cwcycle_lock);
		if(cw_cc_list)    // the clone entry on top
		{
			cw_cc_list->prev = cwc;
			cwc->next = cw_cc_list;
		}
		cw_cc_list = cwc;
		cw_cc_list_size++;
		//write unlock /
		cs_writeunlock(__func__, &cwcycle_lock);
		cs_log_dbg(D_CWC, "cyclecheck [Update Entry and add on top] %s Time: %" PRId64 " Stage: %i Cycletime: %i", er_ecmf, (int64_t)cwc->time, cwc->stage, cwc->cycletime);
	}
	else if(cwc)
	{
		NULLFREE(cwc);
	}
	return ret;
}

static void count_ok(struct s_client *client)
{
	if(client)
	{
		client->cwcycledchecked++;
		client->cwcycledok++;
	}
	if(client && client->account)
	{
		client->account->cwcycledchecked++;
		client->account->cwcycledok++;
	}
}

static void count_nok(struct s_client *client)
{
	if(client)
	{
		client->cwcycledchecked++;
		client->cwcyclednok++;
	}
	if(client && client->account)
	{
		client->account->cwcycledchecked++;
		client->account->cwcyclednok++;
	}
}

static void count_ign(struct s_client *client)
{
	if(client)
	{
		client->cwcycledchecked++;
		client->cwcycledign++;
	}
	if(client && client->account)
	{
		client->account->cwcycledchecked++;
		client->account->cwcycledign++;
	}
}

uint8_t checkcwcycle(struct s_client *client, ECM_REQUEST *er, struct s_reader *reader, uint8_t *cw, int8_t rc, uint8_t cycletime_fr, uint8_t next_cw_cycle_fr)
{

	if(!cfg.cwcycle_check_enable)
		{ return 3; }
	if(client && client->account && client->account->cwc_disable)
		{ return 3; }
	//  if (!(rc == E_FOUND) && !(rc == E_CACHEEX))
	if(rc >= E_NOTFOUND)
		{ return 2; }
	if(!cw || !er)
		{ return 2; }
	if(!(chk_ctab_ex(er->caid, &cfg.cwcycle_check_caidtab)))  // dont check caid not in list
		{ return 1; } // no match leave the check
	if(is_halfCW_er(er))
		{ return 1; } // half cw cycle, checks are done in ecm-handler

	memcpy(er->cw, cw, 16);
	char er_ecmf[ECM_FMT_LEN];
	format_ecm(er, er_ecmf, ECM_FMT_LEN);

	char c_reader[64];
	char user[64];

	if(!streq(username(client), "NULL"))
		{ snprintf(user, sizeof(user), "%s", username(client)); }
	else
		{ snprintf(user, sizeof(user), "---"); }

	if(reader)
		{ snprintf(c_reader, sizeof(c_reader), "%s", reader->label); }
	else
		{ snprintf(c_reader, sizeof(c_reader), "cache"); }


	cs_log_dbg(D_CWC | D_TRACE, "cyclecheck EA: %s rc: %i reader: %s", er_ecmf, rc, c_reader);

	switch(checkcwcycle_int(er, er_ecmf, user, cw, c_reader, cycletime_fr, next_cw_cycle_fr))
	{

	case 0: // CWCYCLE OK
		count_ok(client);
		snprintf(er->cwc_msg_log, sizeof(er->cwc_msg_log), "cwc OK");
		break;

	case 1: // CWCYCLE NOK
		count_nok(client);
		snprintf(er->cwc_msg_log, sizeof(er->cwc_msg_log), "cwc NOK");
		if(cfg.onbadcycle > 0)    // ignore ECM Request
		{
#ifdef CS_CACHEEX_AIO
			if(!er->localgenerated)
			{
#endif
				cs_log("cyclecheck [Bad CW Cycle] for: %s %s from: %s -> drop cw (ECM Answer)", user, er_ecmf, c_reader); //D_CWC| D_TRACE
				return 0;
#ifdef CS_CACHEEX_AIO
			}
			else
			{
				cs_log("cyclecheck [Bad CW Cycle] for: %s %s from: %s -> lg-flagged CW -> do nothing", user, er_ecmf, c_reader); //D_CWC| D_TRACE
				break;
			}
#endif

		}
		else      // only logging
		{
			cs_log("cyclecheck [Bad CW Cycle] for: %s %s from: %s -> do nothing", user, er_ecmf, c_reader);//D_CWC| D_TRACE
			break;
		}

	case 2: // ER to OLD
#ifdef CS_CACHEEX_AIO
		if(!er->localgenerated)
		{
#endif
			count_nok(client);
			snprintf(er->cwc_msg_log, sizeof(er->cwc_msg_log), "cwc NOK(old)");
			cs_log("cyclecheck [Bad CW Cycle] for: %s %s from: %s -> ECM Answer is too OLD -> drop cw (ECM Answer)", user, er_ecmf, c_reader);//D_CWC| D_TRACE
			return 0;
#ifdef CS_CACHEEX_AIO
		}
		else
		{
			cs_log("cyclecheck [Bad CW Cycle] for: %s %s from: %s -> ECM Answer is too OLD -> lg-flagged CW -> do nothing", user, er_ecmf, c_reader); //D_CWC| D_TRACE
			break;
		}
#endif

	case 3: // CycleCheck ignored (stage 3 to stage 4)
		count_ign(client);
		snprintf(er->cwc_msg_log, sizeof(er->cwc_msg_log), "cwc IGN");
		break;

	case 4: // same CW
		cs_log_dbg(D_CWC, "cyclecheck [Same CW] for: %s %s -> same CW detected from: %s -> do nothing ", user, er_ecmf, c_reader);
		break;

	case 5: //answer from fixed Fallbackreader with Bad Cycle
		count_nok(client);
		snprintf(er->cwc_msg_log, sizeof(er->cwc_msg_log), "cwc NOK but IGN (fixed FB)");
		cs_log("cyclecheck [Bad CW Cycle] for: %s %s from: %s -> But Ignored because of answer from Fixed Fallback Reader", user, er_ecmf, c_reader);
		break;

	case 6: // not checked ( learning Stages Cycletime and CWCycle Stage < 3)
	case 7: // not checked ( learning Stages only CWCycle Stage 4)
		snprintf(er->cwc_msg_log, sizeof(er->cwc_msg_log), "cwc LEARN");
		break;

	case 8: // use Cyclecheck from CE Source
		count_ok(client);
		snprintf(er->cwc_msg_log, sizeof(er->cwc_msg_log), "cwc OK(CE)");
		break;

	case 9: // CWCYCLE NOK without counting
		snprintf(er->cwc_msg_log, sizeof(er->cwc_msg_log), "cwc NOK");
		if(cfg.onbadcycle > 0)    // ignore ECM Request
		{
#ifdef CS_CACHEEX_AIO
			if(!er->localgenerated)
			{
#endif
				cs_log("cyclecheck [Bad CW Cycle already Counted] for: %s %s from: %s -> drop cw (ECM Answer)", user, er_ecmf, c_reader);
				return 0;
#ifdef CS_CACHEEX_AIO
			}
			else
			{
				cs_log("cyclecheck [Bad CW Cycle already Counted] for: %s %s from: %s -> lg-flagged CW -> do nothing", user, er_ecmf, c_reader); //D_CWC| D_TRACE
				break;
			}
#endif
		}
		else      // only logging
		{
			cs_log("cyclecheck [Bad CW Cycle already Counted] for: %s %s from: %s -> do nothing", user, er_ecmf, c_reader);
			break;
		}

	}
	return 1;
}


/*
 *
 */

#endif
