#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include "render_remote.h"
#include "common.h"
struct remote_render_ctx {
struct event ev_conn;
struct bufferevent *data_bev;
#pragma pack(push)
#pragma pack(1)
struct {
u_int8_t mode;
u_int32_t img_w;
u_int32_t img_h;
double x1;
double y1;
double x2;
double y2;
} render_cmd;
#pragma pack(pop)
void (*cb_sent)(void *arg);
void (*cb_data)(struct evbuffer *buf, void *arg);
void (*cb_done)(void *arg);
void (*cb_fail)(void *arg);
void *cb_arg;
};
void _remote_render_ctx_free (struct remote_render_ctx **ctx) {
// free the data_bev
if ((*ctx)->data_bev) {
bufferevent_free((*ctx)->data_bev);
(*ctx)->data_bev = NULL;
}
// free the context structure
free(*ctx);
*ctx = NULL;
}
#define RENDER_FAILED(ctx, desc) \
do { \
perror(desc); \
ctx->cb_fail(ctx->cb_arg); \
_remote_render_ctx_free(&ctx); \
} while (0)
void _remote_write (struct bufferevent *bev, void *arg) {
struct remote_render_ctx *ctx = arg;
// the write buffer was drained, so the render command was sent
ctx->cb_sent(ctx->cb_arg);
// we don't care about EV_WRITE anymore
if (bufferevent_disable(ctx->data_bev, EV_WRITE))
RENDER_FAILED(ctx, "render_remote: bufferevent_disable");
// start receiving data
if (bufferevent_enable(ctx->data_bev, EV_READ))
RENDER_FAILED(ctx, "render_remote: bufferevent_enable");
}
void _remote_read (struct bufferevent *bev, void *arg) {
struct remote_render_ctx *ctx = arg;
// pass the bufferevent's input buffer to our callback - libevent doesn't provide any function to access this, but hopefully this works correctly
ctx->cb_data(bev->input, ctx->cb_arg);
}
void _remote_error (struct bufferevent *bev, short what, void *arg) {
struct remote_render_ctx *ctx = arg;
// OH NOES; WHAT DO WE DO!?
if (what & EVBUFFER_EOF) {
// great!
ctx->cb_done(ctx->cb_arg);
} else if (what & EVBUFFER_ERROR) {
// crap.
perr("render_remote");
ctx->cb_fail(ctx->cb_arg);
} else if (what & EVBUFFER_TIMEOUT) {
// ah well
error("render_remote: timeout");
ctx->cb_fail(ctx->cb_arg);
} else {
err_exit("weird bufferevent error code: 0x%02X", what);
}
// free resources
_remote_render_ctx_free(&ctx);
}
void _remote_connected (int fd, short event, void *arg) {
struct remote_render_ctx *ctx = arg;
// set up the read/write bufferevent
if ((ctx->data_bev = bufferevent_new(fd, &_remote_read, &_remote_write, &_remote_error, ctx)) == NULL)
RENDER_FAILED(ctx, "render_remote: bufferevent_new");
// write the render command
if (bufferevent_write(ctx->data_bev, &ctx->render_cmd, sizeof(ctx->render_cmd)))
RENDER_FAILED(ctx, "render_remote: bufferevent_write");
// wait for it to be written out
if (bufferevent_enable(ctx->data_bev, EV_WRITE))
RENDER_FAILED(ctx, "render_remote: bufferevent_enable");
}
void render_cmd_build (render_t *rctx, struct remote_render_ctx *rrctx) {
// just copy over the render params to the render_cmd
rrctx->render_cmd.mode = rctx->mode;
rrctx->render_cmd.img_w = htonl(rctx->img_w);
rrctx->render_cmd.img_h = htonl(rctx->img_h);
rrctx->render_cmd.x1 = rctx->x1;
rrctx->render_cmd.y1 = rctx->y1;
rrctx->render_cmd.x2 = rctx->x2;
rrctx->render_cmd.y2 = rctx->y2;
}
int render_remote (
render_t *render_ctx,
struct sockaddr_storage *remote,
void (*cb_sent)(void *arg),
void (*cb_data)(struct evbuffer *buf, void *arg),
void (*cb_done)(void *arg),
void (*cb_fail)(void *arg),
void *cb_arg
) {
// alloc the remote render ctx
struct remote_render_ctx *ctx = malloc(sizeof(struct remote_render_ctx));
if (!ctx) {
error("render_remote: malloc");
return -1;
}
// store the provided callback functions
ctx->cb_sent = cb_sent;
ctx->cb_data = cb_data;
ctx->cb_done = cb_done;
ctx->cb_fail = cb_fail;
ctx->cb_arg = cb_arg;
// copy the relevant stuff from the render_ctx
render_cmd_build(render_ctx, ctx);
// create the socket
int sock = socket(remote->ss_family, SOCK_STREAM, 0);
if (sock < 0) {
free(ctx);
perror("render_remote: socket");
return -1;
}
// mark it as nonblocking
if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) {
free(ctx);
close(sock);
perror("render_remote: fcntl");
return -1;
}
// initiate the connect
int err = connect(sock, (struct sockaddr *) remote, sizeof(*remote));
if (err != -1 || errno != EINPROGRESS) {
free(ctx);
close(sock);
perror("render_remote: connect");
return -1;
}
// do the libevent dance
event_set(&ctx->ev_conn, sock, EV_WRITE, &_remote_connected, ctx);
if (event_add(&ctx->ev_conn, NULL)) {
free(ctx);
close(sock);
error("render_remote: event_add");
return -1;
}
// success
return 0;
}