./ MultiCS.r82 / cli-cs378x.c
///////////////////////////////////////////////////////////////////////////////
// TCP
///////////////////////////////////////////////////////////////////////////////
//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


void cs378x_send_keepalive(struct server_data *srv)
{
	uint8_t buf[64];

#ifdef CACHEEX
	if (srv->cacheex_mode) {
		// Request Nodeid
		memset(buf, 0, 32);
		buf[0] = CAMD_CEX_IDREQUEST;
		buf[1] = 12;
		memcpy( buf+20, cccam_nodeid, 8);
		cs378x_send( srv->handle, &srv->encryptkey, srv->ucrc, buf, 20+12);
	}
	else
#endif
	{
		// keepalive
		uint8_t buf[64];
		memset(buf,0, 21);
		buf[0] = CAMD_KEEPALIVE;
		buf[1] = 1;
		buf[2] = 0;
		cs378x_send( srv->handle, &srv->encryptkey, srv->ucrc, buf, 20+1);
	}
}

///////////////////////////////////////////////////////////////////////////////
// SEND ECM
///////////////////////////////////////////////////////////////////////////////

int cs378x_sendecm_srv(struct server_data *srv, ECM_DATA *ecm)
{
	srv->lastecmtime = GetTickCount();
	srv->ecmnb++;
	srv->busy=1;
	srv->ecm.msgid++;
	if (srv->ecm.msgid>0xfff) srv->ecm.msgid = 1;
	srv->ecm.request = ecm;

	unsigned char buf[1024];
	memset(buf, 0, 20);
	buf[0] = 0; // Command
	buf[1] = 0; // Length
	buf[8] = ecm->sid>>8;
	buf[9] = ecm->sid&0xff;
	buf[10] = ecm->caid>>8;
	buf[11] = ecm->caid&0xff;
	buf[12] = ecm->provid>>24;
	buf[13] = ecm->provid>>16;
	buf[14] = ecm->provid>>8;
	buf[15] = ecm->provid&0xff;
	buf[16] = srv->ecm.msgid>>8;
	buf[17] = srv->ecm.msgid;
	memcpy( buf+20, ecm->ecm, ecm->ecmlen);
	cs378x_send( srv->handle, &srv->encryptkey, srv->ucrc, buf, 20+ecm->ecmlen);
	return 1;
}

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



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

// CACHEEX MODE 2
void *cs378x_srv_recvmsg(struct server_data *srv)
{
	uint8_t cw[16];
	struct cardserver_data *cs;
/*
		// TEST KEEPALIVE
		uint32_t ticks = GetTickCount();
		if ( !srv->keepalive.status && ((srv->keepalive.time+30000)<ticks) ) {
			cs378x_send_keepalive(srv);
			srv->keepalive.status = 1; // Sent and waiting for reply
			srv->keepalive.time = ticks;
		}
		else if ( (srv->keepalive.status==1) && ((srv->keepalive.time+5000)<ticks) ) {
			cs378x_send_keepalive(srv);
			srv->keepalive.status = 2;
		}
		else if ( (srv->keepalive.status>1) && ((srv->keepalive.time+10000)<ticks) ) {
			debugf(getdbgflag(DBG_SERVER,0,srv->id),, " cs378x server disconnected, keepalive timeout\n");
			close(srv->handle);
			srv->handle = -1;
			srv->pid = 0;
			srv->connected = 0;
			srv->keepalive.status = 0;
			srv->keepalive.time = 0;
			return NULL;
		}
*/

		unsigned char buf[2048];
		int received = recv_nonb( srv->handle, buf, 32+4, 1000);
		if (received<=0) {
			disconnect_srv(srv);
			//debugf(getdbgflag(DBG_SERVER,0,srv->id), " cs378x server disconnected, receive timeout\n");
			return NULL;
		}

		uint32_t ucrc = (buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];
		if (srv->ucrc!=ucrc) {
			disconnect_srv(srv);
			//debugf(getdbgflag(DBG_SERVER,0,srv->id), " cs378x server disconnected, wrong ucrc\n");
			return NULL;
		}
		aes_decrypt( &srv->decryptkey, buf+4, received-4);
		//Fix for ECM request size > 255 (use ecm length field)
		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); // cacheex
		int newlen = 4+camd35_padding(20+datalen);
		if (received<newlen) {
			int n = recv_nonb( srv->handle, buf+received, newlen-received, 500);
			if ( n != (newlen-received) ) {
				disconnect_srv(srv);
				//debugf(getdbgflag(DBG_SERVER,0,srv->id), " cs378x server disconnected, receive timeout\n");
				return NULL;
			}
			aes_decrypt( &srv->decryptkey, buf+received, n);
		}

		//debugf(getdbgflag(DBG_SERVER,0,srv->id)," msg from cs378x server (%s:%d)\n", srv->host->name, srv->port); debughex(buf, newlen);

		switch (buf[4]) {

			case CAMD_ECM_REPLY:
				srv->lastdcwtime = GetTickCount();
				if (!srv->busy) {
					debugf(getdbgflag(DBG_SERVER,0,srv->id)," [!] dcw error from cs378x server (%s:%d), unknown ecm request\n",srv->host->name,srv->port);
					break;
				}
				//
				if (buf[5]!=0x10) {
					debugf(getdbgflag(DBG_SERVER,0,srv->id)," [!] dcw error from cs378x server (%s:%d), wrong length!!!\n",srv->host->name,srv->port);
					break;
				}
				// Check for DCW
				if (!acceptDCW( buf+24 ) ) {
					srv->ecmerrdcw++;
					debugf(getdbgflag(DBG_SERVER,0,srv->id)," [!] dcw error from cs378x server (%s:%d), bad dcw!!!\n",srv->host->name,srv->port);
					break;
				}
				// Checl Stored ECM
				ECM_DATA *ecm = srv->ecm.request;
				if (!ecm) {
					debugf(getdbgflag(DBG_SERVER,0,srv->id)," [!] dcw error from cs378x server (%s:%d), ecm not found!!!\n",srv->host->name,srv->port);
					break;
				}
				//
				srv->busy = 0;
				pipe_cmd( prg.pipe.ecm[1], PIPE_SRV_AVAILABLE );
				pthread_mutex_lock(&prg.lockecm); //###

				// check for ECM validity
				if (ecm->hash!=srv->ecm.hash) {
					debugf(getdbgflag(DBG_SERVER,0,srv->id)," [!] dcw error from cs378x server (%s:%d), ecm deleted!!!\n",srv->host->name,srv->port);
					pthread_mutex_unlock(&prg.lockecm);
					break;
				}

				srv->ecmok++;
				srv->lastecmoktime = GetTickCount()-srv->lastecmtime;
				srv->ecmoktime += srv->lastecmoktime;
				ecm_setsrvflagdcw( ecm, srv->id, ECM_SRV_REPLY_GOOD, buf+24 );
				debugf(getdbgflagpro(DBG_SERVER,0,srv->id,ecm->cs->id)," <= cw from cs378x server (%s:%d) ch %04x:%06x:%04x (%dms)\n", srv->host->name,srv->port, ecm->caid,ecm->provid,ecm->sid, GetTickCount()-srv->lastecmtime);

				if (ecm->dcwstatus!=STAT_DCW_SUCCESS) {
					static char msg[] = "Good dcw from camd35 server";
					ecm->statusmsg = msg;
					// Store ECM Answer
					ecm_setdcw( ecm, buf+24, DCW_SOURCE_SERVER, srv->id );
				}
				else {	//TODO: check same dcw between cards
					srv->ecmerrdcw ++;
					if ( memcmp( ecm->cw, buf+24, 16) ) debugf(getdbgflagpro(DBG_SERVER,0,srv->id,ecm->cs->id)," !!! different dcw from cs378x server (%s:%d)\n",srv->host->name,srv->port);
				}
#ifdef SID_FILTER
				// ADD IN SID LIST
				cs= ecm->cs;
				if (cs) {
					cardsids_update( srv->busycard, ecm->provid, ecm->sid, 1);
					srv_cstatadd( srv, cs->id, 1 , srv->lastecmoktime);
				}
#endif
				pthread_mutex_unlock(&prg.lockecm); //###
				break;


			case 0x44:
				srv->lastdcwtime = GetTickCount();
				if (!srv->busy) {
					debugf(getdbgflag(DBG_SERVER,0,srv->id)," [!] dcw error from cs378x server (%s:%d), unknown ecm request\n",srv->host->name,srv->port);
					break;
				}
				// Checl Stored ECM
				ecm = srv->ecm.request;
				if (!ecm) {
					debugf(getdbgflag(DBG_SERVER,0,srv->id)," [!] dcw error from cs378x server (%s:%d), ecm not found!!!\n",srv->host->name,srv->port);
					break;
				}
				//
				srv->busy = 0;
				pipe_cmd( prg.pipe.ecm[1], PIPE_SRV_AVAILABLE );
				pthread_mutex_lock(&prg.lockecm); //###

				// check for ECM validity
				if (ecm->hash!=srv->ecm.hash) {
					debugf(getdbgflag(DBG_SERVER,0,srv->id)," [!] dcw error from cs378x server (%s:%d), ecm deleted!!!\n",srv->host->name,srv->port);
					pthread_mutex_unlock(&prg.lockecm);
					break;
				}

				cs= ecm->cs;
				ecm_setsrvflag(ecm, srv->id, ECM_SRV_REPLY_FAIL);
				debugf(getdbgflagpro(DBG_SERVER,0,srv->id,ecm->cs->id)," <| decode failed from cs378x server (%s:%d) ch %04x:%06x:%04x (%dms)\n", srv->host->name,srv->port, ecm->caid,ecm->provid,ecm->sid, GetTickCount()-srv->lastecmtime);
#ifdef SID_FILTER
				// ADD IN SID LIST
				if (cs) {
					cardsids_update( srv->busycard, ecm->provid, ecm->sid, -1);
					srv_cstatadd( srv, cs->id, 0 , 0);
				}
#endif
				pthread_mutex_unlock(&prg.lockecm); //###
				wakeup_sendecm(); // Wakeup ecm waiting for availabe servers
				break;

			// Keepalive
			case CAMD_KEEPALIVE:
				srv->keepalive.status = 0;
				//debugf(getdbgflag(DBG_SERVER,0,srv->id)," server(cs378x): Keepalive from (%s:%d)\n", srv->host->name, srv->port);
				if (srv->connection.status<=0) {
					debugf(getdbgflag(DBG_SERVER,0,srv->id)," Connected to cs378x server (%s:%d)\n", srv->host->name, srv->port);
					srv->connection.status = 1;
					srv->connection.time = GetTickCount();
				}
				break;

#ifdef CACHEEX
			// Request Nodeid
			case CAMD_CEX_IDREPLY:
				srv->keepalive.status = 0;
				//debugf(getdbgflag(DBG_SERVER,0,srv->id)," server(cs378x): Got Nodeid from (%s:%d)\n", srv->host->name, srv->port);
				memcpy( srv->nodeid, buf+24, 8);
				if (srv->connection.status<=0) {
					char str[8*3+1];
					array2hex( srv->nodeid, str, 8);
					debugf(getdbgflag(DBG_SERVER,0,srv->id)," Connected to cs378x server (%s:%d), Nodeid = %s\n", srv->host->name, srv->port, str);
					srv->connection.status = 1;
					srv->connection.time = GetTickCount();
				}
				break;

			// push out
			case CAMD_CEX_PUSH:
				if (newlen<60) break;
				memcpy( cw, buf+44, 16);
				if (!acceptDCW(cw)) break;
				//srv->cacheex.totalrep++;
				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 ) {
					srv->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;
				//debugf(getdbgflag(DBG_SERVER,0,srv->id), " CACHEEX PUSH from client(%d) %04x:%06x:%04x (%08x)\n",cli->id,cacheex.caid,cacheex.provid,cacheex.sid,cacheex.hash);
				srv->cacheex.got[0]++;
				int uphop = buf[60];
				if (uphop<10) srv->cacheex.got[uphop]++;
				//
				pthread_mutex_lock( &prg.lockcache );
				int res = cache_setdcw( &cacheex, cw, NO_CYCLE, PEER_CACHEEX_SERVER | srv->id );
				pthread_mutex_unlock( &prg.lockcache );
				if (res&DCW_ERROR) {
					if ( !(res&DCW_SKIP) ) srv->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_SERVER,0,srv->id), " cs378x: push out from server %04x:%06x:%04x|%02x:%08x\n", cacheex.caid,cacheex.provid,cacheex.sid, cacheex.tag,cacheex.hash);
				break;
#endif
		}

	srv->keepalive.time = GetTickCount();
	return NULL;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// CACHEEX MODE 2
void *cs378x_srv_recvmsg_thread(struct server_data *srv)
{
	srv->pid = syscall(SYS_gettid);

	while (!prg.restart) {
		if (srv->handle<=0) {
			srv->pid = 0;
			disconnect_srv(srv);
			return NULL;
		}
		//
		struct pollfd pfd;
		pfd.fd = srv->handle;
		pfd.events = POLLIN | POLLPRI;
		int retval = poll(&pfd, 1, 3009);
		if (retval==0) continue;
		if (retval<0) {
			disconnect_srv(srv);
			return NULL;
		}
		//
		cs378x_srv_recvmsg( srv );
	}
	return NULL;
}


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

int cs378x_connect_srv(struct server_data *srv, int fd)
{

	srv->handle = fd;
	SetSocketTimeout(fd, 5000);
	cs378x_send_keepalive(srv);
	srv->handle = 0;
	// Poll
	struct pollfd pfd;
	pfd.fd = fd;
	pfd.events = POLLIN | POLLPRI;
	if ( poll( &pfd, 1, 3000) <=0 ) return 1;
	//
	unsigned char buf[1024];
	int n = cs378x_recv( fd, srv->ucrc, &srv->decryptkey, buf);
	if (n>0) {
#ifdef CACHEEX
		if ( buf[4] == CAMD_CEX_IDREPLY ) {
			memcpy( srv->nodeid, buf+24, 8);
			//debugf(getdbgflag(DBG_SERVER,0,srv->id)," server(cs378x): Got Nodeid from (%s:%d)\n", srv->host->name, srv->port);
			srv->connection.status = 1;
			srv->connection.time = GetTickCount();
			srv->keepalive.status = 0;
			srv->keepalive.time = GetTickCount();
			srv->handle = fd;
			//
			char str[8*3+1];
			array2hex( srv->nodeid, str, 8);
			debugf(getdbgflag(DBG_SERVER,0,srv->id)," Connected to cs378x server (%s:%d), Nodeid = %s\n", srv->host->name, srv->port, str);
			// Cache EX 2 -> thread
			if (srv->cacheex_mode==2) {
				if (!create_thread(&srv->tid, (threadfn)cs378x_srv_recvmsg_thread, srv)) {
					disconnect_srv(srv);
					return 1;
				}
			}
			return 0;
		}
		else
#endif
		if ( buf[4] == CAMD_KEEPALIVE ) {
			//
			debugf(getdbgflag(DBG_SERVER,0,srv->id)," Connected to cs378x server (%s:%d)\n", srv->host->name, srv->port);
			srv->connection.status = 1;
			srv->connection.time = GetTickCount();
			srv->keepalive.status = 0;
			srv->keepalive.time = GetTickCount();
			srv->handle = fd;
			//SetUP Cards
			int count = 0;
			while (srv->sharelimits[count].caid!=0xFFFF) {
				struct cs_card_data *pcard = malloc( sizeof(struct cs_card_data) );
				memset(pcard, 0, sizeof(struct cs_card_data) );
				pcard->caid = srv->sharelimits[count].caid;
				pcard->nbprov = 1;
				pcard->prov[0] = srv->sharelimits[count].provid;
				pcard->uphops = srv->sharelimits[count].uphops;
				pcard->next = srv->card;
				srv->card = pcard;
				count++;
			}
#ifdef EPOLL_ECM
			pipe_pointer( prg.pipe.ecm[1], PIPE_SRV_CONNECTED, srv );
#else
			pipe_cmd( prg.pipe.ecm[1], PIPE_SRV_CONNECTED );
#endif
			return 0;
		}
	}
	return 1;
}