#define MODULE_LOG_PREFIX "camd35"

#include "globals.h"
#if defined MODULE_CAMD35 || defined MODULE_CAMD35_TCP

#include "cscrypt/md5.h"
#include "module-cacheex.h"
#include "module-camd35.h"
#include "module-camd35-cacheex.h"
#include "oscam-aes.h"
#include "oscam-chk.h"
#include "oscam-cache.h"
#include "oscam-client.h"
#include "oscam-ecm.h"
#include "oscam-emm.h"
#include "oscam-net.h"
#include "oscam-string.h"
#include "oscam-reader.h"

//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

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

//used variable ncd_skey for storing remote node id: ncd_skey[0..7] : 8
//bytes node id ncd_skey[8] : 1=valid node id received

#define REQ_SIZE 20 + MAX_ECM_SIZE + 0x34

static int32_t __camd35_send(struct s_client *cl, uint8_t *buf, int32_t buflen, int answer_awaited)
{
	int32_t l, status;
	uint8_t rbuf[4 + REQ_SIZE + 15];
	uint8_t *sbuf = rbuf + 4;

	if(!cl->udp_fd || !cl->crypted)
	{
		return -1; // exit if no fd or aes key not set
	}

	// Fix ECM len > 255
	if(buflen <= 0)
	{
		buflen = (buf[0] == 0) ? (((buf[21] & 0x0F) << 8) | buf[22]) + 3 : buf[1];
	}
	l = 20 + (((buf[0] == 3) || (buf[0] == 4)) ? 0x34 : 0) + buflen;

	memcpy(rbuf, cl->ucrc, 4);
	memcpy(sbuf, buf, l);
	memset(sbuf + l, 0xFF, 15); // set unused space to 0xFF for newer camd3's
	i2b_buf(4, crc32(0L, sbuf + 20, buflen), sbuf + 4);
	l = boundary(4, l);

	cs_log_dump_dbg(cl->typ == 'c' ? D_CLIENT : D_READER, sbuf, l, "send %d bytes to %s", l, username(cl));

	aes_encrypt_idx(cl->aes_keys, sbuf, l);

	if(cl->is_udp)
	{
		status = sendto(cl->udp_fd, rbuf, l + 4, 0, (struct sockaddr *)&cl->udp_sa, cl->udp_sa_len);
		if(status == -1)
		{
			set_null_ip(&SIN_GET_ADDR(cl->udp_sa));
		}
	}
	else
	{
		status = send(cl->udp_fd, rbuf, l + 4, 0);

		if(cl->typ == 'p' && cl->reader)
		{
			if(status == -1)
			{
				network_tcp_connection_close(cl->reader, "can't send");
			}
		}
		else if(cl->typ == 'c')
		{
			if(status == -1)
			{
				cs_disconnect_client(cl);
			}
		}
	}

	if(status != -1)
	{
		if(cl->reader && answer_awaited)
		{
			cl->reader->last_s = time(NULL);
		}

		if(cl->reader && !answer_awaited)
		{
			cl->reader->last_s = cl->reader->last_g = time(NULL);
		}

		cl->last = time(NULL);
	}

	return status;
}

int32_t camd35_send(struct s_client *cl, uint8_t *buf, int32_t buflen)
{
	// send command and set sending time because we await response
	return __camd35_send(cl, buf, buflen, 1);
}

int32_t camd35_send_without_timeout(struct s_client *cl, uint8_t *buf, int32_t buflen)
{
	// send command and do NOT set sending time because we DON'T await response
	return __camd35_send(cl, buf, buflen, 0);
}

static int32_t camd35_auth_client(struct s_client *cl, uint8_t *ucrc)
{
	int32_t rc = 1, no_delay = 1;
	uint32_t crc;
	struct s_auth *account;
	uint8_t md5tmp[MD5_DIGEST_LENGTH];

	if(cl->upwd[0])
	{
		return memcmp(cl->ucrc, ucrc, 4) ? 1 : 0;
	}

	cl->crypted = 1;

	crc = ((ucrc[0] << 24) | (ucrc[1] << 16) | (ucrc[2] << 8) | ucrc[3]) & 0xffffffffL;

	for(account = cfg.account; account && (!cl->upwd[0]); account = account->next)
	{
		if(crc == crc32(0L, MD5((uint8_t *)account->usr, cs_strlen(account->usr), md5tmp), MD5_DIGEST_LENGTH))
		{
			rc = cs_auth_client(cl, account, NULL);
			if(!rc)
			{
				memcpy(cl->ucrc, ucrc, 4);
				cs_strncpy((char *)cl->upwd, account->pwd, sizeof(cl->upwd));
				if(!aes_set_key_alloc(&cl->aes_keys, (char *) MD5(cl->upwd, cs_strlen((char *)cl->upwd), md5tmp)))
				{
					return 1;
				}

#ifdef CS_CACHEEX
				if(cl->account->cacheex.mode < 2)
#endif
				if(!cl->is_udp && cl->tcp_nodelay == 0)
				{
					setsockopt(cl->udp_fd, IPPROTO_TCP, TCP_NODELAY, (void *)&no_delay, sizeof(no_delay));
					cl->tcp_nodelay = 1;
				}

				return 0;
			}
		}
	}

	return (rc);
}

static int32_t camd35_recv(struct s_client *client, uint8_t *buf, int32_t l)
{
	int32_t rc, s, rs, n = 0, buflen = 0, len = 0;

	for(rc = rs = s = 0; !rc; s++)
	{
		switch(s)
		{
			case 0:
			{
				if(!client->udp_fd)
				{
					return -9;
				}

				if(client->is_udp && client->typ == 'c')
				{
					rs = recv_from_udpipe(buf);
				}
				else
				{
					// read minimum packet size (4 byte ucrc + 32 byte data)
					// to detect packet size (tcp only)

					//rs = cs_recv(client->udp_fd, buf, client->is_udp ? l : 36, 0);

					if(client->is_udp)
					{
						while (1)
						{
							rs = cs_recv(client->udp_fd, buf, l, 0);
							if(rs < 0)
							{
								if(errno == EINTR)
								{
									continue; // try again in case of interrupt
								}

								if(errno == EAGAIN)
								{
									continue; // EAGAIN needs select procedure again
								}

								cs_log_dbg(client->typ == 'c' ? D_CLIENT : D_READER, "ERROR: %s (errno=%d %s)",
											__func__, errno, strerror(errno));
								break;
							}
							else
							{
								break;
							}
						}
					}
					else
					{
						int32_t tot = 36, readed = 0;
						rs = 0;

						do
						{
							readed = cs_recv(client->udp_fd, buf + rs, tot, 0);
							if(readed < 0)
							{
								if(errno == EINTR)
								{
									continue; // try again in case of interrupt
								}

								if(errno == EAGAIN)
								{
									continue; // EAGAIN needs select procedure again
								}

								cs_log_dbg(client->typ == 'c' ? D_CLIENT : D_READER, "ERROR: %s (errno=%d %s)",
											__func__, errno, strerror(errno));
								break;
							}

							if(readed == 0) // nothing to read left!
							{
								rc = -5;
								break;
							}

							if(readed > 0) // received something, add it!
							{
								tot -= readed;
								rs += readed;
							}
						}
						while(tot != 0);
					}
				}

				if(rs < 36)
				{
					if(rc != -5)
					{
						rc = -1;
					}

					goto out;
				}

				break;
			}

			case 1:
			{
				switch(camd35_auth_client(client, buf))
				{
					case 0:
#ifdef CS_CACHEEX_AIO
						if(!client->c35_extmode)
						{
							camd35_send_extmode(client, false);
							client->c35_extmode = 1;
						}
#endif
						break; // ok

					case 1:
						rc = -2;
						break; // unknown user

					default:
						rc = -9;
						break; // error's from cs_auth()
				}

				memmove(buf, buf + 4, rs -= 4);
				break;
			}

			case 2:
			{
				aes_decrypt(client->aes_keys, buf, rs);

				if(rs != boundary(4, rs))
				{
					cs_log_dbg(client->typ == 'c' ? D_CLIENT : D_READER,
								"WARNING: packet size has wrong decryption boundary");
				}

				n = (buf[0] == 3) ? 0x34 : 0;

				// Fix for ECM request size > 255 (use ecm length field)
				if(buf[0] == 0)
				{
					buflen = (((buf[21] & 0x0F) << 8) | buf[22]) + 3;
				}
				else if(
#ifdef CS_CACHEEX_AIO
					buf[0] == 0x40 || buf[0] == 0x41 || buf[0] == 0x42 ||
#endif
					buf[0] == 0x3D || buf[0] == 0x3E || buf[0] == 0x3F
				) // cacheex-push
				{
					buflen = buf[1] | (buf[2] << 8);
				}
				else
				{
					buflen = buf[1];
				}

				n = boundary(4, n + 20 + buflen);

				if(!(client->is_udp && client->typ == 'c') && (rs < n) && ((n - 32) > 0))
				{
					//len = cs_recv(client->udp_fd, buf+32, n-32, 0); // read the rest of the packet

					int32_t tot = n - 32;
					int32_t readed = 0;
					len = 0;

					do
					{
						readed = cs_recv(client->udp_fd, buf + 32 + len, tot, 0); // read the rest of the packet
						if(readed < 0)
						{
							if(errno == EINTR)
							{
								continue; // try again in case of interrupt
							}

							if(errno == EAGAIN)
							{
								continue; // EAGAIN needs select procedure again
							}

							cs_log_dbg(client->typ == 'c' ? D_CLIENT : D_READER, "ERROR: %s (errno=%d %s)",
										__func__, errno, strerror(errno));
							break;
						}

						if(readed == 0) // nothing to read left
						{
							break;
						}

						if(readed > 0) // received something, add it
						{
							tot -= readed;
							len += readed;
						}
					}
					while(tot != 0);


					if(len > 0)
					{
						rs += len;
						aes_decrypt(client->aes_keys, buf + 32, len);
					}

					if(len < 0)
					{
						rc = -1;
						goto out;
					}
				}

				cs_log_dump_dbg(client->typ == 'c' ? D_CLIENT : D_READER, buf, rs, "received %d bytes from %s",
								rs, remote_txt());

				if(n < rs)
				{
					cs_log_dbg(client->typ == 'c' ? D_CLIENT : D_READER, "ignoring %d bytes of garbage", rs - n);
				}
				else if(n > rs)
				{
					rc = -3;
				}

				break;
			}

			case 3:
			{
				if(crc32(0L, buf + 20, buflen) != b2i(4, buf + 4))
				{
					rc = -4;
				}

				if(!rc)
				{
					rc = n;
				}

				break;
			}
		}
	}

out:
	if((rs > 0) && ((rc == -1) || (rc == -2)))
	{
		cs_log_dump_dbg(client->typ == 'c' ? D_CLIENT : D_READER, buf, rs,
						"received %d bytes from %s (native)", rs, remote_txt());
	}

	if(rc >= 0)
	{
		client->last = time(NULL); // last client action is now
	}

	switch(rc)
	{
		//case 0:
		//	break;

		case -1:
			cs_log("packet is too small (received %d bytes, expected %d bytes)", rs, l);
			break;

		case -2:
			if(cs_auth_client(client, 0, "unknown user"))
			{
				cs_disconnect_client(client);
			}
			break;

		case -3:
			cs_log("incomplete request!");
			break;

		case -4:
			cs_log("checksum error (wrong password?)");
			break;

		case -5:
			cs_log_dbg(client->typ == 'c' ? D_CLIENT : D_READER, "connection closed");
			break;

		//default:
		//	cs_log_dbg(D_TRACE, "camd35_recv returns rc=%d", rc);
		//	break;
	}

	return (rc);
}

/*
 * server functions
 */

static void camd35_request_emm(ECM_REQUEST *er)
{
	int32_t i;
	time_t now;
	uint8_t mbuf[1024];
	struct s_client *cl = cur_client();
	struct s_reader *aureader = NULL, *rdr = NULL;

	if(er->selected_reader && !er->selected_reader->audisabled
		&& ll_contains(cl->aureader_list, er->selected_reader))
	{
		aureader = er->selected_reader;
	}

	if(!aureader && cl->aureader_list)
	{
		LL_ITER itr = ll_iter_create(cl->aureader_list);

		while((rdr = ll_iter_next(&itr)))
		{
			if(emm_reader_match(rdr, er->caid, er->prid))
			{
				aureader = rdr;
				break;
			}
		}
	}

	if(!aureader)
	{
		return; // TODO
	}

	uint16_t au_caid = aureader->caid;

	// Bulcrypt has two caids and aureader->caid can't be used.
	// Use ECM_REQUEST caid for AU.
	if(!au_caid && caid_is_bulcrypt(er->caid))
	{
		au_caid = er->caid;
	}

	time(&now);

	if(!memcmp(cl->lastserial, aureader->hexserial, 8))
	{
		if(llabs(now - cl->last) < 180)
		{
			return;
		}
	}

	memcpy(cl->lastserial, aureader->hexserial, 8);
	cl->last = now;

	if(au_caid)
	{
		cl->disable_counter = 0;

		cs_log("%s emm-request sent (reader=%s, caid=%04X, auprovid=%06X)", username(cur_client()),
				aureader->label, au_caid, aureader->auprovid ? aureader->auprovid : b2i(4, aureader->prid[0]));
	}
	else if(cl->disable_counter > 2)
	{
		return;
	}
	else
	{
		cl->disable_counter++;
	}

	memset(mbuf, 0, sizeof(mbuf));
	mbuf[2] = mbuf[3] = 0xFF; // must not be zero
	i2b_buf(2, er->srvid, mbuf + 8);

	// override request provid with auprovid if set in CMD05
	if(aureader->auprovid)
	{
		if(aureader->auprovid != er->prid)
		{
			i2b_buf(4, aureader->auprovid, mbuf + 12);
		}
		else
		{
			i2b_buf(4, er->prid, mbuf + 12);
		}
	}
	else
	{
		i2b_buf(4, er->prid, mbuf + 12);
	}

	i2b_buf(2, er->pid, mbuf + 16);
	mbuf[0] = 5;
	mbuf[1] = 111;

	if(au_caid)
	{
		mbuf[39] = 1; // no. caids
		mbuf[20] = au_caid >> 8; // caid's (max 8)
		mbuf[21] = au_caid & 0xFF;
		memcpy(mbuf + 40, aureader->hexserial, 6); // serial now 6 bytes
		mbuf[47] = aureader->nprov;

		for(i = 0; i < aureader->nprov; i++)
		{
			if(caid_is_betacrypt(au_caid) || caid_is_irdeto(au_caid))
			{
				mbuf[48 + (i * 5)] = aureader->prid[i][0];
				memcpy(&mbuf[50 + (i * 5)], &aureader->prid[i][1], 3);
			}
			else
			{
				mbuf[48 + (i * 5)] = aureader->prid[i][2];
				mbuf[49 + (i * 5)] = aureader->prid[i][3];
				memcpy(&mbuf[50 + (i * 5)], &aureader->sa[i][0], 4); // for conax we need at least 4 bytes
			}
		}

		// we think client/server protocols should deliver
		// all information, and only readers should discard EMM
		mbuf[128] = (aureader->blockemm & EMM_GLOBAL && !(aureader->saveemm & EMM_GLOBAL)) ? 0 : 1;
		mbuf[129] = (aureader->blockemm & EMM_SHARED && !(aureader->saveemm & EMM_SHARED)) ? 0 : 1;
		mbuf[130] = (aureader->blockemm & EMM_UNIQUE && !(aureader->saveemm & EMM_UNIQUE)) ? 0 : 1;
		mbuf[127] = (aureader->blockemm & EMM_UNKNOWN && !(aureader->saveemm & EMM_UNKNOWN)) ? 0 : 1;
	}
	else // disable emm
	{
		mbuf[20] = mbuf[39] = mbuf[40] = mbuf[47] = mbuf[49] = 1;
	}

	memcpy(mbuf + 10, mbuf + 20, 2);
	camd35_send(cl, mbuf, 0); // send with data-len 111 for camd3 > 3.890
	mbuf[1]++;
	camd35_send(cl, mbuf, 0); // send with data-len 112 for camd3 < 3.890
}

static void camd35_send_dcw(struct s_client *client, ECM_REQUEST *er)
{
	uint8_t *buf;
	buf = er->src_data; // get orig request

	if(!buf)
	{
		rdr_log(client->reader, "ERROR: src_data missing");
		return;
	}

	if(er->rc == E_INVALID && !client->c35_suppresscmd08) // send normal CMD08
	{
		buf[0] = 0x08;
		buf[1] = 2;
		memset(buf + 20, 0, buf[1]);
		buf[22] = er->rc; // put rc in byte 22 - hopefully don't break legacy camd3
	}
	else if(er->rc == E_STOPPED) // send sleep CMD08
	{
		buf[0] = 0x08;
		buf[1] = 2;
		buf[20] = 0;
		buf[21] = 0xFF;

		cs_log("%s stop request send", client->account->usr);
	}
	else
	{
		// Send CW
		if((er->rc < E_NOTFOUND) || (er->rc == E_FAKE))
		{
			if(buf[0] == 3)
			{
				memmove(buf + 20 + 16, buf + 20 + buf[1], 0x34);
			}

#ifdef CS_CACHEEX_AIO
			if(er->localgenerated && client->c35_extmode > 1)
			{
				buf[0] += 0x51; // ecm response with lg-flag
			}
			else
			{
#endif
				buf[0]++; // ecm response (CMD01 or CMD04)
#ifdef CS_CACHEEX_AIO
			}
#endif
			buf[1] = 16;
			camd35_cacheex_init_dcw(client, er);
			memcpy(buf + 20, er->cw, buf[1]);
		}
		else
		{
			// Send old CMD44 to prevent cascading
			// problems with older mpcs/oscam versions
			buf[0] = 0x44;
			buf[1] = 0;
		}
	}

	camd35_send(client, buf, 0);
	camd35_request_emm(er);
}

static void camd35_process_ecm(uint8_t *buf, int buflen)
{
	ECM_REQUEST *er;

	if(!buf || buflen < 23)
	{
		return;
	}

	uint16_t ecmlen = SCT_LEN((&buf[20]));

	if(ecmlen > MAX_ECM_SIZE || ecmlen + 20 > buflen || ecmlen < 4)
	{
		return;
	}

	if(!(er = get_ecmtask()))
	{
		return;
	}

	er->ecmlen = ecmlen;

	if(!cs_malloc(&er->src_data, 0x34 + 20 + er->ecmlen))
	{
		NULLFREE(er);
		return;
	}

	memcpy(er->src_data, buf, 0x34 + 20 + er->ecmlen); // save request
	er->srvid = b2i(2, buf + 8);
	er->caid = b2i(2, buf + 10);
	er->prid = b2i(4, buf + 12);
	//er->idx = b2i(2, buf + 16); // ecmtask idx (see camd35_recv_chk)
	memcpy(er->ecm, buf + 20, er->ecmlen);

	get_cw(cur_client(), er);
}

static void camd35_process_emm(uint8_t *buf, int buflen, int emmlen)
{
	EMM_PACKET epg;

	if(!buf || buflen < 20 || emmlen + 20 > buflen)
	{
		return;
	}

	memset(&epg, 0, sizeof(epg));

	epg.emmlen = emmlen;
	if(epg.emmlen < 3 || epg.emmlen > MAX_EMM_SIZE)
	{
		return;
	}

	memcpy(epg.caid, buf + 10, 2);
	memcpy(epg.provid, buf + 12 , 4);
	memcpy(epg.emm, buf + 20, epg.emmlen);

	do_emm(cur_client(), &epg);
}

int32_t camd35_tcp_connect(struct s_client *cl)
{
	if(cl->is_udp) // check for udp client
	{
		if(!IP_ISSET(SIN_GET_ADDR(cl->udp_sa))) // check ip is set
		{
			if(!(hostResolve(cl->reader))) // no ip -> try to resolve ip of client
			{
				network_tcp_connection_close(cl->reader, "no ip");
				return 0;
			}
		}
	}

	if(!cl->reader->tcp_connected) // client not connected
	{
		int32_t handle = 0;
		handle = network_tcp_connection_open(cl->reader); // try to connect
		if(handle < 0) // got no handle -> error!
		{
			cl->reader->last_s = 0; // set last send to zero
			cl->reader->last_g = 0; // set last receive to zero
			cl->last = 0; // set last client action to zero

			return 0;
		}

		cl->reader->tcp_connected = 1;
		cl->reader->card_status = CARD_INSERTED;
		cl->reader->last_s = time(NULL); // reset last send
		cl->reader->last_g = time(NULL); // reset last receive
		cl->last = time(NULL); // reset last client action
		cl->pfd = cl->udp_fd = handle;
	}

	if(!cl->udp_fd) // Check if client has no handle -> error
	{
		return 0;
	}

	// check if client reached timeout
	if(cl->reader->tcp_rto && (cl->reader->last_s - cl->reader->last_g > cl->reader->tcp_rto))
	{
		if(!cl->is_udp) // tcp on timeout disconnect reader
		{
			network_tcp_connection_close(cl->reader, "rto");
			return 0;
		}
		else //udp check to discover ip change on dynamic ip servers
		{
			IN_ADDR_T last_ip;
			IP_ASSIGN(last_ip, cl->ip);

			if(!hostResolve(cl->reader))
			{
				network_tcp_connection_close(cl->reader, "no ip");
				return 0;
			}

			if(!IP_EQUAL(last_ip, cl->ip))
			{
				network_tcp_connection_close(cl->reader, "ip change");
				return 0;
			}
		}
	}

	return 1; // all ok
}

/*
 * client functions
 */

static void camd35_send_keepalive(struct s_client *cl)
{
	if(cl->reader)
	{
		if(camd35_tcp_connect(cl))
		{
			if(cacheex_get_rdr_mode(cl->reader) > 1)
			{
				camd35_cacheex_push_request_remote_id(cl);
				return;
			}

			uint8_t rbuf[32]; // minimal size
			memset(rbuf, 0, sizeof(rbuf));

			rbuf[0] = 55;
			rbuf[1] = 1;
			rbuf[2] = 0;

			camd35_send(cl, rbuf, 1); // send adds +20
		}
	}
}

static void camd35_send_keepalive_answer(struct s_client *cl)
{
	if(check_client(cl) && cl->account)
	{
		uint8_t rbuf[32]; // minimal size
		memset(rbuf, 0, sizeof(rbuf));

		rbuf[0] = 55;
		rbuf[1] = 1;
		rbuf[2] = 0;

		camd35_send(cl, rbuf, 1); // send adds +20
	}
}

static int32_t camd35_client_init(struct s_client *cl)
{
	uint8_t md5tmp[MD5_DIGEST_LENGTH];
	int32_t no_delay = 1;

	cs_strncpy((char *)cl->upwd, cl->reader->r_pwd, sizeof(cl->upwd));
	i2b_buf(4, crc32(0L, MD5((uint8_t *)cl->reader->r_usr, cs_strlen(cl->reader->r_usr), md5tmp), 16), cl->ucrc);

	if(!aes_set_key_alloc(&cl->aes_keys, (char *)MD5(cl->upwd, cs_strlen((char *)cl->upwd), md5tmp)))
	{
		return 1;
	}

	cl->crypted = 1;

	rdr_log(cl->reader, "proxy %s:%d", cl->reader->device, cl->reader->r_port);

	if(!cl->is_udp && cacheex_get_rdr_mode(cl->reader) < 2)
	{
		setsockopt(cl->udp_fd, IPPROTO_TCP, TCP_NODELAY, (void *)&no_delay, sizeof(no_delay));
	}

	if(cl->reader->keepalive)
	{
		camd35_send_keepalive(cl);
	}

	if(cacheex_get_rdr_mode(cl->reader) == 2
#ifdef CS_CACHEEX_AIO
		 || cacheex_get_rdr_mode(cl->reader) == 1
#endif
	)
	{
		camd35_cacheex_send_push_filter(cl, 2);
#ifdef CS_CACHEEX_AIO
		camd35_cacheex_feature_request(cl);
#endif
	}

#ifdef CS_CACHEEX_AIO
	if(!cl->c35_extmode)
	{
		camd35_send_extmode(cl, false);
		cl->c35_extmode = 1;
	}
#endif

	return 0;
}

static void camd35_idle(void)
{
	struct s_client *cl = cur_client();

	if(!cl->reader)
	{
		return;
	}

	if(cl->reader->keepalive)
	{
		camd35_send_keepalive(cl);
	}
	else if(cl->reader->tcp_ito > 0) // only check if user added an inactivity timeout
	{
		// inactivity timeout check
		time_t now;
		int32_t time_diff;

		time(&now);
		time_diff = llabs(now - cl->reader->last_s);

		if(time_diff > cl->reader->tcp_ito)
		{
			if(check_client(cl) && cl->reader->tcp_connected && cl->reader->ph.type == MOD_CONN_TCP)
			{
				rdr_log_dbg(cl->reader, D_READER, "inactive_timeout, close connection (fd=%d)", cl->pfd);
				network_tcp_connection_close(cl->reader, "inactivity");
			}
			else
			{
				cl->reader->last_s = now;
			}
		}
	}
}

static void *camd35_server(struct s_client *client, uint8_t *mbuf, int32_t n)
{
	if(!client || !mbuf)
	{
		return NULL;
	}

	if(client->reader)
	{
		client->reader->last_g = time(NULL); // last receive is now

		if(mbuf[0] == 6 || mbuf[0] == 19) // check for emm command
		{
			// fixup: last send is now (if client is only
			// sending emms, connection would be dropped!)
			client->reader->last_s = time(NULL);
		}

		rdr_log(client->reader, "SERVER last = %d, last_s = %d, last_g = %d",
				(int) client->last, (int) client->reader->last_s, (int) client->reader->last_g);
	}

	client->last = time(NULL); // last client action is now

	switch(mbuf[0])
	{
		case 0: // ECM
		case 3: // ECM (cascading)
			camd35_process_ecm(mbuf, n);
			break;

		case 6: // EMM
		case 19: // EMM
			if(n > 2)
			{
				camd35_process_emm(mbuf, n, mbuf[1]);
			}
			break;

		case 55:
			camd35_send_keepalive_answer(client); // keepalive msg
			break;
#ifdef CS_CACHEEX_AIO
		case 0x43:
			break;
		case 0x50:
			client->c35_extmode = 2;
			break;
#endif
		default:
			if(!camd35_cacheex_server(client, mbuf))
			{
				cs_log("unknown [cs357x/cs378x] command from %s! (%d) n=%d", username(client), mbuf[0], n);
			}
	}

	return NULL;
}

static int32_t camd35_send_ecm(struct s_client *client, ECM_REQUEST *er)
{
	static const char *typtext[] = { "ok", "invalid", "sleeping" };

	if(client->stopped)
	{
		if(er->srvid == client->lastsrvid && er->caid == client->lastcaid)
		{
			cs_log("%s is stopped - requested by server (%s)", client->reader->label, typtext[client->stopped]);
			return -1;
		}
		else
		{
			client->stopped = 0;
		}
	}

	client->lastsrvid = er->srvid;
	client->lastcaid = er->caid;
	client->lastpid = er->pid;

	if(!camd35_tcp_connect(client))
	{
		return -1;
	}

	client->reader->card_status = CARD_INSERTED; // for udp

	uint8_t *buf;
	if(!cs_malloc(&buf, 20 + er->ecmlen + 15))
	{
		return -1;
	}

	memset(buf, 0, 20);
	memset(buf + 20, 0xFF, er->ecmlen + 15);

	buf[1] = er->ecmlen;
	i2b_buf(2, er->srvid, buf + 8);
	i2b_buf(2, er->caid, buf + 10);
	i2b_buf(4, er->prid, buf + 12);
	i2b_buf(2, er->idx, buf + 16);
	buf[18] = 0xFF;
	buf[19] = 0xFF;
	memcpy(buf + 20, er->ecm, er->ecmlen);

	int32_t rc = (camd35_send(client, buf, 0) < 1) ? -1 : 0;

	NULLFREE(buf);
	return rc;
}

static int32_t camd35_send_emm(EMM_PACKET *ep)
{
	uint8_t *buf;
	struct s_client *cl = cur_client();

	if(!camd35_tcp_connect(cl))
	{
		return 0;
	}

	cl->reader->card_status = CARD_INSERTED; // for udp

	if(!cs_malloc(&buf, ep->emmlen + 20 + 15))
	{
		return -1;
	}

	memset(buf, 0, 20);
	memset(buf + 20, 0xFF, ep->emmlen + 15);

	buf[0] = 0x06;
	buf[1] = ep->emmlen;
	memcpy(buf + 10, ep->caid, 2);
	memcpy(buf + 12, ep->provid, 4);
	memcpy(buf + 20, ep->emm, ep->emmlen);

	int32_t rc = (camd35_send_without_timeout(cl, buf, 0) < 1) ? 0 : 1;

	NULLFREE(buf);
	return rc;
}

static int32_t camd35_recv_chk(struct s_client *client, uint8_t *dcw, int32_t *rc, uint8_t *buf, int32_t rc2 __attribute__((unused)))
{
	uint16_t idx;
	static const char *typtext[] = { "ok", "invalid", "sleeping" };
	struct s_reader *rdr = client->reader;

	rdr->last_g = time(NULL); // last receive is now

	// reading CMD05 Emm request and set serial
	if(buf[0] == 0x05 && buf[1] == 111)
	{
		//cs_log("CMD05: %s", cs_hexdump(1, buf, buf[1], tmp, sizeof(tmp)));

		rdr->nprov = 0; // reset if number changes on reader change
		rdr->nprov = buf[47];
		rdr->caid = b2i(2, buf + 20);

		int32_t i;
		for(i = 0; i < rdr->nprov; i++)
		{
			if(caid_is_betacrypt(rdr->caid) || caid_is_irdeto(rdr->caid))
			{
				rdr->prid[i][0] = buf[48 + (i * 5)];
				memcpy(&rdr->prid[i][1], &buf[50 + (i * 5)], 3);
			}
			else
			{
				rdr->prid[i][2] = buf[48 + (i * 5)];
				rdr->prid[i][3] = buf[49 + (i * 5)];
				memcpy(&rdr->sa[i][0], &buf[50 + (i * 5)], 4);
			}
		}

		memcpy(rdr->hexserial, buf + 40, 6);
		rdr->hexserial[6] = 0;
		rdr->hexserial[7] = 0;

		if(cfg.getblockemmauprovid)
		{
			rdr->blockemm = 0;
			rdr->blockemm |= (buf[128] == 1) ? 0 : EMM_GLOBAL;
			rdr->blockemm |= (buf[129] == 1) ? 0 : EMM_SHARED;
			rdr->blockemm |= (buf[130] == 1) ? 0 : EMM_UNIQUE;
			rdr->blockemm |= (buf[127] == 1) ? 0 : EMM_UNKNOWN;
			rdr->auprovid = b2i(4, buf + 12);
		}

		cs_log("%s CMD05 AU request for caid: %04X auprovid: %06X",
				rdr->label, rdr->caid, rdr->auprovid);
	}

	bool rc_invalid = 0;

	if(buf[0] == 0x08 && ((rdr->ph.type == MOD_CONN_TCP && !cfg.c35_tcp_suppresscmd08)
		|| (rdr->ph.type == MOD_CONN_UDP && !cfg.c35_udp_suppresscmd08)))
	{
		if(buf[21] == 0xFF)
		{
			client->stopped = 2; // server says sleep
			rdr->card_status = NO_CARD;
		}
		else
		{
			if(config_enabled(WITH_LB) && cfg.lb_mode)
			{
				rc_invalid = 1;
			}
			else
			{
				client->stopped = 1; // server says invalid
				rdr->card_status = CARD_FAILURE;
			}
		}

		cs_log("%s CMD08 (%02X - %d) stop request by server (%s)",
				rdr->label, buf[21], buf[21], typtext[client->stopped]);
	}

	if(camd35_cacheex_recv_chk(client, buf))
	{
		return -1;
	}

	if(buf[0] == 55) // keepalive answer
	{
		return -1;
	}

#ifdef CS_CACHEEX_AIO
	if(buf[0] == 0x50)
	{
		client->c35_extmode = 2;

		if(buf[12] != 1) // answer
			camd35_send_extmode(client, true);
		return -1;
	}
#endif
	// CMD44: old reject command introduced in mpcs
	// keeping this for backward compatibility
	if((buf[0] != 1) && (buf[0] != 0x44) && (buf[0] != 0x08)
#ifdef CS_CACHEEX_AIO
			 && (buf[0] != 0x51)
#endif
	)
	{
		return -1;
	}

	idx = b2i(2, buf + 16);
	camd35_cacheex_recv_ce1_cwc_info(client, buf, idx);

	*rc = ((buf[0] != 0x44) && (buf[0] != 0x08));

	if(rc_invalid)
	{
		*rc = 2; // INVALID sent by CMD08
	}
#ifdef CS_CACHEEX_AIO
	if(buf[0] == 0x51 || buf[0] == 0x54)	// lg-flag
	{
		*rc = 0x86;
		client->c35_extmode = 2;
	}
#endif

	memcpy(dcw, buf + 20, 16);

	return idx;
}

#ifdef CS_CACHEEX_AIO
void camd35_send_extmode(struct s_client *cl, bool answer)
{
	uint8_t rbuf[32]; // minimal size
	memset(rbuf, 0, sizeof(rbuf));

	rbuf[0] = 0x50;
	rbuf[1] = 1;
	rbuf[2] = 0;
	if(answer)
		rbuf[12] = 1;

	if(cl->reader)
	{
		if(camd35_tcp_connect(cl))
		{
			camd35_send(cl, rbuf, 1); // send adds +20
		}
	}
	else if(check_client(cl) && cl->account)
	{
		camd35_send(cl, rbuf, 1); // send adds +20
	}
}
#endif

/*
 * module definitions
 */
#ifdef MODULE_CAMD35
void module_camd35(struct s_module *ph)
{
	ph->ptab.nports = 1;
	ph->ptab.ports[0].s_port = cfg.c35_port;

	ph->desc = "cs357x";
	ph->type = MOD_CONN_UDP;
	ph->large_ecm_support = 1;
	ph->listenertype = LIS_CAMD35UDP;
	IP_ASSIGN(ph->s_ip, cfg.c35_srvip);
	ph->s_handler = camd35_server;
	ph->recv = camd35_recv;
	ph->send_dcw = camd35_send_dcw;
	ph->c_init = camd35_client_init;
	ph->c_recv_chk = camd35_recv_chk;
	ph->c_send_ecm = camd35_send_ecm;
	ph->c_send_emm = camd35_send_emm;
	ph->c_idle = camd35_idle;
	camd35_cacheex_module_init(ph);
	ph->num = R_CAMD35;
}
#endif

#ifdef MODULE_CAMD35_TCP
void module_camd35_tcp(struct s_module *ph)
{
	ph->desc = "cs378x";
	ph->type = MOD_CONN_TCP;
	ph->large_ecm_support = 1;
	ph->listenertype = LIS_CAMD35TCP;
	ph->ptab = cfg.c35_tcp_ptab;
	IP_ASSIGN(ph->s_ip, cfg.c35_tcp_srvip);
	ph->s_handler = camd35_server;
	ph->recv = camd35_recv;
	ph->send_dcw = camd35_send_dcw;
	ph->c_init = camd35_client_init;
	ph->c_recv_chk = camd35_recv_chk;
	ph->c_send_ecm = camd35_send_ecm;
	ph->c_send_emm = camd35_send_emm;
	ph->c_idle = camd35_idle;
	camd35_cacheex_module_init(ph);
	ph->num = R_CS378X;
}
#endif

#endif
