/* * Example debug transport with a local debug message encoder/decoder. * * Provides a "received dvalue" callback for a fully parsed dvalue (user * code frees dvalue) and a "cooperate" callback for e.g. UI integration. * There are a few other callbacks. See test.c for usage examples. * * This transport implementation is not multithreaded which means that: * * - Callbacks to "received dvalue" callback come from the Duktape thread, * either during normal execution or from duk_debugger_cooperate(). * * - Calls into duk_trans_dvalue_send() must be made from the callbacks * provided (e.g. "received dvalue" or "cooperate") which use the active * Duktape thread. * * - The only exception to this is when Duktape is idle: you can then call * duk_trans_dvalue_send() from any thread (only one thread at a time). * When you next call into Duktape or call duk_debugger_cooperate(), the * queued data will be read and processed by Duktape. * * There are functions for creating and freeing values; internally they use * malloc() and free() for memory management. Duktape heap alloc functions * are not used to minimize disturbances to the Duktape heap under debugging. * * Doesn't depend on C99 types; assumes "int" is at least 32 bits, and makes * a few assumptions about format specifiers. */ #include #include #include #include "duktape.h" #include "duk_trans_dvalue.h" /* Define to enable debug prints to stderr. */ #if 0 #define DEBUG_PRINTS #endif /* Define to enable error prints to stderr. */ #if 1 #define ERROR_PRINTS #endif /* * Dvalue handling */ duk_dvalue *duk_dvalue_alloc(void) { duk_dvalue *dv = (duk_dvalue *) malloc(sizeof(duk_dvalue)); if (dv) { memset((void *) dv, 0, sizeof(duk_dvalue)); dv->buf = NULL; } return dv; } void duk_dvalue_free(duk_dvalue *dv) { if (dv) { free(dv->buf); /* tolerates NULL */ dv->buf = NULL; free(dv); } } static void duk__dvalue_bufesc(duk_dvalue *dv, char *buf, size_t maxbytes, int stresc) { size_t i, limit; *buf = (char) 0; limit = dv->len > maxbytes ? maxbytes : dv->len; for (i = 0; i < limit; i++) { unsigned char c = dv->buf[i]; if (stresc) { if (c >= 0x20 && c <= 0x7e && c != (char) '"' && c != (char) '\'') { sprintf(buf, "%c", c); buf++; } else { sprintf(buf, "\\x%02x", (unsigned int) c); buf += 4; } } else { sprintf(buf, "%02x", (unsigned int) c); buf += 2; } } if (dv->len > maxbytes) { sprintf(buf, "..."); buf += 3; } } /* Caller must provide a buffer at least DUK_DVALUE_TOSTRING_BUFLEN in size. */ void duk_dvalue_to_string(duk_dvalue *dv, char *buf) { char hexbuf[32 * 4 + 4]; /* 32 hex encoded or \xXX escaped bytes, possible "...", NUL */ if (!dv) { sprintf(buf, "NULL"); return; } switch (dv->tag) { case DUK_DVALUE_EOM: sprintf(buf, "EOM"); break; case DUK_DVALUE_REQ: sprintf(buf, "REQ"); break; case DUK_DVALUE_REP: sprintf(buf, "REP"); break; case DUK_DVALUE_ERR: sprintf(buf, "ERR"); break; case DUK_DVALUE_NFY: sprintf(buf, "NFY"); break; case DUK_DVALUE_INTEGER: sprintf(buf, "%d", dv->i); break; case DUK_DVALUE_STRING: duk__dvalue_bufesc(dv, hexbuf, 32, 1); sprintf(buf, "str:%ld:\"%s\"", (long) dv->len, hexbuf); break; case DUK_DVALUE_BUFFER: duk__dvalue_bufesc(dv, hexbuf, 32, 0); sprintf(buf, "buf:%ld:%s", (long) dv->len, hexbuf); break; case DUK_DVALUE_UNUSED: sprintf(buf, "undefined"); break; case DUK_DVALUE_UNDEFINED: sprintf(buf, "undefined"); break; case DUK_DVALUE_NULL: sprintf(buf, "null"); break; case DUK_DVALUE_TRUE: sprintf(buf, "true"); break; case DUK_DVALUE_FALSE: sprintf(buf, "false"); break; case DUK_DVALUE_NUMBER: if (fpclassify(dv->d) == FP_ZERO) { if (signbit(dv->d)) { sprintf(buf, "-0"); } else { sprintf(buf, "0"); } } else { sprintf(buf, "%lg", dv->d); } break; case DUK_DVALUE_OBJECT: duk__dvalue_bufesc(dv, hexbuf, 32, 0); sprintf(buf, "obj:%d:%s", (int) dv->i, hexbuf); break; case DUK_DVALUE_POINTER: duk__dvalue_bufesc(dv, hexbuf, 32, 0); sprintf(buf, "ptr:%s", hexbuf); break; case DUK_DVALUE_LIGHTFUNC: duk__dvalue_bufesc(dv, hexbuf, 32, 0); sprintf(buf, "lfunc:%04x:%s", (unsigned int) dv->i, hexbuf); break; case DUK_DVALUE_HEAPPTR: duk__dvalue_bufesc(dv, hexbuf, 32, 0); sprintf(buf, "heapptr:%s", hexbuf); break; default: sprintf(buf, "unknown:%d", (int) dv->tag); } } duk_dvalue *duk_dvalue_make_tag(int tag) { duk_dvalue *dv = duk_dvalue_alloc(); if (!dv) { return NULL; } dv->tag = tag; return dv; } duk_dvalue *duk_dvalue_make_tag_int(int tag, int intval) { duk_dvalue *dv = duk_dvalue_alloc(); if (!dv) { return NULL; } dv->tag = tag; dv->i = intval; return dv; } duk_dvalue *duk_dvalue_make_tag_double(int tag, double dblval) { duk_dvalue *dv = duk_dvalue_alloc(); if (!dv) { return NULL; } dv->tag = tag; dv->d = dblval; return dv; } duk_dvalue *duk_dvalue_make_tag_data(int tag, const char *buf, size_t len) { unsigned char *p; duk_dvalue *dv = duk_dvalue_alloc(); if (!dv) { return NULL; } /* Alloc size is len + 1 so that a NUL terminator is always * guaranteed which is convenient, e.g. you can printf() the * value safely. */ p = (unsigned char *) malloc(len + 1); if (!p) { free(dv); return NULL; } memcpy((void *) p, (const void *) buf, len); p[len] = (unsigned char) 0; dv->tag = tag; dv->buf = p; dv->len = len; return dv; } duk_dvalue *duk_dvalue_make_tag_int_data(int tag, int intval, const char *buf, size_t len) { duk_dvalue *dv = duk_dvalue_make_tag_data(tag, buf, len); if (!dv) { return NULL; } dv->i = intval; return dv; } /* * Dvalue transport handling */ static void duk__trans_dvalue_double_byteswap(duk_trans_dvalue_ctx *ctx, volatile unsigned char *p) { unsigned char t; /* Portable IEEE double byteswap. Relies on runtime detection of * host endianness. */ if (ctx->double_byteorder == 0) { /* little endian */ t = p[0]; p[0] = p[7]; p[7] = t; t = p[1]; p[1] = p[6]; p[6] = t; t = p[2]; p[2] = p[5]; p[5] = t; t = p[3]; p[3] = p[4]; p[4] = t; } else if (ctx->double_byteorder == 1) { /* big endian: ok as is */ ; } else { /* mixed endian */ t = p[0]; p[0] = p[3]; p[3] = t; t = p[1]; p[1] = p[2]; p[2] = t; t = p[4]; p[4] = p[7]; p[7] = t; t = p[5]; p[5] = p[6]; p[6] = t; } } static unsigned int duk__trans_dvalue_parse_u32(duk_trans_dvalue_ctx *ctx, unsigned char *p) { /* Integers are network endian, read back into host format in * a portable manner. */ (void) ctx; return (((unsigned int) p[0]) << 24) + (((unsigned int) p[1]) << 16) + (((unsigned int) p[2]) << 8) + (((unsigned int) p[3]) << 0); } static int duk__trans_dvalue_parse_i32(duk_trans_dvalue_ctx *ctx, unsigned char *p) { /* Portable sign handling, doesn't assume 'int' is exactly 32 bits * like a direct cast would. */ unsigned int tmp = duk__trans_dvalue_parse_u32(ctx, p); if (tmp & 0x80000000UL) { return -((int) ((tmp ^ 0xffffffffUL) + 1UL)); } else { return tmp; } } static unsigned int duk__trans_dvalue_parse_u16(duk_trans_dvalue_ctx *ctx, unsigned char *p) { /* Integers are network endian, read back into host format. */ (void) ctx; return (((unsigned int) p[0]) << 8) + (((unsigned int) p[1]) << 0); } static double duk__trans_dvalue_parse_double(duk_trans_dvalue_ctx *ctx, unsigned char *p) { /* IEEE doubles are network endian, read back into host format. */ volatile union { double d; unsigned char b[8]; } u; memcpy((void *) u.b, (const void *) p, 8); duk__trans_dvalue_double_byteswap(ctx, u.b); return u.d; } static unsigned char *duk__trans_dvalue_encode_u32(duk_trans_dvalue_ctx *ctx, unsigned char *p, unsigned int val) { /* Integers are written in network endian format. */ (void) ctx; *p++ = (unsigned char) ((val >> 24) & 0xff); *p++ = (unsigned char) ((val >> 16) & 0xff); *p++ = (unsigned char) ((val >> 8) & 0xff); *p++ = (unsigned char) (val & 0xff); return p; } static unsigned char *duk__trans_dvalue_encode_i32(duk_trans_dvalue_ctx *ctx, unsigned char *p, int val) { return duk__trans_dvalue_encode_u32(ctx, p, (unsigned int) val & 0xffffffffUL); } static unsigned char *duk__trans_dvalue_encode_u16(duk_trans_dvalue_ctx *ctx, unsigned char *p, unsigned int val) { /* Integers are written in network endian format. */ (void) ctx; *p++ = (unsigned char) ((val >> 8) & 0xff); *p++ = (unsigned char) (val & 0xff); return p; } static unsigned char *duk__trans_dvalue_encode_double(duk_trans_dvalue_ctx *ctx, unsigned char *p, double val) { /* IEEE doubles are written in network endian format. */ volatile union { double d; unsigned char b[8]; } u; u.d = val; duk__trans_dvalue_double_byteswap(ctx, u.b); memcpy((void *) p, (const void *) u.b, 8); p += 8; return p; } static unsigned char *duk__trans_buffer_ensure(duk_trans_buffer *dbuf, size_t space) { size_t avail; size_t used; size_t new_size; void *new_alloc; used = dbuf->write_offset; avail = dbuf->alloc_size - dbuf->write_offset; if (avail >= space) { if (avail - space > 256) { /* Too big, resize so that we reclaim memory if we have just * received a large string/buffer value. */ goto do_realloc; } } else { /* Too small, resize. */ goto do_realloc; } return dbuf->base + dbuf->write_offset; do_realloc: new_size = used + space + 256; /* some extra to reduce resizes */ new_alloc = realloc(dbuf->base, new_size); if (new_alloc) { dbuf->base = (unsigned char *) new_alloc; dbuf->alloc_size = new_size; #if defined(DEBUG_PRINTS) fprintf(stderr, "%s: resized buffer %p to %ld bytes, read_offset=%ld, write_offset=%ld\n", __func__, (void *) dbuf, (long) new_size, (long) dbuf->read_offset, (long) dbuf->write_offset); fflush(stderr); #endif return dbuf->base + dbuf->write_offset; } else { return NULL; } } /* When read_offset is large enough, "rebase" buffer by deleting already * read data and updating offsets. */ static void duk__trans_buffer_rebase(duk_trans_buffer *dbuf) { if (dbuf->read_offset > 64) { #if defined(DEBUG_PRINTS) fprintf(stderr, "%s: rebasing buffer %p, read_offset=%ld, write_offset=%ld\n", __func__, (void *) dbuf, (long) dbuf->read_offset, (long) dbuf->write_offset); fflush(stderr); #endif if (dbuf->write_offset > dbuf->read_offset) { memmove((void *) dbuf->base, (const void *) (dbuf->base + dbuf->read_offset), dbuf->write_offset - dbuf->read_offset); } dbuf->write_offset -= dbuf->read_offset; dbuf->read_offset = 0; } } duk_trans_dvalue_ctx *duk_trans_dvalue_init(void) { volatile union { double d; unsigned char b[8]; } u; duk_trans_dvalue_ctx *ctx = NULL; ctx = (duk_trans_dvalue_ctx *) malloc(sizeof(duk_trans_dvalue_ctx)); if (!ctx) { goto fail; } memset((void *) ctx, 0, sizeof(duk_trans_dvalue_ctx)); ctx->received = NULL; ctx->cooperate = NULL; ctx->handshake = NULL; ctx->detached = NULL; ctx->send_buf.base = NULL; ctx->recv_buf.base = NULL; ctx->send_buf.base = malloc(256); if (!ctx->send_buf.base) { goto fail; } ctx->send_buf.alloc_size = 256; ctx->recv_buf.base = malloc(256); if (!ctx->recv_buf.base) { goto fail; } ctx->recv_buf.alloc_size = 256; /* IEEE double byte order, detect at run time (could also use * preprocessor defines but that's verbose to make portable). * * >>> struct.unpack('>d', '1122334455667788'.decode('hex')) * (3.841412024471731e-226,) * >>> struct.unpack('>d', '8877665544332211'.decode('hex')) * (-7.086876636573014e-268,) * >>> struct.unpack('>d', '4433221188776655'.decode('hex')) * (3.5294303071877444e+20,) */ u.b[0] = 0x11; u.b[1] = 0x22; u.b[2] = 0x33; u.b[3] = 0x44; u.b[4] = 0x55; u.b[5] = 0x66; u.b[6] = 0x77; u.b[7] = 0x88; if (u.d < 0.0) { ctx->double_byteorder = 0; /* little endian */ } else if (u.d < 1.0) { ctx->double_byteorder = 1; /* big endian */ } else { ctx->double_byteorder = 2; /* mixed endian (arm) */ } #if defined(DEBUG_PRINTS) fprintf(stderr, "double endianness test value is %lg -> byteorder %d\n", u.d, ctx->double_byteorder); fflush(stderr); #endif return ctx; fail: if (ctx) { free(ctx->recv_buf.base); /* tolerates NULL */ free(ctx->send_buf.base); /* tolerates NULL */ free(ctx); } return NULL; } void duk_trans_dvalue_free(duk_trans_dvalue_ctx *ctx) { if (ctx) { free(ctx->send_buf.base); /* tolerates NULL */ free(ctx->recv_buf.base); /* tolerates NULL */ free(ctx); } } void duk_trans_dvalue_send(duk_trans_dvalue_ctx *ctx, duk_dvalue *dv) { unsigned char *p; /* Convert argument dvalue into Duktape debug protocol format. * Literal constants are used here for the debug protocol, * e.g. initial byte 0x02 is REP, see doc/debugger.rst. */ #if defined(DEBUG_PRINTS) { char buf[DUK_DVALUE_TOSTRING_BUFLEN]; duk_dvalue_to_string(dv, buf); fprintf(stderr, "%s: sending dvalue: %s\n", __func__, buf); fflush(stderr); } #endif switch (dv->tag) { case DUK_DVALUE_EOM: { p = duk__trans_buffer_ensure(&ctx->send_buf, 1); if (!p) { goto alloc_error; } *p++ = 0x00; ctx->send_buf.write_offset += 1; break; } case DUK_DVALUE_REQ: { p = duk__trans_buffer_ensure(&ctx->send_buf, 1); if (!p) { goto alloc_error; } *p++ = 0x01; ctx->send_buf.write_offset += 1; break; } case DUK_DVALUE_REP: { p = duk__trans_buffer_ensure(&ctx->send_buf, 1); if (!p) { goto alloc_error; } *p++ = 0x02; ctx->send_buf.write_offset += 1; break; } case DUK_DVALUE_ERR: { p = duk__trans_buffer_ensure(&ctx->send_buf, 1); if (!p) { goto alloc_error; } *p++ = 0x03; ctx->send_buf.write_offset += 1; break; } case DUK_DVALUE_NFY: { p = duk__trans_buffer_ensure(&ctx->send_buf, 1); if (!p) { goto alloc_error; } *p++ = 0x04; ctx->send_buf.write_offset += 1; break; } case DUK_DVALUE_INTEGER: { int i = dv->i; if (i >= 0 && i <= 63) { p = duk__trans_buffer_ensure(&ctx->send_buf, 1); if (!p) { goto alloc_error; } *p++ = (unsigned char) (0x80 + i); ctx->send_buf.write_offset += 1; } else if (i >= 0 && i <= 16383L) { p = duk__trans_buffer_ensure(&ctx->send_buf, 2); if (!p) { goto alloc_error; } *p++ = (unsigned char) (0xc0 + (i >> 8)); *p++ = (unsigned char) (i & 0xff); ctx->send_buf.write_offset += 2; } else if (i >= -0x80000000L && i <= 0x7fffffffL) { /* Harmless warning on some platforms (re: range) */ p = duk__trans_buffer_ensure(&ctx->send_buf, 5); if (!p) { goto alloc_error; } *p++ = 0x10; p = duk__trans_dvalue_encode_i32(ctx, p, i); ctx->send_buf.write_offset += 5; } else { goto dvalue_error; } break; } case DUK_DVALUE_STRING: { size_t i = dv->len; if (i <= 0x1fUL) { p = duk__trans_buffer_ensure(&ctx->send_buf, 1 + i); if (!p) { goto alloc_error; } *p++ = (unsigned char) (0x60 + i); memcpy((void *) p, (const void *) dv->buf, i); p += i; ctx->send_buf.write_offset += 1 + i; } else if (i <= 0xffffUL) { p = duk__trans_buffer_ensure(&ctx->send_buf, 3 + i); if (!p) { goto alloc_error; } *p++ = 0x12; p = duk__trans_dvalue_encode_u16(ctx, p, (unsigned int) i); memcpy((void *) p, (const void *) dv->buf, i); p += i; ctx->send_buf.write_offset += 3 + i; } else if (i <= 0xffffffffUL) { p = duk__trans_buffer_ensure(&ctx->send_buf, 5 + i); if (!p) { goto alloc_error; } *p++ = 0x11; p = duk__trans_dvalue_encode_u32(ctx, p, (unsigned int) i); memcpy((void *) p, (const void *) dv->buf, i); p += i; ctx->send_buf.write_offset += 5 + i; } else { goto dvalue_error; } break; } case DUK_DVALUE_BUFFER: { size_t i = dv->len; if (i <= 0xffffUL) { p = duk__trans_buffer_ensure(&ctx->send_buf, 3 + i); if (!p) { goto alloc_error; } *p++ = 0x14; p = duk__trans_dvalue_encode_u16(ctx, p, (unsigned int) i); memcpy((void *) p, (const void *) dv->buf, i); p += i; ctx->send_buf.write_offset += 3 + i; } else if (i <= 0xffffffffUL) { p = duk__trans_buffer_ensure(&ctx->send_buf, 5 + i); if (!p) { goto alloc_error; } *p++ = 0x13; p = duk__trans_dvalue_encode_u32(ctx, p, (unsigned int) i); memcpy((void *) p, (const void *) dv->buf, i); p += i; ctx->send_buf.write_offset += 5 + i; } else { goto dvalue_error; } break; } case DUK_DVALUE_UNUSED: { p = duk__trans_buffer_ensure(&ctx->send_buf, 1); if (!p) { goto alloc_error; } *p++ = 0x15; ctx->send_buf.write_offset += 1; break; } case DUK_DVALUE_UNDEFINED: { p = duk__trans_buffer_ensure(&ctx->send_buf, 1); if (!p) { goto alloc_error; } *p++ = 0x16; ctx->send_buf.write_offset += 1; break; } case DUK_DVALUE_NULL: { p = duk__trans_buffer_ensure(&ctx->send_buf, 1); if (!p) { goto alloc_error; } *p++ = 0x17; ctx->send_buf.write_offset += 1; break; } case DUK_DVALUE_TRUE: { p = duk__trans_buffer_ensure(&ctx->send_buf, 1); if (!p) { goto alloc_error; } *p++ = 0x18; ctx->send_buf.write_offset += 1; break; } case DUK_DVALUE_FALSE: { p = duk__trans_buffer_ensure(&ctx->send_buf, 1); if (!p) { goto alloc_error; } *p++ = 0x19; ctx->send_buf.write_offset += 1; break; } case DUK_DVALUE_NUMBER: { p = duk__trans_buffer_ensure(&ctx->send_buf, 9); if (!p) { goto alloc_error; } *p++ = 0x1a; p = duk__trans_dvalue_encode_double(ctx, p, dv->d); ctx->send_buf.write_offset += 9; break; } case DUK_DVALUE_OBJECT: { size_t i = dv->len; if (i <= 0xffUL && dv->i >= 0 && dv->i <= 0xffL) { p = duk__trans_buffer_ensure(&ctx->send_buf, 3 + i); if (!p) { goto alloc_error; } *p++ = 0x1b; *p++ = (unsigned char) dv->i; *p++ = (unsigned char) i; memcpy((void *) p, (const void *) dv->buf, i); ctx->send_buf.write_offset += 3 + i; } else { goto dvalue_error; } break; } case DUK_DVALUE_POINTER: { size_t i = dv->len; if (i <= 0xffUL) { p = duk__trans_buffer_ensure(&ctx->send_buf, 2 + i); if (!p) { goto alloc_error; } *p++ = 0x1c; *p++ = (unsigned char) i; memcpy((void *) p, (const void *) dv->buf, i); ctx->send_buf.write_offset += 2 + i; } else { goto dvalue_error; } break; } case DUK_DVALUE_LIGHTFUNC: { size_t i = dv->len; if (i <= 0xffUL && dv->i >= 0 && dv->i <= 0xffffL) { p = duk__trans_buffer_ensure(&ctx->send_buf, 4 + i); if (!p) { goto alloc_error; } *p++ = 0x1d; p = duk__trans_dvalue_encode_u16(ctx, p, (unsigned int) dv->i); *p++ = (unsigned char) i; memcpy((void *) p, (const void *) dv->buf, i); ctx->send_buf.write_offset += 4 + i; } else { goto dvalue_error; } break; } case DUK_DVALUE_HEAPPTR: { size_t i = dv->len; if (i <= 0xffUL) { p = duk__trans_buffer_ensure(&ctx->send_buf, 2 + i); if (!p) { goto alloc_error; } *p++ = 0x1e; *p++ = (unsigned char) i; memcpy((void *) p, (const void *) dv->buf, i); ctx->send_buf.write_offset += 2 + i; } else { goto dvalue_error; } break; } default: { goto dvalue_error; } } /* end switch */ return; dvalue_error: #if defined(ERROR_PRINTS) fprintf(stderr, "%s: internal error, argument dvalue is invalid\n", __func__); fflush(stdout); #endif return; alloc_error: #if defined(ERROR_PRINTS) fprintf(stderr, "%s: internal error, failed to allocate space for write\n", __func__); fflush(stdout); #endif return; } static void duk__trans_dvalue_send_and_free(duk_trans_dvalue_ctx *ctx, duk_dvalue *dv) { if (!dv) { return; } duk_trans_dvalue_send(ctx, dv); duk_dvalue_free(dv); } void duk_trans_dvalue_send_eom(duk_trans_dvalue_ctx *ctx) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag(DUK_DVALUE_EOM)); } void duk_trans_dvalue_send_req(duk_trans_dvalue_ctx *ctx) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag(DUK_DVALUE_REQ)); } void duk_trans_dvalue_send_rep(duk_trans_dvalue_ctx *ctx) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag(DUK_DVALUE_REP)); } void duk_trans_dvalue_send_err(duk_trans_dvalue_ctx *ctx) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag(DUK_DVALUE_ERR)); } void duk_trans_dvalue_send_nfy(duk_trans_dvalue_ctx *ctx) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag(DUK_DVALUE_NFY)); } void duk_trans_dvalue_send_integer(duk_trans_dvalue_ctx *ctx, int val) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag_int(DUK_DVALUE_INTEGER, val)); } void duk_trans_dvalue_send_string(duk_trans_dvalue_ctx *ctx, const char *str) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag_data(DUK_DVALUE_STRING, str, strlen(str))); } void duk_trans_dvalue_send_lstring(duk_trans_dvalue_ctx *ctx, const char *str, size_t len) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag_data(DUK_DVALUE_STRING, str, len)); } void duk_trans_dvalue_send_buffer(duk_trans_dvalue_ctx *ctx, const char *buf, size_t len) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag_data(DUK_DVALUE_BUFFER, buf, len)); } void duk_trans_dvalue_send_unused(duk_trans_dvalue_ctx *ctx) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag(DUK_DVALUE_UNUSED)); } void duk_trans_dvalue_send_undefined(duk_trans_dvalue_ctx *ctx) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag(DUK_DVALUE_UNDEFINED)); } void duk_trans_dvalue_send_null(duk_trans_dvalue_ctx *ctx) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag(DUK_DVALUE_NULL)); } void duk_trans_dvalue_send_true(duk_trans_dvalue_ctx *ctx) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag(DUK_DVALUE_TRUE)); } void duk_trans_dvalue_send_false(duk_trans_dvalue_ctx *ctx) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag(DUK_DVALUE_FALSE)); } void duk_trans_dvalue_send_number(duk_trans_dvalue_ctx *ctx, double val) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag_double(DUK_DVALUE_NUMBER, val)); } void duk_trans_dvalue_send_object(duk_trans_dvalue_ctx *ctx, int classnum, const char *ptr_data, size_t ptr_len) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag_int_data(DUK_DVALUE_OBJECT, classnum, ptr_data, ptr_len)); } void duk_trans_dvalue_send_pointer(duk_trans_dvalue_ctx *ctx, const char *ptr_data, size_t ptr_len) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag_data(DUK_DVALUE_POINTER, ptr_data, ptr_len)); } void duk_trans_dvalue_send_lightfunc(duk_trans_dvalue_ctx *ctx, int lf_flags, const char *ptr_data, size_t ptr_len) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag_int_data(DUK_DVALUE_LIGHTFUNC, lf_flags, ptr_data, ptr_len)); } void duk_trans_dvalue_send_heapptr(duk_trans_dvalue_ctx *ctx, const char *ptr_data, size_t ptr_len) { duk__trans_dvalue_send_and_free(ctx, duk_dvalue_make_tag_data(DUK_DVALUE_HEAPPTR, ptr_data, ptr_len)); } void duk_trans_dvalue_send_req_cmd(duk_trans_dvalue_ctx *ctx, int cmd) { duk_trans_dvalue_send_req(ctx); duk_trans_dvalue_send_integer(ctx, cmd); } static duk_dvalue *duk__trans_trial_parse_dvalue(duk_trans_dvalue_ctx *ctx) { unsigned char *p; size_t len; unsigned char ib; duk_dvalue *dv; size_t datalen; p = ctx->recv_buf.base + ctx->recv_buf.read_offset; len = ctx->recv_buf.write_offset - ctx->recv_buf.read_offset; if (len == 0) { return NULL; } ib = p[0]; #if defined(DEBUG_PRINTS) { size_t i; fprintf(stderr, "%s: parsing dvalue, window:", __func__); for (i = 0; i < 16; i++) { if (i < len) { fprintf(stderr, " %02x", (unsigned int) p[i]); } else { fprintf(stderr, " ??"); } } fprintf(stderr, " (length %ld, read_offset %ld, write_offset %ld, alloc_size %ld)\n", (long) len, (long) ctx->recv_buf.read_offset, (long) ctx->recv_buf.write_offset, (long) ctx->recv_buf.alloc_size); fflush(stderr); } #endif if (ib <= 0x1fU) { /* 0x00 ... 0x1f */ switch (ib) { case 0x00: { ctx->recv_buf.read_offset += 1; dv = duk_dvalue_make_tag(DUK_DVALUE_EOM); if (!dv) { goto alloc_error; } return dv; } case 0x01: { ctx->recv_buf.read_offset += 1; dv = duk_dvalue_make_tag(DUK_DVALUE_REQ); if (!dv) { goto alloc_error; } return dv; } case 0x02: { ctx->recv_buf.read_offset += 1; dv = duk_dvalue_make_tag(DUK_DVALUE_REP); if (!dv) { goto alloc_error; } return dv; } case 0x03: { ctx->recv_buf.read_offset += 1; dv = duk_dvalue_make_tag(DUK_DVALUE_ERR); if (!dv) { goto alloc_error; } return dv; } case 0x04: { ctx->recv_buf.read_offset += 1; dv = duk_dvalue_make_tag(DUK_DVALUE_NFY); if (!dv) { goto alloc_error; } return dv; } case 0x10: { int intval; if (len < 5) { goto partial; } intval = duk__trans_dvalue_parse_i32(ctx, p + 1); ctx->recv_buf.read_offset += 5; dv = duk_dvalue_make_tag_int(DUK_DVALUE_INTEGER, intval); if (!dv) { goto alloc_error; } return dv; } case 0x11: { if (len < 5) { goto partial; } datalen = (size_t) duk__trans_dvalue_parse_u32(ctx, p + 1); if (len < 5 + datalen) { goto partial; } ctx->recv_buf.read_offset += 5 + datalen; dv = duk_dvalue_make_tag_data(DUK_DVALUE_STRING, (const char *) (p + 5), datalen); if (!dv) { goto alloc_error; } return dv; } case 0x12: { if (len < 3) { goto partial; } datalen = (size_t) duk__trans_dvalue_parse_u16(ctx, p + 1); if (len < 3 + datalen) { goto partial; } ctx->recv_buf.read_offset += 3 + datalen; dv = duk_dvalue_make_tag_data(DUK_DVALUE_STRING, (const char *) (p + 3), datalen); if (!dv) { goto alloc_error; } return dv; } case 0x13: { if (len < 5) { goto partial; } datalen = (size_t) duk__trans_dvalue_parse_u32(ctx, p + 1); if (len < 5 + datalen) { goto partial; } ctx->recv_buf.read_offset += 5 + datalen; dv = duk_dvalue_make_tag_data(DUK_DVALUE_BUFFER, (const char *) (p + 5), datalen); if (!dv) { goto alloc_error; } return dv; } case 0x14: { if (len < 3) { goto partial; } datalen = (size_t) duk__trans_dvalue_parse_u16(ctx, p + 1); if (len < 3 + datalen) { goto partial; } ctx->recv_buf.read_offset += 3 + datalen; dv = duk_dvalue_make_tag_data(DUK_DVALUE_BUFFER, (const char *) (p + 3), datalen); if (!dv) { goto alloc_error; } return dv; } case 0x15: { ctx->recv_buf.read_offset += 1; dv = duk_dvalue_make_tag(DUK_DVALUE_UNUSED); if (!dv) { goto alloc_error; } return dv; } case 0x16: { ctx->recv_buf.read_offset += 1; dv = duk_dvalue_make_tag(DUK_DVALUE_UNDEFINED); if (!dv) { goto alloc_error; } return dv; } case 0x17: { ctx->recv_buf.read_offset += 1; dv = duk_dvalue_make_tag(DUK_DVALUE_NULL); if (!dv) { goto alloc_error; } return dv; } case 0x18: { ctx->recv_buf.read_offset += 1; dv = duk_dvalue_make_tag(DUK_DVALUE_TRUE); if (!dv) { goto alloc_error; } return dv; } case 0x19: { ctx->recv_buf.read_offset += 1; dv = duk_dvalue_make_tag(DUK_DVALUE_FALSE); if (!dv) { goto alloc_error; } return dv; } case 0x1a: { double dblval; if (len < 9) { goto partial; } dblval = duk__trans_dvalue_parse_double(ctx, p + 1); ctx->recv_buf.read_offset += 9; dv = duk_dvalue_make_tag_double(DUK_DVALUE_NUMBER, dblval); if (!dv) { goto alloc_error; } return dv; } case 0x1b: { int classnum; if (len < 3) { goto partial; } datalen = (size_t) p[2]; if (len < 3 + datalen) { goto partial; } classnum = (int) p[1]; ctx->recv_buf.read_offset += 3 + datalen; dv = duk_dvalue_make_tag_int_data(DUK_DVALUE_OBJECT, classnum, (const char *) (p + 3), datalen); if (!dv) { goto alloc_error; } return dv; } case 0x1c: { if (len < 2) { goto partial; } datalen = (size_t) p[1]; if (len < 2 + datalen) { goto partial; } ctx->recv_buf.read_offset += 2 + datalen; dv = duk_dvalue_make_tag_data(DUK_DVALUE_POINTER, (const char *) (p + 2), datalen); if (!dv) { goto alloc_error; } return dv; } case 0x1d: { int lf_flags; if (len < 4) { goto partial; } datalen = (size_t) p[3]; if (len < 4 + datalen) { goto partial; } lf_flags = (int) duk__trans_dvalue_parse_u16(ctx, p + 1); ctx->recv_buf.read_offset += 4 + datalen; dv = duk_dvalue_make_tag_int_data(DUK_DVALUE_LIGHTFUNC, lf_flags, (const char *) (p + 4), datalen); if (!dv) { goto alloc_error; } return dv; } case 0x1e: { if (len < 2) { goto partial; } datalen = (size_t) p[1]; if (len < 2 + datalen) { goto partial; } ctx->recv_buf.read_offset += 2 + datalen; dv = duk_dvalue_make_tag_data(DUK_DVALUE_HEAPPTR, (const char *) (p + 2), datalen); if (!dv) { goto alloc_error; } return dv; } default: { goto format_error; } } /* end switch */ } else if (ib <= 0x5fU) { /* 0x20 ... 0x5f */ goto format_error; } else if (ib <= 0x7fU) { /* 0x60 ... 0x7f */ datalen = (size_t) (ib - 0x60U); if (len < 1 + datalen) { goto partial; } ctx->recv_buf.read_offset += 1 + datalen; dv = duk_dvalue_make_tag_data(DUK_DVALUE_STRING, (const char *) (p + 1), datalen); if (!dv) { goto alloc_error; } return dv; } else if (ib <= 0xbfU) { /* 0x80 ... 0xbf */ int intval; intval = (int) (ib - 0x80U); ctx->recv_buf.read_offset += 1; dv = duk_dvalue_make_tag_int(DUK_DVALUE_INTEGER, intval); if (!dv) { goto alloc_error; } return dv; } else { /* 0xc0 ... 0xff */ int intval; if (len < 2) { goto partial; } intval = (((int) (ib - 0xc0U)) << 8) + (int) p[1]; ctx->recv_buf.read_offset += 2; dv = duk_dvalue_make_tag_int(DUK_DVALUE_INTEGER, intval); if (!dv) { goto alloc_error; } return dv; } /* never here */ partial: return NULL; alloc_error: #if defined(ERROR_PRINTS) fprintf(stderr, "%s: internal error, cannot allocate space for dvalue\n", __func__); fflush(stdout); #endif return NULL; format_error: #if defined(ERROR_PRINTS) fprintf(stderr, "%s: internal error, dvalue format error\n", __func__); fflush(stdout); #endif return NULL; } static duk_dvalue *duk__trans_trial_parse_handshake(duk_trans_dvalue_ctx *ctx) { unsigned char *p; size_t len; duk_dvalue *dv; size_t i; p = ctx->recv_buf.base + ctx->recv_buf.read_offset; len = ctx->recv_buf.write_offset - ctx->recv_buf.read_offset; for (i = 0; i < len; i++) { if (p[i] == 0x0a) { /* Handshake line is returned as a dvalue for convenience; it's * not actually a part of the dvalue phase of the protocol. */ ctx->recv_buf.read_offset += i + 1; dv = duk_dvalue_make_tag_data(DUK_DVALUE_STRING, (const char *) p, i); if (!dv) { goto alloc_error; } return dv; } } return NULL; alloc_error: #if defined(ERROR_PRINTS) fprintf(stderr, "%s: internal error, cannot allocate space for handshake line\n", __func__); fflush(stdout); #endif return NULL; } static void duk__trans_call_cooperate(duk_trans_dvalue_ctx *ctx, int block) { if (ctx->cooperate) { ctx->cooperate(ctx, block); } } static void duk__trans_call_received(duk_trans_dvalue_ctx *ctx, duk_dvalue *dv) { if (ctx->received) { ctx->received(ctx, dv); } } static void duk__trans_call_handshake(duk_trans_dvalue_ctx *ctx, const char *line) { if (ctx->handshake) { ctx->handshake(ctx, line); } } static void duk__trans_call_detached(duk_trans_dvalue_ctx *ctx) { if (ctx->detached) { ctx->detached(ctx); } } /* * Duktape callbacks */ duk_size_t duk_trans_dvalue_read_cb(void *udata, char *buffer, duk_size_t length) { duk_trans_dvalue_ctx *ctx = (duk_trans_dvalue_ctx *) udata; #if defined(DEBUG_PRINTS) fprintf(stderr, "%s: %p %p %ld\n", __func__, udata, (void *) buffer, (long) length); fflush(stderr); #endif duk__trans_call_cooperate(ctx, 0); for (;;) { size_t avail, now; avail = (size_t) (ctx->send_buf.write_offset - ctx->send_buf.read_offset); if (avail == 0) { /* Must cooperate until user callback provides data. From * Duktape's perspective we MUST block until data is received. */ duk__trans_call_cooperate(ctx, 1); } else { now = avail; if (now > length) { now = length; } memcpy((void *) buffer, (const void *) (ctx->send_buf.base + ctx->send_buf.read_offset), now); duk__trans_buffer_rebase(&ctx->send_buf); ctx->send_buf.read_offset += now; return now; } } } duk_size_t duk_trans_dvalue_write_cb(void *udata, const char *buffer, duk_size_t length) { duk_trans_dvalue_ctx *ctx = (duk_trans_dvalue_ctx *) udata; unsigned char *p; #if defined(DEBUG_PRINTS) fprintf(stderr, "%s: %p %p %ld\n", __func__, udata, (void *) buffer, (long) length); fflush(stderr); #endif duk__trans_call_cooperate(ctx, 0); /* Append data. */ duk__trans_buffer_rebase(&ctx->recv_buf); p = duk__trans_buffer_ensure(&ctx->recv_buf, length); memcpy((void *) p, (const void *) buffer, (size_t) length); ctx->recv_buf.write_offset += length; /* Trial parse handshake line or dvalue(s). */ if (!ctx->handshake_done) { duk_dvalue *dv = duk__trans_trial_parse_handshake(ctx); if (dv) { /* Handshake line is available for caller for the * duration of the callback, and must not be freed * by the caller. */ duk__trans_call_handshake(ctx, (const char *) dv->buf); #if defined(DEBUG_PRINTS) fprintf(stderr, "%s: handshake ok\n", __func__); fflush(stderr); #endif duk_dvalue_free(dv); ctx->handshake_done = 1; } } if (ctx->handshake_done) { for (;;) { duk_dvalue *dv = duk__trans_trial_parse_dvalue(ctx); if (dv) { #if defined(DEBUG_PRINTS) { char buf[DUK_DVALUE_TOSTRING_BUFLEN]; duk_dvalue_to_string(dv, buf); fprintf(stderr, "%s: received dvalue: %s\n", __func__, buf); fflush(stderr); } #endif duk__trans_call_received(ctx, dv); } else { break; } } } duk__trans_call_cooperate(ctx, 0); /* just in case, if dvalues changed something */ return length; } duk_size_t duk_trans_dvalue_peek_cb(void *udata) { duk_trans_dvalue_ctx *ctx = (duk_trans_dvalue_ctx *) udata; size_t avail; #if defined(DEBUG_PRINTS) fprintf(stderr, "%s: %p\n", __func__, udata); fflush(stderr); #endif duk__trans_call_cooperate(ctx, 0); avail = (size_t) (ctx->send_buf.write_offset - ctx->send_buf.read_offset); return (duk_size_t) avail; } void duk_trans_dvalue_read_flush_cb(void *udata) { duk_trans_dvalue_ctx *ctx = (duk_trans_dvalue_ctx *) udata; #if defined(DEBUG_PRINTS) fprintf(stderr, "%s: %p\n", __func__, udata); fflush(stderr); #endif duk__trans_call_cooperate(ctx, 0); } void duk_trans_dvalue_write_flush_cb(void *udata) { duk_trans_dvalue_ctx *ctx = (duk_trans_dvalue_ctx *) udata; #if defined(DEBUG_PRINTS) fprintf(stderr, "%s: %p\n", __func__, udata); fflush(stderr); #endif duk__trans_call_cooperate(ctx, 0); } void duk_trans_dvalue_detached_cb(duk_context *duk_ctx, void *udata) { duk_trans_dvalue_ctx *ctx = (duk_trans_dvalue_ctx *) udata; (void) duk_ctx; #if defined(DEBUG_PRINTS) fprintf(stderr, "%s: %p\n", __func__, udata); fflush(stderr); #endif duk__trans_call_detached(ctx); }