From ba9bc2e8458da93368e9f80b214085e6ace094c3 Mon Sep 17 00:00:00 2001 From: logicog Date: Fri, 31 Oct 2025 17:47:36 +0100 Subject: [PATCH 01/18] Initial web-files for system settings and configuration management --- html/config | 1 + html/config.js | 41 ++++++++++++++++++++++++++++++ html/navigation.js | 1 + html/style.css | 8 +++++- html/system.html | 36 ++++++++++++++++++++++++++ html/system.js | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 149 insertions(+), 1 deletion(-) create mode 120000 html/config create mode 100644 html/config.js create mode 100644 html/system.html create mode 100644 html/system.js diff --git a/html/config b/html/config new file mode 120000 index 0000000..f1a6cc6 --- /dev/null +++ b/html/config @@ -0,0 +1 @@ +../config.txt \ No newline at end of file diff --git a/html/config.js b/html/config.js new file mode 100644 index 0000000..92a07e5 --- /dev/null +++ b/html/config.js @@ -0,0 +1,41 @@ +var configInterval = Number(); +var configuration = {}; + +function parseConf(c){ + var a = s.split(/\r\n|\n/); + for (var l = 0; l < a.length; l++) { + console.log(l + ' --> ' + a[l]); + } +} + +function fetchConfig() { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + s = xhttp.responseText; + console.log("CONFIG: ", s); + parseConf(s); + clearInterval(configInterval); + } + } + xhttp.open("GET", `/config`, true); + xhttp.send(); +} + +function fetchCmdLog() { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + s = xhttp.responseText; + console.log("CMD-Log: ", s); + parseConf(s); + clearInterval(configInterval); + } + } + xhttp.open("GET", `/cmd_log`, true); + xhttp.send(); +} + +window.addEventListener("load", function() { + configInterval = setInterval(fetchConfig, 1000); +}); diff --git a/html/navigation.js b/html/navigation.js index 92366c2..8b88bb8 100644 --- a/html/navigation.js +++ b/html/navigation.js @@ -5,5 +5,6 @@ document.getElementById('sidebar').innerHTML = + "
  • Mirroring
  • " + "
  • Port Aggregation
  • " + "
  • EEE
  • " + + "
  • System Settings
  • " + "
  • Firmware Update
  • "; diff --git a/html/style.css b/html/style.css index b1cdecc..f0f1a5a 100644 --- a/html/style.css +++ b/html/style.css @@ -42,6 +42,9 @@ input[type=submit] { padding: 8px 16px;background-color:#aaf;color:#000; margin- input[type=submit]:hover { background-color: #226; color: white;} button {padding: 8px; background-color:#99f; color:#000;} button:hover { background-color: #226; color: white;} +.action{padding: 8px 16px;margin-top: 2em;margin-right: 3em; background-color: #aaf;color: #000;} +.action:hover { background-color: #226; color: white;} + .psel { position: absolute; opacity: 0; @@ -73,4 +76,7 @@ object { .isSFP{ opacity: .4; background-color: #660;} .isNOK{ color: #900;} .isOK{ color: #090;} -.action{padding: 8px 16px;margin-top: 2em;margin-right: 3em; background-color: #aaf;color: #000;} +.ip{padding:8px 16px;margin-bottom: 1em;margin-left: 1em} +.row {display: flex;} +.rcol {flex: 90%;} +.lcol {flex: 10%;} diff --git a/html/system.html b/html/system.html new file mode 100644 index 0000000..728b300 --- /dev/null +++ b/html/system.html @@ -0,0 +1,36 @@ + + + + + System Settings + + + +
    +
    +

    System Settings

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + When updating the above settings, remember to point your browser to the new IP afterwards:
    +
    +

    + Save all current settings to Flash:
    + + +
    + + + + + diff --git a/html/system.js b/html/system.js new file mode 100644 index 0000000..a368f9f --- /dev/null +++ b/html/system.js @@ -0,0 +1,63 @@ +var systemInterval = Number(); +const ips = ["ip", "nm", "gw"]; + +function checkIp(ip) { + const ipv4 = /^(\d{1,3}\.){3}\d{1,3}$/; + if (!ipv4.test(ip)) {alert(`Invalid ip:${ip}`); return false }; + return true; +} + +async function ipSub() { + for (let i=0;i<3;i++) { + if (!checkIp(document.getElementById(ips[i]).value)) + return; + } + for (let i=0; i<3;i++){ + var cmd = ips[i]+' '+document.getElementById(ips[i]).value; + try { + const response = await fetch('/cmd', { + method: 'POST', + body: cmd + }); + console.log('Completed!', response); + fetchIP(); + } catch(err) { + console.error(`Error: ${err}`); + } + } +} + +async function flashSave() { + fetchConfig(); + fetchCmdLog(); + return; + try { + const response = await fetch('/save', { + method: 'POST', + body: cmd + }); + console.log('Completed!', response); + } catch(err) { + console.error(`Error: ${err}`); + } +} + +function fetchIP() { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + const s = JSON.parse(xhttp.responseText); + console.log("IP: ", s); + document.getElementById("ip").value=s.ip_address; + document.getElementById("nm").value=s.ip_netmask; + document.getElementById("gw").value=s.ip_gateway; + clearInterval(systemInterval); + } + } + xhttp.open("GET", `/information.json`, true); + xhttp.send(); +} + +window.addEventListener("load", function() { + systemInterval = setInterval(fetchIP, 1000); +}); From 9d4ba57cd02709b2e264cb5d33ee1e241cf517a2 Mon Sep 17 00:00:00 2001 From: logicog Date: Sat, 15 Nov 2025 12:11:00 +0100 Subject: [PATCH 02/18] Add support for chunking of served files This removes the limitation that a file served from FLASH memory needs to be smaller than the TCP buffer size. Now we serve up to the TCP buffer size in the first go and then remember what parts still need to be sent. As soon as the previous package has been acked, the next chunk of data is copied from flash into the TCP transmit buffer and gets sent. We take care only to send the current TCP window size so no further fragmentation needs to happen, thus optimizing transmission of the remainder. --- httpd/httpd.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index fb4d81a..d8deeb6 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -33,6 +33,8 @@ __xdata uint16_t slen; __xdata uint16_t o_idx; __xdata uint16_t mpos; __xdata uint16_t len_left; +__xdata uint16_t cont_len; +__xdata uint32_t cont_addr; // HTTP header properties __xdata uint8_t boundary[72]; @@ -386,17 +388,28 @@ void httpd_appcall(void) if (slen > uip_mss()) { print_string("Sending A: "); print_short(slen); write_char('\n'); uip_send(outbuf + o_idx, uip_mss()); - print_string("Sending A done\n"); s->tstate = TSTATE_TX; } else if (slen > 0) { print_string("Sending B: "); print_short(slen); write_char('\n'); uip_send(outbuf + o_idx, slen); - print_string("Sending B done\n"); + s->tstate = TSTATE_TX; + } else if (cont_len) { + print_string("CONT cont_len: "); print_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) { stream_upload(0); } else if (uip_newdata() && s->tstate != TSTATE_TX) { + cont_len = 0; write_char('<'); print_short(uip_len); write_char('\n'); __xdata uint8_t *p = uip_appdata; // Mark end of request header with \0 @@ -452,7 +465,11 @@ void httpd_appcall(void) slen += strtox(outbuf + slen, mime_strings[f_data[entry].mime]); slen += strtox(outbuf + slen, "\r\nCache-Control: max-age=2592000\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; + } print_string("MIME: "); print_string(mime_strings[f_data[entry].mime]); write_char('\n'); flash_region.addr = f_data[entry].start; flash_region.len = len_left; From 4d915a0d806b94c7589ae4c9edb4727e6d984001 Mon Sep 17 00:00:00 2001 From: logicog Date: Sun, 16 Nov 2025 20:09:11 +0100 Subject: [PATCH 03/18] Add support for command history This adds a command history, both for the CLI as well as for commands sent by the web-interface. They are written to a ring buffer in order to work safely with a relatively small amount of RAM The buffer is used to read the current in-RAM configuration from the switch by the web-interface. It could also be used to implement cmd history via cursor up/down in the CLI. --- cmd_parser.c | 61 +++++++++++++++++++++++++++++++++++++++--------- rtl837x_common.h | 11 ++++++++- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index c667381..32c67b1 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -5,9 +5,6 @@ // #define DEBUG // #define REGDBG 1 -#define CONFIG_START 0x70000 -#define CONFIG_LEN 0x1000 - #include "rtl837x_common.h" #include "rtl837x_port.h" #include "rtl837x_flash.h" @@ -54,12 +51,16 @@ __xdata uint8_t cmd_available; __xdata uint8_t l; __xdata uint8_t line_ptr; __xdata char is_white; +__xdata char save_cmd; __xdata uint8_t ip[4]; #define N_WORDS SBUF_SIZE __xdata signed char cmd_words_b[N_WORDS]; +__xdata uint8_t cmd_history[CMD_HISTORY_SIZE]; +__xdata uint16_t cmd_history_ptr; + // Maps the physical port (starting from 0) to the logical port __code uint8_t phys_to_log_port[6] = { 4, 5, 6, 7, 3, 8 @@ -706,21 +707,52 @@ void cmd_parser(void) __banked if (cmd_compare(0, "version")) { print_sw_version(); } + if (cmd_compare(0, "history")) { + __xdata uint16_t p = (cmd_history_ptr + 1) & CMD_HISTORY_MASK; + __xdata uint8_t found_begin = 0; + print_string("History ptr: "); + print_short(cmd_history_ptr); write_char('\n'); + while (p != cmd_history_ptr) { + print_short(p); write_char(' '); + if (!cmd_history[p] || cmd_history[p] == '\n') + found_begin = 1; + if (found_begin && cmd_history[p]) + write_char(cmd_history[p]); + p = (p + 1) & CMD_HISTORY_MASK; + } + } + if (save_cmd) { + uint8_t i; + for (i = 0; i < N_WORDS; i++) { + if (cmd_words_b[i] < 0) + break; + } + if (i < N_WORDS) { + i = cmd_words_b[--i]; + cmd_history_ptr = (cmd_history_ptr + i) & CMD_HISTORY_MASK; + __xdata uint16_t p = cmd_history_ptr; + cmd_history[cmd_history_ptr++] = '\n'; + do { + i--; + cmd_history[--p & CMD_HISTORY_MASK] = cmd_buffer[i]; + } while (i); + } + } } } #define FLASH_READ_BURST_SIZE 0x100; void execute_config(void) __banked { - memcpyc(flash_buf, "test", 5); - print_string_x(flash_buf); + __xdata uint32_t pos = CONFIG_START; + __xdata uint16_t len_left = CONFIG_LEN; - __xdata uint32_t pos = CONFIG_START; - __xdata uint16_t len_left = CONFIG_LEN; - do { + save_cmd = 0; + + do { flash_region.addr = pos; flash_region.len = FLASH_READ_BURST_SIZE; - write_char('-'); print_long(flash_region.addr); write_char(':'); print_short(flash_region.len); write_char('\n'); + write_char('-'); print_long(flash_region.addr); write_char(':'); print_short(flash_region.len); write_char('\n'); flash_read_bulk(flash_buf); @@ -737,7 +769,7 @@ void execute_config(void) __banked if (cmd_idx && !cmd_tokenize()) cmd_parser(); if (c == 0) - return; + goto config_done; break; } @@ -748,5 +780,12 @@ void execute_config(void) __banked len_left -= FLASH_READ_BURST_SIZE; pos += FLASH_READ_BURST_SIZE; - } while(len_left); + } while(len_left); + +config_done: + // Start saving commands to cmd_history + save_cmd = 1; + for (cmd_history_ptr = 0; cmd_history_ptr < CMD_HISTORY_SIZE; cmd_history_ptr++) + cmd_history[cmd_history_ptr] = 0; + cmd_history_ptr = 0; } diff --git a/rtl837x_common.h b/rtl837x_common.h index 29f6536..cab8b96 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -47,6 +47,15 @@ #define IS_SFP(port) (i == maxPort || i == 3) #endif +#define CONFIG_START 0x70000 +#define CONFIG_LEN 0x1000 +#define CODE0_SIZE 0x4000 +#define CODE_BANK_SIZE 0xc000 + +// Constants for the circular command buffer, the size must be 2^n +#define CMD_HISTORY_SIZE 0x400 +#define CMD_HISTORY_MASK (CMD_HISTORY_SIZE - 1) + /** * Representation of a 48-bit Ethernet address. */ @@ -94,6 +103,6 @@ uint16_t strlen_x(register __xdata const char *s); uint16_t strtox(register __xdata uint8_t *dst, register __code const char *s); void tcpip_output(void); void print_string_x(__xdata char *p); - +uint8_t read_flash(uint8_t bank, __code uint8_t *addr); #endif From f485102d2619cdc4757a3ea74cc63a4206c51dfe Mon Sep 17 00:00:00 2001 From: logicog Date: Sun, 16 Nov 2025 20:12:41 +0100 Subject: [PATCH 04/18] Add fetching of configuration file and current cmd history --- httpd/httpd.c | 53 ++++++++++++++++++++++++++++++++++++----------- httpd/page_impl.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++ httpd/page_impl.h | 2 ++ 3 files changed, 95 insertions(+), 12 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index d8deeb6..346e59d 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -42,7 +42,8 @@ __xdata uint8_t *content_type = 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; #define TSTATE_NONE 0 @@ -232,14 +233,21 @@ uint8_t stream_upload(uint16_t bptr) uptr += write_len; write_len = 0; // TODO: This is a bit premature, what about a nice web-page saying the device will reset??? - print_string("CRC16: "); print_short(crc_final); write_char('\n'); - if (crc_final == 0xb001) { - print_string("Checksum OK."); - } else { - print_string("Checksum incorrect!"); + if (verify_crc) { + print_string("CRC16: "); print_short(crc_final); write_char('\n'); + if (crc_final == 0xb001) { + print_string("Checksum OK."); + } else { + print_string("Checksum incorrect!"); + } + print_string("Upload to flash done, will reset!\n"); + reset_chip(); } - print_string("Upload to flash done, will reset!\n"); - reset_chip(); + // 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; return 1; @@ -309,8 +317,8 @@ void handle_post(void) cmd_buffer[i] = '\0'; if (i) cmd_available = 1; - } else if (is_word(request_path, "upload")) { - print_string("POST upload request\n"); + } else if (is_word(request_path, "upload") || is_word(request_path, "config")) { + print_string("POST upload/config request\n"); if (!boundary[0]) { print_string("Bad request, no boundary!\n"); send_bad_request(); @@ -330,7 +338,18 @@ void handle_post(void) print_string("Have content octets\n"); p += 4; // Skip \r\n\r\n sequence at end of preamble of part - uptr = FIRMWARE_UPLOAD_START; + if (is_word(request_path, "upload")) { + uptr = FIRMWARE_UPLOAD_START; + verify_crc = 1; + max_upload = 1024576; + } else { + print_string("Configuration upload, erasing config mem!\n"); + uptr = CONFIG_START; + verify_crc = 0; + max_upload = 2048; + flash_region.addr = CONFIG_START; + flash_sector_erase(); + } crc_value = 0; bindex = 0; write_len = 0; @@ -407,7 +426,13 @@ void httpd_appcall(void) s->tstate = TSTATE_TX; } } else if (uip_newdata() && s->tstate == TSTATE_POST) { - stream_upload(0); + // Check here maxupload by subtracting uip_len and close socekt if fails! + if (max_upload - uip_len > 0) { + stream_upload(0); + } else { + send_bad_request(); + goto do_send; + } } else if (uip_newdata() && s->tstate != TSTATE_TX) { cont_len = 0; write_char('<'); print_short(uip_len); write_char('\n'); @@ -456,6 +481,10 @@ void httpd_appcall(void) send_eee(); } else if (is_word(q, "/mirror.json")) { send_mirror(); + } else if (is_word(q, "/config")) { + send_config(); + } else if (is_word(q, "/cmd_log")) { + send_cmd_log(); } else { send_not_found(); } diff --git a/httpd/page_impl.c b/httpd/page_impl.c index b787d4a..efedfb7 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -4,6 +4,7 @@ #include "../rtl837x_common.h" #include "../rtl837x_regs.h" #include "../rtl837x_port.h" +#include "../rtl837x_flash.h" #include "uip.h" #include "../html_data.h" #include @@ -15,6 +16,8 @@ extern __xdata uint8_t outbuf[TCP_OUTBUF_SIZE]; extern __xdata uint16_t slen; +extern __xdata uint16_t cont_len; +extern __xdata uint32_t cont_addr; extern __code uint8_t * __code hex; extern __xdata uip_ipaddr_t uip_hostaddr, uip_draddr, uip_netmask; extern __code struct uip_eth_addr uip_ethaddr; @@ -30,7 +33,13 @@ extern __xdata uint8_t isRTL8373; extern __xdata uint8_t sfp_pins_last; extern __xdata uint8_t vlan_names[VLAN_NAMES_SIZE]; +extern __xdata uint8_t cmd_history[CMD_HISTORY_SIZE]; +extern __xdata uint16_t cmd_history_ptr; + +extern __xdata struct flash_region_t flash_region; + __code uint8_t * __code HTTP_RESPONCE_JSON = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"; +__code uint8_t * __code HTTP_RESPONCE_TXT = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n"; /* Convert only the lower nibble to ascii HEX char. For convenience the upper nibble is masked out. @@ -371,3 +380,46 @@ void send_status(void) char_to_html(']'); } } + + +void send_config(void) +{ + print_string("send_config called\n"); + __xdata uint32_t pos = CONFIG_START; // 70000 , 6c000 / 0xc000 = 9 + + extern __xdata uint16_t len_left = CONFIG_LEN; + slen = strtox(outbuf, HTTP_RESPONCE_TXT); + while (read_flash((CONFIG_START-CODE0_SIZE) / CODE_BANK_SIZE + 1, + (__code uint8_t *) (((CONFIG_START + len_left - CODE0_SIZE) % CODE_BANK_SIZE) + CODE0_SIZE + len_left)) == 0xff) { + print_short(len_left); + len_left--; + } + len_left++; + + if (len_left > (TCP_OUTBUF_SIZE - slen)) { + cont_len = len_left - (TCP_OUTBUF_SIZE - slen); + len_left = TCP_OUTBUF_SIZE - slen; + cont_addr = len_left; + } + flash_region.addr = CONFIG_START; + flash_region.len = len_left; + flash_read_bulk(outbuf + slen); + slen += len_left; +} + +void send_cmd_log(void) +{ + print_string("send_cmd_log called\n"); + slen = strtox(outbuf, HTTP_RESPONCE_TXT); + __xdata uint16_t p = (cmd_history_ptr + 1) & CMD_HISTORY_MASK; + __xdata uint8_t found_begin = 0; + print_string("History ptr: "); + print_short(cmd_history_ptr); write_char('\n'); + while (p != cmd_history_ptr) { + if (!cmd_history[p] || cmd_history[p] == '\n') + found_begin = 1; + if (found_begin && cmd_history[p]) + outbuf[slen++] = cmd_history[p]; + p = (p + 1) & CMD_HISTORY_MASK; + } +} diff --git a/httpd/page_impl.h b/httpd/page_impl.h index 9692989..455b276 100644 --- a/httpd/page_impl.h +++ b/httpd/page_impl.h @@ -7,5 +7,7 @@ void send_vlan(register uint16_t vlan); void send_basic_info(void); void send_eee(void); void send_mirror(void); +void send_config(void); +void send_cmd_log(void); #endif From 00a415c755c741f1b3ab95eb7a6daa5d0dea5374 Mon Sep 17 00:00:00 2001 From: logicog Date: Tue, 18 Nov 2025 08:14:28 +0100 Subject: [PATCH 05/18] Add filtering logic for configuration commands --- html/config.js | 70 +++++++++++++++++++++++++++++------------------- html/system.html | 4 +-- html/system.js | 29 ++++++++++++++------ 3 files changed, 65 insertions(+), 38 deletions(-) diff --git a/html/config.js b/html/config.js index 92a07e5..2d48021 100644 --- a/html/config.js +++ b/html/config.js @@ -1,41 +1,55 @@ var configInterval = Number(); -var configuration = {}; +var configuration = []; +const conf_cmds = [ + /ip\s+(\d{1,3}\.){3}\d{1,3}/, /gw\s+(\d{1,3}\.){3}\d{1,3}/, /netmask\s+(\d{1,3}\.){3}\d{1,3}/, + /eee(\s+\d)?\s+(on|off)/, /mirror(\s+(\d|10))(\s+(\d|10)(t|r)?)+/, /vlan\s+(\d{1,4})(\s+(\d|10)(t|u)?)+/ +]; +const conf_overwrite = [ + /ip/, /gw/, /netmask/, /eee\s+\w+/, /eee(\s+\w)/, /mirror/, /vlan\s+(\d{1,4})/ +]; -function parseConf(c){ +function parseConf(s){ var a = s.split(/\r\n|\n/); for (var l = 0; l < a.length; l++) { + if (!a[l].length || a[l] == "\n" || a[l] == "\r\n") + continue; console.log(l + ' --> ' + a[l]); + var ignore = true; + for (const x of conf_cmds) + if (x.test(a[l])) ignore = false; + if (ignore) continue; + for (const x of conf_overwrite) { + if (x.test(a[l])) { + console.log("Match ", x, " to ", a[l]); + m = a[l].match(x); + console.log("Starts with ", m[0]); + configuration = configuration.filter(item => !(item.startsWith(m[0]))); + } + } + configuration.push(a[l]); } + console.log("Configuration now:"); + for (const x of configuration) { console.log(x); } } -function fetchConfig() { - var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function() { - if (this.readyState == 4 && this.status == 200) { - s = xhttp.responseText; - console.log("CONFIG: ", s); - parseConf(s); - clearInterval(configInterval); - } +async function fetchConfig() { + try { + const response = await fetch('/config'); + console.log("CONFIG: ", response); + const t = await response.text(); + return t; + } catch(err) { + console.error("Error: ", err); } - xhttp.open("GET", `/config`, true); - xhttp.send(); } -function fetchCmdLog() { - var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function() { - if (this.readyState == 4 && this.status == 200) { - s = xhttp.responseText; - console.log("CMD-Log: ", s); - parseConf(s); - clearInterval(configInterval); - } +async function fetchCmdLog() { + try { + const response = await fetch('/cmd_log'); + console.log("CMD-Log: ", response); + const t = await response.text(); + return t; + } catch(error) { + console.error("Error: ", err); } - xhttp.open("GET", `/cmd_log`, true); - xhttp.send(); } - -window.addEventListener("load", function() { - configInterval = setInterval(fetchConfig, 1000); -}); diff --git a/html/system.html b/html/system.html index 728b300..57e6a74 100644 --- a/html/system.html +++ b/html/system.html @@ -14,8 +14,8 @@
    -
    -
    +
    +
    diff --git a/html/system.js b/html/system.js index a368f9f..50beec5 100644 --- a/html/system.js +++ b/html/system.js @@ -1,5 +1,5 @@ var systemInterval = Number(); -const ips = ["ip", "nm", "gw"]; +const ips = ["ip", "netmask", "gw"]; function checkIp(ip) { const ipv4 = /^(\d{1,3}\.){3}\d{1,3}$/; @@ -27,14 +27,14 @@ async function ipSub() { } } -async function flashSave() { - fetchConfig(); - fetchCmdLog(); - return; +async function sendConfig(c) { + const form = new FormData(); + form.append("MAX_FILE_SIZE", "4096"); + form.append("configuration", new Blob([c], {type: "application/octet-stream"})); try { - const response = await fetch('/save', { + const response = await fetch('/config', { method: 'POST', - body: cmd + body: form }); console.log('Completed!', response); } catch(err) { @@ -42,6 +42,19 @@ async function flashSave() { } } +async function flashSave() { + fetchConfig().then((s) => { + parseConf(s); + fetchCmdLog().then((s) => { + parseConf(s); + var body = ""; + for (const x of configuration) { body = body + x + "\n"; } + console.log("CONFIGURATION to save: ", body); + sendConfig(body); + }); + }); +} + function fetchIP() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { @@ -49,7 +62,7 @@ function fetchIP() { const s = JSON.parse(xhttp.responseText); console.log("IP: ", s); document.getElementById("ip").value=s.ip_address; - document.getElementById("nm").value=s.ip_netmask; + document.getElementById("netmask").value=s.ip_netmask; document.getElementById("gw").value=s.ip_gateway; clearInterval(systemInterval); } From 69110531b7e8ddfd8e3fc944a3fa488bde06fd65 Mon Sep 17 00:00:00 2001 From: logicog Date: Tue, 18 Nov 2025 08:14:52 +0100 Subject: [PATCH 06/18] Add support for reading configuration and command history --- tools/httpd_sim.c | 77 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/tools/httpd_sim.c b/tools/httpd_sim.c index 0cd3e03..e4ce843 100644 --- a/tools/httpd_sim.c +++ b/tools/httpd_sim.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "httpd_sim.h" #include @@ -20,6 +21,10 @@ char upload_buffer[4194304]; // 4MB char *content_type = NULL; char boundary[72]; +char cmd_history[1024][256]; +uint16_t cmd_ptr = 0; +char *uploaded_config = NULL; +int uploaded_config_len; char is_word(char *c, char *d) { @@ -202,6 +207,32 @@ void send_mirror(int s) } +void send_cmd_log(int s) +{ + char *header = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain; charset=UTF-8\r\n\r\n"; + write(s, header, strlen(header)); + + for (int i = 0; i < cmd_ptr; i++) { + write(s, cmd_history[i], strlen(cmd_history[i])); + write(s, "\n", 1); + printf("%d: %s\n", i, cmd_history[i]); + } +} + + +void send_config(int s) +{ + char *header = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain; charset=UTF-8\r\n\r\n"; + printf("Sending uploaded config\n"); + write(s, header, strlen(header)); + printf("Uploaded config len: %d\n", uploaded_config_len); + printf(">%s<", uploaded_config); + write(s, uploaded_config, uploaded_config_len); +} + + struct Server serverConstructor(int port, void (*launch)(struct Server *server)) { struct Server server; @@ -289,6 +320,11 @@ char *skip_boundary(char *p) return p; } +void print_cmd_history(void) +{ + for (int i = 0; i < cmd_ptr; i++) + printf("%d: %s\n", i, cmd_history[i]); +} void launch(struct Server *server) { @@ -324,7 +360,7 @@ void launch(struct Server *server) send_eee(new_socket); goto done; } - if (!strncmp(&buffer[4], "/information.json", 12)) { + if (!strncmp(&buffer[4], "/information.json", 17)) { printf("Status request\n"); send_basic_info(new_socket); goto done; @@ -340,6 +376,17 @@ void launch(struct Server *server) send_vlan(new_socket, vlan); goto done; } + if (!strncmp(&buffer[4], "/cmd_log", 8)) { + printf("Request cmd_log\n"); + send_cmd_log(new_socket); + goto done; + } + if (!strncmp(&buffer[4], "/config ", 8) && uploaded_config) { + printf("Request current config.\n"); + send_config(new_socket); + goto done; + } + int i = 0; while (!isspace(buffer[4 + i])) i++; @@ -391,8 +438,24 @@ void launch(struct Server *server) } printf("Bytes read %ld\n", bytesRead); - if (is_word(&buffer[5], "/upload")) { - printf("POST upload request\n"); + if (is_word(&buffer[5], "/cmd")) { + printf("POST cmd\n"); + printf("CMD: %s\n", p + 4); + strcpy(cmd_history[cmd_ptr], p + 4); + // cmd_history[cmd_ptr][strlen(cmd_history[cmd_ptr]) -2] = '\0'; + cmd_ptr++; + print_cmd_history(); + char *response = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html\r\n\r\n" + " Upload OK" + "

    Command executed successully

    "; + write(new_socket, response, strlen(response)); + goto done; + } else if (is_word(&buffer[5], "/upload") || is_word(&buffer[5], "/config")) { + printf("POST upload/config request\n"); + bool config_upload = false; + if (is_word(&buffer[5], "/config")) + config_upload = true; if (!boundary[0]) { printf("Bad request, no boundary!\n"); send_bad_request(new_socket); @@ -409,6 +472,7 @@ void launch(struct Server *server) } while (!is_word(content_type, "application/octet-stream")); printf("Have content: >%s<\n", content_type); + p += 4; // Skip \r\n\r\n after content type char *uptr = upload_buffer; int bindex = 0; int bptr = p - buffer; @@ -441,6 +505,13 @@ void launch(struct Server *server) " Upload OK" "

    File uploaded successully

    "; write(new_socket, response, strlen(response)); + if (config_upload) { + uploaded_config_len = (uptr - upload_buffer); + if (uploaded_config) + free (uploaded_config); + uploaded_config = malloc(uploaded_config_len + 1); + memcpy(uploaded_config, upload_buffer, uploaded_config_len); + } goto done; } } From a8a770f27fe27843d689ed92c9f7e19d0a5c0c04 Mon Sep 17 00:00:00 2001 From: logicog Date: Tue, 25 Nov 2025 13:42:03 +0100 Subject: [PATCH 07/18] Add login page and style --- html/login.html | 30 ++++++++++++++++++++++++++++++ html/style.css | 46 ++++++++++++++++++++++++++++++++++++++++++---- html/update.html | 8 ++++---- 3 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 html/login.html diff --git a/html/login.html b/html/login.html new file mode 100644 index 0000000..81a96ea --- /dev/null +++ b/html/login.html @@ -0,0 +1,30 @@ + + + RTL Switch Login + + + + + +
    +

    RTL Switch Login

    +
    +
    + + + +
    + +

    +
    + + + diff --git a/html/style.css b/html/style.css index f0f1a5a..a4edf4d 100644 --- a/html/style.css +++ b/html/style.css @@ -1,4 +1,4 @@ -h1, h2 { +h1, h2, h3 { color: #226; } ul { @@ -38,13 +38,17 @@ td { text-align: right; } -input[type=submit] { padding: 8px 16px;background-color:#aaf;color:#000; margin-bottom: 2em;} +input[type=submit] { padding: 8px 16px;background-color:#aaf;color:#000; margin-bottom: 2em;border-radius: 15px;width: 100%;} input[type=submit]:hover { background-color: #226; color: white;} -button {padding: 8px; background-color:#99f; color:#000;} +input[type=file] { padding: 8px 16px;background-color:#aaf;color:#000; margin-bottom: 2em;border-radius: 15px;width: 60%;} +input[type=file]:hover { background-color: #226; color: white;} +b +button {padding: 8px; background-color:#99f; color:#000;border-radius: 15px;} button:hover { background-color: #226; color: white;} -.action{padding: 8px 16px;margin-top: 2em;margin-right: 3em; background-color: #aaf;color: #000;} +.action{padding: 8px 16px;margin-top: 2em;margin-right: 3em; background-color: #aaf;color: #000;border-radius: 15px; width: 100%;} .action:hover { background-color: #226; color: white;} +/* Port selection inputs */ .psel { position: absolute; opacity: 0; @@ -80,3 +84,37 @@ object { .row {display: flex;} .rcol {flex: 90%;} .lcol {flex: 10%;} +span::before{ content:''; position: absolute; top: 40px; left:0; width: 100%; height: 2px; background: #aaf;} + +/* Login page */ +.login_page {margin: 0; padding: 0; background: #aaf; height: 100vh; overflow: hidden;} +.center{ + position: absolute; top: 50%; left: 50%; + transform: translate(-50%, -40% ); + width: 500px; height: 400px; + background: white; + border-radius: 2px; +} + +.center h1 { + text-align: center; + border-bottom: 1px solid silver; +} + +.center form { +padding: 0 60px; +box-sizing: border-box; +} +form .txt_field{ +position: relative; +border-bottom: 2px solid #adadad; +margin: 30px 0; +} +.txt_field input { width: 100%; padding: 0 5px; height: 40px; font-size: 16px; border: none; + background: none; + outline: none;} +.txt_field label { position: absolute; top: 50%; left: 5px; color:#adadad; transform: translateY(-50%); + font-size: 16px; pointer-events: none;transition: .5s; } +.txt_field span::before{ content:''; position: absolute; top: 40px; left:0; width: 100%; height: 2px; background: #aaf;} +.txt_field input:focus ~ label, +.txt_field input:valid ~ label { top: -5px; color: #aaf; } diff --git a/html/update.html b/html/update.html index 22ee927..30d4ea0 100644 --- a/html/update.html +++ b/html/update.html @@ -6,13 +6,13 @@ -
    -
    +

    Firmware Update

    - Choose a firmware update file to upload:
    - + Choose a firmware update file to upload:

    +
    +
    From 6e0e907afb6f5b2e795f8e2511ef6d2f63a045a0 Mon Sep 17 00:00:00 2001 From: logicog Date: Tue, 25 Nov 2025 13:43:10 +0100 Subject: [PATCH 08/18] Add register definitions for random number generation --- rtl837x_regs.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rtl837x_regs.h b/rtl837x_regs.h index 754b98c..ce6052e 100644 --- a/rtl837x_regs.h +++ b/rtl837x_regs.h @@ -170,6 +170,13 @@ #define EEE_1000 0x04 #define EEE_2G5 0x10 +/* + * RANDOM + */ +#define RTL837X_RLDP_RLPP 0x106C +#define RLDP_RND_EN 3 +#define RTL837X_RAND_NUM0 0x107C +#define RTL837X_RAND_NUM1 0x1080 #ifdef REGDBG From cac7fbe0801903fe850d0d766878a7aa1f36d0b0 Mon Sep 17 00:00:00 2001 From: logicog Date: Tue, 25 Nov 2025 18:24:39 +0100 Subject: [PATCH 09/18] Add simulator support for session management --- tools/httpd_sim.c | 125 +++++++++++++++++++++++++++++++++++++--------- tools/httpd_sim.h | 2 +- 2 files changed, 103 insertions(+), 24 deletions(-) diff --git a/tools/httpd_sim.c b/tools/httpd_sim.c index e4ce843..9f6aca7 100644 --- a/tools/httpd_sim.c +++ b/tools/httpd_sim.c @@ -11,8 +11,13 @@ #include #include "../version.h" +#define SESSION_ID "1234567890ab" +#define PASSWORD "1234" +#define SESSION_TIMEOUT 20 + #define PORTS 6 time_t last_called; +time_t last_session_use; uint64_t txG[PORTS], txB[PORTS], rxG[PORTS], rxB[PORTS]; char txG_buff[20], txB_buff[20], rxG_buff[20], rxB_buff[20]; @@ -25,6 +30,8 @@ char cmd_history[1024][256]; uint16_t cmd_ptr = 0; char *uploaded_config = NULL; int uploaded_config_len; +const char *session = NULL; +bool authenticated = false; char is_word(char *c, char *d) { @@ -108,6 +115,7 @@ void send_status(int s) char *header = "HTTP/1.1 200 OK\r\n" "Content-Type: application/json; charset=UTF-8\r\n\r\n"; + printf("Sending status.\n"); time_t now = time(NULL); now = last_called ? last_called + 1 : now; // Make sure we don't divide by 0 for rates @@ -284,13 +292,30 @@ void send_bad_request(int socket) { } +void send_to_login(int socket) { + char *response = "HTTP/1.1 302 Found\r\n" + "Location: login.html\r\n\r\n"; + write(socket, response, strlen(response)); +} + + +void send_unauthorized(int socket) { + char *response = "HTTP/1.1 301 Unauthorized\r\n\r\n"; + write(socket, response, strlen(response)); +} + + char *scan_header(char *p) { + session = 0; + authenticated = false; while (*p != '\r' || *(p + 1) != '\n' || *(p + 2) != '\r' || *(p + 3) != '\n') { if (!*p++) break; - if (*p == '\n' && is_word(p + 1, "Content-Type:")) + 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")) { printf("Found multiplart\n"); @@ -306,6 +331,19 @@ char *scan_header(char *p) boundary[i + 2] = 0; } + time_t now = time(NULL); + if (session) { + printf("Session: >%s<. time now: %ld last %ld\n", session, now, last_session_use); + if (now - last_session_use > SESSION_TIMEOUT) { + printf("Session expired\n"); + } else { + if (!strncmp(session, SESSION_ID, 12)) + authenticated = true; + else + printf("Invalid session cookie!\n"); + } + } + printf("Time now: %ld last %ld\n", now, last_session_use); return p; } @@ -349,43 +387,65 @@ void launch(struct Server *server) puts(buffer); if (is_word(buffer, "GET")) { - printf("GET request\n"); + scan_header(buffer); if (!strncmp(&buffer[4], "/status.json", 12)) { printf("Status request\n"); - send_status(new_socket); + if (!authenticated) + send_unauthorized(new_socket); + else + send_status(new_socket); goto done; - } - if (!strncmp(&buffer[4], "/eee.json", 9)) { + } else if (!strncmp(&buffer[4], "/eee.json", 9)) { printf("EEE request\n"); - send_eee(new_socket); + if (!authenticated) + send_unauthorized(new_socket); + else + send_eee(new_socket); goto done; - } - if (!strncmp(&buffer[4], "/information.json", 17)) { + } else if (!strncmp(&buffer[4], "/information.json", 17)) { printf("Status request\n"); - send_basic_info(new_socket); + if (!authenticated) + send_unauthorized(new_socket); + else + send_basic_info(new_socket); goto done; - } - if (!strncmp(&buffer[4], "/mirror.json", 12)) { + } else if (!strncmp(&buffer[4], "/mirror.json", 12)) { printf("Mirror request\n"); - send_mirror(new_socket); + if (!authenticated) + send_unauthorized(new_socket); + else + send_mirror(new_socket); goto done; - } - if (!strncmp(&buffer[4], "/vlan.json?vid=", 15)) { + } else if (!strncmp(&buffer[4], "/vlan.json?vid=", 15)) { int vlan = atoi(&buffer[19]); printf("VLAN request for %d\n", vlan); - send_vlan(new_socket, vlan); + if (!authenticated) + send_unauthorized(new_socket); + else + send_vlan(new_socket, vlan); goto done; - } - if (!strncmp(&buffer[4], "/cmd_log", 8)) { + } else if (!strncmp(&buffer[4], "/cmd_log", 8)) { printf("Request cmd_log\n"); - send_cmd_log(new_socket); + if (!authenticated) + send_unauthorized(new_socket); + else + send_cmd_log(new_socket); goto done; - } - if (!strncmp(&buffer[4], "/config ", 8) && uploaded_config) { + } else if (!strncmp(&buffer[4], "/config ", 8) && uploaded_config) { printf("Request current config.\n"); - send_config(new_socket); + if (!authenticated) + send_unauthorized(new_socket); + else + send_config(new_socket); goto done; } + if (!authenticated && !(!strncmp(&buffer[4], "/login.html", 11) || !strncmp(&buffer[4], "/style.css", 10))) { + send_to_login(new_socket); + goto done; + } + + // A web-page is actively accessed, we can reset session time-out + last_session_use = time(NULL); int i = 0; while (!isspace(buffer[4 + i])) @@ -436,13 +496,15 @@ void launch(struct Server *server) send_bad_request(new_socket); goto done; } - + if (!authenticated && !is_word(&buffer[5], "/login")) { + send_to_login(new_socket); + goto done; + } printf("Bytes read %ld\n", bytesRead); if (is_word(&buffer[5], "/cmd")) { printf("POST cmd\n"); printf("CMD: %s\n", p + 4); strcpy(cmd_history[cmd_ptr], p + 4); - // cmd_history[cmd_ptr][strlen(cmd_history[cmd_ptr]) -2] = '\0'; cmd_ptr++; print_cmd_history(); char *response = "HTTP/1.1 200 OK\r\n" @@ -451,6 +513,23 @@ void launch(struct Server *server) "

    Command executed successully

    "; write(new_socket, response, strlen(response)); goto done; + } else if (is_word(&buffer[5], "/login")) { + printf("POST login\n"); + p += 4; + p += 4; // Read also over "pwd=" + char *response; + if (is_word(p, PASSWORD)) { + printf("Password accepted!\n"); + response = "HTTP/1.1 302 Found\r\n" + "Location: index.html\r\n" + "Set-Cookie: session=" SESSION_ID "; SameSite=Strict\r\n"; + } else { + response = "HTTP/1.1 302 Found\r\n" + "Location: login.html\r\n" + "Content-Type: text/html\r\n\r\n"; + } + write(new_socket, response, strlen(response)); + goto done; } else if (is_word(&buffer[5], "/upload") || is_word(&buffer[5], "/config")) { printf("POST upload/config request\n"); bool config_upload = false; diff --git a/tools/httpd_sim.h b/tools/httpd_sim.h index 412d2f8..b0542ad 100644 --- a/tools/httpd_sim.h +++ b/tools/httpd_sim.h @@ -3,7 +3,7 @@ #include -#define BUFFER_SIZE 2400 +#define BUFFER_SIZE 24000 struct Server { int domain; From 73a81ec03158dee9b28489c5586e919b020b801a Mon Sep 17 00:00:00 2001 From: logicog Date: Tue, 25 Nov 2025 21:21:02 +0100 Subject: [PATCH 10/18] Add initial implementation for authentication --- httpd/httpd.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index 346e59d..540c7b0 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -2,11 +2,16 @@ #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 SESSION_ID "1234567890ab" +#define PASSWORD "1234" +#define SESSION_TIMEOUT 200 + // Upload Firmware to 1M #define FIRMWARE_UPLOAD_START 0x100000 @@ -18,6 +23,7 @@ #pragma codeseg BANK1 #pragma constseg BANK1 +extern volatile __xdata uint8_t sfr_data[4]; extern __code struct f_data f_data[]; extern __code char * __code mime_strings[]; extern __xdata struct flash_region_t flash_region; @@ -39,6 +45,7 @@ __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 @@ -46,6 +53,13 @@ __xdata uint8_t verify_crc; __xdata uint32_t max_upload; __xdata uint16_t short_parsed; +__xdata char password[21]; +__xdata char session_id[13]; +__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 @@ -164,6 +178,19 @@ void send_bad_request(void) } +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) { @@ -178,6 +205,8 @@ __xdata uint8_t *skip_boundary(__xdata uint8_t *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') { write_char(*p); @@ -185,6 +214,8 @@ __xdata uint8_t *scan_header(__xdata uint8_t *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")) { print_string("\nFound multiplart\n"); @@ -201,6 +232,23 @@ __xdata uint8_t *scan_header(__xdata uint8_t *p) boundary[3] = '-'; boundary[i + 4] = 0; } + + reg_read_m(RTL837X_REG_SEC_COUNTER); + timeptr = (uint8_t*)&now; // Now is Little endian + timeptr[0] = sfr_data[3]; timeptr[1] = sfr_data[2]; timeptr[2] = sfr_data[1]; timeptr[3] = sfr_data[0]; + + if (session) { + print_string("Session: "); print_string_x(session); print_string(", time now "); print_long(now); + print_string(" last session use: "); print_long(last_session_use); write_char('\n'); + if (now - last_session_use > SESSION_TIMEOUT) { + print_string("Session expired\n"); + } else { + if (is_word(session, SESSION_ID)) // TODO: is_word_x + authenticated = 1; + else + print_string("Invalid session cookie!\n"); + } + } return p; } @@ -312,13 +360,36 @@ void handle_post(void) if (is_word(request_path, "cmd")) { register uint8_t i = 0; p += 4; + if (!authenticated) { + send_unauthorized(); + return; + } while (*p && *p != '\n' && *p != '\r') cmd_buffer[i++] = *p++; cmd_buffer[i] = '\0'; if (i) cmd_available = 1; + } else if (is_word(request_path, "login")) { + print_string("POST login\n"); + p += 8; // Read also over "pwd=" + if (is_word(p, PASSWORD)) { + print_string("Password accepted!\n"); + 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 302 Found\r\nLocation: index.html\r\n" \ + "Set-Cookie: session=" SESSION_ID "; SameSite=Strict\r\n\r\n"); + } else { + slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nLocation: login.html\r\n\r\n"); + } + return; } else if (is_word(request_path, "upload") || is_word(request_path, "config")) { print_string("POST upload/config request\n"); + if (!authenticated) { + send_unauthorized(); + return; + } if (!boundary[0]) { print_string("Bad request, no boundary!\n"); send_bad_request(); @@ -456,6 +527,7 @@ void httpd_appcall(void) if (is_word(p, "GET")) print_string("GET request "); p += 4; + scan_header(p); __xdata uint8_t *q = p; while (!is_separator(*p)) p++; @@ -467,6 +539,11 @@ void httpd_appcall(void) entry = find_entry(q); print_string("Entry is: "); print_byte(entry); write_char('\n'); if (entry == 0xff) { + if (!authenticated) { + print_string("Not authorized!\n"); + send_unauthorized(); + goto do_send; + } print_string("Not file entry\n"); if (!strcmp(q, "/status.json")) { send_status(); @@ -489,7 +566,17 @@ void httpd_appcall(void) send_not_found(); } } else { - print_string("Have entry\n"); + print_string("Have entry, authenticated: "); print_byte(authenticated); write_char('\n'); + if (!authenticated && !(f_data[entry].start == FDATA_START_login_html + || 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, "\r\nCache-Control: max-age=2592000\r\n\r\n"); From 70c14fb23ed9eb08f8d310f4ad2e3448db489be6 Mon Sep 17 00:00:00 2001 From: logicog Date: Sat, 29 Nov 2025 12:35:13 +0100 Subject: [PATCH 11/18] Add get_random_32() --- rtl837x_common.h | 1 + rtlplayground.c | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/rtl837x_common.h b/rtl837x_common.h index cab8b96..5156603 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -104,5 +104,6 @@ uint16_t strtox(register __xdata uint8_t *dst, register __code const char *s); void tcpip_output(void); void print_string_x(__xdata char *p); uint8_t read_flash(uint8_t bank, __code uint8_t *addr); +void get_random_32(void); #endif diff --git a/rtlplayground.c b/rtlplayground.c index 946e472..bba57a4 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -402,6 +402,18 @@ void sfr_set_zero(void) { } } + +/* + * Create 32 random number in sfr_data + */ +void get_random_32(void) +{ + // In order to get a new random numner, this bit has to be set each time! + reg_bit_set(RTL837X_RLDP_RLPP, RLDP_RND_EN); + reg_read_m(RTL837X_RAND_NUM0); +} + + /* * Transfer Network Interface RX data from the ASIC to the 8051 XMEM * data will be stored in the rx_header structure From dc6734236c0f3082ab642a2860b761bdbe4714b8 Mon Sep 17 00:00:00 2001 From: logicog Date: Wed, 26 Nov 2025 21:19:50 +0100 Subject: [PATCH 12/18] Add rnd and passwd commands --- cmd_parser.c | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/cmd_parser.c b/cmd_parser.c index 32c67b1..fc57c75 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -35,6 +35,8 @@ extern __code uint8_t * __code hex; extern __xdata uint8_t flash_buf[512]; extern __xdata struct flash_region_t flash_region; +extern __xdata char passwd[21]; + __xdata uint8_t vlan_names[VLAN_NAMES_SIZE]; __xdata uint16_t vlan_ptr; __xdata uint8_t gpio_last_value[8] = { 0 }; @@ -432,6 +434,36 @@ err: } +void parse_rnd(void) +{ + // In order to get a new random numner, this bit has to be set each time! + reg_bit_set(RTL837X_RLDP_RLPP, RLDP_RND_EN); + reg_read_m(RTL837X_RAND_NUM1); + print_byte(sfr_data[2]); + print_byte(sfr_data[3]); + reg_read_m(RTL837X_RAND_NUM0); + print_byte(sfr_data[0]); + print_byte(sfr_data[1]); + print_byte(sfr_data[2]); + print_byte(sfr_data[3]); + write_char('\n'); +} + + +void parse_passwd(void) +{ + if (cmd_words_b[2] > 0) { + signed char i; + signed char j = 0; + for (i = cmd_words_b[1]; (i != cmd_words_b[2] && i - cmd_words_b[1] < 20); i++) + passwd[j++] = cmd_buffer[i]; + passwd[j] = '\0'; + return; + } + print_string("Missing password\n"); +} + + // Parse command into words uint8_t cmd_tokenize(void) __banked { @@ -680,6 +712,12 @@ void cmd_parser(void) __banked if (cmd_compare(0, "regset")) { parse_regset(); } + if (cmd_compare(0, "rnd")) { + parse_rnd(); + } + if (cmd_compare(0, "passwd")) { + parse_passwd(); + } if (cmd_compare(0, "eee")) { int8_t port = -1; if (cmd_words_b[3] > 0) { @@ -741,12 +779,15 @@ void cmd_parser(void) __banked } } -#define FLASH_READ_BURST_SIZE 0x100; +#define FLASH_READ_BURST_SIZE 0x100 +#define PASSWORD "1234" void execute_config(void) __banked { __xdata uint32_t pos = CONFIG_START; __xdata uint16_t len_left = CONFIG_LEN; + // Set default password, it can be overwritten in the configuration file + strtox(passwd, PASSWORD); save_cmd = 0; do { From 4e4e6a55baf4e732e2367d818e288e713090ddff Mon Sep 17 00:00:00 2001 From: logicog Date: Wed, 26 Nov 2025 21:20:46 +0100 Subject: [PATCH 13/18] Redirect to login page when status request not authorized --- html/main.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/html/main.js b/html/main.js index 27a8587..eac133e 100644 --- a/html/main.js +++ b/html/main.js @@ -10,6 +10,8 @@ var numPorts = 0; function update() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 401) + document.location = "/login.html" if (this.readyState == 4 && this.status == 200) { const s = JSON.parse(xhttp.responseText); if (!numPorts) { From 0722b147feaf72f9e6af82642130278f2d26900b Mon Sep 17 00:00:00 2001 From: logicog Date: Wed, 26 Nov 2025 21:21:35 +0100 Subject: [PATCH 14/18] Add cache control to make sure new pages are served on session timeout --- httpd/httpd.c | 2 +- tools/httpd_sim.c | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index 540c7b0..b1df38a 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -579,7 +579,7 @@ void httpd_appcall(void) 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, "\r\nCache-Control: max-age=2592000\r\n\r\n"); + slen += strtox(outbuf + slen, "\r\nCache-Control: max-age=60, must-revalidate\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); diff --git a/tools/httpd_sim.c b/tools/httpd_sim.c index 9f6aca7..8d45d19 100644 --- a/tools/httpd_sim.c +++ b/tools/httpd_sim.c @@ -113,7 +113,8 @@ void send_status(int s) struct json_object *ports, *v; const char *jstring; char *header = "HTTP/1.1 200 OK\r\n" - "Content-Type: application/json; charset=UTF-8\r\n\r\n"; + "Cache-Control: no-cache\r\n" + "Content-Type: application/json; charset=UTF-8\r\n\r\n"; printf("Sending status.\n"); time_t now = time(NULL); @@ -300,7 +301,7 @@ void send_to_login(int socket) { void send_unauthorized(int socket) { - char *response = "HTTP/1.1 301 Unauthorized\r\n\r\n"; + char *response = "HTTP/1.1 401 Unauthorized\r\n\r\n"; write(socket, response, strlen(response)); } @@ -497,7 +498,7 @@ void launch(struct Server *server) goto done; } if (!authenticated && !is_word(&buffer[5], "/login")) { - send_to_login(new_socket); + send_unauthorized(new_socket); goto done; } printf("Bytes read %ld\n", bytesRead); @@ -525,8 +526,7 @@ void launch(struct Server *server) "Set-Cookie: session=" SESSION_ID "; SameSite=Strict\r\n"; } else { response = "HTTP/1.1 302 Found\r\n" - "Location: login.html\r\n" - "Content-Type: text/html\r\n\r\n"; + "Location: login.html\r\n\r\n"; } write(new_socket, response, strlen(response)); goto done; @@ -595,7 +595,8 @@ void launch(struct Server *server) } } char *response = "HTTP/1.1 200 OK\r\n" - "Content-Type: "; + "Cache-Control: max-age=60, must-revalidate\r\n" + "Content-Type: "; write(new_socket, response, strlen(response)); write(new_socket, mime, strlen(mime)); From ad27566030cc0dc51f2b430448e59eff5c718b48 Mon Sep 17 00:00:00 2001 From: logicog Date: Fri, 28 Nov 2025 07:42:38 +0100 Subject: [PATCH 15/18] Use current password in httpd.c --- httpd/httpd.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index b1df38a..f40aa3b 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -9,7 +9,6 @@ #include "../html_data.h" #define SESSION_ID "1234567890ab" -#define PASSWORD "1234" #define SESSION_TIMEOUT 200 // Upload Firmware to 1M @@ -53,7 +52,7 @@ __xdata uint8_t verify_crc; __xdata uint32_t max_upload; __xdata uint16_t short_parsed; -__xdata char password[21]; +__xdata char passwd[21]; __xdata char session_id[13]; __xdata uint8_t authenticated; __xdata uint32_t now; @@ -372,7 +371,7 @@ void handle_post(void) } else if (is_word(request_path, "login")) { print_string("POST login\n"); p += 8; // Read also over "pwd=" - if (is_word(p, PASSWORD)) { + if (is_word_x(p, passwd)) { print_string("Password accepted!\n"); reg_read_m(RTL837X_REG_SEC_COUNTER); timeptr = (uint8_t*)&last_session_use; // last_session_use is Little endian From 7f715e9218e25942b167e662bd80c4d10a799b19 Mon Sep 17 00:00:00 2001 From: logicog Date: Sat, 29 Nov 2025 12:36:38 +0100 Subject: [PATCH 16/18] Move inline itohex() into header file to allow external access --- httpd/page_impl.c | 16 +--------------- httpd/page_impl.h | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/httpd/page_impl.c b/httpd/page_impl.c index efedfb7..5c66f99 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -10,6 +10,7 @@ #include #include "../phy.h" #include "../version.h" +#include "page_impl.h" #pragma codeseg BANK1 #pragma constseg BANK1 @@ -41,21 +42,6 @@ extern __xdata struct flash_region_t flash_region; __code uint8_t * __code HTTP_RESPONCE_JSON = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"; __code uint8_t * __code HTTP_RESPONCE_TXT = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n"; -/* Convert only the lower nibble to ascii HEX char. - For convenience the upper nibble is masked out. -*/ -inline char itohex(uint8_t val) { - // Ignore upper nibble for convenience. - val &= 0x0f; - val -= 10; - - // 10 or above - if ((int8_t)val >= 0) - val += ('a' - '0' - 10); - - return val + ('0' + 10); -} - // Convert uint8_t to ascii HEX char push on html-buffer. void charhex_to_html(char c) { diff --git a/httpd/page_impl.h b/httpd/page_impl.h index 455b276..709cbd3 100644 --- a/httpd/page_impl.h +++ b/httpd/page_impl.h @@ -3,11 +3,26 @@ void send_counters(char port); void send_status(void); -void send_vlan(register uint16_t vlan); +void send_vlan(uint16_t vlan); void send_basic_info(void); void send_eee(void); void send_mirror(void); void send_config(void); void send_cmd_log(void); +/* Convert only the lower nibble to ascii HEX char. + For convenience the upper nibble is masked out. +*/ +inline char itohex(uint8_t val) { + // Ignore upper nibble for convenience. + val &= 0x0f; + val -= 10; + + // 10 or above + if ((int8_t)val >= 0) + val += ('a' - '0' - 10); + + return val + ('0' + 10); +} + #endif From 9c21d94b656919bd8ca2706a9d7b0c228cb515f5 Mon Sep 17 00:00:00 2001 From: logicog Date: Mon, 1 Dec 2025 08:14:59 +0100 Subject: [PATCH 17/18] Add read_reg_timer function --- rtl837x_common.h | 1 + rtlplayground.c | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/rtl837x_common.h b/rtl837x_common.h index 5156603..06e4a23 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -105,5 +105,6 @@ void tcpip_output(void); void print_string_x(__xdata char *p); uint8_t read_flash(uint8_t bank, __code uint8_t *addr); void get_random_32(void); +void read_reg_timer(uint32_t * tmr); #endif diff --git a/rtlplayground.c b/rtlplayground.c index bba57a4..073ca78 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -602,6 +602,20 @@ void cpy_4(__xdata uint8_t dest[], __xdata uint8_t source[]) } +void read_reg_timer(uint32_t * tmr) +{ + uint8_t * val = (uint8_t *)tmr; + SFR_REG_ADDR_U16 = RTL837X_REG_SEC_COUNTER; + SFR_EXEC_GO = SFR_EXEC_READ_REG; + do { + } while (SFR_EXEC_STATUS != 0); + *val++ = SFR_DATA_0; + *val++ = SFR_DATA_8; + *val++ = SFR_DATA_16; + *val = SFR_DATA_24; +} + + void sds_config_mac(uint8_t sds, uint8_t mode) { reg_read_m(RTL837X_REG_SDS_MODES); From b910692caf06a9714b33afbf75944b0cf30694a8 Mon Sep 17 00:00:00 2001 From: logicog Date: Fri, 28 Nov 2025 07:50:52 +0100 Subject: [PATCH 18/18] Use random session ID in httpd.c --- httpd/httpd.c | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index f40aa3b..eb25cfd 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -8,7 +8,7 @@ #include "uip.h" #include "../html_data.h" -#define SESSION_ID "1234567890ab" +#define SESSION_ID_LENGTH 12 #define SESSION_TIMEOUT 200 // Upload Firmware to 1M @@ -23,6 +23,7 @@ #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; @@ -53,7 +54,7 @@ __xdata uint32_t max_upload; __xdata uint16_t short_parsed; __xdata char passwd[21]; -__xdata char session_id[13]; +__xdata char session_id[SESSION_ID_LENGTH + 1]; __xdata uint8_t authenticated; __xdata uint32_t now; __xdata uint8_t *timeptr; @@ -217,7 +218,7 @@ __xdata uint8_t *scan_header(__xdata uint8_t *p) session = p + 17; } if (content_type && is_word(content_type, "multipart/form-data; boundary")) { - print_string("\nFound multiplart\n"); + print_string("\nFound multipart\n"); content_type += 30; uint8_t i = 0; while (content_type[i] != '\r' && content_type[i] != '\n') { @@ -232,17 +233,13 @@ __xdata uint8_t *scan_header(__xdata uint8_t *p) boundary[i + 4] = 0; } - reg_read_m(RTL837X_REG_SEC_COUNTER); - timeptr = (uint8_t*)&now; // Now is Little endian - timeptr[0] = sfr_data[3]; timeptr[1] = sfr_data[2]; timeptr[2] = sfr_data[1]; timeptr[3] = sfr_data[0]; + read_reg_timer(&now); if (session) { - print_string("Session: "); print_string_x(session); print_string(", time now "); print_long(now); - print_string(" last session use: "); print_long(last_session_use); write_char('\n'); if (now - last_session_use > SESSION_TIMEOUT) { print_string("Session expired\n"); } else { - if (is_word(session, SESSION_ID)) // TODO: is_word_x + if (is_word_x(session, session_id)) authenticated = 1; else print_string("Invalid session cookie!\n"); @@ -252,6 +249,20 @@ __xdata uint8_t *scan_header(__xdata uint8_t *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) @@ -373,12 +384,14 @@ void handle_post(void) p += 8; // Read also over "pwd=" if (is_word_x(p, passwd)) { print_string("Password accepted!\n"); - 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]; - + 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=" SESSION_ID "; SameSite=Strict\r\n\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 { slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nLocation: login.html\r\n\r\n"); }