./ MultiCS.r82 / srv-cs378x.c
//CMD00 - ECM (request)
//CMD01 - ECM (response)
//CMD02 - EMM (in clientmode - set EMM, in server mode - EMM data) - obsolete
//CMD03 - ECM (cascading request)
//CMD04 - ECM (cascading response)
//CMD05 - EMM (emm request) send cardata/cardinfo to client
//CMD06 - EMM (incomming EMM in server mode)
//CMD19 - EMM (incomming EMM in server mode) only seen with caid 0x1830
//CMD08 - Stop sending requests to the server for current srvid,prvid,caid
//CMD44 - MPCS/OScam internal error notification
//CMD55 - connect_on_init/keepalive

//CMD0x3d - CACHEEX Cache-push id request
//CMD0x3e - CACHEEX Cache-push id answer
//CMD0x3f - CACHEEX cache-push


struct camd35_client_data *getcs378xclientbyid(uint32_t id)
{
	if (cfg.cs378x.server) {
		struct camd35_client_data *cli = cfg.cs378x.server->client;
		while (cli) {
			if (!(cli->flags&FLAG_DELETE))
				if (cli->id==id) return cli;
			cli = cli->next;
		}
	}
	return NULL;
}

///////////////////////////////////////////////////////////////////////////////
// CONNECT
///////////////////////////////////////////////////////////////////////////////

void cs378x_disconnect_cli(struct camd35_client_data *cli)
{
	cli->connection.status = 0;
	uint32_t ticks = GetTickCount();
	cli->connection.uptime += ticks - cli->connection.time;
	cli->connection.lastseen = ticks; // Last Seen
	close(cli->handle);
	cli->handle = -1;
	debugf(0," cs378x: client '%s' disconnected \n", cli->user);
}

void *cs378x_connect_cli(struct connect_cli_data *param)
{
	uint8_t buf[1024];
	// Store data from param
	struct camd35_server_data *cs378x = param->server;
	int sock = param->sock;
	uint32_t ip = param->ip;
	free(param);

	int received = recv_nonb( sock, buf, 32+4, 5000);
	if (received<=0) {
		debugf(getdbgflag(DBG_CS378X,0,0), " cs378x: new connection aborted, rto\n");
		close(sock);
		return NULL;
	}
	uint32_t ucrc = (buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];
	//Check for clients
	struct camd35_client_data *cli = cs378x->client;
	while (cli) {
		if (cli->ucrc==ucrc) break;
		cli = cli->next;
	}
	if (!cli) {
		cli = cs378x->cacheexclient;
		while (cli) {
			if (cli->ucrc==ucrc) break;
			cli = cli->next;
		}
	}
	if (!cli) {
		debugf(getdbgflag(DBG_CS378X,cs378x->id,0)," cs378x%d: Unknown Client UCRC=%08x (%s)\n", cs378x->id, ucrc, ip2string(ip));
		close(sock);
		return NULL;
	}
	// Check reconnection
	if (cli->connection.status>0) cs378x_disconnect_cli(cli);
	//
	aes_decrypt( &cli->decryptkey, buf+4, received-4);
	int datalen = buf[5];
	if (buf[4] == 0) datalen = (((buf[25] & 0x0f) << 8) | buf[26]) + 3;
	else if (buf[4] == CAMD_CEX_IDREQUEST || buf[4] == CAMD_CEX_IDREPLY || buf[4] == CAMD_CEX_PUSH) datalen = buf[5] | (buf[6] << 8);
	int newlen = 4+camd35_padding(20+datalen);
	if (received<newlen) {
		int n = recv_nonb( sock, buf+received, newlen-received, 1000);
		if (n<=0) {
			debugf(getdbgflag(DBG_CS378X,0,0), " cs378x: new connection aborted, failed receive data\n");
			close(sock);
			return NULL;
		}
		aes_decrypt( &cli->decryptkey, buf+received, n);
	}
	//
	if (buf[4]==CAMD_KEEPALIVE) {
		//debugf(0," camd35: Keepalive from client '%s'\n", cli->user);
		if ( !cs378x_send( sock, &cli->encryptkey, cli->ucrc, buf+4, 20+1) ) {
			close( sock );
			return NULL;
		}
		debugf(getdbgflag(DBG_CS378X,0,cli->id)," cs378x: client '%s' connected\n", cli->user);
		cli->connection.status = 1;
		cli->connection.time = GetTickCount();
		cli->handle = sock;
		cli->ip = ip;
		pipe_wakeup( prg.pipe.cs378x[1] );
	}
#ifdef CACHEEX
	else if (buf[4]==CAMD_CEX_IDREQUEST) {
		//debugf(getdbgflag(DBG_CS378X,0,cli->id)," camd35: Nodeid from client '%s'\n", cli->user);
		memcpy( cli->nodeid, buf+24, 8);
		memset(buf, 0, 20+12);
		buf[0] = CAMD_CEX_IDREPLY;
		buf[1] = 12;
		memcpy( buf+20, cccam_nodeid, 8);
		if ( !cs378x_send( sock, &cli->encryptkey, cli->ucrc, buf, 20+12) ) {
			close( sock );
			return NULL;
		}
		char str[8*3+1];
		array2hex( cli->nodeid, str, 8);
		debugf(getdbgflag(DBG_CS378X,0,cli->id)," cs378x: CacheEX client '%s' connected, Nodeid = %s\n", cli->user, str);
		cli->connection.status = 1;
		cli->connection.time = GetTickCount();
		cli->handle = sock;
		cli->ip = ip;
		pipe_wakeup( prg.pipe.cs378x_cex[1] );
	}
#endif
	else close(sock);
	return NULL;
}


void cs378x_srv_accept(struct camd35_server_data *srv)
{
	struct sockaddr_in newaddr;
	socklen_t socklen = sizeof(struct sockaddr);
	int newfd = accept( srv->handle, (struct sockaddr*)&newaddr, /*(socklen_t*)*/&socklen);
	if ( newfd<=0 ) {
		if ( (errno!=EAGAIN) && (errno!=EINTR) ) debugf( getdbgflag(DBG_CS378X,0,0)," cs378x: Accept failed (errno=%d)\n", errno);
	}
	else {
		uint32_t newip = newaddr.sin_addr.s_addr;
		if ( isblockedip(newip) ) {
			debugf( getdbgflag(DBG_CS378X,0,0)," cs378x: New Connection (%s) closed, ip blocked\n", ip2string(newip) );
			close(newfd);
		}
		else {
			pthread_t srv_tid;
			if (cfg.cccam.keepalive) SetSocketKeepalive(newfd);
			SetSocketNoDelay(newfd);
			SetSoketNonBlocking(newfd);
			//debugf( getdbgflag(DBG_CS378X,0,0)," cs378x: new client Connection(%d)...%s\n", newfd, ip2string(newip) );
			struct connect_cli_data *newdata = malloc( sizeof(struct connect_cli_data) );
			newdata->server = srv; 
			newdata->sock = newfd; 
			newdata->ip = newaddr.sin_addr.s_addr;
			if ( !create_thread(&srv_tid, (threadfn)cs378x_connect_cli,newdata) ) {
				free( newdata );
				close( newfd );
			}
		}
	}
}

#ifndef MONOTHREAD_ACCEPT

void *cs378x_accept_thread(void *param)
{
	prctl(PR_SET_NAME,"cs378x Accept",0,0,0);

	while(!prg.restart) {

		struct pollfd pfd[MAX_PFD];
		int pfdcount = 0;

		struct camd35_server_data *cs378x = cfg.cs378x.server;
		while (cs378x) {
			if ( !IS_DISABLED(cs378x->flags) && (cs378x->handle>0) ) {
				cs378x->ipoll = pfdcount;
				pfd[pfdcount].fd = cs378x->handle;
				pfd[pfdcount++].events = POLLIN | POLLPRI;
			} else cs378x->ipoll = -1;
			cs378x = cs378x->next;
		}

		if (pfdcount) {
			int retval = poll(pfd, pfdcount, 3006);
			if ( retval>0 ) {
				struct camd35_server_data *cs378x = cfg.cs378x.server;
				while (cs378x) {
					if ( !IS_DISABLED(cs378x->flags) && (cs378x->handle>0) && (cs378x->ipoll>=0) && (cs378x->handle==pfd[cs378x->ipoll].fd) ) {
						if ( pfd[cs378x->ipoll].revents & (POLLIN|POLLPRI) ) cs378x_srv_accept(cs378x);
					}
					cs378x = cs378x->next;
				}
			}
			else if (retval<0) usleep(96000);
		} else sleep(1);
	}
	return NULL;
}

#endif


///////////////////////////////////////////////////////////////////////////////
// SEND DCW
///////////////////////////////////////////////////////////////////////////////

void cs378x_senddcw_cli(struct camd35_client_data *cli)
{
	uint8_t buf[CC_MAXMSGSIZE];
	uint32_t ticks = GetTickCount();

	if (cli->ecm.status==STAT_DCW_SENT) {
		debugf(getdbgflag(DBG_CS378X,0,cli->id)," +> cw send failed to cs378x client '%s', cw already sent\n", cli->user); 
		return;
	}
	if (cli->connection.status<=0) {
		debugf(getdbgflag(DBG_CS378X,0,cli->id)," +> cw send failed to cs378x client '%s', client disconnected\n", cli->user); 
		return;
	}
	if (!cli->ecm.busy) {
		debugf(getdbgflag(DBG_CS378X,0,cli->id)," +> cw send failed to cs378x client '%s', no ecm request\n", cli->user); 
		return;
	}

	ECM_DATA *ecm = cli->ecm.request;
	//FREEZE
	int samechannel = (cli->lastecm.caid==ecm->caid)&&(cli->lastecm.prov==ecm->provid)&&(cli->lastecm.sid==ecm->sid);
	int enablefreeze=0;
	if (samechannel) {
		if ( (cli->lastecm.hash!=ecm->hash)&&(cli->lastecm.tag!=ecm->ecm[0]) )
		if ( (cli->lastecm.status=1)&&(cli->lastdcwtime+200<ticks) ) enablefreeze = 1;
	} else cli->zap++;
	//
	cli->lastecm.caid = ecm->caid;
	cli->lastecm.prov = ecm->provid;
	cli->lastecm.sid = ecm->sid;
	cli->lastecm.hash = ecm->hash;
	cli->lastecm.tag = ecm->ecm[0];
	cli->lastecm.decodetime = ticks-cli->ecm.recvtime;
	cli->lastecm.request = cli->ecm.request;

	// SID
	buf[8] = (ecm->sid>>8) & 0xFF;
	buf[9] = (ecm->sid) & 0xFF;
	// CAID
	buf[10] = (ecm->caid>>8) & 0xFF;
	buf[11] = (ecm->caid) & 0xFF;
	// PROVID
	buf[12] = (ecm->provid>>24) & 0xFF;
	buf[13] = (ecm->provid>>16) & 0xFF;
	buf[14] = (ecm->provid>>8) & 0xFF;
	buf[15] = (ecm->provid) & 0xFF;
	// PIN
	buf[16] = (cli->ecm.pin>>8);
	buf[17] = cli->ecm.pin & 0xFF;

	if ( (ecm->dcwstatus==STAT_DCW_SUCCESS)&&(ecm->hash==cli->ecm.hash) ) {
		cli->lastecm.dcwsrctype = ecm->dcwsrctype;
		cli->lastecm.dcwsrcid = ecm->dcwsrcid;
		cli->lastecm.status=1;
		cli->ecmok++;
		cli->lastdcwtime = ticks;
		cli->ecmoktime += ticks-cli->ecm.recvtime;
		//cli->lastecmoktime = ticks-cli->ecm.recvtime;

		buf[0] = CAMD_ECM_REPLY;
		buf[1] = 16;
		// ECM
		memcpy( buf+20, ecm->cw, 16 );
		if ( !cs378x_send( cli->handle, &cli->encryptkey, cli->ucrc, buf, 20+16) ) {
			cs378x_disconnect_cli( cli );
			return;
		}
		debugf(getdbgflagpro(DBG_CS378X,0,cli->id,ecm->cs->id)," => cw to cs378x client '%s' ch %04x:%06x:%04x (%dms)\n", cli->user, ecm->caid,ecm->provid,ecm->sid, GetTickCount()-cli->ecm.recvtime);
	}
	else { //if (ecm->data->dcwstatus==STAT_DCW_FAILED)
		if (enablefreeze) {
			cli->freeze++;
		}
		cli->lastecm.dcwsrctype = DCW_SOURCE_NONE;
		cli->lastecm.dcwsrcid = 0;
		cli->lastecm.status=0;
		buf[0] = 0x44;
		buf[1] = 0;
		if ( !cs378x_send( cli->handle, &cli->encryptkey, cli->ucrc, buf, 20) ) {
			cs378x_disconnect_cli( cli );
			return;
		}
		debugf(getdbgflagpro(DBG_CS378X,0,cli->id,ecm->cs->id)," |> decode failed to cs378x client '%s' ch %04x:%06x:%04x (%dms)\n", cli->user, ecm->caid,ecm->provid,ecm->sid, GetTickCount()-cli->ecm.recvtime);
	}
	cli->ecm.busy=0;
	cli->ecm.status = STAT_DCW_SENT;
}

///////////////////////////////////////////////////////////////////////////////

// Check sending cw to clients
void cs378x_check_sendcw(ECM_DATA *ecm)
{
	struct camd35_server_data *cs378x = cfg.cs378x.server;
	while (cs378x) {
		if ( !IS_DISABLED(cs378x->flags) && (cs378x->handle>0) ) {
			struct camd35_client_data *cli = cs378x->client;
			while (cli) {
				if ( !IS_DISABLED(cli->flags)&&(cli->handle>0)&&(cli->ecm.busy)&&(cli->ecm.request==ecm) ) {
					cs378x_senddcw_cli( cli );
				}
				cli = cli->next;
			}
		}
		cs378x = cs378x->next;
	}
}



///////////////////////////////////////////////////////////////////////////////
// RECV MSG
///////////////////////////////////////////////////////////////////////////////

void cs378x_store_ecmclient(ECM_DATA *ecm, struct camd35_client_data *cli)
{
	uint32_t ticks = GetTickCount();
	cli->ecm.recvtime = ticks;
	cli->ecm.request = ecm;
    cli->ecm.status = STAT_ECM_SENT;
	ecm_addip(ecm, cli->ip);
}

void *cs378x_recvmsg( struct camd35_client_data *cli )
{
	struct cardserver_data *cs;
	uint8_t cw[16];
	uint8_t buf[2048];
	int received = recv_nonb( cli->handle, buf, 32+4, 1000);
	if (received<=0) {
		debugf(getdbgflag(DBG_CS378X,0,cli->id)," cs378x: read failed from client '%s'\n", cli->user);
		cs378x_disconnect_cli(cli);
		return NULL;
	}
	uint32_t ucrc = (buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];
	if (ucrc!=cli->ucrc) {
		debugf(getdbgflag(DBG_CS378X,0,cli->id)," cs378x: wrong UCRC failed from client '%s'\n", cli->user);
		cs378x_disconnect_cli(cli);
		return NULL;
	}
	aes_decrypt( &cli->decryptkey, buf+4, received-4);
	int datalen = buf[5];
	if (buf[4] == 0) datalen = (((buf[25] & 0x0f) << 8) | buf[26]) + 3;
	else if ( (buf[4]&0xFC)==0x3C ) datalen = buf[5] | (buf[6] << 8);
	else if ((buf[4]&0xFE)==0x80) datalen = buf[5] | (buf[6] << 8);
	int newlen = 4+camd35_padding(20+datalen);
	if (received<newlen) {
		int n = recv_nonb( cli->handle, buf+received, newlen-received, 1000);
		if (n<=0) {
			debugf(getdbgflag(DBG_CS378X,0,cli->id)," cs378x: read failed from client '%s'\n", cli->user);
			cs378x_disconnect_cli(cli);
			return NULL;
		}
		aes_decrypt( &cli->decryptkey, buf+received, n);
	}
	//debugf(getdbgflag(DBG_CS378X,0,cli->id), " cs378x: msg from client '%s' \n", cli->user); //debughex(buf, newlen);

	uint32_t ticks = GetTickCount();

	switch (buf[4]) {
		// Keepalive
		case CAMD_KEEPALIVE:
			//debugf(0," camd35: Keepalive from client '%s'\n", cli->user);
			if ( !cs378x_send( cli->handle, &cli->encryptkey, cli->ucrc, buf+4, 20+1) ) cs378x_disconnect_cli( cli );
			else if (cli->connection.status<=0) {
				debugf(getdbgflag(DBG_CS378X,0,cli->id)," cs378x: client '%s' connected\n", cli->user);
				cli->connection.status = 1;
				cli->connection.time = GetTickCount();
			}
			break;

#ifdef CACHEEX
		// Request Nodeid
		case CAMD_CEX_IDREQUEST:
			//debugf(0," camd35: Nodeid from client '%s'\n", cli->user);
			memcpy( cli->nodeid, buf+24, 8);
			memset(buf, 0, 20+12);
			buf[0] = CAMD_CEX_IDREPLY;
			buf[1] = 12;
			memcpy( buf+20, cccam_nodeid, 8);
			if ( !cs378x_send( cli->handle, &cli->encryptkey, cli->ucrc, buf, 20+12) ) cs378x_disconnect_cli( cli );
			else if (cli->connection.status<=0) {
				char str[8*3+1];
				array2hex( cli->nodeid, str, 8);
				debugf(getdbgflag(DBG_CS378X,0,cli->id)," cs378x: client '%s' connected, Nodeid = %s\n", cli->user, str);
				cli->connection.status = 1;
				cli->connection.time = GetTickCount();
			}
			break;

		// push out
		case CAMD_CEX_PUSH:
			if (cli->cacheex_mode!=3) break;
			memcpy( cw, buf+44, 16);
			if (!acceptDCW(cw)) break;
			struct cache_data cacheex;
			cacheex.sid = (buf[12]<<8)|buf[13];
			cacheex.caid = (buf[14]<<8)|buf[15];
			cacheex.provid = (buf[16]<<24)|(buf[17]<<16)|(buf[18]<<8)|buf[19];
			// Look for cardserver
			cs = getcsbycaprovid(cacheex.caid, cacheex.provid);
			if ( !cs || !cs->option.fallowcacheex ) {
				cli->cacheex.badcw++;
				break;
			}
			if ((buf[23]&0xFE)==0x80) cacheex.tag = buf[23]; else cacheex.tag = 0;
			memcpy( cacheex.ecmd5, buf+24, 16);
			//if ( !checkECMD5(cacheex.ecmd5) ) cli->cacheex.totalcsp++;
			cacheex.hash = (buf[43]<<24) | (buf[42]<<16) | (buf[41]<<8) | buf[40];
			if (!cacheex_check(&cacheex)) break;
			cli->cacheex.got[0]++;
			int uphop = buf[60];
			if (uphop<10) cli->cacheex.got[uphop]++;
			//
			pthread_mutex_lock( &prg.lockcache );
			int res = cache_setdcw( &cacheex, cw, NO_CYCLE, PEER_CS378X_CLIENT | cli->id );
			pthread_mutex_unlock( &prg.lockcache );
			if (res&DCW_ERROR) {
				if ( !(res&DCW_SKIP) ) cli->cacheex.badcw++;
			}
			else if (res&DCW_CYCLE) {
				if ( cs->option.cacheex.maxhop>uphop ) {
					uint8_t nodeid[8];
					memcpy( nodeid, buf+61, 8);
					pipe_send_cacheex_push_cache(&cacheex, cw, nodeid); //cacheex_push(&cacheex, cw, nodeid);
				}
			}
			//debugf(getdbgflag(DBG_CS378X,0,cli->id)," cs378x: push from client '%s' %04x:%06x:%04x|%02x:%08x\n", cli->user, cacheex.caid,cacheex.provid,cacheex.sid, cacheex.tag,cacheex.hash);
			break;
#endif

		case CAMD_ECM_REQUEST:
			if (cli->cacheex_mode) {
				cli->ecmdenied++;
				break;
			}
			cli->ecmnb++;
			cli->lastecmtime = ticks;
			//Check for card availability
			int ecmlen = (((buf[25] & 0x0f) << 8) | buf[26]) + 3;
			uint8_t ecmdata[512];
			memcpy( ecmdata, buf+24, ecmlen );
			uint16_t pin = buf[4+16]<<8 | buf[4+17];
			uint16_t sid = buf[4+8]<<8 | buf[4+9];
			uint16_t caid = buf[4+10]<<8 | buf[4+11];
			uint32_t provid = ecm_getprovid( ecmdata, caid );
			if (provid==0) provid = buf[4+12]<<24 | buf[4+13]<<16 | buf[4+14]<<8 | buf[4+15];
			if ( !caid ) {
				cli->ecmdenied++;
				buf[4] = 0x44;
				buf[5] = 0;
				if ( !cs378x_send( cli->handle, &cli->encryptkey, cli->ucrc, buf+4, 20) ) cs378x_disconnect_cli( cli );
				else debugf(getdbgflag(DBG_CS378X,0,cli->id)," <!> decode failed to cs378x client '%s' ch %04x:%06x:%04x Invalid CAID\n", cli->user,caid,provid,sid);
				break;
			}
			// Look for cardserver
			cs = cfg.cardserver;
			while (cs) {
				if (caid==cs->card.caid) {
					int j;
					for (j=0; j<cs->card.nbprov;j++) if (provid==cs->card.prov[j].id) break;
					if (j<cs->card.nbprov) break;
				}
				cs = cs->next;
			}
			if (!cs) {
				cli->ecmdenied++;
				buf[4] = 0x44;
				buf[5] = 0;
				if ( !cs378x_send( cli->handle, &cli->encryptkey, cli->ucrc, buf+4, 20) ) cs378x_disconnect_cli( cli );
				else debugf(getdbgflag(DBG_CS378X,0,cli->id)," <!> decode failed to client '%s' ch %04x:%06x:%04x, Invalid CAID/PROVIDER\n", cli->user,caid,provid,sid);
				break;
			}
			// Check for Accepted sids
			uint8_t cw1cycle;
			if ( !accept_sid(cs, provid, sid, ecm_getchid(ecmdata,caid), ecmlen, &cw1cycle) ) {
				cli->ecmdenied++;
				cs->ecmdenied++;
				buf[4] = 0x44;
				buf[5] = 0;
				if ( !cs378x_send( cli->handle, &cli->encryptkey, cli->ucrc, buf+4, 20) ) cs378x_disconnect_cli( cli );
				else debugf(getdbgflagpro(DBG_CS378X,0,cli->id,cs->id)," <!> decode failed to cs378x client '%s' ch %04x:%06x:%04x SID not accepted\n", cli->user,caid,provid,sid);
				break;
			}

			// ACCEPTED
			pthread_mutex_lock(&prg.lockecm); //###
			// Search for ECM
			ECM_DATA *ecm = search_ecmdata_any(cs, ecmdata,  ecmlen, sid, caid); // dont get failed ecm request from cache
			int isnew =  ( ecm==NULL );
			if (ecm) {
				ecm->lastrecvtime = ticks;
				if (ecm->dcwstatus==STAT_DCW_FAILED) {
					if (ecm->period > cs->option.dcw.retry) {
						buf[4] = 0x44;
						buf[5] = 0;
						if ( !cs378x_send( cli->handle, &cli->encryptkey, cli->ucrc, buf+4, 20) ) cs378x_disconnect_cli( cli );
						else debugf(getdbgflagpro(DBG_CS378X,0,cli->id, cs->id)," <!> decode failed to cs378x client '%s' ch %04x:%06x:%04x:%08x, already failed\n",cli->user, caid, provid, sid, ecm->hash);
					}
					else {
						ecm->period++; // RETRY
						cs378x_store_ecmclient(ecm, cli);
						debugf(getdbgflagpro(DBG_CS378X,0,cli->id, cs->id)," <- ecm from cs378x client '%s' ch %04x:%06x:%04x:%08x**\n", cli->user, caid, provid, sid, ecm->hash);
						cli->ecm.busy=1;
						cli->ecm.pin = pin;
						cli->ecm.hash = ecm->hash;
						ecm->dcwstatus = STAT_DCW_WAIT;
						ecm->cachestatus = 0; //ECM_CACHE_NONE; // Resend Request
						ecm->checktime = 1; // Check NOW
						pipe_wakeup( prg.pipe.ecm[1] );
					}
				}
				else { // SUCCESS/WAIT
					cs378x_store_ecmclient(ecm, cli);
					cli->ecm.pin = pin;
					debugf(getdbgflagpro(DBG_CS378X,0,cli->id, cs->id)," <- ecm cs378x CCcam client '%s' ch %04x:%06x:%04x:%08x*\n", cli->user, caid, provid, sid, ecm->hash);
					cli->ecm.busy=1;
					cli->ecm.hash = ecm->hash;
					if (cli->dcwcheck) {
						if ( !ecm->lastdecode.ecm && (ecm->lastdecode.ecm!=ecm) ) {
							checkfreeze_checkECM( ecm, cli->lastecm.request);
							if (ecm->lastdecode.ecm) pipe_cache_find(ecm, cs);
						}
					}
					// Check for Success/Timeout
					if (!ecm->checktime) {
						if (cli->dcwcheck) {
							if ( (ecm->dcwstatus==STAT_DCW_SUCCESS) && !checkfreeze_setdcw(ecm,ecm->cw) ) { // ??? last ecm is wrong
								ecm->dcwstatus = STAT_DCW_WAIT;
								memset( ecm->cw, 0, 16 );
								ecm->checktime = 1; // Wakeup Now
								pipe_wakeup( prg.pipe.ecm[1] );
							}
							else {
								pthread_mutex_unlock(&prg.lockecm);
								cs378x_senddcw_cli(cli);
								break;
							}
						}
						else {
							pthread_mutex_unlock(&prg.lockecm);
							cs378x_senddcw_cli(cli);
							break;
						}
					}
				}
			}
			else {
				cs->ecmaccepted++;
				// Setup ECM Request for Server(s)
				ecm = store_ecmdata(cs, ecmdata, ecmlen, sid,caid,provid);
				cs378x_store_ecmclient(ecm, cli);
				cli->ecm.pin = pin;
				debugf(getdbgflagpro(DBG_CS378X,0,cli->id, cs->id)," <- ecm from cs378x client '%s' ch %04x:%06x:%04x:%08x\n",cli->user,caid,provid,sid, ecm->hash);
				cli->ecm.busy=1;
				cli->ecm.hash = ecm->hash;
				ecm->cw1cycle = cw1cycle;
				ecm->dcwstatus = STAT_DCW_WAIT;
#ifdef CHECK_NEXTDCW
				if (cli->dcwcheck) checkfreeze_checkECM( ecm, cli->lastecm.request);
#endif
				if (cs->option.fallowcache) {
					ecm->waitcache = 1;
					ecm->dcwstatus = STAT_DCW_WAITCACHE;
					ecm->checktime = ecm->recvtime + cs->option.cachetimeout;
					pipe_cache_find(ecm, cs);
				}
				else ecm->checktime = 1; // Check NOW
				pipe_wakeup( prg.pipe.ecm[1] );
			}
			pthread_mutex_unlock(&prg.lockecm);
			if (isnew) wakeup_sendecm();
			break;
	}
	return NULL;
}

//xxx: host port user pass { cacheex_mode = 3; shares = 0500:42400&42800; }
void cs378x_recv_pipe()
{
	uint8_t buf[64];
	struct pollfd pfd;
	do {
		pipe_recv( prg.pipe.cs378x[0], buf);
		pfd.fd = prg.pipe.cs378x[0];
		pfd.events = POLLIN | POLLPRI;
	} while (poll(&pfd, 1, 0)>0);
}

///////////////////////////////////////////////////////////////////////////////
void *cs378x_recvmsg_thread(void *param)
{
	cfg.cs378x.pid_recvmsg = syscall(SYS_gettid);
	prctl(PR_SET_NAME,"cs378x RecvMSG Thread",0,0,0);

	while (!prg.restart) {
		struct pollfd pfd[MAX_CSPORTS];
		int pfdcount = 0;

		pfd[pfdcount].fd = prg.pipe.cs378x[0];
		pfd[pfdcount++].events = POLLIN | POLLPRI;
		
		struct camd35_server_data *cs378x = cfg.cs378x.server;
		while (cs378x) {
			if ( !IS_DISABLED(cs378x->flags)&&(cs378x->handle>0) ) {
				struct camd35_client_data *cli = cs378x->client;
				while (cli) {
					if (cli->connection.status>0) {
						cli->ipoll = pfdcount;
						pfd[pfdcount].fd = cli->handle;
						pfd[pfdcount++].events = POLLIN | POLLPRI;
					} else cli->ipoll = -1;
					cli = cli->next;
				}
			}
			cs378x = cs378x->next;
		}

		int retval = poll(pfd, pfdcount, 3000);

		if (retval>0) {

			struct camd35_server_data *cs378x = cfg.cs378x.server;
			while (cs378x) {
				if ( !IS_DISABLED(cs378x->flags)&&(cs378x->handle>0) ) {
					struct camd35_client_data *client = cs378x->client;
					while (client) {
						if ( (client->handle>0) && (client->ipoll>=0) && (client->handle==pfd[client->ipoll].fd) ) {
							if ( pfd[client->ipoll].revents & (POLLIN|POLLPRI) ) {
								cs378x_recvmsg( client );
							}
						}
						client = client->next;
					}
				}
				cs378x = cs378x->next;
			}

			if ( pfd[0].revents & (POLLIN|POLLPRI) ) cs378x_recv_pipe();

		} else if (retval<0) usleep(99000);
	}
	return NULL;
}


///////////////////////////////////////////////////////////////////////////////
// CAMD35 CACHEEX RECVMSG
///////////////////////////////////////////////////////////////////////////////

void cs378x_cacheex_recv_pipe()
{
	uint8_t buf[64];
	struct pollfd pfd;
	do {
		pipe_recv( prg.pipe.cs378x_cex[0], buf);
		pfd.fd = prg.pipe.cs378x_cex[0];
		pfd.events = POLLIN | POLLPRI;
	} while (poll(&pfd, 1, 0)>0);
}

///////////////////////////////////////////////////////////////////////////////
void *cs378x_cacheex_recvmsg_thread(void *param)
{
	cfg.cs378x.pid_recvmsg = syscall(SYS_gettid);
	prctl(PR_SET_NAME,"cs378x RecvMSG Thread",0,0,0);

	while (!prg.restart) {
		struct pollfd pfd[MAX_CSPORTS];
		int pfdcount = 0;

		pfd[pfdcount].fd = prg.pipe.cs378x_cex[0];
		pfd[pfdcount++].events = POLLIN | POLLPRI;
		
		struct camd35_server_data *cs378x = cfg.cs378x.server;
		while (cs378x) {
			if ( !IS_DISABLED(cs378x->flags)&&(cs378x->handle>0) ) {
				struct camd35_client_data *cli = cs378x->cacheexclient;
				while (cli) {
					if (cli->connection.status>0) {
						cli->ipoll = pfdcount;
						pfd[pfdcount].fd = cli->handle;
						pfd[pfdcount++].events = POLLIN | POLLPRI;
					} else cli->ipoll = -1;
					cli = cli->next;
				}
			}
			cs378x = cs378x->next;
		}

		int retval = poll(pfd, pfdcount, 3000);

		if (retval>0) {

			struct camd35_server_data *cs378x = cfg.cs378x.server;
			while (cs378x) {
				if ( !IS_DISABLED(cs378x->flags)&&(cs378x->handle>0) ) {
					struct camd35_client_data *client = cs378x->cacheexclient;
					while (client) {
						if ( (client->handle>0) && (client->ipoll>=0) && (client->handle==pfd[client->ipoll].fd) ) {
							if ( pfd[client->ipoll].revents & (POLLIN|POLLPRI) ) {
								cs378x_recvmsg( client );
							}
						}
						client = client->next;
					}
				}
				cs378x = cs378x->next;
			}

			if ( pfd[0].revents & (POLLIN|POLLPRI) ) cs378x_cacheex_recv_pipe();

		} else if (retval<0) usleep(99000);
	}
	return NULL;
}

///////////////////////////////////////////////////////////////////////////////
// CAMD35 SERVER: START/STOP
///////////////////////////////////////////////////////////////////////////////

int start_thread_cs378x()
{
	pthread_t tid;
#ifndef MONOTHREAD_ACCEPT
	create_thread(&tid, cs378x_accept_thread,NULL);
#endif

	create_thread(&cfg.cs378x.tid_recvmsg, cs378x_cacheex_recvmsg_thread,NULL);
	create_thread(&cfg.cs378x.tid_recvmsg, cs378x_recvmsg_thread,NULL);
	return 0;
}