#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "crc8.h"
#include "crc32.h"

int last_seq = 0;

enum message_type {
	MTYPE_VERSION = 0x02,
	MTYPE_VERSION_REPLY = 0x03,
	MTYPE_STATUS = 0x04,
	MTYPE_STATUS_REPLY = 0x05,
	MTYPE_HEARTBEAT = 0x07,
	MTYPE_STARTUP = 0x0f,
	MTYPE_RESET = 0x10,
	MTYPE_RESET_REPLY = 0x11,
	MTYPE_LEDS = 0x20,
	MTYPE_BUZZER = 0x22,
	MTYPE_CARD_RELEASE = 0x32,
	MTYPE_ABORT_CARD_HANDLING = 0x46,
	MTYPE_PUT_FILE = 0x96,
	MTYPE_PUT_FILE_REPLY = 0x97,
	MTYPE_GET_FILE = 0x98,
	MTYPE_GET_FILE_REPLY = 0x99,
	MTYPE_DELETE_FILE = 0x9a,
	MTYPE_DELETE_FILE_REPLY = 0x9b,
	MTYPE_FILE_INFO = 0x9c,
	MTYPE_FILE_INFO_REPLY = 0x9d,
	MTYPE_FILE_LIST = 0xa4,
	MTYPE_FILE_LIST_REPLY = 0xa5,
	MTYPE_SET_TIME = 0xaa,
	MTYPE_SET_TIME_REPLY = 0xab,
	MTYPE_ITSO_DATA = 0xac,
	MTYPE_ITSO_DATA_REPLY = 0xad,
	MTYPE_ITSO_CTRL = 0xae,
	MTYPE_ITSO_CTRL_REPLY = 0xaf,
	MTYPE_ITSO_READ = 0xb1,
	MTYPE_APDU_PROX = 0xb4,
	MTYPE_APDU_PROX_REPLY = 0xb5,
	MTYPE_SAM_CTRL = 0xb6,
	MTYPE_SAM_CTRL_REPLY = 0xb7,
	MTYPE_DESFIRE_READ = 0xb9,
	MTYPE_DESFIRE_STATUS = 0xba,
	MTYPE_DESFIRE_STATUS_REPLY = 0xbb,
	MTYPE_DESFIRE_COMMAND = 0xbc,
	MTYPE_DESFIRE_COMMAND_REPLY = 0xbd,
	MTYPE_UNHANDLED_CARD = 0xbe,
	MTYPE_EMV = 0xd0,
	MTYPE_EMV_STATUS = 0xd1,
	MTYPE_PROX_CARD_FUNCTION = 0xe4,
	MTYPE_PROX_CARD_FUNCTION_REPLY = 0xe5,
	MTYPE_SECURITY_SERVICES = 0xe8,
	MTYPE_SECURITY_SERVICES_REPLY = 0xe9,
	MTYPE_LOG = 0xed
};

enum card_type {
	CTYPE_VDV_KA = 0x04,
	CTYPE_MIFARE_CLASSIC = 0x05,
	CTYPE_ISO = 0x06,
	CTYPE_DESFIRE = 0x07,
	CTYPE_GIROGO = 0x08,
	CTYPE_ITSO = 0x09,
	CTYPE_ULTRALIGHT_C = 0x0a
};

enum function_status {
	FUNC_DISABLE = 0x00,
	FUNC_ENABLE = 0x01
};

const char *stringify_msg_type(uint8_t msg_type)
{
	switch (msg_type) {
	case MTYPE_VERSION:
		return "MTYPE_VERSION";
	case MTYPE_VERSION_REPLY:
		return "MTYPE_VERSION_REPLY";
	case MTYPE_STATUS:
		return "MTYPE_STATUS";
	case MTYPE_STATUS_REPLY:
		return "MTYPE_STATUS_REPLY";
	case MTYPE_HEARTBEAT:
		return "MTYPE_HEARTBEAT";
	case MTYPE_STARTUP:
		return "MTYPE_STARTUP";
	case MTYPE_RESET:
		return "MTYPE_RESET";
	case MTYPE_RESET_REPLY:
		return "MTYPE_RESET_REPLY";
	case MTYPE_LEDS:
		return "MTYPE_LEDS";
	case MTYPE_BUZZER:
		return "MTYPE_BUZZER";
	case MTYPE_CARD_RELEASE:
		return "MTYPE_CARD_RELEASE";
	case MTYPE_ABORT_CARD_HANDLING:
		return "MTYPE_ABORT_CARD_HANDLING";
	case MTYPE_PUT_FILE:
		return "MTYPE_PUT_FILE";
	case MTYPE_PUT_FILE_REPLY:
		return "MTYPE_PUT_FILE_REPLY";
	case MTYPE_GET_FILE:
		return "MTYPE_GET_FILE";
	case MTYPE_GET_FILE_REPLY:
		return "MTYPE_GET_FILE_REPLY";
	case MTYPE_DELETE_FILE:
		return "MTYPE_DELETE_FILE";
	case MTYPE_DELETE_FILE_REPLY:
		return "MTYPE_DELETE_FILE_REPLY";
	case MTYPE_FILE_INFO:
		return "MTYPE_FILE_INFO";
	case MTYPE_FILE_INFO_REPLY:
		return "MTYPE_FILE_INFO_REPLY";
	case MTYPE_FILE_LIST:
		return "MTYPE_FILE_LIST";
	case MTYPE_FILE_LIST_REPLY:
		return "MTYPE_FILE_LIST_REPLY";
	case MTYPE_SET_TIME:
		return "MTYPE_SET_TIME";
	case MTYPE_SET_TIME_REPLY:
		return "MTYPE_SET_TIME_REPLY";
	case MTYPE_ITSO_DATA:
		return "MTYPE_ITSO_DATA";
	case MTYPE_ITSO_DATA_REPLY:
		return "MTYPE_ITSO_DATA_REPLY";
	case MTYPE_ITSO_CTRL:
		return "MTYPE_ITSO_CTRL";
	case MTYPE_ITSO_CTRL_REPLY:
		return "MTYPE_ITSO_CTRL_REPLY";
	case MTYPE_ITSO_READ:
		return "MTYPE_ITSO_READ";
	case MTYPE_APDU_PROX:
		return "MTYPE_APDU_PROX";
	case MTYPE_APDU_PROX_REPLY:
		return "MTYPE_APDU_PROX_REPLY";
	case MTYPE_SAM_CTRL:
		return "MTYPE_SAM_CTRL";
	case MTYPE_SAM_CTRL_REPLY:
		return "MTYPE_SAM_CTRL_REPLY";
	case MTYPE_DESFIRE_READ:
		return "MTYPE_DESFIRE_READ";
	case MTYPE_UNHANDLED_CARD:
		return "MTYPE_UNHANDLED_CARD";
	case MTYPE_EMV:
		return "MTYPE_EMV";
	case MTYPE_EMV_STATUS:
		return "MTYPE_EMV_STATUS";
	case MTYPE_PROX_CARD_FUNCTION:
		return "MTYPE_PROX_CARD_FUNCTION";
	case MTYPE_PROX_CARD_FUNCTION_REPLY:
		return "MTYPE_PROX_CARD_FUNCTION_REPLY";
	case MTYPE_SECURITY_SERVICES:
		return "MTYPE_SECURITY_SERVICES";
	case MTYPE_SECURITY_SERVICES_REPLY:
		return "MTYPE_SECURITY_SERVICES_REPLY";
	case MTYPE_DESFIRE_STATUS:
		return "MTYPE_DESFIRE_STATUS";
	case MTYPE_DESFIRE_STATUS_REPLY:
		return "MTYPE_DESFIRE_STATUS_REPLY";
	case MTYPE_DESFIRE_COMMAND:
		return "MTYPE_DESFIRE_COMMAND";
	case MTYPE_DESFIRE_COMMAND_REPLY:
		return "MTYPE_DESFIRE_COMMAND_REPLY";
	case MTYPE_LOG:
		return "MTYPE_LOG";
	default:
		return "UNKNOWN";
	}
}

typedef struct cvend_packet {
	uint8_t magic;
	uint8_t seq;
	uint8_t flags;
	uint8_t msg_type;
	uint16_t msg_len;
	uint8_t hdr_crc;
	uint8_t *msg_data;
	uint32_t msg_crc;
} cvend_packet;

void calc_hdr_crc(cvend_packet *packet)
{
	packet->magic = 0xbc;
	packet->msg_len = (packet->msg_len << 8) + (packet->msg_len >> 8);
	packet->hdr_crc = crc8((uint8_t *)packet, 6);
	packet->msg_len = (packet->msg_len << 8) + (packet->msg_len >> 8);
}

void calc_msg_crc(cvend_packet *packet)
{
	if (packet->msg_type & 0x80)
		packet->msg_crc = crc32(packet->msg_data, packet->msg_len);
	else
		packet->msg_crc = 0;
}

void print_packet(cvend_packet *packet) {
	printf("seq      = %u\n", packet->seq);
	printf("flags    = %x\n", packet->flags);
	printf("msg_type = %s", stringify_msg_type(packet->msg_type));
	printf(" (%02X)\n", packet->msg_type);
	printf("msg_len  = %u\n", packet->msg_len);
	printf("hdr_crc  = %u\n", packet->hdr_crc);
	printf("msg_data = ");
	if (packet->msg_type == MTYPE_LOG) {
		printf("%s", packet->msg_data);
	} else {
		for (uint16_t i = 0; i < packet->msg_len; i++)
			printf("%02x ", packet->msg_data[i]);
	}
	printf("\n");
	printf("msg_crc  = %u\n\n", packet->msg_crc);
}

cvend_packet *cvend_read(FILE *file)
{
	uint8_t buf;
	fread(&buf, 1, 1, file);
	if (buf != 0xbc) {
		printf("failed: %x\n", buf);
		exit(1);
		return NULL;
	}

	cvend_packet *packet = malloc(sizeof(cvend_packet));
	fread(&packet->seq, 1, 1, file);
	fread(&packet->flags, 1, 1, file);
	fread(&packet->msg_type, 1, 1, file);
	fread((uint8_t *)&packet->msg_len + 1, 1, 1, file);
	fread(&packet->msg_len, 1, 1, file);
	fread(&packet->hdr_crc, 1, 1, file);

	packet->msg_data = malloc(packet->msg_len);
	fread(packet->msg_data, 1, packet->msg_len, file);
	if (packet->msg_type & 0x80)
		fread(&packet->msg_crc, 1, 4, file);
	else
		packet->msg_crc = 0;

	return packet;
}

void cvend_write(FILE *file, uint8_t msg_type, uint8_t *msg_data, uint16_t msg_len)
{
	cvend_packet packet;
	packet.seq = ++last_seq;
	packet.flags = 0;
	packet.msg_type = msg_type;
	packet.msg_data = msg_data;
	packet.msg_len = msg_len;
	calc_hdr_crc(&packet);
	calc_msg_crc(&packet);

	fwrite(&packet.magic, 1, 1, file);
	fwrite(&packet.seq, 1, 1, file);
	fwrite(&packet.flags, 1, 1, file);
	fwrite(&packet.msg_type, 1, 1, file);
	fwrite((uint8_t *)&packet.msg_len + 1, 1, 1, file);
	fwrite(&packet.msg_len, 1, 1, file);
	fwrite(&packet.hdr_crc, 1, 1, file);
	fwrite(packet.msg_data, 1, packet.msg_len, file);
	if (packet.msg_type & 0x80)
		fwrite(&packet.msg_crc, 1, 4, file);

	fflush(file);
	print_packet(&packet);
}

void cvend_free(cvend_packet *packet)
{
	free(packet->msg_data);
	free(packet);
}

int main(int argc, char *argv[])
{
	FILE *file = fopen(argc > 1 ? argv[1] : "/dev/ttymxc3", "w+");
	uint8_t desfire_enable[] = { 0x00, CTYPE_DESFIRE, 0x01, FUNC_ENABLE };
	// uint8_t desfire_get_version[] = { 0x60 };
	uint8_t desfire_select_application[] = { 0x5a, 0xF2, 0x10, 0xE0 };
	uint8_t desfire_fid_list[] = { 0xf5, 0x00 };
	uint8_t desfire_read[] = { 0xbd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
	uint8_t desfire_free[] = { 0x6e };

	cvend_write(file, MTYPE_PROX_CARD_FUNCTION, desfire_enable, 4);

	while (1) {
		cvend_packet *packet;
		do {
			packet = cvend_read(file);
		} while (!packet);

		if (packet->msg_type != MTYPE_HEARTBEAT)
			print_packet(packet);

		if (packet->msg_type == MTYPE_DESFIRE_READ) {
			//cvend_write(file, MTYPE_DESFIRE_INTERACT, desfire_get_version, 1);
			cvend_write(file, MTYPE_DESFIRE_COMMAND, desfire_select_application, 4);
			cvend_packet *response;
			do {
				response = cvend_read(file);
			} while (!response);
			print_packet(response);
			cvend_write(file, MTYPE_DESFIRE_COMMAND, desfire_fid_list, 1);
			cvend_free(response);
			cvend_write(file, MTYPE_DESFIRE_COMMAND, desfire_read, 8);
			cvend_write(file, MTYPE_DESFIRE_COMMAND, desfire_free, 1);
		}

		cvend_free(packet);
	}
	fclose(file);
	return 0;
}


