mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-08-30 14:52:51 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
984831620c |
@@ -18,7 +18,6 @@ html_data.c html_data.h: html tools
|
|||||||
tools/fileadder -a -s -b BANK1 -d html -p html_data
|
tools/fileadder -a -s -b BANK1 -d html -p html_data
|
||||||
|
|
||||||
httpd: html_data.h
|
httpd: html_data.h
|
||||||
$(MAKE) -C $@
|
|
||||||
|
|
||||||
$(SUBDIRS):
|
$(SUBDIRS):
|
||||||
$(MAKE) -C $@
|
$(MAKE) -C $@
|
||||||
|
|||||||
+42
-44
@@ -12,10 +12,7 @@
|
|||||||
extern __code struct f_data f_data[];
|
extern __code struct f_data f_data[];
|
||||||
extern __code fcall_ptr f_calls[];
|
extern __code fcall_ptr f_calls[];
|
||||||
|
|
||||||
__xdata uint8_t outbuf[2048];
|
|
||||||
__xdata uint8_t entry;
|
__xdata uint8_t entry;
|
||||||
__xdata uint16_t slen;
|
|
||||||
__xdata uint16_t o_idx;
|
|
||||||
__xdata uint16_t mpos;
|
__xdata uint16_t mpos;
|
||||||
__xdata uint16_t len_left;
|
__xdata uint16_t len_left;
|
||||||
|
|
||||||
@@ -60,6 +57,7 @@ uint8_t find_entry(uint8_t *e)
|
|||||||
void httpd_appcall(void)
|
void httpd_appcall(void)
|
||||||
{
|
{
|
||||||
__xdata struct httpd_state *s = &(uip_conn->appstate);
|
__xdata struct httpd_state *s = &(uip_conn->appstate);
|
||||||
|
__xdata uint8_t *outbuf = s->outbuf;
|
||||||
|
|
||||||
write_char('P');
|
write_char('P');
|
||||||
if(uip_connected() && s->tstate == TSTATE_CLOSED) {
|
if(uip_connected() && s->tstate == TSTATE_CLOSED) {
|
||||||
@@ -77,24 +75,24 @@ void httpd_appcall(void)
|
|||||||
// write_char('p');
|
// write_char('p');
|
||||||
} else if (uip_acked() && s->tstate == TSTATE_TX) {
|
} else if (uip_acked() && s->tstate == TSTATE_TX) {
|
||||||
print_string("ACK\n");
|
print_string("ACK\n");
|
||||||
if (slen > uip_mss()) {
|
if (s->slen > uip_mss()) {
|
||||||
slen -= uip_mss();
|
s->slen -= uip_mss();
|
||||||
o_idx += uip_mss();
|
s->o_idx += uip_mss();
|
||||||
} else {
|
} else {
|
||||||
slen = 0;
|
s->slen = 0;
|
||||||
o_idx += slen;
|
s->o_idx += s->slen;
|
||||||
}
|
}
|
||||||
|
|
||||||
s->tstate = TSTATE_ACKED;
|
s->tstate = TSTATE_ACKED;
|
||||||
|
|
||||||
if (slen > uip_mss()) {
|
if (s->slen > uip_mss()) {
|
||||||
print_string("Sending A: "); print_short(slen); write_char('\n');
|
print_string("Sending A: "); print_short(s->slen); write_char('\n');
|
||||||
uip_send(outbuf + o_idx, uip_mss());
|
uip_send(outbuf + s->o_idx, uip_mss());
|
||||||
print_string("Sending A done\n");
|
print_string("Sending A done\n");
|
||||||
s->tstate = TSTATE_TX;
|
s->tstate = TSTATE_TX;
|
||||||
} else if (slen > 0) {
|
} else if (s->slen > 0) {
|
||||||
print_string("Sending B: "); print_short(slen); write_char('\n');
|
print_string("Sending B: "); print_short(s->slen); write_char('\n');
|
||||||
uip_send(outbuf + o_idx, slen);
|
uip_send(outbuf + s->o_idx, s->slen);
|
||||||
print_string("Sending B done\n");
|
print_string("Sending B done\n");
|
||||||
s->tstate = TSTATE_TX;
|
s->tstate = TSTATE_TX;
|
||||||
}
|
}
|
||||||
@@ -121,14 +119,14 @@ void httpd_appcall(void)
|
|||||||
print_string("Entry is: "); print_byte(entry); write_char('\n');
|
print_string("Entry is: "); print_byte(entry); write_char('\n');
|
||||||
if (entry == 0xff) {
|
if (entry == 0xff) {
|
||||||
print_string("Not found\n");
|
print_string("Not found\n");
|
||||||
slen = strtox(outbuf, "HTTP/1.1 404 Not found\r\nContent-Type: text/html\r\n\r\n");
|
s->slen = strtox(outbuf, "HTTP/1.1 404 Not found\r\nContent-Type: text/html\r\n\r\n");
|
||||||
print_string("slen: "); print_short(slen); write_char('\n');
|
print_string("slen: "); print_short(s->slen); write_char('\n');
|
||||||
slen += strtox(outbuf + slen, "<!DOCTYPE HTML PUBLIC>\n<title>404 Not Found</title>\n<h1>Not Found</h1>\n");
|
s->slen += strtox(outbuf + s->slen, "<!DOCTYPE HTML PUBLIC>\n<title>404 Not Found</title>\n<h1>Not Found</h1>\n");
|
||||||
} else {
|
} else {
|
||||||
print_string("Have entry\n");
|
print_string("Have entry\n");
|
||||||
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nContent-Type: ");
|
s->slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nContent-Type: ");
|
||||||
slen += strtox(outbuf + slen, f_data[entry].mime);
|
s->slen += strtox(outbuf + s->slen, f_data[entry].mime);
|
||||||
slen += strtox(outbuf + slen, "\r\n\r\n");
|
s->slen += strtox(outbuf + s->slen, "\r\n\r\n");
|
||||||
len_left = f_data[entry].len;
|
len_left = f_data[entry].len;
|
||||||
if (f_data[entry].mime[0] == 't' && f_data[entry].mime[5] == 'h') {
|
if (f_data[entry].mime[0] == 't' && f_data[entry].mime[5] == 'h') {
|
||||||
print_string("MIME is html len is "); print_short(len_left); write_char('\n');
|
print_string("MIME is html len is "); print_short(len_left); write_char('\n');
|
||||||
@@ -139,49 +137,49 @@ void httpd_appcall(void)
|
|||||||
print_string("Entry-len:"); print_short(len_left); write_char('\n');
|
print_string("Entry-len:"); print_short(len_left); write_char('\n');
|
||||||
mpos = len_left - mpos;
|
mpos = len_left - mpos;
|
||||||
print_string("l/pos: "); print_short(mpos); write_char('\n');
|
print_string("l/pos: "); print_short(mpos); write_char('\n');
|
||||||
flash_read_bulk(outbuf + slen, f_data[entry].start + f_data[entry].len - len_left, mpos + CMARK_S); // call marker is e.g. #{001}
|
flash_read_bulk(outbuf + s->slen, f_data[entry].start + f_data[entry].len - len_left, mpos + CMARK_S); // call marker is e.g. #{001}
|
||||||
slen += mpos;
|
s->slen += mpos;
|
||||||
write_char('@'); write_char(outbuf[slen + 2]); write_char(outbuf[slen + 3]); write_char(outbuf[slen + 4]);
|
write_char('@'); write_char(outbuf[s->slen + 2]); write_char(outbuf[s->slen + 3]); write_char(outbuf[s->slen + 4]);
|
||||||
fcall_ptr ptr = f_calls[(outbuf[slen + 2] - '0') * 100 + (outbuf[slen + 3]-'0') * 10 + outbuf[slen + 4] - '0'];
|
fcall_ptr ptr = f_calls[(outbuf[s->slen + 2] - '0') * 100 + (outbuf[s->slen + 3]-'0') * 10 + outbuf[s->slen + 4] - '0'];
|
||||||
slen -= CMARK_S; // Overwrite marker with generated html
|
s->slen -= CMARK_S; // Overwrite marker with generated html
|
||||||
print_string("Call location is: "); print_short((uint16_t)ptr); write_char('\n');
|
print_string("Call location is: "); print_short((uint16_t)ptr); write_char('\n');
|
||||||
// f_calls[outbuf[slen + 2] * 100 + outbuf[slen + 3] * 10 + outbuf[slen + 4]]();
|
// f_calls[outbuf[s->slen + 2] * 100 + outbuf[s->slen + 3] * 10 + outbuf[s->slen + 4]]();
|
||||||
ptr();
|
ptr(outbuf);
|
||||||
print_string("call done\n");
|
print_string("call done\n");
|
||||||
mpos += CMARK_S;
|
mpos += CMARK_S;
|
||||||
len_left -= mpos;
|
len_left -= mpos;
|
||||||
flash_find_mark(f_data[entry].start + mpos, len_left, "#{");
|
flash_find_mark(f_data[entry].start + mpos, len_left, "#{");
|
||||||
}
|
}
|
||||||
print_string("At end mpos: "); print_short(mpos); write_char('\n');
|
print_string("At end mpos: "); print_short(mpos); write_char('\n');
|
||||||
flash_read_bulk(outbuf + slen, f_data[entry].start + f_data[entry].len - len_left, len_left);
|
flash_read_bulk(outbuf + s->slen, f_data[entry].start + f_data[entry].len - len_left, len_left);
|
||||||
slen += len_left;
|
s->slen += len_left;
|
||||||
} else {
|
} else {
|
||||||
print_string("MIME: "); print_string(f_data[entry].mime); write_char('\n');
|
print_string("MIME: "); print_string(f_data[entry].mime); write_char('\n');
|
||||||
flash_read_bulk(outbuf + slen, f_data[entry].start, len_left);
|
flash_read_bulk(outbuf + s->slen, f_data[entry].start, len_left);
|
||||||
slen += len_left;
|
s->slen += len_left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
print_string("slen: "); print_short(slen); write_char('\n');
|
print_string("slen: "); print_short(s->slen); write_char('\n');
|
||||||
o_idx = 0;
|
s->o_idx = 0;
|
||||||
if (slen > uip_mss()) {
|
if (s->slen > uip_mss()) {
|
||||||
print_string("Sending a: "); print_short(slen); write_char('\n');
|
print_string("Sending a: "); print_short(s->slen); write_char('\n');
|
||||||
uip_send(outbuf + o_idx, uip_mss());
|
uip_send(outbuf + s->o_idx, uip_mss());
|
||||||
print_string("Sending a done\n");
|
print_string("Sending a done\n");
|
||||||
} else {
|
} else {
|
||||||
print_string("Sending b: "); print_short(slen); write_char('\n');
|
print_string("Sending b: "); print_short(s->slen); write_char('\n');
|
||||||
uip_send(outbuf + o_idx, slen);
|
uip_send(outbuf + s->o_idx, s->slen);
|
||||||
print_string("Sending b done\n");
|
print_string("Sending b done\n");
|
||||||
}
|
}
|
||||||
s->tstate = TSTATE_TX;
|
s->tstate = TSTATE_TX;
|
||||||
} else if (uip_rexmit()) { // Connection established, need to rexmit?
|
} else if (uip_rexmit()) { // Connection established, need to rexmit?
|
||||||
print_string("RETRANSMIT requested\n");
|
print_string("RETRANSMIT requested\n");
|
||||||
if (slen > uip_mss()) {
|
if (s->slen > uip_mss()) {
|
||||||
print_string("Sending C: "); print_short(slen); write_char('\n');
|
print_string("Sending C: "); print_short(s->slen); write_char('\n');
|
||||||
uip_send(outbuf + o_idx, uip_mss());
|
uip_send(outbuf + s->o_idx, uip_mss());
|
||||||
print_string("Sending C done\n");
|
print_string("Sending C done\n");
|
||||||
} else if (slen > 0) {
|
} else if (s->slen > 0) {
|
||||||
print_string("Sending D: "); print_short(slen); write_char('\n');
|
print_string("Sending D: "); print_short(s->slen); write_char('\n');
|
||||||
uip_send(outbuf + o_idx, slen);
|
uip_send(outbuf + s->o_idx, s->slen);
|
||||||
print_string("Sending D done\n");
|
print_string("Sending D done\n");
|
||||||
}
|
}
|
||||||
s->tstate = TSTATE_TX;
|
s->tstate = TSTATE_TX;
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
for each TCP connection. */
|
for each TCP connection. */
|
||||||
typedef struct httpd_state {
|
typedef struct httpd_state {
|
||||||
uint8_t tstate;
|
uint8_t tstate;
|
||||||
|
uint8_t outbuf[2048];
|
||||||
|
uint16_t slen;
|
||||||
|
uint16_t o_idx;
|
||||||
} uip_tcp_appstate_t;
|
} uip_tcp_appstate_t;
|
||||||
|
|
||||||
/* Finally we define the application function to be called by uIP. */
|
/* Finally we define the application function to be called by uIP. */
|
||||||
|
|||||||
+43
-49
@@ -4,81 +4,75 @@
|
|||||||
|
|
||||||
#pragma codeseg BANK1
|
#pragma codeseg BANK1
|
||||||
|
|
||||||
extern __xdata uint8_t outbuf[2048];
|
|
||||||
extern __xdata uint16_t slen;
|
|
||||||
extern __code uint8_t * __code hex;
|
extern __code uint8_t * __code hex;
|
||||||
extern __xdata uip_ipaddr_t uip_hostaddr, uip_draddr, uip_netmask;
|
extern __xdata uip_ipaddr_t uip_hostaddr, uip_draddr, uip_netmask;
|
||||||
extern __code uint8_t ownMAC[];
|
extern __code uint8_t ownMAC[];
|
||||||
|
|
||||||
inline void byte_to_html(uint8_t a)
|
#define PUTC(c) *outbuf++ = c;
|
||||||
{
|
|
||||||
outbuf[slen++] = hex[(a >> 4) & 0xf];
|
|
||||||
outbuf[slen++] = hex[a & 0xf];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
#define PUTBYTE(a) { *outbuf++ = hex[(a >> 4) & 0xf]; *outbuf++ = hex[a & 0xf]; }
|
||||||
|
|
||||||
inline void char_to_html(char c)
|
inline uint8_t itoa_html(uint8_t v, __xdata uint8_t *outbuf)
|
||||||
{
|
|
||||||
outbuf[slen++] = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
inline void itoa_html(uint8_t v)
|
|
||||||
{
|
{
|
||||||
uint8_t t = (v / 100) % 10;
|
uint8_t t = (v / 100) % 10;
|
||||||
if (t)
|
if (t)
|
||||||
char_to_html('0' + t);
|
PUTC('0' + t);
|
||||||
t = (v / 10) % 10;
|
t = (v / 10) % 10;
|
||||||
if (t)
|
if (t)
|
||||||
char_to_html('0' + t);
|
PUTC('0' + t);
|
||||||
char_to_html('0' + (v % 10));
|
PUTC('0' + (v % 10));
|
||||||
|
|
||||||
|
if (v >= 100)
|
||||||
|
return 3;
|
||||||
|
else if (v >= 10)
|
||||||
|
return 2;
|
||||||
|
else
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint16_t stat_content(void)
|
uint16_t stat_content(__xdata uint8_t *outbuf)
|
||||||
{
|
{
|
||||||
print_string("stat_content called\n");
|
print_string("stat_content called\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint16_t port_status(void)
|
uint16_t port_status(__xdata uint8_t *outbuf)
|
||||||
{
|
{
|
||||||
print_string("port_status called\n");
|
print_string("port_status called\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint16_t html_index(void)
|
uint16_t html_index(__xdata uint8_t *outbuf)
|
||||||
{
|
{
|
||||||
|
__xdata uint8_t *oldptr = outbuf;
|
||||||
|
|
||||||
print_string("html_index called\n");
|
print_string("html_index called\n");
|
||||||
slen += strtox(outbuf + slen, "<tr><td>IP Address</td><td>");
|
outbuf += strtox(outbuf, "<tr><td>IP Address</td><td>");
|
||||||
print_string("\nIP:");
|
outbuf += itoa_html(uip_hostaddr[0], outbuf); PUTC('.');
|
||||||
print_byte(uip_hostaddr[0]); write_char('.');
|
outbuf += itoa_html(uip_hostaddr[0] >> 8, outbuf); PUTC('.');
|
||||||
print_byte(uip_hostaddr[0] >> 8); write_char('.');
|
outbuf += itoa_html(uip_hostaddr[1], outbuf); PUTC('.');
|
||||||
print_byte(uip_hostaddr[1]); write_char('.');
|
outbuf += itoa_html(uip_hostaddr[1] >> 8, outbuf);
|
||||||
print_byte(uip_hostaddr[1] >> 8); write_char('\n');
|
outbuf += strtox(outbuf, "</td></tr><tr><td>Gateway</td><td>");
|
||||||
itoa_html(uip_hostaddr[0]); char_to_html('.');
|
outbuf += itoa_html(uip_draddr[0], outbuf); PUTC('.');
|
||||||
itoa_html(uip_hostaddr[0] >> 8); char_to_html('.');
|
outbuf += itoa_html(uip_draddr[0] >> 8, outbuf); PUTC('.');
|
||||||
itoa_html(uip_hostaddr[1]); char_to_html('.');
|
outbuf += itoa_html(uip_draddr[1], outbuf); PUTC('.');
|
||||||
itoa_html(uip_hostaddr[1]);
|
outbuf += itoa_html(uip_draddr[1] >> 8, outbuf);
|
||||||
slen += strtox(outbuf + slen, "</td></tr><tr><td>Gateway</td><td>");
|
outbuf += strtox(outbuf, "</td></tr><tr><td>Netmask</td><td>");
|
||||||
itoa_html(uip_draddr[0]); char_to_html('.');
|
outbuf += itoa_html(uip_netmask[0], outbuf); PUTC('.');
|
||||||
itoa_html(uip_draddr[0] >> 8); char_to_html('.');
|
outbuf += itoa_html(uip_netmask[0] >> 8, outbuf); PUTC('.');
|
||||||
itoa_html(uip_draddr[1]); char_to_html('.');
|
outbuf += itoa_html(uip_netmask[1], outbuf); PUTC('.');
|
||||||
itoa_html(uip_draddr[1] >> 8);
|
outbuf += itoa_html(uip_netmask[1] >> 8, outbuf);
|
||||||
slen += strtox(outbuf + slen, "</td></tr><tr><td>Netmask</td><td>");
|
outbuf += strtox(outbuf, "</td></tr><tr><td>MAC Address</td><td>");
|
||||||
itoa_html(uip_netmask[0]); char_to_html('.');
|
PUTBYTE(ownMAC[0]); PUTC(':');
|
||||||
itoa_html(uip_netmask[0] >> 8); char_to_html('.');
|
PUTBYTE(ownMAC[1]); PUTC(':');
|
||||||
itoa_html(uip_netmask[1]); char_to_html('.');
|
PUTBYTE(ownMAC[2]); PUTC(':');
|
||||||
itoa_html(uip_netmask[1] >> 8);
|
PUTBYTE(ownMAC[3]); PUTC(':');
|
||||||
slen += strtox(outbuf + slen, "</td></tr><tr><td>MAC Address</td><td>");
|
PUTBYTE(ownMAC[4]); PUTC(':');
|
||||||
byte_to_html(ownMAC[0]); char_to_html(':');
|
PUTBYTE(ownMAC[5]);
|
||||||
byte_to_html(ownMAC[1]); char_to_html(':');
|
outbuf += strtox(outbuf, "</td></tr>");
|
||||||
byte_to_html(ownMAC[2]); char_to_html(':');
|
|
||||||
byte_to_html(ownMAC[3]); char_to_html(':');
|
return outbuf - oldptr;
|
||||||
byte_to_html(ownMAC[4]); char_to_html(':');
|
|
||||||
byte_to_html(ownMAC[5]);
|
|
||||||
slen += strtox(outbuf + slen, "</td></tr>");
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -200,7 +200,7 @@ int replaceCalls(int pos)
|
|||||||
buffer[pos + i + 5] = '}';
|
buffer[pos + i + 5] = '}';
|
||||||
i += 6;
|
i += 6;
|
||||||
fbuf_p += snprintf(&fbuf[fbuf_p], DEF_SIZE - fbuf_p, " %s,\n", function_buf);
|
fbuf_p += snprintf(&fbuf[fbuf_p], DEF_SIZE - fbuf_p, " %s,\n", function_buf);
|
||||||
xbuf_p += snprintf(&xbuf[xbuf_p], DEF_SIZE - xbuf_p, "extern uint16_t %s(void);\n", function_buf);
|
xbuf_p += snprintf(&xbuf[xbuf_p], DEF_SIZE - xbuf_p, "extern uint16_t %s(__xdata uint8_t *outbuf);\n", function_buf);
|
||||||
callNum++;
|
callNum++;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
@@ -266,7 +266,7 @@ int main(int argc, char **argv)
|
|||||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#define FDATA_DEFS_H\n\n");
|
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#define FDATA_DEFS_H\n\n");
|
||||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#include <stdint.h>\n\n");
|
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#include <stdint.h>\n\n");
|
||||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "struct f_data {\n __code char *file;\n uint32_t start;\n uint16_t len;\n __code char *mime;\n};\n\n");
|
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "struct f_data {\n __code char *file;\n uint32_t start;\n uint16_t len;\n __code char *mime;\n};\n\n");
|
||||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "typedef uint16_t (* fcall_ptr)(void);\n\n");
|
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "typedef uint16_t (* fcall_ptr)(__xdata uint8_t *outbuf);\n\n");
|
||||||
|
|
||||||
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, "// This file is automatically generated, do not edit!\n\n");
|
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, "// This file is automatically generated, do not edit!\n\n");
|
||||||
if (arguments.prefix)
|
if (arguments.prefix)
|
||||||
|
|||||||
+2
-2
@@ -94,14 +94,14 @@ typedef unsigned short uip_stats_t;
|
|||||||
*
|
*
|
||||||
* \hideinitializer
|
* \hideinitializer
|
||||||
*/
|
*/
|
||||||
#define UIP_CONF_MAX_CONNECTIONS 1
|
#define UIP_CONF_MAX_CONNECTIONS 3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum number of listening TCP ports. TODO: increase this!
|
* Maximum number of listening TCP ports. TODO: increase this!
|
||||||
*
|
*
|
||||||
* \hideinitializer
|
* \hideinitializer
|
||||||
*/
|
*/
|
||||||
#define UIP_CONF_MAX_LISTENPORTS 1
|
#define UIP_CONF_MAX_LISTENPORTS 3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* uIP buffer size.
|
* uIP buffer size.
|
||||||
|
|||||||
Reference in New Issue
Block a user