+#include <stdint.h>
+#include <minilib.h>
+#include <output.h>
+#include <fb.h>
+
+#include "lwip/tcp.h"
+
+#include "rfb.h"
+
+#define SET_PIXEL_FORMAT 0
+#define SET_ENCODINGS 2
+#define FB_UPDATE_REQUEST 3
+#define KEY_EVENT 4
+#define POINTER_EVENT 5
+#define CLIENT_CUT_TEXT 6
+
+#define RFB_BUF_SIZE 2048
+
+struct pixel_format {
+ uint8_t bpp;
+ uint8_t depth;
+ uint8_t big_endian;
+ uint8_t true_color;
+ uint16_t red_max;
+ uint16_t green_max;
+ uint16_t blue_max;
+ uint8_t red_shift;
+ uint8_t green_shift;
+ uint8_t blue_shift;
+ uint8_t padding[3];
+};
+
+struct server_init_message {
+ uint16_t fb_width;
+ uint16_t fb_height;
+ struct pixel_format fmt;
+ uint32_t name_length;
+ char name_string[8];
+};
+
+struct fb_update_req {
+ uint8_t msgtype;
+ uint8_t incremental;
+ uint16_t xpos;
+ uint16_t ypos;
+ uint16_t width;
+ uint16_t height;
+};
+
+struct set_encs_req {
+ uint8_t msgtype;
+ uint8_t padding;
+ uint16_t num;
+ int32_t encodings[];
+};
+
+struct key_event_pkt {
+ uint8_t msgtype;
+ uint8_t downflag;
+ uint8_t pad[2];
+ uint32_t keysym;
+};
+
+struct pointer_event_pkt {
+ uint8_t msgtype;
+ uint8_t button_mask;
+ uint16_t x;
+ uint16_t y;
+};
+
+struct text_event_pkt {
+ uint8_t msgtype;
+ uint8_t padding[3];
+ uint32_t length;
+ char text[];
+};
+
+struct rfb_state {
+ enum {
+ ST_BEGIN,
+ ST_CLIENTINIT,
+ ST_MAIN
+ } state;
+ int version;
+ int encs_remaining;
+
+ char data[RFB_BUF_SIZE];
+ int readpos;
+ int writepos;
+
+ char next_update_incremental;
+ struct fb_update_req client_interest_area;
+
+ int needs_updated;
+};
+
+static struct server_init_message server_info;
+
+static void init_server_info() {
+ server_info.name_length = 8;
+ memcpy(server_info.name_string, "NetWatch", 8);
+}
+
+static void update_server_info() {
+ if (fb != NULL) {
+ server_info.fb_width = fb->curmode.xres;
+ server_info.fb_height = fb->curmode.yres;
+ switch (fb->curmode.format) {
+ case FB_RGB888:
+ server_info.fmt.bpp = 32;
+ server_info.fmt.depth = 24;
+ server_info.fmt.big_endian = 0;
+ server_info.fmt.true_color = 1;
+ server_info.fmt.red_max = 255;
+ server_info.fmt.green_max = 255;
+ server_info.fmt.blue_max = 255;
+ server_info.fmt.red_shift = 0;
+ server_info.fmt.green_shift = 8;
+ server_info.fmt.blue_shift = 16;
+ break;
+ default:
+ outputf("RFB: unknown fb fmt %d", fb->curmode.format);
+ break;
+ }
+ }
+}
+
+static void close_conn(struct tcp_pcb *pcb, struct rfb_state *state) {
+ tcp_arg(pcb, NULL);
+ tcp_sent(pcb, NULL);
+ tcp_recv(pcb, NULL);
+ mem_free(state);
+ tcp_close(pcb);
+}
+
+enum fsm_result {
+ NEEDMORE,
+ OK,
+ FAIL
+};
+
+static enum fsm_result recv_fsm(struct tcp_pcb *pcb, struct rfb_state *state) {
+
+ switch(state->state) {
+ case ST_BEGIN:
+ if (state->writepos < 12) return NEEDMORE;
+
+ if (!strncmp(state->data, "RFB 003.003\n", 12)) {
+ state->version = 3;
+ } else if (!strncmp(state->data, "RFB 003.005\n", 12)) {
+ /* Spec states that "RFB 003.005", an incorrect value,
+ * should be treated by the server as 3.3. */
+ state->version = 3;
+ } else if (!strncmp(state->data, "RFB 003.007\n", 12)) {
+ state->version = 7;
+ } else if (!strncmp(state->data, "RFB 003.008\n", 12)) {
+ state->version = 8;
+ } else {
+ outputf("RFB: Negotiation fail");
+ return FAIL;
+ }
+
+ outputf("RFB: Negotiated v3.%d", state->version);
+
+ state->readpos += 12;
+ state->state = ST_CLIENTINIT;
+
+ /* We support one security type, currently "none". */
+ if (state->version >= 7) {
+ tcp_write(pcb, "\x01\x01", 2, 0);
+ } else {
+ tcp_write(pcb, "\x01", 1, 0);
+ }
+
+ /* ... and go right ahead and send SecurityResult message. */
+ tcp_write(pcb, "\x00\x00\x00\x01", 4, 0);
+ tcp_output(pcb);
+
+ return OK;
+
+ case ST_CLIENTINIT:
+ if (state->writepos < 1) return NEEDMORE;
+ state->readpos += 1;
+ state->state = ST_MAIN;
+
+ tcp_write(pcb, &server_info, sizeof(server_info), 0);
+ tcp_output(pcb);
+
+ return OK;
+
+ case ST_MAIN:
+ if (state->writepos < 1) return NEEDMORE;
+
+ switch (state->data[0]) {
+
+ case SET_PIXEL_FORMAT:
+ /* SetPixelFormat */
+ if (state->writepos < (sizeof(struct pixel_format) + 4))
+ return NEEDMORE;
+/*
+ struct pixel_format * new_fmt =
+ (struct pixel_format *)(&state->data[4]);
+*/
+ /* XXX ... */
+
+ state->readpos += sizeof(struct pixel_format) + 4;
+ return OK;
+
+ case SET_ENCODINGS:
+ if (state->writepos < 4) return NEEDMORE;
+
+ struct set_encs_req * req = (struct set_encs_req *)state->data;
+
+ if (state->writepos < (sizeof(struct set_encs_req)
+ + 4 * req->num))
+ return NEEDMORE;
+
+ /* XXX ... */
+
+ state->readpos += (4 * req->num) + sizeof(struct set_encs_req);
+ return OK;
+
+ case FB_UPDATE_REQUEST:
+ if (state->writepos < sizeof(struct fb_update_req))
+ return NEEDMORE;
+
+ state->needs_updated = 1;
+ memcpy(&state->client_interest_area, state->data,
+ sizeof(struct fb_update_req));
+
+ state->readpos += sizeof(struct fb_update_req);
+ return OK;
+
+ case KEY_EVENT:
+ if (state->writepos < sizeof(struct key_event_pkt))
+ return NEEDMORE;
+
+ /* XXX stub */
+
+ state->readpos += sizeof(struct key_event_pkt);
+ return OK;
+
+ case POINTER_EVENT:
+ if (state->writepos < sizeof(struct pointer_event_pkt))
+ return NEEDMORE;
+
+ /* XXX stub */
+
+ state->readpos += sizeof(struct pointer_event_pkt);
+ return OK;
+
+ case CLIENT_CUT_TEXT:
+ if (state->writepos < sizeof(struct text_event_pkt))
+ return NEEDMORE;
+
+ struct text_event_pkt * pkt =
+ (struct text_event_pkt *)state->data;
+
+ if (state->writepos < sizeof(struct text_event_pkt)
+ + pkt->length)
+ return NEEDMORE;
+
+ /* XXX stub */
+
+ state->readpos += sizeof(struct text_event_pkt)
+ + pkt->length;
+ return OK;
+
+ default:
+ outputf("RFB: Bad command: %d", state->data[0]);
+ }
+ default:
+ outputf("RFB: Bad state");
+ return FAIL;
+ }
+}
+
+static err_t rfb_recv(void *arg, struct tcp_pcb *pcb,
+ struct pbuf *p, err_t err) {
+ struct rfb_state *state = arg;
+
+ if (state == NULL)
+
+ if (err != ERR_OK) {
+ outputf("RFB: recv err %d", err);
+ /* FIXME do something better here? */
+ return ERR_OK;
+ }
+
+ if (p == NULL) {
+ outputf("RFB: Connection closed");
+ close_conn(pcb, state);
+ return ERR_OK;
+ }
+
+ if (p->tot_len > (RFB_BUF_SIZE - state->writepos)) {
+ /* Overflow! */
+ outputf("RFB: Overflow!");
+ close_conn(pcb, state);
+ return ERR_OK;
+ }
+
+ outputf("RFB: Processing %d", p->tot_len);
+ pbuf_copy_partial(p, state->data + state->writepos, p->tot_len, 0);
+ tcp_recved(pcb, p->tot_len);
+ pbuf_free(p);
+
+ while (1) {
+ switch (recv_fsm(pcb, state)) {
+ case NEEDMORE:
+ /* Need more data */
+ return ERR_OK;
+
+ case OK:
+ if (state->readpos == state->writepos) {
+ state->readpos = 0;
+ state->writepos = 0;
+ return ERR_OK;
+ } else {
+ memmove(state->data,
+ state->data + state->readpos,
+ state->writepos - state->readpos);
+ }
+ break;
+ case FAIL:
+ /* Shit */
+ outputf("RFB: Protocol error");
+ close_conn(pcb, state);
+ return ERR_OK;
+ }
+ }
+}
+
+static err_t rfb_accept(void *arg, struct tcp_pcb *pcb, err_t err) {
+ struct rfb_state *state;
+
+ LWIP_UNUSED_ARG(arg);
+ LWIP_UNUSED_ARG(err);
+
+ state = (struct rfb_state *)mem_malloc(sizeof(struct rfb_state));
+
+ /* XXX: update_server_info() should be called from the 64ms timer, and deal
+ * with screen resizes appropriately. */
+ update_server_info();
+
+ if (!state)
+ {
+ outputf("rfb_accept: out of memory\n");
+ return ERR_MEM;
+ }
+
+ tcp_arg(pcb, state);
+ tcp_recv(pcb, rfb_recv);
+/*
+ tcp_err(pcb, rfb_err);
+ tcp_poll(pcb, rfb_poll, 2);
+*/
+ tcp_write(pcb, "RFB 003.008\n", 12, 0);
+ tcp_output(pcb);
+
+ return ERR_OK;
+}
+
+void rfb_init() {
+ struct tcp_pcb *pcb;
+
+ init_server_info();
+
+ pcb = tcp_new();
+ tcp_bind(pcb, IP_ADDR_ANY, RFB_PORT);
+ pcb = tcp_listen(pcb);
+ tcp_accept(pcb, rfb_accept);
+}