./ MultiCS.r82 / srv-radegast.c
///////////////////////////////////////////////////////////////////////////////
// PROTO
///////////////////////////////////////////////////////////////////////////////

void *rdgd_connect_cli_thread(void *param);
void rdgd_getclimsg();
uint rdgd_check_sendcw();


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

void rdgd_senddcw_cli(struct rdgd_client_data *cli)
{
	unsigned char buf[CWS_NETMSGSIZE];

	if (cli->ecm.status==STAT_DCW_SENT) {
		debugf(0, " +> cw send failed to radegast client (%s), cw already sent\n", ip2string(cli->ip));
		return;
	}
	if (cli->handle==INVALID_SOCKET) {
		debugf(0, " +> cw send failed to radegast client (%s), client disconnected\n", ip2string(cli->ip));
		return;
	}
	if (!cli->ecm.busy) {
		debugf(0, " +> cw send failed to radegast client (%s), no ecm request\n", ip2string(cli->ip));
		return;
	}

	ECM_DATA *ecm = getecmbyid(cli->ecm.id);
	if (ecm) {
		cli->ecm.lastcaid = ecm->caid;
		cli->ecm.lastprov = ecm->provid;
		cli->ecm.lastsid = ecm->sid;
		cli->ecm.lastdecodetime = GetTickCount()-cli->ecm.recvtime;
	}

	if ( ecm && (ecm->dcwstatus==STAT_DCW_SUCCESS) ) {
		cli->ecm.lastdcwsrctype = ecm->dcwsrctype;
		cli->ecm.lastdcwsrcid = ecm->dcwsrcid;
		cli->ecm.laststatus=1;
		cli->ecmok++;
		cli->ecmoktime += GetTickCount()-cli->ecm.recvtime;
		// Send DCW
		buf[0] = 0x02;
		buf[1] = 0x12;
		buf[2] = 0x05;
		buf[3] = 0x10;
		memcpy( &buf[4], ecm->cw, 16 );
		rdgd_message_send( cli->handle, buf, 0x14);
		debugf(0, " => cw to client (%s) ch %04x:%06x:%04x (%dms)\n", ip2string(cli->ip), ecm->caid,ecm->provid,ecm->sid, GetTickCount()-cli->ecm.recvtime);
		cli->lastdcwtime = GetTickCount();
	}
	else { //if (ecm->data->dcwstatus==STAT_DCW_FAILED)
		cli->ecm.laststatus=0;
		cli->ecm.lastdcwsrctype = DCW_SOURCE_NONE;
		cli->ecm.lastdcwsrcid = 0;
		buf[0] = 0x02;
		buf[1] = 0x02;
		buf[2] = 0x04;
		buf[3] = 0x00;
		rdgd_message_send( cli->handle, buf, 4);
		debugf(0, " |> decode failed to client (%s) ch %04x:%06x:%04x (%dms)\n", ip2string(cli->ip), ecm->caid,ecm->provid,ecm->sid, GetTickCount()-cli->ecm.recvtime);
	}
	cli->ecm.busy=0;
	cli->ecm.status = STAT_DCW_SENT;
}



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

void rdgd_disconnect_cli(struct cardserver_data *cs,struct rdgd_client_data *cli)
{
	if (cli)
	if (cli->handle>0) {
		//pthread_mutex_lock(&prg.lockrdgdcli);
		debugf(0, " radegast: client (%s) disconnected\n", ip2string(cli->ip));
		close(cli->handle);
		// Remove
		struct rdgd_client_data *n = cs->radegast.client;
		if (n) {
			if (n==cli) {
				cs->radegast.client = n->next;
				free(cli);
			}
			else {
				while (n->next) {
					if (cli==n->next) {
						n->next = n->next->next;
						free(cli);
						break;
					}
					n = n->next;
				}
			}
		}
		//pthread_mutex_unlock(&prg.lockrdgdcli);
	}
}


void *rdgd_connect_cli_thread(void *param)
{
	int clientsock;
	struct sockaddr_in clientaddr;
	socklen_t socklen = sizeof(struct sockaddr);

	while (1) {
		pthread_mutex_lock(&prg.lockrdgdsrv);

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

		struct cardserver_data *cs = cfg.cardserver;
		while(cs) {
			if (cs->radegast.handle>0) {
				cs->radegast.ipoll = pfdcount;
				pfd[pfdcount].fd = cs->radegast.handle;
				pfd[pfdcount++].events = POLLIN | POLLPRI;
			} else cs->radegast.ipoll = -1;
			cs = cs->next;
		}

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

		if (retval>0) {
			struct cardserver_data *cs = cfg.cardserver;
			while(cs) {
				if ( (cs->radegast.handle>0) && (cs->radegast.ipoll>=0) && (cs->radegast.handle==pfd[cs->radegast.ipoll].fd) ) {
					if ( pfd[cs->radegast.ipoll].revents & (POLLIN|POLLPRI) ) {
						clientsock = accept(cs->radegast.handle, (struct sockaddr*)&clientaddr, &socklen );
						if (clientsock<0) {
							if (errno == EAGAIN || errno == EINTR) continue;
							else {
								debugf(0, " Radegast: Accept failed (errno=%d)\n",errno);
								usleep(1000);
							}
						}
						else {
							SetSocketKeepalive(clientsock);
							SetSocketNoDelay(clientsock);
							SetSoketNonBlocking(clientsock);
							// ADD TO DB
							struct rdgd_client_data *cli = malloc( sizeof(struct rdgd_client_data) );
							memset( cli, 0, sizeof(struct rdgd_client_data) );
							cli->chkrecvtime = 0;
							cli->handle = clientsock; 
							cli->ip = clientaddr.sin_addr.s_addr;
							pthread_mutex_lock(&prg.lockrdgdcli);
							cli->next = cs->radegast.client;
							cs->radegast.client = cli;
							debugf(0, " radegast: client (%s) connected\n", ip2string(cli->ip));
							pthread_mutex_unlock(&prg.lockrdgdcli);
							pipe_wakeup( prg.pipe.ecm[1] );
						}
					}
				}
				cs = cs->next;
			}
		}
		pthread_mutex_unlock(&prg.lockrdgdsrv);
		usleep(3000);
	}
	END_PROCESS = 1;
}



void rdgd_store_ecmclient(struct cardserver_data *cs, ECM_DATA *ecm, struct rdgd_client_data *cli)
{
	cli->ecm.recvtime = GetTickCount();
	cli->ecm.request = ecm;
    cli->ecm.status = STAT_ECM_SENT;
	ecm_addip(ecm, cli->ip);
}

int accept_ecm (struct cardserver_data *cs, uint16_t caid, uint32_t provid, uint16_t sid)
{
	// Check for caid, accept caid=0
	if ( !accept_caid(cs,caid) ) return 0;

	// Check for provid, accept provid==0
	if ( !accept_prov(cs,provid) ) return 0;

	// Check for Accepted sids
	if ( !accept_sid(cs,sid) ) return 0;

	return 1;
}


void rdgd_cli_recvmsg(struct rdgd_client_data *cli, struct cardserver_data *cs)
{
	int len;
	unsigned char buf[300];
	int i;

	if (cli->handle>0) {
		len = rdgd_check_message(cli->handle);
		if (len==0) {
			debugf(0, " radegast: client (%s) read failed %d\n", ip2string(cli->ip),len);
			rdgd_disconnect_cli(cs,cli);
		}
		else if (len==-1) {
			if (!cli->chkrecvtime) cli->chkrecvtime = GetTickCount();
			else if ( (cli->chkrecvtime+300)<GetTickCount() ) {
				debugf(0, " radegast: client (%s) read failed %d\n", ip2string(cli->ip),len);
				rdgd_disconnect_cli(cs,cli);
			}
		}
		else if (len>0) {
			cli->chkrecvtime = 0;
			len = rdgd_message_receive(cli->handle, buf, 0);
			if (len==0) {
				debugf(0, " radegast: client (%s) read failed %d\n", ip2string(cli->ip),len);
				rdgd_disconnect_cli(cs,cli);
			}
			else if (len<0) {
				debugf(0, " radegast: client '%s' read failed %d(%d)\n", ip2string(cli->ip),len,errno);
				rdgd_disconnect_cli(cs,cli);
			}
			else if (len>0) switch ( buf[0] ) {
				case 0x01: // ECM
					cli->lastecmtime = GetTickCount();
					cli->ecmnb++;
					cli->ecm.id = -1; // ECM DENIED
					if (cli->ecm.busy) {
						cli->ecmdenied++;
						rdgd_senddcw_cli(cli);
						break;
					}
					uint16_t caid = 0;
					uint32_t provid = 0;
					unsigned char ecmdata[300];
					memset(ecmdata, 0, sizeof(ecmdata));
					int ecmlen=0;
					int index = 2;
					while (index<len) {
						//entry = buf[index];	
						//len = buf[index+1];
					    switch (buf[index]) {
							case  2: // CAID (upper byte only, oldstyle)
								caid = buf[index+2]<<8;
								break;
							case 10: // CAID
								caid = (buf[index+2]<<8) | buf[index+3];
								break;
							case  3: // ECM DATA
								ecmlen = buf[index+1];
								memcpy( ecmdata, &buf[index+2], ecmlen);
								break;
							case  6: // PROVID (ASCII)
								for (i=0; i<buf[index+1]; i++) provid = (provid<<4) | hexvalue(buf[index+2+i]);
								//provid = hex2int(char *src); // 6
								break;
							case  7: // KEYNR (ASCII), not needed
								break;
							case  8: // ECM PROCESS PID ?? don't know, not needed
								break;
						}
						index += buf[index+1]+2;
					}

					if (!accept_ecm(cs,caid,provid,0)) {
						rdgd_senddcw_cli(cli);
						break;
					}

					// ACCEPTED
					pthread_mutex_lock(&prg.lockecm); //###

					// Search for ECM
					int ecmid = search_ecmdata_dcw( ecmdata,  ecmlen, 0); // dont get failed ecm request from cache
					if ( ecmid!=-1 ) {
						ECM_DATA *ecm=getecmbyid(ecmid);
						ecm->lastrecvtime = GetTickCount();
						//TODO: Add another card for sending ecm
						rdgd_store_ecmclient(cs, ecmid, cli);
						debugf(0, " <- ecm from client (%s) ch %04x:%06x:%04x*\n", ip2string(cli->ip), caid, provid, 0);
						cli->ecm.busy=1;
						cli->ecm.hash = ecm->hash;
					}
					else {
						cs->ecmaccepted++;
						// Setup ECM Request for Server(s)
						ecmid = store_ecmdata(cs, ecmdata, ecmlen, 0, caid, provid);
						ECM_DATA *ecm=getecmbyid(ecmid);
						rdgd_store_ecmclient(cs, ecmid, cli);
						debugf(0, " <- ecm from client (%s) ch %04x:%06x:%04x\n", ip2string(cli->ip), caid, provid, 0);
						cli->ecm.busy=1;
						cli->ecm.hash = ecm->hash;
						if (cs->option.fallowcache && cfg.cache.peer) {
							ecm->waitcache = 1;
							ecm->dcwstatus = STAT_DCW_WAITCACHE;
							ecm->checktime = ecm->recvtime + cs->option.cachetimeout;
							pipe_cache_find(ecm, cs);
						} else ecm->dcwstatus = STAT_DCW_WAIT;
					}
					pthread_mutex_unlock(&prg.lockecm); //###
					wakeup_sendecm();
					break;

				default:
					buf[0] = 0x81;
					buf[1] = 0;
					rdgd_message_send(cli->handle,buf,2);
					//radegast_send(answer);
					//cs_log("unknown request %02X, len=%d", buf[0], buf[1]);
			}
		}
	}
}


// Check sending cw to clients
uint rdgd_check_sendcw()
{
	struct cardserver_data *cs = cfg.cardserver;
	uint restime = GetTickCount() + 10000;
	uint clitime = 0;

	while (cs) {
		if (cs->radegast.handle>0) {
			struct rdgd_client_data *cli = cs->radegast.client;
			uint32_t ticks = GetTickCount();
			while (cli) {
				if ( (cli->handle>0)&&(cli->ecm.busy) ) {
					clitime = ticks+11000;
					// Check for DCW ANSWER
					ECM_DATA *ecm = getecmbyid(cli->ecm.id);
					if (ecm) {
						// Check for FAILED
						if (ecm->dcwstatus==STAT_DCW_FAILED) rdgd_senddcw_cli( cli );
						// Check for SUCCESS
						else if (ecm->dcwstatus==STAT_DCW_SUCCESS) rdgd_senddcw_cli( cli );
						// check for timeout
						else if ( (cli->ecm.recvtime+cs->option.dcw.timeout) < ticks ) rdgd_senddcw_cli( cli ); else clitime = cli->ecm.recvtime+cs->option.dcw.timeout;
					}
					else rdgd_senddcw_cli( cli ); // failed
					if (restime>clitime) restime = clitime;
				}
				cli = cli->next;
			}
		}
		cs = cs->next;
	}
	return (restime+1);
}