Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
500 lines (418 sloc) 13.6 KB
/* See Copyright Notice in LICENSE.txt */
#define _BSD_SOURCE
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <GL/glew.h>
#include <GL/gl.h>
#include <lauxlib.h>
#include <lualib.h>
#include <event.h>
#include "misc.h"
#include "shader.h"
typedef struct vnc_s vnc_t;
typedef void(*protocol_handler)(vnc_t *);
typedef struct {
uint8_t bpp;
uint8_t depth;
uint8_t bigendian;
uint8_t truecolor;
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];
} pixelformat_t;
struct vnc_s {
GLuint tex;
int width;
int height;
struct bufferevent *buf_ev;
char *host;
int port;
int alive;
protocol_handler handler;
int num_bytes;
pixelformat_t pixelformat;
// state for rect update
int num_rects;
int rect_x;
int rect_y;
int rect_w;
int rect_h;
};
LUA_TYPE_DECL(vnc)
/* Instance methods */
static int vnc_size(lua_State *L) {
vnc_t *vnc = checked_vnc(L, 1);
lua_pushnumber(L, vnc->width);
lua_pushnumber(L, vnc->height);
return 2;
}
static int vnc_draw(lua_State *L) {
vnc_t *vnc = checked_vnc(L, 1);
GLfloat x1 = luaL_checknumber(L, 2);
GLfloat y1 = luaL_checknumber(L, 3);
GLfloat x2 = luaL_checknumber(L, 4);
GLfloat y2 = luaL_checknumber(L, 5);
GLfloat alpha = luaL_optnumber(L, 6, 1.0);
glBindTexture(GL_TEXTURE_2D, vnc->tex);
shader_set_gl_color(1.0, 1.0, 1.0, alpha);
glBegin(GL_QUADS);
glTexCoord2f(0.0, 1.0); glVertex3f(x1, y1, 0);
glTexCoord2f(1.0, 1.0); glVertex3f(x2, y1, 0);
glTexCoord2f(1.0, 0.0); glVertex3f(x2, y2, 0);
glTexCoord2f(0.0, 0.0); glVertex3f(x1, y2, 0);
glEnd();
return 0;
}
static int vnc_alive(lua_State *L) {
vnc_t *vnc = checked_vnc(L, 1);
lua_pushboolean(L, vnc->alive);
return 1;
}
static int vnc_texid(lua_State *L) {
vnc_t *vnc = checked_vnc(L, 1);
lua_pushnumber(L, vnc->tex);
return 1;
}
static const luaL_reg vnc_methods[] = {
{"draw", vnc_draw},
{"size", vnc_size},
{"alive", vnc_alive},
{"texid", vnc_texid},
{0,0}
};
/* Protocol utils */
static void vnc_printf(vnc_t *vnc, const char *fmt, ...) {
char buffer[16384];
va_list ap;
va_start(ap, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, ap);
va_end(ap);
fprintf(stderr, CYAN("[vnc@%s:%d]")" %s", vnc->host, vnc->port, buffer);
}
static void vnc_set_handler(vnc_t *vnc, protocol_handler handler, int num_bytes) {
vnc->handler = handler;
vnc->num_bytes = num_bytes;
if (evbuffer_get_length(vnc->buf_ev->input) >= vnc->num_bytes)
vnc->handler(vnc);
}
static void vnc_close(vnc_t *vnc) {
if (vnc->buf_ev) {
vnc_printf(vnc, "connection closed\n");
bufferevent_free(vnc->buf_ev);
vnc->buf_ev = NULL;
}
if (vnc->tex) {
glDeleteTextures(1, &vnc->tex);
vnc->tex = 0;
}
vnc->alive = 0;
}
static void vnc_read(struct bufferevent *bev, void *arg) {
vnc_t *vnc = arg;
if (evbuffer_get_length(bev->input) >= vnc->num_bytes)
vnc->handler(vnc);
}
static void vnc_event(struct bufferevent *bev, short events, void *arg) {
vnc_t *vnc = arg;
if (events & BEV_EVENT_CONNECTED) {
vnc_printf(vnc, "connected!\n");
} else if (events & BEV_EVENT_ERROR) {
int err = bufferevent_socket_get_dns_error(bev);
if (err) {
vnc_printf(vnc, "dns error: %s\n", evutil_gai_strerror(err));
} else {
vnc_printf(vnc, "connection error!\n");
}
return vnc_close(vnc);
} else if (events & BEV_EVENT_EOF) {
vnc_printf(vnc, "eof!\n");
return vnc_close(vnc);
}
}
static const int endian_test = 1;
#define is_bigendian() ((*(char*)&endian_test) == 0)
#define swap32(v) ((((v) & 0xff000000) >> 24) | \
(((v) & 0x00ff0000) >> 8) | \
(((v) & 0x0000ff00) << 8) | \
(((v) & 0x000000ff) << 24))
static int vnc_decode(vnc_t *vnc, const unsigned char *pixels) {
unsigned char *converted = malloc(vnc->rect_w * vnc->rect_h * 4);
assert(vnc->pixelformat.bpp == 32);
int row_size = vnc->rect_w * 4;
for (int row = 0; row < vnc->rect_h; row++) {
uint32_t *src = (uint32_t*)(pixels + row * row_size);
uint32_t *dest = (uint32_t*)(converted + (vnc->rect_h - row - 1) * row_size);
for (int col = 0; col < vnc->rect_w; col++) {
uint32_t raw = *src;
if (is_bigendian() ^ vnc->pixelformat.bigendian) {
raw = swap32(raw);
}
uint32_t r =
(raw >> vnc->pixelformat.red_shift) &
vnc->pixelformat.red_max;
uint32_t g =
(raw >> vnc->pixelformat.green_shift) &
vnc->pixelformat.green_max;
uint32_t b =
(raw >> vnc->pixelformat.blue_shift) &
vnc->pixelformat.blue_max;
*dest = 255 << 24 | b << 16 | g << 8 | r;
dest++, src++;
}
}
glBindTexture(GL_TEXTURE_2D, vnc->tex);
glTexSubImage2D(
GL_TEXTURE_2D,
0,
vnc->rect_x,
vnc->height - vnc->rect_y - vnc->rect_h,
vnc->rect_w,
vnc->rect_h,
GL_RGBA,
GL_UNSIGNED_BYTE,
converted
);
glGenerateMipmap(GL_TEXTURE_2D);
free(converted);
return 1;
}
/* Packet definitions */
typedef struct {
uint16_t x;
uint16_t y;
uint16_t w;
uint16_t h;
uint32_t encoding;
} pkt_server_rect;
typedef struct {
uint8_t msg_type;
#define SERVER_MSG_TYPE_FRAMEBUFFER_UPDATE 0
#define SERVER_MSG_TYPE_BELL 2
#define SERVER_MSG_TYPE_CUT_TEXT 3
} pkt_server_base_msg;
typedef struct { /* extends pkt_server_base_msg */
uint8_t msg_type;
uint8_t padding[1];
uint16_t num_rects;
} pkt_server_frameupdate;
typedef struct { /* extends pkt_server_base_msg */
uint8_t msg_type;
uint8_t padding[3];
uint32_t text_len;
} pkt_server_cut_text;
typedef struct {
uint16_t width;
uint16_t height;
pixelformat_t pixelformat;
uint32_t name_len;
} pkt_server_init;
typedef struct {
uint32_t security_type;
#define SERVER_SECURITY_NO_AUTH 1
} pkt_server_auth;
typedef struct {
uint8_t R;
uint8_t F;
uint8_t B;
uint8_t handshake[9];
} pkt_server_handshake;
typedef struct {
uint8_t msg_type;
#define CLIENT_MSG_TYPE_UPDATE_REQUEST 3
uint8_t incremental;
uint16_t x;
uint16_t y;
uint16_t w;
uint16_t h;
} pkt_client_update_request;
typedef struct {
uint8_t shared;
} pkt_client_init;
/* Protocol */
static void vnc_read_msg_header(vnc_t *vnc);
static void vnc_read_rect(vnc_t *vnc);
static void vnc_send_update_request(vnc_t *vnc, int x, int y, int w, int h, int incremental);
static void vnc_read_cut_text(vnc_t *vnc) {
evbuffer_drain(vnc->buf_ev->input, vnc->num_bytes);
return vnc_set_handler(vnc, vnc_read_msg_header, sizeof(pkt_server_base_msg));
}
static void vnc_read_cut(vnc_t *vnc) {
pkt_server_cut_text in_pkt;
evbuffer_remove(vnc->buf_ev->input, &in_pkt, sizeof(in_pkt));
uint32_t text_len = ntohl(in_pkt.text_len);
if (text_len > 2048) {
vnc_printf(vnc, "too large server cut text\n");
return vnc_close(vnc);
}
return vnc_set_handler(vnc, vnc_read_cut_text, text_len);
}
static void vnc_read_rect_data(vnc_t *vnc) {
unsigned char *pixels = evbuffer_pullup(vnc->buf_ev->input, vnc->num_bytes);
if (!vnc_decode(vnc, pixels)) {
vnc_printf(vnc, "decoding failed\n");
return vnc_close(vnc);
}
evbuffer_drain(vnc->buf_ev->input, vnc->num_bytes);
if (--vnc->num_rects == 0) {
return vnc_send_update_request(vnc, 0, 0, vnc->width, vnc->height, 1);
} else {
return vnc_set_handler(vnc, vnc_read_rect, sizeof(pkt_server_rect));
}
}
static void vnc_read_rect(vnc_t *vnc) {
pkt_server_rect in_pkt;
evbuffer_remove(vnc->buf_ev->input, &in_pkt, sizeof(in_pkt));
vnc->rect_x = ntohs(in_pkt.x);
vnc->rect_y = ntohs(in_pkt.y);
vnc->rect_w = ntohs(in_pkt.w);
vnc->rect_h = ntohs(in_pkt.h);
if ((vnc->rect_x + vnc->rect_w > vnc->width) ||
(vnc->rect_y + vnc->rect_h > vnc->height)) {
vnc_printf(vnc, "invalid rect (out of bound)\n");
return vnc_close(vnc);
}
return vnc_set_handler(vnc, vnc_read_rect_data, vnc->pixelformat.bpp / 8 * vnc->rect_w * vnc->rect_h);
}
static void vnc_read_rects(vnc_t *vnc) {
pkt_server_frameupdate in_pkt;
evbuffer_remove(vnc->buf_ev->input, &in_pkt, sizeof(in_pkt));
vnc->num_rects = ntohs(in_pkt.num_rects);
if (vnc->num_rects == 0) {
vnc_printf(vnc, "zero rect update\n");
return vnc_close(vnc);
}
return vnc_set_handler(vnc, vnc_read_rect, sizeof(pkt_server_rect));
}
static void vnc_read_msg_header(vnc_t *vnc) {
// peek into header without removing it
pkt_server_base_msg in_pkt = *(pkt_server_base_msg *)evbuffer_pullup(
vnc->buf_ev->input, sizeof(pkt_server_base_msg));
if (in_pkt.msg_type == SERVER_MSG_TYPE_FRAMEBUFFER_UPDATE) {
return vnc_set_handler(vnc, vnc_read_rects, sizeof(pkt_server_frameupdate));
} else if (in_pkt.msg_type == SERVER_MSG_TYPE_BELL) {
// ignore the bell
evbuffer_drain(vnc->buf_ev->input, sizeof(in_pkt));
return vnc_set_handler(vnc, vnc_read_msg_header, sizeof(pkt_server_base_msg));
} else if (in_pkt.msg_type == SERVER_MSG_TYPE_CUT_TEXT) {
return vnc_set_handler(vnc, vnc_read_cut, sizeof(pkt_server_cut_text));
} else {
vnc_printf(vnc, "unexpected msg_type\n");
return vnc_close(vnc);
}
}
static void vnc_send_update_request(vnc_t *vnc, int x, int y, int w, int h, int incremental) {
pkt_client_update_request out_pkt = {
.msg_type = CLIENT_MSG_TYPE_UPDATE_REQUEST,
.incremental = incremental,
.x = htons(x),
.y = htons(y),
.w = htons(w),
.h = htons(h),
};
bufferevent_write(vnc->buf_ev, &out_pkt, sizeof(out_pkt));
return vnc_set_handler(vnc, vnc_read_msg_header, sizeof(pkt_server_base_msg));
}
static void vnc_read_server_name(vnc_t *vnc) {
evbuffer_drain(vnc->buf_ev->input, vnc->num_bytes);
return vnc_send_update_request(vnc, 0, 0, vnc->width, vnc->height, 0);
}
static void vnc_read_server_init(vnc_t *vnc) {
pkt_server_init in_pkt;
evbuffer_remove(vnc->buf_ev->input, &in_pkt, sizeof(in_pkt));
uint32_t name_len = ntohl(in_pkt.name_len);
if (name_len > 512) {
vnc_printf(vnc, "name too long\n");
return vnc_close(vnc);
}
if (in_pkt.pixelformat.bpp != 32) {
vnc_printf(vnc, "invalid bpp (only 32bit supported)\n");
return vnc_close(vnc);
}
vnc->width = ntohs(in_pkt.width);
vnc->height = ntohs(in_pkt.height);
vnc->pixelformat = in_pkt.pixelformat;
vnc->pixelformat.red_max = ntohs(vnc->pixelformat.red_max);
vnc->pixelformat.green_max = ntohs(vnc->pixelformat.green_max);
vnc->pixelformat.blue_max = ntohs(vnc->pixelformat.blue_max);
if (vnc->width > 1920 || vnc->height > 1080) {
vnc_printf(vnc, "screen too large\n");
return vnc_close(vnc);
}
glGenTextures(1, &vnc->tex);
glBindTexture(GL_TEXTURE_2D, vnc->tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA,
vnc->width,
vnc->height,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
NULL
);
vnc_printf(vnc, "got screen: %dx%d\n", vnc->width, vnc->height);
return vnc_set_handler(vnc, vnc_read_server_name, name_len);
}
static void vnc_read_server_auth(vnc_t *vnc) {
pkt_server_auth in_pkt;
evbuffer_remove(vnc->buf_ev->input, &in_pkt, sizeof(in_pkt));
if (ntohl(in_pkt.security_type) != SERVER_SECURITY_NO_AUTH) {
vnc_printf(vnc, "unexpected security type\n");
return vnc_close(vnc);
}
pkt_client_init out_pkt = {
.shared = 1
};
bufferevent_write(vnc->buf_ev, &out_pkt, sizeof(out_pkt));
return vnc_set_handler(vnc, vnc_read_server_init, sizeof(pkt_server_init));
}
static void vnc_read_handshake(vnc_t *vnc) {
pkt_server_handshake in_pkt;
evbuffer_remove(vnc->buf_ev->input, &in_pkt, sizeof(in_pkt));
if (in_pkt.R != 'R' || in_pkt.F != 'F' || in_pkt.B != 'B') {
vnc_printf(vnc, "unexpected handshake packet\n");
return vnc_close(vnc);
}
bufferevent_write(vnc->buf_ev, LITERAL_AND_SIZE("RFB 003.003\n"));
return vnc_set_handler(vnc, vnc_read_server_auth, sizeof(pkt_server_auth));
}
/* Lifecycle */
int vnc_create(lua_State *L, const char *host, int port) {
vnc_t *vnc = push_vnc(L);
vnc->tex = 0;
vnc->width = 0;
vnc->height = 0;
vnc->buf_ev = NULL;
vnc->alive = 1;
vnc->host = strdup(host);
vnc->port = port;
vnc_printf(vnc, "connecting...\n");
vnc->buf_ev = bufferevent_socket_new(event_base, -1, BEV_OPT_CLOSE_ON_FREE);
vnc_set_handler(vnc, vnc_read_handshake, sizeof(pkt_server_handshake));
bufferevent_setcb(vnc->buf_ev, vnc_read, NULL, vnc_event, vnc);
bufferevent_enable(vnc->buf_ev, EV_READ | EV_WRITE);
bufferevent_socket_connect_hostname(vnc->buf_ev, dns_base, AF_UNSPEC, host, port);
return 1;
}
static int vnc_gc(lua_State *L) {
vnc_t *vnc = to_vnc(L, 1);
vnc_close(vnc);
free(vnc->host);
return 0;
}
LUA_TYPE_IMPL(vnc)