
#include "httpd.h"
#include "page_impl.h"
#include "rtl837x_common.h"
#include "rtl837x_regs.h"
#include "cmd_parser.h"
#include "rtl837x_flash.h"
#include "uip.h"
#include "html_data.h"

// #define DEBUG
#include "debug.h"

#define SESSION_ID_LENGTH 12
#define SESSION_TIMEOUT 200

#define CMARK_S 6

#pragma codeseg BANK1
#pragma constseg BANK1

extern volatile __xdata uint8_t sfr_data[4];
extern __code uint8_t * __code hex;
extern __code struct f_data f_data[];
extern __code char * __code mime_strings[];
extern __xdata struct flash_region_t flash_region;
extern __xdata uint32_t flash_size;

// Flash buffer to optimize flash writing speed, write_len is the current filling position
extern __xdata uint8_t flash_buf[FLASH_BUF_SIZE];
__xdata uint32_t uptr; // Current flash write position
__xdata uint16_t write_len;

__xdata uint8_t outbuf[TCP_OUTBUF_SIZE];
__xdata uint8_t entry;
__xdata uint16_t slen;
__xdata uint16_t o_idx;
__xdata uint16_t len_left;
__xdata uint16_t cont_len;
__xdata uint32_t cont_addr;

// HTTP header properties
__xdata uint8_t boundary[72];
__xdata uint8_t *content_type = 0;
__xdata uint8_t *session = 0;

// Global variables holding POST state
__xdata uint16_t bindex; // Current index into the boundary
__xdata uint8_t verify_crc;
__xdata uint32_t max_upload;
__xdata uint16_t short_parsed;

__xdata char passwd[21];
__xdata char session_id[SESSION_ID_LENGTH + 1];
__xdata uint8_t authenticated;
__xdata uint32_t now;
__xdata uint8_t *timeptr;
__xdata uint32_t last_session_use;

#define TSTATE_NONE		0
#define TSTATE_TX		1
#define TSTATE_ACKED 		2
#define TSTATE_CLOSED 		3
#define TSTATE_POST 		4
#define TSTATE_MULTIPART	5

extern __xdata uint16_t crc_value;
__xdata uint16_t crc_final;
void crc16(__xdata uint8_t *v) __naked;


inline uint8_t is_separator(uint8_t c)
{
	return c == ' ' || c == '\t' || c == '?' || c == '=';
}


void httpd_init(void) __banked
{
	__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
	// Start listening to port 80
	uip_listen(HTONS(80));
	s->tstate = TSTATE_CLOSED;
}


uint8_t find_entry(__xdata uint8_t *e)
{
	uint8_t i, j;

	for (i = 0; f_data[i].len; i++) {
		j = 0;
		while (f_data[i].file[j] && (f_data[i].file[j] == e[j])) {
			j++;
		}
		if ((!f_data[i].file[j]) && (!e[j])) {
			return i;
		}
	}
	return 0xff;
}


char strcmp(__xdata uint8_t *c, __code uint8_t * __xdata d)
{
	uint8_t i = 0;

	while (d[i] && (d[i] == c[i]))
		i++;

	if (c[i] < d[i])
		return -1;
	else if (c[i] > d[i])
		return 1;
	return 0;
}


bool is_word(__xdata uint8_t *xdata_str_p, __code uint8_t * __xdata code_str_p)
{
	uint8_t u, c;

	while (1) {
		u = *xdata_str_p++;
		c = *code_str_p++;

		if (c == '\0') {
			if (u != '\0' && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r')
				return false;
			return true;
		}

		if (c != u) {
			return false;
		}
	}
}


bool is_url_word_x(__xdata uint8_t *uri_str_p, __xdata uint8_t *src_str_p)
{
	uint8_t u, s;

	while(1) {
		u = *uri_str_p++;
		s = *src_str_p++;

		if (s == '\0') {
			if (u != '\0' && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r')
				return false;
			return true;
		}

		if (u == '%') {
			bool again = true;
			u = 0;

			while(1) {
				// Swap instruction is fine for rotation
				u = (u << 4) | (u >> 4);

				uint8_t p = *uri_str_p++;
				u |= p - '0' < 10 ? (p - '0') : (p - 'A' + 10);

				// force `jbc`-instruction.
				if (again) {
					again = false;
				} else {
					break;
				}
			}
		} else if (u == '+') {
			u = ' ';
		}

		if (s != u) {
			return false;
		}
	}
}


bool is_word_x(__xdata uint8_t *lhs_str_p, __xdata uint8_t *rhs_str_p)
{
	uint8_t u, c;

	while (1) {
		u = *lhs_str_p++;
		c = *rhs_str_p++;

		if (c == '\0') {
			if (u != '\0' && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r')
				return false;
			return true;
		}

		if (c != u) {
			return false;
		}
	}
}


uint8_t parse_short(__xdata uint8_t *p)
{
	uint8_t err = 1;
	uint8_t c = 0;

	short_parsed = 0;
	while(1) {
		c = *p++ - '0';
		if (c > 9) { break; }
		err = 0;
		short_parsed = (short_parsed * 10) + c;
	}
	return err;
}


void send_not_found(void)
{
	slen = strtox(outbuf, "HTTP/1.1 404 Not found\r\nContent-Type: text/html\r\n\r\n" \
			      "<!DOCTYPE HTML PUBLIC>\n<title>404 Not Found</title>\n<h1>Not Found</h1>\n");
}


void send_bad_request(void)
{
	slen = strtox(outbuf, "HTTP/1.1 400 Bad Request\r\nContent-Type: text/html\r\n\r\n" \
			      "<!DOCTYPE HTML PUBLIC>\n<title>400 Bad Request</title>\n<h1>Bad Request</h1>\n");
}


void send_to_login(void)
{
	slen = strtox(outbuf, "HTTP/1.1 302 Found\r\n" \
			      "Location: login.html\r\n\r\n");
}


void send_unauthorized(void)
{
	slen = strtox(outbuf, "HTTP/1.1 401 Unauthorized\r\n\r\n");
}


__xdata uint8_t *skip_boundary(__xdata uint8_t *p)
{
	while (*p) {
		if (is_word_x(p, boundary))
			return p + strlen_x(boundary);
		p++;
	}
	return p;
}


__xdata uint8_t *scan_header(__xdata uint8_t *p)
{
	content_type = 0;
	session = 0;
	authenticated = 0;

	while (*p != '\r' || *(p + 1) != '\n' || *(p + 2) != '\r' || *(p + 3) != '\n') {
		dbg_char(*p);
		if (!*p++)
			break;
		if (is_word(p, "\nContent-Type:"))
			content_type = p + 15;
		else if (is_word(p, "\nCookie:"))
			session = p + 17;
	}
	if (content_type && is_word(content_type, "multipart/form-data; boundary")) {
		dbg_string("\nFound multipart\n");
		content_type += 30;
		uint8_t i = 0;
		while (content_type[i] != '\r' && content_type[i] != '\n') {
			boundary[i + 4] = content_type[i];
			i++;
		}
		// The boundary between parts is "\r\n--" + the boundary given in the header
		boundary[0] = '\r';
		boundary[1] = '\n';
		boundary[2] = '-';
		boundary[3] = '-';
		boundary[i + 4] = 0;
	}

	read_reg_timer(&now);

	if (session) {
		if (now - last_session_use > SESSION_TIMEOUT) {
			dbg_string("Session expired\n");
		} else {
			if (is_word_x(session, session_id))
				authenticated = 1;
			else
				dbg_string("Invalid session cookie!\n");
		}
	}
	return p;
}


void gen_random_bytes(__xdata uint8_t *b, uint8_t bytes)
{
	__xdata uint8_t i = 0;
	while (bytes) {
		if (!i)
			get_random_32();
		b[--bytes] = itohex(sfr_data[i]);
		if (!bytes) { break; }
		b[--bytes] = itohex(sfr_data[i] >> 4 | sfr_data[i] << 4);
		i = (i + 1) & 0x3;
	}
}


/*
 * Reads post data from the http stream and writes it into flash memory
 * Input: the current position in the TCP buffer (uip_appdata)
 * Returns 1: More data to read, 0: Upload complete, all parts reads
 */
uint8_t stream_upload(uint16_t bptr)
{
	__xdata uint8_t *p = uip_appdata;
	__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);

	dbg_string("Stream_upload called: ");
	dbg_short(bptr); dbg_char('\n');

	do {
		if (bptr >= uip_len) {
			s->tstate = TSTATE_POST;
			return 1;
		}
		// Have we reached the end of the part?
		if (!boundary[bindex]) {
			s->tstate = TSTATE_NONE;
			dbg_string("len 2: "); dbg_short(write_len); dbg_char(' ');
			flash_region.addr = uptr;
			flash_region.len = write_len;
			flash_write_bytes(flash_buf);
			uptr += write_len;
			write_len = 0;
			// TODO: This is a bit premature, what about a nice web-page saying the device will reset???
			if (verify_crc) {
				dbg_string("CRC16: "); dbg_short(crc_final); dbg_char('\n');
				if (crc_final == 0xb001) {
					print_string("Checksum OK.\nUpload to flash done, will reset!\n");
					// close connection to avoid retries by browser
					uip_close();
					reset_chip();
				} else {
					print_string("Checksum incorrect! Aborting.\n");
					uip_close();
				}
			}
			// Make sure there is a 0 at the end of the uploaded data
			flash_buf[0] = 0;
			flash_region.addr = uptr;
			flash_region.len = 1;
			flash_write_bytes(flash_buf);
			if (bptr >= uip_len)
				return 0;
			if(!verify_crc)
				//ugly hack to signal connection finished after config upload.
				uip_close();
			return 1;
		}
		if (p[bptr] == boundary[bindex]) {
			if (!bindex)
				crc_final = crc_value;
			crc16(p + bptr);
			bptr++;
			bindex++;
		} else {
			if (bindex) {
				memcpy(flash_buf + write_len, boundary, bindex);
				write_len += bindex;
				bindex = 0;
			}
			crc16(p + bptr);
			flash_buf[write_len++] = p[bptr++];
			if (write_len >= FLASH_PAGE_SIZE) {
				dbg_string("len: "); dbg_short(write_len); dbg_char(' ');
				dbg_string("CRC16: "); dbg_short(crc_value); dbg_char('\n');
				if (uptr % FLASH_SECTOR_SIZE == 0) {
					flash_region.addr = uptr;
					flash_sector_erase();
				}
				flash_region.addr = uptr;
				flash_region.len = FLASH_PAGE_SIZE;
				flash_write_bytes(flash_buf);
				uptr += FLASH_PAGE_SIZE;
				write_len -= FLASH_PAGE_SIZE;

				// Copy the remaining byte for the next page to the beginning of the buffer.
				if (write_len > 0) {
					memcpy(flash_buf, flash_buf + FLASH_PAGE_SIZE, write_len);
				}
			}
			bindex = 0;
		}
	} while(1);
}


void handle_post(void)
{
	__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
	__xdata uint8_t *p = uip_appdata;
	__xdata uint8_t *request_path = p + 6;

	// Was the multipart header sent in multiple packets?
	if (s->tstate != TSTATE_MULTIPART) {
		dbg_string("Is POST\n");
		p += 5;  // Skip post
		// Find end of request path
		while (*p && !is_separator(*p))
			p++;
		*p++ = '\0';

		// Find end of request header
		boundary[0] ='\0';
		p = scan_header(p);
		dbg_string("Boundary: >"); dbg_string_x(boundary); dbg_string("<\n");
		if (!*p || !content_type) {
			dbg_string("Bad Request!\n");
			send_not_found();
			return;
		}
		if (is_word(request_path, "upload")) {
			if (flash_size < FIRMWARE_UPLOAD_START*2)
			{
				print_string("Flash too small for firmware upload!\n");
				send_bad_request();
				return;
			}
			print_string("Firmware upload started.");
			uptr = FIRMWARE_UPLOAD_START;
			verify_crc = 1;
			max_upload = 1024576;
		} else if (is_word(request_path, "config")) {
			if (!authenticated) {
				send_unauthorized();
				return;
			}
			dbg_string("Configuration upload, erasing config mem!\n");
			uptr = CONFIG_START;
			verify_crc = 0;
			max_upload = 2048;
			flash_region.addr = CONFIG_START;
			flash_sector_erase();
		}
		// Check for other POST requests, which are not multipart, below
	} else {
		dbg_string("Multipart request\n");
	}

	if (is_word(request_path, "cmd")) {
		p += 4;
		if (!authenticated) {
			send_unauthorized();
			return;
		}
		execute_commands(p);
		if (err_status != ERR_OK) {
			send_bad_request();
			return;
		}
	} else if (is_word(request_path, "login")) {
		dbg_string("POST login\n");

		if (!content_type || !is_word(content_type, "application/x-www-form-urlencoded")) {
			dbg_string("Bad request!\n");
			send_bad_request();
			return;
		}

		p += 8; // Read also over "pwd="
		if (is_url_word_x(p, passwd)) {
			dbg_string("Password accepted!\n");
			read_reg_timer(&last_session_use);
			gen_random_bytes(session_id, SESSION_ID_LENGTH);
			session_id[SESSION_ID_LENGTH] = '\0';
			slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nLocation: index.html\r\n" \
					      "Set-Cookie: session=");
			for (register uint8_t i = 0; i < SESSION_ID_LENGTH; i++)
				outbuf[slen++] = session_id[i];
			slen += strtox(outbuf + slen, "; SameSite=Strict\r\n\r\n");
		} else {
			dbg_string("Password invalid!\n");
			slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nLocation: login.html\r\n\r\n");
		}
		return;
	} else if (s->tstate == TSTATE_MULTIPART || is_word(request_path, "upload") || is_word(request_path, "config")) {
		dbg_string("POST upload/config request\n");
		if (!authenticated) {
			send_unauthorized();
			return;
		}
		if (!boundary[0]) {
			dbg_string("Bad request, no boundary!\n");
			send_bad_request();
			return;
		}
		// We skip the intial parts as part of the header
		do {
			p = skip_boundary(p);
			if (!*p) {
				s->tstate = TSTATE_MULTIPART;
				return;
			}
			p = scan_header(p);
			if (!*p)
				goto bad_request;
			if (!content_type) // We are waiting for the part with the octet stream
				continue;
		} while (!is_word(content_type, "application/octet-stream"));
		dbg_string("Have content octets\n");
		p += 4; // Skip \r\n\r\n sequence at end of preamble of part

		flash_init(0); // Re-initialize flash for non-DIO operation, otherwise flashing fails
		set_sys_led_state(SYS_LED_FAST);

		crc_value = 0;
		bindex = 0;
		write_len = 0;
		stream_upload(p - uip_appdata);

		dbg_string("Done reading first fragment\n");
		return;

	} else {
		send_not_found();
		return;
	}
	slen = strtox(outbuf, "HTTP/1.1 200 OK\r\n\r\n");
	return;
bad_request:
	send_bad_request();
	return;
}


void httpd_appcall(void)
{
	__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);

	dbg_char('P');
#ifdef DEBUG
	if (uip_newdata())
		write_char('N');
	print_byte(s->tstate);
	write_char(' ');
#endif
	if(uip_connected() && s->tstate == TSTATE_CLOSED) {
		dbg_string("Connected...\n");
		s->tstate = TSTATE_NONE;
	} else if (uip_closed()) {
		dbg_string("Connection closed\n");
		s->tstate = TSTATE_CLOSED;
	} else if (uip_aborted()) {
		dbg_string("Connection aborted\n");
		uip_close();
		s->tstate = TSTATE_CLOSED;
	} else if (uip_poll()) {
		uip_len = 0;
		if (s->tstate == TSTATE_ACKED) {
			dbg_string("Closing because everything has been transmitted\n");
			uip_close();
			s->tstate = TSTATE_CLOSED;
		}
	} else if (uip_acked() && s->tstate == TSTATE_TX) {
		dbg_string("ACK\n");
		if (slen > uip_mss()) {
			slen -= uip_mss();
			o_idx += uip_mss();
		} else {
			slen = 0;
			o_idx += slen;
		}

		s->tstate = TSTATE_ACKED;

		if (slen > uip_mss()) {
			dbg_string("Sending A: "); dbg_short(slen); dbg_char('\n');
			uip_send(outbuf + o_idx, uip_mss());
			s->tstate = TSTATE_TX;
		} else if (slen > 0) {
			dbg_string("Sending B: "); dbg_short(slen); dbg_char('\n');
			uip_send(outbuf + o_idx, slen);
			s->tstate = TSTATE_TX;
		} else if (cont_len) {
			dbg_string("CONT cont_len: "); dbg_short(cont_len);
			slen = cont_len > uip_mss() ? uip_mss() : cont_len;
			if (slen > TCP_OUTBUF_SIZE)
				slen = TCP_OUTBUF_SIZE;
			flash_region.addr = cont_addr;
			flash_region.len = slen;
			flash_read_bulk(outbuf);
			uip_send(outbuf, slen);
			cont_len -= slen;
			cont_addr += slen;
			s->tstate = TSTATE_TX;
		}
	} else if (uip_newdata() && s->tstate == TSTATE_POST) {
		// Check here maxupload by subtracting uip_len and close socekt if fails!
		if (max_upload - uip_len > 0) {
			stream_upload(0);
			write_char('.');
		} else {
			send_bad_request();
			goto do_send;
		}
	} else if (uip_newdata() && s->tstate != TSTATE_TX) {
		cont_len = 0;
		dbg_char('<'); dbg_short(uip_len); dbg_char('\n');
		__xdata uint8_t *p = uip_appdata;
		// Mark end of request header with \0
		p[uip_len] = 0;
#ifdef DEBUG
		while (*p)
			dbg_char(*p++);
		dbg_char('\n');
#endif
		p = uip_appdata;
		if (is_word(p, "POST") || s->tstate == TSTATE_MULTIPART) {
			handle_post();
			// If this is an ongoing post stream, then wait for the next packet
			if (s->tstate == TSTATE_POST || s->tstate == TSTATE_MULTIPART) {
				uip_len = 0;
				return;
			}
			goto do_send;
		}

		if (is_word(p, "GET"))
			dbg_string("GET request ");
		p += 4;
		scan_header(p);
		__xdata uint8_t *q = p;
		while (!is_separator(*p))
			p++;
		*p = '\0';
		dbg_string_x(q);
		dbg_char('\n');

		s->tstate = TSTATE_NONE;
		entry = find_entry(q);
		dbg_string("Entry is: "); dbg_byte(entry); dbg_char('\n');
		if (entry == 0xff) {
			if (!authenticated) {
				dbg_string("Not authorized!\n");
				send_unauthorized();
				goto do_send;
			}
			dbg_string("Not file entry\n");
			if (!strcmp(q, "/status.json")) {
				send_status();
			} else if (!strcmp(q, "/information.json")) {
				send_basic_info();
			} else if (!strcmp(q, "/vlan.json")) {
				parse_short(q + 15);
				send_vlan(short_parsed);
			} else if (is_word(q, "/counters.json")) {
				send_counters(q[20]-'0');
			} else if (is_word(q, "/eee.json")) {
				send_eee();
			} else if (is_word(q, "/bandwidth.json")) {
				send_bandwidth();
			} else if (is_word(q, "/l2.json")) {
				parse_short(q + 13); // e.g.: /l2.json?idx=10
				send_l2(short_parsed);
			} else if (is_word(q, "/l2_del.json")) {
				parse_short(q + 17);
				l2_delete(short_parsed);
			} else if (is_word(q, "/mirror.json")) {
				send_mirror();
			} else if (is_word(q, "/mtu.json")) {
				send_mtu();
			} else if (is_word(q, "/lag.json")) {
				send_lag();
			} else if (is_word(q, "/config")) {
				send_config();
			} else if (is_word(q, "/cmd_log")) {
				send_cmd_log();
			} else if (is_word(q, "/cmd_log_clear")) {
				clear_command_history();
				send_mtu(); // dummy response
			} else if (is_word(q, "/reset")) {
				uip_close();
				delay(1000); //wait for the close packet to be sent, otherwise the browser will retry
				reset_chip();
			} else {
				send_not_found();
			}
		} else {
			dbg_string("Have entry, authenticated: "); dbg_byte(authenticated); dbg_char('\n');
			if (!authenticated && !(f_data[entry].start == FDATA_START_login_html 
						|| f_data[entry].start == FDATA_START_port_svg 
						|| f_data[entry].start == FDATA_START_sfp_svg
						|| f_data[entry].start == FDATA_START_style_css)) {
				send_to_login();
				goto do_send;
			}
			// A web-page is actively accessed, we can reset session time-out
			reg_read_m(RTL837X_REG_SEC_COUNTER);
			timeptr = (uint8_t*)&last_session_use; // last_session_use is Little endian
			timeptr[0] = sfr_data[3]; timeptr[1] = sfr_data[2]; timeptr[2] = sfr_data[1]; timeptr[3] = sfr_data[0];

			slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nContent-Type: ");
			slen += strtox(outbuf + slen, mime_strings[f_data[entry].mime]);
			slen += strtox(outbuf + slen, "; charset=UTF-8\r\nCache-Control: max-age=60, must-revalidate\r\nAccess-Control-Allow-Origin: *\r\nContent-Security-Policy: style-src 'self' 'unsafe-inline'\r\n\r\n");

			len_left = f_data[entry].len;
			if (len_left > (TCP_OUTBUF_SIZE - slen)) {
				cont_len = len_left - (TCP_OUTBUF_SIZE - slen);
				len_left = TCP_OUTBUF_SIZE - slen;
				cont_addr = f_data[entry].start + len_left;
			}
			dbg_string("MIME: "); dbg_string(mime_strings[f_data[entry].mime]); dbg_char('\n');
			flash_region.addr = f_data[entry].start;
			flash_region.len = len_left;
			flash_read_bulk(outbuf + slen);
			slen += len_left;
		}
do_send:
		dbg_string("slen: "); dbg_short(slen); dbg_char('\n');
		o_idx = 0;
		if (slen > uip_mss()) {
			dbg_string("Sending a: "); dbg_short(slen); dbg_char('\n');
			uip_send(outbuf + o_idx, uip_mss());
			dbg_string("Sending a done\n");
		} else {
			dbg_string("Sending b: "); dbg_short(slen); dbg_char('\n');
			uip_send(outbuf + o_idx, slen);
			dbg_string("Sending b done\n");
		}
		s->tstate = TSTATE_TX;
	} else if (uip_rexmit()) { // Connection established, need to rexmit?
		dbg_string("RETRANSMIT requested\n");
		if (slen > uip_mss()) {
			dbg_string("Sending C: "); dbg_short(slen); dbg_char('\n');
			uip_send(outbuf + o_idx, uip_mss());
			dbg_string("Sending C done\n");
		} else if (slen > 0) {
			dbg_string("Sending D: "); dbg_short(slen); dbg_char('\n');
			uip_send(outbuf + o_idx, slen);
			dbg_string("Sending D done\n");
		}
		s->tstate = TSTATE_TX;
		uip_len = 0;
	} else {
		uip_len = 0;
	}
}
